diff --git a/src/address.js b/src/address.js index 13fa69c..e71bd46 100644 --- a/src/address.js +++ b/src/address.js @@ -2,10 +2,10 @@ const Buffer = require('safe-buffer').Buffer const bech32 = require('bech32') const bs58check = require('bs58check') const bscript = require('./script') -const btemplates = require('./templates') const networks = require('./networks') const typeforce = require('typeforce') const types = require('./types') +const payments = require('./payments') function fromBase58Check (address) { const payload = bs58check.decode(address) @@ -48,15 +48,15 @@ function toBech32 (data, version, prefix) { return bech32.encode(prefix, words) } -function fromOutputScript (outputScript, network) { +function fromOutputScript (output, network) { network = network || networks.bitcoin - if (btemplates.pubKeyHash.output.check(outputScript)) return toBase58Check(bscript.compile(outputScript).slice(3, 23), network.pubKeyHash) - if (btemplates.scriptHash.output.check(outputScript)) return toBase58Check(bscript.compile(outputScript).slice(2, 22), network.scriptHash) - if (btemplates.witnessPubKeyHash.output.check(outputScript)) return toBech32(bscript.compile(outputScript).slice(2, 22), 0, network.bech32) - if (btemplates.witnessScriptHash.output.check(outputScript)) return toBech32(bscript.compile(outputScript).slice(2, 34), 0, network.bech32) + 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(outputScript) + ' has no matching Address') + throw new Error(bscript.toASM(output) + ' has no matching Address') } function toOutputScript (address, network) { @@ -68,8 +68,8 @@ function toOutputScript (address, network) { } catch (e) {} if (decode) { - if (decode.version === network.pubKeyHash) return btemplates.pubKeyHash.output.encode(decode.hash) - if (decode.version === network.scriptHash) return btemplates.scriptHash.output.encode(decode.hash) + if (decode.version === network.pubKeyHash) return payments.p2pkh({ hash: decode.hash }).output + if (decode.version === network.scriptHash) return payments.p2sh({ hash: decode.hash }).output } else { try { decode = fromBech32(address) @@ -78,8 +78,8 @@ function toOutputScript (address, network) { if (decode) { if (decode.prefix !== network.bech32) throw new Error(address + ' has an invalid prefix') if (decode.version === 0) { - if (decode.data.length === 20) return btemplates.witnessPubKeyHash.output.encode(decode.data) - if (decode.data.length === 32) return btemplates.witnessScriptHash.output.encode(decode.data) + if (decode.data.length === 20) return payments.p2wpkh({ hash: decode.data }).output + if (decode.data.length === 32) return payments.p2wsh({ hash: decode.data }).output } } } diff --git a/src/templates/index.js b/src/classify.js similarity index 71% rename from src/templates/index.js rename to src/classify.js index 94ce996..0b98fa6 100644 --- a/src/templates/index.js +++ b/src/classify.js @@ -1,12 +1,12 @@ -const decompile = require('../script').decompile -const multisig = require('./multisig') -const nullData = require('./nulldata') -const pubKey = require('./pubkey') -const pubKeyHash = require('./pubkeyhash') -const scriptHash = require('./scripthash') -const witnessPubKeyHash = require('./witnesspubkeyhash') -const witnessScriptHash = require('./witnessscripthash') -const witnessCommitment = require('./witnesscommitment') +const decompile = require('./script').decompile +const multisig = require('./templates/multisig') +const nullData = require('./templates/nulldata') +const pubKey = require('./templates/pubkey') +const pubKeyHash = require('./templates/pubkeyhash') +const scriptHash = require('./templates/scripthash') +const witnessPubKeyHash = require('./templates/witnesspubkeyhash') +const witnessScriptHash = require('./templates/witnessscripthash') +const witnessCommitment = require('./templates/witnesscommitment') const types = { MULTISIG: 'multisig', @@ -63,16 +63,8 @@ function classifyWitness (script, allowIncomplete) { } module.exports = { - classifyInput: classifyInput, - classifyOutput: classifyOutput, - classifyWitness: classifyWitness, - multisig: multisig, - nullData: nullData, - pubKey: pubKey, - pubKeyHash: pubKeyHash, - scriptHash: scriptHash, - witnessPubKeyHash: witnessPubKeyHash, - witnessScriptHash: witnessScriptHash, - witnessCommitment: witnessCommitment, + input: classifyInput, + output: classifyOutput, + witness: classifyWitness, types: types } diff --git a/src/index.js b/src/index.js index 603c64f..213e98a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,4 @@ const script = require('./script') -const templates = require('./templates') -for (let key in templates) { - script[key] = templates[key] -} module.exports = { Block: require('./block'), diff --git a/src/payments/p2pkh.js b/src/payments/p2pkh.js index 9d0733d..140f707 100644 --- a/src/payments/p2pkh.js +++ b/src/payments/p2pkh.js @@ -1,12 +1,12 @@ -let lazy = require('./lazy') -let typef = require('typeforce') -let OPS = require('bitcoin-ops') -let ecc = require('tiny-secp256k1') +const lazy = require('./lazy') +const typef = require('typeforce') +const OPS = require('bitcoin-ops') +const ecc = require('tiny-secp256k1') -let baddress = require('../address') -let bcrypto = require('../crypto') -let bscript = require('../script') -let BITCOIN_NETWORK = require('../networks').bitcoin +const bcrypto = require('../crypto') +const bscript = require('../script') +const BITCOIN_NETWORK = require('../networks').bitcoin +const bs58check = require('bs58check') // input: {signature} {pubkey} // output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG @@ -31,15 +31,24 @@ function p2pkh (a, opts) { 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) }) + 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 = lazy.value(function () { return bscript.decompile(a.input) }) - let network = a.network || BITCOIN_NETWORK - let o = { network } + const network = a.network || BITCOIN_NETWORK + const o = { network } lazy.prop(o, 'address', function () { if (!o.hash) return - return baddress.toBase58Check(o.hash, network.pubKeyHash) + + 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) diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index b26119d..9b28a3a 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -2,10 +2,10 @@ 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 +const bs58check = require('bs58check') function stacksEqual (a, b) { if (a.length !== b.length) return false @@ -48,7 +48,12 @@ function p2sh (a, opts) { const network = a.network || BITCOIN_NETWORK const o = { network } - const _address = lazy.value(function () { return baddress.fromBase58Check(a.address) }) + 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 = lazy.value(function () { return bscript.decompile(a.input) }) const _redeem = lazy.value(function () { const chunks = _chunks() @@ -63,7 +68,11 @@ function p2sh (a, opts) { // output dependents lazy.prop(o, 'address', function () { if (!o.hash) return - return baddress.toBase58Check(o.hash, network.scriptHash) + + const payload = Buffer.allocUnsafe(21) + payload.writeUInt8(network.scriptHash, 0) + o.hash.copy(payload, 1) + return bs58check.encode(payload) }) lazy.prop(o, 'hash', function () { // in order of least effort diff --git a/src/payments/p2wpkh.js b/src/payments/p2wpkh.js index f2bdeb4..bd010f7 100644 --- a/src/payments/p2wpkh.js +++ b/src/payments/p2wpkh.js @@ -1,14 +1,14 @@ -let lazy = require('./lazy') -let typef = require('typeforce') -let OPS = require('bitcoin-ops') -let ecc = require('tiny-secp256k1') +const lazy = require('./lazy') +const typef = require('typeforce') +const OPS = require('bitcoin-ops') +const ecc = require('tiny-secp256k1') -let baddress = require('../address') -let bcrypto = require('../crypto') -let bscript = require('../script') -let BITCOIN_NETWORK = require('../networks').bitcoin +const bcrypto = require('../crypto') +const bech32 = require('bech32') +const bscript = require('../script') +const BITCOIN_NETWORK = require('../networks').bitcoin -let EMPTY_BUFFER = Buffer.alloc(0) +const EMPTY_BUFFER = Buffer.alloc(0) // witness: {signature} {pubKey} // input: <> @@ -34,14 +34,26 @@ function p2wpkh (a, opts) { witness: typef.maybe(typef.arrayOf(typef.Buffer)) }, a) - let _address = lazy.value(function () { return baddress.fromBech32(a.address) }) + 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) + } + }) - let network = a.network || BITCOIN_NETWORK - let o = { network } + const network = a.network || BITCOIN_NETWORK + const o = { network } lazy.prop(o, 'address', function () { if (!o.hash) return - return baddress.toBech32(o.hash, 0x00, network.bech32) + + 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) @@ -86,7 +98,7 @@ function p2wpkh (a, opts) { } if (a.pubkey) { - let pkh = bcrypto.hash160(a.pubkey) + const pkh = bcrypto.hash160(a.pubkey) if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch') else hash = pkh } @@ -113,7 +125,7 @@ function p2wpkh (a, opts) { 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]) + const pkh = bcrypto.hash160(a.witness[1]) if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch') } } diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index 6f53484..e1adcc5 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -1,13 +1,13 @@ -let lazy = require('./lazy') -let typef = require('typeforce') -let OPS = require('bitcoin-ops') +const lazy = require('./lazy') +const typef = require('typeforce') +const OPS = require('bitcoin-ops') -let baddress = require('../address') -let bcrypto = require('../crypto') -let bscript = require('../script') -let BITCOIN_NETWORK = require('../networks').bitcoin +const bech32 = require('bech32') +const bcrypto = require('../crypto') +const bscript = require('../script') +const BITCOIN_NETWORK = require('../networks').bitcoin -let EMPTY_BUFFER = Buffer.alloc(0) +const EMPTY_BUFFER = Buffer.alloc(0) function stacksEqual (a, b) { if (a.length !== b.length) return false @@ -47,19 +47,30 @@ function p2wsh (a, opts) { 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) }) + 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 = lazy.value(function () { return bscript.decompile(a.redeem.input) }) - let network = a.network || BITCOIN_NETWORK - let o = { network } + const network = a.network || BITCOIN_NETWORK + const o = { network } lazy.prop(o, 'address', function () { if (!o.hash) return - return baddress.toBech32(o.hash, 0x00, network.bech32) + 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 baddress.fromBech32(a.address).data + if (a.address) return _address().data if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output) }) lazy.prop(o, 'output', function () { @@ -145,7 +156,7 @@ function p2wsh (a, opts) { 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) + const hash2 = bcrypto.sha256(a.redeem.output) if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch') else hash = hash2 } diff --git a/src/templates/multisig/input.js b/src/templates/multisig/input.js index 318e911..a66f05f 100644 --- a/src/templates/multisig/input.js +++ b/src/templates/multisig/input.js @@ -1,9 +1,6 @@ // OP_0 [signatures ...] -const Buffer = require('safe-buffer').Buffer const bscript = require('../../script') -const p2mso = require('./output') -const typeforce = require('typeforce') const OPS = require('bitcoin-ops') function partialSignature (value) { @@ -23,50 +20,4 @@ function check (script, allowIncomplete) { } check.toJSON = function () { return 'multisig input' } -const EMPTY_BUFFER = Buffer.allocUnsafe(0) - -function encodeStack (signatures, scriptPubKey) { - typeforce([partialSignature], signatures) - - if (scriptPubKey) { - const scriptData = p2mso.decode(scriptPubKey) - - if (signatures.length < scriptData.m) { - throw new TypeError('Not enough signatures provided') - } - - if (signatures.length > scriptData.pubKeys.length) { - throw new TypeError('Too many signatures provided') - } - } - - return [].concat(EMPTY_BUFFER, signatures.map(function (sig) { - if (sig === OPS.OP_0) { - return EMPTY_BUFFER - } - return sig - })) -} - -function encode (signatures, scriptPubKey) { - return bscript.compile(encodeStack(signatures, scriptPubKey)) -} - -function decodeStack (stack, allowIncomplete) { - typeforce(typeforce.Array, stack) - typeforce(check, stack, allowIncomplete) - return stack.slice(1) -} - -function decode (buffer, allowIncomplete) { - const stack = bscript.decompile(buffer) - return decodeStack(stack, allowIncomplete) -} - -module.exports = { - check: check, - decode: decode, - decodeStack: decodeStack, - encode: encode, - encodeStack: encodeStack -} +module.exports = { check } diff --git a/src/templates/multisig/output.js b/src/templates/multisig/output.js index 34b9ff4..5c9d81f 100644 --- a/src/templates/multisig/output.js +++ b/src/templates/multisig/output.js @@ -2,7 +2,6 @@ const bscript = require('../../script') const types = require('../../types') -const typeforce = require('typeforce') const OPS = require('bitcoin-ops') const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1 @@ -27,38 +26,4 @@ function check (script, allowIncomplete) { } check.toJSON = function () { return 'multi-sig output' } -function encode (m, pubKeys) { - typeforce({ - m: types.Number, - pubKeys: [bscript.isCanonicalPubKey] - }, { - m: m, - pubKeys: pubKeys - }) - - const n = pubKeys.length - if (n < m) throw new TypeError('Not enough pubKeys provided') - - return bscript.compile([].concat( - OP_INT_BASE + m, - pubKeys, - OP_INT_BASE + n, - OPS.OP_CHECKMULTISIG - )) -} - -function decode (buffer, allowIncomplete) { - const chunks = bscript.decompile(buffer) - typeforce(check, chunks, allowIncomplete) - - return { - m: chunks[0] - OP_INT_BASE, - pubKeys: chunks.slice(1, -2) - } -} - -module.exports = { - check: check, - decode: decode, - encode: encode -} +module.exports = { check } diff --git a/src/templates/pubkey/input.js b/src/templates/pubkey/input.js index cd5ffd3..ec21155 100644 --- a/src/templates/pubkey/input.js +++ b/src/templates/pubkey/input.js @@ -1,7 +1,6 @@ // {signature} const bscript = require('../../script') -const typeforce = require('typeforce') function check (script) { const chunks = bscript.decompile(script) @@ -11,30 +10,6 @@ function check (script) { } check.toJSON = function () { return 'pubKey input' } -function encodeStack (signature) { - typeforce(bscript.isCanonicalScriptSignature, signature) - return [signature] -} - -function encode (signature) { - return bscript.compile(encodeStack(signature)) -} - -function decodeStack (stack) { - typeforce(typeforce.Array, stack) - typeforce(check, stack) - return stack[0] -} - -function decode (buffer) { - const stack = bscript.decompile(buffer) - return decodeStack(stack) -} - module.exports = { - check: check, - decode: decode, - decodeStack: decodeStack, - encode: encode, - encodeStack: encodeStack + check: check } diff --git a/src/templates/pubkey/output.js b/src/templates/pubkey/output.js index 2d34b4f..b25c8c1 100644 --- a/src/templates/pubkey/output.js +++ b/src/templates/pubkey/output.js @@ -1,7 +1,6 @@ // {pubKey} OP_CHECKSIG const bscript = require('../../script') -const typeforce = require('typeforce') const OPS = require('bitcoin-ops') function check (script) { @@ -13,21 +12,4 @@ function check (script) { } check.toJSON = function () { return 'pubKey output' } -function encode (pubKey) { - typeforce(bscript.isCanonicalPubKey, pubKey) - - return bscript.compile([pubKey, OPS.OP_CHECKSIG]) -} - -function decode (buffer) { - const chunks = bscript.decompile(buffer) - typeforce(check, chunks) - - return chunks[0] -} - -module.exports = { - check: check, - decode: decode, - encode: encode -} +module.exports = { check } diff --git a/src/templates/pubkeyhash/input.js b/src/templates/pubkeyhash/input.js index 2f633e1..f5bc452 100644 --- a/src/templates/pubkeyhash/input.js +++ b/src/templates/pubkeyhash/input.js @@ -1,7 +1,6 @@ // {signature} {pubKey} const bscript = require('../../script') -const typeforce = require('typeforce') function check (script) { const chunks = bscript.decompile(script) @@ -12,41 +11,4 @@ function check (script) { } check.toJSON = function () { return 'pubKeyHash input' } -function encodeStack (signature, pubKey) { - typeforce({ - signature: bscript.isCanonicalScriptSignature, - pubKey: bscript.isCanonicalPubKey - }, { - signature: signature, - pubKey: pubKey - }) - - return [signature, pubKey] -} - -function encode (signature, pubKey) { - return bscript.compile(encodeStack(signature, pubKey)) -} - -function decodeStack (stack) { - typeforce(typeforce.Array, stack) - typeforce(check, stack) - - return { - signature: stack[0], - pubKey: stack[1] - } -} - -function decode (buffer) { - const stack = bscript.decompile(buffer) - return decodeStack(stack) -} - -module.exports = { - check: check, - decode: decode, - decodeStack: decodeStack, - encode: encode, - encodeStack: encodeStack -} +module.exports = { check } diff --git a/src/templates/pubkeyhash/output.js b/src/templates/pubkeyhash/output.js index c496275..fbb6ed1 100644 --- a/src/templates/pubkeyhash/output.js +++ b/src/templates/pubkeyhash/output.js @@ -1,8 +1,6 @@ // OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG const bscript = require('../../script') -const types = require('../../types') -const typeforce = require('typeforce') const OPS = require('bitcoin-ops') function check (script) { @@ -17,26 +15,4 @@ function check (script) { } check.toJSON = function () { return 'pubKeyHash output' } -function encode (pubKeyHash) { - typeforce(types.Hash160bit, pubKeyHash) - - return bscript.compile([ - OPS.OP_DUP, - OPS.OP_HASH160, - pubKeyHash, - OPS.OP_EQUALVERIFY, - OPS.OP_CHECKSIG - ]) -} - -function decode (buffer) { - typeforce(check, buffer) - - return buffer.slice(3, 23) -} - -module.exports = { - check: check, - decode: decode, - encode: encode -} +module.exports = { check } diff --git a/src/templates/scripthash/input.js b/src/templates/scripthash/input.js index fdea2a1..5164370 100644 --- a/src/templates/scripthash/input.js +++ b/src/templates/scripthash/input.js @@ -2,7 +2,6 @@ const Buffer = require('safe-buffer').Buffer const bscript = require('../../script') -const typeforce = require('typeforce') const p2ms = require('../multisig/') const p2pk = require('../pubkey/') @@ -46,40 +45,4 @@ function check (script, allowIncomplete) { } check.toJSON = function () { return 'scriptHash input' } -function encodeStack (redeemScriptStack, redeemScript) { - const serializedScriptPubKey = bscript.compile(redeemScript) - - return [].concat(redeemScriptStack, serializedScriptPubKey) -} - -function encode (redeemScriptSig, redeemScript) { - const redeemScriptStack = bscript.decompile(redeemScriptSig) - - return bscript.compile(encodeStack(redeemScriptStack, redeemScript)) -} - -function decodeStack (stack) { - typeforce(typeforce.Array, stack) - typeforce(check, stack) - - return { - redeemScriptStack: stack.slice(0, -1), - redeemScript: stack[stack.length - 1] - } -} - -function decode (buffer) { - const stack = bscript.decompile(buffer) - const result = decodeStack(stack) - result.redeemScriptSig = bscript.compile(result.redeemScriptStack) - delete result.redeemScriptStack - return result -} - -module.exports = { - check: check, - decode: decode, - decodeStack: decodeStack, - encode: encode, - encodeStack: encodeStack -} +module.exports = { check } diff --git a/src/templates/scripthash/output.js b/src/templates/scripthash/output.js index 68ee237..b5b6e7a 100644 --- a/src/templates/scripthash/output.js +++ b/src/templates/scripthash/output.js @@ -1,8 +1,6 @@ // OP_HASH160 {scriptHash} OP_EQUAL const bscript = require('../../script') -const types = require('../../types') -const typeforce = require('typeforce') const OPS = require('bitcoin-ops') function check (script) { @@ -15,20 +13,4 @@ function check (script) { } check.toJSON = function () { return 'scriptHash output' } -function encode (scriptHash) { - typeforce(types.Hash160bit, scriptHash) - - return bscript.compile([OPS.OP_HASH160, scriptHash, OPS.OP_EQUAL]) -} - -function decode (buffer) { - typeforce(check, buffer) - - return buffer.slice(2, 22) -} - -module.exports = { - check: check, - decode: decode, - encode: encode -} +module.exports = { check } diff --git a/src/templates/witnesspubkeyhash/input.js b/src/templates/witnesspubkeyhash/input.js index 23de02a..488e5e6 100644 --- a/src/templates/witnesspubkeyhash/input.js +++ b/src/templates/witnesspubkeyhash/input.js @@ -1,7 +1,6 @@ // {signature} {pubKey} const bscript = require('../../script') -const typeforce = require('typeforce') function isCompressedCanonicalPubKey (pubKey) { return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33 @@ -16,30 +15,4 @@ function check (script) { } check.toJSON = function () { return 'witnessPubKeyHash input' } -function encodeStack (signature, pubKey) { - typeforce({ - signature: bscript.isCanonicalScriptSignature, - pubKey: isCompressedCanonicalPubKey - }, { - signature: signature, - pubKey: pubKey - }) - - return [signature, pubKey] -} - -function decodeStack (stack) { - typeforce(typeforce.Array, stack) - typeforce(check, stack) - - return { - signature: stack[0], - pubKey: stack[1] - } -} - -module.exports = { - check: check, - decodeStack: decodeStack, - encodeStack: encodeStack -} +module.exports = { check } diff --git a/src/templates/witnesspubkeyhash/output.js b/src/templates/witnesspubkeyhash/output.js index 65ec47a..08af3bc 100644 --- a/src/templates/witnesspubkeyhash/output.js +++ b/src/templates/witnesspubkeyhash/output.js @@ -1,8 +1,6 @@ // OP_0 {pubKeyHash} const bscript = require('../../script') -const types = require('../../types') -const typeforce = require('typeforce') const OPS = require('bitcoin-ops') function check (script) { @@ -14,20 +12,6 @@ function check (script) { } check.toJSON = function () { return 'Witness pubKeyHash output' } -function encode (pubKeyHash) { - typeforce(types.Hash160bit, pubKeyHash) - - return bscript.compile([OPS.OP_0, pubKeyHash]) -} - -function decode (buffer) { - typeforce(check, buffer) - - return buffer.slice(2) -} - module.exports = { - check: check, - decode: decode, - encode: encode + check } diff --git a/src/templates/witnessscripthash/input.js b/src/templates/witnessscripthash/input.js index c7fb98f..072a289 100644 --- a/src/templates/witnessscripthash/input.js +++ b/src/templates/witnessscripthash/input.js @@ -36,29 +36,4 @@ function check (chunks, allowIncomplete) { } check.toJSON = function () { return 'witnessScriptHash input' } -function encodeStack (witnessData, witnessScript) { - typeforce({ - witnessData: [types.Buffer], - witnessScript: types.Buffer - }, { - witnessData: witnessData, - witnessScript: witnessScript - }) - - return [].concat(witnessData, witnessScript) -} - -function decodeStack (stack) { - typeforce(typeforce.Array, stack) - typeforce(check, stack) - return { - witnessData: stack.slice(0, -1), - witnessScript: stack[stack.length - 1] - } -} - -module.exports = { - check: check, - decodeStack: decodeStack, - encodeStack: encodeStack -} +module.exports = { check } diff --git a/src/templates/witnessscripthash/output.js b/src/templates/witnessscripthash/output.js index d5b3a21..c9fc21a 100644 --- a/src/templates/witnessscripthash/output.js +++ b/src/templates/witnessscripthash/output.js @@ -1,8 +1,6 @@ // OP_0 {scriptHash} const bscript = require('../../script') -const types = require('../../types') -const typeforce = require('typeforce') const OPS = require('bitcoin-ops') function check (script) { @@ -14,20 +12,4 @@ function check (script) { } check.toJSON = function () { return 'Witness scriptHash output' } -function encode (scriptHash) { - typeforce(types.Hash256bit, scriptHash) - - return bscript.compile([OPS.OP_0, scriptHash]) -} - -function decode (buffer) { - typeforce(check, buffer) - - return buffer.slice(2) -} - -module.exports = { - check: check, - decode: decode, - encode: encode -} +module.exports = { check } diff --git a/src/transaction_builder.js b/src/transaction_builder.js index 0f6b1ce..55c6e21 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -2,13 +2,13 @@ const Buffer = require('safe-buffer').Buffer const baddress = require('./address') const bcrypto = require('./crypto') const bscript = require('./script') -const btemplates = require('./templates') const networks = require('./networks') const ops = require('bitcoin-ops') const payments = require('./payments') -const SCRIPT_TYPES = btemplates.types const typeforce = require('typeforce') const types = require('./types') +const classify = require('./classify') +const SCRIPT_TYPES = classify.types const ECPair = require('./ecpair') const Transaction = require('./transaction') @@ -16,8 +16,8 @@ const Transaction = require('./transaction') function expandInput (scriptSig, witnessStack, type, scriptPubKey) { if (scriptSig.length === 0 && witnessStack.length === 0) return {} if (!type) { - let ssType = btemplates.classifyInput(scriptSig, true) - let wsType = btemplates.classifyWitness(witnessStack, true) + let ssType = classify.input(scriptSig, true) + let wsType = classify.witness(witnessStack, true) if (ssType === SCRIPT_TYPES.NONSTANDARD) ssType = undefined if (wsType === SCRIPT_TYPES.NONSTANDARD) wsType = undefined type = ssType || wsType @@ -76,7 +76,7 @@ function expandInput (scriptSig, witnessStack, type, scriptPubKey) { witness: witnessStack }) - const outputType = btemplates.classifyOutput(redeem.output) + const outputType = classify.output(redeem.output) const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output) if (!expanded.prevOutType) return {} @@ -98,7 +98,7 @@ function expandInput (scriptSig, witnessStack, type, scriptPubKey) { input: scriptSig, witness: witnessStack }) - const outputType = btemplates.classifyOutput(redeem.output) + const outputType = classify.output(redeem.output) let expanded if (outputType === SCRIPT_TYPES.P2WPKH) { expanded = expandInput(redeem.input, redeem.witness, outputType) @@ -160,7 +160,7 @@ function fixMultisigOrder (input, transaction, vin) { function expandOutput (script, ourPubKey) { typeforce(types.Buffer, script) - const type = btemplates.classifyOutput(script) + const type = classify.output(script) switch (type) { case SCRIPT_TYPES.P2PKH: { @@ -543,7 +543,7 @@ TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, options) } input.prevOutScript = options.prevOutScript - input.prevOutType = prevOutType || btemplates.classifyOutput(options.prevOutScript) + input.prevOutType = prevOutType || classify.output(options.prevOutScript) } const vin = this.__tx.addInput(txHash, vout, options.sequence, options.scriptSig) diff --git a/test/classify.js b/test/classify.js new file mode 100644 index 0000000..f56647e --- /dev/null +++ b/test/classify.js @@ -0,0 +1,157 @@ +/* global describe, it */ + +const assert = require('assert') +const bscript = require('../src/script') +const classify = require('../src/classify') + +const fixtures = require('./fixtures/templates.json') + +const multisig = require('../src/templates/multisig') +const nullData = require('../src/templates/nulldata') +const pubKey = require('../src/templates/pubkey') +const pubKeyHash = require('../src/templates/pubkeyhash') +const scriptHash = require('../src/templates/scripthash') +const witnessPubKeyHash = require('../src/templates/witnesspubkeyhash') +const witnessScriptHash = require('../src/templates/witnessscripthash') +const witnessCommitment = require('../src/templates/witnesscommitment') + +const tmap = { + pubKey, + pubKeyHash, + scriptHash, + witnessPubKeyHash, + witnessScriptHash, + multisig, + nullData, + witnessCommitment +} + +describe('classify', function () { + describe('input', function () { + fixtures.valid.forEach(function (f) { + if (!f.input) return + + it('classifies ' + f.input + ' as ' + f.type, function () { + const input = bscript.fromASM(f.input) + const type = classify.input(input) + + assert.strictEqual(type, f.type) + }) + }) + + fixtures.valid.forEach(function (f) { + if (!f.input) return + if (!f.typeIncomplete) return + + it('classifies incomplete ' + f.input + ' as ' + f.typeIncomplete, function () { + const input = bscript.fromASM(f.input) + const type = classify.input(input, true) + + assert.strictEqual(type, f.typeIncomplete) + }) + }) + }) + + describe('classifyOutput', function () { + fixtures.valid.forEach(function (f) { + if (!f.output) return + + it('classifies ' + f.output + ' as ' + f.type, function () { + const output = bscript.fromASM(f.output) + const type = classify.output(output) + + assert.strictEqual(type, f.type) + }) + }) + }) + + ;[ + 'pubKey', + 'pubKeyHash', + 'scriptHash', + 'witnessPubKeyHash', + 'witnessScriptHash', + 'multisig', + 'nullData', + 'witnessCommitment' + ].forEach(function (name) { + const inputType = tmap[name].input + const outputType = tmap[name].output + + describe(name + '.input.check', function () { + fixtures.valid.forEach(function (f) { + if (name.toLowerCase() === classify.types.P2WPKH) return + if (name.toLowerCase() === classify.types.P2WSH) return + const expected = name.toLowerCase() === f.type.toLowerCase() + + if (inputType && f.input) { + const input = bscript.fromASM(f.input) + + it('returns ' + expected + ' for ' + f.input, function () { + assert.strictEqual(inputType.check(input), expected) + }) + + if (f.typeIncomplete) { + const expectedIncomplete = name.toLowerCase() === f.typeIncomplete + + it('returns ' + expected + ' for ' + f.input, function () { + assert.strictEqual(inputType.check(input, true), expectedIncomplete) + }) + } + } + }) + + if (!(fixtures.invalid[name])) return + + fixtures.invalid[name].inputs.forEach(function (f) { + if (!f.input && !f.inputHex) return + + it('returns false for ' + f.description + ' (' + (f.input || f.inputHex) + ')', function () { + let input + + if (f.input) { + input = bscript.fromASM(f.input) + } else { + input = Buffer.from(f.inputHex, 'hex') + } + + assert.strictEqual(inputType.check(input), false) + }) + }) + }) + + describe(name + '.output.check', function () { + fixtures.valid.forEach(function (f) { + const expected = name.toLowerCase() === f.type + + if (outputType && f.output) { + it('returns ' + expected + ' for ' + f.output, function () { + const output = bscript.fromASM(f.output) + + if (name.toLowerCase() === 'nulldata' && f.type === classify.types.WITNESS_COMMITMENT) return + if (name.toLowerCase() === 'witnesscommitment' && f.type === classify.types.NULLDATA) return + assert.strictEqual(outputType.check(output), expected) + }) + } + }) + + if (!(fixtures.invalid[name])) return + + fixtures.invalid[name].outputs.forEach(function (f) { + if (!f.output && !f.outputHex) return + + it('returns false for ' + f.description + ' (' + (f.output || f.outputHex) + ')', function () { + let output + + if (f.output) { + output = bscript.fromASM(f.output) + } else { + output = Buffer.from(f.outputHex, 'hex') + } + + assert.strictEqual(outputType.check(output), false) + }) + }) + }) + }) +}) diff --git a/test/integration/bip32.js b/test/integration/bip32.js index e6d5721..3d2f12c 100644 --- a/test/integration/bip32.js +++ b/test/integration/bip32.js @@ -76,12 +76,10 @@ describe('bitcoinjs-lib (BIP32)', function () { const path = "m/49'/1'/0'/0/0" const child = root.derivePath(path) - const keyhash = bitcoin.crypto.hash160(child.publicKey) - const scriptSig = bitcoin.script.witnessPubKeyHash.output.encode(keyhash) - const addressBytes = bitcoin.crypto.hash160(scriptSig) - const outputScript = bitcoin.script.scriptHash.output.encode(addressBytes) - const address = bitcoin.address.fromOutputScript(outputScript, bitcoin.networks.testnet) - + const { address } = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ pubkey: child.publicKey, network: bitcoin.networks.testnet }), + network: bitcoin.networks.testnet + }) assert.equal(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2') }) diff --git a/test/integration/cltv.js b/test/integration/cltv.js index d508f74..626c7e9 100644 --- a/test/integration/cltv.js +++ b/test/integration/cltv.js @@ -46,8 +46,7 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { // 3 hours ago const lockTime = bip65.encode({ utc: utcNow() - (3600 * 3) }) const redeemScript = cltvCheckSigOutput(alice, bob, lockTime) - const scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - const address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) + const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest }) // fund the P2SH(CLTV) address regtestUtils.faucet(address, 1e5, function (err, unspent) { @@ -61,10 +60,15 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { // {Alice's signature} OP_TRUE const tx = txb.buildIncomplete() const signatureHash = tx.hashForSignature(0, redeemScript, hashType) - const redeemScriptSig = bitcoin.script.scriptHash.input.encode([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.opcodes.OP_TRUE - ], redeemScript) + const redeemScriptSig = bitcoin.payments.p2sh({ + redeem: { + input: bitcoin.script.compile([ + bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), + bitcoin.opcodes.OP_TRUE + ]), + output: redeemScript + } + }).input tx.setInputScript(0, redeemScriptSig) regtestUtils.broadcast(tx.toHex(), function (err) { @@ -90,8 +94,7 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { // 5 blocks from now const lockTime = bip65.encode({ blocks: height + 5 }) const redeemScript = cltvCheckSigOutput(alice, bob, lockTime) - const scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - const address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) + const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest }) // fund the P2SH(CLTV) address regtestUtils.faucet(address, 1e5, function (err, unspent) { @@ -105,10 +108,15 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { // {Alice's signature} OP_TRUE const tx = txb.buildIncomplete() const signatureHash = tx.hashForSignature(0, redeemScript, hashType) - const redeemScriptSig = bitcoin.script.scriptHash.input.encode([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.opcodes.OP_TRUE - ], redeemScript) + const redeemScriptSig = bitcoin.payments.p2sh({ + redeem: { + input: bitcoin.script.compile([ + bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), + bitcoin.opcodes.OP_TRUE + ]), + output: redeemScript + } + }).input tx.setInputScript(0, redeemScriptSig) // TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently @@ -139,8 +147,7 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { // two hours ago const lockTime = bip65.encode({ utc: utcNow() - (3600 * 2) }) const redeemScript = cltvCheckSigOutput(alice, bob, lockTime) - const scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - const address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) + const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest }) // fund the P2SH(CLTV) address regtestUtils.faucet(address, 2e5, function (err, unspent) { @@ -154,11 +161,16 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { // {Alice's signature} {Bob's signature} OP_FALSE const tx = txb.buildIncomplete() const signatureHash = tx.hashForSignature(0, redeemScript, hashType) - const redeemScriptSig = bitcoin.script.scriptHash.input.encode([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), - bitcoin.opcodes.OP_FALSE - ], redeemScript) + const redeemScriptSig = bitcoin.payments.p2sh({ + redeem: { + input: bitcoin.script.compile([ + bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), + bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.opcodes.OP_FALSE + ]), + output: redeemScript + } + }).input tx.setInputScript(0, redeemScriptSig) regtestUtils.broadcast(tx.toHex(), function (err) { @@ -181,8 +193,7 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { // two hours from now const lockTime = bip65.encode({ utc: utcNow() + (3600 * 2) }) const redeemScript = cltvCheckSigOutput(alice, bob, lockTime) - const scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - const address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) + const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest }) // fund the P2SH(CLTV) address regtestUtils.faucet(address, 2e4, function (err, unspent) { @@ -196,11 +207,16 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { // {Alice's signature} OP_TRUE const tx = txb.buildIncomplete() const signatureHash = tx.hashForSignature(0, redeemScript, hashType) - const redeemScriptSig = bitcoin.script.scriptHash.input.encode([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), - bitcoin.opcodes.OP_TRUE - ], redeemScript) + const redeemScriptSig = bitcoin.payments.p2sh({ + redeem: { + input: bitcoin.script.compile([ + bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), + bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.opcodes.OP_TRUE + ]), + output: redeemScript + } + }).input tx.setInputScript(0, redeemScriptSig) regtestUtils.broadcast(tx.toHex(), function (err) { diff --git a/test/integration/crypto.js b/test/integration/crypto.js index 78a6ab0..611b306 100644 --- a/test/integration/crypto.js +++ b/test/integration/crypto.js @@ -18,16 +18,11 @@ describe('bitcoinjs-lib (crypto)', function () { const tx = bitcoin.Transaction.fromHex('01000000020b668015b32a6178d8524cfef6dc6fc0a4751915c2e9b2ed2d2eab02424341c8000000006a47304402205e00298dc5265b7a914974c9d0298aa0e69a0ca932cb52a360436d6a622e5cd7022024bf5f506968f5f23f1835574d5afe0e9021b4a5b65cf9742332d5e4acb68f41012103fd089f73735129f3d798a657aaaa4aa62a00fa15c76b61fc7f1b27ed1d0f35b8ffffffffa95fa69f11dc1cbb77ef64f25a95d4b12ebda57d19d843333819d95c9172ff89000000006b48304502205e00298dc5265b7a914974c9d0298aa0e69a0ca932cb52a360436d6a622e5cd7022100832176b59e8f50c56631acbc824bcba936c9476c559c42a4468be98975d07562012103fd089f73735129f3d798a657aaaa4aa62a00fa15c76b61fc7f1b27ed1d0f35b8ffffffff02b000eb04000000001976a91472956eed9a8ecb19ae7e3ebd7b06cae4668696a788ac303db000000000001976a9146c0bd55dd2592287cd9992ce3ba3fc1208fb76da88ac00000000') tx.ins.forEach(function (input, vin) { - const script = input.script - const scriptChunks = bitcoin.script.decompile(script) + const { output: prevOutput, pubkey, signature } = bitcoin.payments.p2pkh({ input: input.script }) - assert(bitcoin.script.pubKeyHash.input.check(scriptChunks), 'Expected pubKeyHash script') - const prevOutScript = bitcoin.address.toOutputScript('1ArJ9vRaQcoQ29mTWZH768AmRwzb6Zif1z') - const scriptSignature = bitcoin.script.signature.decode(scriptChunks[0]) - const publicKey = bitcoin.ECPair.fromPublicKey(scriptChunks[1]) - - const m = tx.hashForSignature(vin, prevOutScript, scriptSignature.hashType) - assert(publicKey.verify(m, scriptSignature.signature), 'Invalid m') + const scriptSignature = bitcoin.script.signature.decode(signature) + const m = tx.hashForSignature(vin, prevOutput, scriptSignature.hashType) + assert(bitcoin.ECPair.fromPublicKey(pubkey).verify(m, scriptSignature.signature), 'Invalid m') // store the required information input.signature = scriptSignature.signature diff --git a/test/integration/csv.js b/test/integration/csv.js index ac0b919..e0ad02f 100644 --- a/test/integration/csv.js +++ b/test/integration/csv.js @@ -12,6 +12,7 @@ const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZs describe('bitcoinjs-lib (transactions w/ CSV)', function () { // force update MTP before(function (done) { + this.timeout(30000) regtestUtils.mine(11, done) }) @@ -44,12 +45,15 @@ describe('bitcoinjs-lib (transactions w/ CSV)', function () { // 5 blocks from now const sequence = bip68.encode({ blocks: 5 }) - const redeemScript = csvCheckSigOutput(alice, bob, sequence) - const scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - const address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) + const p2sh = bitcoin.payments.p2sh({ + redeem: { + output: csvCheckSigOutput(alice, bob, sequence) + }, + network: regtest + }) // fund the P2SH(CSV) address - regtestUtils.faucet(address, 1e5, function (err, unspent) { + regtestUtils.faucet(p2sh.address, 1e5, function (err, unspent) { if (err) return done(err) const txb = new bitcoin.TransactionBuilder(regtest) @@ -58,11 +62,18 @@ describe('bitcoinjs-lib (transactions w/ CSV)', function () { // {Alice's signature} OP_TRUE const tx = txb.buildIncomplete() - const signatureHash = tx.hashForSignature(0, redeemScript, hashType) - const redeemScriptSig = bitcoin.script.scriptHash.input.encode([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.opcodes.OP_TRUE - ], redeemScript) + const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType) + const redeemScriptSig = bitcoin.payments.p2sh({ + network: regtest, + redeem: { + network: regtest, + output: p2sh.redeem.output, + input: bitcoin.script.compile([ + bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), + bitcoin.opcodes.OP_TRUE + ]) + } + }).input tx.setInputScript(0, redeemScriptSig) // TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently @@ -92,12 +103,15 @@ describe('bitcoinjs-lib (transactions w/ CSV)', function () { // two hours after confirmation const sequence = bip68.encode({ seconds: 7168 }) - const redeemScript = csvCheckSigOutput(alice, bob, sequence) - const scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - const address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) + const p2sh = bitcoin.payments.p2sh({ + network: regtest, + redeem: { + output: csvCheckSigOutput(alice, bob, sequence) + } + }) // fund the P2SH(CSV) address - regtestUtils.faucet(address, 2e4, function (err, unspent) { + regtestUtils.faucet(p2sh.address, 2e4, function (err, unspent) { if (err) return done(err) const txb = new bitcoin.TransactionBuilder(regtest) @@ -106,12 +120,19 @@ describe('bitcoinjs-lib (transactions w/ CSV)', function () { // {Alice's signature} OP_TRUE const tx = txb.buildIncomplete() - const signatureHash = tx.hashForSignature(0, redeemScript, hashType) - const redeemScriptSig = bitcoin.script.scriptHash.input.encode([ - bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), - bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), - bitcoin.opcodes.OP_TRUE - ], redeemScript) + const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType) + const redeemScriptSig = bitcoin.payments.p2sh({ + network: regtest, + redeem: { + network: regtest, + output: p2sh.redeem.output, + input: bitcoin.script.compile([ + bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), + bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.opcodes.OP_TRUE + ]) + } + }).input tx.setInputScript(0, redeemScriptSig) regtestUtils.broadcast(tx.toHex(), function (err) { diff --git a/test/integration/transactions.js b/test/integration/transactions.js index a4a1747..5bb1342 100644 --- a/test/integration/transactions.js +++ b/test/integration/transactions.js @@ -91,7 +91,7 @@ describe('bitcoinjs-lib (transactions)', function () { const txb = new bitcoin.TransactionBuilder(regtest) const data = Buffer.from('bitcoinjs-lib', 'utf8') - const dataScript = bitcoin.script.nullData.output.encode([data]) + const dataScript = require('../../src/templates/nulldata').output.encode([data]) txb.addInput(unspent.txId, unspent.vout) txb.addOutput(dataScript, 1000) diff --git a/test/templates.js b/test/templates.js deleted file mode 100644 index 7058c51..0000000 --- a/test/templates.js +++ /dev/null @@ -1,514 +0,0 @@ -/* global describe, it */ - -const assert = require('assert') -const bcrypto = require('../src/crypto') -const bscript = require('../src/script') -const btemplates = require('../src/templates') -const ops = require('bitcoin-ops') - -const fixtures = require('./fixtures/templates.json') - -function fromHex (x) { return Buffer.from(x, 'hex') } -function toHex (x) { return x.toString('hex') } - -describe('script-templates', function () { - describe('classifyInput', function () { - fixtures.valid.forEach(function (f) { - if (!f.input) return - - it('classifies ' + f.input + ' as ' + f.type, function () { - const input = bscript.fromASM(f.input) - const type = btemplates.classifyInput(input) - - assert.strictEqual(type, f.type) - }) - }) - - fixtures.valid.forEach(function (f) { - if (!f.input) return - if (!f.typeIncomplete) return - - it('classifies incomplete ' + f.input + ' as ' + f.typeIncomplete, function () { - const input = bscript.fromASM(f.input) - const type = btemplates.classifyInput(input, true) - - assert.strictEqual(type, f.typeIncomplete) - }) - }) - }) - - describe('classifyOutput', function () { - fixtures.valid.forEach(function (f) { - if (!f.output) return - - it('classifies ' + f.output + ' as ' + f.type, function () { - const output = bscript.fromASM(f.output) - const type = btemplates.classifyOutput(output) - - assert.strictEqual(type, f.type) - }) - }) - }) - - ;[ - 'pubKey', - 'pubKeyHash', - 'scriptHash', - 'witnessPubKeyHash', - 'witnessScriptHash', - 'multisig', - 'nullData', - 'witnessCommitment' - ].forEach(function (name) { - const inputType = btemplates[name].input - const outputType = btemplates[name].output - - describe(name + '.input.check', function () { - fixtures.valid.forEach(function (f) { - if (name.toLowerCase() === btemplates.types.P2WPKH) return - if (name.toLowerCase() === btemplates.types.P2WSH) return - const expected = name.toLowerCase() === f.type.toLowerCase() - - if (inputType && f.input) { - const input = bscript.fromASM(f.input) - - it('returns ' + expected + ' for ' + f.input, function () { - assert.strictEqual(inputType.check(input), expected) - }) - - if (f.typeIncomplete) { - const expectedIncomplete = name.toLowerCase() === f.typeIncomplete - - it('returns ' + expected + ' for ' + f.input, function () { - assert.strictEqual(inputType.check(input, true), expectedIncomplete) - }) - } - } - }) - - if (!(fixtures.invalid[name])) return - - fixtures.invalid[name].inputs.forEach(function (f) { - if (!f.input && !f.inputHex) return - - it('returns false for ' + f.description + ' (' + (f.input || f.inputHex) + ')', function () { - let input - - if (f.input) { - input = bscript.fromASM(f.input) - } else { - input = Buffer.from(f.inputHex, 'hex') - } - - assert.strictEqual(inputType.check(input), false) - }) - }) - }) - - describe(name + '.output.check', function () { - fixtures.valid.forEach(function (f) { - const expected = name.toLowerCase() === f.type - - if (outputType && f.output) { - it('returns ' + expected + ' for ' + f.output, function () { - const output = bscript.fromASM(f.output) - - if (name.toLowerCase() === 'nulldata' && f.type === btemplates.types.WITNESS_COMMITMENT) return - if (name.toLowerCase() === 'witnesscommitment' && f.type === btemplates.types.NULLDATA) return - assert.strictEqual(outputType.check(output), expected) - }) - } - }) - - if (!(fixtures.invalid[name])) return - - fixtures.invalid[name].outputs.forEach(function (f) { - if (!f.output && !f.outputHex) return - - it('returns false for ' + f.description + ' (' + (f.output || f.outputHex) + ')', function () { - let output - - if (f.output) { - output = bscript.fromASM(f.output) - } else { - output = Buffer.from(f.outputHex, 'hex') - } - - assert.strictEqual(outputType.check(output), false) - }) - }) - }) - }) - - describe('pubKey.input', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'pubkey') return - - const signature = Buffer.from(f.signature, 'hex') - const input = btemplates.pubKey.input.encode(signature) - - it('encodes to ' + f.input, function () { - assert.strictEqual(bscript.toASM(input), f.input) - }) - - it('decodes to ' + f.signature, function () { - assert.deepEqual(btemplates.pubKey.input.decode(input), signature) - }) - }) - }) - - describe('pubKey.output', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'pubkey') return - - const pubKey = Buffer.from(f.pubKey, 'hex') - const output = btemplates.pubKey.output.encode(pubKey) - - it('encodes to ' + f.output, function () { - assert.strictEqual(bscript.toASM(output), f.output) - }) - - it('decodes to ' + f.pubKey, function () { - assert.deepEqual(btemplates.pubKey.output.decode(output), pubKey) - }) - }) - }) - - describe('pubKeyHash.input', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'pubkeyhash') return - - const pubKey = Buffer.from(f.pubKey, 'hex') - const signature = Buffer.from(f.signature, 'hex') - const input = btemplates.pubKeyHash.input.encode(signature, pubKey) - - it('encodes to ' + f.input, function () { - assert.strictEqual(bscript.toASM(input), f.input) - }) - - it('decodes to original arguments', function () { - assert.deepEqual(btemplates.pubKeyHash.input.decode(input), { - signature: signature, - pubKey: pubKey - }) - }) - }) - }) - - describe('pubKeyHash.output', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'pubkeyhash') return - - const pubKey = Buffer.from(f.pubKey, 'hex') - const pubKeyHash = bcrypto.hash160(pubKey) - const output = btemplates.pubKeyHash.output.encode(pubKeyHash) - - it('encodes to ' + f.output, function () { - assert.strictEqual(bscript.toASM(output), f.output) - }) - - it('decodes to ' + pubKeyHash.toString('hex'), function () { - assert.deepEqual(btemplates.pubKeyHash.output.decode(output), pubKeyHash) - }) - }) - - fixtures.invalid.pubKeyHash.outputs.forEach(function (f) { - if (!f.hash) return - const hash = Buffer.from(f.hash, 'hex') - - it('throws on ' + f.exception, function () { - assert.throws(function () { - btemplates.pubKeyHash.output.encode(hash) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('multisig.input', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'multisig' && f.typeIncomplete !== 'multisig') return - const allowIncomplete = f.typeIncomplete !== undefined - - const signatures = f.signatures.map(function (signature) { - return signature ? Buffer.from(signature, 'hex') : ops.OP_0 - }) - - const input = btemplates.multisig.input.encode(signatures) - - it('encodes to ' + f.input, function () { - assert.strictEqual(bscript.toASM(input), f.input) - }) - - it('decodes to ' + signatures.map(function (x) { return x === ops.OP_0 ? 'OP_0' : x.toString('hex') }), function () { - assert.deepEqual(btemplates.multisig.input.decode(input, allowIncomplete), signatures) - }) - }) - - fixtures.invalid.multisig.inputs.forEach(function (f) { - if (!f.output) return - const output = bscript.fromASM(f.output) - - it('throws on ' + f.exception, function () { - const signatures = f.signatures.map(function (signature) { - return signature ? Buffer.from(signature, 'hex') : ops.OP_0 - }) - - assert.throws(function () { - btemplates.multisig.input.encode(signatures, output) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('multisig.output', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'multisig') return - - const pubKeys = f.pubKeys.map(function (p) { return Buffer.from(p, 'hex') }) - const m = pubKeys.length - - const output = btemplates.multisig.output.encode(m, pubKeys) - - it('encodes ' + f.output, function () { - assert.strictEqual(bscript.toASM(output), f.output) - }) - - it('decodes to original arguments', function () { - assert.deepEqual(btemplates.multisig.output.decode(output), { - m: m, - pubKeys: pubKeys - }) - }) - }) - - fixtures.invalid.multisig.outputs.forEach(function (f) { - if (!f.pubKeys) return - const pubKeys = f.pubKeys.map(function (p) { - return Buffer.from(p, 'hex') - }) - - it('throws on ' + f.exception, function () { - assert.throws(function () { - btemplates.multisig.output.encode(f.m, pubKeys) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('scriptHash.input', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'scripthash') return - - const redeemScriptSig = bscript.fromASM(f.redeemScriptSig) - const redeemScript = bscript.fromASM(f.redeemScript) - const input = btemplates.scriptHash.input.encode(redeemScriptSig, redeemScript) - - it('encodes to ' + f.output, function () { - if (f.input) { - assert.strictEqual(bscript.toASM(input), f.input) - } else { - assert.strictEqual(input.toString('hex'), f.inputHex) - } - }) - - it('decodes to original arguments', function () { - assert.deepEqual(btemplates.scriptHash.input.decode(input), { - redeemScriptSig: redeemScriptSig, - redeemScript: redeemScript - }) - }) - }) - }) - - describe('scriptHash.output', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'scripthash') return - if (!f.output) return - - const redeemScript = bscript.fromASM(f.redeemScript) - const scriptHash = bcrypto.hash160(redeemScript) - const output = btemplates.scriptHash.output.encode(scriptHash) - - it('encodes to ' + f.output, function () { - assert.strictEqual(bscript.toASM(output), f.output) - }) - - it('decodes to ' + scriptHash.toString('hex'), function () { - assert.deepEqual(btemplates.scriptHash.output.decode(output), scriptHash) - }) - }) - - fixtures.invalid.scriptHash.outputs.forEach(function (f) { - if (!f.hash) return - const hash = Buffer.from(f.hash, 'hex') - - it('throws on ' + f.exception, function () { - assert.throws(function () { - btemplates.scriptHash.output.encode(hash) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('witnessPubKeyHash.input', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'pubkeyhash' && f.type !== 'witnesspubkeyhash') return - if (!f.inputStack) return - - const pubKey = Buffer.from(f.pubKey, 'hex') - const signature = Buffer.from(f.signature, 'hex') - - it('encodes to ' + f.input, function () { - const inputStack = btemplates.witnessPubKeyHash.input.encodeStack(signature, pubKey) - - assert.deepEqual(inputStack.map(toHex), f.inputStack) - }) - - it('decodes to original arguments', function () { - const fInputStack = f.inputStack.map(fromHex) - - assert.deepEqual(btemplates.witnessPubKeyHash.input.decodeStack(fInputStack), { - signature: signature, - pubKey: pubKey - }) - }) - }) - }) - - describe('witnessPubKeyHash.output', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'witnesspubkeyhash') return - if (!f.output) return - - const pubKey = Buffer.from(f.pubKey, 'hex') - const pubKeyHash = bcrypto.hash160(pubKey) - const output = btemplates.witnessPubKeyHash.output.encode(pubKeyHash) - - it('encodes to ' + f.output, function () { - assert.strictEqual(bscript.toASM(output), f.output) - }) - - it('decodes to ' + pubKeyHash.toString('hex'), function () { - assert.deepEqual(btemplates.witnessPubKeyHash.output.decode(output), pubKeyHash) - }) - }) - - fixtures.invalid.witnessPubKeyHash.outputs.forEach(function (f) { - if (!f.hash) return - const hash = Buffer.from(f.hash, 'hex') - - it('throws on ' + f.exception, function () { - assert.throws(function () { - btemplates.witnessPubKeyHash.output.encode(hash) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('witnessScriptHash.input', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'witnessscripthash') return - if (!f.inputStack || !f.witnessData) return - - const witnessData = f.witnessData.map(fromHex) - const witnessScript = bscript.fromASM(f.witnessScript || f.redeemScript) - - it('encodes to ' + f.input, function () { - const inputStack = btemplates.witnessScriptHash.input.encodeStack(witnessData, witnessScript) - - assert.deepEqual(inputStack.map(toHex), f.inputStack) - }) - - it('decodes to original arguments', function () { - const result = btemplates.witnessScriptHash.input.decodeStack(f.inputStack.map(fromHex)) - - assert.deepEqual(result.witnessData.map(toHex), f.witnessData) - assert.strictEqual(bscript.toASM(result.witnessScript), f.witnessScript) - }) - }) - }) - - describe('witnessScriptHash.output', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'witnessscripthash') return - if (!f.output) return - - const witnessScriptPubKey = bscript.fromASM(f.witnessScript) - const scriptHash = bcrypto.hash256(witnessScriptPubKey) - const output = btemplates.witnessScriptHash.output.encode(scriptHash) - - it('encodes to ' + f.output, function () { - assert.strictEqual(bscript.toASM(output), f.output) - }) - - it('decodes to ' + scriptHash.toString('hex'), function () { - assert.deepEqual(btemplates.witnessScriptHash.output.decode(output), scriptHash) - }) - }) - - fixtures.invalid.witnessScriptHash.outputs.forEach(function (f) { - if (!f.hash) return - const hash = Buffer.from(f.hash, 'hex') - - it('throws on ' + f.exception, function () { - assert.throws(function () { - btemplates.witnessScriptHash.output.encode(hash) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('witnessCommitment.output', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'witnesscommitment') return - if (!f.scriptPubKey) return - - const commitment = Buffer.from(f.witnessCommitment, 'hex') - const scriptPubKey = btemplates.witnessCommitment.output.encode(commitment) - - it('encodes to ' + f.scriptPubKey, function () { - assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey) - }) - - it('decodes to ' + commitment.toString('hex'), function () { - assert.deepEqual(btemplates.witnessCommitment.output.decode(scriptPubKey), commitment) - }) - }) - - fixtures.invalid.witnessCommitment.outputs.forEach(function (f) { - if (f.commitment) { - const hash = Buffer.from(f.commitment, 'hex') - it('throws on bad encode data', function () { - assert.throws(function () { - btemplates.witnessCommitment.output.encode(hash) - }, new RegExp(f.exception)) - }) - } - - if (f.scriptPubKeyHex) { - it('.decode throws on ' + f.description, function () { - assert.throws(function () { - btemplates.witnessCommitment.output.decode(Buffer.from(f.scriptPubKeyHex, 'hex')) - }, new RegExp(f.exception)) - }) - } - }) - }) - - describe('nullData.output', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'nulldata') return - - const data = f.data.map(function (x) { return Buffer.from(x, 'hex') }) - const output = btemplates.nullData.output.encode(data) - - it('encodes to ' + f.output, function () { - assert.strictEqual(bscript.toASM(output), f.output) - }) - - it('decodes to ' + f.data, function () { - assert.deepEqual(btemplates.nullData.output.decode(output), data) - }) - }) - }) -}) diff --git a/test/transaction_builder.js b/test/transaction_builder.js index e307398..34ec9da 100644 --- a/test/transaction_builder.js +++ b/test/transaction_builder.js @@ -4,8 +4,8 @@ const assert = require('assert') const baddress = require('../src/address') const bcrypto = require('../src/crypto') const bscript = require('../src/script') -const btemplates = require('../src/templates') const ops = require('bitcoin-ops') +const payments = require('../src/payments') const ECPair = require('../src/ecpair') const Transaction = require('../src/transaction') @@ -468,10 +468,17 @@ describe('TransactionBuilder', function () { const scriptSig = tx.ins[i].script // ignore OP_0 on the front, ignore redeemScript - const signatures = bscript.decompile(scriptSig).slice(1, -1).filter(function (x) { return x !== ops.OP_0 }) + const signatures = bscript.decompile(scriptSig) + .slice(1, -1) + .filter(x => x !== ops.OP_0) // rebuild/replace the scriptSig without them - const replacement = btemplates.scriptHash.input.encode(btemplates.multisig.input.encode(signatures), redeemScript) + const replacement = payments.p2sh({ + redeem: payments.p2ms({ + output: redeemScript, + signatures + }, { allowIncomplete: true }) + }).input assert.strictEqual(bscript.toASM(replacement), sign.scriptSigFiltered) tx.ins[i].script = replacement