Browse Source

Merge pull request #1119 from bitcoinjs/templates

rm Templates exports
addLowRGrinding
Jonathan Underwood 7 years ago
committed by GitHub
parent
commit
d338f7cd64
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      src/address.js
  2. 32
      src/classify.js
  3. 4
      src/index.js
  4. 35
      src/payments/p2pkh.js
  5. 15
      src/payments/p2sh.js
  6. 42
      src/payments/p2wpkh.js
  7. 41
      src/payments/p2wsh.js
  8. 51
      src/templates/multisig/input.js
  9. 37
      src/templates/multisig/output.js
  10. 27
      src/templates/pubkey/input.js
  11. 20
      src/templates/pubkey/output.js
  12. 40
      src/templates/pubkeyhash/input.js
  13. 26
      src/templates/pubkeyhash/output.js
  14. 39
      src/templates/scripthash/input.js
  15. 20
      src/templates/scripthash/output.js
  16. 29
      src/templates/witnesspubkeyhash/input.js
  17. 18
      src/templates/witnesspubkeyhash/output.js
  18. 27
      src/templates/witnessscripthash/input.js
  19. 20
      src/templates/witnessscripthash/output.js
  20. 16
      src/transaction_builder.js
  21. 157
      test/classify.js
  22. 10
      test/integration/bip32.js
  23. 68
      test/integration/cltv.js
  24. 13
      test/integration/crypto.js
  25. 59
      test/integration/csv.js
  26. 2
      test/integration/transactions.js
  27. 514
      test/templates.js
  28. 13
      test/transaction_builder.js

22
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
}
}
}

32
src/templates/index.js → 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
}

4
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'),

35
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)

15
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

42
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')
}
}

41
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
}

51
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 }

37
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 }

27
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
}

20
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 }

40
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 }

26
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 }

39
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 }

20
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 }

29
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 }

18
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
}

27
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 }

20
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 }

16
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)

157
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)
})
})
})
})
})

10
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')
})

68
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) {

13
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

59
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) {

2
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)

514
test/templates.js

@ -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)
})
})
})
})

13
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

Loading…
Cancel
Save