UNPKG

@scrypt-inc/bitcoinjs-lib

Version:
630 lines (629 loc) 25.6 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.Transaction = void 0; exports.isFinal = isFinal; exports.isNullInput = isNullInput; exports.varSliceSize = varSliceSize; exports.vectorSize = vectorSize; exports.readOutput = readOutput; const bufferutils_js_1 = require('./bufferutils.cjs'); const bcrypto = __importStar(require('./crypto.cjs')); const sha256_1 = require("@noble/hashes/sha256"); const bscript = __importStar(require('./script.cjs')); const script_js_1 = require('./script.cjs'); const types = __importStar(require('./types.cjs')); const tools = __importStar(require("uint8array-tools")); const v = __importStar(require("valibot")); const MAXINT = 0xffffffff; // Math.pow(2, 32) - 1; function isFinal(sequenceNumber) { return sequenceNumber !== MAXINT; } function isNullInput(input) { return tools.compare(input.hash, ZERO) === 0 && input.index === 0xffffffff; } function varSliceSize(someScript) { const length = someScript.length; return bufferutils_js_1.varuint.encodingLength(length) + length; } function vectorSize(someVector) { const length = someVector.length; return (bufferutils_js_1.varuint.encodingLength(length) + someVector.reduce((sum, witness) => { return sum + varSliceSize(witness); }, 0)); } const EMPTY_BUFFER = new Uint8Array(0); const EMPTY_WITNESS = []; const ZERO = tools.fromHex('0000000000000000000000000000000000000000000000000000000000000000'); const ONE = tools.fromHex('0100000000000000000000000000000000000000000000000000000000000000'); const VALUE_UINT64_MAX = tools.fromHex('ffffffffffffffff'); const BLANK_OUTPUT = { script: EMPTY_BUFFER, valueBuffer: VALUE_UINT64_MAX, }; function isOutput(out) { return out.value !== undefined; } function readOutput(buffer) { const bufferReader = new bufferutils_js_1.BufferReader(buffer); return { value: bufferReader.readInt64(), script: bufferReader.readVarSlice(), }; } /** * Represents a Bitcoin transaction. */ class Transaction { static DEFAULT_SEQUENCE = 0xffffffff; static SIGHASH_DEFAULT = 0x00; static SIGHASH_ALL = 0x01; static SIGHASH_NONE = 0x02; static SIGHASH_SINGLE = 0x03; static SIGHASH_ANYONECANPAY = 0x80; static SIGHASH_OUTPUT_MASK = 0x03; static SIGHASH_INPUT_MASK = 0x80; static ADVANCED_TRANSACTION_MARKER = 0x00; static ADVANCED_TRANSACTION_FLAG = 0x01; static fromBuffer(buffer, _NO_STRICT) { const bufferReader = new bufferutils_js_1.BufferReader(buffer); const tx = new Transaction(); tx.version = bufferReader.readInt32(); const marker = bufferReader.readUInt8(); const flag = bufferReader.readUInt8(); let hasWitnesses = false; if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && flag === Transaction.ADVANCED_TRANSACTION_FLAG) { hasWitnesses = true; } else { bufferReader.offset -= 2; } const vinLen = bufferReader.readVarInt(); for (let i = 0; i < vinLen; ++i) { tx.ins.push({ hash: bufferReader.readSlice(32), index: bufferReader.readUInt32(), script: bufferReader.readVarSlice(), sequence: bufferReader.readUInt32(), witness: EMPTY_WITNESS, }); } const voutLen = bufferReader.readVarInt(); for (let i = 0; i < voutLen; ++i) { tx.outs.push({ value: bufferReader.readInt64(), script: bufferReader.readVarSlice(), }); } if (hasWitnesses) { for (let i = 0; i < vinLen; ++i) { tx.ins[i].witness = bufferReader.readVector(); } // was this pointless? if (!tx.hasWitnesses()) throw new Error('Transaction has superfluous witness data'); } tx.locktime = bufferReader.readUInt32(); if (_NO_STRICT) return tx; if (bufferReader.offset !== buffer.length) throw new Error('Transaction has unexpected data'); return tx; } static fromHex(hex) { return Transaction.fromBuffer(tools.fromHex(hex), false); } static isCoinbaseHash(buffer) { v.parse(types.Hash256bitSchema, buffer); for (let i = 0; i < 32; ++i) { if (buffer[i] !== 0) return false; } return true; } version = 1; locktime = 0; ins = []; outs = []; isCoinbase() { return (this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)); } addInput(hash, index, sequence, scriptSig) { v.parse(v.tuple([ types.Hash256bitSchema, types.UInt32Schema, v.nullable(v.optional(types.UInt32Schema)), v.nullable(v.optional(types.BufferSchema)), ]), [hash, index, sequence, scriptSig]); if (sequence === undefined || sequence === null) { sequence = Transaction.DEFAULT_SEQUENCE; } // Add the input and return the input's index return (this.ins.push({ hash, index, script: scriptSig || EMPTY_BUFFER, sequence: sequence, witness: EMPTY_WITNESS, }) - 1); } addOutput(scriptPubKey, value) { v.parse(v.tuple([types.BufferSchema, types.SatoshiSchema]), [ scriptPubKey, value, ]); // Add the output and return the output's index return (this.outs.push({ script: scriptPubKey, value, }) - 1); } hasWitnesses() { return this.ins.some(x => { return x.witness.length !== 0; }); } weight() { const base = this.byteLength(false); const total = this.byteLength(true); return base * 3 + total; } virtualSize() { return Math.ceil(this.weight() / 4); } byteLength(_ALLOW_WITNESS = true) { const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); return ((hasWitnesses ? 10 : 8) + bufferutils_js_1.varuint.encodingLength(this.ins.length) + bufferutils_js_1.varuint.encodingLength(this.outs.length) + this.ins.reduce((sum, input) => { return sum + 40 + varSliceSize(input.script); }, 0) + this.outs.reduce((sum, output) => { return sum + 8 + varSliceSize(output.script); }, 0) + (hasWitnesses ? this.ins.reduce((sum, input) => { return sum + vectorSize(input.witness); }, 0) : 0)); } clone() { const newTx = new Transaction(); newTx.version = this.version; newTx.locktime = this.locktime; newTx.ins = this.ins.map(txIn => { return { hash: txIn.hash, index: txIn.index, script: txIn.script, sequence: txIn.sequence, witness: txIn.witness, }; }); newTx.outs = this.outs.map(txOut => { return { script: txOut.script, value: txOut.value, }; }); return newTx; } /** * Hash transaction for signing a specific input. * * Bitcoin uses a different hash for each signed transaction input. * This method copies the transaction, makes the necessary changes based on the * hashType, and then hashes the result. * This hash can then be used to sign the provided transaction input. */ hashForSignature(inIndex, prevOutScript, hashType) { v.parse(v.tuple([types.UInt32Schema, types.BufferSchema, v.number()]), [ inIndex, prevOutScript, hashType, ]); // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 if (inIndex >= this.ins.length) return ONE; // ignore OP_CODESEPARATOR const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(x => { return x !== script_js_1.OPS.OP_CODESEPARATOR; })); const txTmp = this.clone(); // SIGHASH_NONE: ignore all outputs? (wildcard payee) if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { txTmp.outs = []; // ignore sequence numbers (except at inIndex) txTmp.ins.forEach((input, i) => { if (i === inIndex) return; input.sequence = 0; }); // SIGHASH_SINGLE: ignore all outputs, except at the same index? } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 if (inIndex >= this.outs.length) return ONE; // truncate outputs after txTmp.outs.length = inIndex + 1; // "blank" outputs before for (let i = 0; i < inIndex; i++) { txTmp.outs[i] = BLANK_OUTPUT; } // ignore sequence numbers (except at inIndex) txTmp.ins.forEach((input, y) => { if (y === inIndex) return; input.sequence = 0; }); } // SIGHASH_ANYONECANPAY: ignore inputs entirely? if (hashType & Transaction.SIGHASH_ANYONECANPAY) { txTmp.ins = [txTmp.ins[inIndex]]; txTmp.ins[0].script = ourScript; // SIGHASH_ALL: only ignore input scripts } else { // "blank" others input scripts txTmp.ins.forEach(input => { input.script = EMPTY_BUFFER; }); txTmp.ins[inIndex].script = ourScript; } // serialize and hash const buffer = new Uint8Array(txTmp.byteLength(false) + 4); tools.writeInt32(buffer, buffer.length - 4, hashType, 'LE'); txTmp.__toBuffer(buffer, 0, false); return bcrypto.hash256(buffer); } hashForWitnessV1(inIndex, prevOutScripts, values, hashType, leafHash, annex, codeseparatorPos) { const preimage = this.shPreimageForWitnessV1(inIndex, prevOutScripts, values, hashType, leafHash, annex, codeseparatorPos); return (0, sha256_1.sha256)(preimage); } shPreimageForWitnessV1(inIndex, prevOutScripts, values, hashType, leafHash, annex, codeseparatorPos) { // https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#common-signature-message v.parse(v.tuple([ types.UInt32Schema, v.array(types.BufferSchema), v.array(types.SatoshiSchema), types.UInt32Schema, ]), [inIndex, prevOutScripts, values, hashType]); if (values.length !== this.ins.length || prevOutScripts.length !== this.ins.length) { throw new Error('Must supply prevout script and value for all inputs'); } const outputType = hashType === Transaction.SIGHASH_DEFAULT ? Transaction.SIGHASH_ALL : hashType & Transaction.SIGHASH_OUTPUT_MASK; const inputType = hashType & Transaction.SIGHASH_INPUT_MASK; const isAnyoneCanPay = inputType === Transaction.SIGHASH_ANYONECANPAY; const isNone = outputType === Transaction.SIGHASH_NONE; const isSingle = outputType === Transaction.SIGHASH_SINGLE; let hashPrevouts = EMPTY_BUFFER; let hashAmounts = EMPTY_BUFFER; let hashScriptPubKeys = EMPTY_BUFFER; let hashSequences = EMPTY_BUFFER; let hashOutputs = EMPTY_BUFFER; if (!isAnyoneCanPay) { let bufferWriter = bufferutils_js_1.BufferWriter.withCapacity(36 * this.ins.length); this.ins.forEach(txIn => { bufferWriter.writeSlice(txIn.hash); bufferWriter.writeUInt32(txIn.index); }); hashPrevouts = (0, sha256_1.sha256)(bufferWriter.end()); bufferWriter = bufferutils_js_1.BufferWriter.withCapacity(8 * this.ins.length); values.forEach(value => bufferWriter.writeInt64(value)); hashAmounts = (0, sha256_1.sha256)(bufferWriter.end()); bufferWriter = bufferutils_js_1.BufferWriter.withCapacity(prevOutScripts.map(varSliceSize).reduce((a, b) => a + b)); prevOutScripts.forEach(prevOutScript => bufferWriter.writeVarSlice(prevOutScript)); hashScriptPubKeys = (0, sha256_1.sha256)(bufferWriter.end()); bufferWriter = bufferutils_js_1.BufferWriter.withCapacity(4 * this.ins.length); this.ins.forEach(txIn => bufferWriter.writeUInt32(txIn.sequence)); hashSequences = (0, sha256_1.sha256)(bufferWriter.end()); } if (!(isNone || isSingle)) { if (!this.outs.length) throw new Error('Add outputs to the transaction before signing.'); const txOutsSize = this.outs .map(output => 8 + varSliceSize(output.script)) .reduce((a, b) => a + b); const bufferWriter = bufferutils_js_1.BufferWriter.withCapacity(txOutsSize); this.outs.forEach(out => { bufferWriter.writeInt64(out.value); bufferWriter.writeVarSlice(out.script); }); hashOutputs = (0, sha256_1.sha256)(bufferWriter.end()); } else if (isSingle && inIndex < this.outs.length) { const output = this.outs[inIndex]; const bufferWriter = bufferutils_js_1.BufferWriter.withCapacity(8 + varSliceSize(output.script)); bufferWriter.writeInt64(output.value); bufferWriter.writeVarSlice(output.script); hashOutputs = (0, sha256_1.sha256)(bufferWriter.end()); } const spendType = (leafHash ? 2 : 0) + (annex ? 1 : 0); // Length calculation from: // https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-14 // With extension from: // https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#signature-validation const sigMsgSize = 174 - (isAnyoneCanPay ? 49 : 0) - (isNone ? 32 : 0) + (annex ? 32 : 0) + (leafHash ? 37 : 0); const sigMsgWriter = bufferutils_js_1.BufferWriter.withCapacity(sigMsgSize); sigMsgWriter.writeUInt8(hashType); // Transaction sigMsgWriter.writeInt32(this.version); sigMsgWriter.writeUInt32(this.locktime); sigMsgWriter.writeSlice(hashPrevouts); sigMsgWriter.writeSlice(hashAmounts); sigMsgWriter.writeSlice(hashScriptPubKeys); sigMsgWriter.writeSlice(hashSequences); if (!(isNone || isSingle)) { sigMsgWriter.writeSlice(hashOutputs); } // Input sigMsgWriter.writeUInt8(spendType); if (isAnyoneCanPay) { const input = this.ins[inIndex]; sigMsgWriter.writeSlice(input.hash); sigMsgWriter.writeUInt32(input.index); sigMsgWriter.writeInt64(values[inIndex]); sigMsgWriter.writeVarSlice(prevOutScripts[inIndex]); sigMsgWriter.writeUInt32(input.sequence); } else { sigMsgWriter.writeUInt32(inIndex); } if (annex) { const bufferWriter = bufferutils_js_1.BufferWriter.withCapacity(varSliceSize(annex)); bufferWriter.writeVarSlice(annex); sigMsgWriter.writeSlice((0, sha256_1.sha256)(bufferWriter.end())); } // Output if (isSingle) { sigMsgWriter.writeSlice(hashOutputs); } // BIP342 extension if (leafHash) { sigMsgWriter.writeSlice(leafHash); sigMsgWriter.writeUInt8(0); sigMsgWriter.writeUInt32(typeof codeseparatorPos === 'number' ? codeseparatorPos : 0xffffffff); } // Extra zero byte because: // https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-19 return tools.concat([ bcrypto.TAGGED_HASH_PREFIXES.TapSighash, Uint8Array.from([0x00]), sigMsgWriter.end(), ]); } hashForWitnessV0(inIndex, prevOutScript, value, hashType) { v.parse(v.tuple([ types.UInt32Schema, types.BufferSchema, types.SatoshiSchema, types.UInt32Schema, ]), [inIndex, prevOutScript, value, hashType]); let tbuffer = Uint8Array.from([]); let bufferWriter; let hashOutputs = ZERO; let hashPrevouts = ZERO; let hashSequence = ZERO; if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { tbuffer = new Uint8Array(36 * this.ins.length); bufferWriter = new bufferutils_js_1.BufferWriter(tbuffer, 0); this.ins.forEach(txIn => { bufferWriter.writeSlice(txIn.hash); bufferWriter.writeUInt32(txIn.index); }); hashPrevouts = bcrypto.hash256(tbuffer); } if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) && (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { tbuffer = new Uint8Array(4 * this.ins.length); bufferWriter = new bufferutils_js_1.BufferWriter(tbuffer, 0); this.ins.forEach(txIn => { bufferWriter.writeUInt32(txIn.sequence); }); hashSequence = bcrypto.hash256(tbuffer); } if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { const txOutsSize = this.outs.reduce((sum, output) => { return sum + 8 + varSliceSize(output.script); }, 0); tbuffer = new Uint8Array(txOutsSize); bufferWriter = new bufferutils_js_1.BufferWriter(tbuffer, 0); this.outs.forEach(out => { bufferWriter.writeInt64(out.value); bufferWriter.writeVarSlice(out.script); }); hashOutputs = bcrypto.hash256(tbuffer); } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) { const output = this.outs[inIndex]; tbuffer = new Uint8Array(8 + varSliceSize(output.script)); bufferWriter = new bufferutils_js_1.BufferWriter(tbuffer, 0); bufferWriter.writeInt64(output.value); bufferWriter.writeVarSlice(output.script); hashOutputs = bcrypto.hash256(tbuffer); } tbuffer = new Uint8Array(156 + varSliceSize(prevOutScript)); bufferWriter = new bufferutils_js_1.BufferWriter(tbuffer, 0); const input = this.ins[inIndex]; bufferWriter.writeInt32(this.version); bufferWriter.writeSlice(hashPrevouts); bufferWriter.writeSlice(hashSequence); bufferWriter.writeSlice(input.hash); bufferWriter.writeUInt32(input.index); bufferWriter.writeVarSlice(prevOutScript); bufferWriter.writeInt64(value); bufferWriter.writeUInt32(input.sequence); bufferWriter.writeSlice(hashOutputs); bufferWriter.writeUInt32(this.locktime); bufferWriter.writeUInt32(hashType); return bcrypto.hash256(tbuffer); } getHash(forWitness) { // wtxid for coinbase is always 32 bytes of 0x00 if (forWitness && this.isCoinbase()) return new Uint8Array(32); return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness)); } getId() { // transaction hash's are displayed in reverse order return tools.toHex((0, bufferutils_js_1.reverseBuffer)(this.getHash(false))); } toBuffer(buffer, initialOffset, _ALLOW_WITNESS = true) { return this.__toBuffer(buffer, initialOffset, _ALLOW_WITNESS); } toHex() { return tools.toHex(this.toBuffer(undefined, undefined)); } setInputScript(index, scriptSig) { v.parse(v.tuple([v.number(), types.BufferSchema]), [index, scriptSig]); this.ins[index].script = scriptSig; } setWitness(index, witness) { v.parse(v.tuple([v.number(), v.array(types.BufferSchema)]), [ index, witness, ]); this.ins[index].witness = witness; } __toBuffer(buffer, initialOffset, _ALLOW_WITNESS = false) { if (!buffer) buffer = new Uint8Array(this.byteLength(_ALLOW_WITNESS)); const bufferWriter = new bufferutils_js_1.BufferWriter(buffer, initialOffset || 0); bufferWriter.writeInt32(this.version); const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); if (hasWitnesses) { bufferWriter.writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER); bufferWriter.writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); } bufferWriter.writeVarInt(this.ins.length); this.ins.forEach(txIn => { bufferWriter.writeSlice(txIn.hash); bufferWriter.writeUInt32(txIn.index); bufferWriter.writeVarSlice(txIn.script); bufferWriter.writeUInt32(txIn.sequence); }); bufferWriter.writeVarInt(this.outs.length); this.outs.forEach(txOut => { if (isOutput(txOut)) { bufferWriter.writeInt64(txOut.value); } else { bufferWriter.writeSlice(txOut.valueBuffer); } bufferWriter.writeVarSlice(txOut.script); }); if (hasWitnesses) { this.ins.forEach(input => { bufferWriter.writeVector(input.witness); }); } bufferWriter.writeUInt32(this.locktime); // avoid slicing unless necessary if (initialOffset !== undefined) return buffer.slice(initialOffset, bufferWriter.offset); return buffer; } /** * Check that a transaction passes basic sanity tests. If not, return a string * describing the error. This function contains the same logic as * CheckTransaction in bitcoin core. */ verify() { // Basic checks that don't depend on any context if (this.ins.length === 0) { return 'transaction txins empty'; } if (this.outs.length === 0) { return 'transaction txouts empty'; } // Check for negative or overflow output values let valueoutbn = BigInt(0); for (let i = 0; i < this.outs.length; i++) { const txout = this.outs[i]; if (invalidSatoshis(txout.value)) { return 'transaction txout ' + i + ' satoshis is invalid'; } valueoutbn += txout.value; if (invalidSatoshis(valueoutbn)) { return ('transaction txout ' + i + ' total output greater than MAX_MONEY'); } } const MAX_BLOCK_SIZE = 1000000; // Size limits if (this.toBuffer().length > MAX_BLOCK_SIZE) { return 'transaction over the maximum block size'; } // Check for duplicate inputs const txinmap = {}; for (let i = 0; i < this.ins.length; i++) { const txin = this.ins[i]; const inputid = tools.toHex(txin.hash) + ':' + txin.index; if (txinmap[inputid] === true) { return 'transaction input ' + i + ' duplicate input'; } txinmap[inputid] = true; } const isCoinbase = this.isCoinbase(); if (isCoinbase) { const buf = this.ins[0].script; if (buf.length < 2 || buf.length > 100) { return 'coinbase transaction script size invalid'; } } else { for (let i = 0; i < this.ins.length; i++) { if (isNullInput(this.ins[i])) { return 'transaction input ' + i + ' has null input'; } } } return true; } } exports.Transaction = Transaction; const MAX_MONEY = BigInt(21000000 * 1e8); function invalidSatoshis(value) { if (value > MAX_MONEY) { return true; } if (value < BigInt(0)) { return true; } return false; }