From 7756c5dd762ac8487d24f55ceb57dbc9cbf55771 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Mon, 25 Jun 2018 16:15:45 +1000 Subject: [PATCH 1/4] bump tiny-secp256k1 --- package.json | 4 ++-- test/integration/crypto.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7f1ef7b..d6947a2 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "pushdata-bitcoin": "^1.0.1", "randombytes": "^2.0.1", "safe-buffer": "^5.1.1", - "tiny-secp256k1": "^0.1.0", + "tiny-secp256k1": "^0.2.2", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.0.4", "wif": "^2.0.1" @@ -50,7 +50,7 @@ "bip39": "^2.3.0", "bip65": "^1.0.1", "bip68": "^1.0.3", - "bn.js": "4.11.8", + "bn.js": "^4.11.8", "bs58": "^4.0.0", "dhttp": "^2.5.0", "hoodwink": "^1.0.0", diff --git a/test/integration/crypto.js b/test/integration/crypto.js index cd2af49..78a6ab0 100644 --- a/test/integration/crypto.js +++ b/test/integration/crypto.js @@ -1,6 +1,5 @@ /* global describe, it */ - const assert = require('assert') const BN = require('bn.js') const bitcoin = require('../../') From f9a739e1db2ba5ee4dde11f90a514aabcdaddf1b Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Tue, 5 Jun 2018 17:24:47 +1000 Subject: [PATCH 2/4] add payments p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh --- src/payments/index.js | 19 ++ src/payments/lazy.js | 30 +++ src/payments/p2ms.js | 140 ++++++++++++++ src/payments/p2pk.js | 80 ++++++++ src/payments/p2pkh.js | 127 +++++++++++++ src/payments/p2sh.js | 176 ++++++++++++++++++ src/payments/p2wpkh.js | 124 +++++++++++++ src/payments/p2wsh.js | 154 ++++++++++++++++ src/payments/package.json | 40 ++++ src/script.js | 4 +- test/fixtures/p2ms.json | 378 ++++++++++++++++++++++++++++++++++++++ test/fixtures/p2pk.json | 152 +++++++++++++++ test/fixtures/p2pkh.json | 214 +++++++++++++++++++++ test/fixtures/p2sh.json | 346 ++++++++++++++++++++++++++++++++++ test/fixtures/p2wpkh.json | 195 ++++++++++++++++++++ test/fixtures/p2wsh.json | 326 ++++++++++++++++++++++++++++++++ test/payments.js | 65 +++++++ test/payments.utils.js | 128 +++++++++++++ 18 files changed, 2696 insertions(+), 2 deletions(-) create mode 100644 src/payments/index.js create mode 100644 src/payments/lazy.js create mode 100644 src/payments/p2ms.js create mode 100644 src/payments/p2pk.js create mode 100644 src/payments/p2pkh.js create mode 100644 src/payments/p2sh.js create mode 100644 src/payments/p2wpkh.js create mode 100644 src/payments/p2wsh.js create mode 100644 src/payments/package.json create mode 100644 test/fixtures/p2ms.json create mode 100644 test/fixtures/p2pk.json create mode 100644 test/fixtures/p2pkh.json create mode 100644 test/fixtures/p2sh.json create mode 100644 test/fixtures/p2wpkh.json create mode 100644 test/fixtures/p2wsh.json create mode 100644 test/payments.js create mode 100644 test/payments.utils.js diff --git a/src/payments/index.js b/src/payments/index.js new file mode 100644 index 0000000..9e869f5 --- /dev/null +++ b/src/payments/index.js @@ -0,0 +1,19 @@ +const p2ms = require('./p2ms') +const p2pk = require('./p2pk') +const p2pkh = require('./p2pkh') +const p2sh = require('./p2sh') +const p2wpkh = require('./p2wpkh') +const p2wsh = require('./p2wsh') + +module.exports = { + p2ms: p2ms, + p2pk: p2pk, + p2pkh: p2pkh, + p2sh: p2sh, + p2wpkh: p2wpkh, + p2wsh: p2wsh +} + +// TODO +// OP_RETURN +// witness commitment diff --git a/src/payments/lazy.js b/src/payments/lazy.js new file mode 100644 index 0000000..8538752 --- /dev/null +++ b/src/payments/lazy.js @@ -0,0 +1,30 @@ +function prop (object, name, f) { + Object.defineProperty(object, name, { + configurable: true, + enumerable: true, + get: function () { + let value = f.call(this) + this[name] = value + return value + }, + set: function (value) { + Object.defineProperty(this, name, { + configurable: true, + enumerable: true, + value: value, + writable: true + }) + } + }) +} + +function value (f) { + let value + return function () { + if (value !== undefined) return value + value = f() + return value + } +} + +module.exports = { prop, value } diff --git a/src/payments/p2ms.js b/src/payments/p2ms.js new file mode 100644 index 0000000..9bce722 --- /dev/null +++ b/src/payments/p2ms.js @@ -0,0 +1,140 @@ +let lazy = require('./lazy') +let typef = require('typeforce') +let OPS = require('bitcoin-ops') +let ecc = require('tiny-secp256k1') + +let bscript = require('../script') +let BITCOIN_NETWORK = require('../networks').bitcoin +let OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1 + +function stacksEqual (a, b) { + 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 +function p2ms (a, opts) { + if ( + !a.output && + !(a.pubkeys && a.m !== undefined) + ) throw new TypeError('Not enough data') + opts = opts || { validate: true } + + function isAcceptableSignature (x) { + return bscript.isCanonicalScriptSignature(x) || (opts.allowIncomplete && (x === OPS.OP_0)) + } + + 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) + + let network = a.network || BITCOIN_NETWORK + let o = { network } + + let chunks + let decoded = false + function decode (output) { + if (decoded) return + decoded = true + chunks = bscript.decompile(output) + let om = chunks[0] - OP_INT_BASE + let on = chunks[chunks.length - 2] - OP_INT_BASE + o.m = om + o.n = on + 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 [] + }) + + // extended validation + if (opts.validate) { + if (a.output) { + decode(a.output) + if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid') + if (!typef.Number(chunks[chunks.length - 2])) throw new TypeError('Output is invalid') + if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) throw new TypeError('Output is invalid') + + if ( + o.m <= 0 || + o.n > 16 || + o.m > o.n || + o.n !== chunks.length - 3) throw new TypeError('Output is invalid') + if (!o.pubkeys.every(x => ecc.isPoint(x))) throw new TypeError('Output is invalid') + + if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch') + if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch') + if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) throw new TypeError('Pubkeys mismatch') + } + + if (a.pubkeys) { + if (a.n !== undefined && a.n !== a.pubkeys.length) throw new TypeError('Pubkey count mismatch') + o.n = a.pubkeys.length + + if (o.n < o.m) throw new TypeError('Pubkey count cannot be less than m') + } + + if (a.signatures) { + if (a.signatures.length < o.m) throw new TypeError('Not enough signatures provided') + if (a.signatures.length > o.m) throw new TypeError('Too many signatures provided') + } + + if (a.input) { + if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid') + if (o.signatures.length === 0 || !o.signatures.every(isAcceptableSignature)) throw new TypeError('Input has invalid signature(s)') + + if (a.signatures && !stacksEqual(a.signatures.equals(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) +} + +module.exports = p2ms diff --git a/src/payments/p2pk.js b/src/payments/p2pk.js new file mode 100644 index 0000000..b0408aa --- /dev/null +++ b/src/payments/p2pk.js @@ -0,0 +1,80 @@ +let lazy = require('./lazy') +let typef = require('typeforce') +let OPS = require('bitcoin-ops') +let ecc = require('tiny-secp256k1') + +let bscript = require('../script') +let BITCOIN_NETWORK = require('../networks').bitcoin + +// input: {signature} +// output: {pubKey} OP_CHECKSIG +function p2pk (a, opts) { + if ( + !a.output && + !a.pubkey + ) throw new TypeError('Not enough data') + opts = opts || { validate: true } + + 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) + + let _chunks = lazy.value(function () { return bscript.decompile(a.input) }) + + let network = a.network || BITCOIN_NETWORK + let o = { 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 [] + }) + + // extended validation + if (opts.validate) { + if (a.pubkey && a.output) { + if (!a.pubkey.equals(o.pubkey)) throw new TypeError('Pubkey mismatch') + } + + 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.signature) { + if (a.input && !a.input.equals(o.input)) throw new TypeError('Input mismatch') + } + + if (a.input) { + if (_chunks().length !== 1) throw new TypeError('Input is invalid') + if (!bscript.isCanonicalScriptSignature(_chunks()[0])) throw new TypeError('Input has invalid signature') + } + } + + return Object.assign(o, a) +} + +module.exports = p2pk diff --git a/src/payments/p2pkh.js b/src/payments/p2pkh.js new file mode 100644 index 0000000..9d0733d --- /dev/null +++ b/src/payments/p2pkh.js @@ -0,0 +1,127 @@ +let lazy = require('./lazy') +let typef = require('typeforce') +let OPS = require('bitcoin-ops') +let ecc = require('tiny-secp256k1') + +let baddress = require('../address') +let bcrypto = require('../crypto') +let bscript = require('../script') +let BITCOIN_NETWORK = require('../networks').bitcoin + +// input: {signature} {pubkey} +// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG +function p2pkh (a, opts) { + if ( + !a.address && + !a.hash && + !a.output && + !a.pubkey && + !a.input + ) throw new TypeError('Not enough data') + opts = opts || { validate: true } + + 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) + + let _address = lazy.value(function () { return baddress.fromBase58Check(a.address) }) + let _chunks = lazy.value(function () { return bscript.decompile(a.input) }) + + let network = a.network || BITCOIN_NETWORK + let o = { network } + + lazy.prop(o, 'address', function () { + if (!o.hash) return + return baddress.toBase58Check(o.hash, network.pubKeyHash) + }) + 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 [] + }) + + // extended validation + if (opts.validate) { + let hash + if (a.address) { + if (_address().version !== network.pubKeyHash) throw new TypeError('Network mismatch') + if (_address().hash.length !== 20) throw new TypeError('Invalid address') + else hash = _address().hash + } + + if (a.hash) { + if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') + else hash = a.hash + } + + if (a.output) { + if ( + a.output.length !== 25 || + a.output[0] !== OPS.OP_DUP || + a.output[1] !== OPS.OP_HASH160 || + a.output[2] !== 0x14 || + a.output[23] !== OPS.OP_EQUALVERIFY || + a.output[24] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid') + + if (hash && !hash.equals(a.output.slice(3, 23))) throw new TypeError('Hash mismatch') + else hash = a.output.slice(3, 23) + } + + if (a.pubkey) { + let pkh = bcrypto.hash160(a.pubkey) + if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch') + else hash = pkh + } + + if (a.input) { + let 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') + + let pkh = bcrypto.hash160(chunks[1]) + if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch') + } + } + + return Object.assign(o, a) +} + +module.exports = p2pkh diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js new file mode 100644 index 0000000..c67d921 --- /dev/null +++ b/src/payments/p2sh.js @@ -0,0 +1,176 @@ +const lazy = require('./lazy') +const typef = require('typeforce') +const OPS = require('bitcoin-ops') + +const baddress = require('../address') +const bcrypto = require('../crypto') +const bscript = require('../script') +const BITCOIN_NETWORK = require('../networks').bitcoin + +function stacksEqual (a, b) { + 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 +function p2sh (a, opts) { + if ( + !a.address && + !a.hash && + !a.output && + !a.redeem && + !a.input + ) throw new TypeError('Not enough data') + opts = opts || { validate: true } + + typef({ + network: typef.maybe(typef.Object), + + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + output: typef.maybe(typef.BufferN(23)), + + redeem: typef.maybe({ + network: typef.maybe(typef.Object), + output: 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) + + const network = a.network || BITCOIN_NETWORK + const o = { network } + + const _address = lazy.value(function () { return baddress.fromBase58Check(a.address) }) + const _chunks = lazy.value(function () { return bscript.decompile(a.input) }) + const _redeem = lazy.value(function () { + const chunks = _chunks() + return { + network: network, + output: chunks[chunks.length - 1], + input: bscript.compile(chunks.slice(0, -1)), + witness: a.witness || [] + } + }) + + // output dependents + lazy.prop(o, 'address', function () { + if (!o.hash) return + return baddress.toBase58Check(o.hash, network.scriptHash) + }) + 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 + ]) + }) + + // input dependents + lazy.prop(o, 'redeem', function () { + if (!a.input) return + return _redeem() + }) + lazy.prop(o, 'input', function () { + if (!a.redeem || !a.redeem.input) 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 + if (a.address) { + if (_address().version !== network.scriptHash) throw new TypeError('Network mismatch') + if (_address().hash.length !== 20) throw new TypeError('Invalid address') + else hash = _address().hash + } + + if (a.hash) { + if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') + else hash = a.hash + } + + if (a.output) { + if ( + a.output.length !== 23 || + a.output[0] !== OPS.OP_HASH160 || + a.output[1] !== 0x14 || + a.output[22] !== OPS.OP_EQUAL) throw new TypeError('Output is invalid') + const hash2 = a.output.slice(2, 22) + if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch') + else hash = hash2 + } + + // inlined to prevent 'no-inner-declarations' failing + const checkRedeem = function (redeem) { + // is the redeem output empty/invalid? + 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 && !hash.equals(hash2)) throw new TypeError('Hash mismatch') + else hash = hash2 + + if (redeem.input) { + const hasInput = redeem.input.length > 0 + const hasWitness = redeem.witness && redeem.witness.length > 0 + if (!hasInput && !hasWitness) throw new TypeError('Empty input') + if (hasInput && hasWitness) throw new TypeError('Input and witness provided') + if (hasInput) { + const richunks = bscript.decompile(redeem.input) + if (!bscript.isPushOnly(richunks)) throw new TypeError('Non push-only scriptSig') + } + } + } + + if (a.input) { + const chunks = _chunks() + if (!chunks || chunks.length < 1) throw new TypeError('Input too short') + if (!Buffer.isBuffer(_redeem().output)) throw new TypeError('Input is invalid') + + checkRedeem(_redeem()) + } + + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch') + if (o.redeem) { + if (a.redeem.output && !a.redeem.output.equals(o.redeem.output)) throw new TypeError('Redeem.output mismatch') + if (a.redeem.input && !a.redeem.input.equals(o.redeem.input)) throw new TypeError('Redeem.input mismatch') + } + + checkRedeem(a.redeem) + } + + if (a.witness) { + if ( + a.redeem && + a.redeem.witness && + !stacksEqual(a.redeem.witness, a.witness)) throw new TypeError('Witness and redeem.witness mismatch') + } + } + + return Object.assign(o, a) +} + +module.exports = p2sh diff --git a/src/payments/p2wpkh.js b/src/payments/p2wpkh.js new file mode 100644 index 0000000..f2bdeb4 --- /dev/null +++ b/src/payments/p2wpkh.js @@ -0,0 +1,124 @@ +let lazy = require('./lazy') +let typef = require('typeforce') +let OPS = require('bitcoin-ops') +let ecc = require('tiny-secp256k1') + +let baddress = require('../address') +let bcrypto = require('../crypto') +let bscript = require('../script') +let BITCOIN_NETWORK = require('../networks').bitcoin + +let EMPTY_BUFFER = Buffer.alloc(0) + +// witness: {signature} {pubKey} +// input: <> +// output: OP_0 {pubKeyHash} +function p2wpkh (a, opts) { + if ( + !a.address && + !a.hash && + !a.output && + !a.pubkey && + !a.witness + ) throw new TypeError('Not enough data') + opts = opts || { validate: true } + + 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) + + let _address = lazy.value(function () { return baddress.fromBech32(a.address) }) + + let network = a.network || BITCOIN_NETWORK + let o = { network } + + lazy.prop(o, 'address', function () { + if (!o.hash) return + return baddress.toBech32(o.hash, 0x00, network.bech32) + }) + 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 + if (a.address) { + if (network && network.bech32 !== _address().prefix) throw new TypeError('Network mismatch') + if (_address().version !== 0x00) throw new TypeError('Invalid version') + if (_address().data.length !== 20) throw new TypeError('Invalid data') + if (hash && !hash.equals(_address().data)) throw new TypeError('Hash mismatch') + else hash = _address().data + } + + if (a.pubkey) { + let pkh = bcrypto.hash160(a.pubkey) + if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch') + else hash = pkh + } + + if (a.hash) { + if (hash && !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 && !hash.equals(a.output.slice(2))) throw new TypeError('Hash mismatch') + else hash = a.output.slice(2) + } + + if (a.witness) { + if (a.witness.length !== 2) throw new TypeError('Input is invalid') + if (!bscript.isCanonicalScriptSignature(a.witness[0])) throw new TypeError('Input has invalid signature') + if (!ecc.isPoint(a.witness[1])) throw new TypeError('Input 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') + + let pkh = bcrypto.hash160(a.witness[1]) + if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch') + } + } + + return Object.assign(o, a) +} + +module.exports = p2wpkh diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js new file mode 100644 index 0000000..8e2e4fe --- /dev/null +++ b/src/payments/p2wsh.js @@ -0,0 +1,154 @@ +let lazy = require('./lazy') +let typef = require('typeforce') +let OPS = require('bitcoin-ops') + +let baddress = require('../address') +let bcrypto = require('../crypto') +let bscript = require('../script') +let BITCOIN_NETWORK = require('../networks').bitcoin + +let EMPTY_BUFFER = Buffer.alloc(0) + +function stacksEqual (a, b) { + if (a.length !== b.length) return false + + return a.every(function (x, i) { + return x.equals(b[i]) + }) +} + +// input: <> +// witness: [redeemScriptSig ...] {redeemScript} +// output: OP_0 {sha256(redeemScript)} +function p2wsh (a, opts) { + if ( + !a.address && + !a.hash && + !a.output && + !a.redeem && + !a.witness + ) throw new TypeError('Not enough data') + opts = opts || { validate: true } + + typef({ + network: typef.maybe(typef.Object), + + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(32)), + output: typef.maybe(typef.BufferN(34)), + + redeem: typef.maybe({ + input: typef.maybe(typef.Buffer), + network: typef.maybe(typef.Object), + output: typef.Buffer, + witness: typef.maybe(typef.arrayOf(typef.Buffer)) + }), + input: typef.maybe(typef.BufferN(0)), + witness: typef.maybe(typef.arrayOf(typef.Buffer)) + }, a) + + let _address = lazy.value(function () { return baddress.fromBech32(a.address) }) + let _rchunks = lazy.value(function () { return bscript.decompile(a.redeem.input) }) + + let network = a.network || BITCOIN_NETWORK + let o = { network } + + lazy.prop(o, 'address', function () { + if (!o.hash) return + return baddress.toBech32(o.hash, 0x00, network.bech32) + }) + lazy.prop(o, 'hash', function () { + if (a.output) return a.output.slice(2) + if (a.address) return baddress.fromBech32(a.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 () { + // transform redeem input to witness stack? + if (a.redeem && a.redeem.input && a.redeem.input.length > 0) { + let stack = bscript.toStack(_rchunks()) + + // assign, and blank the existing input + o.redeem = Object.assign({ witness: stack }, a.redeem) + o.redeem.input = EMPTY_BUFFER + return [].concat(stack, a.redeem.output) + } + + if (!a.redeem) return + if (!a.redeem.witness) return + return [].concat(a.redeem.witness, a.redeem.output) + }) + + // extended validation + if (opts.validate) { + let hash + if (a.address) { + if (_address().prefix !== network.bech32) throw new TypeError('Network mismatch') + if (_address().version !== 0x00) throw new TypeError('Invalid version') + if (_address().data.length !== 32) throw new TypeError('Invalid data') + else hash = _address().data + } + + if (a.hash) { + if (hash && !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') + let hash2 = a.output.slice(2) + if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch') + else hash = hash2 + } + + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch') + + // is there two redeem sources? + if ( + a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.witness) throw new TypeError('Ambiguous witness source') + + // is the redeem output non-empty? + if (bscript.decompile(a.redeem.output).length === 0) throw new TypeError('Redeem.output is invalid') + + // match hash against other sources + let hash2 = bcrypto.sha256(a.redeem.output) + if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch') + else hash = hash2 + + if (a.redeem.input && !bscript.isPushOnly(_rchunks())) throw new TypeError('Non push-only scriptSig') + if (a.witness && a.redeem.witness && !stacksEqual(a.witness, a.redeem.witness)) throw new TypeError('Witness and redeem.witness mismatch') + } + + if (a.witness) { + if (a.redeem && !a.redeem.output.equals(a.witness[a.witness.length - 1])) throw new TypeError('Witness and redeem.output mismatch') + } + } + + return Object.assign(o, a) +} + +module.exports = p2wsh diff --git a/src/payments/package.json b/src/payments/package.json new file mode 100644 index 0000000..9383db2 --- /dev/null +++ b/src/payments/package.json @@ -0,0 +1,40 @@ +{ + "name": "bitcoinjs-playground", + "version": "1.0.0", + "description": "Go nuts!", + "main": "_testnet.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/bitcoinjs/bitcoinjs-playground.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/bitcoinjs/bitcoinjs-playground/issues" + }, + "homepage": "https://github.com/bitcoinjs/bitcoinjs-playground#readme", + "dependencies": { + "async": "^2.5.0", + "bech32": "^1.1.3", + "bip21": "^2.0.1", + "bip32-utils": "^0.11.1", + "bip38": "^2.0.2", + "bip39": "^2.5.0", + "bip69": "^2.1.1", + "bitcoin-ops": "^1.4.1", + "bitcoinjs-lib": "^3.3.2", + "bs58": "^4.0.1", + "bs58check": "^2.1.1", + "cb-http-client": "^0.2.3", + "coinselect": "^3.1.11", + "dhttp": "^2.4.2", + "merkle-lib": "^2.0.10", + "mocha": "^5.0.5", + "tape": "^4.9.0", + "typeforce": "^1.11.4", + "utxo": "^2.0.4" + } +} diff --git a/src/script.js b/src/script.js index 30efa0a..ad7c4e4 100644 --- a/src/script.js +++ b/src/script.js @@ -98,11 +98,11 @@ function decompile (buffer) { if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) { const d = pushdata.decode(buffer, i) - // did reading a pushDataInt fail? empty script + // did reading a pushDataInt fail? if (d === null) return null i += d.size - // attempt to read too much data? empty script + // attempt to read too much data? if (i + d.number > buffer.length) return null const data = buffer.slice(i, i + d.number) diff --git a/test/fixtures/p2ms.json b/test/fixtures/p2ms.json new file mode 100644 index 0000000..d5bb0c8 --- /dev/null +++ b/test/fixtures/p2ms.json @@ -0,0 +1,378 @@ +{ + "valid": [ + { + "description": "output from output", + "arguments": { + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG" + }, + "expected": { + "m": 2, + "n": 2, + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ], + "signatures": null, + "input": null, + "witness": null + } + }, + { + "description": "output from m/pubkeys", + "arguments": { + "m": 1, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ] + }, + "expected": { + "m": 1, + "n": 2, + "output": "OP_1 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ], + "signatures": null, + "input": null, + "witness": null + } + }, + { + "description": "input/output from m/pubkeys/signatures", + "arguments": { + "m": 2, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002", + "030000000000000000000000000000000000000000000000000000000000000003" + ], + "signatures": [ + "300602010002010001", + "300602010102010001" + ] + }, + "expected": { + "m": 2, + "n": 3, + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002", + "030000000000000000000000000000000000000000000000000000000000000003" + ], + "signatures": [ + "300602010002010001", + "300602010102010001" + ], + "input": "OP_0 300602010002010001 300602010102010001", + "witness": [] + } + }, + { + "description": "input/output from output/signatures", + "arguments": { + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "signatures": [ + "300602010002010001", + "300602010102010001" + ] + }, + "expected": { + "m": 2, + "n": 3, + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002", + "030000000000000000000000000000000000000000000000000000000000000003" + ], + "signatures": [ + "300602010002010001", + "300602010102010001" + ], + "input": "OP_0 300602010002010001 300602010102010001", + "witness": [] + } + }, + { + "description": "input/output from input/output", + "arguments": { + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "input": "OP_0 300602010002010001 300602010102010001" + }, + "expected": { + "m": 2, + "n": 3, + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002", + "030000000000000000000000000000000000000000000000000000000000000003" + ], + "signatures": [ + "300602010002010001", + "300602010102010001" + ], + "input": "OP_0 300602010002010001 300602010102010001", + "witness": [] + } + }, + { + "description": "input/output from input/output, even if incomplete", + "arguments": { + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "input": "OP_0 OP_0 300602010102010001" + }, + "options": { + "allowIncomplete": true + }, + "expected": { + "m": 2, + "n": 2, + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ], + "signatures": [ + 0, + "300602010102010001" + ], + "input": "OP_0 OP_0 300602010102010001", + "witness": [] + } + }, + { + "description": "input/output from output/signatures, even if incomplete", + "arguments": { + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "signatures": [ + 0, + "300602010102010001" + ] + }, + "options": { + "allowIncomplete": true + }, + "expected": { + "m": 2, + "n": 2, + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ], + "signatures": [ + 0, + "300602010102010001" + ], + "input": "OP_0 OP_0 300602010102010001", + "witness": [] + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "exception": "Not enough data", + "arguments": { + "m": 2 + } + }, + { + "exception": "Not enough data", + "arguments": { + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ] + } + }, + { + "description": "Non OP_INT chunk (m)", + "exception": "Output is invalid", + "arguments": { + "output": "OP_RESERVED" + } + }, + { + "description": "Non OP_INT chunk (n)", + "exception": "Output is invalid", + "arguments": { + "output": "OP_1 OP_RESERVED" + } + }, + { + "description": "Missing OP_CHECKMULTISIG", + "exception": "Output is invalid", + "arguments": { + "output": "OP_1 OP_2 OP_RESERVED" + } + }, + { + "description": "m is 0", + "exception": "Output is invalid", + "arguments": { + "output": "OP_0 OP_2 OP_CHECKMULTISIG" + } + }, + { + "description": "n is 0 (m > n)", + "exception": "Output is invalid", + "arguments": { + "output": "OP_2 OP_0 OP_CHECKMULTISIG" + } + }, + { + "description": "m > n", + "exception": "Output is invalid", + "arguments": { + "output": "OP_3 OP_2 OP_CHECKMULTISIG" + } + }, + { + "description": "n !== output pubkeys", + "exception": "Output is invalid", + "arguments": { + "output": "OP_1 030000000000000000000000000000000000000000000000000000000000000001 OP_2 OP_CHECKMULTISIG" + } + }, + { + "description": "Non-canonical output public key", + "exception": "Output is invalid", + "arguments": { + "output": "OP_1 ffff OP_1 OP_CHECKMULTISIG" + } + }, + { + "exception": "n mismatch", + "arguments": { + "n": 2, + "output": "OP_1 030000000000000000000000000000000000000000000000000000000000000001 OP_1 OP_CHECKMULTISIG" + } + }, + { + "exception": "m mismatch", + "arguments": { + "m": 2, + "output": "OP_1 030000000000000000000000000000000000000000000000000000000000000001 OP_1 OP_CHECKMULTISIG" + } + }, + { + "exception": "Pubkeys mismatch", + "arguments": { + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "output": "OP_1 030000000000000000000000000000000000000000000000000000000000000002 OP_1 OP_CHECKMULTISIG" + } + }, + { + "exception": "Pubkey count mismatch", + "arguments": { + "m": 2, + "n": 3, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ] + } + }, + { + "exception": "Pubkey count cannot be less than m", + "arguments": { + "m": 4, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + } + }, + { + "exception": "Not enough signatures provided", + "arguments": { + "m": 2, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "signatures": [ + "300602010002010001" + ] + } + }, + { + "exception": "Too many signatures provided", + "arguments": { + "m": 2, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "signatures": [ + "300602010002010001", + "300602010002010001", + "300602010002010001" + ] + } + }, + { + "description": "Missing OP_0", + "exception": "Input is invalid", + "arguments": { + "m": 2, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "input": "OP_RESERVED" + } + }, + { + "exception": "Input has invalid signature\\(s\\)", + "arguments": { + "m": 1, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "input": "OP_0 ffffffffffffffff" + } + } + ], + "dynamic": { + "depends": { + "m": [ "output" ], + "n": [ "output", [ "m", "pubkeys" ] ], + "output": [ "output", [ "m", "pubkeys" ] ], + "pubkeys": [ "output" ], + "signatures": [ ["input", "output"] ], + "input": [ ["signatures", "output"] ], + "witness": [ ["input", "output"] ] + }, + "details": [ + { + "description": "p2ms", + "m": 2, + "n": 3, + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002", + "030000000000000000000000000000000000000000000000000000000000000003" + ], + "signatures": [ + "300602010002010001", + "300602010102010001" + ], + "input": "OP_0 300602010002010001 300602010102010001", + "witness": [] + } + ] + } +} diff --git a/test/fixtures/p2pk.json b/test/fixtures/p2pk.json new file mode 100644 index 0000000..ff0bbcd --- /dev/null +++ b/test/fixtures/p2pk.json @@ -0,0 +1,152 @@ +{ + "valid": [ + { + "description": "output from output", + "arguments": { + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG" + }, + "expected": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signatures": null, + "input": null, + "witness": null + } + }, + { + "description": "output from pubkey", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001" + }, + "expected": { + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG", + "signatures": null, + "input": null, + "witness": null + } + }, + { + "description": "input/output from output/signature", + "arguments": { + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG", + "signature": "300602010002010001" + }, + "expected": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "input": "300602010002010001", + "witness": [] + } + }, + { + "description": "input/output from pubkey/signature", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001" + }, + "expected": { + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG", + "input": "300602010002010001", + "witness": [] + } + }, + { + "description": "input/output from input/output", + "arguments": { + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG", + "input": "300602010002010001" + }, + "expected": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "witness": [] + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "exception": "Not enough data", + "arguments": { + "input": "300602010002010001" + } + }, + { + "exception": "Not enough data", + "arguments": { + "signature": "300602010002010001" + } + }, + { + "description": "Non-canonical signature", + "exception": "Expected property \"signature\" of type \\?isCanonicalScriptSignature, got Buffer", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "3044" + } + }, + { + "description": "Unexpected OP_RESERVED", + "exception": "Output is invalid", + "arguments": { + "output": "OP_RESERVED" + } + }, + { + "description": "Non-canonical output public key", + "exception": "Output pubkey is invalid", + "arguments": { + "output": "ffff OP_CHECKSIG" + } + }, + { + "description": "Unexpected OP_0 (at end)", + "exception": "Output is invalid", + "arguments": { + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG OP_0" + } + }, + { + "exception": "Pubkey mismatch", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "output": "030000000000000000000000000000000000000000000000000000000000000002 OP_CHECKSIG" + } + }, + { + "description": "Too many chunks", + "exception": "Input is invalid", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "input": "300602010002010001 OP_RESERVED" + } + }, + { + "exception": "Input has invalid signature", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "input": "ffffffffffffffff" + } + } + ], + "dynamic": { + "depends": { + "output": [ "pubkey" ], + "pubkey": [ "output" ], + "signature": [ ["input", "output"] ], + "input": [ ["signature", "output"] ], + "witness": [ ["input", "output"] ] + }, + "details": [ + { + "description": "p2pk", + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "input": "300602010002010001", + "witness": [] + } + ] + } +} diff --git a/test/fixtures/p2pkh.json b/test/fixtures/p2pkh.json new file mode 100644 index 0000000..7d47152 --- /dev/null +++ b/test/fixtures/p2pkh.json @@ -0,0 +1,214 @@ +{ + "valid": [ + { + "description": "output from address", + "arguments": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh" + }, + "expected": { + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from hash", + "arguments": { + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1" + }, + "expected": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from output", + "arguments": { + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG" + }, + "expected": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from pubkey", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001" + }, + "expected": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "input/output from pubkey/signature", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001" + }, + "expected": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001", + "witness": [] + } + }, + { + "description": "input/output from input", + "arguments": { + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001" + }, + "expected": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "witness": [] + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "exception": "Not enough data", + "arguments": { + "signature": "300602010002010001" + } + }, + { + "description": "Unexpected OP_RESERVED", + "exception": "Output is invalid", + "arguments": { + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_RESERVED" + } + }, + { + "description": "Unexpected OP_DUP", + "exception": "Output is invalid", + "arguments": { + "output": "OP_DUP OP_DUP 168b992bcfc44050310b3a94bd0771136d0b28d137 OP_EQUALVERIFY" + } + }, + { + "description": "Hash too short (too many chunks)", + "exception": "Output is invalid", + "arguments": { + "output": "OP_DUP OP_DUP 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_TRUE OP_EQUALVERIFY" + } + }, + { + "description": "Non-minimally encoded (non BIP62 compliant)", + "exception": "Expected property \"output\" of type Buffer\\(Length: 25\\), got Buffer\\(Length: 26\\)", + "arguments": { + "outputHex": "76a94c14aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac" + } + }, + { + "exception": "Pubkey mismatch", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000002" + } + }, + { + "exception": "Input has invalid signature", + "arguments": { + "input": "ffffffffffffffffff 030000000000000000000000000000000000000000000000000000000000000001" + } + }, + { + "exception": "Input has invalid pubkey", + "arguments": { + "input": "300602010002010001 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "Input has unexpected data", + "exception": "Input is invalid", + "arguments": { + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001 ffff" + } + }, + { + "description": "H(pubkey) != H", + "exception": "Hash mismatch", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "address.hash != H", + "exception": "Hash mismatch", + "arguments": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "address.hash != output.hash", + "exception": "Hash mismatch", + "arguments": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "output": "OP_DUP OP_HASH160 ffffffffffffffffffffffffffffffffffffffff OP_EQUALVERIFY OP_CHECKSIG" + } + }, + { + "description": "output.hash != H", + "exception": "Hash mismatch", + "arguments": { + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "H(input.pubkey) != H", + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffff", + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001" + } + } + ], + "dynamic": { + "depends": { + "address": [ "address", "output", "hash", "pubkey", "input" ], + "hash": [ "address", "output", "hash", "pubkey", "input" ], + "output": [ "address", "output", "hash", "pubkey", "input" ], + "pubkey": [ "input" ], + "signature": [ "input" ], + "input": [ [ "pubkey", "signature" ] ], + "witness": [ "input" ] + }, + "details": [ + { + "description": "p2pkh", + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001", + "witness": [] + } + ] + } +} diff --git a/test/fixtures/p2sh.json b/test/fixtures/p2sh.json new file mode 100644 index 0000000..c87c8a8 --- /dev/null +++ b/test/fixtures/p2sh.json @@ -0,0 +1,346 @@ +{ + "valid": [ + { + "description": "p2sh-*, out (from address)", + "arguments": { + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT" + }, + "expected": { + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086", + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2sh-*, out (from hash)", + "arguments": { + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086" + }, + "expected": { + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT", + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2sh-*, out (from output)", + "arguments": { + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL" + }, + "expected": { + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT", + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2sh-p2pkh, out (from redeem)", + "arguments": { + "redeem": { + "address": "this is P2PKH context, unknown and ignored by P2SH", + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG" + } + }, + "expected": { + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT", + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086", + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL", + "input": null, + "witness": null + } + }, + { + "description": "p2sh-p2wpkh, out (from redeem)", + "arguments": { + "redeem": { + "hash": "this is P2WPKH context, unknown and ignored by P2SH", + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400" + } + }, + "expected": { + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "hash": "0432515d8fe8de31be8207987fc6d67b29d5e7cc", + "output": "OP_HASH160 0432515d8fe8de31be8207987fc6d67b29d5e7cc OP_EQUAL", + "input": null, + "witness": null + } + }, + { + "description": "p2sh-p2pk, out (from redeem)", + "arguments": { + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG", + "pubkey": "this is P2WPKH context, unknown and ignored by P2SH" + } + }, + "expected": { + "address": "36TibC8RrPB9WrBdPoGXhHqDHJosyFVtVQ", + "hash": "3454c084887afe854e80221c69d6282926f809c4", + "output": "OP_HASH160 3454c084887afe854e80221c69d6282926f809c4 OP_EQUAL", + "input": null, + "witness": null + } + }, + { + "description": "p2sh-p2pkh, in and out (from redeem)", + "arguments": { + "redeem": { + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + } + }, + "expected": { + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT", + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086", + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 76a914c30afa58ae0673b00a45b5c17dff4633780f140088ac", + "witness": [] + } + }, + { + "description": "p2sh-p2wpkh, in and out (from redeem w/ witness)", + "arguments": { + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + }, + "expected": { + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "hash": "0432515d8fe8de31be8207987fc6d67b29d5e7cc", + "output": "OP_HASH160 0432515d8fe8de31be8207987fc6d67b29d5e7cc OP_EQUAL", + "input": "0014c30afa58ae0673b00a45b5c17dff4633780f1400", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + }, + { + "description": "p2sh-p2pk, in and out (from input)", + "arguments": { + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 2103e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058ac" + }, + "expected": { + "address": "36TibC8RrPB9WrBdPoGXhHqDHJosyFVtVQ", + "hash": "3454c084887afe854e80221c69d6282926f809c4", + "output": "OP_HASH160 3454c084887afe854e80221c69d6282926f809c4 OP_EQUAL", + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "witness": [] + }, + "witness": [] + } + }, + { + "description": "p2sh-p2wpkh, in and out (from input AND witness)", + "arguments": { + "input": "0014c30afa58ae0673b00a45b5c17dff4633780f1400", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + }, + "expected": { + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "hash": "0432515d8fe8de31be8207987fc6d67b29d5e7cc", + "output": "OP_HASH160 0432515d8fe8de31be8207987fc6d67b29d5e7cc OP_EQUAL", + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "description": "Non-minimally encoded (non BIP62 compliant)", + "exception": "Expected property \"output\" of type Buffer\\(Length: 23\\), got Buffer\\(Length: 24\\)", + "arguments": { + "outputHex": "a94c14c286a1af0947f58d1ad787385b1c2c4a976f9e7187" + } + }, + { + "description": "Expected OP_HASH160", + "exception": "Output is invalid", + "arguments": { + "output": "OP_HASH256 ffffffffffffffffffffffffffffffffffffffff OP_EQUAL" + } + }, + { + "description": "Unexpected OP_RESERVED", + "exception": "Output is invalid", + "arguments": { + "output": "OP_HASH256 ffffffffffffffffffffffffffffffffffffff OP_EQUAL OP_RESERVED" + } + }, + { + "description": "address.hash != H", + "exception": "Hash mismatch", + "arguments": { + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "address.hash != output.hash", + "exception": "Hash mismatch", + "arguments": { + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "output": "OP_HASH160 ffffffffffffffffffffffffffffffffffffffff OP_EQUAL" + } + }, + { + "description": "output.hash != H", + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffff", + "output": "OP_HASH160 0432515d8fe8de31be8207987fc6d67b29d5e7cc OP_EQUAL" + } + }, + { + "description": "H(redeem.output) != H", + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffff", + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG" + } + } + }, + { + "exception": "Input too short", + "arguments": { + "input": "" + } + }, + { + "exception": "Input too short", + "arguments": { + "inputHex": "01ff02ff" + } + }, + { + "exception": "Input is invalid", + "arguments": { + "input": "OP_0 OP_0" + } + }, + { + "exception": "Redeem.input mismatch", + "arguments": { + "input": "OP_0 02ffff", + "redeem": { + "input": "OP_CHECKSIG", + "output": "ffff" + } + } + }, + { + "exception": "Redeem.output mismatch", + "arguments": { + "input": "OP_0 02ffff", + "redeem": { + "input": "OP_0", + "output": "fff3" + } + } + }, + { + "exception": "Redeem.output too short", + "arguments": { + "redeem": { + "input": "OP_0", + "output": "" + } + } + }, + { + "exception": "Redeem.output too short", + "arguments": { + "inputHex": "021000" + } + }, + { + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffff", + "redeem": { + "input": "OP_0", + "output": "ffff" + } + } + }, + { + "exception": "Empty input", + "arguments": { + "inputHex": "01ff" + } + } + ], + "dynamic": { + "depends": { + "address": [ "address", "output", "hash", "redeem.output", [ "input", "witness" ] ], + "hash": [ "address", "output", "hash", "redeem.output", [ "input", "witness" ] ], + "output": [ "address", "output", "hash", "redeem.output", [ "input", "witness" ] ], + "redeem.output": [ [ "input", "witness" ] ], + "redeem.input": [ [ "input", "witness" ] ], + "redeem.witness": [ [ "input", "witness" ] ], + "input": [ "redeem" ], + "witness": [ "redeem" ] + }, + "details": [ + { + "description": "p2sh-p2pkh", + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT", + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086", + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL", + "redeem": { + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "witness": [] + }, + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 76a914c30afa58ae0673b00a45b5c17dff4633780f140088ac", + "witness": [] + }, + { + "description": "p2sh-p2wpkh", + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "hash": "0432515d8fe8de31be8207987fc6d67b29d5e7cc", + "output": "OP_HASH160 0432515d8fe8de31be8207987fc6d67b29d5e7cc OP_EQUAL", + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + }, + "input": "0014c30afa58ae0673b00a45b5c17dff4633780f1400", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + ] + } +} diff --git a/test/fixtures/p2wpkh.json b/test/fixtures/p2wpkh.json new file mode 100644 index 0000000..84e0c08 --- /dev/null +++ b/test/fixtures/p2wpkh.json @@ -0,0 +1,195 @@ +{ + "valid": [ + { + "description": "output from address", + "arguments": { + "address": "bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d" + }, + "expected": { + "hash": "ea6d525c0c955d90d3dbd29a81ef8bfb79003727", + "output": "OP_0 ea6d525c0c955d90d3dbd29a81ef8bfb79003727", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from hash", + "arguments": { + "hash": "ea6d525c0c955d90d3dbd29a81ef8bfb79003727" + }, + "expected": { + "address": "bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d", + "output": "OP_0 ea6d525c0c955d90d3dbd29a81ef8bfb79003727", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from output", + "arguments": { + "output": "OP_0 ea6d525c0c955d90d3dbd29a81ef8bfb79003727" + }, + "expected": { + "address": "bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d", + "hash": "ea6d525c0c955d90d3dbd29a81ef8bfb79003727", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from pubkey", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001" + }, + "expected": { + "address": "bc1qz69ej270c3q9qvgt822t6pm3zdksk2x35j2jlm", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_0 168b992bcfc44050310b3a94bd0771136d0b28d1", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "witness/output from pubkey/signature", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001" + }, + "expected": { + "address": "bc1qz69ej270c3q9qvgt822t6pm3zdksk2x35j2jlm", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_0 168b992bcfc44050310b3a94bd0771136d0b28d1", + "input": "", + "witness": [ + "300602010002010001", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + } + }, + { + "description": "witness/output from witness", + "arguments": { + "witness": [ + "300602010002010001", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + }, + "expected": { + "address": "bc1qz69ej270c3q9qvgt822t6pm3zdksk2x35j2jlm", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_0 168b992bcfc44050310b3a94bd0771136d0b28d1", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "input": "" + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "exception": "Not enough data", + "arguments": { + "signature": "300602010002010001" + } + }, + { + "exception": "Output is invalid", + "description": "Unexpected OP", + "arguments": { + "output": "OP_RESERVED ea6d525c0c955d90d3dbd29a81ef8bfb79003727" + } + }, + { + "exception": "Pubkey mismatch", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "witness": [ + "300602010002010001", + "030000000000000000000000000000000000000000000000000000000000000002" + ] + } + }, + { + "exception": "Hash mismatch", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "exception": "Hash mismatch", + "arguments": { + "address": "bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "exception": "Hash mismatch", + "arguments": { + "output": "OP_0 ea6d525c0c955d90d3dbd29a81ef8bfb79003727", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffff", + "witness": [ + "300602010002010001", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + } + }, + { + "exception": "Input has invalid signature", + "arguments": { + "witness": [ + "ffffffffffffffffff", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + } + }, + { + "exception": "Input has invalid pubkey", + "arguments": { + "witness": [ + "300602010002010001", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + ] + } + } + ], + "dynamic": { + "depends": { + "address": [ "address", "output", "hash", "pubkey", "witness" ], + "hash": [ "address", "output", "hash", "pubkey", "witness" ], + "output": [ "address", "output", "hash", "pubkey", "witness" ], + "pubkey": [ "witness" ], + "signature": [ "witness" ], + "input": [ "witness" ], + "witness": [ [ "pubkey", "signature" ] ] + }, + "details": [ + { + "description": "p2wpkh", + "address": "bc1qz69ej270c3q9qvgt822t6pm3zdksk2x35j2jlm", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_0 168b992bcfc44050310b3a94bd0771136d0b28d1", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "input": "", + "witness": [ + "300602010002010001", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + } + ] + } +} diff --git a/test/fixtures/p2wsh.json b/test/fixtures/p2wsh.json new file mode 100644 index 0000000..c123d64 --- /dev/null +++ b/test/fixtures/p2wsh.json @@ -0,0 +1,326 @@ +{ + "valid": [ + { + "description": "p2wsh-*, out (from address)", + "arguments": { + "address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q" + }, + "expected": { + "hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-*, out (from hash)", + "arguments": { + "hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0" + }, + "expected": { + "address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q", + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-*, out (from output)", + "arguments": { + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0" + }, + "expected": { + "address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q", + "hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-p2pkh, out (from redeem)", + "arguments": { + "redeem": { + "address": "this is P2PKH context, unknown and ignored by p2wsh", + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG" + } + }, + "expected": { + "address": "bc1qusxlgq9quu27ucxs7a2fg8nv0pycdzvxsjk9npyupupxw3y892ss2cq5ar", + "hash": "e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "output": "OP_0 e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-p2wpkh, out (from redeem)", + "arguments": { + "redeem": { + "hash": "this is P2WPKH context, unknown and ignored by p2wsh", + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400" + } + }, + "expected": { + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "hash": "0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "output": "OP_0 0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-p2pk, out (from redeem)", + "arguments": { + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG", + "pubkey": "this is P2WPKH context, unknown and ignored by p2wsh" + } + }, + "expected": { + "address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q", + "hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-p2pkh, in and out (from redeem, transformed to witness)", + "arguments": { + "redeem": { + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + } + }, + "expected": { + "address": "bc1qusxlgq9quu27ucxs7a2fg8nv0pycdzvxsjk9npyupupxw3y892ss2cq5ar", + "hash": "e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "output": "OP_0 e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "redeem": { + "input": "" + }, + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "76a914c30afa58ae0673b00a45b5c17dff4633780f140088ac" + ] + } + }, + { + "description": "p2wsh-p2wpkh, in and out (from redeem w/ witness)", + "arguments": { + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + }, + "expected": { + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "hash": "0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "output": "OP_0 0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "0014c30afa58ae0673b00a45b5c17dff4633780f1400" + ] + } + }, + { + "description": "p2wsh-p2pk, in and out (from witness)", + "arguments": { + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "2103e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058ac" + ] + }, + "expected": { + "address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q", + "hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501" + ] + }, + "input": "" + } + }, + { + "description": "p2wsh-p2wpkh, in and out (from witness)", + "arguments": { + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "0014c30afa58ae0673b00a45b5c17dff4633780f1400" + ] + }, + "expected": { + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "hash": "0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "output": "OP_0 0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "description": "address.hash != H", + "exception": "Hash mismatch", + "arguments": { + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "hash": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "address.hash != output.hash", + "exception": "Hash mismatch", + "arguments": { + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "output": "OP_0 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "output.hash != H", + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0" + } + }, + { + "description": "H(redeem.output) != H", + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG" + } + } + }, + { + "exception": "Output is invalid", + "arguments": { + "output": "OP_HASH256 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff OP_EQUAL" + } + }, + { + "exception": "Redeem.output is invalid", + "arguments": { + "redeem": { + "output": "" + } + } + }, + { + "exception": "Non push-only scriptSig", + "arguments": { + "redeem": { + "output": "OP_TRUE", + "input": "OP_HASH256" + } + } + }, + { + "exception": "Witness and redeem.output mismatch", + "arguments": { + "redeem": { + "output": "OP_TRUE", + "input": "OP_0" + }, + "witness": [ + "02ffff" + ] + } + }, + { + "exception": "Witness and redeem.witness mismatch", + "arguments": { + "redeem": { + "output": "OP_TRUE", + "witness": [ + "01" + ] + }, + "witness": [ + "00", + "02ffff" + ] + } + } + ], + "dynamic": { + "depends": { + "address": [ "address", "output", "hash", "redeem.output", "witness" ], + "hash": [ "address", "output", "hash", "redeem.output", "witness" ], + "output": [ "address", "output", "hash", "redeem.output", "witness" ], + "redeem.output": [ "witness" ], + "redeem.input": [ [ "input", "witness" ], "witness" ], + "input": [ "witness" ], + "witness": [ "redeem" ] + }, + "details": [ + { + "description": "p2wsh-p2pkh", + "disabled": [ + "redeem.input" + ], + "address": "bc1qusxlgq9quu27ucxs7a2fg8nv0pycdzvxsjk9npyupupxw3y892ss2cq5ar", + "hash": "e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "output": "OP_0 e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "redeem": { + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "witness": null + }, + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "76a914c30afa58ae0673b00a45b5c17dff4633780f140088ac" + ] + }, + { + "description": "p2wsh-p2wpkh", + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "hash": "0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "output": "OP_0 0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + }, + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "0014c30afa58ae0673b00a45b5c17dff4633780f1400" + ] + } + ] + } +} diff --git a/test/payments.js b/test/payments.js new file mode 100644 index 0000000..14382e5 --- /dev/null +++ b/test/payments.js @@ -0,0 +1,65 @@ +/* global describe, it */ + +const assert = require('assert') +const u = require('./payments.utils') + +;['p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(function (p) { + describe(p, function () { + const fn = require('../src/payments/' + p) + const fixtures = require('./fixtures/' + p) + + fixtures.valid.forEach(function (f, i) { + const args = u.preform(f.arguments) + + it(f.description + ' as expected', function () { + const actual = fn(args, f.options) + u.equate(actual, f.expected, f.arguments) + }) + + it(f.description + ' as expected (no validation)', function () { + const actual = fn(args, Object.assign({}, f.options, { + validate: false + })) + + u.equate(actual, f.expected, f.arguments) + }) + }) + + fixtures.invalid.forEach(function (f) { + it('throws ' + (f.description || f.exception), function () { + const args = u.preform(f.arguments) + + assert.throws(function () { + fn(args, f.options) + }, new RegExp(f.exception)) + }) + }) + + // cross-verify dynamically too + if (!fixtures.dynamic) return + const { depends, details } = fixtures.dynamic + + details.forEach(function (f) { + const detail = u.preform(f) + const disabled = {} + if (f.disabled) f.disabled.forEach(function (k) { disabled[k] = true }) + + for (let key in depends) { + if (key in disabled) continue + const dependencies = depends[key] + + dependencies.forEach(function (dependency) { + if (!Array.isArray(dependency)) dependency = [dependency] + + const args = {} + dependency.forEach(function (d) { u.from(d, detail, args) }) + const expected = u.from(key, detail) + + it(f.description + ', ' + key + ' derives from ' + JSON.stringify(dependency), function () { + u.equate(fn(args), expected) + }) + }) + } + }) + }) +}) diff --git a/test/payments.utils.js b/test/payments.utils.js new file mode 100644 index 0000000..22001e9 --- /dev/null +++ b/test/payments.utils.js @@ -0,0 +1,128 @@ +let t = require('assert') +let bscript = require('../src/script') +let bnetworks = require('../src/networks') + +function tryHex (x) { + if (Buffer.isBuffer(x)) return x.toString('hex') + if (Array.isArray(x)) return x.map(tryHex) + return x +} +function tryASM (x) { + if (Buffer.isBuffer(x)) return bscript.toASM(x) + return x +} +function asmToBuffer (x) { + if (x === '') return Buffer.alloc(0) + return bscript.fromASM(x) +} +function carryOver (a, b) { + for (let k in b) { + if (k in a && k === 'redeem') { + carryOver(a[k], b[k]) + continue + } + + // don't, the value was specified + if (k in a) continue + + // otherwise, expect match + a[k] = b[k] + } +} + +function equateBase (a, b, context) { + if ('output' in b) t.strictEqual(tryASM(a.output), tryASM(b.output), `Inequal ${context}output`) + if ('input' in b) t.strictEqual(tryASM(a.input), tryASM(b.input), `Inequal ${context}input`) + if ('witness' in b) t.deepEqual(tryHex(a.witness), tryHex(b.witness), `Inequal ${context}witness`) +} + +function equate (a, b, args) { + b = Object.assign({}, b) + carryOver(b, args) + + // by null, we mean 'undefined', but JSON + if (b.input === null) b.input = undefined + if (b.output === null) b.output = undefined + if (b.witness === null) b.witness = undefined + if (b.redeem) { + if (b.redeem.input === null) b.redeem.input = undefined + if (b.redeem.output === null) b.redeem.output = undefined + if (b.redeem.witness === null) b.redeem.witness = undefined + } + + equateBase(a, b, '') + if (b.redeem) equateBase(a.redeem, b.redeem, 'redeem.') + if (b.network) t.deepEqual(a.network, b.network, 'Inequal *.network') + + // contextual + if (b.signature === null) b.signature = undefined + if ('address' in b) t.strictEqual(a.address, b.address, 'Inequal *.address') + if ('hash' in b) t.strictEqual(tryHex(a.hash), tryHex(b.hash), 'Inequal *.hash') + if ('pubkey' in b) t.strictEqual(tryHex(a.pubkey), tryHex(b.pubkey), 'Inequal *.pubkey') + if ('signature' in b) t.strictEqual(tryHex(a.signature), tryHex(b.signature), 'Inequal signature') + if ('m' in b) t.strictEqual(a.m, b.m, 'Inequal *.m') + if ('n' in b) t.strictEqual(a.n, b.n, 'Inequal *.n') + if ('pubkeys' in b) t.deepEqual(tryHex(a.pubkeys), tryHex(b.pubkeys), 'Inequal *.pubkeys') + if ('signatures' in b) t.deepEqual(tryHex(a.signatures), tryHex(b.signatures), 'Inequal *.signatures') +} + +function preform (x) { + x = Object.assign({}, x) + + if (x.network) x.network = bnetworks[x.network] + if (typeof x.inputHex === 'string') { + x.input = Buffer.from(x.inputHex, 'hex') + delete x.inputHex + } + if (typeof x.outputHex === 'string') { + x.output = Buffer.from(x.outputHex, 'hex') + delete x.outputHex + } + if (typeof x.output === 'string') x.output = asmToBuffer(x.output) + if (typeof x.input === 'string') x.input = asmToBuffer(x.input) + if (Array.isArray(x.witness)) { + x.witness = x.witness.map(function (y) { + return Buffer.from(y, 'hex') + }) + } + + if (x.hash) x.hash = Buffer.from(x.hash, 'hex') + if (x.pubkey) x.pubkey = Buffer.from(x.pubkey, 'hex') + if (x.signature) x.signature = Buffer.from(x.signature, 'hex') + if (x.pubkeys) x.pubkeys = x.pubkeys.map(function (y) { return Buffer.from(y, 'hex') }) + if (x.signatures) x.signatures = x.signatures.map(function (y) { return Number.isFinite(y) ? y : Buffer.from(y, 'hex') }) + if (x.redeem) { + if (typeof x.redeem.input === 'string') x.redeem.input = asmToBuffer(x.redeem.input) + if (typeof x.redeem.output === 'string') x.redeem.output = asmToBuffer(x.redeem.output) + if (Array.isArray(x.redeem.witness)) x.redeem.witness = x.redeem.witness.map(function (y) { return Buffer.from(y, 'hex') }) + x.redeem.network = bnetworks[x.redeem.network] || x.network || bnetworks.bitcoin + } + + return x +} + +function from (path, object, result) { + path = path.split('.') + result = result || {} + + let r = result + path.forEach((k, i) => { + if (i < path.length - 1) { + r[k] = r[k] || {} + + // recurse + r = r[k] + object = object[k] + } else { + r[k] = object[k] + } + }) + + return result +} + +module.exports = { + from, + equate, + preform +} From 6c957533d6dd77cb28d16c6817236028698c8d7d Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 6 Jun 2018 00:16:52 +1000 Subject: [PATCH 3/4] index: expose payments --- src/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.js b/src/index.js index 3630bb8..603c64f 100644 --- a/src/index.js +++ b/src/index.js @@ -15,5 +15,6 @@ module.exports = { crypto: require('./crypto'), networks: require('./networks'), opcodes: require('bitcoin-ops'), + payments: require('./payments'), script: script } From 8197f9d7040671375f250f7656974a1ee28709e7 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 6 Jun 2018 00:09:55 +1000 Subject: [PATCH 4/4] less strict on inputs --- test/fixtures/p2pk.json | 12 ------------ test/payments.js | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/test/fixtures/p2pk.json b/test/fixtures/p2pk.json index ff0bbcd..7ede5f4 100644 --- a/test/fixtures/p2pk.json +++ b/test/fixtures/p2pk.json @@ -66,18 +66,6 @@ "exception": "Not enough data", "arguments": {} }, - { - "exception": "Not enough data", - "arguments": { - "input": "300602010002010001" - } - }, - { - "exception": "Not enough data", - "arguments": { - "signature": "300602010002010001" - } - }, { "description": "Non-canonical signature", "exception": "Expected property \"signature\" of type \\?isCanonicalScriptSignature, got Buffer", diff --git a/test/payments.js b/test/payments.js index 14382e5..5722d88 100644 --- a/test/payments.js +++ b/test/payments.js @@ -26,7 +26,7 @@ const u = require('./payments.utils') }) fixtures.invalid.forEach(function (f) { - it('throws ' + (f.description || f.exception), function () { + it('throws ' + f.exception + (f.description ? ('for ' + f.description) : ''), function () { const args = u.preform(f.arguments) assert.throws(function () {