UNPKG

parkingsv-contract

Version:

ParkingSV library

269 lines (218 loc) 6.58 kB
import bsv = require('bsv'); export { bsv }; const BN = bsv.crypto.BN; const Interpreter = bsv.Script.Interpreter; export const DEFAULT_FLAGS = // @ts-ignore Interpreter.SCRIPT_VERIFY_MINIMALDATA | // @ts-ignore Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID | // @ts-ignore Interpreter.SCRIPT_ENABLE_MAGNETIC_OPCODES | // @ts-ignore Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES; // @ts-ignore export const DEFAULT_SIGHASH_TYPE = // @ts-ignore bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID; export function bool2Asm(str: string): string { if (str === 'true') { return 'OP_TRUE'; } if (str === 'false') { return 'OP_FALSE'; } throw new Error(`invalid str '${str}' to convert to bool`); } /** * decimal int to little-endian signed magnitude */ export function int2Asm(str: string): string { if (!/^-?(0x)?\d+$/.test(str)) { throw new Error(`invalid str '${str}' to convert to int`); } // @ts-ignore const number = new BN(str, 10); // @ts-ignore if (number.eqn(-1)) { return 'OP_1NEGATE'; } // @ts-ignore if (number.gten(0) && number.lten(16)) { return 'OP_' + str; } // @ts-ignore const m = number.toSM({ endian: 'little' }); return m.toString('hex'); } /** * convert literals to script ASM format */ export function literal2Asm(l: string): [string, string] { // bool if (l === 'false') { return ['OP_FALSE', 'bool']; } if (l === 'true') { return ['OP_TRUE', 'bool']; } // hex int if (/^0x[0-9a-fA-F]+$/.test(l)) { return [int2Asm(l), 'int']; } // decimal int if (/^-?\d+$/.test(l)) { return [int2Asm(l), 'int']; } // bytes let m = /^b'([\da-fA-F]+)'$/.exec(l); if (m) { return [getValidatedHexString(m[1]), 'bytes']; } // PrivKey // 1) decimal int m = /^PrivKey\((-?\d+)\)$/.exec(l); if (m) { return [m[1], 'PrivKey']; } // 2) hex int m = /^PrivKey\((0x[0-9a-fA-F]+)\)$/.exec(l); if (m) { return [m[1], 'PrivKey']; } // PubKey m = /^PubKey\(b'([\da-fA-F]+)'\)$/.exec(l); if (m) { return [getValidatedHexString(m[1]), 'PubKey']; } // Sig m = /^Sig\(b'([\da-fA-F]+)'\)$/.exec(l); if (m) { return [getValidatedHexString(m[1]), 'Sig']; } // Ripemd160 m = /^Ripemd160\(b'([\da-fA-F]+)'\)$/.exec(l); if (m) { return [getValidatedHexString(m[1]), 'Ripemd160']; } // Sha1 m = /^Sha1\(b'([\da-fA-F]+)'\)$/.exec(l); if (m) { return [getValidatedHexString(m[1]), 'Sha1']; } // Sha256 m = /^Sha256\(b'([\da-fA-F]+)'\)$/.exec(l); if (m) { return [getValidatedHexString(m[1]), 'Sha256']; } // SigHashType m = /^SigHashType\(b'([\da-fA-F]+)'\)$/.exec(l); if (m) { return [getValidatedHexString(m[1]), 'SigHashType']; } // OpCodeType m = /^OpCodeType\(b'([\da-fA-F]+)'\)$/.exec(l); if (m) { return [getValidatedHexString(m[1]), 'OpCodeType']; } throw new Error(`<${l}> can't be casted to asm format, only support sCrypt native types`); } export function bytes2Literal(bytearray: number[], type: string): string { switch (type) { case 'bool': // @ts-ignore return BN.fromBuffer(bytearray, { endian: 'little' }) > 0 ? 'true' : 'false'; case 'int': // @ts-ignore return BN.fromSM(bytearray, { endian: 'little' }).toString(); case 'bytes': return `b'${bytesToHexString(bytearray)}'`; default: return `b'${bytesToHexString(bytearray)}'`; } } export function bytesToHexString(bytearray: number[]): string { return bytearray.reduce(function (o, c) { return o += ('0' + (c & 0xFF).toString(16)).slice(-2); }, ''); } export function hexStringToBytes(hex: string): number[] { getValidatedHexString(hex); return hex.split('') .reduce(function (o, c, i) { if (i % 2 === 0) { o.push(c); } else { o[o.length - 1] += c; } return o; }, new Array<string>()) .map(b => parseInt(b, 16)); } export function getValidatedHexString(hex: string, allowEmpty = true): string { const ret = hex.trim(); if (ret.length < 1 && !allowEmpty) { throw new Error("can't be empty string"); } if (ret.length % 2) { throw new Error('should have even length'); } if (ret.length > 0 && !(/^[\da-f]+$/i.test(ret))) { throw new Error('should only contain [0-9] or characters [a-fA-F]'); } return ret; } export function signTx(tx, privateKey, lockingScriptASM: string, inputAmount: number, inputIndex = 0, sighashType = DEFAULT_SIGHASH_TYPE, flags = DEFAULT_FLAGS) { if (!tx) { throw new Error('param tx can not be empty'); } if (!privateKey) { throw new Error('param privateKey can not be empty'); } if (!lockingScriptASM) { throw new Error('param lockingScriptASM can not be empty'); } if (!inputAmount) { throw new Error('param inputAmount can not be empty'); } // @ts-ignore return bsv.Transaction.sighash.sign( tx, privateKey, sighashType, inputIndex, // @ts-ignore bsv.Script.fromASM(lockingScriptASM), new bsv.crypto.BN(inputAmount), flags ).toTxFormat(); } export function toHex(x: { toString(format: 'hex'): string }): string { return x.toString('hex'); } export function getPreimage(tx, inputLockingScriptASM: string, inputAmount: number, inputIndex = 0, sighashType = DEFAULT_SIGHASH_TYPE, flags = DEFAULT_FLAGS) { // @ts-ignore return bsv.Transaction.sighash.sighashPreimage(tx, sighashType, inputIndex, bsv.Script.fromASM(inputLockingScriptASM), new bsv.crypto.BN(inputAmount), flags); } // Converts a number into a sign-magnitude representation of certain size as a string // Throws if the number cannot be accommodated // Often used to append numbers to OP_RETURN, which are read in contracts // TODO: handle bigint export function num2bin(n: number, dataLen: number): string { if (n === 0) { return "00".repeat(dataLen); } // @ts-ignore const num = BN.fromNumber(n); const s = num.toSM({ endian: 'little' }).toString('hex'); const byteLen_ = s.length / 2; if (byteLen_ > dataLen) { throw new Error(`${n} cannot fit in ${dataLen} byte[s]`); } if (byteLen_ === dataLen) { return s; } const paddingLen = dataLen - byteLen_; const lastByte = s.substring(s.length - 2); const rest = s.substring(0, s.length - 2); let m = parseInt(lastByte, 16); if (n < 0) { // reset sign bit m &= 0x7F; } let mHex = m.toString(16); if (mHex.length < 2) { mHex = '0' + mHex; } const padding = n > 0 ? '00'.repeat(paddingLen) : '00'.repeat(paddingLen - 1) + '80'; return rest + mHex + padding; }