scryptlib
Version:
Javascript SDK for integration of Bitcoin SV Smart Contracts written in sCrypt language.
478 lines • 17.9 kB
JavaScript
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
;