UNPKG

scryptlib

Version:

Javascript SDK for integration of Bitcoin SV Smart Contracts written in sCrypt language.

478 lines 17.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkNOPScript = exports.md5 = exports.findSrcInfoV1 = exports.findSrcInfoV2 = exports.JSONStringify = exports.JSONParserSync = exports.JSONParser = exports.structSign = exports.librarySign = exports.parseAbiFromUnlockingScript = exports.buildContractCode = exports.stripAnsi = exports.ansiRegex = exports.resolveConstValue = exports.newCall = exports.compileContractAsync = exports.compileContract = exports.isEmpty = exports.readFileByLine = exports.subscript = exports.toGenericType = exports.findStructByType = exports.getNameByType = exports.isArrayType = exports.findStructByName = exports.uri2path = exports.path2uri = exports.isNode = exports.getLowSPreimage = exports.hashIsPositiveNumber = exports.getPreimage = exports.signTx = exports.hexStringToBytes = exports.bytesToHexString = exports.bytes2Literal = exports.utf82Hex = exports.toHex = exports.uint82hex = exports.asm2int = exports.int2Asm = exports.DEFAULT_SIGHASH_TYPE = exports.DEFAULT_FLAGS = exports.bsv = void 0; const json_ext_1 = require("@discoveryjs/json-ext"); const bsv = require("@scrypt-inc/bsv"); exports.bsv = bsv; const crypto = require("crypto"); const fs = require("fs"); const path_1 = require("path"); const sourcemap_codec_1 = require("@jridgewell/sourcemap-codec"); const url_1 = require("url"); const compilerWrapper_1 = require("./compilerWrapper"); const internal_1 = require("./internal"); const typeCheck_1 = require("./typeCheck"); const BN = bsv.crypto.BN; const Interp = bsv.Script.Interpreter; exports.DEFAULT_FLAGS = //Interp.SCRIPT_VERIFY_P2SH | Interp.SCRIPT_VERIFY_CLEANSTACK | // no longer applies now p2sh is deprecated: cleanstack only applies to p2sh Interp.SCRIPT_ENABLE_MAGNETIC_OPCODES | Interp.SCRIPT_ENABLE_MONOLITH_OPCODES | // TODO: to be removed after upgrade to bsv 2.0 Interp.SCRIPT_VERIFY_STRICTENC | Interp.SCRIPT_ENABLE_SIGHASH_FORKID | Interp.SCRIPT_VERIFY_LOW_S | Interp.SCRIPT_VERIFY_NULLFAIL | Interp.SCRIPT_VERIFY_DERSIG | Interp.SCRIPT_VERIFY_MINIMALDATA | Interp.SCRIPT_VERIFY_NULLDUMMY | Interp.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS | Interp.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | Interp.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY | Interp.SCRIPT_VERIFY_CLEANSTACK; exports.DEFAULT_SIGHASH_TYPE = bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID; /** * decimal or hex int to little-endian signed magnitude */ function int2Asm(str) { if (/^(-?\d+)$/.test(str) || /^0x([0-9a-fA-F]+)$/.test(str)) { const number = str.startsWith('0x') ? new BN(str.substring(2), 16) : new BN(str, 10); if (number.eqn(-1)) { return 'OP_1NEGATE'; } if (number.gten(0) && number.lten(16)) { return 'OP_' + number.toString(); } const m = number.toSM({ endian: 'little' }); return m.toString('hex'); } else { throw new Error(`invalid str '${str}' to convert to int`); } } exports.int2Asm = int2Asm; /** * convert asm string to number or bigint */ function asm2int(str) { switch (str) { case 'OP_1NEGATE': return -1; case '0': case 'OP_0': case 'OP_1': case 'OP_2': case 'OP_3': case 'OP_4': case 'OP_5': case 'OP_6': case 'OP_7': case 'OP_8': case 'OP_9': case 'OP_10': case 'OP_11': case 'OP_12': case 'OP_13': case 'OP_14': case 'OP_15': case 'OP_16': return parseInt(str.replace('OP_', '')); default: { const value = (0, internal_1.getValidatedHexString)(str); const bn = BN.fromHex(value, { endian: 'little' }); if (bn.toNumber() < Number.MAX_SAFE_INTEGER && bn.toNumber() > Number.MIN_SAFE_INTEGER) { return bn.toNumber(); } else { return bn.toString(); } } } } exports.asm2int = asm2int; function uint82hex(val) { let hex = val.toString(16); if (hex.length % 2 === 1) { hex = '0' + hex; } return hex; } exports.uint82hex = uint82hex; function toHex(x) { return x.toString('hex'); } exports.toHex = toHex; function utf82Hex(val) { const encoder = new TextEncoder(); const uint8array = encoder.encode(val); return toHex(Buffer.from(uint8array)); } exports.utf82Hex = utf82Hex; function bytes2Literal(bytearray, type) { switch (type) { case 'bool': return BN.fromBuffer(bytearray, { endian: 'little' }).gt(0) ? 'true' : 'false'; case 'int': case 'PrivKey': return BN.fromSM(bytearray, { endian: 'little' }).toString(); case 'bytes': return `b'${bytesToHexString(bytearray)}'`; default: return `b'${bytesToHexString(bytearray)}'`; } } exports.bytes2Literal = bytes2Literal; function bytesToHexString(bytearray) { return bytearray.reduce(function (o, c) { return o += ('0' + (c & 0xFF).toString(16)).slice(-2); }, ''); } exports.bytesToHexString = bytesToHexString; function hexStringToBytes(hex) { (0, internal_1.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()) .map(b => parseInt(b, 16)); } exports.hexStringToBytes = hexStringToBytes; function signTx(tx, privateKey, lockingScript, inputAmount, inputIndex = 0, sighashType = exports.DEFAULT_SIGHASH_TYPE, flags = exports.DEFAULT_FLAGS, hashCache) { if (!tx) { throw new Error('param tx can not be empty'); } if (!privateKey) { throw new Error('param privateKey can not be empty'); } if (!lockingScript) { throw new Error('param lockingScript can not be empty'); } if (!inputAmount) { throw new Error('param inputAmount can not be empty'); } if (typeof lockingScript === 'string') { throw new Error('Breaking change: LockingScript in ASM format is no longer supported, please use the lockingScript object directly'); } return toHex(bsv.Transaction.Sighash.sign(tx, privateKey, sighashType, inputIndex, lockingScript, new bsv.crypto.BN(inputAmount), flags, hashCache).toTxFormat()); } exports.signTx = signTx; function getPreimage(tx, lockingScript, inputAmount, inputIndex = 0, sighashType = exports.DEFAULT_SIGHASH_TYPE, flags = exports.DEFAULT_FLAGS) { const preimageBuf = bsv.Transaction.Sighash.sighashPreimage(tx, sighashType, inputIndex, lockingScript, new bsv.crypto.BN(inputAmount), flags); return toHex(preimageBuf); } exports.getPreimage = getPreimage; const MSB_THRESHOLD = 0x7e; function hashIsPositiveNumber(sighash) { const highByte = sighash.readUInt8(31); return highByte < MSB_THRESHOLD; } exports.hashIsPositiveNumber = hashIsPositiveNumber; function getLowSPreimage(tx, lockingScript, inputAmount, inputIndex = 0, sighashType = exports.DEFAULT_SIGHASH_TYPE, flags = exports.DEFAULT_FLAGS) { for (let i = 0; i < Number.MAX_SAFE_INTEGER; i++) { const preimage = getPreimage(tx, lockingScript, inputAmount, inputIndex, sighashType, flags); const sighash = bsv.crypto.Hash.sha256sha256(Buffer.from(preimage, 'hex')); const msb = sighash.readUInt8(); if (msb < MSB_THRESHOLD && hashIsPositiveNumber(sighash)) { return preimage; } tx.inputs[inputIndex].sequenceNumber--; } } exports.getLowSPreimage = getLowSPreimage; function isNode() { return typeof window === 'undefined' && typeof process === 'object'; } exports.isNode = isNode; function path2uri(path) { if (isNode()) { return (0, url_1.pathToFileURL)(path).toString(); } else { return path; } } exports.path2uri = path2uri; function uri2path(uri) { if (isNode()) { return (0, url_1.fileURLToPath)(uri); } else { return uri; } } exports.uri2path = uri2path; function findStructByName(name, s) { return s.find(s => { return s.name == name; }); } exports.findStructByName = findStructByName; // test Token[3], int[3], st.b.c[3] function isArrayType(type) { return /^(.+)(\[[\w.]+\])+$/.test(type); } exports.isArrayType = isArrayType; function getNameByType(type) { if (isArrayType(type)) { /* eslint-disable @typescript-eslint/no-unused-vars */ const [elemType, _] = (0, typeCheck_1.arrayTypeAndSizeStr)(type); return getNameByType(elemType); } if ((0, typeCheck_1.isGenericType)(type)) { /* eslint-disable @typescript-eslint/no-unused-vars */ const [tn, _] = (0, typeCheck_1.parseGenericType)(type); return getNameByType(tn); } return type; } exports.getNameByType = getNameByType; function findStructByType(type, s) { const name = getNameByType(type); if (name) { return findStructByName(name, s); } return undefined; } exports.findStructByType = findStructByType; function toGenericType(name, genericTypes) { return `${name}<${genericTypes.join(',')}>`; } exports.toGenericType = toGenericType; function subscript(index, arraySizes) { if (arraySizes.length == 1) { return `[${index}]`; } else { const subArraySizes = arraySizes.slice(1); const offset = subArraySizes.reduce(function (acc, val) { return acc * val; }, 1); return `[${Math.floor(index / offset)}]${subscript(index % offset, subArraySizes)}`; } } exports.subscript = subscript; function readFileByLine(path, index) { let result = ''; fs.readFileSync(path, 'utf8').split(/\r?\n/).every(function (line, i) { if (i === (index - 1)) { result = line; return false; } return true; }); return result; } exports.readFileByLine = readFileByLine; function isEmpty(obj) { return Object.keys(obj).length === 0; } exports.isEmpty = isEmpty; function compileContract(file, options) { options = Object.assign({ out: (0, path_1.join)(__dirname, '../out'), sourceMap: false, artifact: false, optimize: false, }, options); if (!fs.existsSync(file)) { throw (`file ${file} not exists!`); } if (!fs.existsSync(options.out)) { fs.mkdirSync(options.out); } const result = (0, internal_1.compile)({ path: file }, { artifact: options.artifact, outputDir: options.out, sourceMap: options.sourceMap, optimize: options.optimize, cmdPrefix: (0, internal_1.findCompiler)() }); return result; } exports.compileContract = compileContract; function compileContractAsync(file, options) { options = Object.assign({ out: (0, path_1.join)(__dirname, '..', 'out'), sourceMap: false, artifact: false, optimize: false, }, options); if (!fs.existsSync(file)) { throw (`file ${file} not exists!`); } if (!fs.existsSync(options.out)) { fs.mkdirSync(options.out); } return (0, compilerWrapper_1.compileAsync)({ path: file }, { artifact: options.artifact, outputDir: options.out, sourceMap: options.sourceMap, optimize: options.optimize, hex: true, cmdPrefix: (0, internal_1.findCompiler)() }); } exports.compileContractAsync = compileContractAsync; function newCall(Cls, args) { return new (Function.prototype.bind.apply(Cls, [null].concat(args))); } exports.newCall = newCall; function resolveConstValue(node) { let value = undefined; if (node.expr.nodeType === 'IntLiteral') { value = node.expr.value.toString(10); } else if (node.expr.nodeType === 'BoolLiteral') { value = node.expr.value; } if (node.expr.nodeType === 'BytesLiteral') { value = `b'${node.expr.value.map(a => uint82hex(a)).join('')}'`; } if (node.expr.nodeType === 'FunctionCall') { if ([internal_1.ScryptType.PUBKEY, internal_1.ScryptType.RIPEMD160, internal_1.ScryptType.SIG, internal_1.ScryptType.SIGHASHTYPE, internal_1.ScryptType.OPCODETYPE, internal_1.ScryptType.SIGHASHPREIMAGE, internal_1.ScryptType.SHA1, internal_1.ScryptType.SHA256].includes(node.expr.name)) { value = `b'${node.expr.params[0].value.map(a => uint82hex(a)).join('')}'`; } else if (node.expr.name === internal_1.ScryptType.PRIVKEY) { value = node.expr.params[0].value.toString(10); } } return value; } exports.resolveConstValue = resolveConstValue; function ansiRegex({ onlyFirst = false } = {}) { const pattern = [ '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))' ].join('|'); return new RegExp(pattern, onlyFirst ? undefined : 'g'); } exports.ansiRegex = ansiRegex; function stripAnsi(string) { if (typeof string !== 'string') { throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``); } return string.replace(ansiRegex(), ''); } exports.stripAnsi = stripAnsi; function escapeRegExp(stringToGoIntoTheRegex) { return stringToGoIntoTheRegex.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); } function buildContractCode(hexTemplateArgs, hexTemplateInlineASM, hexTemplate) { let lsHex = hexTemplate; for (const entry of hexTemplateArgs.entries()) { const name = entry[0]; const value = entry[1]; lsHex = lsHex.replace(name, value); } for (const entry of hexTemplateInlineASM.entries()) { const name = entry[0]; const value = entry[1]; lsHex = lsHex.replace(new RegExp(`${escapeRegExp(name)}`, 'g'), value); } return bsv.Script.fromHex(lsHex); } exports.buildContractCode = buildContractCode; /** * Parse out which public function is called through unlocking script * @param contract * @param hex hex of unlocking script * @returns return ABIEntity of the public function which is call by the unlocking script */ function parseAbiFromUnlockingScript(contract, hex) { const abis = Object.getPrototypeOf(contract).constructor.abi; const pubFunAbis = abis.filter(entity => entity.type === 'function'); const pubFunCount = pubFunAbis.length; if (pubFunCount === 1) { return pubFunAbis[0]; } const script = bsv.Script.fromHex(hex); const usASM = script.toASM(); const pubFuncIndexASM = usASM.substr(usASM.lastIndexOf(' ') + 1); const pubFuncIndex = asm2int(pubFuncIndexASM); const entity = abis.find(entity => entity.index === pubFuncIndex); if (!entity) { throw new Error(`the raw unlocking script cannot match the contract ${contract.contractName}`); } return entity; } exports.parseAbiFromUnlockingScript = parseAbiFromUnlockingScript; function librarySign(genericEntity) { return `[${genericEntity.params.map(p => p.type).join(',')}]`; } exports.librarySign = librarySign; function structSign(structEntity) { return `${JSON.stringify(structEntity.params.reduce((p, v) => Object.assign(p, { [v.name]: v.type }), {}), null, 4)}`; } exports.structSign = structSign; async function JSONParser(file) { return new Promise((resolve, reject) => { (0, json_ext_1.parseChunked)(fs.createReadStream(file)) .then(data => { resolve(data); }) .catch(e => { reject(e); }); }); } exports.JSONParser = JSONParser; function JSONParserSync(file) { return JSON.parse(fs.readFileSync(file, 'utf8')); } exports.JSONParserSync = JSONParserSync; async function JSONStringify(file, data) { return new Promise((resolve, reject) => { (0, json_ext_1.stringifyStream)(data) .pipe(fs.createWriteStream(file)) .on('finish', () => { resolve(true); }) .on('error', (e) => { reject(e); }); }); } exports.JSONStringify = JSONStringify; function findSrcInfoV2(pc, sourceMap) { const decoded = (0, sourcemap_codec_1.decode)(sourceMap['mappings']); for (let index = 0; index < decoded[0].length; index++) { const element = decoded[0][index]; if (element[0] <= pc) { continue; } return decoded[0][index - 1]; } return decoded[0][decoded[0].length - 1]; } exports.findSrcInfoV2 = findSrcInfoV2; /** * @deprecated use findSrcInfoV2 * @param opcodes OpCode[] from sourceMap */ function findSrcInfoV1(opcodes, opcodesIndex) { while (--opcodesIndex > 0) { if (opcodes[opcodesIndex].pos && opcodes[opcodesIndex].pos.file !== 'std' && opcodes[opcodesIndex].pos.line > 0) { return opcodes[opcodesIndex]; } } } exports.findSrcInfoV1 = findSrcInfoV1; function md5(s) { const md5 = crypto.createHash('md5'); return md5.update(s).digest('hex'); } exports.md5 = md5; function checkNOPScript(nopScript) { bsv.Script.Interpreter.MAX_SCRIPT_ELEMENT_SIZE = Number.MAX_SAFE_INTEGER; bsv.Script.Interpreter.MAXIMUM_ELEMENT_SIZE = Number.MAX_SAFE_INTEGER; const bsi = new bsv.Script.Interpreter(); const tx = new bsv.Transaction().from({ txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458', outputIndex: 0, script: '', satoshis: 1 }); const result = bsi.verify(new bsv.Script(""), nopScript, tx, 0, exports.DEFAULT_FLAGS, new bsv.crypto.BN(1)); if (result || bsi.errstr !== "SCRIPT_ERR_EVAL_FALSE_NO_RESULT") { throw new Error("NopScript should be a script that does not affect the Bitcoin virtual machine stack."); } } exports.checkNOPScript = checkNOPScript; //# sourceMappingURL=utils.js.map