UNPKG

@scrypt-inc/bitcoinjs-lib

Version:
399 lines (398 loc) 13.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.signature = exports.number = exports.OPS = void 0; exports.isPushOnly = isPushOnly; exports.isScriptHashOut = isScriptHashOut; exports.countNonPushOnlyOPs = countNonPushOnlyOPs; exports.asMinimalOP = asMinimalOP; exports.compile = compile; exports.decompile = decompile; exports.checkMinimalPush = checkMinimalPush; exports.toASM = toASM; exports.fromASM = fromASM; exports.toStack = toStack; exports.isCanonicalPubKey = isCanonicalPubKey; exports.isUncompressedPubkey = isUncompressedPubkey; exports.isDefinedHashType = isDefinedHashType; exports.isCanonicalScriptSignature = isCanonicalScriptSignature; exports.isMinimalPush = isMinimalPush; exports.createWitnessProgram = createWitnessProgram; exports.findAndDelete = findAndDelete; /** * Script tools, including decompile, compile, toASM, fromASM, toStack, isCanonicalPubKey, isCanonicalScriptSignature * @packageDocumentation */ const bip66 = __importStar(require('./bip66.cjs')); const ops_js_1 = require('./ops.cjs'); Object.defineProperty(exports, "OPS", { enumerable: true, get: function () { return ops_js_1.OPS; } }); const pushdata = __importStar(require('./push_data.cjs')); const scriptNumber = __importStar(require('./script_number.cjs')); const scriptSignature = __importStar(require('./script_signature.cjs')); const types = __importStar(require('./types.cjs')); const tools = __importStar(require("uint8array-tools")); const v = __importStar(require("valibot")); const OP_INT_BASE = ops_js_1.OPS.OP_RESERVED; // OP_1 - 1 const StackSchema = v.array(v.union([v.instance(Uint8Array), v.number()])); function isOPInt(value) { return (v.is(v.number(), value) && (value === ops_js_1.OPS.OP_0 || (value >= ops_js_1.OPS.OP_1 && value <= ops_js_1.OPS.OP_16) || value === ops_js_1.OPS.OP_1NEGATE)); } function isPushOnlyChunk(value) { return v.is(types.BufferSchema, value) || isOPInt(value); } function isPushOnly(value) { if (value instanceof Uint8Array) { const res = decompile(value); if (res === null) { throw new Error('script is invalid!'); } value = res; } return v.is(v.pipe(v.any(), v.everyItem(isPushOnlyChunk)), value); } function isScriptHashOut(value) { return (value.length === 23 && value[0] === ops_js_1.OPS.OP_HASH160 && value[1] === 0x14 && value[value.length - 1] === ops_js_1.OPS.OP_EQUAL); } function countNonPushOnlyOPs(value) { return value.length - value.filter(isPushOnlyChunk).length; } function asMinimalOP(buffer) { if (buffer.length === 0) return ops_js_1.OPS.OP_0; if (buffer.length !== 1) return; if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]; if (buffer[0] === 0x81) return ops_js_1.OPS.OP_1NEGATE; } function chunksIsBuffer(buf) { return buf instanceof Uint8Array; } function chunksIsArray(buf) { return v.is(StackSchema, buf); } function singleChunkIsBuffer(buf) { return buf instanceof Uint8Array; } /** * Compiles an array of chunks into a Buffer. * * @param chunks - The array of chunks to compile. * @returns The compiled Buffer. * @throws Error if the compilation fails. */ function compile(chunks) { // TODO: remove me if (chunksIsBuffer(chunks)) return chunks; v.parse(StackSchema, chunks); const bufferSize = chunks.reduce((accum, chunk) => { // data chunk if (singleChunkIsBuffer(chunk)) { // adhere to BIP62.3, minimal push policy if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { return accum + 1; } return accum + pushdata.encodingLength(chunk.length) + chunk.length; } // opcode return accum + 1; }, 0.0); const buffer = new Uint8Array(bufferSize); let offset = 0; chunks.forEach(chunk => { // data chunk if (singleChunkIsBuffer(chunk)) { // adhere to BIP62.3, minimal push policy const opcode = asMinimalOP(chunk); if (opcode !== undefined) { tools.writeUInt8(buffer, offset, opcode); offset += 1; return; } offset += pushdata.encode(buffer, chunk.length, offset); buffer.set(chunk, offset); offset += chunk.length; // opcode } else { tools.writeUInt8(buffer, offset, chunk); offset += 1; } }); if (offset !== buffer.length) throw new Error('Could not decode chunks'); return buffer; } function decompile(buffer, fRequireMinimal = false) { // TODO: remove me if (chunksIsArray(buffer)) return buffer; v.parse(types.BufferSchema, buffer); const chunks = []; let i = 0; while (i < buffer.length) { const opcode = buffer[i]; // data chunk if (opcode > ops_js_1.OPS.OP_0 && opcode <= ops_js_1.OPS.OP_PUSHDATA4) { const d = pushdata.decode(buffer, i); // did reading a pushDataInt fail? if (d === null) return null; i += d.size; // attempt to read too much data? if (i + d.number > buffer.length) return null; const data = buffer.slice(i, i + d.number); i += d.number; if (fRequireMinimal && !checkMinimalPush(opcode, data)) { return null; } // decompile minimally const op = asMinimalOP(data); if (op !== undefined) { chunks.push(op); } else { chunks.push(data); } // opcode } else { chunks.push(opcode); if ((0, ops_js_1.isOpSuccess)(opcode)) { chunks.push(buffer.slice(i + 1)); break; } i += 1; } } return chunks; } /** * Comes from bitcoind's script interpreter CheckMinimalPush function * @returns {boolean} if the chunk {i} is the smallest way to push that particular data. */ function checkMinimalPush(opcodenum, buf) { if (buf.length === 0) { // Could have used OP_0. return opcodenum === ops_js_1.OPS.OP_0; } else if (buf.length === 1 && buf[0] >= 1 && buf[0] <= 16) { // Could have used OP_1 .. OP_16. return opcodenum === ops_js_1.OPS.OP_1 + (buf[0] - 1); } else if (buf.length === 1 && buf[0] === 0x81) { // Could have used OP_1NEGATE return opcodenum === ops_js_1.OPS.OP_1NEGATE; } else if (buf.length <= 75) { // Could have used a direct push (opcode indicating number of bytes pushed + those bytes). return opcodenum === buf.length; } else if (buf.length <= 255) { // Could have used OP_PUSHDATA. return opcodenum === ops_js_1.OPS.OP_PUSHDATA1; } else if (buf.length <= 65535) { // Could have used OP_PUSHDATA2. return opcodenum === ops_js_1.OPS.OP_PUSHDATA2; } return true; } /** * Converts the given chunks into an ASM (Assembly) string representation. * If the chunks parameter is a Buffer, it will be decompiled into a Stack before conversion. * @param chunks - The chunks to convert into ASM. * @returns The ASM string representation of the chunks. */ function toASM(chunks) { if (chunksIsBuffer(chunks)) { chunks = decompile(chunks); } if (!chunks) { throw new Error('Could not convert invalid chunks to ASM'); } return chunks .map(chunk => { // data? if (singleChunkIsBuffer(chunk)) { const op = asMinimalOP(chunk); if (op === undefined) return tools.toHex(chunk); chunk = op; } // opcode! return ops_js_1.REVERSE_OPS[chunk]; }) .join(' '); } /** * Converts an ASM string to a Buffer. * @param asm The ASM string to convert. * @returns The converted Buffer. */ function fromASM(asm) { v.parse(v.string(), asm); if (asm === '') { return Uint8Array.from([]); } return compile(asm.split(' ').map(chunkStr => { // opcode? if (ops_js_1.OPS[chunkStr] !== undefined) return ops_js_1.OPS[chunkStr]; try { v.parse(types.HexSchema, chunkStr); // data! return tools.fromHex(chunkStr); } catch (error) { } v.parse(types.x0HexSchema, chunkStr); // data! return tools.fromHex(chunkStr.slice(2)); })); } /** * Converts the given chunks into a stack of buffers. * * @param chunks - The chunks to convert. * @returns The stack of buffers. */ function toStack(chunks) { chunks = decompile(chunks); v.parse(v.custom(isPushOnly), chunks); return chunks.map(op => { if (singleChunkIsBuffer(op)) return op; if (op === ops_js_1.OPS.OP_0) return new Uint8Array(0); return scriptNumber.encode(op - OP_INT_BASE); }); } function isCanonicalPubKey(buffer) { return types.isPoint(buffer); } function isUncompressedPubkey(buffer) { if (buffer instanceof Uint8Array && buffer.length === 65 && buffer[0] === 0x04 && types.isPoint(buffer)) { return true; } else { return false; } } function isDefinedHashType(hashType) { const hashTypeMod = hashType & ~0x80; return hashTypeMod > 0x00 && hashTypeMod < 0x04; } function isCanonicalScriptSignature(buffer) { if (!(buffer instanceof Uint8Array)) return false; if (!isDefinedHashType(buffer[buffer.length - 1])) return false; return bip66.check(buffer.slice(0, -1)); } function isMinimalPush(opcodenum, buf) { if (buf.length === 0) { // Could have used OP_0. return opcodenum === ops_js_1.OPS.OP_0; } else if (buf.length === 1 && buf[0] >= 1 && buf[0] <= 16) { // Could have used OP_1 .. OP_16. return opcodenum === ops_js_1.OPS.OP_1 + (buf[0] - 1); } else if (buf.length === 1 && buf[0] === 0x81) { // Could have used OP_1NEGATE return opcodenum === ops_js_1.OPS.OP_1NEGATE; } else if (buf.length <= 75) { // Could have used a direct push (opcode indicating number of bytes pushed + those bytes). return opcodenum === buf.length; } else if (buf.length <= 255) { // Could have used OP_PUSHDATA. return opcodenum === ops_js_1.OPS.OP_PUSHDATA1; } else if (buf.length <= 65535) { // Could have used OP_PUSHDATA2. return opcodenum === ops_js_1.OPS.OP_PUSHDATA2; } return true; } function decodeOpN(opcode) { if (opcode === ops_js_1.OPS.OP_0) { return 0; } return opcode - (ops_js_1.OPS.OP_1 - 1); } function createWitnessProgram(buf) { if (buf.length < 4 || buf.length > 42) { return false; } if (buf[0] !== ops_js_1.OPS.OP_0 && !(buf[0] >= ops_js_1.OPS.OP_1 && buf[0] <= ops_js_1.OPS.OP_16)) { return false; } if (buf.length === buf[1] + 2) { return { version: decodeOpN(buf[0]), program: buf.slice(2, buf.length), }; } return false; } /** * Analogous to bitcoind's FindAndDelete. Find and delete equivalent chunks, * typically used with push data chunks. Note that this will find and delete * not just the same data, but the same data with the same push data op as * produced by default. i.e., if a pushdata in a tx does not use the minimal * pushdata op, then when you try to remove the data it is pushing, it will not * be removed, because they do not use the same pushdata op. */ function findAndDelete(script, subScript) { let nFound = 0; if (subScript.length === 0) { return nFound; } let pc = 0; do { const chunk = script[pc]; if (chunk instanceof Uint8Array && tools.compare(chunk, subScript) === 0) { script.splice(pc, 1); ++nFound; } } while (pc++ < script.length); return nFound; } exports.number = scriptNumber; exports.signature = scriptSignature;