diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..e69de29 diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..a20502b --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} diff --git a/.travis.yml b/.travis.yml index 397dc23..90088bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ node_js: matrix: include: - node_js: "lts/*" - env: TEST_SUITE=standard + env: TEST_SUITE=format:ci - node_js: "lts/*" env: TEST_SUITE=coverage env: diff --git a/package.json b/package.json index 384b53d..8e40c29 100644 --- a/package.json +++ b/package.json @@ -19,16 +19,17 @@ "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", + "format:ci": "npm run prettier -- --check", "integration": "npm run build && npm run nobuild:integration", "nobuild:coverage-report": "nyc report --reporter=lcov", "nobuild:coverage-html": "nyc report --reporter=html", "nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha", "nobuild:integration": "mocha --timeout 50000 test/integration/", - "nobuild:standard": "standard ts_src/**/*.ts", "nobuild:unit": "mocha", "prepare": "npm run build", - "standard": "npm run build && npm run nobuild:standard", - "test": "npm run build && npm run nobuild:standard && npm run nobuild:coverage", + "prettier": "prettier ts_src/*.ts ts_src/**/*.ts --ignore-path ./.prettierignore", + "test": "npm run build && npm run format:ci && npm run nobuild:coverage", "unit": "npm run build && npm run nobuild:unit" }, "repository": { @@ -68,16 +69,10 @@ "minimaldata": "^1.0.2", "mocha": "^5.2.0", "nyc": "^11.8.0", + "prettier": "^1.16.4", "proxyquire": "^2.0.1", - "standard": "^11.0.1", "typescript": "3.2.2", "typescript-eslint-parser": "^21.0.2" }, - "license": "MIT", - "standard": { - "parser": "typescript-eslint-parser", - "plugins": [ - "typescript" - ] - } + "license": "MIT" } diff --git a/src/address.js b/src/address.js index ed11e5c..f12cd8d 100644 --- a/src/address.js +++ b/src/address.js @@ -25,7 +25,7 @@ function fromBech32(address) { return { version: result.words[0], prefix: result.prefix, - data: Buffer.from(data) + data: Buffer.from(data), }; } exports.fromBech32 = fromBech32; @@ -44,6 +44,7 @@ function toBech32(data, version, prefix) { } exports.toBech32 = toBech32; function fromOutputScript(output, network) { + //TODO: Network network = network || networks.bitcoin; try { return payments.p2pkh({ output, network }).address; diff --git a/src/block.js b/src/block.js index cb593ac..eb3fb8b 100644 --- a/src/block.js +++ b/src/block.js @@ -10,22 +10,22 @@ 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'); function txesHaveWitnessCommit(transactions) { - return transactions instanceof Array && + 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; + transactions[0].ins[0].witness.length > 0); } function anyTxHasWitness(transactions) { - return transactions instanceof Array && + 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)); + input.witness.length > 0))); } class Block { constructor() { @@ -116,9 +116,7 @@ class Block { // 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. - let witnessCommits = this.transactions[0].outs - .filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))) - .map(out => out.script.slice(6, 38)); + let 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) @@ -141,8 +139,9 @@ class Block { byteLength(headersOnly) { if (headersOnly || !this.transactions) return 80; - return 80 + varuint.encodingLength(this.transactions.length) + - this.transactions.reduce((a, x) => a + x.byteLength(), 0); + return (80 + + varuint.encodingLength(this.transactions.length) + + this.transactions.reduce((a, x) => a + x.byteLength(), 0)); } getHash() { return bcrypto.hash256(this.toBuffer(true)); @@ -197,8 +196,8 @@ class Block { let hasWitnessCommit = this.hasWitnessCommit(); if (!hasWitnessCommit && this.hasWitness()) return false; - return this.__checkMerkleRoot() && - (hasWitnessCommit ? this.__checkWitnessCommit() : true); + return (this.__checkMerkleRoot() && + (hasWitnessCommit ? this.__checkWitnessCommit() : true)); } checkMerkleRoot() { console.warn('Deprecation Warning: Block method checkMerkleRoot will be ' + diff --git a/src/classify.js b/src/classify.js index a2109c2..0aec5c4 100644 --- a/src/classify.js +++ b/src/classify.js @@ -18,7 +18,7 @@ const types = { P2SH: 'scripthash', P2WPKH: 'witnesspubkeyhash', P2WSH: 'witnessscripthash', - WITNESS_COMMITMENT: 'witnesscommitment' + WITNESS_COMMITMENT: 'witnesscommitment', }; exports.types = types; function classifyOutput(script) { diff --git a/src/crypto.js b/src/crypto.js index 4165d30..d494b86 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -2,15 +2,21 @@ Object.defineProperty(exports, "__esModule", { value: true }); const createHash = require('create-hash'); function ripemd160(buffer) { - return createHash('rmd160').update(buffer).digest(); + return createHash('rmd160') + .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) { diff --git a/src/ecpair.js b/src/ecpair.js index d4dc94f..7d376ac 100644 --- a/src/ecpair.js +++ b/src/ecpair.js @@ -8,13 +8,14 @@ const typeforce = require('typeforce'); const wif = require('wif'); const isOptions = typeforce.maybe(typeforce.compile({ compressed: types.maybe(types.Boolean), - network: types.maybe(types.Network) + network: types.maybe(types.Network), })); class ECPair { constructor(d, Q, options) { if (options === undefined) options = {}; - this.compressed = options.compressed === undefined ? true : options.compressed; + this.compressed = + options.compressed === undefined ? true : options.compressed; this.network = options.network || NETWORKS.bitcoin; this.__d = undefined; this.__Q = undefined; @@ -64,9 +65,11 @@ function fromWIF(string, network) { const version = decoded.version; // list of networks? if (types.Array(network)) { - network = network.filter(function (x) { + network = network + .filter(function (x) { return version === x.wif; - }).pop(); + }) + .pop(); if (!network) throw new Error('Unknown network version'); // otherwise, assume a network object (or default to bitcoin) @@ -78,7 +81,7 @@ function fromWIF(string, network) { } return fromPrivateKey(decoded.privateKey, { compressed: decoded.compressed, - network: network + network: network, }); } exports.fromWIF = fromWIF; diff --git a/src/networks.js b/src/networks.js index 821cd96..298808d 100644 --- a/src/networks.js +++ b/src/networks.js @@ -5,31 +5,31 @@ exports.bitcoin = { bech32: 'bc', bip32: { public: 0x0488b21e, - private: 0x0488ade4 + private: 0x0488ade4, }, pubKeyHash: 0x00, scriptHash: 0x05, - wif: 0x80 + wif: 0x80, }; exports.regtest = { messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'bcrt', bip32: { public: 0x043587cf, - private: 0x04358394 + private: 0x04358394, }, pubKeyHash: 0x6f, scriptHash: 0xc4, - wif: 0xef + wif: 0xef, }; exports.testnet = { messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'tb', bip32: { public: 0x043587cf, - private: 0x04358394 + private: 0x04358394, }, pubKeyHash: 0x6f, scriptHash: 0xc4, - wif: 0xef + wif: 0xef, }; diff --git a/src/payments/embed.js b/src/payments/embed.js index 7d341ab..0e2a0ae 100644 --- a/src/payments/embed.js +++ b/src/payments/embed.js @@ -14,14 +14,13 @@ function stacksEqual(a, b) { } // output: OP_RETURN ... function p2data(a, opts) { - if (!a.data && - !a.output) + 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)) + data: typef.maybe(typef.arrayOf(typef.Buffer)), }, a); const network = a.network || networks_1.bitcoin; const o = { network }; diff --git a/src/payments/lazy.js b/src/payments/lazy.js index 2c848e1..9eda8e8 100644 --- a/src/payments/lazy.js +++ b/src/payments/lazy.js @@ -14,9 +14,9 @@ function prop(object, name, f) { configurable: true, enumerable: true, value: value, - writable: true + writable: true, }); - } + }, }); } exports.prop = prop; diff --git a/src/payments/p2ms.js b/src/payments/p2ms.js index a45bd48..1e51faa 100644 --- a/src/payments/p2ms.js +++ b/src/payments/p2ms.js @@ -24,9 +24,8 @@ function p2ms(a, opts) { 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; // eslint-disable-line + return (bscript.isCanonicalScriptSignature(x) || + (opts.allowIncomplete && x === OPS.OP_0) !== undefined); } typef({ network: typef.maybe(typef.Object), @@ -35,7 +34,7 @@ function p2ms(a, opts) { output: typef.maybe(typef.Buffer), pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), - input: typef.maybe(typef.Buffer) + input: typef.maybe(typef.Buffer), }, a); const network = a.network || networks_1.bitcoin; const o = { network }; @@ -46,8 +45,8 @@ function p2ms(a, opts) { return; decoded = true; chunks = bscript.decompile(output); - o.m = chunks[0] - OP_INT_BASE; // eslint-disable-line - o.n = chunks[chunks.length - 2] - OP_INT_BASE; // eslint-disable-line + 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', function () { @@ -101,10 +100,7 @@ function p2ms(a, opts) { throw new TypeError('Output is invalid'); if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) throw new TypeError('Output is invalid'); - if (o.m <= 0 || // eslint-disable-line - o.n > 16 || // eslint-disable-line - o.m > o.n || // eslint-disable-line - o.n !== chunks.length - 3) + 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'); @@ -131,7 +127,8 @@ function p2ms(a, opts) { 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)) + 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'); diff --git a/src/payments/p2pk.js b/src/payments/p2pk.js index ab8654d..9c9318f 100644 --- a/src/payments/p2pk.js +++ b/src/payments/p2pk.js @@ -9,11 +9,7 @@ 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) + if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); typef({ @@ -21,18 +17,17 @@ function p2pk(a, opts) { output: typef.maybe(typef.Buffer), pubkey: typef.maybe(ecc.isPoint), signature: typef.maybe(bscript.isCanonicalScriptSignature), - input: typef.maybe(typef.Buffer) + input: typef.maybe(typef.Buffer), }, a); - const _chunks = lazy.value(function () { return bscript.decompile(a.input); }); + const _chunks = lazy.value(function () { + return bscript.decompile(a.input); + }); const network = a.network || networks_1.bitcoin; const o = { network }; lazy.prop(o, 'output', function () { if (!a.pubkey) return; - return bscript.compile([ - a.pubkey, - OPS.OP_CHECKSIG - ]); + return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); }); lazy.prop(o, 'pubkey', function () { if (!a.output) diff --git a/src/payments/p2pkh.js b/src/payments/p2pkh.js index 55f4817..6b41a77 100644 --- a/src/payments/p2pkh.js +++ b/src/payments/p2pkh.js @@ -11,11 +11,7 @@ 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) + if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); typef({ @@ -25,7 +21,7 @@ function p2pkh(a, opts) { output: typef.maybe(typef.BufferN(25)), pubkey: typef.maybe(ecc.isPoint), signature: typef.maybe(bscript.isCanonicalScriptSignature), - input: typef.maybe(typef.Buffer) + input: typef.maybe(typef.Buffer), }, a); const _address = lazy.value(function () { const payload = bs58check.decode(a.address); @@ -33,7 +29,9 @@ function p2pkh(a, opts) { const hash = payload.slice(1); return { version, hash }; }); - const _chunks = lazy.value(function () { return bscript.decompile(a.input); }); + const _chunks = lazy.value(function () { + return bscript.decompile(a.input); + }); const network = a.network || networks_1.bitcoin; const o = { network }; lazy.prop(o, 'address', function () { @@ -60,7 +58,7 @@ function p2pkh(a, opts) { OPS.OP_HASH160, o.hash, OPS.OP_EQUALVERIFY, - OPS.OP_CHECKSIG + OPS.OP_CHECKSIG, ]); }); lazy.prop(o, 'pubkey', function () { diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index 8ec9910..f98ef71 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const networks_1 = require("../networks"); // eslint-disable-line +const networks_1 = require("../networks"); const bscript = require("../script"); const bcrypto = require("../crypto"); const lazy = require("./lazy"); @@ -18,11 +18,7 @@ function stacksEqual(a, b) { // witness: // output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL function p2sh(a, opts) { - if (!a.address && - !a.hash && - !a.output && - !a.redeem && - !a.input) + if (!a.address && !a.hash && !a.output && !a.redeem && !a.input) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); typef({ @@ -34,10 +30,10 @@ function p2sh(a, opts) { network: typef.maybe(typef.Object), output: typef.maybe(typef.Buffer), input: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)) + witness: typef.maybe(typef.arrayOf(typef.Buffer)), }), input: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)) + witness: typef.maybe(typef.arrayOf(typef.Buffer)), }, a); let network = a.network; if (!network) { @@ -50,14 +46,16 @@ function p2sh(a, opts) { const hash = payload.slice(1); return { version, hash }; }); - const _chunks = lazy.value(function () { return bscript.decompile(a.input); }); + const _chunks = lazy.value(function () { + return bscript.decompile(a.input); + }); const _redeem = lazy.value(function () { const chunks = _chunks(); return { network, output: chunks[chunks.length - 1], input: bscript.compile(chunks.slice(0, -1)), - witness: a.witness || [] + witness: a.witness || [], }; }); // output dependents @@ -81,11 +79,7 @@ function p2sh(a, opts) { lazy.prop(o, 'output', function () { if (!o.hash) return; - return bscript.compile([ - OPS.OP_HASH160, - o.hash, - OPS.OP_EQUAL - ]); + return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); }); // input dependents lazy.prop(o, 'redeem', function () { @@ -153,7 +147,7 @@ function p2sh(a, opts) { if (hasInput && hasWitness) throw new TypeError('Input and witness provided'); if (hasInput) { - const richunks = bscript.decompile(redeem.input); + const richunks = (bscript.decompile(redeem.input)); if (!bscript.isPushOnly(richunks)) throw new TypeError('Non push-only scriptSig'); } diff --git a/src/payments/p2wpkh.js b/src/payments/p2wpkh.js index 7b269b7..9165f96 100644 --- a/src/payments/p2wpkh.js +++ b/src/payments/p2wpkh.js @@ -13,11 +13,7 @@ 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) + if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); typef({ @@ -28,7 +24,7 @@ function p2wpkh(a, opts) { output: typef.maybe(typef.BufferN(22)), pubkey: typef.maybe(ecc.isPoint), signature: typef.maybe(bscript.isCanonicalScriptSignature), - witness: typef.maybe(typef.arrayOf(typef.Buffer)) + witness: typef.maybe(typef.arrayOf(typef.Buffer)), }, a); const _address = lazy.value(function () { const result = bech32.decode(a.address); @@ -37,7 +33,7 @@ function p2wpkh(a, opts) { return { version, prefix: result.prefix, - data: Buffer.from(data) + data: Buffer.from(data), }; }); const network = a.network || networks_1.bitcoin; @@ -60,10 +56,7 @@ function p2wpkh(a, opts) { lazy.prop(o, 'output', function () { if (!o.hash) return; - return bscript.compile([ - OPS.OP_0, - o.hash - ]); + return bscript.compile([OPS.OP_0, o.hash]); }); lazy.prop(o, 'pubkey', function () { if (a.pubkey) diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index a7e9f8b..7c71ac3 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const networks_1 = require("../networks"); // eslint-disable-line +const networks_1 = require("../networks"); const bscript = require("../script"); const bcrypto = require("../crypto"); const lazy = require("./lazy"); @@ -19,11 +19,7 @@ function stacksEqual(a, b) { // witness: [redeemScriptSig ...] {redeemScript} // output: OP_0 {sha256(redeemScript)} function p2wsh(a, opts) { - if (!a.address && - !a.hash && - !a.output && - !a.redeem && - !a.witness) + if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); typef({ @@ -35,10 +31,10 @@ function p2wsh(a, opts) { input: typef.maybe(typef.Buffer), network: typef.maybe(typef.Object), output: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)) + witness: typef.maybe(typef.arrayOf(typef.Buffer)), }), input: typef.maybe(typef.BufferN(0)), - witness: typef.maybe(typef.arrayOf(typef.Buffer)) + witness: typef.maybe(typef.arrayOf(typef.Buffer)), }, a); const _address = lazy.value(function () { const result = bech32.decode(a.address); @@ -47,10 +43,12 @@ function p2wsh(a, opts) { return { version, prefix: result.prefix, - data: Buffer.from(data) + data: Buffer.from(data), }; }); - const _rchunks = lazy.value(function () { return bscript.decompile(a.redeem.input); }); + const _rchunks = lazy.value(function () { + return bscript.decompile(a.redeem.input); + }); let network = a.network; if (!network) { network = (a.redeem && a.redeem.network) || networks_1.bitcoin; @@ -74,10 +72,7 @@ function p2wsh(a, opts) { lazy.prop(o, 'output', function () { if (!o.hash) return; - return bscript.compile([ - OPS.OP_0, - o.hash - ]); + return bscript.compile([OPS.OP_0, o.hash]); }); lazy.prop(o, 'redeem', function () { if (!a.witness) @@ -85,7 +80,7 @@ function p2wsh(a, opts) { return { output: a.witness[a.witness.length - 1], input: EMPTY_BUFFER, - witness: a.witness.slice(0, -1) + witness: a.witness.slice(0, -1), }; }); lazy.prop(o, 'input', function () { @@ -165,11 +160,15 @@ function p2wsh(a, opts) { } 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)) + 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])) + if (a.redeem && + a.redeem.output && + !a.redeem.output.equals(a.witness[a.witness.length - 1])) throw new TypeError('Witness and redeem.output mismatch'); } } diff --git a/src/script.js b/src/script.js index 3d115cb..a114d1f 100644 --- a/src/script.js +++ b/src/script.js @@ -11,10 +11,10 @@ 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) || + return (types.Number(value) && + (value === exports.OPS.OP_0 || (value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) || - (value === exports.OPS.OP_1NEGATE)); + value === exports.OPS.OP_1NEGATE)); } function isPushOnlyChunk(value) { return types.Buffer(value) || isOPInt(value); @@ -96,7 +96,7 @@ function decompile(buffer) { while (i < buffer.length) { const opcode = buffer[i]; // data chunk - if ((opcode > exports.OPS.OP_0) && (opcode <= exports.OPS.OP_PUSHDATA4)) { + 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) @@ -129,7 +129,8 @@ function toASM(chunks) { if (chunksIsBuffer(chunks)) { chunks = decompile(chunks); } - return chunks.map(function (chunk) { + return chunks + .map(function (chunk) { // data? if (singleChunkIsBuffer(chunk)) { const op = asMinimalOP(chunk); @@ -139,7 +140,8 @@ function toASM(chunks) { } // opcode! return REVERSE_OPS[chunk]; - }).join(' '); + }) + .join(' '); } exports.toASM = toASM; function fromASM(asm) { diff --git a/src/script_number.js b/src/script_number.js index da12de6..a8c42d3 100644 --- a/src/script_number.js +++ b/src/script_number.js @@ -19,8 +19,8 @@ function decode(buffer, maxLength, minimal) { const a = buffer.readUInt32LE(0); const b = buffer.readUInt8(4); if (b & 0x80) - return -(((b & ~0x80) * 0x100000000) + a); - return (b * 0x100000000) + a; + return -((b & ~0x80) * 0x100000000 + a); + return b * 0x100000000 + a; } // 32-bit / 24-bit / 16-bit / 8-bit let result = 0; @@ -33,11 +33,16 @@ function decode(buffer, maxLength, minimal) { } exports.decode = decode; function scriptNumSize(i) { - return i > 0x7fffffff ? 5 - : i > 0x7fffff ? 4 - : i > 0x7fff ? 3 - : i > 0x7f ? 2 - : i > 0x00 ? 1 + return i > 0x7fffffff + ? 5 + : i > 0x7fffff + ? 4 + : i > 0x7fff + ? 3 + : i > 0x7f + ? 2 + : i > 0x00 + ? 1 : 0; } function encode(number) { diff --git a/src/script_signature.js b/src/script_signature.js index c3372cd..c185981 100644 --- a/src/script_signature.js +++ b/src/script_signature.js @@ -34,14 +34,14 @@ function decode(buffer) { const s = fromDER(decode.s); return { signature: Buffer.concat([r, s], 64), - hashType: hashType + hashType: hashType, }; } exports.decode = decode; function encode(signature, hashType) { typeforce({ signature: types.BufferN(64), - hashType: types.UInt8 + hashType: types.UInt8, }, { signature, hashType }); const hashTypeMod = hashType & ~0x80; if (hashTypeMod <= 0 || hashTypeMod >= 4) @@ -50,9 +50,6 @@ function encode(signature, hashType) { 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 - ]); + return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); } exports.encode = encode; diff --git a/src/templates/nulldata.js b/src/templates/nulldata.js index fd5320d..b5ffdce 100644 --- a/src/templates/nulldata.js +++ b/src/templates/nulldata.js @@ -5,10 +5,11 @@ 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; + return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; } exports.check = check; -check.toJSON = function () { return 'null data output'; }; +check.toJSON = function () { + return 'null data output'; +}; const output = { check }; exports.output = output; diff --git a/src/transaction.js b/src/transaction.js index 34d7b40..cfd31fe 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -14,9 +14,10 @@ function varSliceSize(someScript) { } function vectorSize(someVector) { const length = someVector.length; - return varuint.encodingLength(length) + someVector.reduce((sum, witness) => { - return sum + varSliceSize(witness); - }, 0); + return (varuint.encodingLength(length) + + someVector.reduce((sum, witness) => { + return sum + varSliceSize(witness); + }, 0)); } const EMPTY_SCRIPT = Buffer.allocUnsafe(0); const EMPTY_WITNESS = []; @@ -25,7 +26,7 @@ const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000 const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex'); const BLANK_OUTPUT = { script: EMPTY_SCRIPT, - valueBuffer: VALUE_UINT64_MAX + valueBuffer: VALUE_UINT64_MAX, }; function isOutput(out) { return out.value !== undefined; @@ -90,14 +91,14 @@ class Transaction { index: readUInt32(), script: readVarSlice(), sequence: readUInt32(), - witness: EMPTY_WITNESS + witness: EMPTY_WITNESS, }); } const voutLen = readVarInt(); for (i = 0; i < voutLen; ++i) { tx.outs.push({ value: readUInt64(), - script: readVarSlice() + script: readVarSlice(), }); } if (hasWitnesses) { @@ -127,7 +128,7 @@ class Transaction { return true; } isCoinbase() { - return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash); + 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); @@ -140,7 +141,7 @@ class Transaction { index: index, script: scriptSig || EMPTY_SCRIPT, sequence: sequence, - witness: EMPTY_WITNESS + witness: EMPTY_WITNESS, }) - 1); } addOutput(scriptPubKey, value) { @@ -148,11 +149,11 @@ class Transaction { // Add the output and return the output's index return (this.outs.push({ script: scriptPubKey, - value: value + value: value, }) - 1); } hasWitnesses() { - return this.ins.some((x) => { + return this.ins.some(x => { return x.witness.length !== 0; }); } @@ -178,27 +179,29 @@ class Transaction { 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)); + (hasWitnesses + ? this.ins.reduce((sum, input) => { + return sum + vectorSize(input.witness); + }, 0) + : 0)); } clone() { const newTx = new Transaction(); newTx.version = this.version; newTx.locktime = this.locktime; - newTx.ins = this.ins.map((txIn) => { + newTx.ins = this.ins.map(txIn => { return { hash: txIn.hash, index: txIn.index, script: txIn.script, sequence: txIn.sequence, - witness: txIn.witness + witness: txIn.witness, }; }); - newTx.outs = this.outs.map((txOut) => { + newTx.outs = this.outs.map(txOut => { return { script: txOut.script, - value: txOut.value + value: txOut.value, }; }); return newTx; @@ -217,7 +220,7 @@ class Transaction { if (inIndex >= this.ins.length) return ONE; // ignore OP_CODESEPARATOR - const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter((x) => { + const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(x => { return x !== script_1.OPS.OP_CODESEPARATOR; })); const txTmp = this.clone(); @@ -257,7 +260,7 @@ class Transaction { } else { // "blank" others input scripts - txTmp.ins.forEach((input) => { + txTmp.ins.forEach(input => { input.script = EMPTY_SCRIPT; }); txTmp.ins[inIndex].script = ourScript; @@ -295,7 +298,7 @@ class Transaction { if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { tbuffer = Buffer.allocUnsafe(36 * this.ins.length); toffset = 0; - this.ins.forEach((txIn) => { + this.ins.forEach(txIn => { writeSlice(txIn.hash); writeUInt32(txIn.index); }); @@ -306,7 +309,7 @@ class Transaction { (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { tbuffer = Buffer.allocUnsafe(4 * this.ins.length); toffset = 0; - this.ins.forEach((txIn) => { + this.ins.forEach(txIn => { writeUInt32(txIn.sequence); }); hashSequence = bcrypto.hash256(tbuffer); @@ -318,13 +321,14 @@ class Transaction { }, 0); tbuffer = Buffer.allocUnsafe(txOutsSize); toffset = 0; - this.outs.forEach((out) => { + 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) { + 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; @@ -369,13 +373,13 @@ class Transaction { offset += slice.copy(buffer, offset); } function writeUInt8(i) { - offset = (buffer).writeUInt8(i, offset); + offset = buffer.writeUInt8(i, offset); } function writeUInt32(i) { - offset = (buffer).writeUInt32LE(i, offset); + offset = buffer.writeUInt32LE(i, offset); } function writeInt32(i) { - offset = (buffer).writeInt32LE(i, offset); + offset = buffer.writeInt32LE(i, offset); } function writeUInt64(i) { offset = bufferutils.writeUInt64LE(buffer, i, offset); @@ -399,14 +403,14 @@ class Transaction { writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); } writeVarInt(this.ins.length); - this.ins.forEach((txIn) => { + this.ins.forEach(txIn => { writeSlice(txIn.hash); writeUInt32(txIn.index); writeVarSlice(txIn.script); writeUInt32(txIn.sequence); }); writeVarInt(this.outs.length); - this.outs.forEach((txOut) => { + this.outs.forEach(txOut => { if (isOutput(txOut)) { writeUInt64(txOut.value); } @@ -416,7 +420,7 @@ class Transaction { writeVarSlice(txOut.script); }); if (hasWitnesses) { - this.ins.forEach((input) => { + this.ins.forEach(input => { writeVector(input.witness); }); } diff --git a/src/transaction_builder.js b/src/transaction_builder.js index 1cdc6bf..540a17a 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -43,7 +43,7 @@ class TransactionBuilder { txb.__addInputUnsafe(txIn.hash, txIn.index, { sequence: txIn.sequence, script: txIn.script, - witness: txIn.witness + witness: txIn.witness, }); }); // fix some things not possible through the public API @@ -89,7 +89,7 @@ class TransactionBuilder { return this.__addInputUnsafe(txHash, vout, { sequence: sequence, prevOutScript: prevOutScript, - value: value + value: value, }); } __addInputUnsafe(txHash, vout, options) { @@ -194,7 +194,7 @@ class TransactionBuilder { if (!canSign(input)) { if (witnessValue !== undefined) { if (input.value !== undefined && input.value !== witnessValue) - throw new Error('Input didn\'t match witnessValue'); + throw new Error("Input didn't match witnessValue"); typeforce(types.Satoshi, witnessValue); input.value = witnessValue; } @@ -251,18 +251,19 @@ class TransactionBuilder { } // 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 - }); - }); + 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; @@ -313,21 +314,25 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) { } switch (type) { case SCRIPT_TYPES.P2WPKH: { - const { output, pubkey, signature } = payments.p2wpkh({ witness: witnessStack }); + const { output, pubkey, signature } = payments.p2wpkh({ + witness: witnessStack, + }); return { prevOutScript: output, prevOutType: SCRIPT_TYPES.P2WPKH, pubkeys: [pubkey], - signatures: [signature] + signatures: [signature], }; } case SCRIPT_TYPES.P2PKH: { - const { output, pubkey, signature } = payments.p2pkh({ input: scriptSig }); + const { output, pubkey, signature } = payments.p2pkh({ + input: scriptSig, + }); return { prevOutScript: output, prevOutType: SCRIPT_TYPES.P2PKH, pubkeys: [pubkey], - signatures: [signature] + signatures: [signature], }; } case SCRIPT_TYPES.P2PK: { @@ -335,26 +340,26 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) { return { prevOutType: SCRIPT_TYPES.P2PK, pubkeys: [undefined], - signatures: [signature] + signatures: [signature], }; } case SCRIPT_TYPES.P2MS: { const { m, pubkeys, signatures } = payments.p2ms({ input: scriptSig, - output: scriptPubKey + output: scriptPubKey, }, { allowIncomplete: true }); return { prevOutType: SCRIPT_TYPES.P2MS, pubkeys: pubkeys, signatures: signatures, - maxSignatures: m + maxSignatures: m, }; } } if (type === SCRIPT_TYPES.P2SH) { const { output, redeem } = payments.p2sh({ input: scriptSig, - witness: witnessStack + witness: witnessStack, }); const outputType = classify.output(redeem.output); const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output); @@ -368,13 +373,13 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) { witnessScript: expanded.witnessScript, witnessScriptType: expanded.witnessScriptType, pubkeys: expanded.pubkeys, - signatures: expanded.signatures + signatures: expanded.signatures, }; } if (type === SCRIPT_TYPES.P2WSH) { const { output, redeem } = payments.p2wsh({ input: scriptSig, - witness: witnessStack + witness: witnessStack, }); const outputType = classify.output(redeem.output); let expanded; @@ -392,12 +397,12 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) { witnessScript: redeem.output, witnessScriptType: expanded.prevOutType, pubkeys: expanded.pubkeys, - signatures: expanded.signatures + signatures: expanded.signatures, }; } return { prevOutType: SCRIPT_TYPES.NONSTANDARD, - prevOutScript: scriptSig + prevOutScript: scriptSig, }; } // could be done in expandInput, but requires the original Transaction for hashForSignature @@ -444,7 +449,7 @@ function expandOutput(script, ourPubKey) { return { type, pubkeys: [ourPubKey], - signatures: [undefined] + signatures: [undefined], }; } case SCRIPT_TYPES.P2WPKH: { @@ -458,7 +463,7 @@ function expandOutput(script, ourPubKey) { return { type, pubkeys: [ourPubKey], - signatures: [undefined] + signatures: [undefined], }; } case SCRIPT_TYPES.P2PK: { @@ -466,7 +471,7 @@ function expandOutput(script, ourPubKey) { return { type, pubkeys: [p2pk.pubkey], - signatures: [undefined] + signatures: [undefined], }; } case SCRIPT_TYPES.P2MS: { @@ -475,7 +480,7 @@ function expandOutput(script, ourPubKey) { type, pubkeys: p2ms.pubkeys, signatures: p2ms.pubkeys.map(() => undefined), - maxSignatures: p2ms.m + maxSignatures: p2ms.m, }; } } @@ -483,7 +488,7 @@ function expandOutput(script, ourPubKey) { } function prepareInput(input, ourPubKey, redeemScript, witnessScript) { if (redeemScript && witnessScript) { - const p2wsh = payments.p2wsh({ redeem: { output: 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 }); @@ -494,7 +499,10 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { 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) + ')'); + 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; } @@ -513,7 +521,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { signType: expanded.type, pubkeys: expanded.pubkeys, signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures + maxSignatures: expanded.maxSignatures, }; } if (redeemScript) { @@ -531,7 +539,10 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { } const expanded = expandOutput(p2sh.redeem.output, ourPubKey); if (!expanded.pubkeys) - throw new Error(expanded.type + ' not supported as redeemScript (' + bscript.toASM(redeemScript) + ')'); + 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; } @@ -549,7 +560,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { signType: expanded.type, pubkeys: expanded.pubkeys, signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures + maxSignatures: expanded.maxSignatures, }; } if (witnessScript) { @@ -561,7 +572,10 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { } const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); if (!expanded.pubkeys) - throw new Error(expanded.type + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')'); + 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; } @@ -578,7 +592,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { signType: expanded.type, pubkeys: expanded.pubkeys, signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures + maxSignatures: expanded.maxSignatures, }; } if (input.prevOutType && input.prevOutScript) { @@ -591,13 +605,16 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { 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) + ')'); + 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; + signScript = (payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output); } return { prevOutType: expanded.type, @@ -607,7 +624,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { signType: expanded.type, pubkeys: expanded.pubkeys, signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures + maxSignatures: expanded.maxSignatures, }; } const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output; @@ -618,7 +635,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { signScript: prevOutScript, signType: SCRIPT_TYPES.P2PKH, pubkeys: [ourPubKey], - signatures: [undefined] + signatures: [undefined], }; } function build(type, input, allowIncomplete) { @@ -656,7 +673,7 @@ function build(type, input, allowIncomplete) { } // 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); + const validate = !allowIncomplete || m === signatures.length; return payments.p2ms({ m, pubkeys, signatures }, { allowIncomplete, validate }); } case SCRIPT_TYPES.P2SH: { @@ -667,8 +684,8 @@ function build(type, input, allowIncomplete) { redeem: { output: redeem.output || input.redeemScript, input: redeem.input, - witness: redeem.witness - } + witness: redeem.witness, + }, }); } case SCRIPT_TYPES.P2WSH: { @@ -679,21 +696,20 @@ function build(type, input, allowIncomplete) { redeem: { output: input.witnessScript, input: redeem.input, - witness: redeem.witness - } + witness: redeem.witness, + }, }); } } } function canSign(input) { - return input.signScript !== 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); + (input.hasWitness === false || input.value !== undefined)); } function signatureHashType(buffer) { return buffer.readUInt8(buffer.length - 1); diff --git a/src/types.js b/src/types.js index 13d1bc8..76b98cf 100644 --- a/src/types.js +++ b/src/types.js @@ -10,7 +10,9 @@ function BIP32Path(value) { return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); } exports.BIP32Path = BIP32Path; -BIP32Path.toJSON = function () { return 'BIP32 derivation path'; }; +BIP32Path.toJSON = function () { + return 'BIP32 derivation path'; +}; const SATOSHI_MAX = 21 * 1e14; function Satoshi(value) { return typeforce.UInt53(value) && value <= SATOSHI_MAX; @@ -23,11 +25,11 @@ exports.Network = typeforce.compile({ messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), bip32: { public: typeforce.UInt32, - private: typeforce.UInt32 + private: typeforce.UInt32, }, pubKeyHash: typeforce.UInt8, scriptHash: typeforce.UInt8, - wif: typeforce.UInt8 + wif: typeforce.UInt8, }); exports.Buffer256bit = typeforce.BufferN(32); exports.Hash160bit = typeforce.BufferN(20); diff --git a/test/ecpair.js b/test/ecpair.js index 4299470..0ff0e8b 100644 --- a/test/ecpair.js +++ b/test/ecpair.js @@ -1,5 +1,3 @@ -/* eslint-disable no-new */ - const { describe, it, beforeEach } = require('mocha') const assert = require('assert') const proxyquire = require('proxyquire') diff --git a/ts_src/address.ts b/ts_src/address.ts index 4299321..d547e78 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -1,101 +1,119 @@ -import { Network } from './networks' -import * as types from './types' -import * as bscript from './script' -import * as networks from './networks' -import * as payments from './payments' +import { Network } from './networks'; +import * as types from './types'; +import * as bscript from './script'; +import * as networks from './networks'; +import * as payments from './payments'; -const bech32 = require('bech32') -const bs58check = require('bs58check') -const typeforce = require('typeforce') +const bech32 = require('bech32'); +const bs58check = require('bs58check'); +const typeforce = require('typeforce'); export type Base58CheckResult = { hash: Buffer; version: number; -} +}; export type Bech32Result = { version: number; prefix: string; data: Buffer; -} +}; -export function fromBase58Check (address: string): Base58CheckResult { - const payload = bs58check.decode(address) +export function fromBase58Check(address: string): Base58CheckResult { + 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') + 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) + const version = payload.readUInt8(0); + const hash = payload.slice(1); - return { version: version, hash: hash } + return { version: version, hash: hash }; } -export function fromBech32 (address: string): Bech32Result { - const result = bech32.decode(address) - const data = bech32.fromWords(result.words.slice(1)) +export function fromBech32(address: string): Bech32Result { + 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) - } + data: Buffer.from(data), + }; } -export function toBase58Check (hash: Buffer, version: number): string { - typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments) +export function toBase58Check(hash: Buffer, version: number): string { + typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); - const payload = Buffer.allocUnsafe(21) - payload.writeUInt8(version, 0) - hash.copy(payload, 1) + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(version, 0); + hash.copy(payload, 1); - return bs58check.encode(payload) + return bs58check.encode(payload); } -export function toBech32 (data: Buffer, version: number, prefix: string): string { - const words = bech32.toWords(data) - words.unshift(version) +export function toBech32( + data: Buffer, + version: number, + prefix: string, +): string { + const words = bech32.toWords(data); + words.unshift(version); - return bech32.encode(prefix, words) + return bech32.encode(prefix, words); } -export function fromOutputScript (output: Buffer, network: Network): string { //TODO: Network - network = network || networks.bitcoin +export function fromOutputScript(output: Buffer, network: Network): string { + //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) {} + 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') + throw new Error(bscript.toASM(output) + ' has no matching Address'); } -export function toOutputScript (address: string, network: Network): Buffer { - network = network || networks.bitcoin +export function toOutputScript(address: string, network: Network): Buffer { + network = network || networks.bitcoin; - let decodeBase58: Base58CheckResult | undefined = undefined - let decodeBech32: Bech32Result | undefined = undefined + let decodeBase58: Base58CheckResult | undefined = undefined; + let decodeBech32: Bech32Result | undefined = undefined; try { - decodeBase58 = fromBase58Check(address) + 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 + 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) + decodeBech32 = fromBech32(address); } catch (e) {} if (decodeBech32) { - if (decodeBech32.prefix !== network.bech32) throw new Error(address + ' has an invalid prefix') + 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 + 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'); } diff --git a/ts_src/block.ts b/ts_src/block.ts index e3d1fc5..feb141e 100644 --- a/ts_src/block.ts +++ b/ts_src/block.ts @@ -1,17 +1,22 @@ -import { Transaction } from './transaction' -import * as types from './types' -import * as bcrypto from './crypto' -import { reverseBuffer } from './bufferutils' - -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') - -function txesHaveWitnessCommit (transactions: Array): boolean { - return transactions instanceof Array && +import { Transaction } from './transaction'; +import * as types from './types'; +import * as bcrypto from './crypto'; +import { reverseBuffer } from './bufferutils'; + +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', +); + +function txesHaveWitnessCommit(transactions: Array): boolean { + return ( + transactions instanceof Array && transactions[0] && transactions[0].ins && transactions[0].ins instanceof Array && @@ -19,255 +24,281 @@ function txesHaveWitnessCommit (transactions: Array): boolean { transactions[0].ins[0].witness && transactions[0].ins[0].witness instanceof Array && transactions[0].ins[0].witness.length > 0 + ); } -function anyTxHasWitness (transactions: Array): boolean { - 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 - ) +function anyTxHasWitness(transactions: Array): boolean { + 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, + ), ) + ); } export class Block { - version: number - prevHash?: Buffer - merkleRoot?: Buffer - timestamp: number - witnessCommit?: Buffer - bits: number - nonce: number - transactions?: Array - - constructor () { - this.version = 1 - this.timestamp = 0 - this.bits = 0 - this.nonce = 0 - this.prevHash = undefined - this.merkleRoot = undefined - this.witnessCommit = undefined - this.transactions = undefined + version: number; + prevHash?: Buffer; + merkleRoot?: Buffer; + timestamp: number; + witnessCommit?: Buffer; + bits: number; + nonce: number; + transactions?: Array; + + constructor() { + this.version = 1; + this.timestamp = 0; + this.bits = 0; + this.nonce = 0; + this.prevHash = undefined; + this.merkleRoot = undefined; + this.witnessCommit = undefined; + this.transactions = undefined; } - static fromBuffer (buffer: Buffer): Block { - if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)') + static fromBuffer(buffer: Buffer): Block { + if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)'); - let offset: number = 0 + let offset: number = 0; const readSlice = (n: number): Buffer => { - offset += n - return buffer.slice(offset - n, offset) - } + offset += n; + return buffer.slice(offset - n, offset); + }; const readUInt32 = (): number => { - const i = buffer.readUInt32LE(offset) - offset += 4 - return i - } + const i = buffer.readUInt32LE(offset); + offset += 4; + return i; + }; const readInt32 = (): number => { - const i = buffer.readInt32LE(offset) - offset += 4 - return i - } + 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() + 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 + if (buffer.length === 80) return block; const readVarInt = (): number => { - const vi = varuint.decode(buffer, offset) - offset += varuint.decode.bytes - return vi - } + const vi = varuint.decode(buffer, offset); + offset += varuint.decode.bytes; + return vi; + }; const readTransaction = (): any => { - const tx = Transaction.fromBuffer(buffer.slice(offset), true) - offset += tx.byteLength() - return tx - } + const tx = Transaction.fromBuffer(buffer.slice(offset), true); + offset += tx.byteLength(); + return tx; + }; - const nTransactions = readVarInt() - block.transactions = [] + const nTransactions = readVarInt(); + block.transactions = []; for (var i = 0; i < nTransactions; ++i) { - const tx = readTransaction() - block.transactions.push(tx) + const tx = readTransaction(); + block.transactions.push(tx); } - let witnessCommit = block.getWitnessCommit() + let witnessCommit = block.getWitnessCommit(); // This Block contains a witness commit - if (witnessCommit) block.witnessCommit = witnessCommit + if (witnessCommit) block.witnessCommit = witnessCommit; - return block + return block; } - static fromHex (hex: string): Block { - return Block.fromBuffer(Buffer.from(hex, 'hex')) + static fromHex(hex: string): Block { + return Block.fromBuffer(Buffer.from(hex, 'hex')); } - static calculateTarget (bits: number): Buffer { - 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 calculateTarget(bits: number): Buffer { + 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: Array, forWitness?: boolean): Buffer { - typeforce([{ getHash: types.Function }], transactions) - if (transactions.length === 0) throw errorMerkleNoTxes - if (forWitness && !txesHaveWitnessCommit(transactions)) throw errorWitnessNotSegwit + static calculateMerkleRoot( + transactions: Array, + forWitness?: boolean, + ): Buffer { + 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 hashes = transactions.map(transaction => + transaction.getHash(forWitness!), + ); - const rootHash = fastMerkleRoot(hashes, bcrypto.hash256) + const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); return forWitness - ? bcrypto.hash256(Buffer.concat([rootHash, transactions[0].ins[0].witness[0]])) - : rootHash + ? bcrypto.hash256( + Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]), + ) + : rootHash; } - getWitnessCommit (): Buffer | null { - if (!txesHaveWitnessCommit(this.transactions!)) return null + getWitnessCommit(): Buffer | null { + 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. - let 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 + let 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) - let result = witnessCommits[witnessCommits.length - 1] + let result = witnessCommits[witnessCommits.length - 1]; - if (!(result instanceof Buffer && result.length === 32)) return null - return result + if (!(result instanceof Buffer && result.length === 32)) return null; + return result; } - hasWitnessCommit (): boolean { - if (this.witnessCommit instanceof Buffer && - this.witnessCommit.length === 32) return true - if (this.getWitnessCommit() !== null) return true - return false + hasWitnessCommit(): boolean { + if ( + this.witnessCommit instanceof Buffer && + this.witnessCommit.length === 32 + ) + return true; + if (this.getWitnessCommit() !== null) return true; + return false; } - hasWitness (): boolean { - return anyTxHasWitness(this.transactions!) + hasWitness(): boolean { + return anyTxHasWitness(this.transactions!); } - byteLength (headersOnly: boolean): number { - if (headersOnly || !this.transactions) return 80 + byteLength(headersOnly: boolean): number { + if (headersOnly || !this.transactions) return 80; - return 80 + varuint.encodingLength(this.transactions.length) + + return ( + 80 + + varuint.encodingLength(this.transactions.length) + this.transactions.reduce((a, x) => a + x.byteLength(), 0) + ); } - getHash (): Buffer { - return bcrypto.hash256(this.toBuffer(true)) + getHash(): Buffer { + return bcrypto.hash256(this.toBuffer(true)); } - getId (): string { - return reverseBuffer(this.getHash()).toString('hex') + getId(): string { + return reverseBuffer(this.getHash()).toString('hex'); } - getUTCDate (): Date { - const date = new Date(0) // epoch - date.setUTCSeconds(this.timestamp) + getUTCDate(): Date { + const date = new Date(0); // epoch + date.setUTCSeconds(this.timestamp); - return date + return date; } // TODO: buffer, offset compatibility - toBuffer (headersOnly: boolean): Buffer { - const buffer: Buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)) + toBuffer(headersOnly: boolean): Buffer { + const buffer: Buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); - let offset: number = 0 + let offset: number = 0; const writeSlice = (slice: Buffer): void => { - slice.copy(buffer, offset) - offset += slice.length - } + slice.copy(buffer, offset); + offset += slice.length; + }; const writeInt32 = (i: number): void => { - buffer.writeInt32LE(i, offset) - offset += 4 - } + buffer.writeInt32LE(i, offset); + offset += 4; + }; const writeUInt32 = (i: number): void => { - buffer.writeUInt32LE(i, offset) - offset += 4 - } + buffer.writeUInt32LE(i, offset); + offset += 4; + }; - writeInt32(this.version) - writeSlice(this.prevHash!) - writeSlice(this.merkleRoot!) - writeUInt32(this.timestamp) - writeUInt32(this.bits) - writeUInt32(this.nonce) + writeInt32(this.version); + writeSlice(this.prevHash!); + writeSlice(this.merkleRoot!); + writeUInt32(this.timestamp); + writeUInt32(this.bits); + writeUInt32(this.nonce); - if (headersOnly || !this.transactions) return buffer + if (headersOnly || !this.transactions) return buffer; - varuint.encode(this.transactions.length, buffer, offset) - offset += varuint.encode.bytes + 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 - }) + const txSize = tx.byteLength(); // TODO: extract from toBuffer? + tx.toBuffer(buffer, offset); + offset += txSize; + }); - return buffer + return buffer; } - toHex (headersOnly: boolean): string { - return this.toBuffer(headersOnly).toString('hex') + toHex(headersOnly: boolean): string { + return this.toBuffer(headersOnly).toString('hex'); } - checkTxRoots (): boolean { + checkTxRoots(): boolean { // If the Block has segwit transactions but no witness commit, // there's no way it can be valid, so fail the check. - let hasWitnessCommit = this.hasWitnessCommit() - if (!hasWitnessCommit && this.hasWitness()) return false - return this.__checkMerkleRoot() && + let hasWitnessCommit = this.hasWitnessCommit(); + if (!hasWitnessCommit && this.hasWitness()) return false; + return ( + this.__checkMerkleRoot() && (hasWitnessCommit ? this.__checkWitnessCommit() : true) + ); } - checkMerkleRoot (): boolean { - console.warn('Deprecation Warning: Block method checkMerkleRoot will be ' + - 'deprecated in v5. Please use checkTxRoots instead.') - return this.checkTxRoots() + checkMerkleRoot(): boolean { + console.warn( + 'Deprecation Warning: Block method checkMerkleRoot will be ' + + 'deprecated in v5. Please use checkTxRoots instead.', + ); + return this.checkTxRoots(); } - __checkMerkleRoot (): boolean { - if (!this.transactions) throw errorMerkleNoTxes + __checkMerkleRoot(): boolean { + if (!this.transactions) throw errorMerkleNoTxes; - const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions) - return this.merkleRoot!.compare(actualMerkleRoot) === 0 + const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions); + return this.merkleRoot!.compare(actualMerkleRoot) === 0; } - __checkWitnessCommit (): boolean { - if (!this.transactions) throw errorMerkleNoTxes - if (!this.hasWitnessCommit()) throw errorWitnessNotSegwit + __checkWitnessCommit(): boolean { + if (!this.transactions) throw errorMerkleNoTxes; + if (!this.hasWitnessCommit()) throw errorWitnessNotSegwit; - const actualWitnessCommit = Block.calculateMerkleRoot(this.transactions, true) - return this.witnessCommit!.compare(actualWitnessCommit) === 0 + const actualWitnessCommit = Block.calculateMerkleRoot( + this.transactions, + true, + ); + return this.witnessCommit!.compare(actualWitnessCommit) === 0; } - checkProofOfWork (): boolean { - const hash: Buffer = reverseBuffer(this.getHash()) - const target = Block.calculateTarget(this.bits) + checkProofOfWork(): boolean { + const hash: Buffer = reverseBuffer(this.getHash()); + const target = Block.calculateTarget(this.bits); - return hash.compare(target) <= 0 + return hash.compare(target) <= 0; } } diff --git a/ts_src/bufferutils.ts b/ts_src/bufferutils.ts index 3ef7492..adb6060 100644 --- a/ts_src/bufferutils.ts +++ b/ts_src/bufferutils.ts @@ -1,37 +1,44 @@ // https://github.com/feross/buffer/blob/master/index.js#L1127 -function verifuint (value: number, max: number): void { - 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 verifuint(value: number, max: number): void { + 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'); } -export function readUInt64LE (buffer: Buffer, offset: number): number { - const a = buffer.readUInt32LE(offset) - let b = buffer.readUInt32LE(offset + 4) - b *= 0x100000000 +export function readUInt64LE(buffer: Buffer, offset: number): number { + const a = buffer.readUInt32LE(offset); + let b = buffer.readUInt32LE(offset + 4); + b *= 0x100000000; - verifuint(b + a, 0x001fffffffffffff) - return b + a + verifuint(b + a, 0x001fffffffffffff); + return b + a; } -export function writeUInt64LE (buffer: Buffer, value: number, offset: number): number { - verifuint(value, 0x001fffffffffffff) +export function writeUInt64LE( + buffer: Buffer, + value: number, + offset: number, +): number { + verifuint(value, 0x001fffffffffffff); - buffer.writeInt32LE(value & -1, offset) - buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4) - return offset + 8 + buffer.writeInt32LE(value & -1, offset); + buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4); + return offset + 8; } -export function reverseBuffer (buffer: Buffer): Buffer { - if (buffer.length < 1) return buffer - let j = buffer.length - 1 - let tmp = 0 +export function reverseBuffer(buffer: Buffer): 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-- + tmp = buffer[i]; + buffer[i] = buffer[j]; + buffer[j] = tmp; + j--; } - return buffer + return buffer; } diff --git a/ts_src/classify.ts b/ts_src/classify.ts index 9a5c849..0de93f9 100644 --- a/ts_src/classify.ts +++ b/ts_src/classify.ts @@ -1,65 +1,69 @@ -import { decompile } from './script' -import * as multisig from './templates/multisig' -import * as nullData from './templates/nulldata' -import * as pubKey from './templates/pubkey' -import * as pubKeyHash from './templates/pubkeyhash' -import * as scriptHash from './templates/scripthash' -import * as witnessPubKeyHash from './templates/witnesspubkeyhash' -import * as witnessScriptHash from './templates/witnessscripthash' -import * as witnessCommitment from './templates/witnesscommitment' +import { decompile } from './script'; +import * as multisig from './templates/multisig'; +import * as nullData from './templates/nulldata'; +import * as pubKey from './templates/pubkey'; +import * as pubKeyHash from './templates/pubkeyhash'; +import * as scriptHash from './templates/scripthash'; +import * as witnessPubKeyHash from './templates/witnesspubkeyhash'; +import * as witnessScriptHash from './templates/witnessscripthash'; +import * as witnessCommitment from './templates/witnesscommitment'; 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', +}; -function classifyOutput (script: Buffer): string { - 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 +function classifyOutput(script: Buffer): string { + 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 = decompile(script) - if (!chunks) throw new TypeError('Invalid script') + const chunks = 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 + 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 + return types.NONSTANDARD; } -function classifyInput (script: Buffer, allowIncomplete: boolean): string { +function classifyInput(script: Buffer, allowIncomplete: boolean): string { // XXX: optimization, below functions .decompile before use - const chunks = decompile(script) - if (!chunks) throw new TypeError('Invalid script') + const chunks = 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 + 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 + return types.NONSTANDARD; } -function classifyWitness (script: Array, allowIncomplete: boolean): string { +function classifyWitness( + script: Array, + allowIncomplete: boolean, +): string { // XXX: optimization, below functions .decompile before use - const chunks = decompile(script) - if (!chunks) throw new TypeError('Invalid script') + const chunks = 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 + if (witnessPubKeyHash.input.check(chunks)) return types.P2WPKH; + if (witnessScriptHash.input.check(>chunks, allowIncomplete)) + return types.P2WSH; - return types.NONSTANDARD + return types.NONSTANDARD; } export { @@ -67,4 +71,4 @@ export { classifyOutput as output, classifyWitness as witness, types, -} +}; diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts index 6aabb5b..6d92e60 100644 --- a/ts_src/crypto.ts +++ b/ts_src/crypto.ts @@ -1,21 +1,27 @@ -const createHash = require('create-hash') +const createHash = require('create-hash'); -export function ripemd160 (buffer: Buffer): Buffer { - return createHash('rmd160').update(buffer).digest() +export function ripemd160(buffer: Buffer): Buffer { + return createHash('rmd160') + .update(buffer) + .digest(); } -export function sha1 (buffer: Buffer): Buffer { - return createHash('sha1').update(buffer).digest() +export function sha1(buffer: Buffer): Buffer { + return createHash('sha1') + .update(buffer) + .digest(); } -export function sha256 (buffer: Buffer): Buffer { - return createHash('sha256').update(buffer).digest() +export function sha256(buffer: Buffer): Buffer { + return createHash('sha256') + .update(buffer) + .digest(); } -export function hash160 (buffer: Buffer): Buffer { - return ripemd160(sha256(buffer)) +export function hash160(buffer: Buffer): Buffer { + return ripemd160(sha256(buffer)); } -export function hash256 (buffer: Buffer): Buffer { - return sha256(sha256(buffer)) +export function hash256(buffer: Buffer): Buffer { + return sha256(sha256(buffer)); } diff --git a/ts_src/ecpair.ts b/ts_src/ecpair.ts index ad28adf..15ec00a 100644 --- a/ts_src/ecpair.ts +++ b/ts_src/ecpair.ts @@ -1,130 +1,132 @@ -import { Network } from './networks' -import * as NETWORKS from './networks' -import * as types from './types' -const ecc = require('tiny-secp256k1') -const randomBytes = require('randombytes') -const typeforce = require('typeforce') -const wif = require('wif') - -const isOptions = typeforce.maybe(typeforce.compile({ - compressed: types.maybe(types.Boolean), - network: types.maybe(types.Network) -})) +import { Network } from './networks'; +import * as NETWORKS from './networks'; +import * as types from './types'; +const ecc = require('tiny-secp256k1'); +const randomBytes = require('randombytes'); +const typeforce = require('typeforce'); +const wif = require('wif'); + +const isOptions = typeforce.maybe( + typeforce.compile({ + compressed: types.maybe(types.Boolean), + network: types.maybe(types.Network), + }), +); interface ECPairOptions { - compressed?: boolean - network?: Network - rng?(arg0: Buffer): Buffer + compressed?: boolean; + network?: Network; + rng?(arg0: Buffer): Buffer; } export interface ECPairInterface { - compressed: boolean - network: Network - privateKey?: Buffer - publicKey?: Buffer - toWIF(): string - sign(hash: Buffer): Buffer - verify(hash: Buffer, signature: Buffer): Buffer - getPublicKey?(): Buffer + compressed: boolean; + network: Network; + privateKey?: Buffer; + publicKey?: Buffer; + toWIF(): string; + sign(hash: Buffer): Buffer; + verify(hash: Buffer, signature: Buffer): Buffer; + getPublicKey?(): Buffer; } class ECPair implements ECPairInterface { - compressed: boolean - network: Network - private __d?: Buffer - private __Q?: Buffer - - constructor (d?: Buffer, Q?: Buffer, options?: ECPairOptions) { - if (options === undefined) options = {} - this.compressed = options.compressed === undefined ? true : options.compressed - this.network = options.network || NETWORKS.bitcoin - - this.__d = undefined - this.__Q = undefined - if (d !== undefined) this.__d = d - if (Q !== undefined) this.__Q = ecc.pointCompress(Q, this.compressed) + compressed: boolean; + network: Network; + private __d?: Buffer; + private __Q?: Buffer; + + constructor(d?: Buffer, Q?: Buffer, options?: ECPairOptions) { + if (options === undefined) options = {}; + this.compressed = + options.compressed === undefined ? true : options.compressed; + this.network = options.network || NETWORKS.bitcoin; + + this.__d = undefined; + this.__Q = undefined; + if (d !== undefined) this.__d = d; + if (Q !== undefined) this.__Q = ecc.pointCompress(Q, this.compressed); } - get privateKey (): Buffer | undefined { - return this.__d + get privateKey(): Buffer | undefined { + return this.__d; } - get publicKey (): Buffer | undefined { - if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__d, this.compressed) - return this.__Q + get publicKey(): Buffer | undefined { + if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__d, this.compressed); + return this.__Q; } - toWIF (): string { - if (!this.__d) throw new Error('Missing private key') - return wif.encode(this.network.wif, this.__d, this.compressed) + toWIF(): string { + if (!this.__d) throw new Error('Missing private key'); + return wif.encode(this.network.wif, this.__d, this.compressed); } - sign (hash: Buffer): Buffer { - if (!this.__d) throw new Error('Missing private key') - return ecc.sign(hash, this.__d) + sign(hash: Buffer): Buffer { + if (!this.__d) throw new Error('Missing private key'); + return ecc.sign(hash, this.__d); } - verify (hash: Buffer, signature: Buffer): Buffer { - return ecc.verify(hash, this.publicKey, signature) + verify(hash: Buffer, signature: Buffer): Buffer { + return ecc.verify(hash, this.publicKey, signature); } } -function fromPrivateKey (buffer: Buffer, options?: ECPairOptions): ECPair { - typeforce(types.Buffer256bit, buffer) - if (!ecc.isPrivate(buffer)) throw new TypeError('Private key not in range [1, n)') - typeforce(isOptions, options) +function fromPrivateKey(buffer: Buffer, options?: ECPairOptions): ECPair { + 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) + return new ECPair(buffer, undefined, options); } -function fromPublicKey (buffer: Buffer, options?: ECPairOptions): ECPair { - typeforce(ecc.isPoint, buffer) - typeforce(isOptions, options) - return new ECPair(undefined, buffer, options) +function fromPublicKey(buffer: Buffer, options?: ECPairOptions): ECPair { + typeforce(ecc.isPoint, buffer); + typeforce(isOptions, options); + return new ECPair(undefined, buffer, options); } -function fromWIF (string: string, network?: Network | Array): ECPair { - const decoded = wif.decode(string) - const version = decoded.version +function fromWIF(string: string, network?: Network | Array): ECPair { + const decoded = wif.decode(string); + const version = decoded.version; // list of networks? if (types.Array(network)) { - network = (>network).filter(function (x: Network) { - return version === x.wif - }).pop() + network = (>network) + .filter(function(x: Network) { + return version === x.wif; + }) + .pop(); - if (!network) throw new Error('Unknown network version') + if (!network) throw new Error('Unknown network version'); - // otherwise, assume a network object (or default to bitcoin) + // otherwise, assume a network object (or default to bitcoin) } else { - network = network || NETWORKS.bitcoin + network = network || NETWORKS.bitcoin; - if (version !== (network).wif) throw new Error('Invalid network version') + if (version !== (network).wif) + throw new Error('Invalid network version'); } return fromPrivateKey(decoded.privateKey, { compressed: decoded.compressed, - network: network - }) + network: network, + }); } -function makeRandom (options?: ECPairOptions): ECPair { - typeforce(isOptions, options) - if (options === undefined) options = {} - const rng = options.rng || randomBytes +function makeRandom(options?: ECPairOptions): ECPair { + typeforce(isOptions, options); + if (options === undefined) options = {}; + const rng = options.rng || randomBytes; - let d + let d; do { - d = rng(32) - typeforce(types.Buffer256bit, d) - } while (!ecc.isPrivate(d)) + d = rng(32); + typeforce(types.Buffer256bit, d); + } while (!ecc.isPrivate(d)); - return fromPrivateKey(d, options) + return fromPrivateKey(d, options); } -export { - makeRandom, - fromPrivateKey, - fromPublicKey, - fromWIF -} +export { makeRandom, fromPrivateKey, fromPublicKey, fromWIF }; diff --git a/ts_src/index.ts b/ts_src/index.ts index 7574048..fce7304 100644 --- a/ts_src/index.ts +++ b/ts_src/index.ts @@ -1,28 +1,20 @@ -import * as bip32 from 'bip32' -import * as ECPair from './ecpair' -import * as address from './address' -import * as crypto from './crypto' -import * as networks from './networks' -import * as payments from './payments' -import * as script from './script' +import * as bip32 from 'bip32'; +import * as ECPair from './ecpair'; +import * as address from './address'; +import * as crypto from './crypto'; +import * as networks from './networks'; +import * as payments from './payments'; +import * as script from './script'; -export { - ECPair, - address, - bip32, - crypto, - networks, - payments, - script, -} +export { ECPair, address, bip32, crypto, networks, payments, script }; -export { Block } from './block' -export { Transaction } from './transaction' -export { TransactionBuilder } from './transaction_builder' -export { OPS as opcodes } from './script' +export { Block } from './block'; +export { Transaction } from './transaction'; +export { TransactionBuilder } from './transaction_builder'; +export { OPS as opcodes } from './script'; -export { Payment, PaymentOpts } from './payments' -export { Input as TxInput, Output as TxOutput } from './transaction' -export { Network } from './networks' -export { OpCode } from './script' -export { BIP32Interface } from 'bip32' +export { Payment, PaymentOpts } from './payments'; +export { Input as TxInput, Output as TxOutput } from './transaction'; +export { Network } from './networks'; +export { OpCode } from './script'; +export { BIP32Interface } from 'bip32'; diff --git a/ts_src/networks.ts b/ts_src/networks.ts index 11147af..0212a44 100644 --- a/ts_src/networks.ts +++ b/ts_src/networks.ts @@ -7,43 +7,43 @@ export type Network = { pubKeyHash: number; scriptHash: number; wif: number; -} +}; type bip32 = { public: number; private: number; -} +}; export const bitcoin: Network = { messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'bc', bip32: { public: 0x0488b21e, - private: 0x0488ade4 + private: 0x0488ade4, }, pubKeyHash: 0x00, scriptHash: 0x05, - wif: 0x80 -} + wif: 0x80, +}; export const regtest: Network = { messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'bcrt', bip32: { public: 0x043587cf, - private: 0x04358394 + private: 0x04358394, }, pubKeyHash: 0x6f, scriptHash: 0xc4, - wif: 0xef -} + wif: 0xef, +}; export const testnet: Network = { messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'tb', bip32: { public: 0x043587cf, - private: 0x04358394 + private: 0x04358394, }, pubKeyHash: 0x6f, scriptHash: 0xc4, - wif: 0xef -} + wif: 0xef, +}; diff --git a/ts_src/payments/embed.ts b/ts_src/payments/embed.ts index bfaeb8b..d7a2b0f 100644 --- a/ts_src/payments/embed.ts +++ b/ts_src/payments/embed.ts @@ -1,54 +1,59 @@ -import { Payment, PaymentOpts } from './index' // eslint-disable-line -import * as bscript from '../script' -import * as lazy from './lazy' -import { bitcoin as BITCOIN_NETWORK } from '../networks' -const typef = require('typeforce') -const OPS = bscript.OPS - -function stacksEqual (a: Array, b: Array): boolean { - if (a.length !== b.length) return false - - return a.every(function (x, i) { - return x.equals(b[i]) - }) +import { Payment, PaymentOpts } from './index'; +import * as bscript from '../script'; +import * as lazy from './lazy'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +const typef = require('typeforce'); +const OPS = bscript.OPS; + +function stacksEqual(a: Array, b: Array): boolean { + if (a.length !== b.length) return false; + + return a.every(function(x, i) { + return x.equals(b[i]); + }); } // output: OP_RETURN ... -export function p2data (a: Payment, opts?: PaymentOpts): Payment { - 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 || BITCOIN_NETWORK - const o = { network } - - lazy.prop(o, 'output', function () { - if (!a.data) return - return bscript.compile((>[OPS.OP_RETURN]).concat(a.data)) - }) - lazy.prop(o, 'data', function () { - if (!a.output) return - return bscript.decompile(a.output)!.slice(1) - }) +export function p2data(a: Payment, opts?: PaymentOpts): Payment { + 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 || BITCOIN_NETWORK; + const o = { network }; + + lazy.prop(o, 'output', function() { + if (!a.data) return; + return bscript.compile( + (>[OPS.OP_RETURN]).concat(a.data), + ); + }); + lazy.prop(o, 'data', function() { + 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') + 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); } diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts index 7fc72d8..6727eeb 100644 --- a/ts_src/payments/index.ts +++ b/ts_src/payments/index.ts @@ -1,35 +1,35 @@ -import { Network } from '../networks' // eslint-disable-line -import { p2data as embed } from './embed' -import { p2ms } from './p2ms' -import { p2pk } from './p2pk' -import { p2pkh } from './p2pkh' -import { p2sh } from './p2sh' -import { p2wpkh } from './p2wpkh' -import { p2wsh } from './p2wsh' +import { Network } from '../networks'; +import { p2data as embed } from './embed'; +import { p2ms } from './p2ms'; +import { p2pk } from './p2pk'; +import { p2pkh } from './p2pkh'; +import { p2sh } from './p2sh'; +import { p2wpkh } from './p2wpkh'; +import { p2wsh } from './p2wsh'; export interface Payment { - network?: Network, - output?: Buffer, - data?: Array, - m?: number, - n?: number, - pubkeys?: Array, - input?: Buffer, - signatures?: Array, - pubkey?: Buffer, - signature?: Buffer, - address?: string, - hash?: Buffer, - redeem?: Payment, - witness?: Array, + network?: Network; + output?: Buffer; + data?: Array; + m?: number; + n?: number; + pubkeys?: Array; + input?: Buffer; + signatures?: Array; + pubkey?: Buffer; + signature?: Buffer; + address?: string; + hash?: Buffer; + redeem?: Payment; + witness?: Array; } export interface PaymentOpts { - validate?: boolean, - allowIncomplete?: boolean, + validate?: boolean; + allowIncomplete?: boolean; } -export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh } +export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh }; // TODO // witness commitment diff --git a/ts_src/payments/lazy.ts b/ts_src/payments/lazy.ts index a6f0951..474c8e9 100644 --- a/ts_src/payments/lazy.ts +++ b/ts_src/payments/lazy.ts @@ -1,28 +1,28 @@ -export function prop (object: Object, name: string, f: ()=>any): void { +export function prop(object: Object, name: string, f: () => any): void { Object.defineProperty(object, name, { configurable: true, enumerable: true, - get: function () { - let value = f.call(this) - this[name] = value - return value + get: function() { + let value = f.call(this); + this[name] = value; + return value; }, - set: function (value) { + set: function(value) { Object.defineProperty(this, name, { configurable: true, enumerable: true, value: value, - writable: true - }) - } - }) + writable: true, + }); + }, + }); } -export function value (f: ()=>T): ()=>T { - let value: T - return function (): T { - if (value !== undefined) return value - value = f() - return value - } +export function value(f: () => T): () => T { + let value: T; + return function(): T { + if (value !== undefined) return value; + value = f(); + return value; + }; } diff --git a/ts_src/payments/p2ms.ts b/ts_src/payments/p2ms.ts index 34e2d31..766f341 100644 --- a/ts_src/payments/p2ms.ts +++ b/ts_src/payments/p2ms.ts @@ -1,141 +1,160 @@ -import { Payment, PaymentOpts } from './index' // eslint-disable-line -import * as bscript from '../script' -import * as lazy from './lazy' -import { bitcoin as BITCOIN_NETWORK } from '../networks' -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: Array, b: Array): boolean { - if (a.length !== b.length) return false - - return a.every(function (x, i) { - return x.equals(b[i]) - }) +import { Payment, PaymentOpts } from './index'; +import * as bscript from '../script'; +import * as lazy from './lazy'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +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: Array, b: Array): boolean { + if (a.length !== b.length) return false; + + return a.every(function(x, i) { + return x.equals(b[i]); + }); } // input: OP_0 [signatures ...] // output: m [pubKeys ...] n OP_CHECKMULTISIG -export function p2ms (a: Payment, opts?: PaymentOpts): Payment { +export function p2ms(a: Payment, opts?: PaymentOpts): Payment { 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: Buffer | number) { - return bscript.isCanonicalScriptSignature(x) || - (opts!.allowIncomplete && - ( x === OPS.OP_0)) !== undefined // eslint-disable-line + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + + function isAcceptableSignature(x: Buffer | number) { + 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 || BITCOIN_NETWORK - const o: Payment = { network } - - let chunks: Array = [] - let decoded = false - function decode (output: Buffer | Array): void { - if (decoded) return - decoded = true - chunks = >bscript.decompile(output) - o.m = chunks[0] - OP_INT_BASE // eslint-disable-line - o.n = chunks[chunks.length - 2] - OP_INT_BASE // eslint-disable-line - o.pubkeys = >chunks.slice(1, -2) + 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 || BITCOIN_NETWORK; + const o: Payment = { network }; + + let chunks: Array = []; + let decoded = false; + function decode(output: Buffer | Array): void { + 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', function () { - 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', function () { - if (!o.output) return - decode(o.output) - return o.m - }) - lazy.prop(o, 'n', function () { - if (!o.pubkeys) return - return o.pubkeys.length - }) - lazy.prop(o, 'pubkeys', function () { - if (!a.output) return - decode(a.output) - return o.pubkeys - }) - lazy.prop(o, 'signatures', function () { - if (!a.input) return - return bscript.decompile(a.input)!.slice(1) - }) - lazy.prop(o, 'input', function () { - if (!a.signatures) return - return bscript.compile((>[OPS.OP_0]).concat(a.signatures)) - }) - lazy.prop(o, 'witness', function () { - if (!o.input) return - return [] - }) + lazy.prop(o, 'output', function() { + 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', function() { + if (!o.output) return; + decode(o.output); + return o.m; + }); + lazy.prop(o, 'n', function() { + if (!o.pubkeys) return; + return o.pubkeys.length; + }); + lazy.prop(o, 'pubkeys', function() { + if (!a.output) return; + decode(a.output); + return o.pubkeys; + }); + lazy.prop(o, 'signatures', function() { + if (!a.input) return; + return bscript.decompile(a.input)!.slice(1); + }); + lazy.prop(o, 'input', function() { + if (!a.signatures) return; + return bscript.compile( + (>[OPS.OP_0]).concat(a.signatures), + ); + }); + lazy.prop(o, 'witness', function() { + 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 || // eslint-disable-line - o.n! > 16 || // eslint-disable-line - o.m! > o.n! || // eslint-disable-line - 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') + 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 (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 (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.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.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) + return Object.assign(o, a); } diff --git a/ts_src/payments/p2pk.ts b/ts_src/payments/p2pk.ts index 3385c56..69fb404 100644 --- a/ts_src/payments/p2pk.ts +++ b/ts_src/payments/p2pk.ts @@ -1,78 +1,80 @@ -import { Payment, PaymentOpts } from './index' // eslint-disable-line -import * as bscript from '../script' -import * as lazy from './lazy' -import { bitcoin as BITCOIN_NETWORK } from '../networks' -const typef = require('typeforce') -const OPS = bscript.OPS -const ecc = require('tiny-secp256k1') +import { Payment, PaymentOpts } from './index'; +import * as bscript from '../script'; +import * as lazy from './lazy'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); // input: {signature} // output: {pubKey} OP_CHECKSIG -export function p2pk (a: Payment, opts?: PaymentOpts): Payment { - if ( - !a.input && - !a.output && - !a.pubkey && - !a.input && - !a.signature - ) throw new TypeError('Not enough data') - opts = Object.assign({ validate: true }, opts || {}) +export function p2pk(a: Payment, opts?: PaymentOpts): Payment { + 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), + 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) + signature: typef.maybe(bscript.isCanonicalScriptSignature), + input: typef.maybe(typef.Buffer), + }, + a, + ); - const _chunks = <()=>Array>lazy.value(function () { return bscript.decompile(a.input!) }) + const _chunks = <() => Array>lazy.value(function() { + return bscript.decompile(a.input!); + }); - const network = a.network || BITCOIN_NETWORK - const o: Payment = { network } + const network = a.network || BITCOIN_NETWORK; + const o: Payment = { network }; - lazy.prop(o, 'output', function () { - if (!a.pubkey) return - return bscript.compile([ - a.pubkey, - OPS.OP_CHECKSIG - ]) - }) - lazy.prop(o, 'pubkey', function () { - if (!a.output) return - return a.output.slice(1, -1) - }) - lazy.prop(o, 'signature', function () { - if (!a.input) return - return _chunks()[0] - }) - lazy.prop(o, 'input', function () { - if (!a.signature) return - return bscript.compile([a.signature]) - }) - lazy.prop(o, 'witness', function () { - if (!o.input) return - return [] - }) + lazy.prop(o, 'output', function() { + if (!a.pubkey) return; + return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); + }); + lazy.prop(o, 'pubkey', function() { + if (!a.output) return; + return a.output.slice(1, -1); + }); + lazy.prop(o, 'signature', function() { + if (!a.input) return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', function() { + if (!a.signature) return; + return bscript.compile([a.signature]); + }); + lazy.prop(o, 'witness', function() { + 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.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 && !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 (_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) + return Object.assign(o, a); } diff --git a/ts_src/payments/p2pkh.ts b/ts_src/payments/p2pkh.ts index ddb9cc3..63594ad 100644 --- a/ts_src/payments/p2pkh.ts +++ b/ts_src/payments/p2pkh.ts @@ -1,101 +1,103 @@ -import { Payment, PaymentOpts } from './index' // eslint-disable-line -import * as bscript from '../script' -import * as bcrypto from '../crypto' -import * as lazy from './lazy' -import { bitcoin as BITCOIN_NETWORK } from '../networks' -const typef = require('typeforce') -const OPS = bscript.OPS -const ecc = require('tiny-secp256k1') +import { Payment, PaymentOpts } from './index'; +import * as bscript from '../script'; +import * as bcrypto from '../crypto'; +import * as lazy from './lazy'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); -const bs58check = require('bs58check') +const bs58check = require('bs58check'); // input: {signature} {pubkey} // output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG -export function p2pkh (a: Payment, opts?: PaymentOpts): Payment { - 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(function () { - const payload = bs58check.decode(a.address) - const version = payload.readUInt8(0) - const hash = payload.slice(1) - return { version, hash } - }) - const _chunks = <()=>Array>lazy.value(function () { return bscript.decompile(a.input!) }) - - const network = a.network || BITCOIN_NETWORK - const o: Payment = { network } - - lazy.prop(o, 'address', function () { - 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', function () { - 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', function () { - if (!o.hash) return +export function p2pkh(a: Payment, opts?: PaymentOpts): Payment { + 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(function() { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = <() => Array>lazy.value(function() { + return bscript.decompile(a.input!); + }); + + const network = a.network || BITCOIN_NETWORK; + const o: Payment = { network }; + + lazy.prop(o, 'address', function() { + 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', function() { + 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', function() { + 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', function () { - if (!a.input) return - return _chunks()[1] - }) - lazy.prop(o, 'signature', function () { - if (!a.input) return - return _chunks()[0] - }) - lazy.prop(o, 'input', function () { - if (!a.pubkey) return - if (!a.signature) return - return bscript.compile([a.signature, a.pubkey]) - }) - lazy.prop(o, 'witness', function () { - if (!o.input) return - return [] - }) + OPS.OP_CHECKSIG, + ]); + }); + lazy.prop(o, 'pubkey', function() { + if (!a.input) return; + return _chunks()[1]; + }); + lazy.prop(o, 'signature', function() { + if (!a.input) return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', function() { + if (!a.pubkey) return; + if (!a.signature) return; + return bscript.compile([a.signature, a.pubkey]); + }); + lazy.prop(o, 'witness', function() { + if (!o.input) return; + return []; + }); // extended validation if (opts.validate) { - let hash: Buffer = Buffer.from([]) + let hash: Buffer = 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 (_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 (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; } if (a.output) { @@ -105,32 +107,41 @@ export function p2pkh (a: Payment, opts?: PaymentOpts): Payment { 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 + 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 + 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') + 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) + return Object.assign(o, a); } diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index daaa8cc..b7df1dc 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -1,127 +1,127 @@ -import { Payment, PaymentOpts } from './index' // eslint-disable-line -import { bitcoin as BITCOIN_NETWORK } from '../networks' // eslint-disable-line -import * as bscript from '../script' -import * as bcrypto from '../crypto' -import * as lazy from './lazy' -const typef = require('typeforce') -const OPS = bscript.OPS - -const bs58check = require('bs58check') - -function stacksEqual (a: Array, b: Array): boolean { - if (a.length !== b.length) return false - - return a.every(function (x, i) { - return x.equals(b[i]) - }) +import { Payment, PaymentOpts } from './index'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +import * as bscript from '../script'; +import * as bcrypto from '../crypto'; +import * as lazy from './lazy'; +const typef = require('typeforce'); +const OPS = bscript.OPS; + +const bs58check = require('bs58check'); + +function stacksEqual(a: Array, b: Array): boolean { + if (a.length !== b.length) return false; + + return a.every(function(x, i) { + return x.equals(b[i]); + }); } // input: [redeemScriptSig ...] {redeemScript} // witness: // output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL -export function p2sh (a: Payment, opts?: PaymentOpts): Payment { - 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({ +export function p2sh(a: Payment, opts?: PaymentOpts): Payment { + 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), - output: typef.maybe(typef.Buffer), + + 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)), + }), input: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)) - }), - input: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)) - }, a) + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }, + a, + ); - let network = a.network + let network = a.network; if (!network) { - network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK + network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK; } - const o: Payment = { network } - - const _address = lazy.value(function () { - const payload = bs58check.decode(a.address) - const version = payload.readUInt8(0) - const hash = payload.slice(1) - return { version, hash } - }) - const _chunks = <()=>Array>lazy.value(function () { return bscript.decompile(a.input!) }) - const _redeem = lazy.value(function (): Payment { - const chunks = _chunks() + const o: Payment = { network }; + + const _address = lazy.value(function() { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = <() => Array>lazy.value(function() { + return bscript.decompile(a.input!); + }); + const _redeem = lazy.value(function(): Payment { + const chunks = _chunks(); return { network, output: chunks[chunks.length - 1], input: bscript.compile(chunks.slice(0, -1)), - witness: a.witness || [] - } - }) + witness: a.witness || [], + }; + }); // output dependents - lazy.prop(o, 'address', function () { - 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', function () { + lazy.prop(o, 'address', function() { + 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', function() { // 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', function () { - if (!o.hash) return - return bscript.compile([ - OPS.OP_HASH160, - o.hash, - OPS.OP_EQUAL - ]) - }) + 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', function() { + if (!o.hash) return; + return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); + }); // input dependents - lazy.prop(o, 'redeem', function () { - if (!a.input) return - return _redeem() - }) - lazy.prop(o, 'input', function () { - 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', function () { - if (o.redeem && o.redeem.witness) return o.redeem.witness - if (o.input) return [] - }) + lazy.prop(o, 'redeem', function() { + if (!a.input) return; + return _redeem(); + }); + lazy.prop(o, 'input', function() { + 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', function() { + if (o.redeem && o.redeem.witness) return o.redeem.witness; + if (o.input) return []; + }); if (opts.validate) { - let hash: Buffer = Buffer.from([]) + let hash: Buffer = 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 (_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 (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; } if (a.output) { @@ -129,64 +129,79 @@ export function p2sh (a: Payment, opts?: PaymentOpts): Payment { 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 + 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 = function (redeem: Payment): void { + const checkRedeem = function(redeem: Payment): void { // 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') + 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 + 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') + 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') + 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') + 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()) + checkRedeem(_redeem()); } if (a.redeem) { - if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch') + 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') + 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) + 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') + !stacksEqual(a.redeem.witness, a.witness) + ) + throw new TypeError('Witness and redeem.witness mismatch'); } } - return Object.assign(o, a) + return Object.assign(o, a); } diff --git a/ts_src/payments/p2wpkh.ts b/ts_src/payments/p2wpkh.ts index 6cfecc4..04f0e92 100644 --- a/ts_src/payments/p2wpkh.ts +++ b/ts_src/payments/p2wpkh.ts @@ -1,134 +1,142 @@ -import { Payment, PaymentOpts } from './index' // eslint-disable-line -import * as bscript from '../script' -import * as bcrypto from '../crypto' -import * as lazy from './lazy' -import { bitcoin as BITCOIN_NETWORK } from '../networks' -const typef = require('typeforce') -const OPS = bscript.OPS -const ecc = require('tiny-secp256k1') +import { Payment, PaymentOpts } from './index'; +import * as bscript from '../script'; +import * as bcrypto from '../crypto'; +import * as lazy from './lazy'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); -const bech32 = require('bech32') +const bech32 = require('bech32'); -const EMPTY_BUFFER = Buffer.alloc(0) +const EMPTY_BUFFER = Buffer.alloc(0); // witness: {signature} {pubKey} // input: <> // output: OP_0 {pubKeyHash} -export function p2wpkh (a: Payment, opts?: PaymentOpts): Payment { - 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(function () { - const result = bech32.decode(a.address) - const version = result.words.shift() - const data = bech32.fromWords(result.words) +export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment { + 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(function() { + 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 || BITCOIN_NETWORK - const o: Payment = { network } - - lazy.prop(o, 'address', function () { - if (!o.hash) return - - const words = bech32.toWords(o.hash) - words.unshift(0x00) - return bech32.encode(network.bech32, words) - }) - lazy.prop(o, 'hash', function () { - 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', function () { - if (!o.hash) return - return bscript.compile([ - OPS.OP_0, - o.hash - ]) - }) - lazy.prop(o, 'pubkey', function () { - if (a.pubkey) return a.pubkey - if (!a.witness) return - return a.witness[1] - }) - lazy.prop(o, 'signature', function () { - if (!a.witness) return - return a.witness[0] - }) - lazy.prop(o, 'input', function () { - if (!o.witness) return - return EMPTY_BUFFER - }) - lazy.prop(o, 'witness', function () { - if (!a.pubkey) return - if (!a.signature) return - return [a.signature, a.pubkey] - }) + data: Buffer.from(data), + }; + }); + + const network = a.network || BITCOIN_NETWORK; + const o: Payment = { network }; + + lazy.prop(o, 'address', function() { + if (!o.hash) return; + + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', function() { + 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', function() { + if (!o.hash) return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'pubkey', function() { + if (a.pubkey) return a.pubkey; + if (!a.witness) return; + return a.witness[1]; + }); + lazy.prop(o, 'signature', function() { + if (!a.witness) return; + return a.witness[0]; + }); + lazy.prop(o, 'input', function() { + if (!o.witness) return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', function() { + if (!a.pubkey) return; + if (!a.signature) return; + return [a.signature, a.pubkey]; + }); // extended validation if (opts.validate) { - let hash: Buffer = Buffer.from([]) + let hash: Buffer = 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 (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 (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) + 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 + 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.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) + return Object.assign(o, a); } diff --git a/ts_src/payments/p2wsh.ts b/ts_src/payments/p2wsh.ts index edb0a69..28a235c 100644 --- a/ts_src/payments/p2wsh.ts +++ b/ts_src/payments/p2wsh.ts @@ -1,103 +1,100 @@ -import { Payment, PaymentOpts } from './index' // eslint-disable-line -import { bitcoin as BITCOIN_NETWORK } from '../networks' // eslint-disable-line -import * as bscript from '../script' -import * as bcrypto from '../crypto' -import * as lazy from './lazy' -const typef = require('typeforce') -const OPS = bscript.OPS +import { Payment, PaymentOpts } from './index'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +import * as bscript from '../script'; +import * as bcrypto from '../crypto'; +import * as lazy from './lazy'; +const typef = require('typeforce'); +const OPS = bscript.OPS; -const bech32 = require('bech32') +const bech32 = require('bech32'); -const EMPTY_BUFFER = Buffer.alloc(0) +const EMPTY_BUFFER = Buffer.alloc(0); -function stacksEqual (a: Array, b: Array): boolean { - if (a.length !== b.length) return false +function stacksEqual(a: Array, b: Array): boolean { + if (a.length !== b.length) return false; - return a.every(function (x, i) { - return x.equals(b[i]) - }) + return a.every(function(x, i) { + return x.equals(b[i]); + }); } // input: <> // witness: [redeemScriptSig ...] {redeemScript} // output: OP_0 {sha256(redeemScript)} -export function p2wsh (a: Payment, opts?: PaymentOpts): Payment { - 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), +export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { + 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), - output: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)) - }), - input: typef.maybe(typef.BufferN(0)), - witness: typef.maybe(typef.arrayOf(typef.Buffer)) - }, a) - - const _address = lazy.value(function () { - const result = bech32.decode(a.address) - const version = result.words.shift() - const data = bech32.fromWords(result.words) + + 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)), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }, + a, + ); + + const _address = lazy.value(function() { + 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 = <()=>Array>lazy.value(function () { return bscript.decompile(a.redeem!.input!) }) - - let network = a.network + data: Buffer.from(data), + }; + }); + const _rchunks = <() => Array>lazy.value(function() { + return bscript.decompile(a.redeem!.input!); + }); + + let network = a.network; if (!network) { - network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK + network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK; } - const o: Payment = { network } - - lazy.prop(o, 'address', function () { - if (!o.hash) return - const words = bech32.toWords(o.hash) - words.unshift(0x00) - return bech32.encode(network!.bech32, words) - }) - lazy.prop(o, 'hash', function () { - 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', function () { - if (!o.hash) return - return bscript.compile([ - OPS.OP_0, - o.hash - ]) - }) - lazy.prop(o, 'redeem', function () { - if (!a.witness) return + const o: Payment = { network }; + + lazy.prop(o, 'address', function() { + if (!o.hash) return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network!.bech32, words); + }); + lazy.prop(o, 'hash', function() { + 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', function() { + if (!o.hash) return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'redeem', function() { + 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', function () { - if (!o.witness) return - return EMPTY_BUFFER - }) - lazy.prop(o, 'witness', function () { + witness: a.witness.slice(0, -1), + }; + }); + lazy.prop(o, 'input', function() { + if (!o.witness) return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', function() { // transform redeem input to witness stack? if ( a.redeem && @@ -106,47 +103,55 @@ export function p2wsh (a: Payment, opts?: PaymentOpts): Payment { a.redeem.output && a.redeem.output.length > 0 ) { - const stack = bscript.toStack(_rchunks()) + 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) + 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) - }) + 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 = Buffer.from([]) + let hash: Buffer = 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 (_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 (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 + 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') + if (a.redeem.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); // is there two redeem sources? if ( @@ -154,26 +159,40 @@ export function p2wsh (a: Payment, opts?: PaymentOpts): Payment { a.redeem.input.length > 0 && a.redeem.witness && a.redeem.witness.length > 0 - ) throw new TypeError('Ambiguous witness source') + ) + 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') + 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 + 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.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 && + 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) + return Object.assign(o, a); } diff --git a/ts_src/script.ts b/ts_src/script.ts index 64b74ad..36fef95 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -1,206 +1,218 @@ -import * as types from './types' -import * as scriptNumber from './script_number' -import * as scriptSignature from './script_signature' -const bip66 = require('bip66') -const ecc = require('tiny-secp256k1') -const pushdata = require('pushdata-bitcoin') -const typeforce = require('typeforce') - -export type OpCode = number -export const OPS = <{[index:string]: OpCode}> require('bitcoin-ops') - -const REVERSE_OPS = <{[index:number]: string}> require('bitcoin-ops/map') -const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1 - -function isOPInt (value:number): boolean { - return types.Number(value) && - ((value === OPS.OP_0) || - (value >= OPS.OP_1 && value <= OPS.OP_16) || - (value === OPS.OP_1NEGATE)) +import * as types from './types'; +import * as scriptNumber from './script_number'; +import * as scriptSignature from './script_signature'; +const bip66 = require('bip66'); +const ecc = require('tiny-secp256k1'); +const pushdata = require('pushdata-bitcoin'); +const typeforce = require('typeforce'); + +export type OpCode = number; +export const OPS = <{ [index: string]: OpCode }>require('bitcoin-ops'); + +const REVERSE_OPS = <{ [index: number]: string }>require('bitcoin-ops/map'); +const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 + +function isOPInt(value: number): boolean { + return ( + types.Number(value) && + (value === OPS.OP_0 || + (value >= OPS.OP_1 && value <= OPS.OP_16) || + value === OPS.OP_1NEGATE) + ); } -function isPushOnlyChunk (value: number | Buffer): boolean { - return types.Buffer(value) || isOPInt(value) +function isPushOnlyChunk(value: number | Buffer): boolean { + return types.Buffer(value) || isOPInt(value); } -export function isPushOnly (value: Array) { - return types.Array(value) && value.every(isPushOnlyChunk) +export function isPushOnly(value: Array) { + return types.Array(value) && value.every(isPushOnlyChunk); } -function asMinimalOP (buffer: Buffer): number | void { - if (buffer.length === 0) return OPS.OP_0 - if (buffer.length !== 1) return - if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0] - if (buffer[0] === 0x81) return OPS.OP_1NEGATE +function asMinimalOP(buffer: Buffer): number | void { + if (buffer.length === 0) return OPS.OP_0; + if (buffer.length !== 1) return; + if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]; + if (buffer[0] === 0x81) return OPS.OP_1NEGATE; } function chunksIsBuffer(buf: Buffer | Array): buf is Buffer { - return Buffer.isBuffer(buf) + return Buffer.isBuffer(buf); } -function chunksIsArray(buf: Buffer | Array): buf is Array { - return types.Array(buf) +function chunksIsArray( + buf: Buffer | Array, +): buf is Array { + return types.Array(buf); } function singleChunkIsBuffer(buf: number | Buffer): buf is Buffer { - return Buffer.isBuffer(buf) + return Buffer.isBuffer(buf); } -export function compile (chunks: Buffer | Array): Buffer { +export function compile(chunks: Buffer | Array): Buffer { // TODO: remove me - if (chunksIsBuffer(chunks)) return chunks + if (chunksIsBuffer(chunks)) return chunks; - typeforce(types.Array, chunks) + typeforce(types.Array, chunks); - const bufferSize = chunks.reduce(function (accum: number, chunk) { + const bufferSize = chunks.reduce(function(accum: number, 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 + 1; } - return accum + pushdata.encodingLength(chunk.length) + chunk.length + return accum + pushdata.encodingLength(chunk.length) + chunk.length; } // opcode - return accum + 1 - }, 0.0) + return accum + 1; + }, 0.0); - const buffer = Buffer.allocUnsafe(bufferSize) - let offset = 0 + const buffer = Buffer.allocUnsafe(bufferSize); + let offset = 0; - chunks.forEach(function (chunk) { + chunks.forEach(function(chunk) { // data chunk if (singleChunkIsBuffer(chunk)) { // adhere to BIP62.3, minimal push policy - const opcode = asMinimalOP(chunk) + const opcode = asMinimalOP(chunk); if (opcode !== undefined) { - buffer.writeUInt8(opcode, offset) - offset += 1 - return + buffer.writeUInt8(opcode, offset); + offset += 1; + return; } - offset += pushdata.encode(buffer, chunk.length, offset) - chunk.copy(buffer, offset) - offset += chunk.length + offset += pushdata.encode(buffer, chunk.length, offset); + chunk.copy(buffer, offset); + offset += chunk.length; - // opcode + // opcode } else { - buffer.writeUInt8(chunk, offset) - offset += 1 + buffer.writeUInt8(chunk, offset); + offset += 1; } - }) + }); - if (offset !== buffer.length) throw new Error('Could not decode chunks') - return buffer + if (offset !== buffer.length) throw new Error('Could not decode chunks'); + return buffer; } -export function decompile (buffer: Buffer | Array): Array | null { +export function decompile( + buffer: Buffer | Array, +): Array | null { // TODO: remove me - if (chunksIsArray(buffer)) return buffer + if (chunksIsArray(buffer)) return buffer; - typeforce(types.Buffer, buffer) + typeforce(types.Buffer, buffer); - const chunks: Array = [] - let i = 0 + const chunks: Array = []; + let i = 0; while (i < buffer.length) { - const opcode = buffer[i] + const opcode = buffer[i]; // data chunk - if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) { - const d = pushdata.decode(buffer, i) + if (opcode > OPS.OP_0 && opcode <= OPS.OP_PUSHDATA4) { + const d = pushdata.decode(buffer, i); // did reading a pushDataInt fail? - if (d === null) return null - i += d.size + if (d === null) return null; + i += d.size; // attempt to read too much data? - if (i + d.number > buffer.length) return null + if (i + d.number > buffer.length) return null; - const data = buffer.slice(i, i + d.number) - i += d.number + const data = buffer.slice(i, i + d.number); + i += d.number; // decompile minimally - const op = asMinimalOP(data) + const op = asMinimalOP(data); if (op !== undefined) { - chunks.push(op) + chunks.push(op); } else { - chunks.push(data) + chunks.push(data); } - // opcode + // opcode } else { - chunks.push(opcode) + chunks.push(opcode); - i += 1 + i += 1; } } - return chunks + return chunks; } -export function toASM (chunks: Buffer | Array): string { +export function toASM(chunks: Buffer | Array): string { if (chunksIsBuffer(chunks)) { - chunks = >decompile(chunks) + chunks = >decompile(chunks); } - return chunks.map(function (chunk) { - // data? - if (singleChunkIsBuffer(chunk)) { - const op = asMinimalOP(chunk) - if (op === undefined) return chunk.toString('hex') - chunk = op - } + return chunks + .map(function(chunk) { + // data? + if (singleChunkIsBuffer(chunk)) { + const op = asMinimalOP(chunk); + if (op === undefined) return chunk.toString('hex'); + chunk = op; + } - // opcode! - return REVERSE_OPS[chunk] - }).join(' ') + // opcode! + return REVERSE_OPS[chunk]; + }) + .join(' '); } -export function fromASM (asm: string): Buffer { - typeforce(types.String, asm) +export function fromASM(asm: string): Buffer { + typeforce(types.String, asm); - return compile(asm.split(' ').map(function (chunkStr) { - // opcode? - if (OPS[chunkStr] !== undefined) return OPS[chunkStr] - typeforce(types.Hex, chunkStr) + return compile( + asm.split(' ').map(function(chunkStr) { + // opcode? + if (OPS[chunkStr] !== undefined) return OPS[chunkStr]; + typeforce(types.Hex, chunkStr); - // data! - return Buffer.from(chunkStr, 'hex') - })) + // data! + return Buffer.from(chunkStr, 'hex'); + }), + ); } -export function toStack (chunks: Buffer | Array): Array { - chunks = >decompile(chunks) - typeforce(isPushOnly, chunks) +export function toStack( + chunks: Buffer | Array, +): Array { + chunks = >decompile(chunks); + typeforce(isPushOnly, chunks); - return chunks.map(function (op) { - if (singleChunkIsBuffer(op)) return op - if (op === OPS.OP_0) return Buffer.allocUnsafe(0) + return chunks.map(function(op) { + if (singleChunkIsBuffer(op)) return op; + if (op === OPS.OP_0) return Buffer.allocUnsafe(0); - return scriptNumber.encode(op - OP_INT_BASE) - }) + return scriptNumber.encode(op - OP_INT_BASE); + }); } -export function isCanonicalPubKey (buffer: Buffer): boolean { - return ecc.isPoint(buffer) +export function isCanonicalPubKey(buffer: Buffer): boolean { + return ecc.isPoint(buffer); } -export function isDefinedHashType (hashType: number): boolean { - const hashTypeMod = hashType & ~0x80 +export function isDefinedHashType(hashType: number): boolean { + const hashTypeMod = hashType & ~0x80; // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE - return hashTypeMod > 0x00 && hashTypeMod < 0x04 + return hashTypeMod > 0x00 && hashTypeMod < 0x04; } -export function isCanonicalScriptSignature (buffer: Buffer): boolean { - if (!Buffer.isBuffer(buffer)) return false - if (!isDefinedHashType(buffer[buffer.length - 1])) return false +export function isCanonicalScriptSignature(buffer: Buffer): boolean { + if (!Buffer.isBuffer(buffer)) return false; + if (!isDefinedHashType(buffer[buffer.length - 1])) return false; - return bip66.check(buffer.slice(0, -1)) + return bip66.check(buffer.slice(0, -1)); } -export const number = scriptNumber -export const signature = scriptSignature +export const number = scriptNumber; +export const signature = scriptSignature; diff --git a/ts_src/script_number.ts b/ts_src/script_number.ts index d88c463..cd48373 100644 --- a/ts_src/script_number.ts +++ b/ts_src/script_number.ts @@ -1,62 +1,71 @@ +export function decode( + buffer: Buffer, + maxLength?: number, + minimal?: boolean, +): number { + maxLength = maxLength || 4; + minimal = minimal === undefined ? true : minimal; - -export function decode (buffer: Buffer, maxLength?: number, minimal?: boolean): 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') + 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') + 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) + const a = buffer.readUInt32LE(0); + const b = buffer.readUInt8(4); - if (b & 0x80) return -(((b & ~0x80) * 0x100000000) + a) - return (b * 0x100000000) + a + if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a); + return b * 0x100000000 + a; } // 32-bit / 24-bit / 16-bit / 8-bit - let result = 0 + let result = 0; for (var i = 0; i < length; ++i) { - result |= buffer[i] << (8 * i) + result |= buffer[i] << (8 * i); } - if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1)))) - return result + if (buffer[length - 1] & 0x80) + return -(result & ~(0x80 << (8 * (length - 1)))); + return result; } -function scriptNumSize (i: number): number { - return i > 0x7fffffff ? 5 - : i > 0x7fffff ? 4 - : i > 0x7fff ? 3 - : i > 0x7f ? 2 - : i > 0x00 ? 1 - : 0 +function scriptNumSize(i: number): number { + return i > 0x7fffffff + ? 5 + : i > 0x7fffff + ? 4 + : i > 0x7fff + ? 3 + : i > 0x7f + ? 2 + : i > 0x00 + ? 1 + : 0; } -export function encode (number: number): Buffer { - let value = Math.abs(number) - const size = scriptNumSize(value) - const buffer = Buffer.allocUnsafe(size) - const negative = number < 0 +export function encode(number: number): Buffer { + let value = Math.abs(number); + const size = scriptNumSize(value); + const buffer = Buffer.allocUnsafe(size); + const negative = number < 0; for (var i = 0; i < size; ++i) { - buffer.writeUInt8(value & 0xff, i) - value >>= 8 + buffer.writeUInt8(value & 0xff, i); + value >>= 8; } if (buffer[size - 1] & 0x80) { - buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1) + buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); } else if (negative) { - buffer[size - 1] |= 0x80 + buffer[size - 1] |= 0x80; } - return buffer + return buffer; } diff --git a/ts_src/script_signature.ts b/ts_src/script_signature.ts index 3b5bc6e..1f11d00 100644 --- a/ts_src/script_signature.ts +++ b/ts_src/script_signature.ts @@ -1,64 +1,66 @@ -import * as types from './types' -const bip66 = require('bip66') +import * as types from './types'; +const bip66 = require('bip66'); -const typeforce = require('typeforce') +const typeforce = require('typeforce'); -const ZERO = Buffer.alloc(1, 0) -function toDER (x: Buffer): Buffer { - 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 +const ZERO = Buffer.alloc(1, 0); +function toDER(x: Buffer): Buffer { + 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: Buffer): 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 +function fromDER(x: Buffer): 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; } interface ScriptSignature { - signature: Buffer - hashType: number + signature: Buffer; + hashType: number; } // BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) -export function decode (buffer: Buffer): ScriptSignature { - const hashType = buffer.readUInt8(buffer.length - 1) - const hashTypeMod = hashType & ~0x80 - if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType) +export function decode(buffer: Buffer): ScriptSignature { + const hashType = buffer.readUInt8(buffer.length - 1); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); - const decode = bip66.decode(buffer.slice(0, -1)) - const r = fromDER(decode.r) - const s = fromDER(decode.s) + const decode = bip66.decode(buffer.slice(0, -1)); + const r = fromDER(decode.r); + const s = fromDER(decode.s); return { signature: Buffer.concat([r, s], 64), - hashType: hashType - } + hashType: hashType, + }; } -export function encode (signature: Buffer, hashType: number): Buffer { - typeforce({ - signature: types.BufferN(64), - hashType: types.UInt8 - }, { signature, hashType }) +export function encode(signature: Buffer, hashType: number): Buffer { + 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 hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); - const hashTypeBuffer = Buffer.allocUnsafe(1) - hashTypeBuffer.writeUInt8(hashType, 0) + const hashTypeBuffer = Buffer.allocUnsafe(1); + hashTypeBuffer.writeUInt8(hashType, 0); - const r = toDER(signature.slice(0, 32)) - const s = toDER(signature.slice(32, 64)) + const r = toDER(signature.slice(0, 32)); + const s = toDER(signature.slice(32, 64)); - return Buffer.concat([ - bip66.encode(r, s), - hashTypeBuffer - ]) + return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); } diff --git a/ts_src/templates/nulldata.ts b/ts_src/templates/nulldata.ts index 0833eac..f0694a3 100644 --- a/ts_src/templates/nulldata.ts +++ b/ts_src/templates/nulldata.ts @@ -1,17 +1,16 @@ // OP_RETURN {data} -import * as bscript from '../script' -const OPS = bscript.OPS +import * as bscript from '../script'; +const OPS = bscript.OPS; -export function check (script: Buffer | Array): boolean { - const buffer = bscript.compile(script) +export function check(script: Buffer | Array): boolean { + const buffer = bscript.compile(script); - return buffer.length > 1 && - buffer[0] === OPS.OP_RETURN + return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; } -check.toJSON = function () { return 'null data output' } +check.toJSON = function() { + return 'null data output'; +}; -const output = { check } +const output = { check }; -export { - output -} +export { output }; diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index e6e1d24..a456a32 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -1,282 +1,311 @@ -import * as bcrypto from './crypto' -import * as bscript from './script' -import * as types from './types' -import * as bufferutils from './bufferutils' -import { reverseBuffer } from './bufferutils' -import { OPS as opcodes } from './script' +import * as bcrypto from './crypto'; +import * as bscript from './script'; +import * as types from './types'; +import * as bufferutils from './bufferutils'; +import { reverseBuffer } from './bufferutils'; +import { OPS as opcodes } from './script'; -const typeforce = require('typeforce') -const varuint = require('varuint-bitcoin') +const typeforce = require('typeforce'); +const varuint = require('varuint-bitcoin'); -function varSliceSize (someScript: Buffer): number { - const length = someScript.length +function varSliceSize(someScript: Buffer): number { + const length = someScript.length; - return varuint.encodingLength(length) + length + return varuint.encodingLength(length) + length; } -function vectorSize (someVector: Array): number { - const length = someVector.length +function vectorSize(someVector: Array): number { + const length = someVector.length; - return varuint.encodingLength(length) + someVector.reduce((sum, witness) => { - return sum + varSliceSize(witness) - }, 0) + return ( + varuint.encodingLength(length) + + someVector.reduce((sum, witness) => { + return sum + varSliceSize(witness); + }, 0) + ); } -const EMPTY_SCRIPT: Buffer = Buffer.allocUnsafe(0) -const EMPTY_WITNESS: Array = [] -const ZERO: Buffer = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex') -const ONE: Buffer = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex') -const VALUE_UINT64_MAX: Buffer = Buffer.from('ffffffffffffffff', 'hex') +const EMPTY_SCRIPT: Buffer = Buffer.allocUnsafe(0); +const EMPTY_WITNESS: Array = []; +const ZERO: Buffer = Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000000', + 'hex', +); +const ONE: Buffer = Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000001', + 'hex', +); +const VALUE_UINT64_MAX: Buffer = Buffer.from('ffffffffffffffff', 'hex'); const BLANK_OUTPUT: BlankOutput = { script: EMPTY_SCRIPT, - valueBuffer: VALUE_UINT64_MAX -} + valueBuffer: VALUE_UINT64_MAX, +}; function isOutput(out: Output | BlankOutput): out is Output { - return (out).value !== undefined + return (out).value !== undefined; } export type BlankOutput = { - script: Buffer - valueBuffer: Buffer -} + script: Buffer; + valueBuffer: Buffer; +}; export type Output = { - script: Buffer - value: number -} + script: Buffer; + value: number; +}; export type Input = { - hash: Buffer - index: number - script: Buffer - sequence: number - witness: Array -} + hash: Buffer; + index: number; + script: Buffer; + sequence: number; + witness: Array; +}; export class Transaction { - version: number - locktime: number - ins: Array - outs: Array - - static readonly DEFAULT_SEQUENCE = 0xffffffff - static readonly SIGHASH_ALL = 0x01 - static readonly SIGHASH_NONE = 0x02 - static readonly SIGHASH_SINGLE = 0x03 - static readonly SIGHASH_ANYONECANPAY = 0x80 - static readonly ADVANCED_TRANSACTION_MARKER = 0x00 - static readonly ADVANCED_TRANSACTION_FLAG = 0x01 - - constructor () { - this.version = 1 - this.locktime = 0 - this.ins = [] - this.outs = [] + version: number; + locktime: number; + ins: Array; + outs: Array; + + static readonly DEFAULT_SEQUENCE = 0xffffffff; + static readonly SIGHASH_ALL = 0x01; + static readonly SIGHASH_NONE = 0x02; + static readonly SIGHASH_SINGLE = 0x03; + static readonly SIGHASH_ANYONECANPAY = 0x80; + static readonly ADVANCED_TRANSACTION_MARKER = 0x00; + static readonly ADVANCED_TRANSACTION_FLAG = 0x01; + + constructor() { + this.version = 1; + this.locktime = 0; + this.ins = []; + this.outs = []; } - static fromBuffer (buffer: Buffer, __noStrict?: boolean): Transaction { - let offset: number = 0 + static fromBuffer(buffer: Buffer, __noStrict?: boolean): Transaction { + let offset: number = 0; - function readSlice (n: number): Buffer { - offset += n - return buffer.slice(offset - n, offset) + function readSlice(n: number): Buffer { + offset += n; + return buffer.slice(offset - n, offset); } - function readUInt32 (): number { - const i = buffer.readUInt32LE(offset) - offset += 4 - return i + function readUInt32(): number { + const i = buffer.readUInt32LE(offset); + offset += 4; + return i; } - function readInt32 (): number { - const i = buffer.readInt32LE(offset) - offset += 4 - return i + function readInt32(): number { + const i = buffer.readInt32LE(offset); + offset += 4; + return i; } - function readUInt64 (): number { - const i = bufferutils.readUInt64LE(buffer, offset) - offset += 8 - return i + function readUInt64(): number { + const i = bufferutils.readUInt64LE(buffer, offset); + offset += 8; + return i; } - function readVarInt (): number { - const vi = varuint.decode(buffer, offset) - offset += varuint.decode.bytes - return vi + function readVarInt(): number { + const vi = varuint.decode(buffer, offset); + offset += varuint.decode.bytes; + return vi; } - function readVarSlice (): Buffer { - return readSlice(readVarInt()) + function readVarSlice(): Buffer { + return readSlice(readVarInt()); } - function readVector (): Array { - const count = readVarInt() - const vector: Array = [] - for (var i = 0; i < count; i++) vector.push(readVarSlice()) - return vector + function readVector(): Array { + const count = readVarInt(); + const vector: Array = []; + for (var i = 0; i < count; i++) vector.push(readVarSlice()); + return vector; } - const tx = new Transaction() - tx.version = readInt32() + const tx = new Transaction(); + tx.version = readInt32(); - const marker = buffer.readUInt8(offset) - const flag = buffer.readUInt8(offset + 1) + 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 + let hasWitnesses = false; + if ( + marker === Transaction.ADVANCED_TRANSACTION_MARKER && + flag === Transaction.ADVANCED_TRANSACTION_FLAG + ) { + offset += 2; + hasWitnesses = true; } - const vinLen = readVarInt() + const vinLen = readVarInt(); for (var i = 0; i < vinLen; ++i) { tx.ins.push({ hash: readSlice(32), index: readUInt32(), script: readVarSlice(), sequence: readUInt32(), - witness: EMPTY_WITNESS - }) + witness: EMPTY_WITNESS, + }); } - const voutLen = readVarInt() + const voutLen = readVarInt(); for (i = 0; i < voutLen; ++i) { tx.outs.push({ value: readUInt64(), - script: readVarSlice() - }) + script: readVarSlice(), + }); } if (hasWitnesses) { for (i = 0; i < vinLen; ++i) { - tx.ins[i].witness = readVector() + tx.ins[i].witness = readVector(); } // was this pointless? - if (!tx.hasWitnesses()) throw new Error('Transaction has superfluous witness data') + if (!tx.hasWitnesses()) + throw new Error('Transaction has superfluous witness data'); } - tx.locktime = readUInt32() + tx.locktime = readUInt32(); - if (__noStrict) return tx - if (offset !== buffer.length) throw new Error('Transaction has unexpected data') + if (__noStrict) return tx; + if (offset !== buffer.length) + throw new Error('Transaction has unexpected data'); - return tx + return tx; } - static fromHex (hex: string): Transaction { - return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false) + static fromHex(hex: string): Transaction { + return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false); } - static isCoinbaseHash (buffer: Buffer): boolean { - typeforce(types.Hash256bit, buffer) + static isCoinbaseHash(buffer: Buffer): boolean { + typeforce(types.Hash256bit, buffer); for (var i = 0; i < 32; ++i) { - if (buffer[i] !== 0) return false + if (buffer[i] !== 0) return false; } - return true + return true; } - isCoinbase (): boolean { - return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) + isCoinbase(): boolean { + return ( + this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) + ); } - addInput (hash: Buffer, index: number, sequence?: number, scriptSig?: Buffer): number { - typeforce(types.tuple( - types.Hash256bit, - types.UInt32, - types.maybe(types.UInt32), - types.maybe(types.Buffer) - ), arguments) + addInput( + hash: Buffer, + index: number, + sequence?: number, + scriptSig?: Buffer, + ): number { + typeforce( + types.tuple( + types.Hash256bit, + types.UInt32, + types.maybe(types.UInt32), + types.maybe(types.Buffer), + ), + arguments, + ); if (types.Null(sequence)) { - sequence = Transaction.DEFAULT_SEQUENCE + sequence = Transaction.DEFAULT_SEQUENCE; } // Add the input and return the input's index - return (this.ins.push({ - hash: hash, - index: index, - script: scriptSig || EMPTY_SCRIPT, - sequence: sequence, - witness: EMPTY_WITNESS - }) - 1) + return ( + this.ins.push({ + hash: hash, + index: index, + script: scriptSig || EMPTY_SCRIPT, + sequence: sequence, + witness: EMPTY_WITNESS, + }) - 1 + ); } - addOutput (scriptPubKey: Buffer, value: number): number { - typeforce(types.tuple(types.Buffer, types.Satoshi), arguments) + addOutput(scriptPubKey: Buffer, value: number): number { + typeforce(types.tuple(types.Buffer, types.Satoshi), arguments); // Add the output and return the output's index - return (this.outs.push({ - script: scriptPubKey, - value: value - }) - 1) + return ( + this.outs.push({ + script: scriptPubKey, + value: value, + }) - 1 + ); } - hasWitnesses (): boolean { - return this.ins.some((x) => { - return x.witness.length !== 0 - }) + hasWitnesses(): boolean { + return this.ins.some(x => { + return x.witness.length !== 0; + }); } - weight (): number { - const base = this.__byteLength(false) - const total = this.__byteLength(true) - return base * 3 + total + weight(): number { + const base = this.__byteLength(false); + const total = this.__byteLength(true); + return base * 3 + total; } - virtualSize (): number { - return Math.ceil(this.weight() / 4) + virtualSize(): number { + return Math.ceil(this.weight() / 4); } - byteLength (): number { - return this.__byteLength(true) + byteLength(): number { + return this.__byteLength(true); } - private __byteLength (__allowWitness: boolean): number { - const hasWitnesses = __allowWitness && this.hasWitnesses() + private __byteLength(__allowWitness: boolean): number { + const hasWitnesses = __allowWitness && 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) + return sum + 40 + varSliceSize(input.script); }, 0) + this.outs.reduce((sum, output) => { - return sum + 8 + varSliceSize(output.script) + return sum + 8 + varSliceSize(output.script); }, 0) + - (hasWitnesses ? this.ins.reduce((sum, input) => { - return sum + vectorSize(input.witness) - }, 0) : 0) - ) + (hasWitnesses + ? this.ins.reduce((sum, input) => { + return sum + vectorSize(input.witness); + }, 0) + : 0) + ); } - clone (): Transaction { - const newTx = new Transaction() - newTx.version = this.version - newTx.locktime = this.locktime + clone(): Transaction { + const newTx = new Transaction(); + newTx.version = this.version; + newTx.locktime = this.locktime; - newTx.ins = this.ins.map((txIn) => { + newTx.ins = this.ins.map(txIn => { return { hash: txIn.hash, index: txIn.index, script: txIn.script, sequence: txIn.sequence, - witness: txIn.witness - } - }) + witness: txIn.witness, + }; + }); - newTx.outs = this.outs.map((txOut) => { + newTx.outs = this.outs.map(txOut => { return { script: txOut.script, - value: (txOut).value - } - }) + value: (txOut).value, + }; + }); - return newTx + return newTx; } /** @@ -287,284 +316,313 @@ export class Transaction { * hashType, and then hashes the result. * This hash can then be used to sign the provided transaction input. */ - hashForSignature (inIndex: number, prevOutScript: Buffer, hashType: number): Buffer { - typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments) + hashForSignature( + inIndex: number, + prevOutScript: Buffer, + hashType: number, + ): Buffer { + 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 + if (inIndex >= this.ins.length) return ONE; // ignore OP_CODESEPARATOR - const ourScript = bscript.compile(bscript.decompile(prevOutScript)!.filter((x) => { - return x !== opcodes.OP_CODESEPARATOR - })) + const ourScript = bscript.compile( + bscript.decompile(prevOutScript)!.filter(x => { + return x !== opcodes.OP_CODESEPARATOR; + }), + ); - const txTmp = this.clone() + const txTmp = this.clone(); // SIGHASH_NONE: ignore all outputs? (wildcard payee) if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { - txTmp.outs = [] + txTmp.outs = []; // ignore sequence numbers (except at inIndex) txTmp.ins.forEach((input, i) => { - if (i === inIndex) return + if (i === inIndex) return; - input.sequence = 0 - }) + 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 + if (inIndex >= this.outs.length) return ONE; // truncate outputs after - txTmp.outs.length = inIndex + 1 + txTmp.outs.length = inIndex + 1; // "blank" outputs before for (var i = 0; i < inIndex; i++) { - txTmp.outs[i] = BLANK_OUTPUT + txTmp.outs[i] = BLANK_OUTPUT; } // ignore sequence numbers (except at inIndex) txTmp.ins.forEach((input, y) => { - if (y === inIndex) return + if (y === inIndex) return; - input.sequence = 0 - }) + input.sequence = 0; + }); } // SIGHASH_ANYONECANPAY: ignore inputs entirely? if (hashType & Transaction.SIGHASH_ANYONECANPAY) { - txTmp.ins = [txTmp.ins[inIndex]] - txTmp.ins[0].script = ourScript + 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 + txTmp.ins.forEach(input => { + input.script = EMPTY_SCRIPT; + }); + txTmp.ins[inIndex].script = ourScript; } // serialize and hash - const buffer: Buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4) - buffer.writeInt32LE(hashType, buffer.length - 4) - txTmp.__toBuffer(buffer, 0, false) + const buffer: Buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4); + buffer.writeInt32LE(hashType, buffer.length - 4); + txTmp.__toBuffer(buffer, 0, false); - return bcrypto.hash256(buffer) + return bcrypto.hash256(buffer); } - hashForWitnessV0 (inIndex: number, prevOutScript: Buffer, value: number, hashType: number): Buffer { - typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments) + hashForWitnessV0( + inIndex: number, + prevOutScript: Buffer, + value: number, + hashType: number, + ): Buffer { + typeforce( + types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), + arguments, + ); - let tbuffer: Buffer = Buffer.from([]) - let toffset: number = 0 + let tbuffer: Buffer = Buffer.from([]); + let toffset: number = 0; - function writeSlice (slice: Buffer): void { - toffset += slice.copy(tbuffer, toffset) + function writeSlice(slice: Buffer): void { + toffset += slice.copy(tbuffer, toffset); } - function writeUInt32 (i: number): void { - toffset = tbuffer.writeUInt32LE(i, toffset) + function writeUInt32(i: number): void { + toffset = tbuffer.writeUInt32LE(i, toffset); } - function writeUInt64 (i: number): void { - toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset) + function writeUInt64(i: number): void { + toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset); } - function writeVarInt (i: number): void { - varuint.encode(i, tbuffer, toffset) - toffset += varuint.encode.bytes + function writeVarInt(i: number): void { + varuint.encode(i, tbuffer, toffset); + toffset += varuint.encode.bytes; } - function writeVarSlice (slice: Buffer): void { - writeVarInt(slice.length) - writeSlice(slice) + function writeVarSlice(slice: Buffer): void { + writeVarInt(slice.length); + writeSlice(slice); } - let hashOutputs = ZERO - let hashPrevouts = ZERO - let hashSequence = ZERO + let hashOutputs = ZERO; + let hashPrevouts = ZERO; + let hashSequence = ZERO; if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { - tbuffer = Buffer.allocUnsafe(36 * this.ins.length) - toffset = 0 + tbuffer = Buffer.allocUnsafe(36 * this.ins.length); + toffset = 0; - this.ins.forEach((txIn) => { - writeSlice(txIn.hash) - writeUInt32(txIn.index) - }) + this.ins.forEach(txIn => { + writeSlice(txIn.hash); + writeUInt32(txIn.index); + }); - hashPrevouts = bcrypto.hash256(tbuffer) + hashPrevouts = bcrypto.hash256(tbuffer); } - if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) && + if ( + !(hashType & Transaction.SIGHASH_ANYONECANPAY) && (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && - (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { - tbuffer = Buffer.allocUnsafe(4 * this.ins.length) - toffset = 0 + (hashType & 0x1f) !== Transaction.SIGHASH_NONE + ) { + tbuffer = Buffer.allocUnsafe(4 * this.ins.length); + toffset = 0; - this.ins.forEach((txIn) => { - writeUInt32(txIn.sequence) - }) + this.ins.forEach(txIn => { + writeUInt32(txIn.sequence); + }); - hashSequence = bcrypto.hash256(tbuffer) + hashSequence = bcrypto.hash256(tbuffer); } - if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && - (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { + 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) + 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?: boolean): Buffer { + getHash(forWitness?: boolean): Buffer { // 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)) + if (forWitness && this.isCoinbase()) return Buffer.alloc(32, 0); + return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness)); } - getId (): string { + getId(): string { // transaction hash's are displayed in reverse order - return reverseBuffer(this.getHash(false)).toString('hex') + return reverseBuffer(this.getHash(false)).toString('hex'); } - toBuffer (buffer?: Buffer, initialOffset?: number): Buffer { - return this.__toBuffer(buffer, initialOffset, true) + toBuffer(buffer?: Buffer, initialOffset?: number): Buffer { + return this.__toBuffer(buffer, initialOffset, true); } - private __toBuffer (buffer?: Buffer, initialOffset?: number, __allowWitness?: boolean): Buffer { - if (!buffer) buffer = Buffer.allocUnsafe(this.__byteLength(__allowWitness!)) + private __toBuffer( + buffer?: Buffer, + initialOffset?: number, + __allowWitness?: boolean, + ): Buffer { + if (!buffer) + buffer = Buffer.allocUnsafe(this.__byteLength(__allowWitness!)); - let offset = initialOffset || 0 + let offset = initialOffset || 0; - function writeSlice (slice: Buffer): void { - offset += slice.copy(buffer!, offset) + function writeSlice(slice: Buffer): void { + offset += slice.copy(buffer!, offset); } - function writeUInt8 (i: number) { - offset = (buffer!).writeUInt8(i, offset) + function writeUInt8(i: number) { + offset = buffer!.writeUInt8(i, offset); } - function writeUInt32 (i: number) { - offset = (buffer!).writeUInt32LE(i, offset) + function writeUInt32(i: number) { + offset = buffer!.writeUInt32LE(i, offset); } - function writeInt32 (i: number) { - offset = (buffer!).writeInt32LE(i, offset) + function writeInt32(i: number) { + offset = buffer!.writeInt32LE(i, offset); } - function writeUInt64 (i: number) { - offset = bufferutils.writeUInt64LE(buffer!, i, offset) + function writeUInt64(i: number) { + offset = bufferutils.writeUInt64LE(buffer!, i, offset); } - function writeVarInt (i: number) { - varuint.encode(i, buffer, offset) - offset += varuint.encode.bytes + function writeVarInt(i: number) { + varuint.encode(i, buffer, offset); + offset += varuint.encode.bytes; } - function writeVarSlice (slice: Buffer) { - writeVarInt(slice.length) - writeSlice(slice) + function writeVarSlice(slice: Buffer) { + writeVarInt(slice.length); + writeSlice(slice); } - function writeVector (vector: Array) { - writeVarInt(vector.length) - vector.forEach(writeVarSlice) + function writeVector(vector: Array) { + writeVarInt(vector.length); + vector.forEach(writeVarSlice); } - writeInt32(this.version) + writeInt32(this.version); - const hasWitnesses = __allowWitness && this.hasWitnesses() + const hasWitnesses = __allowWitness && this.hasWitnesses(); if (hasWitnesses) { - writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER) - writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG) + writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER); + writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG); } - writeVarInt(this.ins.length) + writeVarInt(this.ins.length); - this.ins.forEach((txIn) => { - writeSlice(txIn.hash) - writeUInt32(txIn.index) - writeVarSlice(txIn.script) - writeUInt32(txIn.sequence) - }) + this.ins.forEach(txIn => { + writeSlice(txIn.hash); + writeUInt32(txIn.index); + writeVarSlice(txIn.script); + writeUInt32(txIn.sequence); + }); - writeVarInt(this.outs.length) - this.outs.forEach((txOut) => { + writeVarInt(this.outs.length); + this.outs.forEach(txOut => { if (isOutput(txOut)) { - writeUInt64(txOut.value) + writeUInt64(txOut.value); } else { - writeSlice(txOut.valueBuffer) + writeSlice(txOut.valueBuffer); } - writeVarSlice(txOut.script) - }) + writeVarSlice(txOut.script); + }); if (hasWitnesses) { - this.ins.forEach((input) => { - writeVector(input.witness) - }) + this.ins.forEach(input => { + writeVector(input.witness); + }); } - writeUInt32(this.locktime) + writeUInt32(this.locktime); // avoid slicing unless necessary - if (initialOffset !== undefined) return buffer.slice(initialOffset, offset) - return buffer + if (initialOffset !== undefined) return buffer.slice(initialOffset, offset); + return buffer; } - toHex () { - return this.toBuffer(undefined, undefined).toString('hex') + toHex() { + return this.toBuffer(undefined, undefined).toString('hex'); } - setInputScript (index: number, scriptSig: Buffer) { - typeforce(types.tuple(types.Number, types.Buffer), arguments) + setInputScript(index: number, scriptSig: Buffer) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); - this.ins[index].script = scriptSig + this.ins[index].script = scriptSig; } - setWitness (index: number, witness: Array) { - typeforce(types.tuple(types.Number, [types.Buffer]), arguments) + setWitness(index: number, witness: Array) { + typeforce(types.tuple(types.Number, [types.Buffer]), arguments); - this.ins[index].witness = witness + this.ins[index].witness = witness; } } diff --git a/ts_src/transaction_builder.ts b/ts_src/transaction_builder.ts index 969462c..a3bd9e6 100644 --- a/ts_src/transaction_builder.ts +++ b/ts_src/transaction_builder.ts @@ -1,458 +1,528 @@ -import { Network } from './networks' -import * as networks from './networks' -import { reverseBuffer } from './bufferutils' -import { Transaction, Output } from './transaction' -import { ECPairInterface } from './ecpair' -import * as ECPair from './ecpair' -import * as types from './types' -import * as baddress from './address' -import * as bcrypto from './crypto' -import * as bscript from './script' -import { Payment } from './payments' -import * as payments from './payments' -import * as classify from './classify' -import { OPS as ops } from './script' -const typeforce = require('typeforce') - -const SCRIPT_TYPES = classify.types - -type TxbSignatures = Array | Array -type TxbPubkeys = Array -type TxbWitness = Array -type TxbScriptType = string -type TxbScript = Buffer +import { Network } from './networks'; +import * as networks from './networks'; +import { reverseBuffer } from './bufferutils'; +import { Transaction, Output } from './transaction'; +import { ECPairInterface } from './ecpair'; +import * as ECPair from './ecpair'; +import * as types from './types'; +import * as baddress from './address'; +import * as bcrypto from './crypto'; +import * as bscript from './script'; +import { Payment } from './payments'; +import * as payments from './payments'; +import * as classify from './classify'; +import { OPS as ops } from './script'; +const typeforce = require('typeforce'); + +const SCRIPT_TYPES = classify.types; + +type TxbSignatures = Array | Array; +type TxbPubkeys = Array; +type TxbWitness = Array; +type TxbScriptType = string; +type TxbScript = Buffer; interface TxbInput { - value?: number - hasWitness?: boolean - signScript?: TxbScript - signType?: TxbScriptType - prevOutScript?: TxbScript - redeemScript?: TxbScript - redeemScriptType?: TxbScriptType - prevOutType?: TxbScriptType - pubkeys?: TxbPubkeys - signatures?: TxbSignatures - witness?: TxbWitness - witnessScript?: TxbScript - witnessScriptType?: TxbScriptType - script?: TxbScript - sequence?: number - scriptSig?: TxbScript - maxSignatures?: number + value?: number; + hasWitness?: boolean; + signScript?: TxbScript; + signType?: TxbScriptType; + prevOutScript?: TxbScript; + redeemScript?: TxbScript; + redeemScriptType?: TxbScriptType; + prevOutType?: TxbScriptType; + pubkeys?: TxbPubkeys; + signatures?: TxbSignatures; + witness?: TxbWitness; + witnessScript?: TxbScript; + witnessScriptType?: TxbScriptType; + script?: TxbScript; + sequence?: number; + scriptSig?: TxbScript; + maxSignatures?: number; } interface TxbOutput { - type: string - pubkeys?: TxbPubkeys - signatures?: TxbSignatures - maxSignatures?: number + type: string; + pubkeys?: TxbPubkeys; + signatures?: TxbSignatures; + maxSignatures?: number; } function txIsString(tx: Buffer | string | Transaction): tx is string { - return typeof tx === 'string' || tx instanceof String + return typeof tx === 'string' || tx instanceof String; } function txIsTransaction(tx: Buffer | string | Transaction): tx is Transaction { - return tx instanceof Transaction + return tx instanceof Transaction; } export class TransactionBuilder { - network: Network - maximumFeeRate: number - private __prevTxSet: { [index: string]: boolean } - private __inputs: Array - private __tx: Transaction + network: Network; + maximumFeeRate: number; + private __prevTxSet: { [index: string]: boolean }; + private __inputs: Array; + private __tx: Transaction; - constructor (network?: Network, maximumFeeRate?: number) { - this.__prevTxSet = {} - this.network = network || networks.bitcoin + constructor(network?: Network, maximumFeeRate?: number) { + this.__prevTxSet = {}; + this.network = network || networks.bitcoin; // WARNING: This is __NOT__ to be relied on, its just another potential safety mechanism (safety in-depth) - this.maximumFeeRate = maximumFeeRate || 2500 + this.maximumFeeRate = maximumFeeRate || 2500; - this.__inputs = [] - this.__tx = new Transaction() - this.__tx.version = 2 + this.__inputs = []; + this.__tx = new Transaction(); + this.__tx.version = 2; } - static fromTransaction (transaction: Transaction, network?: Network): TransactionBuilder { - const txb = new TransactionBuilder(network) + static fromTransaction( + transaction: Transaction, + network?: Network, + ): TransactionBuilder { + const txb = new TransactionBuilder(network); // Copy transaction fields - txb.setVersion(transaction.version) - txb.setLockTime(transaction.locktime) + 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) - }) + 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 - }) - }) + witness: txIn.witness, + }); + }); // fix some things not possible through the public API txb.__inputs.forEach((input, i) => { - fixMultisigOrder(input, transaction, i) - }) + fixMultisigOrder(input, transaction, i); + }); - return txb + return txb; } - setLockTime (locktime: number): void { - typeforce(types.UInt32, locktime) + setLockTime(locktime: number): void { + typeforce(types.UInt32, locktime); // if any signatures exist, throw - if (this.__inputs.some(input => { - if (!input.signatures) return false + 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') + return input.signatures.some(s => s !== undefined); + }) + ) { + throw new Error('No, this would invalidate signatures'); } - this.__tx.locktime = locktime + this.__tx.locktime = locktime; } - setVersion (version: number): void { - typeforce(types.UInt32, version) + setVersion(version: number): void { + typeforce(types.UInt32, version); // XXX: this might eventually become more complex depending on what the versions represent - this.__tx.version = version + this.__tx.version = version; } - addInput (txHash: Buffer | string | Transaction, vout: number, sequence: number, prevOutScript: Buffer): number { + addInput( + txHash: Buffer | string | Transaction, + vout: number, + sequence: number, + prevOutScript: Buffer, + ): number { if (!this.__canModifyInputs()) { - throw new Error('No, this would invalidate signatures') + throw new Error('No, this would invalidate signatures'); } - let value: number | undefined = undefined + let value: number | undefined = undefined; // is it a hex string? if (txIsString(txHash)) { // transaction hashs's are displayed in reverse order, un-reverse it - txHash = reverseBuffer(Buffer.from(txHash, 'hex')) + txHash = reverseBuffer(Buffer.from(txHash, 'hex')); - // is it a Transaction object? + // is it a Transaction object? } else if (txIsTransaction(txHash)) { - const txOut = txHash.outs[vout] - prevOutScript = txOut.script - value = (txOut).value + const txOut = txHash.outs[vout]; + prevOutScript = txOut.script; + value = (txOut).value; - txHash = txHash.getHash(false) + txHash = txHash.getHash(false); } return this.__addInputUnsafe(txHash, vout, { sequence: sequence, prevOutScript: prevOutScript, - value: value - }) + value: value, + }); } - private __addInputUnsafe (txHash: Buffer, vout: number, options: TxbInput): number { + private __addInputUnsafe( + txHash: Buffer, + vout: number, + options: TxbInput, + ): number { if (Transaction.isCoinbaseHash(txHash)) { - throw new Error('coinbase inputs not supported') + throw new Error('coinbase inputs not supported'); } - const prevTxOut = txHash.toString('hex') + ':' + vout - if (this.__prevTxSet[prevTxOut] !== undefined) throw new Error('Duplicate TxOut: ' + prevTxOut) + const prevTxOut = txHash.toString('hex') + ':' + vout; + if (this.__prevTxSet[prevTxOut] !== undefined) + throw new Error('Duplicate TxOut: ' + prevTxOut); - let input = {} + let input = {}; // derive what we can from the scriptSig if (options.script !== undefined) { - input = expandInput(options.script, options.witness || []) + input = expandInput(options.script, options.witness || []); } // if an input value was given, retain it if (options.value !== undefined) { - input.value = options.value + input.value = options.value; } // derive what we can from the previous transactions output script if (!input.prevOutScript && options.prevOutScript) { - let prevOutType + let prevOutType; if (!input.pubkeys && !input.signatures) { - const expanded = expandOutput(options.prevOutScript) + const expanded = expandOutput(options.prevOutScript); if (expanded.pubkeys) { - input.pubkeys = expanded.pubkeys - input.signatures = expanded.signatures + input.pubkeys = expanded.pubkeys; + input.signatures = expanded.signatures; } - prevOutType = expanded.type + prevOutType = expanded.type; } - input.prevOutScript = options.prevOutScript - input.prevOutType = prevOutType || classify.output(options.prevOutScript) + 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.__prevTxSet[prevTxOut] = true - return vin + const vin = this.__tx.addInput( + txHash, + vout, + options.sequence, + options.scriptSig, + ); + this.__inputs[vin] = input; + this.__prevTxSet[prevTxOut] = true; + return vin; } - addOutput (scriptPubKey: string | Buffer, value: number): number { + addOutput(scriptPubKey: string | Buffer, value: number): number { if (!this.__canModifyOutputs()) { - throw new Error('No, this would invalidate signatures') + 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) + scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network); } - return this.__tx.addOutput(scriptPubKey, value) + return this.__tx.addOutput(scriptPubKey, value); } - build (): Transaction { - return this.__build(false) + build(): Transaction { + return this.__build(false); } - buildIncomplete (): Transaction { - return this.__build(true) + buildIncomplete(): Transaction { + return this.__build(true); } - private __build (allowIncomplete?: boolean): Transaction { + private __build(allowIncomplete?: boolean): Transaction { 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') + 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() + 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') + if (!input.prevOutType && !allowIncomplete) + throw new Error('Transaction is not complete'); - const result = build(input.prevOutType!, input, allowIncomplete) + 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 + 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!) - }) + 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') + throw new Error('Transaction has absurd fees'); } } - return tx + return tx; } - sign (vin: number, keyPair: ECPairInterface, redeemScript: Buffer, hashType: number, witnessValue: number, witnessScript: Buffer) { + sign( + vin: number, + keyPair: ECPairInterface, + redeemScript: Buffer, + hashType: number, + witnessValue: number, + witnessScript: Buffer, + ) { // 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) + 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.SIGHASH_ALL - if (this.__needsOutputs(hashType)) throw new Error('Transaction needs outputs') + hashType = hashType || Transaction.SIGHASH_ALL; + if (this.__needsOutputs(hashType)) + throw new Error('Transaction needs outputs'); - const input = this.__inputs[vin] + 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') + if ( + input.redeemScript !== undefined && + redeemScript && + !input.redeemScript.equals(redeemScript) + ) { + throw new Error('Inconsistent redeemScript'); } - const ourPubKey = keyPair.publicKey || keyPair.getPublicKey!() + const ourPubKey = keyPair.publicKey || keyPair.getPublicKey!(); if (!canSign(input)) { if (witnessValue !== undefined) { - if (input.value !== undefined && input.value !== witnessValue) throw new Error('Input didn\'t match witnessValue') - typeforce(types.Satoshi, witnessValue) - input.value = witnessValue + if (input.value !== undefined && input.value !== witnessValue) + throw new Error("Input didn't match witnessValue"); + typeforce(types.Satoshi, witnessValue); + input.value = witnessValue; } if (!canSign(input)) { - const prepared = prepareInput(input, ourPubKey, redeemScript, witnessScript) + const prepared = prepareInput( + input, + ourPubKey, + redeemScript, + witnessScript, + ); // updates inline - Object.assign(input, prepared) + Object.assign(input, prepared); } - if (!canSign(input)) throw Error(input.prevOutType + ' not supported') + if (!canSign(input)) throw Error(input.prevOutType + ' not supported'); } // ready to sign - let signatureHash: Buffer + let signatureHash: Buffer; if (input.hasWitness) { - signatureHash = this.__tx.hashForWitnessV0(vin, input.signScript, input.value, hashType) + signatureHash = this.__tx.hashForWitnessV0( + vin, + input.signScript, + input.value, + hashType, + ); } else { - signatureHash = this.__tx.hashForSignature(vin, input.signScript, hashType) + 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') + 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') + 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 - }) + 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') + if (!signed) throw new Error('Key pair cannot sign for this input'); } - private __canModifyInputs (): boolean { + private __canModifyInputs(): boolean { return this.__inputs.every(input => { - if (!input.signatures) return true + if (!input.signatures) return true; return input.signatures.every(signature => { - if (!signature) return true - const hashType = signatureHashType(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.SIGHASH_ANYONECANPAY) !== 0 - }) - }) + return (hashType & Transaction.SIGHASH_ANYONECANPAY) !== 0; + }); + }); } - private __needsOutputs (signingHashType: number): boolean { + private __needsOutputs(signingHashType: number): boolean { if (signingHashType === Transaction.SIGHASH_ALL) { - return this.__tx.outs.length === 0 + 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.SIGHASH_NONE) return false // SIGHASH_NONE doesn't care about outputs - return true // SIGHASH_* does care + 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.SIGHASH_NONE) return false; // SIGHASH_NONE doesn't care about outputs + return true; // SIGHASH_* does care + }); }) - }) + ); } - private __canModifyOutputs (): boolean { - const nInputs = this.__tx.ins.length - const nOutputs = this.__tx.outs.length + private __canModifyOutputs(): boolean { + const nInputs = this.__tx.ins.length; + const nOutputs = this.__tx.outs.length; return this.__inputs.every(input => { - if (input.signatures === undefined) return true + if (input.signatures === undefined) return true; return input.signatures.every(signature => { - if (!signature) return true - const hashType = signatureHashType(signature) + if (!signature) return true; + const hashType = signatureHashType(signature); - const hashTypeMod = hashType & 0x1f - if (hashTypeMod === Transaction.SIGHASH_NONE) return true + const hashTypeMod = hashType & 0x1f; + if (hashTypeMod === Transaction.SIGHASH_NONE) return true; if (hashTypeMod === 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 nInputs <= nOutputs; } - return false - }) - }) + return false; + }); + }); } - private __overMaximumFees (bytes: number): boolean { + private __overMaximumFees(bytes: number): boolean { // not all inputs will have .value defined - const incoming = this.__inputs.reduce((a, x) => a + (x.value! >>> 0), 0) + 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 + 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 feeRate > this.maximumFeeRate; } } -function expandInput (scriptSig: Buffer, witnessStack: Array, type?: string, scriptPubKey?: Buffer): TxbInput { - if (scriptSig.length === 0 && witnessStack.length === 0) return {} +function expandInput( + scriptSig: Buffer, + witnessStack: Array, + type?: string, + scriptPubKey?: Buffer, +): TxbInput { + if (scriptSig.length === 0 && witnessStack.length === 0) return {}; if (!type) { - let ssType: string | undefined = classify.input(scriptSig, true) - let wsType: string | undefined = classify.witness(witnessStack, true) - if (ssType === SCRIPT_TYPES.NONSTANDARD) ssType = undefined - if (wsType === SCRIPT_TYPES.NONSTANDARD) wsType = undefined - type = ssType || wsType + let ssType: string | undefined = classify.input(scriptSig, true); + let wsType: string | undefined = 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 }) + const { output, pubkey, signature } = payments.p2wpkh({ + witness: witnessStack, + }); return { prevOutScript: output, prevOutType: SCRIPT_TYPES.P2WPKH, pubkeys: [pubkey], - signatures: [signature] - } + signatures: [signature], + }; } case SCRIPT_TYPES.P2PKH: { - const { output, pubkey, signature } = payments.p2pkh({ input: scriptSig }) + const { output, pubkey, signature } = payments.p2pkh({ + input: scriptSig, + }); return { prevOutScript: output, prevOutType: SCRIPT_TYPES.P2PKH, pubkeys: [pubkey], - signatures: [signature] - } + signatures: [signature], + }; } case SCRIPT_TYPES.P2PK: { - const { signature } = payments.p2pk({ input: scriptSig }) + const { signature } = payments.p2pk({ input: scriptSig }); return { prevOutType: SCRIPT_TYPES.P2PK, pubkeys: [undefined], - signatures: [signature] - } + signatures: [signature], + }; } case SCRIPT_TYPES.P2MS: { - const { m, pubkeys, signatures } = payments.p2ms({ - input: scriptSig, - output: scriptPubKey - }, { allowIncomplete: true }) + const { m, pubkeys, signatures } = payments.p2ms( + { + input: scriptSig, + output: scriptPubKey, + }, + { allowIncomplete: true }, + ); return { prevOutType: SCRIPT_TYPES.P2MS, pubkeys: pubkeys, signatures: signatures, - maxSignatures: m - } + 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 {} + 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, @@ -463,23 +533,28 @@ function expandInput (scriptSig: Buffer, witnessStack: Array, type?: str witnessScriptType: expanded.witnessScriptType, pubkeys: expanded.pubkeys, - signatures: expanded.signatures - } + 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 + witness: witnessStack, + }); + const outputType = classify.output(redeem!.output!); + let expanded; if (outputType === SCRIPT_TYPES.P2WPKH) { - expanded = expandInput(redeem!.input!, redeem!.witness!, outputType) + expanded = expandInput(redeem!.input!, redeem!.witness!, outputType); } else { - expanded = expandInput(bscript.compile(redeem!.witness!), [], outputType, redeem!.output) + expanded = expandInput( + bscript.compile(redeem!.witness!), + [], + outputType, + redeem!.output, + ); } - if (!expanded.prevOutType) return {} + if (!expanded.prevOutType) return {}; return { prevOutScript: output, @@ -488,127 +563,152 @@ function expandInput (scriptSig: Buffer, witnessStack: Array, type?: str witnessScriptType: expanded.prevOutType, pubkeys: expanded.pubkeys, - signatures: expanded.signatures - } + signatures: expanded.signatures, + }; } return { prevOutType: SCRIPT_TYPES.NONSTANDARD, - prevOutScript: scriptSig - } + prevOutScript: scriptSig, + }; } // could be done in expandInput, but requires the original Transaction for hashForSignature -function fixMultisigOrder (input: TxbInput, transaction: Transaction, vin: number): void { - if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) return - if (input.pubkeys!.length === input.signatures!.length) return +function fixMultisigOrder( + input: TxbInput, + transaction: Transaction, + vin: number, +): void { + if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) + return; + if (input.pubkeys!.length === input.signatures!.length) return; - const unmatched = input.signatures!.concat() + const unmatched = input.signatures!.concat(); input.signatures = input.pubkeys!.map(pubKey => { - const keyPair = ECPair.fromPublicKey(pubKey!) - let match: Buffer | undefined + const keyPair = ECPair.fromPublicKey(pubKey!); + let match: Buffer | undefined; // check for a signature unmatched.some((signature, i) => { // skip if undefined || OP_0 - if (!signature) return false + if (!signature) return false; // TODO: avoid O(n) hashForSignature - const parsed = bscript.signature.decode(signature) - const hash = transaction.hashForSignature(vin, input.redeemScript!, parsed.hashType) + 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 + if (!keyPair.verify(hash, parsed.signature)) return false; // remove matched signature from unmatched - unmatched[i] = undefined - match = signature + unmatched[i] = undefined; + match = signature; - return true - }) + return true; + }); - return match - }) + return match; + }); } -function expandOutput (script: Buffer, ourPubKey?: Buffer): TxbOutput { - typeforce(types.Buffer, script) - const type = classify.output(script) +function expandOutput(script: Buffer, ourPubKey?: Buffer): TxbOutput { + typeforce(types.Buffer, script); + const type = classify.output(script); switch (type) { case SCRIPT_TYPES.P2PKH: { - if (!ourPubKey) return { type } + 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 } + const pkh1 = payments.p2pkh({ output: script }).hash; + const pkh2 = bcrypto.hash160(ourPubKey); + if (!pkh1!.equals(pkh2)) return { type }; return { type, pubkeys: [ourPubKey], - signatures: [undefined] - } + signatures: [undefined], + }; } case SCRIPT_TYPES.P2WPKH: { - if (!ourPubKey) return { type } + 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 } + const wpkh1 = payments.p2wpkh({ output: script }).hash; + const wpkh2 = bcrypto.hash160(ourPubKey); + if (!wpkh1!.equals(wpkh2)) return { type }; return { type, pubkeys: [ourPubKey], - signatures: [undefined] - } + signatures: [undefined], + }; } case SCRIPT_TYPES.P2PK: { - const p2pk = payments.p2pk({ output: script }) + const p2pk = payments.p2pk({ output: script }); return { type, pubkeys: [p2pk.pubkey], - signatures: [undefined] - } + signatures: [undefined], + }; } case SCRIPT_TYPES.P2MS: { - const p2ms = payments.p2ms({ output: script }) + const p2ms = payments.p2ms({ output: script }); return { type, pubkeys: p2ms.pubkeys, signatures: p2ms.pubkeys!.map((): undefined => undefined), - maxSignatures: p2ms.m - } + maxSignatures: p2ms.m, + }; } } - return { type } + return { type }; } -function prepareInput (input: TxbInput, ourPubKey: Buffer, redeemScript: Buffer, witnessScript: Buffer): TxbInput { +function prepareInput( + input: TxbInput, + ourPubKey: Buffer, + redeemScript: Buffer, + witnessScript: Buffer, +): TxbInput { 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 }) + 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 (!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 + expanded.signatures = input.signatures; } - let signScript = witnessScript - if (expanded.type === SCRIPT_TYPES.P2WPKH) throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure') + let signScript = witnessScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) + throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure'); return { redeemScript, @@ -626,30 +726,39 @@ function prepareInput (input: TxbInput, ourPubKey: Buffer, redeemScript: Buffer, pubkeys: expanded.pubkeys, signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures - } + maxSignatures: expanded.maxSignatures, + }; } if (redeemScript) { - const p2sh = payments.p2sh({ redeem: { output: redeemScript } }) + const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); if (input.prevOutScript) { - let p2shAlt + 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) + ')') + 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 + expanded.signatures = input.signatures; } - let signScript = redeemScript + let signScript = redeemScript; if (expanded.type === SCRIPT_TYPES.P2WPKH) { - signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output! + signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output!; } return { @@ -665,26 +774,34 @@ function prepareInput (input: TxbInput, ourPubKey: Buffer, redeemScript: Buffer, pubkeys: expanded.pubkeys, signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures - } + maxSignatures: expanded.maxSignatures, + }; } if (witnessScript) { - const p2wsh = payments.p2wsh({ redeem: { output: 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) + ')') + 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 + expanded.signatures = input.signatures; } - let signScript = witnessScript - if (expanded.type === SCRIPT_TYPES.P2WPKH) throw new Error('P2WSH(P2WPKH) is a consensus failure') + let signScript = witnessScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) + throw new Error('P2WSH(P2WPKH) is a consensus failure'); return { witnessScript, @@ -699,25 +816,39 @@ function prepareInput (input: TxbInput, ourPubKey: Buffer, redeemScript: Buffer, pubkeys: expanded.pubkeys, signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures - } + 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.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 + expanded.signatures = input.signatures; } - let signScript = input.prevOutScript + let signScript = input.prevOutScript; if (expanded.type === SCRIPT_TYPES.P2WPKH) { - signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output + signScript = ( + payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output + ); } return { @@ -730,11 +861,11 @@ function prepareInput (input: TxbInput, ourPubKey: Buffer, redeemScript: Buffer, pubkeys: expanded.pubkeys, signatures: expanded.signatures, - maxSignatures: expanded.maxSignatures - } + maxSignatures: expanded.maxSignatures, + }; } - const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output + const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output; return { prevOutType: SCRIPT_TYPES.P2PKH, prevOutScript: prevOutScript, @@ -744,86 +875,92 @@ function prepareInput (input: TxbInput, ourPubKey: Buffer, redeemScript: Buffer, signType: SCRIPT_TYPES.P2PKH, pubkeys: [ourPubKey], - signatures: [undefined] - } + signatures: [undefined], + }; } -function build (type: string, input: TxbInput, allowIncomplete?: boolean): Payment | undefined { - const pubkeys = >(input.pubkeys || []) - let signatures = >(input.signatures || []) +function build( + type: string, + input: TxbInput, + allowIncomplete?: boolean, +): Payment | undefined { + const pubkeys = >(input.pubkeys || []); + let signatures = >(input.signatures || []); switch (type) { case SCRIPT_TYPES.P2PKH: { - if (pubkeys.length === 0) break - if (signatures.length === 0) break + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; - return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }) + return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }); } case SCRIPT_TYPES.P2WPKH: { - if (pubkeys.length === 0) break - if (signatures.length === 0) break + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; - return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }) + return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }); } case SCRIPT_TYPES.P2PK: { - if (pubkeys.length === 0) break - if (signatures.length === 0) break + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; - return payments.p2pk({ signature: signatures[0] }) + return payments.p2pk({ signature: signatures[0] }); } case SCRIPT_TYPES.P2MS: { - const m = input.maxSignatures + const m = input.maxSignatures; if (allowIncomplete) { - signatures = signatures.map(x => x || ops.OP_0) + signatures = signatures.map(x => x || ops.OP_0); } else { - signatures = signatures.filter(x => x) + 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 }) + 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 + 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 - } - }) + witness: redeem.witness, + }, + }); } case SCRIPT_TYPES.P2WSH: { - const redeem = build(input.witnessScriptType!, input, allowIncomplete) - if (!redeem) return + const redeem = build(input.witnessScriptType!, input, allowIncomplete); + if (!redeem) return; return payments.p2wsh({ redeem: { output: input.witnessScript, input: redeem.input, - witness: redeem.witness - } - }) + witness: redeem.witness, + }, + }); } } } -function canSign (input: TxbInput): boolean { - return input.signScript !== undefined && +function canSign(input: TxbInput): boolean { + 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 - ) + (input.hasWitness === false || input.value !== undefined) + ); } -function signatureHashType (buffer: Buffer): number { - return buffer.readUInt8(buffer.length - 1) +function signatureHashType(buffer: Buffer): number { + return buffer.readUInt8(buffer.length - 1); } diff --git a/ts_src/types.ts b/ts_src/types.ts index 8eacbdc..42ddb40 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -1,49 +1,51 @@ -const typeforce = require('typeforce') +const typeforce = require('typeforce'); -const UINT31_MAX: number = Math.pow(2, 31) - 1 -export function UInt31 (value: number): boolean { - return typeforce.UInt32(value) && value <= UINT31_MAX +const UINT31_MAX: number = Math.pow(2, 31) - 1; +export function UInt31(value: number): boolean { + return typeforce.UInt32(value) && value <= UINT31_MAX; } -export function BIP32Path (value: string): boolean { - return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/) +export function BIP32Path(value: string): boolean { + return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); } -BIP32Path.toJSON = function () { return 'BIP32 derivation path' } +BIP32Path.toJSON = function() { + return 'BIP32 derivation path'; +}; -const SATOSHI_MAX: number = 21 * 1e14 -export function Satoshi (value: number): boolean { - return typeforce.UInt53(value) && value <= SATOSHI_MAX +const SATOSHI_MAX: number = 21 * 1e14; +export function Satoshi(value: number): boolean { + return typeforce.UInt53(value) && value <= SATOSHI_MAX; } // external dependent types -export const ECPoint = typeforce.quacksLike('Point') +export const ECPoint = typeforce.quacksLike('Point'); // exposed, external API export const Network = typeforce.compile({ messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), bip32: { public: typeforce.UInt32, - private: typeforce.UInt32 + private: typeforce.UInt32, }, pubKeyHash: typeforce.UInt8, scriptHash: typeforce.UInt8, - wif: typeforce.UInt8 -}) + wif: typeforce.UInt8, +}); -export const Buffer256bit = typeforce.BufferN(32) -export const Hash160bit = typeforce.BufferN(20) -export const Hash256bit = typeforce.BufferN(32) -export const Number = typeforce.Number -export const Array = typeforce.Array -export const Boolean = typeforce.Boolean -export const String = typeforce.String -export const Buffer = typeforce.Buffer -export const Hex = typeforce.Hex -export const maybe = typeforce.maybe -export const tuple = typeforce.tuple -export const UInt8 = typeforce.UInt8 -export const UInt32 = typeforce.UInt32 -export const Function = typeforce.Function -export const BufferN = typeforce.BufferN -export const Null = typeforce.Null -export const oneOf = typeforce.oneOf +export const Buffer256bit = typeforce.BufferN(32); +export const Hash160bit = typeforce.BufferN(20); +export const Hash256bit = typeforce.BufferN(32); +export const Number = typeforce.Number; +export const Array = typeforce.Array; +export const Boolean = typeforce.Boolean; +export const String = typeforce.String; +export const Buffer = typeforce.Buffer; +export const Hex = typeforce.Hex; +export const maybe = typeforce.maybe; +export const tuple = typeforce.tuple; +export const UInt8 = typeforce.UInt8; +export const UInt32 = typeforce.UInt32; +export const Function = typeforce.Function; +export const BufferN = typeforce.BufferN; +export const Null = typeforce.Null; +export const oneOf = typeforce.oneOf; diff --git a/types/index.d.ts b/types/index.d.ts index 9682271..3512872 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -5,7 +5,7 @@ import * as crypto from './crypto'; import * as networks from './networks'; import * as payments from './payments'; import * as script from './script'; -export { ECPair, address, bip32, crypto, networks, payments, script, }; +export { ECPair, address, bip32, crypto, networks, payments, script }; export { Block } from './block'; export { Transaction } from './transaction'; export { TransactionBuilder } from './transaction_builder';