From e28e04427e0ba072cf4af5b08300eaa45aadcfcd Mon Sep 17 00:00:00 2001 From: junderw Date: Sun, 21 Apr 2019 21:30:21 +0900 Subject: [PATCH] Use Prettier to make JS easier to read/audit --- package.json | 5 +- src/address.js | 145 +-- src/block.js | 442 +++---- src/bufferutils.js | 58 +- src/classify.js | 110 +- src/crypto.js | 39 +- src/ecpair.js | 149 ++- src/index.js | 26 +- src/networks.js | 58 +- src/payments/embed.js | 82 +- src/payments/index.js | 18 +- src/payments/lazy.js | 47 +- src/payments/p2ms.js | 254 ++-- src/payments/p2pk.js | 129 +- src/payments/p2pkh.js | 254 ++-- src/payments/p2sh.js | 331 +++-- src/payments/p2wpkh.js | 242 ++-- src/payments/p2wsh.js | 321 +++-- src/script.js | 264 ++-- src/script_number.js | 108 +- src/script_signature.js | 77 +- src/templates/multisig/index.js | 8 +- src/templates/multisig/input.js | 30 +- src/templates/multisig/output.js | 49 +- src/templates/nulldata.js | 12 +- src/templates/pubkey/index.js | 8 +- src/templates/pubkey/input.js | 13 +- src/templates/pubkey/output.js | 20 +- src/templates/pubkeyhash/index.js | 8 +- src/templates/pubkeyhash/input.js | 18 +- src/templates/pubkeyhash/output.js | 26 +- src/templates/scripthash/index.js | 8 +- src/templates/scripthash/input.js | 82 +- src/templates/scripthash/output.js | 22 +- src/templates/witnesscommitment/index.js | 6 +- src/templates/witnesscommitment/output.js | 38 +- src/templates/witnesspubkeyhash/index.js | 8 +- src/templates/witnesspubkeyhash/input.js | 20 +- src/templates/witnesspubkeyhash/output.js | 18 +- src/templates/witnessscripthash/index.js | 8 +- src/templates/witnessscripthash/input.js | 61 +- src/templates/witnessscripthash/output.js | 18 +- src/transaction.js | 840 +++++++------ src/transaction_builder.js | 1387 +++++++++++---------- src/types.js | 28 +- 45 files changed, 2947 insertions(+), 2948 deletions(-) diff --git a/package.json b/package.json index 591e98a..d9fe283 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,13 @@ "bitcoinjs" ], "scripts": { - "build": "tsc -p ./tsconfig.json", + "build": "npm run clean && tsc -p ./tsconfig.json && npm run formatjs", + "clean": "rm -rf src/", "coverage-report": "npm run build && npm run nobuild:coverage-report", "coverage-html": "npm run build && npm run nobuild:coverage-html", "coverage": "npm run build && npm run nobuild:coverage", "format": "npm run prettier -- --write", + "formatjs": "npm run prettierjs -- --write > /dev/null 2>&1", "format:ci": "npm run prettier -- --check", "gitdiff:ci": "npm run build && git diff --exit-code", "integration": "npm run build && npm run nobuild:integration", @@ -30,6 +32,7 @@ "nobuild:integration": "mocha --timeout 50000 test/integration/", "nobuild:unit": "mocha", "prettier": "prettier 'ts_src/**/*.ts' --ignore-path ./.prettierignore", + "prettierjs": "prettier 'src/**/*.js' --ignore-path ./.prettierignore", "test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage", "unit": "npm run build && npm run nobuild:unit" }, diff --git a/src/address.js b/src/address.js index b0be0e1..e15c55e 100644 --- a/src/address.js +++ b/src/address.js @@ -1,100 +1,91 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const networks = require("./networks"); -const payments = require("./payments"); -const bscript = require("./script"); -const types = require("./types"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks = require('./networks'); +const payments = require('./payments'); +const bscript = require('./script'); +const types = require('./types'); const bech32 = require('bech32'); const bs58check = require('bs58check'); const typeforce = require('typeforce'); function fromBase58Check(address) { - const payload = bs58check.decode(address); - // TODO: 4.0.0, move to "toOutputScript" - if (payload.length < 21) - throw new TypeError(address + ' is too short'); - if (payload.length > 21) - throw new TypeError(address + ' is too long'); - const version = payload.readUInt8(0); - const hash = payload.slice(1); - return { version, hash }; + const payload = bs58check.decode(address); + // TODO: 4.0.0, move to "toOutputScript" + if (payload.length < 21) throw new TypeError(address + ' is too short'); + if (payload.length > 21) throw new TypeError(address + ' is too long'); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; } exports.fromBase58Check = fromBase58Check; function fromBech32(address) { - const result = bech32.decode(address); - const data = bech32.fromWords(result.words.slice(1)); - return { - version: result.words[0], - prefix: result.prefix, - data: Buffer.from(data), - }; + const result = bech32.decode(address); + const data = bech32.fromWords(result.words.slice(1)); + return { + version: result.words[0], + prefix: result.prefix, + data: Buffer.from(data), + }; } exports.fromBech32 = fromBech32; function toBase58Check(hash, version) { - typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); - const payload = Buffer.allocUnsafe(21); - payload.writeUInt8(version, 0); - hash.copy(payload, 1); - return bs58check.encode(payload); + typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(version, 0); + hash.copy(payload, 1); + return bs58check.encode(payload); } exports.toBase58Check = toBase58Check; function toBech32(data, version, prefix) { - const words = bech32.toWords(data); - words.unshift(version); - return bech32.encode(prefix, words); + const words = bech32.toWords(data); + words.unshift(version); + return bech32.encode(prefix, words); } exports.toBech32 = toBech32; function fromOutputScript(output, network) { - // TODO: Network - network = network || networks.bitcoin; - try { - return payments.p2pkh({ output, network }).address; - } - catch (e) { } - try { - return payments.p2sh({ output, network }).address; - } - catch (e) { } - try { - return payments.p2wpkh({ output, network }).address; - } - catch (e) { } - try { - return payments.p2wsh({ output, network }).address; - } - catch (e) { } - throw new Error(bscript.toASM(output) + ' has no matching Address'); + // TODO: Network + network = network || networks.bitcoin; + try { + return payments.p2pkh({ output, network }).address; + } catch (e) {} + try { + return payments.p2sh({ output, network }).address; + } catch (e) {} + try { + return payments.p2wpkh({ output, network }).address; + } catch (e) {} + try { + return payments.p2wsh({ output, network }).address; + } catch (e) {} + throw new Error(bscript.toASM(output) + ' has no matching Address'); } exports.fromOutputScript = fromOutputScript; function toOutputScript(address, network) { - network = network || networks.bitcoin; - let decodeBase58; - let decodeBech32; + network = network || networks.bitcoin; + let decodeBase58; + let decodeBech32; + try { + decodeBase58 = fromBase58Check(address); + } catch (e) {} + if (decodeBase58) { + if (decodeBase58.version === network.pubKeyHash) + return payments.p2pkh({ hash: decodeBase58.hash }).output; + if (decodeBase58.version === network.scriptHash) + return payments.p2sh({ hash: decodeBase58.hash }).output; + } else { try { - decodeBase58 = fromBase58Check(address); + decodeBech32 = fromBech32(address); + } catch (e) {} + if (decodeBech32) { + if (decodeBech32.prefix !== network.bech32) + throw new Error(address + ' has an invalid prefix'); + if (decodeBech32.version === 0) { + if (decodeBech32.data.length === 20) + return payments.p2wpkh({ hash: decodeBech32.data }).output; + if (decodeBech32.data.length === 32) + return payments.p2wsh({ hash: decodeBech32.data }).output; + } } - catch (e) { } - if (decodeBase58) { - if (decodeBase58.version === network.pubKeyHash) - return payments.p2pkh({ hash: decodeBase58.hash }).output; - if (decodeBase58.version === network.scriptHash) - return payments.p2sh({ hash: decodeBase58.hash }).output; - } - else { - try { - decodeBech32 = fromBech32(address); - } - catch (e) { } - if (decodeBech32) { - if (decodeBech32.prefix !== network.bech32) - throw new Error(address + ' has an invalid prefix'); - if (decodeBech32.version === 0) { - if (decodeBech32.data.length === 20) - return payments.p2wpkh({ hash: decodeBech32.data }).output; - if (decodeBech32.data.length === 32) - return payments.p2wsh({ hash: decodeBech32.data }).output; - } - } - } - throw new Error(address + ' has no matching Script'); + } + throw new Error(address + ' has no matching Script'); } exports.toOutputScript = toOutputScript; diff --git a/src/block.js b/src/block.js index da17193..22449fd 100644 --- a/src/block.js +++ b/src/block.js @@ -1,222 +1,242 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const bufferutils_1 = require("./bufferutils"); -const bcrypto = require("./crypto"); -const transaction_1 = require("./transaction"); -const types = require("./types"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bufferutils_1 = require('./bufferutils'); +const bcrypto = require('./crypto'); +const transaction_1 = require('./transaction'); +const types = require('./types'); const fastMerkleRoot = require('merkle-lib/fastRoot'); const typeforce = require('typeforce'); const varuint = require('varuint-bitcoin'); -const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions'); -const errorWitnessNotSegwit = new TypeError('Cannot compute witness commit for non-segwit block'); +const errorMerkleNoTxes = new TypeError( + 'Cannot compute merkle root for zero transactions', +); +const errorWitnessNotSegwit = new TypeError( + 'Cannot compute witness commit for non-segwit block', +); class Block { - constructor() { - this.version = 1; - this.prevHash = undefined; - this.merkleRoot = undefined; - this.timestamp = 0; - this.witnessCommit = undefined; - this.bits = 0; - this.nonce = 0; - this.transactions = undefined; - } - static fromBuffer(buffer) { - if (buffer.length < 80) - throw new Error('Buffer too small (< 80 bytes)'); - let offset = 0; - const readSlice = (n) => { - offset += n; - return buffer.slice(offset - n, offset); - }; - const readUInt32 = () => { - const i = buffer.readUInt32LE(offset); - offset += 4; - return i; - }; - const readInt32 = () => { - const i = buffer.readInt32LE(offset); - offset += 4; - return i; - }; - const block = new Block(); - block.version = readInt32(); - block.prevHash = readSlice(32); - block.merkleRoot = readSlice(32); - block.timestamp = readUInt32(); - block.bits = readUInt32(); - block.nonce = readUInt32(); - if (buffer.length === 80) - return block; - const readVarInt = () => { - const vi = varuint.decode(buffer, offset); - offset += varuint.decode.bytes; - return vi; - }; - const readTransaction = () => { - const tx = transaction_1.Transaction.fromBuffer(buffer.slice(offset), true); - offset += tx.byteLength(); - return tx; - }; - const nTransactions = readVarInt(); - block.transactions = []; - for (let i = 0; i < nTransactions; ++i) { - const tx = readTransaction(); - block.transactions.push(tx); - } - const witnessCommit = block.getWitnessCommit(); - // This Block contains a witness commit - if (witnessCommit) - block.witnessCommit = witnessCommit; - return block; - } - static fromHex(hex) { - return Block.fromBuffer(Buffer.from(hex, 'hex')); - } - static calculateTarget(bits) { - const exponent = ((bits & 0xff000000) >> 24) - 3; - const mantissa = bits & 0x007fffff; - const target = Buffer.alloc(32, 0); - target.writeUIntBE(mantissa, 29 - exponent, 3); - return target; - } - static calculateMerkleRoot(transactions, forWitness) { - typeforce([{ getHash: types.Function }], transactions); - if (transactions.length === 0) - throw errorMerkleNoTxes; - if (forWitness && !txesHaveWitnessCommit(transactions)) - throw errorWitnessNotSegwit; - const hashes = transactions.map(transaction => transaction.getHash(forWitness)); - const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); - return forWitness - ? bcrypto.hash256(Buffer.concat([rootHash, transactions[0].ins[0].witness[0]])) - : rootHash; - } - getWitnessCommit() { - if (!txesHaveWitnessCommit(this.transactions)) - return null; - // The merkle root for the witness data is in an OP_RETURN output. - // There is no rule for the index of the output, so use filter to find it. - // The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed - // If multiple commits are found, the output with highest index is assumed. - const witnessCommits = this.transactions[0].outs.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))).map(out => out.script.slice(6, 38)); - if (witnessCommits.length === 0) - return null; - // Use the commit with the highest output (should only be one though) - const result = witnessCommits[witnessCommits.length - 1]; - if (!(result instanceof Buffer && result.length === 32)) - return null; - return result; - } - hasWitnessCommit() { - if (this.witnessCommit instanceof Buffer && - this.witnessCommit.length === 32) - return true; - if (this.getWitnessCommit() !== null) - return true; - return false; - } - hasWitness() { - return anyTxHasWitness(this.transactions); - } - byteLength(headersOnly) { - if (headersOnly || !this.transactions) - return 80; - return (80 + - varuint.encodingLength(this.transactions.length) + - this.transactions.reduce((a, x) => a + x.byteLength(), 0)); - } - getHash() { - return bcrypto.hash256(this.toBuffer(true)); - } - getId() { - return bufferutils_1.reverseBuffer(this.getHash()).toString('hex'); - } - getUTCDate() { - const date = new Date(0); // epoch - date.setUTCSeconds(this.timestamp); - return date; - } - // TODO: buffer, offset compatibility - toBuffer(headersOnly) { - const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); - let offset = 0; - const writeSlice = (slice) => { - slice.copy(buffer, offset); - offset += slice.length; - }; - const writeInt32 = (i) => { - buffer.writeInt32LE(i, offset); - offset += 4; - }; - const writeUInt32 = (i) => { - buffer.writeUInt32LE(i, offset); - offset += 4; - }; - writeInt32(this.version); - writeSlice(this.prevHash); - writeSlice(this.merkleRoot); - writeUInt32(this.timestamp); - writeUInt32(this.bits); - writeUInt32(this.nonce); - if (headersOnly || !this.transactions) - return buffer; - varuint.encode(this.transactions.length, buffer, offset); - offset += varuint.encode.bytes; - this.transactions.forEach(tx => { - const txSize = tx.byteLength(); // TODO: extract from toBuffer? - tx.toBuffer(buffer, offset); - offset += txSize; - }); - return buffer; - } - toHex(headersOnly) { - return this.toBuffer(headersOnly).toString('hex'); - } - checkTxRoots() { - // If the Block has segwit transactions but no witness commit, - // there's no way it can be valid, so fail the check. - const hasWitnessCommit = this.hasWitnessCommit(); - if (!hasWitnessCommit && this.hasWitness()) - return false; - return (this.__checkMerkleRoot() && - (hasWitnessCommit ? this.__checkWitnessCommit() : true)); - } - checkProofOfWork() { - const hash = bufferutils_1.reverseBuffer(this.getHash()); - const target = Block.calculateTarget(this.bits); - return hash.compare(target) <= 0; - } - __checkMerkleRoot() { - if (!this.transactions) - throw errorMerkleNoTxes; - const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions); - return this.merkleRoot.compare(actualMerkleRoot) === 0; - } - __checkWitnessCommit() { - if (!this.transactions) - throw errorMerkleNoTxes; - if (!this.hasWitnessCommit()) - throw errorWitnessNotSegwit; - const actualWitnessCommit = Block.calculateMerkleRoot(this.transactions, true); - return this.witnessCommit.compare(actualWitnessCommit) === 0; + constructor() { + this.version = 1; + this.prevHash = undefined; + this.merkleRoot = undefined; + this.timestamp = 0; + this.witnessCommit = undefined; + this.bits = 0; + this.nonce = 0; + this.transactions = undefined; + } + static fromBuffer(buffer) { + if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)'); + let offset = 0; + const readSlice = n => { + offset += n; + return buffer.slice(offset - n, offset); + }; + const readUInt32 = () => { + const i = buffer.readUInt32LE(offset); + offset += 4; + return i; + }; + const readInt32 = () => { + const i = buffer.readInt32LE(offset); + offset += 4; + return i; + }; + const block = new Block(); + block.version = readInt32(); + block.prevHash = readSlice(32); + block.merkleRoot = readSlice(32); + block.timestamp = readUInt32(); + block.bits = readUInt32(); + block.nonce = readUInt32(); + if (buffer.length === 80) return block; + const readVarInt = () => { + const vi = varuint.decode(buffer, offset); + offset += varuint.decode.bytes; + return vi; + }; + const readTransaction = () => { + const tx = transaction_1.Transaction.fromBuffer( + buffer.slice(offset), + true, + ); + offset += tx.byteLength(); + return tx; + }; + const nTransactions = readVarInt(); + block.transactions = []; + for (let i = 0; i < nTransactions; ++i) { + const tx = readTransaction(); + block.transactions.push(tx); } + const witnessCommit = block.getWitnessCommit(); + // This Block contains a witness commit + if (witnessCommit) block.witnessCommit = witnessCommit; + return block; + } + static fromHex(hex) { + return Block.fromBuffer(Buffer.from(hex, 'hex')); + } + static calculateTarget(bits) { + const exponent = ((bits & 0xff000000) >> 24) - 3; + const mantissa = bits & 0x007fffff; + const target = Buffer.alloc(32, 0); + target.writeUIntBE(mantissa, 29 - exponent, 3); + return target; + } + static calculateMerkleRoot(transactions, forWitness) { + typeforce([{ getHash: types.Function }], transactions); + if (transactions.length === 0) throw errorMerkleNoTxes; + if (forWitness && !txesHaveWitnessCommit(transactions)) + throw errorWitnessNotSegwit; + const hashes = transactions.map(transaction => + transaction.getHash(forWitness), + ); + const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); + return forWitness + ? bcrypto.hash256( + Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]), + ) + : rootHash; + } + getWitnessCommit() { + if (!txesHaveWitnessCommit(this.transactions)) return null; + // The merkle root for the witness data is in an OP_RETURN output. + // There is no rule for the index of the output, so use filter to find it. + // The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed + // If multiple commits are found, the output with highest index is assumed. + const witnessCommits = this.transactions[0].outs + .filter(out => + out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex')), + ) + .map(out => out.script.slice(6, 38)); + if (witnessCommits.length === 0) return null; + // Use the commit with the highest output (should only be one though) + const result = witnessCommits[witnessCommits.length - 1]; + if (!(result instanceof Buffer && result.length === 32)) return null; + return result; + } + hasWitnessCommit() { + if ( + this.witnessCommit instanceof Buffer && + this.witnessCommit.length === 32 + ) + return true; + if (this.getWitnessCommit() !== null) return true; + return false; + } + hasWitness() { + return anyTxHasWitness(this.transactions); + } + byteLength(headersOnly) { + if (headersOnly || !this.transactions) return 80; + return ( + 80 + + varuint.encodingLength(this.transactions.length) + + this.transactions.reduce((a, x) => a + x.byteLength(), 0) + ); + } + getHash() { + return bcrypto.hash256(this.toBuffer(true)); + } + getId() { + return bufferutils_1.reverseBuffer(this.getHash()).toString('hex'); + } + getUTCDate() { + const date = new Date(0); // epoch + date.setUTCSeconds(this.timestamp); + return date; + } + // TODO: buffer, offset compatibility + toBuffer(headersOnly) { + const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); + let offset = 0; + const writeSlice = slice => { + slice.copy(buffer, offset); + offset += slice.length; + }; + const writeInt32 = i => { + buffer.writeInt32LE(i, offset); + offset += 4; + }; + const writeUInt32 = i => { + buffer.writeUInt32LE(i, offset); + offset += 4; + }; + writeInt32(this.version); + writeSlice(this.prevHash); + writeSlice(this.merkleRoot); + writeUInt32(this.timestamp); + writeUInt32(this.bits); + writeUInt32(this.nonce); + if (headersOnly || !this.transactions) return buffer; + varuint.encode(this.transactions.length, buffer, offset); + offset += varuint.encode.bytes; + this.transactions.forEach(tx => { + const txSize = tx.byteLength(); // TODO: extract from toBuffer? + tx.toBuffer(buffer, offset); + offset += txSize; + }); + return buffer; + } + toHex(headersOnly) { + return this.toBuffer(headersOnly).toString('hex'); + } + checkTxRoots() { + // If the Block has segwit transactions but no witness commit, + // there's no way it can be valid, so fail the check. + const hasWitnessCommit = this.hasWitnessCommit(); + if (!hasWitnessCommit && this.hasWitness()) return false; + return ( + this.__checkMerkleRoot() && + (hasWitnessCommit ? this.__checkWitnessCommit() : true) + ); + } + checkProofOfWork() { + const hash = bufferutils_1.reverseBuffer(this.getHash()); + const target = Block.calculateTarget(this.bits); + return hash.compare(target) <= 0; + } + __checkMerkleRoot() { + if (!this.transactions) throw errorMerkleNoTxes; + const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions); + return this.merkleRoot.compare(actualMerkleRoot) === 0; + } + __checkWitnessCommit() { + if (!this.transactions) throw errorMerkleNoTxes; + if (!this.hasWitnessCommit()) throw errorWitnessNotSegwit; + const actualWitnessCommit = Block.calculateMerkleRoot( + this.transactions, + true, + ); + return this.witnessCommit.compare(actualWitnessCommit) === 0; + } } exports.Block = Block; function txesHaveWitnessCommit(transactions) { - return (transactions instanceof Array && - transactions[0] && - transactions[0].ins && - transactions[0].ins instanceof Array && - transactions[0].ins[0] && - transactions[0].ins[0].witness && - transactions[0].ins[0].witness instanceof Array && - transactions[0].ins[0].witness.length > 0); + return ( + transactions instanceof Array && + transactions[0] && + transactions[0].ins && + transactions[0].ins instanceof Array && + transactions[0].ins[0] && + transactions[0].ins[0].witness && + transactions[0].ins[0].witness instanceof Array && + transactions[0].ins[0].witness.length > 0 + ); } function anyTxHasWitness(transactions) { - return (transactions instanceof Array && - transactions.some(tx => typeof tx === 'object' && - tx.ins instanceof Array && - tx.ins.some(input => typeof input === 'object' && - input.witness instanceof Array && - input.witness.length > 0))); + return ( + transactions instanceof Array && + transactions.some( + tx => + typeof tx === 'object' && + tx.ins instanceof Array && + tx.ins.some( + input => + typeof input === 'object' && + input.witness instanceof Array && + input.witness.length > 0, + ), + ) + ); } diff --git a/src/bufferutils.js b/src/bufferutils.js index f768250..54ce1c9 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -1,42 +1,40 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); // https://github.com/feross/buffer/blob/master/index.js#L1127 function verifuint(value, max) { - if (typeof value !== 'number') - throw new Error('cannot write a non-number as a number'); - if (value < 0) - throw new Error('specified a negative value for writing an unsigned value'); - if (value > max) - throw new Error('RangeError: value out of range'); - if (Math.floor(value) !== value) - throw new Error('value has a fractional component'); + if (typeof value !== 'number') + throw new Error('cannot write a non-number as a number'); + if (value < 0) + throw new Error('specified a negative value for writing an unsigned value'); + if (value > max) throw new Error('RangeError: value out of range'); + if (Math.floor(value) !== value) + throw new Error('value has a fractional component'); } function readUInt64LE(buffer, offset) { - const a = buffer.readUInt32LE(offset); - let b = buffer.readUInt32LE(offset + 4); - b *= 0x100000000; - verifuint(b + a, 0x001fffffffffffff); - return b + a; + const a = buffer.readUInt32LE(offset); + let b = buffer.readUInt32LE(offset + 4); + b *= 0x100000000; + verifuint(b + a, 0x001fffffffffffff); + return b + a; } exports.readUInt64LE = readUInt64LE; function writeUInt64LE(buffer, value, offset) { - verifuint(value, 0x001fffffffffffff); - buffer.writeInt32LE(value & -1, offset); - buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4); - return offset + 8; + verifuint(value, 0x001fffffffffffff); + buffer.writeInt32LE(value & -1, offset); + buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4); + return offset + 8; } exports.writeUInt64LE = writeUInt64LE; function reverseBuffer(buffer) { - if (buffer.length < 1) - return buffer; - let j = buffer.length - 1; - let tmp = 0; - for (let i = 0; i < buffer.length / 2; i++) { - tmp = buffer[i]; - buffer[i] = buffer[j]; - buffer[j] = tmp; - j--; - } - return buffer; + if (buffer.length < 1) return buffer; + let j = buffer.length - 1; + let tmp = 0; + for (let i = 0; i < buffer.length / 2; i++) { + tmp = buffer[i]; + buffer[i] = buffer[j]; + buffer[j] = tmp; + j--; + } + return buffer; } exports.reverseBuffer = reverseBuffer; diff --git a/src/classify.js b/src/classify.js index 7d8e57d..70c600c 100644 --- a/src/classify.js +++ b/src/classify.js @@ -1,75 +1,59 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const script_1 = require("./script"); -const multisig = require("./templates/multisig"); -const nullData = require("./templates/nulldata"); -const pubKey = require("./templates/pubkey"); -const pubKeyHash = require("./templates/pubkeyhash"); -const scriptHash = require("./templates/scripthash"); -const witnessCommitment = require("./templates/witnesscommitment"); -const witnessPubKeyHash = require("./templates/witnesspubkeyhash"); -const witnessScriptHash = require("./templates/witnessscripthash"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const script_1 = require('./script'); +const multisig = require('./templates/multisig'); +const nullData = require('./templates/nulldata'); +const pubKey = require('./templates/pubkey'); +const pubKeyHash = require('./templates/pubkeyhash'); +const scriptHash = require('./templates/scripthash'); +const witnessCommitment = require('./templates/witnesscommitment'); +const witnessPubKeyHash = require('./templates/witnesspubkeyhash'); +const witnessScriptHash = require('./templates/witnessscripthash'); const types = { - P2MS: 'multisig', - NONSTANDARD: 'nonstandard', - NULLDATA: 'nulldata', - P2PK: 'pubkey', - P2PKH: 'pubkeyhash', - P2SH: 'scripthash', - P2WPKH: 'witnesspubkeyhash', - P2WSH: 'witnessscripthash', - WITNESS_COMMITMENT: 'witnesscommitment', + P2MS: 'multisig', + NONSTANDARD: 'nonstandard', + NULLDATA: 'nulldata', + P2PK: 'pubkey', + P2PKH: 'pubkeyhash', + P2SH: 'scripthash', + P2WPKH: 'witnesspubkeyhash', + P2WSH: 'witnessscripthash', + WITNESS_COMMITMENT: 'witnesscommitment', }; exports.types = types; function classifyOutput(script) { - if (witnessPubKeyHash.output.check(script)) - return types.P2WPKH; - if (witnessScriptHash.output.check(script)) - return types.P2WSH; - if (pubKeyHash.output.check(script)) - return types.P2PKH; - if (scriptHash.output.check(script)) - return types.P2SH; - // XXX: optimization, below functions .decompile before use - const chunks = script_1.decompile(script); - if (!chunks) - throw new TypeError('Invalid script'); - if (multisig.output.check(chunks)) - return types.P2MS; - if (pubKey.output.check(chunks)) - return types.P2PK; - if (witnessCommitment.output.check(chunks)) - return types.WITNESS_COMMITMENT; - if (nullData.output.check(chunks)) - return types.NULLDATA; - return types.NONSTANDARD; + if (witnessPubKeyHash.output.check(script)) return types.P2WPKH; + if (witnessScriptHash.output.check(script)) return types.P2WSH; + if (pubKeyHash.output.check(script)) return types.P2PKH; + if (scriptHash.output.check(script)) return types.P2SH; + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) throw new TypeError('Invalid script'); + if (multisig.output.check(chunks)) return types.P2MS; + if (pubKey.output.check(chunks)) return types.P2PK; + if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT; + if (nullData.output.check(chunks)) return types.NULLDATA; + return types.NONSTANDARD; } exports.output = classifyOutput; function classifyInput(script, allowIncomplete) { - // XXX: optimization, below functions .decompile before use - const chunks = script_1.decompile(script); - if (!chunks) - throw new TypeError('Invalid script'); - if (pubKeyHash.input.check(chunks)) - return types.P2PKH; - if (scriptHash.input.check(chunks, allowIncomplete)) - return types.P2SH; - if (multisig.input.check(chunks, allowIncomplete)) - return types.P2MS; - if (pubKey.input.check(chunks)) - return types.P2PK; - return types.NONSTANDARD; + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) throw new TypeError('Invalid script'); + if (pubKeyHash.input.check(chunks)) return types.P2PKH; + if (scriptHash.input.check(chunks, allowIncomplete)) return types.P2SH; + if (multisig.input.check(chunks, allowIncomplete)) return types.P2MS; + if (pubKey.input.check(chunks)) return types.P2PK; + return types.NONSTANDARD; } exports.input = classifyInput; function classifyWitness(script, allowIncomplete) { - // XXX: optimization, below functions .decompile before use - const chunks = script_1.decompile(script); - if (!chunks) - throw new TypeError('Invalid script'); - if (witnessPubKeyHash.input.check(chunks)) - return types.P2WPKH; - if (witnessScriptHash.input.check(chunks, allowIncomplete)) - return types.P2WSH; - return types.NONSTANDARD; + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) throw new TypeError('Invalid script'); + if (witnessPubKeyHash.input.check(chunks)) return types.P2WPKH; + if (witnessScriptHash.input.check(chunks, allowIncomplete)) + return types.P2WSH; + return types.NONSTANDARD; } exports.witness = classifyWitness; diff --git a/src/crypto.js b/src/crypto.js index 38ec4f9..e7dd596 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,36 +1,35 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); const createHash = require('create-hash'); function ripemd160(buffer) { - try { - return createHash('rmd160') - .update(buffer) - .digest(); - } - catch (err) { - return createHash('ripemd160') - .update(buffer) - .digest(); - } + try { + return createHash('rmd160') + .update(buffer) + .digest(); + } catch (err) { + return createHash('ripemd160') + .update(buffer) + .digest(); + } } exports.ripemd160 = ripemd160; function sha1(buffer) { - return createHash('sha1') - .update(buffer) - .digest(); + return createHash('sha1') + .update(buffer) + .digest(); } exports.sha1 = sha1; function sha256(buffer) { - return createHash('sha256') - .update(buffer) - .digest(); + return createHash('sha256') + .update(buffer) + .digest(); } exports.sha256 = sha256; function hash160(buffer) { - return ripemd160(sha256(buffer)); + return ripemd160(sha256(buffer)); } exports.hash160 = hash160; function hash256(buffer) { - return sha256(sha256(buffer)); + return sha256(sha256(buffer)); } exports.hash256 = hash256; diff --git a/src/ecpair.js b/src/ecpair.js index 2026c63..9acdd3c 100644 --- a/src/ecpair.js +++ b/src/ecpair.js @@ -1,98 +1,91 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const NETWORKS = require("./networks"); -const types = require("./types"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const NETWORKS = require('./networks'); +const types = require('./types'); const ecc = require('tiny-secp256k1'); const randomBytes = require('randombytes'); const typeforce = require('typeforce'); const wif = require('wif'); -const isOptions = typeforce.maybe(typeforce.compile({ +const isOptions = typeforce.maybe( + typeforce.compile({ compressed: types.maybe(types.Boolean), network: types.maybe(types.Network), -})); + }), +); class ECPair { - constructor(__D, __Q, options) { - this.__D = __D; - this.__Q = __Q; - if (options === undefined) - options = {}; - this.compressed = - options.compressed === undefined ? true : options.compressed; - this.network = options.network || NETWORKS.bitcoin; - if (__Q !== undefined) - this.__Q = ecc.pointCompress(__Q, this.compressed); - } - get privateKey() { - return this.__D; - } - get publicKey() { - if (!this.__Q) - this.__Q = ecc.pointFromScalar(this.__D, this.compressed); - return this.__Q; - } - toWIF() { - if (!this.__D) - throw new Error('Missing private key'); - return wif.encode(this.network.wif, this.__D, this.compressed); - } - sign(hash) { - if (!this.__D) - throw new Error('Missing private key'); - return ecc.sign(hash, this.__D); - } - verify(hash, signature) { - return ecc.verify(hash, this.publicKey, signature); - } + constructor(__D, __Q, options) { + this.__D = __D; + this.__Q = __Q; + if (options === undefined) options = {}; + this.compressed = + options.compressed === undefined ? true : options.compressed; + this.network = options.network || NETWORKS.bitcoin; + if (__Q !== undefined) this.__Q = ecc.pointCompress(__Q, this.compressed); + } + get privateKey() { + return this.__D; + } + get publicKey() { + if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__D, this.compressed); + return this.__Q; + } + toWIF() { + if (!this.__D) throw new Error('Missing private key'); + return wif.encode(this.network.wif, this.__D, this.compressed); + } + sign(hash) { + if (!this.__D) throw new Error('Missing private key'); + return ecc.sign(hash, this.__D); + } + verify(hash, signature) { + return ecc.verify(hash, this.publicKey, signature); + } } function fromPrivateKey(buffer, options) { - typeforce(types.Buffer256bit, buffer); - if (!ecc.isPrivate(buffer)) - throw new TypeError('Private key not in range [1, n)'); - typeforce(isOptions, options); - return new ECPair(buffer, undefined, options); + typeforce(types.Buffer256bit, buffer); + if (!ecc.isPrivate(buffer)) + throw new TypeError('Private key not in range [1, n)'); + typeforce(isOptions, options); + return new ECPair(buffer, undefined, options); } exports.fromPrivateKey = fromPrivateKey; function fromPublicKey(buffer, options) { - typeforce(ecc.isPoint, buffer); - typeforce(isOptions, options); - return new ECPair(undefined, buffer, options); + typeforce(ecc.isPoint, buffer); + typeforce(isOptions, options); + return new ECPair(undefined, buffer, options); } exports.fromPublicKey = fromPublicKey; function fromWIF(wifString, network) { - const decoded = wif.decode(wifString); - const version = decoded.version; - // list of networks? - if (types.Array(network)) { - network = network - .filter((x) => { - return version === x.wif; - }) - .pop(); - if (!network) - throw new Error('Unknown network version'); - // otherwise, assume a network object (or default to bitcoin) - } - else { - network = network || NETWORKS.bitcoin; - if (version !== network.wif) - throw new Error('Invalid network version'); - } - return fromPrivateKey(decoded.privateKey, { - compressed: decoded.compressed, - network: network, - }); + const decoded = wif.decode(wifString); + const version = decoded.version; + // list of networks? + if (types.Array(network)) { + network = network + .filter(x => { + return version === x.wif; + }) + .pop(); + if (!network) throw new Error('Unknown network version'); + // otherwise, assume a network object (or default to bitcoin) + } else { + network = network || NETWORKS.bitcoin; + if (version !== network.wif) throw new Error('Invalid network version'); + } + return fromPrivateKey(decoded.privateKey, { + compressed: decoded.compressed, + network: network, + }); } exports.fromWIF = fromWIF; function makeRandom(options) { - typeforce(isOptions, options); - if (options === undefined) - options = {}; - const rng = options.rng || randomBytes; - let d; - do { - d = rng(32); - typeforce(types.Buffer256bit, d); - } while (!ecc.isPrivate(d)); - return fromPrivateKey(d, options); + typeforce(isOptions, options); + if (options === undefined) options = {}; + const rng = options.rng || randomBytes; + let d; + do { + d = rng(32); + typeforce(types.Buffer256bit, d); + } while (!ecc.isPrivate(d)); + return fromPrivateKey(d, options); } exports.makeRandom = makeRandom; diff --git a/src/index.js b/src/index.js index 73ac6f4..499380e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,24 +1,24 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const bip32 = require("bip32"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bip32 = require('bip32'); exports.bip32 = bip32; -const address = require("./address"); +const address = require('./address'); exports.address = address; -const crypto = require("./crypto"); +const crypto = require('./crypto'); exports.crypto = crypto; -const ECPair = require("./ecpair"); +const ECPair = require('./ecpair'); exports.ECPair = ECPair; -const networks = require("./networks"); +const networks = require('./networks'); exports.networks = networks; -const payments = require("./payments"); +const payments = require('./payments'); exports.payments = payments; -const script = require("./script"); +const script = require('./script'); exports.script = script; -var block_1 = require("./block"); +var block_1 = require('./block'); exports.Block = block_1.Block; -var script_1 = require("./script"); +var script_1 = require('./script'); exports.opcodes = script_1.OPS; -var transaction_1 = require("./transaction"); +var transaction_1 = require('./transaction'); exports.Transaction = transaction_1.Transaction; -var transaction_builder_1 = require("./transaction_builder"); +var transaction_builder_1 = require('./transaction_builder'); exports.TransactionBuilder = transaction_builder_1.TransactionBuilder; diff --git a/src/networks.js b/src/networks.js index 298808d..0c31fe1 100644 --- a/src/networks.js +++ b/src/networks.js @@ -1,35 +1,35 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); exports.bitcoin = { - messagePrefix: '\x18Bitcoin Signed Message:\n', - bech32: 'bc', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4, - }, - pubKeyHash: 0x00, - scriptHash: 0x05, - wif: 0x80, + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, }; exports.regtest = { - messagePrefix: '\x18Bitcoin Signed Message:\n', - bech32: 'bcrt', - bip32: { - public: 0x043587cf, - private: 0x04358394, - }, - pubKeyHash: 0x6f, - scriptHash: 0xc4, - wif: 0xef, + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bcrt', + bip32: { + public: 0x043587cf, + private: 0x04358394, + }, + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, }; exports.testnet = { - messagePrefix: '\x18Bitcoin Signed Message:\n', - bech32: 'tb', - bip32: { - public: 0x043587cf, - private: 0x04358394, - }, - pubKeyHash: 0x6f, - scriptHash: 0xc4, - wif: 0xef, + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'tb', + bip32: { + public: 0x043587cf, + private: 0x04358394, + }, + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, }; diff --git a/src/payments/embed.js b/src/payments/embed.js index 800c8de..3ddceb9 100644 --- a/src/payments/embed.js +++ b/src/payments/embed.js @@ -1,51 +1,49 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const networks_1 = require("../networks"); -const bscript = require("../script"); -const lazy = require("./lazy"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); const typef = require('typeforce'); const OPS = bscript.OPS; function stacksEqual(a, b) { - if (a.length !== b.length) - return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // output: OP_RETURN ... function p2data(a, opts) { - if (!a.data && !a.output) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef({ - network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), - data: typef.maybe(typef.arrayOf(typef.Buffer)), - }, a); - const network = a.network || networks_1.bitcoin; - const o = { network }; - lazy.prop(o, 'output', () => { - if (!a.data) - return; - return bscript.compile([OPS.OP_RETURN].concat(a.data)); - }); - lazy.prop(o, 'data', () => { - if (!a.output) - return; - return bscript.decompile(a.output).slice(1); - }); - // extended validation - if (opts.validate) { - if (a.output) { - const chunks = bscript.decompile(a.output); - if (chunks[0] !== OPS.OP_RETURN) - throw new TypeError('Output is invalid'); - if (!chunks.slice(1).every(typef.Buffer)) - throw new TypeError('Output is invalid'); - if (a.data && !stacksEqual(a.data, o.data)) - throw new TypeError('Data mismatch'); - } + if (!a.data && !a.output) throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + data: typef.maybe(typef.arrayOf(typef.Buffer)), + }, + a, + ); + const network = a.network || networks_1.bitcoin; + const o = { network }; + lazy.prop(o, 'output', () => { + if (!a.data) return; + return bscript.compile([OPS.OP_RETURN].concat(a.data)); + }); + lazy.prop(o, 'data', () => { + if (!a.output) return; + return bscript.decompile(a.output).slice(1); + }); + // extended validation + if (opts.validate) { + if (a.output) { + const chunks = bscript.decompile(a.output); + if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid'); + if (!chunks.slice(1).every(typef.Buffer)) + throw new TypeError('Output is invalid'); + if (a.data && !stacksEqual(a.data, o.data)) + throw new TypeError('Data mismatch'); } - return Object.assign(o, a); + } + return Object.assign(o, a); } exports.p2data = p2data; diff --git a/src/payments/index.js b/src/payments/index.js index f21762d..ddab977 100644 --- a/src/payments/index.js +++ b/src/payments/index.js @@ -1,18 +1,18 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const embed_1 = require("./embed"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const embed_1 = require('./embed'); exports.embed = embed_1.p2data; -const p2ms_1 = require("./p2ms"); +const p2ms_1 = require('./p2ms'); exports.p2ms = p2ms_1.p2ms; -const p2pk_1 = require("./p2pk"); +const p2pk_1 = require('./p2pk'); exports.p2pk = p2pk_1.p2pk; -const p2pkh_1 = require("./p2pkh"); +const p2pkh_1 = require('./p2pkh'); exports.p2pkh = p2pkh_1.p2pkh; -const p2sh_1 = require("./p2sh"); +const p2sh_1 = require('./p2sh'); exports.p2sh = p2sh_1.p2sh; -const p2wpkh_1 = require("./p2wpkh"); +const p2wpkh_1 = require('./p2wpkh'); exports.p2wpkh = p2wpkh_1.p2wpkh; -const p2wsh_1 = require("./p2wsh"); +const p2wsh_1 = require('./p2wsh'); exports.p2wsh = p2wsh_1.p2wsh; // TODO // witness commitment diff --git a/src/payments/lazy.js b/src/payments/lazy.js index d8494fd..1a71521 100644 --- a/src/payments/lazy.js +++ b/src/payments/lazy.js @@ -1,32 +1,31 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); function prop(object, name, f) { - Object.defineProperty(object, name, { + Object.defineProperty(object, name, { + configurable: true, + enumerable: true, + get() { + const _value = f.call(this); + this[name] = _value; + return _value; + }, + set(_value) { + Object.defineProperty(this, name, { configurable: true, enumerable: true, - get() { - const _value = f.call(this); - this[name] = _value; - return _value; - }, - set(_value) { - Object.defineProperty(this, name, { - configurable: true, - enumerable: true, - value: _value, - writable: true, - }); - }, - }); + value: _value, + writable: true, + }); + }, + }); } exports.prop = prop; function value(f) { - let _value; - return () => { - if (_value !== undefined) - return _value; - _value = f(); - return _value; - }; + let _value; + return () => { + if (_value !== undefined) return _value; + _value = f(); + return _value; + }; } exports.value = value; diff --git a/src/payments/p2ms.js b/src/payments/p2ms.js index 5e48432..1e7c6ba 100644 --- a/src/payments/p2ms.js +++ b/src/payments/p2ms.js @@ -1,141 +1,141 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const networks_1 = require("../networks"); -const bscript = require("../script"); -const lazy = require("./lazy"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); const OPS = bscript.OPS; const typef = require('typeforce'); const ecc = require('tiny-secp256k1'); const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 function stacksEqual(a, b) { - if (a.length !== b.length) - return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // input: OP_0 [signatures ...] // output: m [pubKeys ...] n OP_CHECKMULTISIG function p2ms(a, opts) { - if (!a.input && - !a.output && - !(a.pubkeys && a.m !== undefined) && - !a.signatures) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - function isAcceptableSignature(x) { - return (bscript.isCanonicalScriptSignature(x) || - (opts.allowIncomplete && x === OPS.OP_0) !== undefined); + if ( + !a.input && + !a.output && + !(a.pubkeys && a.m !== undefined) && + !a.signatures + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + function isAcceptableSignature(x) { + return ( + bscript.isCanonicalScriptSignature(x) || + (opts.allowIncomplete && x === OPS.OP_0) !== undefined + ); + } + typef( + { + network: typef.maybe(typef.Object), + m: typef.maybe(typef.Number), + n: typef.maybe(typef.Number), + output: typef.maybe(typef.Buffer), + pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), + signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), + input: typef.maybe(typef.Buffer), + }, + a, + ); + const network = a.network || networks_1.bitcoin; + const o = { network }; + let chunks = []; + let decoded = false; + function decode(output) { + if (decoded) return; + decoded = true; + chunks = bscript.decompile(output); + o.m = chunks[0] - OP_INT_BASE; + o.n = chunks[chunks.length - 2] - OP_INT_BASE; + o.pubkeys = chunks.slice(1, -2); + } + lazy.prop(o, 'output', () => { + if (!a.m) return; + if (!o.n) return; + if (!a.pubkeys) return; + return bscript.compile( + [].concat( + OP_INT_BASE + a.m, + a.pubkeys, + OP_INT_BASE + o.n, + OPS.OP_CHECKMULTISIG, + ), + ); + }); + lazy.prop(o, 'm', () => { + if (!o.output) return; + decode(o.output); + return o.m; + }); + lazy.prop(o, 'n', () => { + if (!o.pubkeys) return; + return o.pubkeys.length; + }); + lazy.prop(o, 'pubkeys', () => { + if (!a.output) return; + decode(a.output); + return o.pubkeys; + }); + lazy.prop(o, 'signatures', () => { + if (!a.input) return; + return bscript.decompile(a.input).slice(1); + }); + lazy.prop(o, 'input', () => { + if (!a.signatures) return; + return bscript.compile([OPS.OP_0].concat(a.signatures)); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + // extended validation + if (opts.validate) { + if (a.output) { + decode(a.output); + if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid'); + if (!typef.Number(chunks[chunks.length - 2])) + throw new TypeError('Output is invalid'); + if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) + throw new TypeError('Output is invalid'); + if (o.m <= 0 || o.n > 16 || o.m > o.n || o.n !== chunks.length - 3) + throw new TypeError('Output is invalid'); + if (!o.pubkeys.every(x => ecc.isPoint(x))) + throw new TypeError('Output is invalid'); + if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch'); + if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch'); + if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) + throw new TypeError('Pubkeys mismatch'); } - typef({ - network: typef.maybe(typef.Object), - m: typef.maybe(typef.Number), - n: typef.maybe(typef.Number), - output: typef.maybe(typef.Buffer), - pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), - signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), - input: typef.maybe(typef.Buffer), - }, a); - const network = a.network || networks_1.bitcoin; - const o = { network }; - let chunks = []; - let decoded = false; - function decode(output) { - if (decoded) - return; - decoded = true; - chunks = bscript.decompile(output); - o.m = chunks[0] - OP_INT_BASE; - o.n = chunks[chunks.length - 2] - OP_INT_BASE; - o.pubkeys = chunks.slice(1, -2); + if (a.pubkeys) { + if (a.n !== undefined && a.n !== a.pubkeys.length) + throw new TypeError('Pubkey count mismatch'); + o.n = a.pubkeys.length; + if (o.n < o.m) throw new TypeError('Pubkey count cannot be less than m'); } - lazy.prop(o, 'output', () => { - if (!a.m) - return; - if (!o.n) - return; - if (!a.pubkeys) - return; - return bscript.compile([].concat(OP_INT_BASE + a.m, a.pubkeys, OP_INT_BASE + o.n, OPS.OP_CHECKMULTISIG)); - }); - lazy.prop(o, 'm', () => { - if (!o.output) - return; - decode(o.output); - return o.m; - }); - lazy.prop(o, 'n', () => { - if (!o.pubkeys) - return; - return o.pubkeys.length; - }); - lazy.prop(o, 'pubkeys', () => { - if (!a.output) - return; - decode(a.output); - return o.pubkeys; - }); - lazy.prop(o, 'signatures', () => { - if (!a.input) - return; - return bscript.decompile(a.input).slice(1); - }); - lazy.prop(o, 'input', () => { - if (!a.signatures) - return; - return bscript.compile([OPS.OP_0].concat(a.signatures)); - }); - lazy.prop(o, 'witness', () => { - if (!o.input) - return; - return []; - }); - // extended validation - if (opts.validate) { - if (a.output) { - decode(a.output); - if (!typef.Number(chunks[0])) - throw new TypeError('Output is invalid'); - if (!typef.Number(chunks[chunks.length - 2])) - throw new TypeError('Output is invalid'); - if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) - throw new TypeError('Output is invalid'); - if (o.m <= 0 || o.n > 16 || o.m > o.n || o.n !== chunks.length - 3) - throw new TypeError('Output is invalid'); - if (!o.pubkeys.every(x => ecc.isPoint(x))) - throw new TypeError('Output is invalid'); - if (a.m !== undefined && a.m !== o.m) - throw new TypeError('m mismatch'); - if (a.n !== undefined && a.n !== o.n) - throw new TypeError('n mismatch'); - if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) - throw new TypeError('Pubkeys mismatch'); - } - if (a.pubkeys) { - if (a.n !== undefined && a.n !== a.pubkeys.length) - throw new TypeError('Pubkey count mismatch'); - o.n = a.pubkeys.length; - if (o.n < o.m) - throw new TypeError('Pubkey count cannot be less than m'); - } - if (a.signatures) { - if (a.signatures.length < o.m) - throw new TypeError('Not enough signatures provided'); - if (a.signatures.length > o.m) - throw new TypeError('Too many signatures provided'); - } - if (a.input) { - if (a.input[0] !== OPS.OP_0) - throw new TypeError('Input is invalid'); - if (o.signatures.length === 0 || - !o.signatures.every(isAcceptableSignature)) - throw new TypeError('Input has invalid signature(s)'); - if (a.signatures && !stacksEqual(a.signatures, o.signatures)) - throw new TypeError('Signature mismatch'); - if (a.m !== undefined && a.m !== a.signatures.length) - throw new TypeError('Signature count mismatch'); - } + if (a.signatures) { + if (a.signatures.length < o.m) + throw new TypeError('Not enough signatures provided'); + if (a.signatures.length > o.m) + throw new TypeError('Too many signatures provided'); } - return Object.assign(o, a); + if (a.input) { + if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid'); + if ( + o.signatures.length === 0 || + !o.signatures.every(isAcceptableSignature) + ) + throw new TypeError('Input has invalid signature(s)'); + if (a.signatures && !stacksEqual(a.signatures, o.signatures)) + throw new TypeError('Signature mismatch'); + if (a.m !== undefined && a.m !== a.signatures.length) + throw new TypeError('Signature count mismatch'); + } + } + return Object.assign(o, a); } exports.p2ms = p2ms; diff --git a/src/payments/p2pk.js b/src/payments/p2pk.js index 81fe427..13356d1 100644 --- a/src/payments/p2pk.js +++ b/src/payments/p2pk.js @@ -1,75 +1,72 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const networks_1 = require("../networks"); -const bscript = require("../script"); -const lazy = require("./lazy"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); const typef = require('typeforce'); const OPS = bscript.OPS; const ecc = require('tiny-secp256k1'); // input: {signature} // output: {pubKey} OP_CHECKSIG function p2pk(a, opts) { - if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef({ - network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), - pubkey: typef.maybe(ecc.isPoint), - signature: typef.maybe(bscript.isCanonicalScriptSignature), - input: typef.maybe(typef.Buffer), - }, a); - const _chunks = lazy.value(() => { - return bscript.decompile(a.input); - }); - const network = a.network || networks_1.bitcoin; - const o = { network }; - lazy.prop(o, 'output', () => { - if (!a.pubkey) - return; - return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); - }); - lazy.prop(o, 'pubkey', () => { - if (!a.output) - return; - return a.output.slice(1, -1); - }); - lazy.prop(o, 'signature', () => { - if (!a.input) - return; - return _chunks()[0]; - }); - lazy.prop(o, 'input', () => { - if (!a.signature) - return; - return bscript.compile([a.signature]); - }); - lazy.prop(o, 'witness', () => { - if (!o.input) - return; - return []; - }); - // extended validation - if (opts.validate) { - if (a.output) { - if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) - throw new TypeError('Output is invalid'); - if (!ecc.isPoint(o.pubkey)) - throw new TypeError('Output pubkey is invalid'); - if (a.pubkey && !a.pubkey.equals(o.pubkey)) - throw new TypeError('Pubkey mismatch'); - } - if (a.signature) { - if (a.input && !a.input.equals(o.input)) - throw new TypeError('Signature mismatch'); - } - if (a.input) { - if (_chunks().length !== 1) - throw new TypeError('Input is invalid'); - if (!bscript.isCanonicalScriptSignature(o.signature)) - throw new TypeError('Input has invalid signature'); - } + if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + pubkey: typef.maybe(ecc.isPoint), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + input: typef.maybe(typef.Buffer), + }, + a, + ); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const network = a.network || networks_1.bitcoin; + const o = { network }; + lazy.prop(o, 'output', () => { + if (!a.pubkey) return; + return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); + }); + lazy.prop(o, 'pubkey', () => { + if (!a.output) return; + return a.output.slice(1, -1); + }); + lazy.prop(o, 'signature', () => { + if (!a.input) return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', () => { + if (!a.signature) return; + return bscript.compile([a.signature]); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + // extended validation + if (opts.validate) { + if (a.output) { + if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) + throw new TypeError('Output is invalid'); + if (!ecc.isPoint(o.pubkey)) + throw new TypeError('Output pubkey is invalid'); + if (a.pubkey && !a.pubkey.equals(o.pubkey)) + throw new TypeError('Pubkey mismatch'); } - return Object.assign(o, a); + if (a.signature) { + if (a.input && !a.input.equals(o.input)) + throw new TypeError('Signature mismatch'); + } + if (a.input) { + if (_chunks().length !== 1) throw new TypeError('Input is invalid'); + if (!bscript.isCanonicalScriptSignature(o.signature)) + throw new TypeError('Input has invalid signature'); + } + } + return Object.assign(o, a); } exports.p2pk = p2pk; diff --git a/src/payments/p2pkh.js b/src/payments/p2pkh.js index 9f06bde..ceb7093 100644 --- a/src/payments/p2pkh.js +++ b/src/payments/p2pkh.js @@ -1,9 +1,9 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const bcrypto = require("../crypto"); -const networks_1 = require("../networks"); -const bscript = require("../script"); -const lazy = require("./lazy"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bcrypto = require('../crypto'); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); const typef = require('typeforce'); const OPS = bscript.OPS; const ecc = require('tiny-secp256k1'); @@ -11,132 +11,122 @@ const bs58check = require('bs58check'); // input: {signature} {pubkey} // output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG function p2pkh(a, opts) { - if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef({ - network: typef.maybe(typef.Object), - address: typef.maybe(typef.String), - hash: typef.maybe(typef.BufferN(20)), - output: typef.maybe(typef.BufferN(25)), - pubkey: typef.maybe(ecc.isPoint), - signature: typef.maybe(bscript.isCanonicalScriptSignature), - input: typef.maybe(typef.Buffer), - }, a); - const _address = lazy.value(() => { - const payload = bs58check.decode(a.address); - const version = payload.readUInt8(0); - const hash = payload.slice(1); - return { version, hash }; - }); - const _chunks = lazy.value(() => { - return bscript.decompile(a.input); - }); - const network = a.network || networks_1.bitcoin; - const o = { network }; - lazy.prop(o, 'address', () => { - if (!o.hash) - return; - const payload = Buffer.allocUnsafe(21); - payload.writeUInt8(network.pubKeyHash, 0); - o.hash.copy(payload, 1); - return bs58check.encode(payload); - }); - lazy.prop(o, 'hash', () => { - if (a.output) - return a.output.slice(3, 23); - if (a.address) - return _address().hash; - if (a.pubkey || o.pubkey) - return bcrypto.hash160(a.pubkey || o.pubkey); - }); - lazy.prop(o, 'output', () => { - if (!o.hash) - return; - return bscript.compile([ - OPS.OP_DUP, - OPS.OP_HASH160, - o.hash, - OPS.OP_EQUALVERIFY, - OPS.OP_CHECKSIG, - ]); - }); - lazy.prop(o, 'pubkey', () => { - if (!a.input) - return; - return _chunks()[1]; - }); - lazy.prop(o, 'signature', () => { - if (!a.input) - return; - return _chunks()[0]; - }); - lazy.prop(o, 'input', () => { - if (!a.pubkey) - return; - if (!a.signature) - return; - return bscript.compile([a.signature, a.pubkey]); - }); - lazy.prop(o, 'witness', () => { - if (!o.input) - return; - return []; - }); - // extended validation - if (opts.validate) { - let hash = Buffer.from([]); - if (a.address) { - if (_address().version !== network.pubKeyHash) - throw new TypeError('Invalid version or Network mismatch'); - if (_address().hash.length !== 20) - throw new TypeError('Invalid address'); - hash = _address().hash; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else - hash = a.hash; - } - if (a.output) { - if (a.output.length !== 25 || - a.output[0] !== OPS.OP_DUP || - a.output[1] !== OPS.OP_HASH160 || - a.output[2] !== 0x14 || - a.output[23] !== OPS.OP_EQUALVERIFY || - a.output[24] !== OPS.OP_CHECKSIG) - throw new TypeError('Output is invalid'); - const hash2 = a.output.slice(3, 23); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else - hash = hash2; - } - if (a.pubkey) { - const pkh = bcrypto.hash160(a.pubkey); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - else - hash = pkh; - } - if (a.input) { - const chunks = _chunks(); - if (chunks.length !== 2) - throw new TypeError('Input is invalid'); - if (!bscript.isCanonicalScriptSignature(chunks[0])) - throw new TypeError('Input has invalid signature'); - if (!ecc.isPoint(chunks[1])) - throw new TypeError('Input has invalid pubkey'); - if (a.signature && !a.signature.equals(chunks[0])) - throw new TypeError('Signature mismatch'); - if (a.pubkey && !a.pubkey.equals(chunks[1])) - throw new TypeError('Pubkey mismatch'); - const pkh = bcrypto.hash160(chunks[1]); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - } + if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + network: typef.maybe(typef.Object), + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + output: typef.maybe(typef.BufferN(25)), + pubkey: typef.maybe(ecc.isPoint), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + input: typef.maybe(typef.Buffer), + }, + a, + ); + const _address = lazy.value(() => { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const network = a.network || networks_1.bitcoin; + const o = { network }; + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(network.pubKeyHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', () => { + if (a.output) return a.output.slice(3, 23); + if (a.address) return _address().hash; + if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([ + OPS.OP_DUP, + OPS.OP_HASH160, + o.hash, + OPS.OP_EQUALVERIFY, + OPS.OP_CHECKSIG, + ]); + }); + lazy.prop(o, 'pubkey', () => { + if (!a.input) return; + return _chunks()[1]; + }); + lazy.prop(o, 'signature', () => { + if (!a.input) return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', () => { + if (!a.pubkey) return; + if (!a.signature) return; + return bscript.compile([a.signature, a.pubkey]); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (_address().version !== network.pubKeyHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) throw new TypeError('Invalid address'); + hash = _address().hash; } - return Object.assign(o, a); + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + if (a.output) { + if ( + a.output.length !== 25 || + a.output[0] !== OPS.OP_DUP || + a.output[1] !== OPS.OP_HASH160 || + a.output[2] !== 0x14 || + a.output[23] !== OPS.OP_EQUALVERIFY || + a.output[24] !== OPS.OP_CHECKSIG + ) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(3, 23); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + if (a.pubkey) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else hash = pkh; + } + if (a.input) { + const chunks = _chunks(); + if (chunks.length !== 2) throw new TypeError('Input is invalid'); + if (!bscript.isCanonicalScriptSignature(chunks[0])) + throw new TypeError('Input has invalid signature'); + if (!ecc.isPoint(chunks[1])) + throw new TypeError('Input has invalid pubkey'); + if (a.signature && !a.signature.equals(chunks[0])) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(chunks[1])) + throw new TypeError('Pubkey mismatch'); + const pkh = bcrypto.hash160(chunks[1]); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + } + } + return Object.assign(o, a); } exports.p2pkh = p2pkh; diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index e419deb..5fe660a 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -1,185 +1,178 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const bcrypto = require("../crypto"); -const networks_1 = require("../networks"); -const bscript = require("../script"); -const lazy = require("./lazy"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bcrypto = require('../crypto'); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); const typef = require('typeforce'); const OPS = bscript.OPS; const bs58check = require('bs58check'); function stacksEqual(a, b) { - if (a.length !== b.length) - return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // input: [redeemScriptSig ...] {redeemScript} // witness: // output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL function p2sh(a, opts) { - if (!a.address && !a.hash && !a.output && !a.redeem && !a.input) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef({ + if (!a.address && !a.hash && !a.output && !a.redeem && !a.input) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + network: typef.maybe(typef.Object), + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + output: typef.maybe(typef.BufferN(23)), + redeem: typef.maybe({ network: typef.maybe(typef.Object), - address: typef.maybe(typef.String), - hash: typef.maybe(typef.BufferN(20)), - output: typef.maybe(typef.BufferN(23)), - redeem: typef.maybe({ - network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), - input: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), - }), + output: typef.maybe(typef.Buffer), input: typef.maybe(typef.Buffer), witness: typef.maybe(typef.arrayOf(typef.Buffer)), - }, a); - let network = a.network; - if (!network) { - network = (a.redeem && a.redeem.network) || networks_1.bitcoin; + }), + input: typef.maybe(typef.Buffer), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }, + a, + ); + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || networks_1.bitcoin; + } + const o = { network }; + const _address = lazy.value(() => { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const _redeem = lazy.value(() => { + const chunks = _chunks(); + return { + network, + output: chunks[chunks.length - 1], + input: bscript.compile(chunks.slice(0, -1)), + witness: a.witness || [], + }; + }); + // output dependents + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(o.network.scriptHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', () => { + // in order of least effort + if (a.output) return a.output.slice(2, 22); + if (a.address) return _address().hash; + if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); + }); + // input dependents + lazy.prop(o, 'redeem', () => { + if (!a.input) return; + return _redeem(); + }); + lazy.prop(o, 'input', () => { + if (!a.redeem || !a.redeem.input || !a.redeem.output) return; + return bscript.compile( + [].concat(bscript.decompile(a.redeem.input), a.redeem.output), + ); + }); + lazy.prop(o, 'witness', () => { + if (o.redeem && o.redeem.witness) return o.redeem.witness; + if (o.input) return []; + }); + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (_address().version !== network.scriptHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) throw new TypeError('Invalid address'); + hash = _address().hash; } - const o = { network }; - const _address = lazy.value(() => { - const payload = bs58check.decode(a.address); - const version = payload.readUInt8(0); - const hash = payload.slice(1); - return { version, hash }; - }); - const _chunks = lazy.value(() => { - return bscript.decompile(a.input); - }); - const _redeem = lazy.value(() => { - const chunks = _chunks(); - return { - network, - output: chunks[chunks.length - 1], - input: bscript.compile(chunks.slice(0, -1)), - witness: a.witness || [], - }; - }); - // output dependents - lazy.prop(o, 'address', () => { - if (!o.hash) - return; - const payload = Buffer.allocUnsafe(21); - payload.writeUInt8(o.network.scriptHash, 0); - o.hash.copy(payload, 1); - return bs58check.encode(payload); - }); - lazy.prop(o, 'hash', () => { - // in order of least effort - if (a.output) - return a.output.slice(2, 22); - if (a.address) - return _address().hash; - if (o.redeem && o.redeem.output) - return bcrypto.hash160(o.redeem.output); - }); - lazy.prop(o, 'output', () => { - if (!o.hash) - return; - return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); - }); - // input dependents - lazy.prop(o, 'redeem', () => { - if (!a.input) - return; - return _redeem(); - }); - lazy.prop(o, 'input', () => { - if (!a.redeem || !a.redeem.input || !a.redeem.output) - return; - return bscript.compile([].concat(bscript.decompile(a.redeem.input), a.redeem.output)); - }); - lazy.prop(o, 'witness', () => { - if (o.redeem && o.redeem.witness) - return o.redeem.witness; - if (o.input) - return []; - }); - if (opts.validate) { - let hash = Buffer.from([]); - if (a.address) { - if (_address().version !== network.scriptHash) - throw new TypeError('Invalid version or Network mismatch'); - if (_address().hash.length !== 20) - throw new TypeError('Invalid address'); - hash = _address().hash; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else - hash = a.hash; - } - if (a.output) { - if (a.output.length !== 23 || - a.output[0] !== OPS.OP_HASH160 || - a.output[1] !== 0x14 || - a.output[22] !== OPS.OP_EQUAL) - throw new TypeError('Output is invalid'); - const hash2 = a.output.slice(2, 22); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else - hash = hash2; - } - // inlined to prevent 'no-inner-declarations' failing - const checkRedeem = (redeem) => { - // is the redeem output empty/invalid? - if (redeem.output) { - const decompile = bscript.decompile(redeem.output); - if (!decompile || decompile.length < 1) - throw new TypeError('Redeem.output too short'); - // match hash against other sources - const hash2 = bcrypto.hash160(redeem.output); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else - hash = hash2; - } - if (redeem.input) { - const hasInput = redeem.input.length > 0; - const hasWitness = redeem.witness && redeem.witness.length > 0; - if (!hasInput && !hasWitness) - throw new TypeError('Empty input'); - if (hasInput && hasWitness) - throw new TypeError('Input and witness provided'); - if (hasInput) { - const richunks = bscript.decompile(redeem.input); - if (!bscript.isPushOnly(richunks)) - throw new TypeError('Non push-only scriptSig'); - } - } - }; - if (a.input) { - const chunks = _chunks(); - if (!chunks || chunks.length < 1) - throw new TypeError('Input too short'); - if (!Buffer.isBuffer(_redeem().output)) - throw new TypeError('Input is invalid'); - checkRedeem(_redeem()); - } - if (a.redeem) { - if (a.redeem.network && a.redeem.network !== network) - throw new TypeError('Network mismatch'); - if (a.input) { - const redeem = _redeem(); - if (a.redeem.output && !a.redeem.output.equals(redeem.output)) - throw new TypeError('Redeem.output mismatch'); - if (a.redeem.input && !a.redeem.input.equals(redeem.input)) - throw new TypeError('Redeem.input mismatch'); - } - checkRedeem(a.redeem); - } - if (a.witness) { - if (a.redeem && - a.redeem.witness && - !stacksEqual(a.redeem.witness, a.witness)) - throw new TypeError('Witness and redeem.witness mismatch'); - } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; } - return Object.assign(o, a); + if (a.output) { + if ( + a.output.length !== 23 || + a.output[0] !== OPS.OP_HASH160 || + a.output[1] !== 0x14 || + a.output[22] !== OPS.OP_EQUAL + ) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(2, 22); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + // inlined to prevent 'no-inner-declarations' failing + const checkRedeem = redeem => { + // is the redeem output empty/invalid? + if (redeem.output) { + const decompile = bscript.decompile(redeem.output); + if (!decompile || decompile.length < 1) + throw new TypeError('Redeem.output too short'); + // match hash against other sources + const hash2 = bcrypto.hash160(redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + if (redeem.input) { + const hasInput = redeem.input.length > 0; + const hasWitness = redeem.witness && redeem.witness.length > 0; + if (!hasInput && !hasWitness) throw new TypeError('Empty input'); + if (hasInput && hasWitness) + throw new TypeError('Input and witness provided'); + if (hasInput) { + const richunks = bscript.decompile(redeem.input); + if (!bscript.isPushOnly(richunks)) + throw new TypeError('Non push-only scriptSig'); + } + } + }; + if (a.input) { + const chunks = _chunks(); + if (!chunks || chunks.length < 1) throw new TypeError('Input too short'); + if (!Buffer.isBuffer(_redeem().output)) + throw new TypeError('Input is invalid'); + checkRedeem(_redeem()); + } + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + if (a.input) { + const redeem = _redeem(); + if (a.redeem.output && !a.redeem.output.equals(redeem.output)) + throw new TypeError('Redeem.output mismatch'); + if (a.redeem.input && !a.redeem.input.equals(redeem.input)) + throw new TypeError('Redeem.input mismatch'); + } + checkRedeem(a.redeem); + } + if (a.witness) { + if ( + a.redeem && + a.redeem.witness && + !stacksEqual(a.redeem.witness, a.witness) + ) + throw new TypeError('Witness and redeem.witness mismatch'); + } + } + return Object.assign(o, a); } exports.p2sh = p2sh; diff --git a/src/payments/p2wpkh.js b/src/payments/p2wpkh.js index 9e99610..9571e50 100644 --- a/src/payments/p2wpkh.js +++ b/src/payments/p2wpkh.js @@ -1,9 +1,9 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const bcrypto = require("../crypto"); -const networks_1 = require("../networks"); -const bscript = require("../script"); -const lazy = require("./lazy"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bcrypto = require('../crypto'); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); const typef = require('typeforce'); const OPS = bscript.OPS; const ecc = require('tiny-secp256k1'); @@ -13,126 +13,116 @@ const EMPTY_BUFFER = Buffer.alloc(0); // input: <> // output: OP_0 {pubKeyHash} function p2wpkh(a, opts) { - if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef({ - address: typef.maybe(typef.String), - hash: typef.maybe(typef.BufferN(20)), - input: typef.maybe(typef.BufferN(0)), - network: typef.maybe(typef.Object), - output: typef.maybe(typef.BufferN(22)), - pubkey: typef.maybe(ecc.isPoint), - signature: typef.maybe(bscript.isCanonicalScriptSignature), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), - }, a); - const _address = lazy.value(() => { - const result = bech32.decode(a.address); - const version = result.words.shift(); - const data = bech32.fromWords(result.words); - return { - version, - prefix: result.prefix, - data: Buffer.from(data), - }; - }); - const network = a.network || networks_1.bitcoin; - const o = { network }; - lazy.prop(o, 'address', () => { - if (!o.hash) - return; - const words = bech32.toWords(o.hash); - words.unshift(0x00); - return bech32.encode(network.bech32, words); - }); - lazy.prop(o, 'hash', () => { - if (a.output) - return a.output.slice(2, 22); - if (a.address) - return _address().data; - if (a.pubkey || o.pubkey) - return bcrypto.hash160(a.pubkey || o.pubkey); - }); - lazy.prop(o, 'output', () => { - if (!o.hash) - return; - return bscript.compile([OPS.OP_0, o.hash]); - }); - lazy.prop(o, 'pubkey', () => { - if (a.pubkey) - return a.pubkey; - if (!a.witness) - return; - return a.witness[1]; - }); - lazy.prop(o, 'signature', () => { - if (!a.witness) - return; - return a.witness[0]; - }); - lazy.prop(o, 'input', () => { - if (!o.witness) - return; - return EMPTY_BUFFER; - }); - lazy.prop(o, 'witness', () => { - if (!a.pubkey) - return; - if (!a.signature) - return; - return [a.signature, a.pubkey]; - }); - // extended validation - if (opts.validate) { - let hash = Buffer.from([]); - if (a.address) { - if (network && network.bech32 !== _address().prefix) - throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== 0x00) - throw new TypeError('Invalid address version'); - if (_address().data.length !== 20) - throw new TypeError('Invalid address data'); - hash = _address().data; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else - hash = a.hash; - } - if (a.output) { - if (a.output.length !== 22 || - a.output[0] !== OPS.OP_0 || - a.output[1] !== 0x14) - throw new TypeError('Output is invalid'); - if (hash.length > 0 && !hash.equals(a.output.slice(2))) - throw new TypeError('Hash mismatch'); - else - hash = a.output.slice(2); - } - if (a.pubkey) { - const pkh = bcrypto.hash160(a.pubkey); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - else - hash = pkh; - } - if (a.witness) { - if (a.witness.length !== 2) - throw new TypeError('Witness is invalid'); - if (!bscript.isCanonicalScriptSignature(a.witness[0])) - throw new TypeError('Witness has invalid signature'); - if (!ecc.isPoint(a.witness[1])) - throw new TypeError('Witness has invalid pubkey'); - if (a.signature && !a.signature.equals(a.witness[0])) - throw new TypeError('Signature mismatch'); - if (a.pubkey && !a.pubkey.equals(a.witness[1])) - throw new TypeError('Pubkey mismatch'); - const pkh = bcrypto.hash160(a.witness[1]); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - } + if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + input: typef.maybe(typef.BufferN(0)), + network: typef.maybe(typef.Object), + output: typef.maybe(typef.BufferN(22)), + pubkey: typef.maybe(ecc.isPoint), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }, + a, + ); + const _address = lazy.value(() => { + const result = bech32.decode(a.address); + const version = result.words.shift(); + const data = bech32.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + const network = a.network || networks_1.bitcoin; + const o = { network }; + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.output) return a.output.slice(2, 22); + if (a.address) return _address().data; + if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'pubkey', () => { + if (a.pubkey) return a.pubkey; + if (!a.witness) return; + return a.witness[1]; + }); + lazy.prop(o, 'signature', () => { + if (!a.witness) return; + return a.witness[0]; + }); + lazy.prop(o, 'input', () => { + if (!o.witness) return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', () => { + if (!a.pubkey) return; + if (!a.signature) return; + return [a.signature, a.pubkey]; + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 20) + throw new TypeError('Invalid address data'); + hash = _address().data; } - return Object.assign(o, a); + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + if (a.output) { + if ( + a.output.length !== 22 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x14 + ) + throw new TypeError('Output is invalid'); + if (hash.length > 0 && !hash.equals(a.output.slice(2))) + throw new TypeError('Hash mismatch'); + else hash = a.output.slice(2); + } + if (a.pubkey) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else hash = pkh; + } + if (a.witness) { + if (a.witness.length !== 2) throw new TypeError('Witness is invalid'); + if (!bscript.isCanonicalScriptSignature(a.witness[0])) + throw new TypeError('Witness has invalid signature'); + if (!ecc.isPoint(a.witness[1])) + throw new TypeError('Witness has invalid pubkey'); + if (a.signature && !a.signature.equals(a.witness[0])) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(a.witness[1])) + throw new TypeError('Pubkey mismatch'); + const pkh = bcrypto.hash160(a.witness[1]); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + } + } + return Object.assign(o, a); } exports.p2wpkh = p2wpkh; diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index 0def430..9363718 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -1,177 +1,176 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const bcrypto = require("../crypto"); -const networks_1 = require("../networks"); -const bscript = require("../script"); -const lazy = require("./lazy"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bcrypto = require('../crypto'); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); const typef = require('typeforce'); const OPS = bscript.OPS; const bech32 = require('bech32'); const EMPTY_BUFFER = Buffer.alloc(0); function stacksEqual(a, b) { - if (a.length !== b.length) - return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // input: <> // witness: [redeemScriptSig ...] {redeemScript} // output: OP_0 {sha256(redeemScript)} function p2wsh(a, opts) { - if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef({ + if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + network: typef.maybe(typef.Object), + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(32)), + output: typef.maybe(typef.BufferN(34)), + redeem: typef.maybe({ + input: typef.maybe(typef.Buffer), network: typef.maybe(typef.Object), - address: typef.maybe(typef.String), - hash: typef.maybe(typef.BufferN(32)), - output: typef.maybe(typef.BufferN(34)), - redeem: typef.maybe({ - input: typef.maybe(typef.Buffer), - network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), - }), - input: typef.maybe(typef.BufferN(0)), + output: typef.maybe(typef.Buffer), witness: typef.maybe(typef.arrayOf(typef.Buffer)), - }, a); - const _address = lazy.value(() => { - const result = bech32.decode(a.address); - const version = result.words.shift(); - const data = bech32.fromWords(result.words); - return { - version, - prefix: result.prefix, - data: Buffer.from(data), - }; - }); - const _rchunks = lazy.value(() => { - return bscript.decompile(a.redeem.input); - }); - let network = a.network; - if (!network) { - network = (a.redeem && a.redeem.network) || networks_1.bitcoin; + }), + input: typef.maybe(typef.BufferN(0)), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }, + a, + ); + const _address = lazy.value(() => { + const result = bech32.decode(a.address); + const version = result.words.shift(); + const data = bech32.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + const _rchunks = lazy.value(() => { + return bscript.decompile(a.redeem.input); + }); + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || networks_1.bitcoin; + } + const o = { network }; + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.output) return a.output.slice(2); + if (a.address) return _address().data; + if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'redeem', () => { + if (!a.witness) return; + return { + output: a.witness[a.witness.length - 1], + input: EMPTY_BUFFER, + witness: a.witness.slice(0, -1), + }; + }); + lazy.prop(o, 'input', () => { + if (!o.witness) return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', () => { + // transform redeem input to witness stack? + if ( + a.redeem && + a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.output && + a.redeem.output.length > 0 + ) { + const stack = bscript.toStack(_rchunks()); + // assign, and blank the existing input + o.redeem = Object.assign({ witness: stack }, a.redeem); + o.redeem.input = EMPTY_BUFFER; + return [].concat(stack, a.redeem.output); } - const o = { network }; - lazy.prop(o, 'address', () => { - if (!o.hash) - return; - const words = bech32.toWords(o.hash); - words.unshift(0x00); - return bech32.encode(network.bech32, words); - }); - lazy.prop(o, 'hash', () => { - if (a.output) - return a.output.slice(2); - if (a.address) - return _address().data; - if (o.redeem && o.redeem.output) - return bcrypto.sha256(o.redeem.output); - }); - lazy.prop(o, 'output', () => { - if (!o.hash) - return; - return bscript.compile([OPS.OP_0, o.hash]); - }); - lazy.prop(o, 'redeem', () => { - if (!a.witness) - return; - return { - output: a.witness[a.witness.length - 1], - input: EMPTY_BUFFER, - witness: a.witness.slice(0, -1), - }; - }); - lazy.prop(o, 'input', () => { - if (!o.witness) - return; - return EMPTY_BUFFER; - }); - lazy.prop(o, 'witness', () => { - // transform redeem input to witness stack? - if (a.redeem && - a.redeem.input && - a.redeem.input.length > 0 && - a.redeem.output && - a.redeem.output.length > 0) { - const stack = bscript.toStack(_rchunks()); - // assign, and blank the existing input - o.redeem = Object.assign({ witness: stack }, a.redeem); - o.redeem.input = EMPTY_BUFFER; - return [].concat(stack, a.redeem.output); - } - if (!a.redeem) - return; - if (!a.redeem.output) - return; - if (!a.redeem.witness) - return; - return [].concat(a.redeem.witness, a.redeem.output); - }); - // extended validation - if (opts.validate) { - let hash = Buffer.from([]); - if (a.address) { - if (_address().prefix !== network.bech32) - throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== 0x00) - throw new TypeError('Invalid address version'); - if (_address().data.length !== 32) - throw new TypeError('Invalid address data'); - hash = _address().data; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else - hash = a.hash; - } - if (a.output) { - if (a.output.length !== 34 || - a.output[0] !== OPS.OP_0 || - a.output[1] !== 0x20) - throw new TypeError('Output is invalid'); - const hash2 = a.output.slice(2); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else - hash = hash2; - } - if (a.redeem) { - if (a.redeem.network && a.redeem.network !== network) - throw new TypeError('Network mismatch'); - // is there two redeem sources? - if (a.redeem.input && - a.redeem.input.length > 0 && - a.redeem.witness && - a.redeem.witness.length > 0) - throw new TypeError('Ambiguous witness source'); - // is the redeem output non-empty? - if (a.redeem.output) { - if (bscript.decompile(a.redeem.output).length === 0) - throw new TypeError('Redeem.output is invalid'); - // match hash against other sources - const hash2 = bcrypto.sha256(a.redeem.output); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else - hash = hash2; - } - if (a.redeem.input && !bscript.isPushOnly(_rchunks())) - throw new TypeError('Non push-only scriptSig'); - if (a.witness && - a.redeem.witness && - !stacksEqual(a.witness, a.redeem.witness)) - throw new TypeError('Witness and redeem.witness mismatch'); - } - if (a.witness) { - if (a.redeem && - a.redeem.output && - !a.redeem.output.equals(a.witness[a.witness.length - 1])) - throw new TypeError('Witness and redeem.output mismatch'); - } + if (!a.redeem) return; + if (!a.redeem.output) return; + if (!a.redeem.witness) return; + return [].concat(a.redeem.witness, a.redeem.output); + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (_address().prefix !== network.bech32) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + hash = _address().data; } - return Object.assign(o, a); + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + if (a.output) { + if ( + a.output.length !== 34 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x20 + ) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(2); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + // is there two redeem sources? + if ( + a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.witness && + a.redeem.witness.length > 0 + ) + throw new TypeError('Ambiguous witness source'); + // is the redeem output non-empty? + if (a.redeem.output) { + if (bscript.decompile(a.redeem.output).length === 0) + throw new TypeError('Redeem.output is invalid'); + // match hash against other sources + const hash2 = bcrypto.sha256(a.redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + if (a.redeem.input && !bscript.isPushOnly(_rchunks())) + throw new TypeError('Non push-only scriptSig'); + if ( + a.witness && + a.redeem.witness && + !stacksEqual(a.witness, a.redeem.witness) + ) + throw new TypeError('Witness and redeem.witness mismatch'); + } + if (a.witness) { + if ( + a.redeem && + a.redeem.output && + !a.redeem.output.equals(a.witness[a.witness.length - 1]) + ) + throw new TypeError('Witness and redeem.output mismatch'); + } + } + return Object.assign(o, a); } exports.p2wsh = p2wsh; diff --git a/src/script.js b/src/script.js index ac334f8..39859dc 100644 --- a/src/script.js +++ b/src/script.js @@ -1,8 +1,8 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const scriptNumber = require("./script_number"); -const scriptSignature = require("./script_signature"); -const types = require("./types"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const scriptNumber = require('./script_number'); +const scriptSignature = require('./script_signature'); +const types = require('./types'); const bip66 = require('bip66'); const ecc = require('tiny-secp256k1'); const pushdata = require('pushdata-bitcoin'); @@ -11,179 +11,165 @@ exports.OPS = require('bitcoin-ops'); const REVERSE_OPS = require('bitcoin-ops/map'); const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1 function isOPInt(value) { - return (types.Number(value) && - (value === exports.OPS.OP_0 || - (value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) || - value === exports.OPS.OP_1NEGATE)); + return ( + types.Number(value) && + (value === exports.OPS.OP_0 || + (value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) || + value === exports.OPS.OP_1NEGATE) + ); } function isPushOnlyChunk(value) { - return types.Buffer(value) || isOPInt(value); + return types.Buffer(value) || isOPInt(value); } function isPushOnly(value) { - return types.Array(value) && value.every(isPushOnlyChunk); + return types.Array(value) && value.every(isPushOnlyChunk); } exports.isPushOnly = isPushOnly; function asMinimalOP(buffer) { - if (buffer.length === 0) - return exports.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 exports.OPS.OP_1NEGATE; + if (buffer.length === 0) return exports.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 exports.OPS.OP_1NEGATE; } function chunksIsBuffer(buf) { - return Buffer.isBuffer(buf); + return Buffer.isBuffer(buf); } function chunksIsArray(buf) { - return types.Array(buf); + return types.Array(buf); } function singleChunkIsBuffer(buf) { - return Buffer.isBuffer(buf); + return Buffer.isBuffer(buf); } function compile(chunks) { - // TODO: remove me - if (chunksIsBuffer(chunks)) - return chunks; - typeforce(types.Array, 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 + // TODO: remove me + if (chunksIsBuffer(chunks)) return chunks; + typeforce(types.Array, 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; - }, 0.0); - const buffer = Buffer.allocUnsafe(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) { - buffer.writeUInt8(opcode, offset); - offset += 1; - return; - } - offset += pushdata.encode(buffer, chunk.length, offset); - chunk.copy(buffer, offset); - offset += chunk.length; - // opcode - } - else { - buffer.writeUInt8(chunk, offset); - offset += 1; - } - }); - if (offset !== buffer.length) - throw new Error('Could not decode chunks'); - return buffer; + } + return accum + pushdata.encodingLength(chunk.length) + chunk.length; + } + // opcode + return accum + 1; + }, 0.0); + const buffer = Buffer.allocUnsafe(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) { + buffer.writeUInt8(opcode, offset); + offset += 1; + return; + } + offset += pushdata.encode(buffer, chunk.length, offset); + chunk.copy(buffer, offset); + offset += chunk.length; + // opcode + } else { + buffer.writeUInt8(chunk, offset); + offset += 1; + } + }); + if (offset !== buffer.length) throw new Error('Could not decode chunks'); + return buffer; } exports.compile = compile; function decompile(buffer) { - // TODO: remove me - if (chunksIsArray(buffer)) - return buffer; - typeforce(types.Buffer, buffer); - const chunks = []; - let i = 0; - while (i < buffer.length) { - const opcode = buffer[i]; - // data chunk - if (opcode > exports.OPS.OP_0 && opcode <= exports.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; - // decompile minimally - const op = asMinimalOP(data); - if (op !== undefined) { - chunks.push(op); - } - else { - chunks.push(data); - } - // opcode - } - else { - chunks.push(opcode); - i += 1; - } + // TODO: remove me + if (chunksIsArray(buffer)) return buffer; + typeforce(types.Buffer, buffer); + const chunks = []; + let i = 0; + while (i < buffer.length) { + const opcode = buffer[i]; + // data chunk + if (opcode > exports.OPS.OP_0 && opcode <= exports.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; + // decompile minimally + const op = asMinimalOP(data); + if (op !== undefined) { + chunks.push(op); + } else { + chunks.push(data); + } + // opcode + } else { + chunks.push(opcode); + i += 1; } - return chunks; + } + return chunks; } exports.decompile = decompile; function toASM(chunks) { - if (chunksIsBuffer(chunks)) { - chunks = decompile(chunks); - } - return chunks - .map(chunk => { - // data? - if (singleChunkIsBuffer(chunk)) { - const op = asMinimalOP(chunk); - if (op === undefined) - return chunk.toString('hex'); - chunk = op; - } - // opcode! - return REVERSE_OPS[chunk]; + if (chunksIsBuffer(chunks)) { + chunks = decompile(chunks); + } + return chunks + .map(chunk => { + // data? + if (singleChunkIsBuffer(chunk)) { + const op = asMinimalOP(chunk); + if (op === undefined) return chunk.toString('hex'); + chunk = op; + } + // opcode! + return REVERSE_OPS[chunk]; }) - .join(' '); + .join(' '); } exports.toASM = toASM; function fromASM(asm) { - typeforce(types.String, asm); - return compile(asm.split(' ').map(chunkStr => { - // opcode? - if (exports.OPS[chunkStr] !== undefined) - return exports.OPS[chunkStr]; - typeforce(types.Hex, chunkStr); - // data! - return Buffer.from(chunkStr, 'hex'); - })); + typeforce(types.String, asm); + return compile( + asm.split(' ').map(chunkStr => { + // opcode? + if (exports.OPS[chunkStr] !== undefined) return exports.OPS[chunkStr]; + typeforce(types.Hex, chunkStr); + // data! + return Buffer.from(chunkStr, 'hex'); + }), + ); } exports.fromASM = fromASM; function toStack(chunks) { - chunks = decompile(chunks); - typeforce(isPushOnly, chunks); - return chunks.map(op => { - if (singleChunkIsBuffer(op)) - return op; - if (op === exports.OPS.OP_0) - return Buffer.allocUnsafe(0); - return scriptNumber.encode(op - OP_INT_BASE); - }); + chunks = decompile(chunks); + typeforce(isPushOnly, chunks); + return chunks.map(op => { + if (singleChunkIsBuffer(op)) return op; + if (op === exports.OPS.OP_0) return Buffer.allocUnsafe(0); + return scriptNumber.encode(op - OP_INT_BASE); + }); } exports.toStack = toStack; function isCanonicalPubKey(buffer) { - return ecc.isPoint(buffer); + return ecc.isPoint(buffer); } exports.isCanonicalPubKey = isCanonicalPubKey; function isDefinedHashType(hashType) { - const hashTypeMod = hashType & ~0x80; - // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE - return hashTypeMod > 0x00 && hashTypeMod < 0x04; + const hashTypeMod = hashType & ~0x80; + // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE + return hashTypeMod > 0x00 && hashTypeMod < 0x04; } exports.isDefinedHashType = isDefinedHashType; function isCanonicalScriptSignature(buffer) { - if (!Buffer.isBuffer(buffer)) - return false; - if (!isDefinedHashType(buffer[buffer.length - 1])) - return false; - return bip66.check(buffer.slice(0, -1)); + if (!Buffer.isBuffer(buffer)) return false; + if (!isDefinedHashType(buffer[buffer.length - 1])) return false; + return bip66.check(buffer.slice(0, -1)); } exports.isCanonicalScriptSignature = isCanonicalScriptSignature; // tslint:disable-next-line variable-name diff --git a/src/script_number.js b/src/script_number.js index 3a30a43..3f313af 100644 --- a/src/script_number.js +++ b/src/script_number.js @@ -1,65 +1,61 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); function decode(buffer, maxLength, minimal) { - maxLength = maxLength || 4; - minimal = minimal === undefined ? true : minimal; - const length = buffer.length; - if (length === 0) - return 0; - if (length > maxLength) - throw new TypeError('Script number overflow'); - if (minimal) { - if ((buffer[length - 1] & 0x7f) === 0) { - if (length <= 1 || (buffer[length - 2] & 0x80) === 0) - throw new Error('Non-minimally encoded script number'); - } + maxLength = maxLength || 4; + minimal = minimal === undefined ? true : minimal; + const length = buffer.length; + if (length === 0) return 0; + if (length > maxLength) throw new TypeError('Script number overflow'); + if (minimal) { + if ((buffer[length - 1] & 0x7f) === 0) { + if (length <= 1 || (buffer[length - 2] & 0x80) === 0) + throw new Error('Non-minimally encoded script number'); } - // 40-bit - if (length === 5) { - const a = buffer.readUInt32LE(0); - const b = buffer.readUInt8(4); - if (b & 0x80) - return -((b & ~0x80) * 0x100000000 + a); - return b * 0x100000000 + a; - } - // 32-bit / 24-bit / 16-bit / 8-bit - let result = 0; - for (let i = 0; i < length; ++i) { - result |= buffer[i] << (8 * i); - } - if (buffer[length - 1] & 0x80) - return -(result & ~(0x80 << (8 * (length - 1)))); - return result; + } + // 40-bit + if (length === 5) { + const a = buffer.readUInt32LE(0); + const b = buffer.readUInt8(4); + if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a); + return b * 0x100000000 + a; + } + // 32-bit / 24-bit / 16-bit / 8-bit + let result = 0; + for (let i = 0; i < length; ++i) { + result |= buffer[i] << (8 * i); + } + if (buffer[length - 1] & 0x80) + return -(result & ~(0x80 << (8 * (length - 1)))); + return result; } exports.decode = decode; function scriptNumSize(i) { - return i > 0x7fffffff - ? 5 - : i > 0x7fffff - ? 4 - : i > 0x7fff - ? 3 - : i > 0x7f - ? 2 - : i > 0x00 - ? 1 - : 0; + return i > 0x7fffffff + ? 5 + : i > 0x7fffff + ? 4 + : i > 0x7fff + ? 3 + : i > 0x7f + ? 2 + : i > 0x00 + ? 1 + : 0; } function encode(_number) { - let value = Math.abs(_number); - const size = scriptNumSize(value); - const buffer = Buffer.allocUnsafe(size); - const negative = _number < 0; - for (let i = 0; i < size; ++i) { - buffer.writeUInt8(value & 0xff, i); - value >>= 8; - } - if (buffer[size - 1] & 0x80) { - buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); - } - else if (negative) { - buffer[size - 1] |= 0x80; - } - return buffer; + let value = Math.abs(_number); + const size = scriptNumSize(value); + const buffer = Buffer.allocUnsafe(size); + const negative = _number < 0; + for (let i = 0; i < size; ++i) { + buffer.writeUInt8(value & 0xff, i); + value >>= 8; + } + if (buffer[size - 1] & 0x80) { + buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); + } else if (negative) { + buffer[size - 1] |= 0x80; + } + return buffer; } exports.encode = encode; diff --git a/src/script_signature.js b/src/script_signature.js index a1f2f22..fb52fe9 100644 --- a/src/script_signature.js +++ b/src/script_signature.js @@ -1,53 +1,52 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const types = require("./types"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const types = require('./types'); const bip66 = require('bip66'); const typeforce = require('typeforce'); const ZERO = Buffer.alloc(1, 0); function toDER(x) { - let i = 0; - while (x[i] === 0) - ++i; - if (i === x.length) - return ZERO; - x = x.slice(i); - if (x[0] & 0x80) - return Buffer.concat([ZERO, x], 1 + x.length); - return x; + let i = 0; + while (x[i] === 0) ++i; + if (i === x.length) return ZERO; + x = x.slice(i); + if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length); + return x; } function fromDER(x) { - if (x[0] === 0x00) - x = x.slice(1); - const buffer = Buffer.alloc(32, 0); - const bstart = Math.max(0, 32 - x.length); - x.copy(buffer, bstart); - return buffer; + if (x[0] === 0x00) x = x.slice(1); + const buffer = Buffer.alloc(32, 0); + const bstart = Math.max(0, 32 - x.length); + x.copy(buffer, bstart); + return buffer; } // BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) function decode(buffer) { - const hashType = buffer.readUInt8(buffer.length - 1); - const hashTypeMod = hashType & ~0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) - throw new Error('Invalid hashType ' + hashType); - const decoded = bip66.decode(buffer.slice(0, -1)); - const r = fromDER(decoded.r); - const s = fromDER(decoded.s); - const signature = Buffer.concat([r, s], 64); - return { signature, hashType }; + const hashType = buffer.readUInt8(buffer.length - 1); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + const decoded = bip66.decode(buffer.slice(0, -1)); + const r = fromDER(decoded.r); + const s = fromDER(decoded.s); + const signature = Buffer.concat([r, s], 64); + return { signature, hashType }; } exports.decode = decode; function encode(signature, hashType) { - typeforce({ - signature: types.BufferN(64), - hashType: types.UInt8, - }, { signature, hashType }); - const hashTypeMod = hashType & ~0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) - throw new Error('Invalid hashType ' + hashType); - const hashTypeBuffer = Buffer.allocUnsafe(1); - hashTypeBuffer.writeUInt8(hashType, 0); - const r = toDER(signature.slice(0, 32)); - const s = toDER(signature.slice(32, 64)); - return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); + typeforce( + { + signature: types.BufferN(64), + hashType: types.UInt8, + }, + { signature, hashType }, + ); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + const hashTypeBuffer = Buffer.allocUnsafe(1); + hashTypeBuffer.writeUInt8(hashType, 0); + const r = toDER(signature.slice(0, 32)); + const s = toDER(signature.slice(32, 64)); + return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); } exports.encode = encode; diff --git a/src/templates/multisig/index.js b/src/templates/multisig/index.js index 85a15b9..b8cd6c4 100644 --- a/src/templates/multisig/index.js +++ b/src/templates/multisig/index.js @@ -1,6 +1,6 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = require("./input"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); exports.input = input; -const output = require("./output"); +const output = require('./output'); exports.output = output; diff --git a/src/templates/multisig/input.js b/src/templates/multisig/input.js index 4b4f395..403c2f7 100644 --- a/src/templates/multisig/input.js +++ b/src/templates/multisig/input.js @@ -1,23 +1,23 @@ -"use strict"; +'use strict'; // OP_0 [signatures ...] -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); -const script_1 = require("../../script"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); function partialSignature(value) { - return (value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value)); + return ( + value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value) + ); } function check(script, allowIncomplete) { - const chunks = bscript.decompile(script); - if (chunks.length < 2) - return false; - if (chunks[0] !== script_1.OPS.OP_0) - return false; - if (allowIncomplete) { - return chunks.slice(1).every(partialSignature); - } - return chunks.slice(1).every(bscript.isCanonicalScriptSignature); + const chunks = bscript.decompile(script); + if (chunks.length < 2) return false; + if (chunks[0] !== script_1.OPS.OP_0) return false; + if (allowIncomplete) { + return chunks.slice(1).every(partialSignature); + } + return chunks.slice(1).every(bscript.isCanonicalScriptSignature); } exports.check = check; check.toJSON = () => { - return 'multisig input'; + return 'multisig input'; }; diff --git a/src/templates/multisig/output.js b/src/templates/multisig/output.js index c79fe9b..0896605 100644 --- a/src/templates/multisig/output.js +++ b/src/templates/multisig/output.js @@ -1,36 +1,27 @@ -"use strict"; +'use strict'; // m [pubKeys ...] n OP_CHECKMULTISIG -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); -const script_1 = require("../../script"); -const types = require("../../types"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); +const types = require('../../types'); const OP_INT_BASE = script_1.OPS.OP_RESERVED; // OP_1 - 1 function check(script, allowIncomplete) { - const chunks = bscript.decompile(script); - if (chunks.length < 4) - return false; - if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) - return false; - if (!types.Number(chunks[0])) - return false; - if (!types.Number(chunks[chunks.length - 2])) - return false; - const m = chunks[0] - OP_INT_BASE; - const n = chunks[chunks.length - 2] - OP_INT_BASE; - if (m <= 0) - return false; - if (n > 16) - return false; - if (m > n) - return false; - if (n !== chunks.length - 3) - return false; - if (allowIncomplete) - return true; - const keys = chunks.slice(1, -2); - return keys.every(bscript.isCanonicalPubKey); + const chunks = bscript.decompile(script); + if (chunks.length < 4) return false; + if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) return false; + if (!types.Number(chunks[0])) return false; + if (!types.Number(chunks[chunks.length - 2])) return false; + const m = chunks[0] - OP_INT_BASE; + const n = chunks[chunks.length - 2] - OP_INT_BASE; + if (m <= 0) return false; + if (n > 16) return false; + if (m > n) return false; + if (n !== chunks.length - 3) return false; + if (allowIncomplete) return true; + const keys = chunks.slice(1, -2); + return keys.every(bscript.isCanonicalPubKey); } exports.check = check; check.toJSON = () => { - return 'multi-sig output'; + return 'multi-sig output'; }; diff --git a/src/templates/nulldata.js b/src/templates/nulldata.js index 29bee7a..50355d3 100644 --- a/src/templates/nulldata.js +++ b/src/templates/nulldata.js @@ -1,15 +1,15 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); // OP_RETURN {data} -const bscript = require("../script"); +const bscript = require('../script'); const OPS = bscript.OPS; function check(script) { - const buffer = bscript.compile(script); - return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; + const buffer = bscript.compile(script); + return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; } exports.check = check; check.toJSON = () => { - return 'null data output'; + return 'null data output'; }; const output = { check }; exports.output = output; diff --git a/src/templates/pubkey/index.js b/src/templates/pubkey/index.js index 85a15b9..b8cd6c4 100644 --- a/src/templates/pubkey/index.js +++ b/src/templates/pubkey/index.js @@ -1,6 +1,6 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = require("./input"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); exports.input = input; -const output = require("./output"); +const output = require('./output'); exports.output = output; diff --git a/src/templates/pubkey/input.js b/src/templates/pubkey/input.js index 479fdc5..9715b80 100644 --- a/src/templates/pubkey/input.js +++ b/src/templates/pubkey/input.js @@ -1,13 +1,12 @@ -"use strict"; +'use strict'; // {signature} -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); function check(script) { - const chunks = bscript.decompile(script); - return (chunks.length === 1 && - bscript.isCanonicalScriptSignature(chunks[0])); + const chunks = bscript.decompile(script); + return chunks.length === 1 && bscript.isCanonicalScriptSignature(chunks[0]); } exports.check = check; check.toJSON = () => { - return 'pubKey input'; + return 'pubKey input'; }; diff --git a/src/templates/pubkey/output.js b/src/templates/pubkey/output.js index 1f17990..2edb731 100644 --- a/src/templates/pubkey/output.js +++ b/src/templates/pubkey/output.js @@ -1,15 +1,17 @@ -"use strict"; +'use strict'; // {pubKey} OP_CHECKSIG -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); -const script_1 = require("../../script"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); function check(script) { - const chunks = bscript.decompile(script); - return (chunks.length === 2 && - bscript.isCanonicalPubKey(chunks[0]) && - chunks[1] === script_1.OPS.OP_CHECKSIG); + const chunks = bscript.decompile(script); + return ( + chunks.length === 2 && + bscript.isCanonicalPubKey(chunks[0]) && + chunks[1] === script_1.OPS.OP_CHECKSIG + ); } exports.check = check; check.toJSON = () => { - return 'pubKey output'; + return 'pubKey output'; }; diff --git a/src/templates/pubkeyhash/index.js b/src/templates/pubkeyhash/index.js index 85a15b9..b8cd6c4 100644 --- a/src/templates/pubkeyhash/index.js +++ b/src/templates/pubkeyhash/index.js @@ -1,6 +1,6 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = require("./input"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); exports.input = input; -const output = require("./output"); +const output = require('./output'); exports.output = output; diff --git a/src/templates/pubkeyhash/input.js b/src/templates/pubkeyhash/input.js index 7de30ec..14d72cc 100644 --- a/src/templates/pubkeyhash/input.js +++ b/src/templates/pubkeyhash/input.js @@ -1,14 +1,16 @@ -"use strict"; +'use strict'; // {signature} {pubKey} -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); function check(script) { - const chunks = bscript.decompile(script); - return (chunks.length === 2 && - bscript.isCanonicalScriptSignature(chunks[0]) && - bscript.isCanonicalPubKey(chunks[1])); + const chunks = bscript.decompile(script); + return ( + chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0]) && + bscript.isCanonicalPubKey(chunks[1]) + ); } exports.check = check; check.toJSON = () => { - return 'pubKeyHash input'; + return 'pubKeyHash input'; }; diff --git a/src/templates/pubkeyhash/output.js b/src/templates/pubkeyhash/output.js index 5ee692b..079e1ed 100644 --- a/src/templates/pubkeyhash/output.js +++ b/src/templates/pubkeyhash/output.js @@ -1,18 +1,20 @@ -"use strict"; +'use strict'; // OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); -const script_1 = require("../../script"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); function check(script) { - const buffer = bscript.compile(script); - return (buffer.length === 25 && - buffer[0] === script_1.OPS.OP_DUP && - buffer[1] === script_1.OPS.OP_HASH160 && - buffer[2] === 0x14 && - buffer[23] === script_1.OPS.OP_EQUALVERIFY && - buffer[24] === script_1.OPS.OP_CHECKSIG); + const buffer = bscript.compile(script); + return ( + buffer.length === 25 && + buffer[0] === script_1.OPS.OP_DUP && + buffer[1] === script_1.OPS.OP_HASH160 && + buffer[2] === 0x14 && + buffer[23] === script_1.OPS.OP_EQUALVERIFY && + buffer[24] === script_1.OPS.OP_CHECKSIG + ); } exports.check = check; check.toJSON = () => { - return 'pubKeyHash output'; + return 'pubKeyHash output'; }; diff --git a/src/templates/scripthash/index.js b/src/templates/scripthash/index.js index 85a15b9..b8cd6c4 100644 --- a/src/templates/scripthash/index.js +++ b/src/templates/scripthash/index.js @@ -1,6 +1,6 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = require("./input"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); exports.input = input; -const output = require("./output"); +const output = require('./output'); exports.output = output; diff --git a/src/templates/scripthash/input.js b/src/templates/scripthash/input.js index 488b931..999cc83 100644 --- a/src/templates/scripthash/input.js +++ b/src/templates/scripthash/input.js @@ -1,44 +1,50 @@ -"use strict"; +'use strict'; // {serialized scriptPubKey script} -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); -const p2ms = require("../multisig"); -const p2pk = require("../pubkey"); -const p2pkh = require("../pubkeyhash"); -const p2wpkho = require("../witnesspubkeyhash/output"); -const p2wsho = require("../witnessscripthash/output"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const p2ms = require('../multisig'); +const p2pk = require('../pubkey'); +const p2pkh = require('../pubkeyhash'); +const p2wpkho = require('../witnesspubkeyhash/output'); +const p2wsho = require('../witnessscripthash/output'); function check(script, allowIncomplete) { - const chunks = bscript.decompile(script); - if (chunks.length < 1) - return false; - const lastChunk = chunks[chunks.length - 1]; - if (!Buffer.isBuffer(lastChunk)) - return false; - const scriptSigChunks = bscript.decompile(bscript.compile(chunks.slice(0, -1))); - const redeemScriptChunks = bscript.decompile(lastChunk); - // is redeemScript a valid script? - if (!redeemScriptChunks) - return false; - // is redeemScriptSig push only? - if (!bscript.isPushOnly(scriptSigChunks)) - return false; - // is witness? - if (chunks.length === 1) { - return (p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks)); - } - // match types - if (p2pkh.input.check(scriptSigChunks) && - p2pkh.output.check(redeemScriptChunks)) - return true; - if (p2ms.input.check(scriptSigChunks, allowIncomplete) && - p2ms.output.check(redeemScriptChunks)) - return true; - if (p2pk.input.check(scriptSigChunks) && - p2pk.output.check(redeemScriptChunks)) - return true; - return false; + const chunks = bscript.decompile(script); + if (chunks.length < 1) return false; + const lastChunk = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(lastChunk)) return false; + const scriptSigChunks = bscript.decompile( + bscript.compile(chunks.slice(0, -1)), + ); + const redeemScriptChunks = bscript.decompile(lastChunk); + // is redeemScript a valid script? + if (!redeemScriptChunks) return false; + // is redeemScriptSig push only? + if (!bscript.isPushOnly(scriptSigChunks)) return false; + // is witness? + if (chunks.length === 1) { + return ( + p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks) + ); + } + // match types + if ( + p2pkh.input.check(scriptSigChunks) && + p2pkh.output.check(redeemScriptChunks) + ) + return true; + if ( + p2ms.input.check(scriptSigChunks, allowIncomplete) && + p2ms.output.check(redeemScriptChunks) + ) + return true; + if ( + p2pk.input.check(scriptSigChunks) && + p2pk.output.check(redeemScriptChunks) + ) + return true; + return false; } exports.check = check; check.toJSON = () => { - return 'scriptHash input'; + return 'scriptHash input'; }; diff --git a/src/templates/scripthash/output.js b/src/templates/scripthash/output.js index bf1246a..3797003 100644 --- a/src/templates/scripthash/output.js +++ b/src/templates/scripthash/output.js @@ -1,16 +1,18 @@ -"use strict"; +'use strict'; // OP_HASH160 {scriptHash} OP_EQUAL -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); -const script_1 = require("../../script"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); function check(script) { - const buffer = bscript.compile(script); - return (buffer.length === 23 && - buffer[0] === script_1.OPS.OP_HASH160 && - buffer[1] === 0x14 && - buffer[22] === script_1.OPS.OP_EQUAL); + const buffer = bscript.compile(script); + return ( + buffer.length === 23 && + buffer[0] === script_1.OPS.OP_HASH160 && + buffer[1] === 0x14 && + buffer[22] === script_1.OPS.OP_EQUAL + ); } exports.check = check; check.toJSON = () => { - return 'scriptHash output'; + return 'scriptHash output'; }; diff --git a/src/templates/witnesscommitment/index.js b/src/templates/witnesscommitment/index.js index aff0618..099ac72 100644 --- a/src/templates/witnesscommitment/index.js +++ b/src/templates/witnesscommitment/index.js @@ -1,4 +1,4 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const output = require("./output"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const output = require('./output'); exports.output = output; diff --git a/src/templates/witnesscommitment/output.js b/src/templates/witnesscommitment/output.js index f4d6af0..fb1d59c 100644 --- a/src/templates/witnesscommitment/output.js +++ b/src/templates/witnesscommitment/output.js @@ -1,32 +1,34 @@ -"use strict"; +'use strict'; // OP_RETURN {aa21a9ed} {commitment} -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); -const script_1 = require("../../script"); -const types = require("../../types"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); +const types = require('../../types'); const typeforce = require('typeforce'); const HEADER = Buffer.from('aa21a9ed', 'hex'); function check(script) { - const buffer = bscript.compile(script); - return (buffer.length > 37 && - buffer[0] === script_1.OPS.OP_RETURN && - buffer[1] === 0x24 && - buffer.slice(2, 6).equals(HEADER)); + const buffer = bscript.compile(script); + return ( + buffer.length > 37 && + buffer[0] === script_1.OPS.OP_RETURN && + buffer[1] === 0x24 && + buffer.slice(2, 6).equals(HEADER) + ); } exports.check = check; check.toJSON = () => { - return 'Witness commitment output'; + return 'Witness commitment output'; }; function encode(commitment) { - typeforce(types.Hash256bit, commitment); - const buffer = Buffer.allocUnsafe(36); - HEADER.copy(buffer, 0); - commitment.copy(buffer, 4); - return bscript.compile([script_1.OPS.OP_RETURN, buffer]); + typeforce(types.Hash256bit, commitment); + const buffer = Buffer.allocUnsafe(36); + HEADER.copy(buffer, 0); + commitment.copy(buffer, 4); + return bscript.compile([script_1.OPS.OP_RETURN, buffer]); } exports.encode = encode; function decode(buffer) { - typeforce(check, buffer); - return bscript.decompile(buffer)[1].slice(4, 36); + typeforce(check, buffer); + return bscript.decompile(buffer)[1].slice(4, 36); } exports.decode = decode; diff --git a/src/templates/witnesspubkeyhash/index.js b/src/templates/witnesspubkeyhash/index.js index 85a15b9..b8cd6c4 100644 --- a/src/templates/witnesspubkeyhash/index.js +++ b/src/templates/witnesspubkeyhash/index.js @@ -1,6 +1,6 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = require("./input"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); exports.input = input; -const output = require("./output"); +const output = require('./output'); exports.output = output; diff --git a/src/templates/witnesspubkeyhash/input.js b/src/templates/witnesspubkeyhash/input.js index 3d589e9..4343584 100644 --- a/src/templates/witnesspubkeyhash/input.js +++ b/src/templates/witnesspubkeyhash/input.js @@ -1,17 +1,19 @@ -"use strict"; +'use strict'; // {signature} {pubKey} -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); function isCompressedCanonicalPubKey(pubKey) { - return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33; + return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33; } function check(script) { - const chunks = bscript.decompile(script); - return (chunks.length === 2 && - bscript.isCanonicalScriptSignature(chunks[0]) && - isCompressedCanonicalPubKey(chunks[1])); + const chunks = bscript.decompile(script); + return ( + chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0]) && + isCompressedCanonicalPubKey(chunks[1]) + ); } exports.check = check; check.toJSON = () => { - return 'witnessPubKeyHash input'; + return 'witnessPubKeyHash input'; }; diff --git a/src/templates/witnesspubkeyhash/output.js b/src/templates/witnesspubkeyhash/output.js index 69aab11..ea5ed1e 100644 --- a/src/templates/witnesspubkeyhash/output.js +++ b/src/templates/witnesspubkeyhash/output.js @@ -1,13 +1,17 @@ -"use strict"; +'use strict'; // OP_0 {pubKeyHash} -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); -const script_1 = require("../../script"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); function check(script) { - const buffer = bscript.compile(script); - return buffer.length === 22 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x14; + const buffer = bscript.compile(script); + return ( + buffer.length === 22 && + buffer[0] === script_1.OPS.OP_0 && + buffer[1] === 0x14 + ); } exports.check = check; check.toJSON = () => { - return 'Witness pubKeyHash output'; + return 'Witness pubKeyHash output'; }; diff --git a/src/templates/witnessscripthash/index.js b/src/templates/witnessscripthash/index.js index 85a15b9..b8cd6c4 100644 --- a/src/templates/witnessscripthash/index.js +++ b/src/templates/witnessscripthash/index.js @@ -1,6 +1,6 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = require("./input"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); exports.input = input; -const output = require("./output"); +const output = require('./output'); exports.output = output; diff --git a/src/templates/witnessscripthash/input.js b/src/templates/witnessscripthash/input.js index 3f5c002..f69a810 100644 --- a/src/templates/witnessscripthash/input.js +++ b/src/templates/witnessscripthash/input.js @@ -1,36 +1,39 @@ -"use strict"; +'use strict'; // {serialized scriptPubKey script} -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); const typeforce = require('typeforce'); -const p2ms = require("../multisig"); -const p2pk = require("../pubkey"); -const p2pkh = require("../pubkeyhash"); +const p2ms = require('../multisig'); +const p2pk = require('../pubkey'); +const p2pkh = require('../pubkeyhash'); function check(chunks, allowIncomplete) { - typeforce(typeforce.Array, chunks); - if (chunks.length < 1) - return false; - const witnessScript = chunks[chunks.length - 1]; - if (!Buffer.isBuffer(witnessScript)) - return false; - const witnessScriptChunks = bscript.decompile(witnessScript); - // is witnessScript a valid script? - if (!witnessScriptChunks || witnessScriptChunks.length === 0) - return false; - const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)); - // match types - if (p2pkh.input.check(witnessRawScriptSig) && - p2pkh.output.check(witnessScriptChunks)) - return true; - if (p2ms.input.check(witnessRawScriptSig, allowIncomplete) && - p2ms.output.check(witnessScriptChunks)) - return true; - if (p2pk.input.check(witnessRawScriptSig) && - p2pk.output.check(witnessScriptChunks)) - return true; - return false; + typeforce(typeforce.Array, chunks); + if (chunks.length < 1) return false; + const witnessScript = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(witnessScript)) return false; + const witnessScriptChunks = bscript.decompile(witnessScript); + // is witnessScript a valid script? + if (!witnessScriptChunks || witnessScriptChunks.length === 0) return false; + const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)); + // match types + if ( + p2pkh.input.check(witnessRawScriptSig) && + p2pkh.output.check(witnessScriptChunks) + ) + return true; + if ( + p2ms.input.check(witnessRawScriptSig, allowIncomplete) && + p2ms.output.check(witnessScriptChunks) + ) + return true; + if ( + p2pk.input.check(witnessRawScriptSig) && + p2pk.output.check(witnessScriptChunks) + ) + return true; + return false; } exports.check = check; check.toJSON = () => { - return 'witnessScriptHash input'; + return 'witnessScriptHash input'; }; diff --git a/src/templates/witnessscripthash/output.js b/src/templates/witnessscripthash/output.js index a6d4d95..f69a429 100644 --- a/src/templates/witnessscripthash/output.js +++ b/src/templates/witnessscripthash/output.js @@ -1,13 +1,17 @@ -"use strict"; +'use strict'; // OP_0 {scriptHash} -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = require("../../script"); -const script_1 = require("../../script"); +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); function check(script) { - const buffer = bscript.compile(script); - return buffer.length === 34 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x20; + const buffer = bscript.compile(script); + return ( + buffer.length === 34 && + buffer[0] === script_1.OPS.OP_0 && + buffer[1] === 0x20 + ); } exports.check = check; check.toJSON = () => { - return 'Witness scriptHash output'; + return 'Witness scriptHash output'; }; diff --git a/src/transaction.js b/src/transaction.js index c6c847a..c4e6506 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -1,446 +1,472 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const bufferutils = require("./bufferutils"); -const bufferutils_1 = require("./bufferutils"); -const bcrypto = require("./crypto"); -const bscript = require("./script"); -const script_1 = require("./script"); -const types = require("./types"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bufferutils = require('./bufferutils'); +const bufferutils_1 = require('./bufferutils'); +const bcrypto = require('./crypto'); +const bscript = require('./script'); +const script_1 = require('./script'); +const types = require('./types'); const typeforce = require('typeforce'); const varuint = require('varuint-bitcoin'); function varSliceSize(someScript) { - const length = someScript.length; - return varuint.encodingLength(length) + length; + const length = someScript.length; + return varuint.encodingLength(length) + length; } function vectorSize(someVector) { - const length = someVector.length; - return (varuint.encodingLength(length) + - someVector.reduce((sum, witness) => { - return sum + varSliceSize(witness); - }, 0)); + const length = someVector.length; + return ( + varuint.encodingLength(length) + + someVector.reduce((sum, witness) => { + return sum + varSliceSize(witness); + }, 0) + ); } const EMPTY_SCRIPT = Buffer.allocUnsafe(0); const EMPTY_WITNESS = []; -const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'); -const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex'); +const ZERO = Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000000', + 'hex', +); +const ONE = Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000001', + 'hex', +); const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex'); const BLANK_OUTPUT = { - script: EMPTY_SCRIPT, - valueBuffer: VALUE_UINT64_MAX, + script: EMPTY_SCRIPT, + valueBuffer: VALUE_UINT64_MAX, }; function isOutput(out) { - return out.value !== undefined; + return out.value !== undefined; } class Transaction { - constructor() { - this.version = 1; - this.locktime = 0; - this.ins = []; - this.outs = []; + constructor() { + this.version = 1; + this.locktime = 0; + this.ins = []; + this.outs = []; + } + static fromBuffer(buffer, _NO_STRICT) { + let offset = 0; + function readSlice(n) { + offset += n; + return buffer.slice(offset - n, offset); } - static fromBuffer(buffer, _NO_STRICT) { - let offset = 0; - function readSlice(n) { - offset += n; - return buffer.slice(offset - n, offset); - } - function readUInt32() { - const i = buffer.readUInt32LE(offset); - offset += 4; - return i; - } - function readInt32() { - const i = buffer.readInt32LE(offset); - offset += 4; - return i; - } - function readUInt64() { - const i = bufferutils.readUInt64LE(buffer, offset); - offset += 8; - return i; - } - function readVarInt() { - const vi = varuint.decode(buffer, offset); - offset += varuint.decode.bytes; - return vi; - } - function readVarSlice() { - return readSlice(readVarInt()); - } - function readVector() { - const count = readVarInt(); - const vector = []; - for (let i = 0; i < count; i++) - vector.push(readVarSlice()); - return vector; - } - const tx = new Transaction(); - tx.version = readInt32(); - const marker = buffer.readUInt8(offset); - const flag = buffer.readUInt8(offset + 1); - let hasWitnesses = false; - if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && - flag === Transaction.ADVANCED_TRANSACTION_FLAG) { - offset += 2; - hasWitnesses = true; - } - const vinLen = readVarInt(); - for (let i = 0; i < vinLen; ++i) { - tx.ins.push({ - hash: readSlice(32), - index: readUInt32(), - script: readVarSlice(), - sequence: readUInt32(), - witness: EMPTY_WITNESS, - }); - } - const voutLen = readVarInt(); - for (let i = 0; i < voutLen; ++i) { - tx.outs.push({ - value: readUInt64(), - script: readVarSlice(), - }); - } - if (hasWitnesses) { - for (let i = 0; i < vinLen; ++i) { - tx.ins[i].witness = readVector(); - } - // was this pointless? - if (!tx.hasWitnesses()) - throw new Error('Transaction has superfluous witness data'); - } - tx.locktime = readUInt32(); - if (_NO_STRICT) - return tx; - if (offset !== buffer.length) - throw new Error('Transaction has unexpected data'); - return tx; + function readUInt32() { + const i = buffer.readUInt32LE(offset); + offset += 4; + return i; } - static fromHex(hex) { - return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false); + function readInt32() { + const i = buffer.readInt32LE(offset); + offset += 4; + return i; } - static isCoinbaseHash(buffer) { - typeforce(types.Hash256bit, buffer); - for (let i = 0; i < 32; ++i) { - if (buffer[i] !== 0) - return false; - } - return true; + function readUInt64() { + const i = bufferutils.readUInt64LE(buffer, offset); + offset += 8; + return i; } - isCoinbase() { - return (this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)); + function readVarInt() { + const vi = varuint.decode(buffer, offset); + offset += varuint.decode.bytes; + return vi; } - addInput(hash, index, sequence, scriptSig) { - typeforce(types.tuple(types.Hash256bit, types.UInt32, types.maybe(types.UInt32), types.maybe(types.Buffer)), arguments); - if (types.Null(sequence)) { - sequence = Transaction.DEFAULT_SEQUENCE; - } - // Add the input and return the input's index - return (this.ins.push({ - hash, - index, - script: scriptSig || EMPTY_SCRIPT, - sequence: sequence, - witness: EMPTY_WITNESS, - }) - 1); + function readVarSlice() { + return readSlice(readVarInt()); } - addOutput(scriptPubKey, value) { - typeforce(types.tuple(types.Buffer, types.Satoshi), arguments); - // Add the output and return the output's index - return (this.outs.push({ - script: scriptPubKey, - value, - }) - 1); + function readVector() { + const count = readVarInt(); + const vector = []; + for (let i = 0; i < count; i++) vector.push(readVarSlice()); + return vector; } - hasWitnesses() { - return this.ins.some(x => { - return x.witness.length !== 0; - }); + const tx = new Transaction(); + tx.version = readInt32(); + const marker = buffer.readUInt8(offset); + const flag = buffer.readUInt8(offset + 1); + let hasWitnesses = false; + if ( + marker === Transaction.ADVANCED_TRANSACTION_MARKER && + flag === Transaction.ADVANCED_TRANSACTION_FLAG + ) { + offset += 2; + hasWitnesses = true; } - weight() { - const base = this.__byteLength(false); - const total = this.__byteLength(true); - return base * 3 + total; + const vinLen = readVarInt(); + for (let i = 0; i < vinLen; ++i) { + tx.ins.push({ + hash: readSlice(32), + index: readUInt32(), + script: readVarSlice(), + sequence: readUInt32(), + witness: EMPTY_WITNESS, + }); } - virtualSize() { - return Math.ceil(this.weight() / 4); + const voutLen = readVarInt(); + for (let i = 0; i < voutLen; ++i) { + tx.outs.push({ + value: readUInt64(), + script: readVarSlice(), + }); } - byteLength() { - return this.__byteLength(true); + if (hasWitnesses) { + for (let i = 0; i < vinLen; ++i) { + tx.ins[i].witness = readVector(); + } + // was this pointless? + if (!tx.hasWitnesses()) + throw new Error('Transaction has superfluous witness data'); } - 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; + tx.locktime = readUInt32(); + if (_NO_STRICT) return tx; + if (offset !== buffer.length) + throw new Error('Transaction has unexpected data'); + return tx; + } + static fromHex(hex) { + return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false); + } + static isCoinbaseHash(buffer) { + typeforce(types.Hash256bit, buffer); + for (let i = 0; i < 32; ++i) { + if (buffer[i] !== 0) return false; } - /** - * 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) { - typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments); - // 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_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_SCRIPT; - }); - txTmp.ins[inIndex].script = ourScript; - } - // serialize and hash - const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4); - buffer.writeInt32LE(hashType, buffer.length - 4); - txTmp.__toBuffer(buffer, 0, false); - return bcrypto.hash256(buffer); + return true; + } + isCoinbase() { + return ( + this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) + ); + } + addInput(hash, index, sequence, scriptSig) { + typeforce( + types.tuple( + types.Hash256bit, + types.UInt32, + types.maybe(types.UInt32), + types.maybe(types.Buffer), + ), + arguments, + ); + if (types.Null(sequence)) { + sequence = Transaction.DEFAULT_SEQUENCE; } - hashForWitnessV0(inIndex, prevOutScript, value, hashType) { - typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments); - let tbuffer = Buffer.from([]); - let toffset = 0; - function writeSlice(slice) { - toffset += slice.copy(tbuffer, toffset); - } - function writeUInt32(i) { - toffset = tbuffer.writeUInt32LE(i, toffset); - } - function writeUInt64(i) { - toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset); - } - function writeVarInt(i) { - varuint.encode(i, tbuffer, toffset); - toffset += varuint.encode.bytes; - } - function writeVarSlice(slice) { - writeVarInt(slice.length); - writeSlice(slice); - } - let hashOutputs = ZERO; - let hashPrevouts = ZERO; - let hashSequence = ZERO; - if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { - tbuffer = Buffer.allocUnsafe(36 * this.ins.length); - toffset = 0; - this.ins.forEach(txIn => { - writeSlice(txIn.hash); - writeUInt32(txIn.index); - }); - hashPrevouts = bcrypto.hash256(tbuffer); - } - if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) && - (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && - (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { - tbuffer = Buffer.allocUnsafe(4 * this.ins.length); - toffset = 0; - this.ins.forEach(txIn => { - 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 = Buffer.allocUnsafe(txOutsSize); - toffset = 0; - this.outs.forEach(out => { - writeUInt64(out.value); - writeVarSlice(out.script); - }); - hashOutputs = bcrypto.hash256(tbuffer); - } - else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && - inIndex < this.outs.length) { - const output = this.outs[inIndex]; - tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script)); - toffset = 0; - writeUInt64(output.value); - writeVarSlice(output.script); - hashOutputs = bcrypto.hash256(tbuffer); - } - tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript)); - toffset = 0; - const input = this.ins[inIndex]; - writeUInt32(this.version); - writeSlice(hashPrevouts); - writeSlice(hashSequence); - writeSlice(input.hash); - writeUInt32(input.index); - writeVarSlice(prevOutScript); - writeUInt64(value); - writeUInt32(input.sequence); - writeSlice(hashOutputs); - writeUInt32(this.locktime); - writeUInt32(hashType); - return bcrypto.hash256(tbuffer); + // Add the input and return the input's index + return ( + this.ins.push({ + hash, + index, + script: scriptSig || EMPTY_SCRIPT, + sequence: sequence, + witness: EMPTY_WITNESS, + }) - 1 + ); + } + addOutput(scriptPubKey, value) { + typeforce(types.tuple(types.Buffer, types.Satoshi), arguments); + // 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() { + return this.__byteLength(true); + } + 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) { + typeforce( + types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), + arguments, + ); + // 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_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; + }); } - getHash(forWitness) { - // wtxid for coinbase is always 32 bytes of 0x00 - if (forWitness && this.isCoinbase()) - return Buffer.alloc(32, 0); - return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness)); + // 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_SCRIPT; + }); + txTmp.ins[inIndex].script = ourScript; } - getId() { - // transaction hash's are displayed in reverse order - return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex'); + // serialize and hash + const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4); + buffer.writeInt32LE(hashType, buffer.length - 4); + txTmp.__toBuffer(buffer, 0, false); + return bcrypto.hash256(buffer); + } + hashForWitnessV0(inIndex, prevOutScript, value, hashType) { + typeforce( + types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), + arguments, + ); + let tbuffer = Buffer.from([]); + let toffset = 0; + function writeSlice(slice) { + toffset += slice.copy(tbuffer, toffset); } - toBuffer(buffer, initialOffset) { - return this.__toBuffer(buffer, initialOffset, true); + function writeUInt32(i) { + toffset = tbuffer.writeUInt32LE(i, toffset); } - toHex() { - return this.toBuffer(undefined, undefined).toString('hex'); + function writeUInt64(i) { + toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset); } - setInputScript(index, scriptSig) { - typeforce(types.tuple(types.Number, types.Buffer), arguments); - this.ins[index].script = scriptSig; + function writeVarInt(i) { + varuint.encode(i, tbuffer, toffset); + toffset += varuint.encode.bytes; } - setWitness(index, witness) { - typeforce(types.tuple(types.Number, [types.Buffer]), arguments); - this.ins[index].witness = witness; + function writeVarSlice(slice) { + writeVarInt(slice.length); + writeSlice(slice); } - __byteLength(_ALLOW_WITNESS) { - const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); - return ((hasWitnesses ? 10 : 8) + - varuint.encodingLength(this.ins.length) + - 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)); + let hashOutputs = ZERO; + let hashPrevouts = ZERO; + let hashSequence = ZERO; + if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { + tbuffer = Buffer.allocUnsafe(36 * this.ins.length); + toffset = 0; + this.ins.forEach(txIn => { + writeSlice(txIn.hash); + writeUInt32(txIn.index); + }); + hashPrevouts = bcrypto.hash256(tbuffer); } - __toBuffer(buffer, initialOffset, _ALLOW_WITNESS) { - if (!buffer) - buffer = Buffer.allocUnsafe(this.__byteLength(_ALLOW_WITNESS)); - let offset = initialOffset || 0; - function writeSlice(slice) { - offset += slice.copy(buffer, offset); - } - function writeUInt8(i) { - offset = buffer.writeUInt8(i, offset); - } - function writeUInt32(i) { - offset = buffer.writeUInt32LE(i, offset); - } - function writeInt32(i) { - offset = buffer.writeInt32LE(i, offset); - } - function writeUInt64(i) { - offset = bufferutils.writeUInt64LE(buffer, i, offset); - } - function writeVarInt(i) { - varuint.encode(i, buffer, offset); - offset += varuint.encode.bytes; - } - function writeVarSlice(slice) { - writeVarInt(slice.length); - writeSlice(slice); - } - function writeVector(vector) { - writeVarInt(vector.length); - vector.forEach(writeVarSlice); - } - writeInt32(this.version); - const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); - if (hasWitnesses) { - writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER); - writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); - } - writeVarInt(this.ins.length); - this.ins.forEach(txIn => { - writeSlice(txIn.hash); - writeUInt32(txIn.index); - writeVarSlice(txIn.script); - writeUInt32(txIn.sequence); - }); - writeVarInt(this.outs.length); - this.outs.forEach(txOut => { - if (isOutput(txOut)) { - writeUInt64(txOut.value); - } - else { - writeSlice(txOut.valueBuffer); - } - writeVarSlice(txOut.script); - }); - if (hasWitnesses) { - this.ins.forEach(input => { - writeVector(input.witness); - }); - } - writeUInt32(this.locktime); - // avoid slicing unless necessary - if (initialOffset !== undefined) - return buffer.slice(initialOffset, offset); - return buffer; + if ( + !(hashType & Transaction.SIGHASH_ANYONECANPAY) && + (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && + (hashType & 0x1f) !== Transaction.SIGHASH_NONE + ) { + tbuffer = Buffer.allocUnsafe(4 * this.ins.length); + toffset = 0; + this.ins.forEach(txIn => { + 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 = Buffer.allocUnsafe(txOutsSize); + toffset = 0; + this.outs.forEach(out => { + writeUInt64(out.value); + writeVarSlice(out.script); + }); + hashOutputs = bcrypto.hash256(tbuffer); + } else if ( + (hashType & 0x1f) === Transaction.SIGHASH_SINGLE && + inIndex < this.outs.length + ) { + const output = this.outs[inIndex]; + tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script)); + toffset = 0; + writeUInt64(output.value); + writeVarSlice(output.script); + hashOutputs = bcrypto.hash256(tbuffer); + } + tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript)); + toffset = 0; + const input = this.ins[inIndex]; + writeUInt32(this.version); + writeSlice(hashPrevouts); + writeSlice(hashSequence); + writeSlice(input.hash); + writeUInt32(input.index); + writeVarSlice(prevOutScript); + writeUInt64(value); + writeUInt32(input.sequence); + writeSlice(hashOutputs); + writeUInt32(this.locktime); + writeUInt32(hashType); + return bcrypto.hash256(tbuffer); + } + getHash(forWitness) { + // wtxid for coinbase is always 32 bytes of 0x00 + if (forWitness && this.isCoinbase()) return Buffer.alloc(32, 0); + return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness)); + } + getId() { + // transaction hash's are displayed in reverse order + return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex'); + } + toBuffer(buffer, initialOffset) { + return this.__toBuffer(buffer, initialOffset, true); + } + toHex() { + return this.toBuffer(undefined, undefined).toString('hex'); + } + setInputScript(index, scriptSig) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + this.ins[index].script = scriptSig; + } + setWitness(index, witness) { + typeforce(types.tuple(types.Number, [types.Buffer]), arguments); + this.ins[index].witness = witness; + } + __byteLength(_ALLOW_WITNESS) { + const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); + return ( + (hasWitnesses ? 10 : 8) + + varuint.encodingLength(this.ins.length) + + 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) + ); + } + __toBuffer(buffer, initialOffset, _ALLOW_WITNESS) { + if (!buffer) buffer = Buffer.allocUnsafe(this.__byteLength(_ALLOW_WITNESS)); + let offset = initialOffset || 0; + function writeSlice(slice) { + offset += slice.copy(buffer, offset); + } + function writeUInt8(i) { + offset = buffer.writeUInt8(i, offset); + } + function writeUInt32(i) { + offset = buffer.writeUInt32LE(i, offset); + } + function writeInt32(i) { + offset = buffer.writeInt32LE(i, offset); + } + function writeUInt64(i) { + offset = bufferutils.writeUInt64LE(buffer, i, offset); + } + function writeVarInt(i) { + varuint.encode(i, buffer, offset); + offset += varuint.encode.bytes; + } + function writeVarSlice(slice) { + writeVarInt(slice.length); + writeSlice(slice); + } + function writeVector(vector) { + writeVarInt(vector.length); + vector.forEach(writeVarSlice); + } + writeInt32(this.version); + const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); + if (hasWitnesses) { + writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER); + writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); + } + writeVarInt(this.ins.length); + this.ins.forEach(txIn => { + writeSlice(txIn.hash); + writeUInt32(txIn.index); + writeVarSlice(txIn.script); + writeUInt32(txIn.sequence); + }); + writeVarInt(this.outs.length); + this.outs.forEach(txOut => { + if (isOutput(txOut)) { + writeUInt64(txOut.value); + } else { + writeSlice(txOut.valueBuffer); + } + writeVarSlice(txOut.script); + }); + if (hasWitnesses) { + this.ins.forEach(input => { + writeVector(input.witness); + }); + } + writeUInt32(this.locktime); + // avoid slicing unless necessary + if (initialOffset !== undefined) return buffer.slice(initialOffset, offset); + return buffer; + } } Transaction.DEFAULT_SEQUENCE = 0xffffffff; Transaction.SIGHASH_ALL = 0x01; diff --git a/src/transaction_builder.js b/src/transaction_builder.js index d3b2673..a4dcdf8 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -1,720 +1,739 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const baddress = require("./address"); -const bufferutils_1 = require("./bufferutils"); -const classify = require("./classify"); -const bcrypto = require("./crypto"); -const ECPair = require("./ecpair"); -const networks = require("./networks"); -const payments = require("./payments"); -const bscript = require("./script"); -const script_1 = require("./script"); -const transaction_1 = require("./transaction"); -const types = require("./types"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const baddress = require('./address'); +const bufferutils_1 = require('./bufferutils'); +const classify = require('./classify'); +const bcrypto = require('./crypto'); +const ECPair = require('./ecpair'); +const networks = require('./networks'); +const payments = require('./payments'); +const bscript = require('./script'); +const script_1 = require('./script'); +const transaction_1 = require('./transaction'); +const types = require('./types'); const typeforce = require('typeforce'); const SCRIPT_TYPES = classify.types; function txIsString(tx) { - return typeof tx === 'string' || tx instanceof String; + return typeof tx === 'string' || tx instanceof String; } function txIsTransaction(tx) { - return tx instanceof transaction_1.Transaction; + return tx instanceof transaction_1.Transaction; } class TransactionBuilder { - // WARNING: maximumFeeRate is __NOT__ to be relied on, - // it's just another potential safety mechanism (safety in-depth) - constructor(network = networks.bitcoin, maximumFeeRate = 2500) { - this.network = network; - this.maximumFeeRate = maximumFeeRate; - this.__PREV_TX_SET = {}; - this.__INPUTS = []; - this.__TX = new transaction_1.Transaction(); - this.__TX.version = 2; + // WARNING: maximumFeeRate is __NOT__ to be relied on, + // it's just another potential safety mechanism (safety in-depth) + constructor(network = networks.bitcoin, maximumFeeRate = 2500) { + this.network = network; + this.maximumFeeRate = maximumFeeRate; + this.__PREV_TX_SET = {}; + this.__INPUTS = []; + this.__TX = new transaction_1.Transaction(); + this.__TX.version = 2; + } + static fromTransaction(transaction, network) { + const txb = new TransactionBuilder(network); + // Copy transaction fields + txb.setVersion(transaction.version); + txb.setLockTime(transaction.locktime); + // Copy outputs (done first to avoid signature invalidation) + transaction.outs.forEach(txOut => { + txb.addOutput(txOut.script, txOut.value); + }); + // Copy inputs + transaction.ins.forEach(txIn => { + txb.__addInputUnsafe(txIn.hash, txIn.index, { + sequence: txIn.sequence, + script: txIn.script, + witness: txIn.witness, + }); + }); + // fix some things not possible through the public API + txb.__INPUTS.forEach((input, i) => { + fixMultisigOrder(input, transaction, i); + }); + return txb; + } + setLockTime(locktime) { + typeforce(types.UInt32, locktime); + // if any signatures exist, throw + if ( + this.__INPUTS.some(input => { + if (!input.signatures) return false; + return input.signatures.some(s => s !== undefined); + }) + ) { + throw new Error('No, this would invalidate signatures'); } - static fromTransaction(transaction, network) { - const txb = new TransactionBuilder(network); - // Copy transaction fields - txb.setVersion(transaction.version); - txb.setLockTime(transaction.locktime); - // Copy outputs (done first to avoid signature invalidation) - transaction.outs.forEach(txOut => { - txb.addOutput(txOut.script, txOut.value); + this.__TX.locktime = locktime; + } + setVersion(version) { + typeforce(types.UInt32, version); + // XXX: this might eventually become more complex depending on what the versions represent + this.__TX.version = version; + } + addInput(txHash, vout, sequence, prevOutScript) { + if (!this.__canModifyInputs()) { + throw new Error('No, this would invalidate signatures'); + } + let value; + // is it a hex string? + if (txIsString(txHash)) { + // transaction hashs's are displayed in reverse order, un-reverse it + txHash = bufferutils_1.reverseBuffer(Buffer.from(txHash, 'hex')); + // is it a Transaction object? + } else if (txIsTransaction(txHash)) { + const txOut = txHash.outs[vout]; + prevOutScript = txOut.script; + value = txOut.value; + txHash = txHash.getHash(false); + } + return this.__addInputUnsafe(txHash, vout, { + sequence, + prevOutScript, + value, + }); + } + addOutput(scriptPubKey, value) { + if (!this.__canModifyOutputs()) { + throw new Error('No, this would invalidate signatures'); + } + // Attempt to get a script if it's a base58 or bech32 address string + if (typeof scriptPubKey === 'string') { + scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network); + } + return this.__TX.addOutput(scriptPubKey, value); + } + build() { + return this.__build(false); + } + buildIncomplete() { + return this.__build(true); + } + sign(vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) { + // TODO: remove keyPair.network matching in 4.0.0 + if (keyPair.network && keyPair.network !== this.network) + throw new TypeError('Inconsistent network'); + if (!this.__INPUTS[vin]) throw new Error('No input at index: ' + vin); + hashType = hashType || transaction_1.Transaction.SIGHASH_ALL; + if (this.__needsOutputs(hashType)) + throw new Error('Transaction needs outputs'); + const input = this.__INPUTS[vin]; + // if redeemScript was previously provided, enforce consistency + if ( + input.redeemScript !== undefined && + redeemScript && + !input.redeemScript.equals(redeemScript) + ) { + throw new Error('Inconsistent redeemScript'); + } + const ourPubKey = keyPair.publicKey || keyPair.getPublicKey(); + if (!canSign(input)) { + if (witnessValue !== undefined) { + if (input.value !== undefined && input.value !== witnessValue) + throw new Error('Input did not match witnessValue'); + typeforce(types.Satoshi, witnessValue); + input.value = witnessValue; + } + if (!canSign(input)) { + const prepared = prepareInput( + input, + ourPubKey, + redeemScript, + witnessScript, + ); + // updates inline + Object.assign(input, prepared); + } + if (!canSign(input)) throw Error(input.prevOutType + ' not supported'); + } + // ready to sign + let signatureHash; + if (input.hasWitness) { + signatureHash = this.__TX.hashForWitnessV0( + vin, + input.signScript, + input.value, + hashType, + ); + } else { + signatureHash = this.__TX.hashForSignature( + vin, + input.signScript, + hashType, + ); + } + // enforce in order signing of public keys + const signed = input.pubkeys.some((pubKey, i) => { + if (!ourPubKey.equals(pubKey)) return false; + if (input.signatures[i]) throw new Error('Signature already exists'); + // TODO: add tests + if (ourPubKey.length !== 33 && input.hasWitness) { + throw new Error( + 'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH', + ); + } + const signature = keyPair.sign(signatureHash); + input.signatures[i] = bscript.signature.encode(signature, hashType); + return true; + }); + if (!signed) throw new Error('Key pair cannot sign for this input'); + } + __addInputUnsafe(txHash, vout, options) { + if (transaction_1.Transaction.isCoinbaseHash(txHash)) { + throw new Error('coinbase inputs not supported'); + } + const prevTxOut = txHash.toString('hex') + ':' + vout; + if (this.__PREV_TX_SET[prevTxOut] !== undefined) + throw new Error('Duplicate TxOut: ' + prevTxOut); + let input = {}; + // derive what we can from the scriptSig + if (options.script !== undefined) { + input = expandInput(options.script, options.witness || []); + } + // if an input value was given, retain it + if (options.value !== undefined) { + input.value = options.value; + } + // derive what we can from the previous transactions output script + if (!input.prevOutScript && options.prevOutScript) { + let prevOutType; + if (!input.pubkeys && !input.signatures) { + const expanded = expandOutput(options.prevOutScript); + if (expanded.pubkeys) { + input.pubkeys = expanded.pubkeys; + input.signatures = expanded.signatures; + } + prevOutType = expanded.type; + } + input.prevOutScript = options.prevOutScript; + input.prevOutType = prevOutType || classify.output(options.prevOutScript); + } + const vin = this.__TX.addInput( + txHash, + vout, + options.sequence, + options.scriptSig, + ); + this.__INPUTS[vin] = input; + this.__PREV_TX_SET[prevTxOut] = true; + return vin; + } + __build(allowIncomplete) { + if (!allowIncomplete) { + if (!this.__TX.ins.length) throw new Error('Transaction has no inputs'); + if (!this.__TX.outs.length) throw new Error('Transaction has no outputs'); + } + const tx = this.__TX.clone(); + // create script signatures from inputs + this.__INPUTS.forEach((input, i) => { + if (!input.prevOutType && !allowIncomplete) + throw new Error('Transaction is not complete'); + const result = build(input.prevOutType, input, allowIncomplete); + if (!result) { + if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD) + throw new Error('Unknown input type'); + if (!allowIncomplete) throw new Error('Not enough information'); + return; + } + tx.setInputScript(i, result.input); + tx.setWitness(i, result.witness); + }); + if (!allowIncomplete) { + // do not rely on this, its merely a last resort + if (this.__overMaximumFees(tx.virtualSize())) { + throw new Error('Transaction has absurd fees'); + } + } + return tx; + } + __canModifyInputs() { + return this.__INPUTS.every(input => { + if (!input.signatures) return true; + return input.signatures.every(signature => { + if (!signature) return true; + const hashType = signatureHashType(signature); + // if SIGHASH_ANYONECANPAY is set, signatures would not + // be invalidated by more inputs + return ( + (hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY) !== 0 + ); + }); + }); + } + __needsOutputs(signingHashType) { + if (signingHashType === transaction_1.Transaction.SIGHASH_ALL) { + return this.__TX.outs.length === 0; + } + // if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs + // .build() will fail, but .buildIncomplete() is OK + return ( + this.__TX.outs.length === 0 && + this.__INPUTS.some(input => { + if (!input.signatures) return false; + return input.signatures.some(signature => { + if (!signature) return false; // no signature, no issue + const hashType = signatureHashType(signature); + if (hashType & transaction_1.Transaction.SIGHASH_NONE) return false; // SIGHASH_NONE doesn't care about outputs + return true; // SIGHASH_* does care }); - // Copy inputs - transaction.ins.forEach(txIn => { - txb.__addInputUnsafe(txIn.hash, txIn.index, { - sequence: txIn.sequence, - script: txIn.script, - witness: txIn.witness, - }); - }); - // fix some things not possible through the public API - txb.__INPUTS.forEach((input, i) => { - fixMultisigOrder(input, transaction, i); - }); - return txb; - } - setLockTime(locktime) { - typeforce(types.UInt32, locktime); - // if any signatures exist, throw - if (this.__INPUTS.some(input => { - if (!input.signatures) - return false; - return input.signatures.some(s => s !== undefined); - })) { - throw new Error('No, this would invalidate signatures'); + }) + ); + } + __canModifyOutputs() { + const nInputs = this.__TX.ins.length; + const nOutputs = this.__TX.outs.length; + return this.__INPUTS.every(input => { + if (input.signatures === undefined) return true; + return input.signatures.every(signature => { + if (!signature) return true; + const hashType = signatureHashType(signature); + const hashTypeMod = hashType & 0x1f; + if (hashTypeMod === transaction_1.Transaction.SIGHASH_NONE) return true; + if (hashTypeMod === transaction_1.Transaction.SIGHASH_SINGLE) { + // if SIGHASH_SINGLE is set, and nInputs > nOutputs + // some signatures would be invalidated by the addition + // of more outputs + return nInputs <= nOutputs; } - this.__TX.locktime = locktime; - } - setVersion(version) { - typeforce(types.UInt32, version); - // XXX: this might eventually become more complex depending on what the versions represent - this.__TX.version = version; - } - addInput(txHash, vout, sequence, prevOutScript) { - if (!this.__canModifyInputs()) { - throw new Error('No, this would invalidate signatures'); - } - let value; - // is it a hex string? - if (txIsString(txHash)) { - // transaction hashs's are displayed in reverse order, un-reverse it - txHash = bufferutils_1.reverseBuffer(Buffer.from(txHash, 'hex')); - // is it a Transaction object? - } - else if (txIsTransaction(txHash)) { - const txOut = txHash.outs[vout]; - prevOutScript = txOut.script; - value = txOut.value; - txHash = txHash.getHash(false); - } - return this.__addInputUnsafe(txHash, vout, { - sequence, - prevOutScript, - value, - }); - } - addOutput(scriptPubKey, value) { - if (!this.__canModifyOutputs()) { - throw new Error('No, this would invalidate signatures'); - } - // Attempt to get a script if it's a base58 or bech32 address string - if (typeof scriptPubKey === 'string') { - scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network); - } - return this.__TX.addOutput(scriptPubKey, value); - } - build() { - return this.__build(false); - } - buildIncomplete() { - return this.__build(true); - } - sign(vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) { - // TODO: remove keyPair.network matching in 4.0.0 - if (keyPair.network && keyPair.network !== this.network) - throw new TypeError('Inconsistent network'); - if (!this.__INPUTS[vin]) - throw new Error('No input at index: ' + vin); - hashType = hashType || transaction_1.Transaction.SIGHASH_ALL; - if (this.__needsOutputs(hashType)) - throw new Error('Transaction needs outputs'); - const input = this.__INPUTS[vin]; - // if redeemScript was previously provided, enforce consistency - if (input.redeemScript !== undefined && - redeemScript && - !input.redeemScript.equals(redeemScript)) { - throw new Error('Inconsistent redeemScript'); - } - const ourPubKey = keyPair.publicKey || keyPair.getPublicKey(); - if (!canSign(input)) { - if (witnessValue !== undefined) { - if (input.value !== undefined && input.value !== witnessValue) - throw new Error('Input did not match witnessValue'); - typeforce(types.Satoshi, witnessValue); - input.value = witnessValue; - } - if (!canSign(input)) { - const prepared = prepareInput(input, ourPubKey, redeemScript, witnessScript); - // updates inline - Object.assign(input, prepared); - } - if (!canSign(input)) - throw Error(input.prevOutType + ' not supported'); - } - // ready to sign - let signatureHash; - if (input.hasWitness) { - signatureHash = this.__TX.hashForWitnessV0(vin, input.signScript, input.value, hashType); - } - else { - signatureHash = this.__TX.hashForSignature(vin, input.signScript, hashType); - } - // enforce in order signing of public keys - const signed = input.pubkeys.some((pubKey, i) => { - if (!ourPubKey.equals(pubKey)) - return false; - if (input.signatures[i]) - throw new Error('Signature already exists'); - // TODO: add tests - if (ourPubKey.length !== 33 && input.hasWitness) { - throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH'); - } - const signature = keyPair.sign(signatureHash); - input.signatures[i] = bscript.signature.encode(signature, hashType); - return true; - }); - if (!signed) - throw new Error('Key pair cannot sign for this input'); - } - __addInputUnsafe(txHash, vout, options) { - if (transaction_1.Transaction.isCoinbaseHash(txHash)) { - throw new Error('coinbase inputs not supported'); - } - const prevTxOut = txHash.toString('hex') + ':' + vout; - if (this.__PREV_TX_SET[prevTxOut] !== undefined) - throw new Error('Duplicate TxOut: ' + prevTxOut); - let input = {}; - // derive what we can from the scriptSig - if (options.script !== undefined) { - input = expandInput(options.script, options.witness || []); - } - // if an input value was given, retain it - if (options.value !== undefined) { - input.value = options.value; - } - // derive what we can from the previous transactions output script - if (!input.prevOutScript && options.prevOutScript) { - let prevOutType; - if (!input.pubkeys && !input.signatures) { - const expanded = expandOutput(options.prevOutScript); - if (expanded.pubkeys) { - input.pubkeys = expanded.pubkeys; - input.signatures = expanded.signatures; - } - prevOutType = expanded.type; - } - input.prevOutScript = options.prevOutScript; - input.prevOutType = prevOutType || classify.output(options.prevOutScript); - } - const vin = this.__TX.addInput(txHash, vout, options.sequence, options.scriptSig); - this.__INPUTS[vin] = input; - this.__PREV_TX_SET[prevTxOut] = true; - return vin; - } - __build(allowIncomplete) { - if (!allowIncomplete) { - if (!this.__TX.ins.length) - throw new Error('Transaction has no inputs'); - if (!this.__TX.outs.length) - throw new Error('Transaction has no outputs'); - } - const tx = this.__TX.clone(); - // create script signatures from inputs - this.__INPUTS.forEach((input, i) => { - if (!input.prevOutType && !allowIncomplete) - throw new Error('Transaction is not complete'); - const result = build(input.prevOutType, input, allowIncomplete); - if (!result) { - if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD) - throw new Error('Unknown input type'); - if (!allowIncomplete) - throw new Error('Not enough information'); - return; - } - tx.setInputScript(i, result.input); - tx.setWitness(i, result.witness); - }); - if (!allowIncomplete) { - // do not rely on this, its merely a last resort - if (this.__overMaximumFees(tx.virtualSize())) { - throw new Error('Transaction has absurd fees'); - } - } - return tx; - } - __canModifyInputs() { - return this.__INPUTS.every(input => { - if (!input.signatures) - return true; - return input.signatures.every(signature => { - if (!signature) - return true; - const hashType = signatureHashType(signature); - // if SIGHASH_ANYONECANPAY is set, signatures would not - // be invalidated by more inputs - return (hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY) !== 0; - }); - }); - } - __needsOutputs(signingHashType) { - if (signingHashType === transaction_1.Transaction.SIGHASH_ALL) { - return this.__TX.outs.length === 0; - } - // if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs - // .build() will fail, but .buildIncomplete() is OK - return (this.__TX.outs.length === 0 && - this.__INPUTS.some(input => { - if (!input.signatures) - return false; - return input.signatures.some(signature => { - if (!signature) - return false; // no signature, no issue - const hashType = signatureHashType(signature); - if (hashType & transaction_1.Transaction.SIGHASH_NONE) - return false; // SIGHASH_NONE doesn't care about outputs - return true; // SIGHASH_* does care - }); - })); - } - __canModifyOutputs() { - const nInputs = this.__TX.ins.length; - const nOutputs = this.__TX.outs.length; - return this.__INPUTS.every(input => { - if (input.signatures === undefined) - return true; - return input.signatures.every(signature => { - if (!signature) - return true; - const hashType = signatureHashType(signature); - const hashTypeMod = hashType & 0x1f; - if (hashTypeMod === transaction_1.Transaction.SIGHASH_NONE) - return true; - if (hashTypeMod === transaction_1.Transaction.SIGHASH_SINGLE) { - // if SIGHASH_SINGLE is set, and nInputs > nOutputs - // some signatures would be invalidated by the addition - // of more outputs - return nInputs <= nOutputs; - } - return false; - }); - }); - } - __overMaximumFees(bytes) { - // not all inputs will have .value defined - const incoming = this.__INPUTS.reduce((a, x) => a + (x.value >>> 0), 0); - // but all outputs do, and if we have any input value - // we can immediately determine if the outputs are too small - const outgoing = this.__TX.outs.reduce((a, x) => a + x.value, 0); - const fee = incoming - outgoing; - const feeRate = fee / bytes; - return feeRate > this.maximumFeeRate; - } + return false; + }); + }); + } + __overMaximumFees(bytes) { + // not all inputs will have .value defined + const incoming = this.__INPUTS.reduce((a, x) => a + (x.value >>> 0), 0); + // but all outputs do, and if we have any input value + // we can immediately determine if the outputs are too small + const outgoing = this.__TX.outs.reduce((a, x) => a + x.value, 0); + const fee = incoming - outgoing; + const feeRate = fee / bytes; + return feeRate > this.maximumFeeRate; + } } exports.TransactionBuilder = TransactionBuilder; function expandInput(scriptSig, witnessStack, type, scriptPubKey) { - if (scriptSig.length === 0 && witnessStack.length === 0) - return {}; - if (!type) { - let ssType = classify.input(scriptSig, true); - let wsType = classify.witness(witnessStack, true); - if (ssType === SCRIPT_TYPES.NONSTANDARD) - ssType = undefined; - if (wsType === SCRIPT_TYPES.NONSTANDARD) - wsType = undefined; - type = ssType || wsType; + if (scriptSig.length === 0 && witnessStack.length === 0) return {}; + if (!type) { + let ssType = classify.input(scriptSig, true); + let wsType = classify.witness(witnessStack, true); + if (ssType === SCRIPT_TYPES.NONSTANDARD) ssType = undefined; + if (wsType === SCRIPT_TYPES.NONSTANDARD) wsType = undefined; + type = ssType || wsType; + } + switch (type) { + case SCRIPT_TYPES.P2WPKH: { + const { output, pubkey, signature } = payments.p2wpkh({ + witness: witnessStack, + }); + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2WPKH, + pubkeys: [pubkey], + signatures: [signature], + }; } - switch (type) { - case SCRIPT_TYPES.P2WPKH: { - const { output, pubkey, signature } = payments.p2wpkh({ - witness: witnessStack, - }); - return { - prevOutScript: output, - prevOutType: SCRIPT_TYPES.P2WPKH, - pubkeys: [pubkey], - signatures: [signature], - }; - } - case SCRIPT_TYPES.P2PKH: { - const { output, pubkey, signature } = payments.p2pkh({ - input: scriptSig, - }); - return { - prevOutScript: output, - prevOutType: SCRIPT_TYPES.P2PKH, - pubkeys: [pubkey], - signatures: [signature], - }; - } - case SCRIPT_TYPES.P2PK: { - const { signature } = payments.p2pk({ input: scriptSig }); - return { - prevOutType: SCRIPT_TYPES.P2PK, - pubkeys: [undefined], - signatures: [signature], - }; - } - case SCRIPT_TYPES.P2MS: { - const { m, pubkeys, signatures } = payments.p2ms({ - input: scriptSig, - output: scriptPubKey, - }, { allowIncomplete: true }); - return { - prevOutType: SCRIPT_TYPES.P2MS, - pubkeys, - signatures, - maxSignatures: m, - }; - } + case SCRIPT_TYPES.P2PKH: { + const { output, pubkey, signature } = payments.p2pkh({ + input: scriptSig, + }); + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2PKH, + pubkeys: [pubkey], + signatures: [signature], + }; } - if (type === SCRIPT_TYPES.P2SH) { - const { output, redeem } = payments.p2sh({ - input: scriptSig, - witness: witnessStack, - }); - const outputType = classify.output(redeem.output); - const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output); - if (!expanded.prevOutType) - return {}; - return { - prevOutScript: output, - prevOutType: SCRIPT_TYPES.P2SH, - redeemScript: redeem.output, - redeemScriptType: expanded.prevOutType, - witnessScript: expanded.witnessScript, - witnessScriptType: expanded.witnessScriptType, - pubkeys: expanded.pubkeys, - signatures: expanded.signatures, - }; + case SCRIPT_TYPES.P2PK: { + const { signature } = payments.p2pk({ input: scriptSig }); + return { + prevOutType: SCRIPT_TYPES.P2PK, + pubkeys: [undefined], + signatures: [signature], + }; } - if (type === SCRIPT_TYPES.P2WSH) { - const { output, redeem } = payments.p2wsh({ - input: scriptSig, - witness: witnessStack, - }); - const outputType = classify.output(redeem.output); - let expanded; - if (outputType === SCRIPT_TYPES.P2WPKH) { - expanded = expandInput(redeem.input, redeem.witness, outputType); - } - else { - expanded = expandInput(bscript.compile(redeem.witness), [], outputType, redeem.output); - } - if (!expanded.prevOutType) - return {}; - return { - prevOutScript: output, - prevOutType: SCRIPT_TYPES.P2WSH, - witnessScript: redeem.output, - witnessScriptType: expanded.prevOutType, - pubkeys: expanded.pubkeys, - signatures: expanded.signatures, - }; + case SCRIPT_TYPES.P2MS: { + const { m, pubkeys, signatures } = payments.p2ms( + { + input: scriptSig, + output: scriptPubKey, + }, + { allowIncomplete: true }, + ); + return { + prevOutType: SCRIPT_TYPES.P2MS, + pubkeys, + signatures, + maxSignatures: m, + }; } + } + if (type === SCRIPT_TYPES.P2SH) { + const { output, redeem } = payments.p2sh({ + input: scriptSig, + witness: witnessStack, + }); + const outputType = classify.output(redeem.output); + const expanded = expandInput( + redeem.input, + redeem.witness, + outputType, + redeem.output, + ); + if (!expanded.prevOutType) return {}; return { - prevOutType: SCRIPT_TYPES.NONSTANDARD, - prevOutScript: scriptSig, + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2SH, + redeemScript: redeem.output, + redeemScriptType: expanded.prevOutType, + witnessScript: expanded.witnessScript, + witnessScriptType: expanded.witnessScriptType, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, }; + } + if (type === SCRIPT_TYPES.P2WSH) { + const { output, redeem } = payments.p2wsh({ + input: scriptSig, + witness: witnessStack, + }); + const outputType = classify.output(redeem.output); + let expanded; + if (outputType === SCRIPT_TYPES.P2WPKH) { + expanded = expandInput(redeem.input, redeem.witness, outputType); + } else { + expanded = expandInput( + bscript.compile(redeem.witness), + [], + outputType, + redeem.output, + ); + } + if (!expanded.prevOutType) return {}; + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2WSH, + witnessScript: redeem.output, + witnessScriptType: expanded.prevOutType, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + }; + } + return { + prevOutType: SCRIPT_TYPES.NONSTANDARD, + prevOutScript: scriptSig, + }; } // could be done in expandInput, but requires the original Transaction for hashForSignature function fixMultisigOrder(input, transaction, vin) { - if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) - return; - if (input.pubkeys.length === input.signatures.length) - return; - const unmatched = input.signatures.concat(); - input.signatures = input.pubkeys.map(pubKey => { - const keyPair = ECPair.fromPublicKey(pubKey); - let match; - // check for a signature - unmatched.some((signature, i) => { - // skip if undefined || OP_0 - if (!signature) - return false; - // TODO: avoid O(n) hashForSignature - const parsed = bscript.signature.decode(signature); - const hash = transaction.hashForSignature(vin, input.redeemScript, parsed.hashType); - // skip if signature does not match pubKey - if (!keyPair.verify(hash, parsed.signature)) - return false; - // remove matched signature from unmatched - unmatched[i] = undefined; - match = signature; - return true; - }); - return match; + if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) + return; + if (input.pubkeys.length === input.signatures.length) return; + const unmatched = input.signatures.concat(); + input.signatures = input.pubkeys.map(pubKey => { + const keyPair = ECPair.fromPublicKey(pubKey); + let match; + // check for a signature + unmatched.some((signature, i) => { + // skip if undefined || OP_0 + if (!signature) return false; + // TODO: avoid O(n) hashForSignature + const parsed = bscript.signature.decode(signature); + const hash = transaction.hashForSignature( + vin, + input.redeemScript, + parsed.hashType, + ); + // skip if signature does not match pubKey + if (!keyPair.verify(hash, parsed.signature)) return false; + // remove matched signature from unmatched + unmatched[i] = undefined; + match = signature; + return true; }); + return match; + }); } function expandOutput(script, ourPubKey) { - typeforce(types.Buffer, script); - const type = classify.output(script); - switch (type) { - case SCRIPT_TYPES.P2PKH: { - if (!ourPubKey) - return { type }; - // does our hash160(pubKey) match the output scripts? - const pkh1 = payments.p2pkh({ output: script }).hash; - const pkh2 = bcrypto.hash160(ourPubKey); - if (!pkh1.equals(pkh2)) - return { type }; - return { - type, - pubkeys: [ourPubKey], - signatures: [undefined], - }; - } - case SCRIPT_TYPES.P2WPKH: { - if (!ourPubKey) - return { type }; - // does our hash160(pubKey) match the output scripts? - const wpkh1 = payments.p2wpkh({ output: script }).hash; - const wpkh2 = bcrypto.hash160(ourPubKey); - if (!wpkh1.equals(wpkh2)) - return { type }; - return { - type, - pubkeys: [ourPubKey], - signatures: [undefined], - }; - } - case SCRIPT_TYPES.P2PK: { - const p2pk = payments.p2pk({ output: script }); - return { - type, - pubkeys: [p2pk.pubkey], - signatures: [undefined], - }; - } - case SCRIPT_TYPES.P2MS: { - const p2ms = payments.p2ms({ output: script }); - return { - type, - pubkeys: p2ms.pubkeys, - signatures: p2ms.pubkeys.map(() => undefined), - maxSignatures: p2ms.m, - }; - } - } - return { type }; -} -function prepareInput(input, ourPubKey, redeemScript, witnessScript) { - if (redeemScript && witnessScript) { - const p2wsh = payments.p2wsh({ - redeem: { output: witnessScript }, - }); - const p2wshAlt = payments.p2wsh({ output: redeemScript }); - const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); - const p2shAlt = payments.p2sh({ redeem: p2wsh }); - // enforces P2SH(P2WSH(...)) - if (!p2wsh.hash.equals(p2wshAlt.hash)) - throw new Error('Witness script inconsistent with prevOutScript'); - if (!p2sh.hash.equals(p2shAlt.hash)) - throw new Error('Redeem script inconsistent with prevOutScript'); - const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); - if (!expanded.pubkeys) - throw new Error(expanded.type + - ' not supported as witnessScript (' + - bscript.toASM(witnessScript) + - ')'); - if (input.signatures && input.signatures.some(x => x !== undefined)) { - expanded.signatures = input.signatures; - } - const signScript = witnessScript; - if (expanded.type === SCRIPT_TYPES.P2WPKH) - throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure'); - return { - redeemScript, - redeemScriptType: SCRIPT_TYPES.P2WSH, - witnessScript, - witnessScriptType: expanded.type, - prevOutType: SCRIPT_TYPES.P2SH, - prevOutScript: p2sh.output, - hasWitness: true, - signScript, - signType: expanded.type, - pubkeys: expanded.pubkeys, - signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures, - }; - } - if (redeemScript) { - const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); - if (input.prevOutScript) { - let p2shAlt; - try { - p2shAlt = payments.p2sh({ output: input.prevOutScript }); - } - catch (e) { - throw new Error('PrevOutScript must be P2SH'); - } - if (!p2sh.hash.equals(p2shAlt.hash)) - throw new Error('Redeem script inconsistent with prevOutScript'); - } - const expanded = expandOutput(p2sh.redeem.output, ourPubKey); - if (!expanded.pubkeys) - throw new Error(expanded.type + - ' not supported as redeemScript (' + - bscript.toASM(redeemScript) + - ')'); - if (input.signatures && input.signatures.some(x => x !== undefined)) { - expanded.signatures = input.signatures; - } - let signScript = redeemScript; - if (expanded.type === SCRIPT_TYPES.P2WPKH) { - signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output; - } - return { - redeemScript, - redeemScriptType: expanded.type, - prevOutType: SCRIPT_TYPES.P2SH, - prevOutScript: p2sh.output, - hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, - signScript, - signType: expanded.type, - pubkeys: expanded.pubkeys, - signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures, - }; - } - if (witnessScript) { - const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } }); - if (input.prevOutScript) { - const p2wshAlt = payments.p2wsh({ output: input.prevOutScript }); - if (!p2wsh.hash.equals(p2wshAlt.hash)) - throw new Error('Witness script inconsistent with prevOutScript'); - } - const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); - if (!expanded.pubkeys) - throw new Error(expanded.type + - ' not supported as witnessScript (' + - bscript.toASM(witnessScript) + - ')'); - if (input.signatures && input.signatures.some(x => x !== undefined)) { - expanded.signatures = input.signatures; - } - const signScript = witnessScript; - if (expanded.type === SCRIPT_TYPES.P2WPKH) - throw new Error('P2WSH(P2WPKH) is a consensus failure'); - return { - witnessScript, - witnessScriptType: expanded.type, - prevOutType: SCRIPT_TYPES.P2WSH, - prevOutScript: p2wsh.output, - hasWitness: true, - signScript, - signType: expanded.type, - pubkeys: expanded.pubkeys, - signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures, - }; - } - if (input.prevOutType && input.prevOutScript) { - // embedded scripts are not possible without extra information - if (input.prevOutType === SCRIPT_TYPES.P2SH) - throw new Error('PrevOutScript is ' + input.prevOutType + ', requires redeemScript'); - if (input.prevOutType === SCRIPT_TYPES.P2WSH) - throw new Error('PrevOutScript is ' + input.prevOutType + ', requires witnessScript'); - if (!input.prevOutScript) - throw new Error('PrevOutScript is missing'); - const expanded = expandOutput(input.prevOutScript, ourPubKey); - if (!expanded.pubkeys) - throw new Error(expanded.type + - ' not supported (' + - bscript.toASM(input.prevOutScript) + - ')'); - if (input.signatures && input.signatures.some(x => x !== undefined)) { - expanded.signatures = input.signatures; - } - let signScript = input.prevOutScript; - if (expanded.type === SCRIPT_TYPES.P2WPKH) { - signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }) - .output; - } - return { - prevOutType: expanded.type, - prevOutScript: input.prevOutScript, - hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, - signScript, - signType: expanded.type, - pubkeys: expanded.pubkeys, - signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures, - }; - } - const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output; - return { - prevOutType: SCRIPT_TYPES.P2PKH, - prevOutScript, - hasWitness: false, - signScript: prevOutScript, - signType: SCRIPT_TYPES.P2PKH, + typeforce(types.Buffer, script); + const type = classify.output(script); + switch (type) { + case SCRIPT_TYPES.P2PKH: { + if (!ourPubKey) return { type }; + // does our hash160(pubKey) match the output scripts? + const pkh1 = payments.p2pkh({ output: script }).hash; + const pkh2 = bcrypto.hash160(ourPubKey); + if (!pkh1.equals(pkh2)) return { type }; + return { + type, pubkeys: [ourPubKey], signatures: [undefined], + }; + } + case SCRIPT_TYPES.P2WPKH: { + if (!ourPubKey) return { type }; + // does our hash160(pubKey) match the output scripts? + const wpkh1 = payments.p2wpkh({ output: script }).hash; + const wpkh2 = bcrypto.hash160(ourPubKey); + if (!wpkh1.equals(wpkh2)) return { type }; + return { + type, + pubkeys: [ourPubKey], + signatures: [undefined], + }; + } + case SCRIPT_TYPES.P2PK: { + const p2pk = payments.p2pk({ output: script }); + return { + type, + pubkeys: [p2pk.pubkey], + signatures: [undefined], + }; + } + case SCRIPT_TYPES.P2MS: { + const p2ms = payments.p2ms({ output: script }); + return { + type, + pubkeys: p2ms.pubkeys, + signatures: p2ms.pubkeys.map(() => undefined), + maxSignatures: p2ms.m, + }; + } + } + return { type }; +} +function prepareInput(input, ourPubKey, redeemScript, witnessScript) { + if (redeemScript && witnessScript) { + const p2wsh = payments.p2wsh({ + redeem: { output: witnessScript }, + }); + const p2wshAlt = payments.p2wsh({ output: redeemScript }); + const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); + const p2shAlt = payments.p2sh({ redeem: p2wsh }); + // enforces P2SH(P2WSH(...)) + if (!p2wsh.hash.equals(p2wshAlt.hash)) + throw new Error('Witness script inconsistent with prevOutScript'); + if (!p2sh.hash.equals(p2shAlt.hash)) + throw new Error('Redeem script inconsistent with prevOutScript'); + const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported as witnessScript (' + + bscript.toASM(witnessScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + const signScript = witnessScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) + throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure'); + return { + redeemScript, + redeemScriptType: SCRIPT_TYPES.P2WSH, + witnessScript, + witnessScriptType: expanded.type, + prevOutType: SCRIPT_TYPES.P2SH, + prevOutScript: p2sh.output, + hasWitness: true, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, }; + } + if (redeemScript) { + const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); + if (input.prevOutScript) { + let p2shAlt; + try { + p2shAlt = payments.p2sh({ output: input.prevOutScript }); + } catch (e) { + throw new Error('PrevOutScript must be P2SH'); + } + if (!p2sh.hash.equals(p2shAlt.hash)) + throw new Error('Redeem script inconsistent with prevOutScript'); + } + const expanded = expandOutput(p2sh.redeem.output, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported as redeemScript (' + + bscript.toASM(redeemScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + let signScript = redeemScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) { + signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output; + } + return { + redeemScript, + redeemScriptType: expanded.type, + prevOutType: SCRIPT_TYPES.P2SH, + prevOutScript: p2sh.output, + hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, + }; + } + if (witnessScript) { + const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } }); + if (input.prevOutScript) { + const p2wshAlt = payments.p2wsh({ output: input.prevOutScript }); + if (!p2wsh.hash.equals(p2wshAlt.hash)) + throw new Error('Witness script inconsistent with prevOutScript'); + } + const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported as witnessScript (' + + bscript.toASM(witnessScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + const signScript = witnessScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) + throw new Error('P2WSH(P2WPKH) is a consensus failure'); + return { + witnessScript, + witnessScriptType: expanded.type, + prevOutType: SCRIPT_TYPES.P2WSH, + prevOutScript: p2wsh.output, + hasWitness: true, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, + }; + } + if (input.prevOutType && input.prevOutScript) { + // embedded scripts are not possible without extra information + if (input.prevOutType === SCRIPT_TYPES.P2SH) + throw new Error( + 'PrevOutScript is ' + input.prevOutType + ', requires redeemScript', + ); + if (input.prevOutType === SCRIPT_TYPES.P2WSH) + throw new Error( + 'PrevOutScript is ' + input.prevOutType + ', requires witnessScript', + ); + if (!input.prevOutScript) throw new Error('PrevOutScript is missing'); + const expanded = expandOutput(input.prevOutScript, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported (' + + bscript.toASM(input.prevOutScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + let signScript = input.prevOutScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) { + signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output; + } + return { + prevOutType: expanded.type, + prevOutScript: input.prevOutScript, + hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, + }; + } + const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output; + return { + prevOutType: SCRIPT_TYPES.P2PKH, + prevOutScript, + hasWitness: false, + signScript: prevOutScript, + signType: SCRIPT_TYPES.P2PKH, + pubkeys: [ourPubKey], + signatures: [undefined], + }; } function build(type, input, allowIncomplete) { - const pubkeys = (input.pubkeys || []); - let signatures = (input.signatures || []); - switch (type) { - case SCRIPT_TYPES.P2PKH: { - if (pubkeys.length === 0) - break; - if (signatures.length === 0) - break; - return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }); - } - case SCRIPT_TYPES.P2WPKH: { - if (pubkeys.length === 0) - break; - if (signatures.length === 0) - break; - return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }); - } - case SCRIPT_TYPES.P2PK: { - if (pubkeys.length === 0) - break; - if (signatures.length === 0) - break; - return payments.p2pk({ signature: signatures[0] }); - } - case SCRIPT_TYPES.P2MS: { - const m = input.maxSignatures; - if (allowIncomplete) { - signatures = signatures.map(x => x || script_1.OPS.OP_0); - } - else { - signatures = signatures.filter(x => x); - } - // if the transaction is not not complete (complete), or if signatures.length === m, validate - // otherwise, the number of OP_0's may be >= m, so don't validate (boo) - const validate = !allowIncomplete || m === signatures.length; - return payments.p2ms({ m, pubkeys, signatures }, { allowIncomplete, validate }); - } - case SCRIPT_TYPES.P2SH: { - const redeem = build(input.redeemScriptType, input, allowIncomplete); - if (!redeem) - return; - return payments.p2sh({ - redeem: { - output: redeem.output || input.redeemScript, - input: redeem.input, - witness: redeem.witness, - }, - }); - } - case SCRIPT_TYPES.P2WSH: { - const redeem = build(input.witnessScriptType, input, allowIncomplete); - if (!redeem) - return; - return payments.p2wsh({ - redeem: { - output: input.witnessScript, - input: redeem.input, - witness: redeem.witness, - }, - }); - } + const pubkeys = input.pubkeys || []; + let signatures = input.signatures || []; + switch (type) { + case SCRIPT_TYPES.P2PKH: { + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; + return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }); } + case SCRIPT_TYPES.P2WPKH: { + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; + return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }); + } + case SCRIPT_TYPES.P2PK: { + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; + return payments.p2pk({ signature: signatures[0] }); + } + case SCRIPT_TYPES.P2MS: { + const m = input.maxSignatures; + if (allowIncomplete) { + signatures = signatures.map(x => x || script_1.OPS.OP_0); + } else { + signatures = signatures.filter(x => x); + } + // if the transaction is not not complete (complete), or if signatures.length === m, validate + // otherwise, the number of OP_0's may be >= m, so don't validate (boo) + const validate = !allowIncomplete || m === signatures.length; + return payments.p2ms( + { m, pubkeys, signatures }, + { allowIncomplete, validate }, + ); + } + case SCRIPT_TYPES.P2SH: { + const redeem = build(input.redeemScriptType, input, allowIncomplete); + if (!redeem) return; + return payments.p2sh({ + redeem: { + output: redeem.output || input.redeemScript, + input: redeem.input, + witness: redeem.witness, + }, + }); + } + case SCRIPT_TYPES.P2WSH: { + const redeem = build(input.witnessScriptType, input, allowIncomplete); + if (!redeem) return; + return payments.p2wsh({ + redeem: { + output: input.witnessScript, + input: redeem.input, + witness: redeem.witness, + }, + }); + } + } } function canSign(input) { - return (input.signScript !== undefined && - input.signType !== undefined && - input.pubkeys !== undefined && - input.signatures !== undefined && - input.signatures.length === input.pubkeys.length && - input.pubkeys.length > 0 && - (input.hasWitness === false || input.value !== undefined)); + return ( + input.signScript !== undefined && + input.signType !== undefined && + input.pubkeys !== undefined && + input.signatures !== undefined && + input.signatures.length === input.pubkeys.length && + input.pubkeys.length > 0 && + (input.hasWitness === false || input.value !== undefined) + ); } function signatureHashType(buffer) { - return buffer.readUInt8(buffer.length - 1); + return buffer.readUInt8(buffer.length - 1); } diff --git a/src/types.js b/src/types.js index dbe7ca5..be95266 100644 --- a/src/types.js +++ b/src/types.js @@ -1,35 +1,35 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); const typeforce = require('typeforce'); const UINT31_MAX = Math.pow(2, 31) - 1; function UInt31(value) { - return typeforce.UInt32(value) && value <= UINT31_MAX; + return typeforce.UInt32(value) && value <= UINT31_MAX; } exports.UInt31 = UInt31; function BIP32Path(value) { - return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); + return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); } exports.BIP32Path = BIP32Path; BIP32Path.toJSON = () => { - return 'BIP32 derivation path'; + return 'BIP32 derivation path'; }; const SATOSHI_MAX = 21 * 1e14; function Satoshi(value) { - return typeforce.UInt53(value) && value <= SATOSHI_MAX; + return typeforce.UInt53(value) && value <= SATOSHI_MAX; } exports.Satoshi = Satoshi; // external dependent types exports.ECPoint = typeforce.quacksLike('Point'); // exposed, external API exports.Network = typeforce.compile({ - messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), - bip32: { - public: typeforce.UInt32, - private: typeforce.UInt32, - }, - pubKeyHash: typeforce.UInt8, - scriptHash: typeforce.UInt8, - wif: typeforce.UInt8, + messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), + bip32: { + public: typeforce.UInt32, + private: typeforce.UInt32, + }, + pubKeyHash: typeforce.UInt8, + scriptHash: typeforce.UInt8, + wif: typeforce.UInt8, }); exports.Buffer256bit = typeforce.BufferN(32); exports.Hash160bit = typeforce.BufferN(20);