diff --git a/src/transaction_builder.js b/src/transaction_builder.js index 9abf242..e63bdd0 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -13,6 +13,33 @@ const transaction_1 = require('./transaction'); const types = require('./types'); const typeforce = require('typeforce'); const SCRIPT_TYPES = classify.types; +const PREVOUT_TYPES = new Set([ + // Raw + 'p2pkh', + 'p2pk', + 'p2wpkh', + 'p2ms', + // P2SH wrapped + 'p2sh-p2pkh', + 'p2sh-p2pk', + 'p2sh-p2wpkh', + 'p2sh-p2ms', + // P2WSH wrapped + 'p2wsh-p2pkh', + 'p2wsh-p2pk', + 'p2wsh-p2ms', + // P2SH-P2WSH wrapper + 'p2sh-p2wsh-p2pkh', + 'p2sh-p2wsh-p2pk', + 'p2sh-p2wsh-p2ms', +]); +function tfMessage(type, value, message) { + try { + typeforce(type, value); + } catch (err) { + throw new Error(message); + } +} function txIsString(tx) { return typeof tx === 'string' || tx instanceof String; } @@ -118,74 +145,29 @@ class TransactionBuilder { buildIncomplete() { return this.__build(true); } - sign(vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) { - // TODO: remove keyPair.network matching in 4.0.0 - if (keyPair.network && keyPair.network !== this.network) - throw new TypeError('Inconsistent network'); - if (!this.__INPUTS[vin]) throw new Error('No input at index: ' + vin); - hashType = hashType || transaction_1.Transaction.SIGHASH_ALL; - if (this.__needsOutputs(hashType)) - throw new Error('Transaction needs outputs'); - const input = this.__INPUTS[vin]; - // if redeemScript was previously provided, enforce consistency - if ( - input.redeemScript !== undefined && - redeemScript && - !input.redeemScript.equals(redeemScript) - ) { - throw new Error('Inconsistent redeemScript'); - } - const ourPubKey = keyPair.publicKey || keyPair.getPublicKey(); - if (!canSign(input)) { - if (witnessValue !== undefined) { - if (input.value !== undefined && input.value !== witnessValue) - throw new Error('Input did not match witnessValue'); - typeforce(types.Satoshi, witnessValue); - input.value = witnessValue; - } - if (!canSign(input)) { - const prepared = prepareInput( - input, - ourPubKey, - redeemScript, - witnessScript, - ); - // updates inline - Object.assign(input, prepared); - } - if (!canSign(input)) throw Error(input.prevOutType + ' not supported'); - } - // ready to sign - let signatureHash; - if (input.hasWitness) { - signatureHash = this.__TX.hashForWitnessV0( - vin, - input.signScript, - input.value, + sign( + signParams, + keyPair, + redeemScript, + hashType, + witnessValue, + witnessScript, + ) { + trySign( + getSigningData( + this.network, + this.__INPUTS, + this.__needsOutputs.bind(this), + this.__TX, + signParams, + keyPair, + redeemScript, hashType, - ); - } else { - signatureHash = this.__TX.hashForSignature( - vin, - input.signScript, - hashType, - ); - } - // enforce in order signing of public keys - const signed = input.pubkeys.some((pubKey, i) => { - if (!ourPubKey.equals(pubKey)) return false; - if (input.signatures[i]) throw new Error('Signature already exists'); - // TODO: add tests - if (ourPubKey.length !== 33 && input.hasWitness) { - throw new Error( - 'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH', - ); - } - const signature = keyPair.sign(signatureHash, this.__USE_LOW_R); - input.signatures[i] = bscript.signature.encode(signature, hashType); - return true; - }); - if (!signed) throw new Error('Key pair cannot sign for this input'); + witnessValue, + witnessScript, + this.__USE_LOW_R, + ), + ); } __addInputUnsafe(txHash, vout, options) { if (transaction_1.Transaction.isCoinbaseHash(txHash)) { @@ -746,3 +728,331 @@ function canSign(input) { function signatureHashType(buffer) { return buffer.readUInt8(buffer.length - 1); } +function checkSignArgs(inputs, signParams) { + if (!PREVOUT_TYPES.has(signParams.prevOutScriptType)) { + throw new TypeError( + `Unknown prevOutScriptType "${signParams.prevOutScriptType}"`, + ); + } + tfMessage( + typeforce.Number, + signParams.vin, + `sign must include vin parameter as Number (input index)`, + ); + tfMessage( + types.Signer, + signParams.keyPair, + `sign must include keyPair parameter as Signer interface`, + ); + tfMessage( + typeforce.maybe(typeforce.Number), + signParams.hashType, + `sign hashType parameter must be a number`, + ); + const prevOutType = (inputs[signParams.vin] || []).prevOutType; + const posType = signParams.prevOutScriptType; + switch (posType) { + case 'p2pkh': + if (prevOutType && prevOutType !== 'pubkeyhash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2pkh: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2pk': + if (prevOutType && prevOutType !== 'pubkey') { + throw new TypeError( + `input #${signParams.vin} is not of type p2pk: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2wpkh': + if (prevOutType && prevOutType !== 'witnesspubkeyhash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2wpkh: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2ms': + if (prevOutType && prevOutType !== 'multisig') { + throw new TypeError( + `input #${signParams.vin} is not of type p2ms: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2sh-p2wpkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2sh-p2wpkh: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2sh-p2ms': + case 'p2sh-p2pk': + case 'p2sh-p2pkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2wsh-p2ms': + case 'p2wsh-p2pk': + case 'p2wsh-p2pkh': + if (prevOutType && prevOutType !== 'witnessscripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.Buffer, + signParams.witnessScript, + `${posType} requires witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2sh-p2wsh-p2ms': + case 'p2sh-p2wsh-p2pk': + case 'p2sh-p2wsh-p2pkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.Buffer, + signParams.witnessScript, + `${posType} requires witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires witnessScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessScript`, + ); + break; + } +} +function trySign({ + input, + ourPubKey, + keyPair, + signatureHash, + hashType, + useLowR, +}) { + // enforce in order signing of public keys + let signed = false; + for (const [i, pubKey] of input.pubkeys.entries()) { + if (!ourPubKey.equals(pubKey)) continue; + if (input.signatures[i]) throw new Error('Signature already exists'); + // TODO: add tests + if (ourPubKey.length !== 33 && input.hasWitness) { + throw new Error( + 'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH', + ); + } + const signature = keyPair.sign(signatureHash, useLowR); + input.signatures[i] = bscript.signature.encode(signature, hashType); + signed = true; + } + if (!signed) throw new Error('Key pair cannot sign for this input'); +} +function getSigningData( + network, + inputs, + needsOutputs, + tx, + signParams, + keyPair, + redeemScript, + hashType, + witnessValue, + witnessScript, + useLowR, +) { + let vin; + if (typeof signParams === 'number') { + console.warn( + 'DEPRECATED: TransactionBuilder sign method arguments ' + + 'will change in v6, please use the TxbSignArg interface', + ); + vin = signParams; + } else if (typeof signParams === 'object') { + checkSignArgs(inputs, signParams); + ({ + vin, + keyPair, + redeemScript, + hashType, + witnessValue, + witnessScript, + } = signParams); + } else { + throw new TypeError( + 'TransactionBuilder sign first arg must be TxbSignArg or number', + ); + } + if (keyPair === undefined) { + throw new Error('sign requires keypair'); + } + // TODO: remove keyPair.network matching in 4.0.0 + if (keyPair.network && keyPair.network !== network) + throw new TypeError('Inconsistent network'); + if (!inputs[vin]) throw new Error('No input at index: ' + vin); + hashType = hashType || transaction_1.Transaction.SIGHASH_ALL; + if (needsOutputs(hashType)) throw new Error('Transaction needs outputs'); + const input = inputs[vin]; + // if redeemScript was previously provided, enforce consistency + if ( + input.redeemScript !== undefined && + redeemScript && + !input.redeemScript.equals(redeemScript) + ) { + throw new Error('Inconsistent redeemScript'); + } + const ourPubKey = + keyPair.publicKey || (keyPair.getPublicKey && keyPair.getPublicKey()); + if (!canSign(input)) { + if (witnessValue !== undefined) { + if (input.value !== undefined && input.value !== witnessValue) + throw new Error('Input did not match witnessValue'); + typeforce(types.Satoshi, witnessValue); + input.value = witnessValue; + } + if (!canSign(input)) { + const prepared = prepareInput( + input, + ourPubKey, + redeemScript, + witnessScript, + ); + // updates inline + Object.assign(input, prepared); + } + if (!canSign(input)) throw Error(input.prevOutType + ' not supported'); + } + // ready to sign + let signatureHash; + if (input.hasWitness) { + signatureHash = tx.hashForWitnessV0( + vin, + input.signScript, + input.value, + hashType, + ); + } else { + signatureHash = tx.hashForSignature(vin, input.signScript, hashType); + } + return { + input, + ourPubKey, + keyPair, + signatureHash, + hashType, + useLowR: !!useLowR, + }; +} diff --git a/src/types.js b/src/types.js index be95266..8bcee2c 100644 --- a/src/types.js +++ b/src/types.js @@ -13,6 +13,14 @@ exports.BIP32Path = BIP32Path; BIP32Path.toJSON = () => { return 'BIP32 derivation path'; }; +function Signer(obj) { + return ( + (typeforce.Buffer(obj.publicKey) || + typeof obj.getPublicKey === 'function') && + typeof obj.sign === 'function' + ); +} +exports.Signer = Signer; const SATOSHI_MAX = 21 * 1e14; function Satoshi(value) { return typeforce.UInt53(value) && value <= SATOSHI_MAX; diff --git a/test/fixtures/transaction_builder.json b/test/fixtures/transaction_builder.json index 24be3ba..07226b0 100644 --- a/test/fixtures/transaction_builder.json +++ b/test/fixtures/transaction_builder.json @@ -11,6 +11,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" } ] @@ -34,6 +35,7 @@ "prevTxScript": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 OP_CHECKSIG", "signs": [ { + "prevOutScriptType": "p2pk", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" } ] @@ -56,6 +58,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "redeemScript": "OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG" } @@ -80,11 +83,14 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG" }, { - "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG" } ] } @@ -108,9 +114,11 @@ "prevTxScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", "signs": [ { + "prevOutScriptType": "p2ms", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" }, { + "prevOutScriptType": "p2ms", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" } ] @@ -135,9 +143,11 @@ "prevTxScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", "signs": [ { + "prevOutScriptType": "p2ms", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" }, { + "prevOutScriptType": "p2ms", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" } ] @@ -162,9 +172,11 @@ "prevTxScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", "signs": [ { + "prevOutScriptType": "p2ms", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" }, { + "prevOutScriptType": "p2ms", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" } ] @@ -188,11 +200,14 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG" }, { - "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe" + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG" } ] } @@ -215,12 +230,15 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", "hashType": 1 }, { + "prevOutScriptType": "p2sh-p2ms", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", "hashType": 2 } ] @@ -244,6 +262,7 @@ "prevTxScript": "OP_HASH160 e89677d91455e541630d62c63718bef738b478b1 OP_EQUAL", "signs": [ { + "prevOutScriptType": "p2sh-p2pk", "keyPair": "KxLDMPtVM7sLSu2v5n1LybDibw6P9FFbL4pUwJ51UDm7rp5AmXWW", "redeemScript": "033e29aea1168a835d5e386c292082db7b7807172a10ec634ad34226f36d79e70f OP_CHECKSIG" } @@ -267,6 +286,7 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" } ] @@ -291,6 +311,7 @@ "sequence": 2147001, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" } ] @@ -314,6 +335,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" } ] @@ -337,6 +359,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "cQ6483mDWwoG8o4tn6nU9Jg52RKMjPUWXSY1vycAyPRXQJ1Pn2Rq" } ] @@ -359,6 +382,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" } ] @@ -381,6 +405,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" } ] @@ -404,6 +429,7 @@ "prevTxScript": "OP_0 751e76e8199196d454941c45d1b3a323f1433bd6", "signs": [ { + "prevOutScriptType": "p2wpkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "value": 10000 } @@ -428,6 +454,7 @@ "prevTxScript": "OP_0 0f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210", "signs": [ { + "prevOutScriptType": "p2wsh-p2pk", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "witnessScript": "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_CHECKSIG", "value": 80000 @@ -452,9 +479,9 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 3, - "value": 30000000 + "hashType": 3 } ], "sequence": 4294967295, @@ -482,9 +509,9 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 1, - "value": 40000 + "hashType": 1 } ], "sequence": 4294967295, @@ -495,9 +522,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 1, - "value": 40000 + "hashType": 1 } ], "sequence": 4294967295, @@ -508,9 +535,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 1, - "value": 40000 + "hashType": 1 } ], "sequence": 4294967295, @@ -542,9 +569,9 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 129, - "value": 40000 + "hashType": 129 } ], "sequence": 4294967295, @@ -555,9 +582,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 129, - "value": 40000 + "hashType": 129 } ], "sequence": 4294967295, @@ -568,9 +595,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 129, - "value": 40000 + "hashType": 129 } ], "sequence": 4294967295, @@ -602,9 +629,9 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 3, - "value": 40000 + "hashType": 3 } ], "sequence": 4294967295, @@ -615,9 +642,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 3, - "value": 40000 + "hashType": 3 } ], "sequence": 4294967295, @@ -628,9 +655,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 3, - "value": 40000 + "hashType": 3 } ], "sequence": 4294967295, @@ -662,9 +689,9 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 131, - "value": 40000 + "hashType": 131 } ], "sequence": 4294967295, @@ -675,9 +702,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 131, - "value": 40000 + "hashType": 131 } ], "sequence": 4294967295, @@ -688,9 +715,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 131, - "value": 40000 + "hashType": 131 } ], "sequence": 4294967295, @@ -722,9 +749,9 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 2, - "value": 40000 + "hashType": 2 } ], "sequence": 4294967295, @@ -735,9 +762,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 2, - "value": 40000 + "hashType": 2 } ], "sequence": 4294967295, @@ -748,9 +775,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 2, - "value": 40000 + "hashType": 2 } ], "sequence": 4294967295, @@ -782,9 +809,9 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 130, - "value": 40000 + "hashType": 130 } ], "sequence": 4294967295, @@ -795,9 +822,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 130, - "value": 40000 + "hashType": 130 } ], "sequence": 4294967295, @@ -808,9 +835,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", - "hashType": 130, - "value": 40000 + "hashType": 130 } ], "sequence": 4294967295, @@ -842,9 +869,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pk", "keyPair": "L3Wh2WPg21MWqzMFYsVC7PeBXcq1ow32KRccRihnTUnAhJaZUvg1", - "hashType": 1, - "value": 625000000 + "hashType": 1 } ], "sequence": 4294967278, @@ -855,6 +882,7 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2wpkh", "keyPair": "KzVTBhbMaKrAYagJ11VdTaBrb6yzLykLGyuMBkf9sCFPDxdT8shL", "hashType": 1, "value": 600000000 @@ -886,6 +914,7 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2sh-p2wpkh", "keyPair": "L57KYn5isHFThD4cohjJgLTZA2vaxnMMKWngnzbttF159yH9dARf", "hashType": 1, "redeemScript": "OP_0 79091972186c449eb1ded22b78e40d009bdf0089", @@ -909,7 +938,7 @@ "locktime": 1170 }, { - "description": "Sighash V1: P2WSH(P2SH(P2MS 6/6))", + "description": "Sighash V1: P2SH(P2WSH(P2MS 6/6))", "txHex": "0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff02e6312761010000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac583e0f00000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac0800483045022100f902f491c4df15199e584790ae8c7202569a977accac0a09fa3f4f3b6ec3517602205961a951c4a12fa966da67b6fd75975b9de156b9895f8ab5f289ecaee12b9b3501473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502483045022100bd5294e145d729e9593f49079b74e6e4b8aeba63440408595ce0949d5c6450a702207f9c9fb45907fe0180d3f4bee499006007bb90894b5f824a26dfa5d3afec543303483045022100febf9409d7f3c091ddc4d296a483aae7b3d2a91d38f6ea2a153f7ff085fe7766022078d11972c74cd78f816152463a5e1a5d986dfb94b55cf5f7242e4f6d5df000ff81483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a088247304402201a0e125aed6a700e45d6c86017d5a9d2264c8079319d868f3f163f5d63cb5bfe02200887608f2322ca0d82df67316275371028b0b21750417d594117963fe23b67ec83cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000", "version": 1, "inputs": [ @@ -918,6 +947,7 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "L15NqbRvcqso8ZCqD8aFaZV3CTypw6svjk8oCWsAfMmNViahS2Mw", "hashType": 1, "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", @@ -925,6 +955,7 @@ "value": 987654321 }, { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "Kwpf3fycToLH1ymZUkezFrYwTjhKaucHD861Ft5A4Tih855LBxVx", "hashType": 2, "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", @@ -932,6 +963,7 @@ "value": 987654321 }, { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "L1EV111k2WzNTapY2etd1TaB2aWbjUgouko9YyipS2S8H8WdGkQi", "hashType": 3, "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", @@ -939,6 +971,7 @@ "value": 987654321 }, { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "KwuvEmpBtJaw8SQLnpi3CoEHZJvv33EnYBHn13VcDuwprJqmkfSH", "hashType": 129, "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", @@ -946,6 +979,7 @@ "value": 987654321 }, { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "L5kdM8eWyfj8pdRDWA8j5SmBwAQt2yyhqjb2ZZQxtRGJfCquC6TB", "hashType": 130, "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", @@ -953,6 +987,7 @@ "value": 987654321 }, { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "KyT4JbJVRy5FZ6ZEZhkaocP2JSBXiF7X3Cx6DBAGLrydR9fiXQUK", "hashType": 131, "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", @@ -986,9 +1021,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pk", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", - "hashType": 1, - "value": 80000 + "hashType": 1 } ], "sequence": 4294967295, @@ -1013,10 +1048,10 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2pk", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "hashType": 1, - "redeemScript": "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_CHECKSIG", - "value": 80000 + "redeemScript": "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_CHECKSIG" } ], "sequence": 4294967295, @@ -1041,6 +1076,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2wsh-p2pk", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "hashType": 1, "witnessScript": "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_CHECKSIG", @@ -1069,6 +1105,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2wsh-p2pk", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "hashType": 1, "redeemScript": "OP_0 0f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210", @@ -1098,9 +1135,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", - "hashType": 1, - "value": 80000 + "hashType": 1 } ], "sequence": 4294967295, @@ -1125,10 +1162,10 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2pkh", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "hashType": 1, - "redeemScript": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", - "value": 80000 + "redeemScript": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" } ], "sequence": 4294967295, @@ -1153,6 +1190,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2wsh-p2pkh", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "hashType": 1, "witnessScript": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", @@ -1181,6 +1219,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2wsh-p2pkh", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "hashType": 1, "witnessScript": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", @@ -1210,9 +1249,9 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2ms", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", - "hashType": 1, - "value": 80000 + "hashType": 1 } ], "sequence": 4294967295, @@ -1237,10 +1276,10 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "hashType": 1, - "redeemScript": "OP_1 038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_1 OP_CHECKMULTISIG", - "value": 80000 + "redeemScript": "OP_1 038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_1 OP_CHECKMULTISIG" } ], "sequence": 4294967295, @@ -1265,6 +1304,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2wsh-p2ms", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "hashType": 1, "witnessScript": "OP_1 038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_1 OP_CHECKMULTISIG", @@ -1293,6 +1333,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "hashType": 1, "witnessScript": "OP_1 038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_1 OP_CHECKMULTISIG", @@ -1322,6 +1363,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2wpkh", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "hashType": 1, "value": 80000 @@ -1349,6 +1391,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2wpkh", "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", "hashType": 1, "redeemScript": "OP_0 851a33a5ef0d4279bd5854949174e2c65b1d4500", @@ -1377,6 +1420,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" } ] @@ -1402,6 +1446,7 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "cRAwuVuVSBZMPu7hdrYvMCZ8eevzmkExjFbaBLhqnDdrezxN3nTS", "witnessScript": "OP_2 02bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e2 02d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea19 OP_2 OP_CHECKMULTISIG", "redeemScript": "OP_0 24376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5af", @@ -1430,12 +1475,14 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "cRAwuVuVSBZMPu7hdrYvMCZ8eevzmkExjFbaBLhqnDdrezxN3nTS", "witnessScript": "OP_2 02bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e2 02d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea19 OP_2 OP_CHECKMULTISIG", "redeemScript": "OP_0 24376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5af", "value": 100000 }, { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "cTUFsNeVd8TKU4yREN8nMdViNnHyNvCCYVRmRUmkMLgomiMWTiii", "witnessScript": "OP_2 02bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e2 02d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea19 OP_2 OP_CHECKMULTISIG", "redeemScript": "OP_0 24376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5af", @@ -1453,7 +1500,7 @@ ] }, { - "description": "P2WSH(P2MS 2/3) -> P2PKH", + "description": "P2SH(P2WSH(P2MS 2/3)) -> P2PKH", "network": "testnet", "txHex": "01000000000101ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01000000232200201b48bf145648b9492ecd6d76754ea3def4b90e22e4ef7aee9ca291b2de455701ffffffff01f07e0e00000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac0400473044022036c9ecb03cb04c09be1f52766725dcfe9a815973bd2f34ce19a345f2d925a45502207b90737852d2508db104ad17612de473687e67928c045555a1ed8d495c0570d901483045022100aec0e58e4e597b35ca5a727702a0da3d4f2ef4759914da7fc80aecb3c479a6d902201ec27ea8dcca4b73ee81e4b627f52f9e627c3497f61e4beeb98f86e02979640a0169522103c411cf39aca4395c81c35921dc832a0d1585d652ab1b52ccc619ff9fbbc5787721020636d944458a4663b75a912c37dc1cd59b11f9a00106783a65ba230d929b96b02102d1448cbf19528a1a27e5958ba73d930b5b3facdbe5c30c7094951a287fcc914953ae00000000", "version": 1, @@ -1466,6 +1513,7 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "cUxccFVBdJRq6HnyxiFMd8Z15GLThXaNLcnPBgoXLEv9iX6wuV2b", "witnessScript": "OP_2 03c411cf39aca4395c81c35921dc832a0d1585d652ab1b52ccc619ff9fbbc57877 020636d944458a4663b75a912c37dc1cd59b11f9a00106783a65ba230d929b96b0 02d1448cbf19528a1a27e5958ba73d930b5b3facdbe5c30c7094951a287fcc9149 OP_3 OP_CHECKMULTISIG", "redeemScript": "OP_0 1b48bf145648b9492ecd6d76754ea3def4b90e22e4ef7aee9ca291b2de455701", @@ -1473,6 +1521,7 @@ "stage": true }, { + "prevOutScriptType": "p2sh-p2wsh-p2ms", "keyPair": "cVSNe9ZdZRsRvEBL8YRR7YiZmH4cLsf5FthgERWkZezJVrGseaXy", "witnessScript": "OP_2 03c411cf39aca4395c81c35921dc832a0d1585d652ab1b52ccc619ff9fbbc57877 020636d944458a4663b75a912c37dc1cd59b11f9a00106783a65ba230d929b96b0 02d1448cbf19528a1a27e5958ba73d930b5b3facdbe5c30c7094951a287fcc9149 OP_3 OP_CHECKMULTISIG", "redeemScript": "OP_0 1b48bf145648b9492ecd6d76754ea3def4b90e22e4ef7aee9ca291b2de455701", @@ -1561,6 +1610,7 @@ "scriptSigAfter": "OP_0 OP_0 OP_0 30440220793d87f2a8afeb856816efa38984418c692c15170e99ca371f547454079c0dd3022074ae95e438fee1f37619fabe0ce1083c3be0d65c3defb5337833d50fdc694b1301 52210258db1bb3801f1ecde47602143beaeb9cac93251724b8e589fae5c08c1a399a9121038e803e3d84cfc821cc8bf46233a9c2bb359d529db0bcdd3f1a4f38678dd02d7f2103b83e59d848407d7f62a82c99905f5ca3e8e8f5d6400eb78a0b4b067aea0720d953ae", "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "keyPair": "cTkcnMZoFYH1UgumzCFHv2veLMNN1PaJyHHUxFT127zhNGBqqEZ2", "redeemScript": "OP_2 0258db1bb3801f1ecde47602143beaeb9cac93251724b8e589fae5c08c1a399a91 038e803e3d84cfc821cc8bf46233a9c2bb359d529db0bcdd3f1a4f38678dd02d7f 03b83e59d848407d7f62a82c99905f5ca3e8e8f5d6400eb78a0b4b067aea0720d9 OP_3 OP_CHECKMULTISIG" } @@ -1585,11 +1635,13 @@ "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 0, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "scriptSig": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" }, { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 1, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", "scriptSig": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 3045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b0657601 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" @@ -1616,11 +1668,13 @@ "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 1, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", "scriptSig": "OP_0 OP_0 3045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b0657601 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" }, { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 0, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "scriptSig": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 3045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b0657601 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" @@ -1647,11 +1701,13 @@ "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 0, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "scriptSig": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" }, { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 1, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", "scriptSig": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 3045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b0657601 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" @@ -1678,11 +1734,13 @@ "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 0, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" }, { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 1, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 3045022100ae06d8269b79b5cfa0297d1d88261b0061e52fc2814948c3aa05fa78ee76894302206e0c79a5c90569d8c72a542ef9a06471cbbcd2c651b312339983dfba4f8ff46301 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" @@ -1709,11 +1767,13 @@ "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 0, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" }, { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 2, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" @@ -1740,11 +1800,13 @@ "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 2, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", "scriptSig": "OP_0 OP_0 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" }, { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 0, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" @@ -1771,11 +1833,13 @@ "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 0, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" }, { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 2, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" @@ -1802,11 +1866,13 @@ "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 2, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", "scriptSig": "OP_0 OP_0 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" }, { + "prevOutScriptType": "p2sh-p2ms", "pubKeyIndex": 0, "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "scriptSigBefore": "OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae", @@ -1863,6 +1929,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzBQVXYUGDAvqG7VeU3C7ZMRYiwtsxSVVFcYGzKU9E4aUVDUquZU" } ] @@ -1900,6 +1967,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" } ] @@ -1927,6 +1995,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG" } @@ -1980,10 +2049,11 @@ "network": "testnet", "inputs": [ { - "txHex": "0100000001f7e6430096cd2790bac115aaab22c0a50fb0a1794305302e1a399e81d8d354f4020000006a47304402205793a862d193264afc32713e2e14541e1ff9ebb647dd7e7e6a0051d0faa87de302205216653741ecbbed573ea2fc053209dd6980616701c27be5b958a159fc97f45a012103e877e7deb32d19250dcfe534ea82c99ad739800295cd5429a7f69e2896c36fcdfeffffff0340420f00000000001976a9145c7b8d623fba952d2387703d051d8e931a6aa0a188ac8bda2702000000001976a9145a0ef60784137d03e7868d063b05424f2f43799f88ac40420f00000000001976a9145c7b8d623fba952d2387703d051d8e931a6aa0a188ac2fcc0e00", + "txHex": "01000000000101f7e6430096cd2790bac115aaab22c0a50fb0a1794305302e1a399e81d8d354f40200000000feffffff0340420f00000000001600145c7b8d623fba952d2387703d051d8e931a6aa0a18bda2702000000001976a9145a0ef60784137d03e7868d063b05424f2f43799f88ac40420f00000000001976a9145c7b8d623fba952d2387703d051d8e931a6aa0a188ac0247304402205793a862d193264afc32713e2e14541e1ff9ebb647dd7e7e6a0051d0faa87de302205216653741ecbbed573ea2fc053209dd6980616701c27be5b958a159fc97f45a012103e877e7deb32d19250dcfe534ea82c99ad739800295cd5429a7f69e2896c36fcd2fcc0e00", "vout": 0, "signs": [ { + "prevOutScriptType": "p2wpkh", "keyPair": "cQ6483mDWwoG8o4tn6nU9Jg52RKMjPUWXSY1vycAyPRXQJ1Pn2Rq", "throws": true, "value": 22500000000 @@ -2007,9 +2077,11 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" }, { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "throws": true } @@ -2033,6 +2105,7 @@ "prevTxScript": "OP_0 15a71ffa7b5bb70cddefcf364494071022efe390", "signs": [ { + "prevOutScriptType": "p2wpkh", "keyPair": "5JiHJJjdufSiMxbvnyNcKtQNLYH6SvUpQnRv9yZENFDWTQKQkzC", "value": 10000, "throws": true @@ -2057,6 +2130,7 @@ "prevTxScript": "OP_0 5339df4de3854c4208376443ed075014ad996aa349ad6b5abf6c4d20f604d348", "signs": [ { + "prevOutScriptType": "p2wsh-p2pk", "keyPair": "5JiHJJjdufSiMxbvnyNcKtQNLYH6SvUpQnRv9yZENFDWTQKQkzC", "witnessScript": "04f56d09b32cefc818735150bf8560eefdaf30d2edb3fe557bf27682aedaed81bf9aaff7eeb496e088058ec548826c12b521dbb566a862d9b67677910c2b421e06 OP_CHECKSIG", "value": 80000, @@ -2082,6 +2156,7 @@ "prevTxScript": "OP_HASH160 5afe12b2827e3eac05fe3f17c59406ef262aa177 OP_EQUAL", "signs": [ { + "prevOutScriptType": "p2sh-p2wsh-p2pk", "keyPair": "5JiHJJjdufSiMxbvnyNcKtQNLYH6SvUpQnRv9yZENFDWTQKQkzC", "redeemScript": "OP_0 5339df4de3854c4208376443ed075014ad996aa349ad6b5abf6c4d20f604d348", "witnessScript": "04f56d09b32cefc818735150bf8560eefdaf30d2edb3fe557bf27682aedaed81bf9aaff7eeb496e088058ec548826c12b521dbb566a862d9b67677910c2b421e06 OP_CHECKSIG", @@ -2106,6 +2181,7 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2sh-p2pk", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "redeemScript": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", "throws": true @@ -2121,7 +2197,7 @@ ] }, { - "exception": "PrevOutScript is scripthash, requires redeemScript", + "exception": "p2sh-p2pkh requires redeemScript", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", @@ -2129,6 +2205,7 @@ "prevTxScript": "OP_HASH160 7f67f0521934a57d3039f77f9f32cf313f3ac74b OP_EQUAL", "signs": [ { + "prevOutScriptType": "p2sh-p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "throws": true } @@ -2143,7 +2220,7 @@ ] }, { - "exception": "PrevOutScript is witnessscripthash, requires witnessScript", + "exception": "p2wsh-p2pk requires witnessScript", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", @@ -2151,6 +2228,7 @@ "prevTxScript": "OP_0 0f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210", "signs": [ { + "prevOutScriptType": "p2wsh-p2pk", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "throws": true } @@ -2173,10 +2251,12 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" }, { + "prevOutScriptType": "p2sh-p2ms", "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", "throws": true @@ -2201,9 +2281,9 @@ "prevTxScript": "OP_HASH160 ffffffffffffffffffffffffffffffffffffffff OP_EQUAL", "signs": [ { + "prevOutScriptType": "p2sh-p2pkh", "keyPair": "5JiHJJjdufSiMxbvnyNcKtQNLYH6SvUpQnRv9yZENFDWTQKQkzC", "redeemScript": "OP_1", - "value": 10000, "throws": true } ] @@ -2226,6 +2306,7 @@ "prevTxScript": "OP_0 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "signs": [ { + "prevOutScriptType": "p2wsh-p2pkh", "keyPair": "5JiHJJjdufSiMxbvnyNcKtQNLYH6SvUpQnRv9yZENFDWTQKQkzC", "witnessScript": "OP_1", "value": 10000, @@ -2249,6 +2330,7 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2sh-p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "redeemScript": "OP_HASH160 7f67f0521934a57d3039f77f9f32cf313f3ac74b OP_EQUAL", "throws": true @@ -2264,7 +2346,7 @@ ] }, { - "exception": "PrevOutScript must be P2SH", + "exception": "input #0 is not of type p2sh-p2pkh: pubkeyhash", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", @@ -2272,6 +2354,7 @@ "prevTxScript": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", "signs": [ { + "prevOutScriptType": "p2sh-p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG", "throws": true @@ -2295,11 +2378,14 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "keyPair": "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf", "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG" }, { + "prevOutScriptType": "p2sh-p2ms", "keyPair": "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf", + "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG", "throws": true } ] @@ -2322,6 +2408,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "network": "testnet", "throws": true @@ -2345,6 +2432,7 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2sh-p2ms", "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG", "throws": true @@ -2360,7 +2448,7 @@ ] }, { - "exception": "nulldata not supported", + "exception": "input #0 is not of type p2pkh: nulldata", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", @@ -2368,6 +2456,7 @@ "prevTxScript": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", "throws": true } @@ -2381,6 +2470,190 @@ } ] }, + { + "exception": "input #0 is not of type p2pk: nulldata", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", + "signs": [ + { + "prevOutScriptType": "p2pk", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_RETURN deadbeef", + "value": 1000 + } + ] + }, + { + "exception": "input #0 is not of type p2wpkh: nulldata", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", + "signs": [ + { + "prevOutScriptType": "p2wpkh", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_RETURN deadbeef", + "value": 1000 + } + ] + }, + { + "exception": "input #0 is not of type p2ms: nulldata", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", + "signs": [ + { + "prevOutScriptType": "p2ms", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_RETURN deadbeef", + "value": 1000 + } + ] + }, + { + "exception": "input #0 is not of type p2sh-p2wpkh: nulldata", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", + "signs": [ + { + "prevOutScriptType": "p2sh-p2wpkh", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_RETURN deadbeef", + "value": 1000 + } + ] + }, + { + "exception": "input #0 is not of type p2sh-p2pk: nulldata", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", + "signs": [ + { + "prevOutScriptType": "p2sh-p2pk", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_RETURN deadbeef", + "value": 1000 + } + ] + }, + { + "exception": "input #0 is not of type p2wsh-p2pk: nulldata", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", + "signs": [ + { + "prevOutScriptType": "p2wsh-p2pk", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_RETURN deadbeef", + "value": 1000 + } + ] + }, + { + "exception": "input #0 is not of type p2sh-p2wsh-p2pk: nulldata", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", + "signs": [ + { + "prevOutScriptType": "p2sh-p2wsh-p2pk", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_RETURN deadbeef", + "value": 1000 + } + ] + }, + { + "exception": "Unknown prevOutScriptType \"notvalidtype\"", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", + "signs": [ + { + "prevOutScriptType": "notvalidtype", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_RETURN deadbeef", + "value": 1000 + } + ] + }, { "description": "Transaction w/ no outputs (but 1 SIGHASH_NONE)", "exception": "Transaction needs outputs", @@ -2390,6 +2663,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "hashType": 2 } @@ -2400,6 +2674,7 @@ "vout": 1, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "throws": true } @@ -2417,6 +2692,7 @@ "vout": 0, "signs": [ { + "prevOutScriptType": "p2pkh", "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "throws": true } diff --git a/test/integration/_regtest.js b/test/integration/_regtest.js index 8be864a..30b868c 100644 --- a/test/integration/_regtest.js +++ b/test/integration/_regtest.js @@ -92,7 +92,11 @@ async function faucetComplex (output, value) { const txvb = new bitcoin.TransactionBuilder(NETWORK) txvb.addInput(unspent.txId, unspent.vout, null, p2pkh.output) txvb.addOutput(output, value) - txvb.sign(0, keyPair) + txvb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair + }) const txv = txvb.build() await broadcast(txv.toHex()) diff --git a/test/integration/payments.js b/test/integration/payments.js index 66b0a13..256bd00 100644 --- a/test/integration/payments.js +++ b/test/integration/payments.js @@ -15,12 +15,29 @@ async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) { txb.addInput(unspent.txId, unspent.vout, null, prevOutput) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) + const posType = depends.prevOutScriptType + const needsValue = !!witnessScript || posType.slice(-6) === 'p2wpkh' + if (depends.signatures) { keyPairs.forEach(keyPair => { - txb.sign(0, keyPair, redeemScript, null, unspent.value, witnessScript) + txb.sign({ + prevOutScriptType: posType, + vin: 0, + keyPair, + redeemScript, + witnessValue: needsValue ? unspent.value : undefined, + witnessScript, + }) }) } else if (depends.signature) { - txb.sign(0, keyPairs[0], redeemScript, null, unspent.value, witnessScript) + txb.sign({ + prevOutScriptType: posType, + vin: 0, + keyPair: keyPairs[0], + redeemScript, + witnessValue: needsValue ? unspent.value : undefined, + witnessScript, + }) } return regtestUtils.broadcast(txb.build().toHex()) @@ -41,12 +58,14 @@ async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) { describe('bitcoinjs-lib (payments - ' + k + ')', () => { it('can broadcast as an output, and be spent as an input', async () => { - await buildAndSign(depends, output, null, null) + Object.assign(depends, { prevOutScriptType: k }) + await buildAndSign(depends, output, undefined, undefined) }) it('can (as P2SH(' + k + ')) broadcast as an output, and be spent as an input', async () => { const p2sh = bitcoin.payments.p2sh({ redeem: { output }, network: NETWORK }) - await buildAndSign(depends, p2sh.output, p2sh.redeem.output, null) + Object.assign(depends, { prevOutScriptType: 'p2sh-' + k }) + await buildAndSign(depends, p2sh.output, p2sh.redeem.output, undefined) }) // NOTE: P2WPKH cannot be wrapped in P2WSH, consensus fail @@ -54,13 +73,15 @@ async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) { it('can (as P2WSH(' + k + ')) broadcast as an output, and be spent as an input', async () => { const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK }) - await buildAndSign(depends, p2wsh.output, null, p2wsh.redeem.output) + Object.assign(depends, { prevOutScriptType: 'p2wsh-' + k }) + await buildAndSign(depends, p2wsh.output, undefined, p2wsh.redeem.output) }) it('can (as P2SH(P2WSH(' + k + '))) broadcast as an output, and be spent as an input', async () => { const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK }) const p2sh = bitcoin.payments.p2sh({ redeem: { output: p2wsh.output }, network: NETWORK }) + Object.assign(depends, { prevOutScriptType: 'p2sh-p2wsh-' + k }) await buildAndSign(depends, p2sh.output, p2sh.redeem.output, p2wsh.redeem.output) }) }) diff --git a/test/integration/transactions.js b/test/integration/transactions.js index c096b87..464460e 100644 --- a/test/integration/transactions.js +++ b/test/integration/transactions.js @@ -18,7 +18,11 @@ describe('bitcoinjs-lib (transactions)', () => { txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000) // (in)15000 - (out)12000 = (fee)3000, this is the miner fee - txb.sign(0, alice) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair: alice + }) // prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below assert.strictEqual(txb.build().toHex(), '01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000') @@ -36,8 +40,16 @@ describe('bitcoinjs-lib (transactions)', () => { txb.addOutput('1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9', 170000) // (in)(200000 + 300000) - (out)(180000 + 170000) = (fee)150000, this is the miner fee - txb.sign(1, bob) // Bob signs his input, which was the second input (1th) - txb.sign(0, alice) // Alice signs her input, which was the first input (0th) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 1, + keyPair: bob + }) // Bob signs his input, which was the second input (1th) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair: alice + }) // Alice signs her input, which was the first input (0th) // prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below assert.strictEqual(txb.build().toHex(), '01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000') @@ -65,8 +77,16 @@ describe('bitcoinjs-lib (transactions)', () => { // (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee // Alice signs each input with the respective private keys - txb.sign(0, alice1) - txb.sign(1, alice2) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair: alice1 + }) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 1, + keyPair: alice2 + }) // build and broadcast our RegTest network await regtestUtils.broadcast(txb.build().toHex()) @@ -85,7 +105,11 @@ describe('bitcoinjs-lib (transactions)', () => { txb.addInput(unspent.txId, unspent.vout) txb.addOutput(embed.output, 1000) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5) - txb.sign(0, keyPair) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) // build and broadcast to the RegTest network await regtestUtils.broadcast(txb.build().toHex()) @@ -108,8 +132,18 @@ describe('bitcoinjs-lib (transactions)', () => { txb.addInput(unspent.txId, unspent.vout) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4) - txb.sign(0, keyPairs[0], p2sh.redeem.output) - txb.sign(0, keyPairs[2], p2sh.redeem.output) + txb.sign({ + prevOutScriptType: 'p2sh-p2ms', + vin: 0, + keyPair: keyPairs[0], + redeemScript: p2sh.redeem.output, + }) + txb.sign({ + prevOutScriptType: 'p2sh-p2ms', + vin: 0, + keyPair: keyPairs[2], + redeemScript: p2sh.redeem.output, + }) const tx = txb.build() // build and broadcast to the Bitcoin RegTest network @@ -133,7 +167,13 @@ describe('bitcoinjs-lib (transactions)', () => { const txb = new bitcoin.TransactionBuilder(regtest) txb.addInput(unspent.txId, unspent.vout) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) - txb.sign(0, keyPair, p2sh.redeem.output, null, unspent.value) + txb.sign({ + prevOutScriptType: 'p2sh-p2wpkh', + vin: 0, + keyPair: keyPair, + redeemScript: p2sh.redeem.output, + witnessValue: unspent.value, + }) const tx = txb.build() @@ -158,7 +198,12 @@ describe('bitcoinjs-lib (transactions)', () => { const txb = new bitcoin.TransactionBuilder(regtest) txb.addInput(unspent.txId, unspent.vout, null, p2wpkh.output) // NOTE: provide the prevOutScript! txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) - txb.sign(0, keyPair, null, null, unspent.value) // NOTE: no redeem script + txb.sign({ + prevOutScriptType: 'p2wpkh', + vin: 0, + keyPair: keyPair, + witnessValue: unspent.value, + }) // NOTE: no redeem script const tx = txb.build() // build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network @@ -183,7 +228,13 @@ describe('bitcoinjs-lib (transactions)', () => { const txb = new bitcoin.TransactionBuilder(regtest) txb.addInput(unspent.txId, unspent.vout, null, p2wsh.output) // NOTE: provide the prevOutScript! txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) - txb.sign(0, keyPair, null, null, 5e4, p2wsh.redeem.output) // NOTE: provide a witnessScript! + txb.sign({ + prevOutScriptType: 'p2wsh-p2pk', + vin: 0, + keyPair: keyPair, + witnessValue: 5e4, + witnessScript: p2wsh.redeem.output, + }) // NOTE: provide a witnessScript! const tx = txb.build() // build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network @@ -215,9 +266,30 @@ describe('bitcoinjs-lib (transactions)', () => { const txb = new bitcoin.TransactionBuilder(regtest) txb.addInput(unspent.txId, unspent.vout, null, p2sh.output) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4) - txb.sign(0, keyPairs[0], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output) - txb.sign(0, keyPairs[2], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output) - txb.sign(0, keyPairs[3], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output) + txb.sign({ + prevOutScriptType: 'p2sh-p2wsh-p2ms', + vin: 0, + keyPair: keyPairs[0], + redeemScript: p2sh.redeem.output, + witnessValue: unspent.value, + witnessScript: p2wsh.redeem.output, + }) + txb.sign({ + prevOutScriptType: 'p2sh-p2wsh-p2ms', + vin: 0, + keyPair: keyPairs[2], + redeemScript: p2sh.redeem.output, + witnessValue: unspent.value, + witnessScript: p2wsh.redeem.output, + }) + txb.sign({ + prevOutScriptType: 'p2sh-p2wsh-p2ms', + vin: 0, + keyPair: keyPairs[3], + redeemScript: p2sh.redeem.output, + witnessValue: unspent.value, + witnessScript: p2wsh.redeem.output, + }) const tx = txb.build() diff --git a/test/transaction_builder.js b/test/transaction_builder.js index 1af8272..a135cca 100644 --- a/test/transaction_builder.js +++ b/test/transaction_builder.js @@ -11,7 +11,7 @@ const NETWORKS = require('../src/networks') const fixtures = require('./fixtures/transaction_builder') -function constructSign (f, txb) { +function constructSign (f, txb, useOldSignArgs) { const network = NETWORKS[f.network] const stages = f.stages && f.stages.concat() @@ -21,21 +21,36 @@ function constructSign (f, txb) { const keyPair = ECPair.fromWIF(sign.keyPair, network) let redeemScript let witnessScript - let value + let witnessValue if (sign.redeemScript) { redeemScript = bscript.fromASM(sign.redeemScript) } if (sign.value) { - value = sign.value + witnessValue = sign.value } if (sign.witnessScript) { witnessScript = bscript.fromASM(sign.witnessScript) } - txb.sign(index, keyPair, redeemScript, sign.hashType, value, witnessScript) + if (useOldSignArgs) { + // DEPRECATED: v6 will remove this interface + txb.sign(index, keyPair, redeemScript, sign.hashType, witnessValue, witnessScript) + } else { + // prevOutScriptType is required, see /ts_src/transaction_builder.ts + // The PREVOUT_TYPES constant is a Set with all possible values. + txb.sign({ + prevOutScriptType: sign.prevOutScriptType, + vin: index, + keyPair, + redeemScript, + hashType: sign.hashType, + witnessValue, + witnessScript, + }) + } if (sign.stage) { const tx = txb.buildIncomplete() @@ -48,7 +63,7 @@ function constructSign (f, txb) { return txb } -function construct (f, dontSign) { +function construct (f, dontSign, useOldSignArgs) { const network = NETWORKS[f.network] const txb = new TransactionBuilder(network) @@ -84,545 +99,689 @@ function construct (f, dontSign) { }) if (dontSign) return txb - return constructSign(f, txb) + return constructSign(f, txb, useOldSignArgs) } -describe('TransactionBuilder', () => { - // constants - const keyPair = ECPair.fromPrivateKey(Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex')) - const scripts = [ - '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', - '1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP' - ].map(x => { - return baddress.toOutputScript(x) - }) - const txHash = Buffer.from('0e7cea811c0be9f73c0aca591034396e7264473fc25c1ca45195d7417b36cbe2', 'hex') - - describe('fromTransaction', () => { - fixtures.valid.build.forEach(f => { - it('returns TransactionBuilder, with ' + f.description, () => { - const network = NETWORKS[f.network || 'bitcoin'] - - const tx = Transaction.fromHex(f.txHex) - const txb = TransactionBuilder.fromTransaction(tx, network) - const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build() - - assert.strictEqual(txAfter.toHex(), f.txHex) - assert.strictEqual(txb.network, network) - }) +// TODO: Remove loop in v6 +for (const useOldSignArgs of [ false, true ]) { + // Search for "useOldSignArgs" + // to find the second part of this console.warn replace + let consoleWarn; + if (useOldSignArgs) { + consoleWarn = console.warn; + // Silence console.warn during these tests + console.warn = () => undefined; + } + describe(`TransactionBuilder: useOldSignArgs === ${useOldSignArgs}`, () => { + // constants + const keyPair = ECPair.fromPrivateKey(Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex')) + const scripts = [ + '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', + '1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP' + ].map(x => { + return baddress.toOutputScript(x) }) + const txHash = Buffer.from('0e7cea811c0be9f73c0aca591034396e7264473fc25c1ca45195d7417b36cbe2', 'hex') - fixtures.valid.fromTransaction.forEach(f => { - it('returns TransactionBuilder, with ' + f.description, () => { - const tx = new Transaction() + describe('fromTransaction', () => { + fixtures.valid.build.forEach(f => { + it('returns TransactionBuilder, with ' + f.description, () => { + const network = NETWORKS[f.network || 'bitcoin'] - f.inputs.forEach(input => { - const txHash2 = Buffer.from(input.txId, 'hex').reverse() + const tx = Transaction.fromHex(f.txHex) + const txb = TransactionBuilder.fromTransaction(tx, network) + const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build() - tx.addInput(txHash2, input.vout, undefined, bscript.fromASM(input.scriptSig)) - }) - - f.outputs.forEach(output => { - tx.addOutput(bscript.fromASM(output.script), output.value) - }) - - const txb = TransactionBuilder.fromTransaction(tx) - const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build() - - txAfter.ins.forEach((input, i) => { - assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSigAfter) - }) - - txAfter.outs.forEach((output, i) => { - assert.strictEqual(bscript.toASM(output.script), f.outputs[i].script) + assert.strictEqual(txAfter.toHex(), f.txHex) + assert.strictEqual(txb.network, network) }) }) - }) - fixtures.valid.fromTransactionSequential.forEach(f => { - it('with ' + f.description, () => { - const network = NETWORKS[f.network] - const tx = Transaction.fromHex(f.txHex) - const txb = TransactionBuilder.fromTransaction(tx, network) + fixtures.valid.fromTransaction.forEach(f => { + it('returns TransactionBuilder, with ' + f.description, () => { + const tx = new Transaction() - tx.ins.forEach((input, i) => { - assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSig) - }) + f.inputs.forEach(input => { + const txHash2 = Buffer.from(input.txId, 'hex').reverse() - constructSign(f, txb) - const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build() + tx.addInput(txHash2, input.vout, undefined, bscript.fromASM(input.scriptSig)) + }) - txAfter.ins.forEach((input, i) => { - assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSigAfter) - }) + f.outputs.forEach(output => { + tx.addOutput(bscript.fromASM(output.script), output.value) + }) - assert.strictEqual(txAfter.toHex(), f.txHexAfter) - }) - }) + const txb = TransactionBuilder.fromTransaction(tx) + const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build() - it('classifies transaction inputs', () => { - const tx = Transaction.fromHex(fixtures.valid.classification.hex) - const txb = TransactionBuilder.fromTransaction(tx) + txAfter.ins.forEach((input, i) => { + assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSigAfter) + }) - txb.__INPUTS.forEach(i => { - assert.strictEqual(i.prevOutType, 'scripthash') - assert.strictEqual(i.redeemScriptType, 'multisig') - }) - }) - - fixtures.invalid.fromTransaction.forEach(f => { - it('throws ' + f.exception, () => { - const tx = Transaction.fromHex(f.txHex) - - assert.throws(() => { - TransactionBuilder.fromTransaction(tx) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('addInput', () => { - let txb - beforeEach(() => { - txb = new TransactionBuilder() - }) - - it('accepts a txHash, index [and sequence number]', () => { - const vin = txb.addInput(txHash, 1, 54) - assert.strictEqual(vin, 0) - - const txIn = txb.__TX.ins[0] - assert.strictEqual(txIn.hash, txHash) - assert.strictEqual(txIn.index, 1) - assert.strictEqual(txIn.sequence, 54) - assert.strictEqual(txb.__INPUTS[0].prevOutScript, undefined) - }) - - it('accepts a txHash, index [, sequence number and scriptPubKey]', () => { - const vin = txb.addInput(txHash, 1, 54, scripts[1]) - assert.strictEqual(vin, 0) - - const txIn = txb.__TX.ins[0] - assert.strictEqual(txIn.hash, txHash) - assert.strictEqual(txIn.index, 1) - assert.strictEqual(txIn.sequence, 54) - assert.strictEqual(txb.__INPUTS[0].prevOutScript, scripts[1]) - }) - - it('accepts a prevTx, index [and sequence number]', () => { - const prevTx = new Transaction() - prevTx.addOutput(scripts[0], 0) - prevTx.addOutput(scripts[1], 1) - - const vin = txb.addInput(prevTx, 1, 54) - assert.strictEqual(vin, 0) - - const txIn = txb.__TX.ins[0] - assert.deepStrictEqual(txIn.hash, prevTx.getHash()) - assert.strictEqual(txIn.index, 1) - assert.strictEqual(txIn.sequence, 54) - assert.strictEqual(txb.__INPUTS[0].prevOutScript, scripts[1]) - }) - - it('returns the input index', () => { - assert.strictEqual(txb.addInput(txHash, 0), 0) - assert.strictEqual(txb.addInput(txHash, 1), 1) - }) - - it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => { - txb.addInput(txHash, 0) - txb.addOutput(scripts[0], 1000) - txb.sign(0, keyPair) - - assert.throws(() => { - txb.addInput(txHash, 0) - }, /No, this would invalidate signatures/) - }) - }) - - describe('addOutput', () => { - let txb - beforeEach(() => { - txb = new TransactionBuilder() - }) - - it('accepts an address string and value', () => { - const { address } = payments.p2pkh({ pubkey: keyPair.publicKey }) - const vout = txb.addOutput(address, 1000) - assert.strictEqual(vout, 0) - - const txout = txb.__TX.outs[0] - assert.deepStrictEqual(txout.script, scripts[0]) - assert.strictEqual(txout.value, 1000) - }) - - it('accepts a ScriptPubKey and value', () => { - const vout = txb.addOutput(scripts[0], 1000) - assert.strictEqual(vout, 0) - - const txout = txb.__TX.outs[0] - assert.deepStrictEqual(txout.script, scripts[0]) - assert.strictEqual(txout.value, 1000) - }) - - it('throws if address is of the wrong network', () => { - assert.throws(() => { - txb.addOutput('2NGHjvjw83pcVFgMcA7QvSMh2c246rxLVz9', 1000) - }, /2NGHjvjw83pcVFgMcA7QvSMh2c246rxLVz9 has no matching Script/) - }) - - it('add second output after signed first input with SIGHASH_NONE', () => { - txb.addInput(txHash, 0) - txb.addOutput(scripts[0], 2000) - txb.sign(0, keyPair, undefined, Transaction.SIGHASH_NONE) - assert.strictEqual(txb.addOutput(scripts[1], 9000), 1) - }) - - it('add first output after signed first input with SIGHASH_NONE', () => { - txb.addInput(txHash, 0) - txb.sign(0, keyPair, undefined, Transaction.SIGHASH_NONE) - assert.strictEqual(txb.addOutput(scripts[0], 2000), 0) - }) - - it('add second output after signed first input with SIGHASH_SINGLE', () => { - txb.addInput(txHash, 0) - txb.addOutput(scripts[0], 2000) - txb.sign(0, keyPair, undefined, Transaction.SIGHASH_SINGLE) - assert.strictEqual(txb.addOutput(scripts[1], 9000), 1) - }) - - it('add first output after signed first input with SIGHASH_SINGLE', () => { - txb.addInput(txHash, 0) - txb.sign(0, keyPair, undefined, Transaction.SIGHASH_SINGLE) - assert.throws(() => { - txb.addOutput(scripts[0], 2000) - }, /No, this would invalidate signatures/) - }) - - it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => { - txb.addInput(txHash, 0) - txb.addOutput(scripts[0], 2000) - txb.sign(0, keyPair) - - assert.throws(() => { - txb.addOutput(scripts[1], 9000) - }, /No, this would invalidate signatures/) - }) - }) - - describe('setLockTime', () => { - it('throws if if there exist any scriptSigs', () => { - const txb = new TransactionBuilder() - txb.addInput(txHash, 0) - txb.addOutput(scripts[0], 100) - txb.sign(0, keyPair) - - assert.throws(() => { - txb.setLockTime(65535) - }, /No, this would invalidate signatures/) - }) - }) - - describe('sign', () => { - it('supports the alternative abstract interface { publicKey, sign }', () => { - const keyPair = { - publicKey: ECPair.makeRandom({ rng: () => { return Buffer.alloc(32, 1) } }).publicKey, - sign: hash => { return Buffer.alloc(64, 0x5f) } - } - - const txb = new TransactionBuilder() - txb.setVersion(1) - txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) - txb.addOutput('1111111111111111111114oLvT2', 100000) - txb.sign(0, keyPair) - assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f02205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') - }) - - it('supports low R signature signing', () => { - let txb = new TransactionBuilder() - txb.setVersion(1) - txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) - txb.addOutput('1111111111111111111114oLvT2', 100000) - txb.sign(0, keyPair) - // high R - assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006b483045022100b872677f35c9c14ad9c41d83649fb049250f32574e0b2547d67e209ed14ff05d022059b36ad058be54e887a1a311d5c393cb4941f6b93a0b090845ec67094de8972b01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') - - txb = new TransactionBuilder() - txb.setVersion(1) - txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) - txb.addOutput('1111111111111111111114oLvT2', 100000) - txb.setLowR() - txb.sign(0, keyPair) - // low R - assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a473044022012a601efa8756ebe83e9ac7a7db061c3147e3b49d8be67685799fe51a4c8c62f02204d568d301d5ce14af390d566d4fd50e7b8ee48e71ec67786c029e721194dae3601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') - }) - - fixtures.invalid.sign.forEach(f => { - it('throws ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), () => { - const txb = construct(f, true) - - let threw = false - f.inputs.forEach((input, index) => { - input.signs.forEach(sign => { - const keyPairNetwork = NETWORKS[sign.network || f.network] - const keyPair2 = ECPair.fromWIF(sign.keyPair, keyPairNetwork) - let redeemScript - let witnessScript - - if (sign.redeemScript) { - redeemScript = bscript.fromASM(sign.redeemScript) - } - - if (sign.witnessScript) { - witnessScript = bscript.fromASM(sign.witnessScript) - } - - if (sign.throws) { - assert.throws(() => { - txb.sign(index, keyPair2, redeemScript, sign.hashType, sign.value, witnessScript) - }, new RegExp(f.exception)) - threw = true - } else { - txb.sign(index, keyPair2, redeemScript, sign.hashType, sign.value, witnessScript) - } + txAfter.outs.forEach((output, i) => { + assert.strictEqual(bscript.toASM(output.script), f.outputs[i].script) }) }) - - assert.strictEqual(threw, true) }) - }) - }) - describe('build', () => { - fixtures.valid.build.forEach(f => { - it('builds "' + f.description + '"', () => { - const txb = construct(f) - const tx = f.incomplete ? txb.buildIncomplete() : txb.build() + fixtures.valid.fromTransactionSequential.forEach(f => { + it('with ' + f.description, () => { + const network = NETWORKS[f.network] + const tx = Transaction.fromHex(f.txHex) + const txb = TransactionBuilder.fromTransaction(tx, network) - assert.strictEqual(tx.toHex(), f.txHex) + tx.ins.forEach((input, i) => { + assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSig) + }) + + constructSign(f, txb, useOldSignArgs) + const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build() + + txAfter.ins.forEach((input, i) => { + assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSigAfter) + }) + + assert.strictEqual(txAfter.toHex(), f.txHexAfter) + }) }) - }) - // TODO: remove duplicate test code - fixtures.invalid.build.forEach(f => { - describe('for ' + (f.description || f.exception), () => { + it('classifies transaction inputs', () => { + const tx = Transaction.fromHex(fixtures.valid.classification.hex) + const txb = TransactionBuilder.fromTransaction(tx) + + txb.__INPUTS.forEach(i => { + assert.strictEqual(i.prevOutType, 'scripthash') + assert.strictEqual(i.redeemScriptType, 'multisig') + }) + }) + + fixtures.invalid.fromTransaction.forEach(f => { it('throws ' + f.exception, () => { - assert.throws(() => { - let txb - if (f.txHex) { - txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) - } else { - txb = construct(f) - } + const tx = Transaction.fromHex(f.txHex) - txb.build() + assert.throws(() => { + TransactionBuilder.fromTransaction(tx) }, new RegExp(f.exception)) }) + }) + }) - // if throws on incomplete too, enforce that - if (f.incomplete) { + describe('addInput', () => { + let txb + beforeEach(() => { + txb = new TransactionBuilder() + }) + + it('accepts a txHash, index [and sequence number]', () => { + const vin = txb.addInput(txHash, 1, 54) + assert.strictEqual(vin, 0) + + const txIn = txb.__TX.ins[0] + assert.strictEqual(txIn.hash, txHash) + assert.strictEqual(txIn.index, 1) + assert.strictEqual(txIn.sequence, 54) + assert.strictEqual(txb.__INPUTS[0].prevOutScript, undefined) + }) + + it('accepts a txHash, index [, sequence number and scriptPubKey]', () => { + const vin = txb.addInput(txHash, 1, 54, scripts[1]) + assert.strictEqual(vin, 0) + + const txIn = txb.__TX.ins[0] + assert.strictEqual(txIn.hash, txHash) + assert.strictEqual(txIn.index, 1) + assert.strictEqual(txIn.sequence, 54) + assert.strictEqual(txb.__INPUTS[0].prevOutScript, scripts[1]) + }) + + it('accepts a prevTx, index [and sequence number]', () => { + const prevTx = new Transaction() + prevTx.addOutput(scripts[0], 0) + prevTx.addOutput(scripts[1], 1) + + const vin = txb.addInput(prevTx, 1, 54) + assert.strictEqual(vin, 0) + + const txIn = txb.__TX.ins[0] + assert.deepStrictEqual(txIn.hash, prevTx.getHash()) + assert.strictEqual(txIn.index, 1) + assert.strictEqual(txIn.sequence, 54) + assert.strictEqual(txb.__INPUTS[0].prevOutScript, scripts[1]) + }) + + it('returns the input index', () => { + assert.strictEqual(txb.addInput(txHash, 0), 0) + assert.strictEqual(txb.addInput(txHash, 1), 1) + }) + + it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => { + txb.addInput(txHash, 0) + txb.addOutput(scripts[0], 1000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + + assert.throws(() => { + txb.addInput(txHash, 0) + }, /No, this would invalidate signatures/) + }) + }) + + describe('addOutput', () => { + let txb + beforeEach(() => { + txb = new TransactionBuilder() + }) + + it('accepts an address string and value', () => { + const { address } = payments.p2pkh({ pubkey: keyPair.publicKey }) + const vout = txb.addOutput(address, 1000) + assert.strictEqual(vout, 0) + + const txout = txb.__TX.outs[0] + assert.deepStrictEqual(txout.script, scripts[0]) + assert.strictEqual(txout.value, 1000) + }) + + it('accepts a ScriptPubKey and value', () => { + const vout = txb.addOutput(scripts[0], 1000) + assert.strictEqual(vout, 0) + + const txout = txb.__TX.outs[0] + assert.deepStrictEqual(txout.script, scripts[0]) + assert.strictEqual(txout.value, 1000) + }) + + it('throws if address is of the wrong network', () => { + assert.throws(() => { + txb.addOutput('2NGHjvjw83pcVFgMcA7QvSMh2c246rxLVz9', 1000) + }, /2NGHjvjw83pcVFgMcA7QvSMh2c246rxLVz9 has no matching Script/) + }) + + it('add second output after signed first input with SIGHASH_NONE', () => { + txb.addInput(txHash, 0) + txb.addOutput(scripts[0], 2000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + hashType: Transaction.SIGHASH_NONE, + }) + assert.strictEqual(txb.addOutput(scripts[1], 9000), 1) + }) + + it('add first output after signed first input with SIGHASH_NONE', () => { + txb.addInput(txHash, 0) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + hashType: Transaction.SIGHASH_NONE, + }) + assert.strictEqual(txb.addOutput(scripts[0], 2000), 0) + }) + + it('add second output after signed first input with SIGHASH_SINGLE', () => { + txb.addInput(txHash, 0) + txb.addOutput(scripts[0], 2000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + hashType: Transaction.SIGHASH_SINGLE, + }) + assert.strictEqual(txb.addOutput(scripts[1], 9000), 1) + }) + + it('add first output after signed first input with SIGHASH_SINGLE', () => { + txb.addInput(txHash, 0) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + hashType: Transaction.SIGHASH_SINGLE, + }) + assert.throws(() => { + txb.addOutput(scripts[0], 2000) + }, /No, this would invalidate signatures/) + }) + + it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => { + txb.addInput(txHash, 0) + txb.addOutput(scripts[0], 2000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + + assert.throws(() => { + txb.addOutput(scripts[1], 9000) + }, /No, this would invalidate signatures/) + }) + }) + + describe('setLockTime', () => { + it('throws if if there exist any scriptSigs', () => { + const txb = new TransactionBuilder() + txb.addInput(txHash, 0) + txb.addOutput(scripts[0], 100) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + + assert.throws(() => { + txb.setLockTime(65535) + }, /No, this would invalidate signatures/) + }) + }) + + describe('sign', () => { + it('supports the alternative abstract interface { publicKey, sign }', () => { + const keyPair = { + publicKey: ECPair.makeRandom({ rng: () => { return Buffer.alloc(32, 1) } }).publicKey, + sign: hash => { return Buffer.alloc(64, 0x5f) } + } + + const txb = new TransactionBuilder() + txb.setVersion(1) + txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) + txb.addOutput('1111111111111111111114oLvT2', 100000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f02205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') + }) + + it('supports low R signature signing', () => { + let txb = new TransactionBuilder() + txb.setVersion(1) + txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) + txb.addOutput('1111111111111111111114oLvT2', 100000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + // high R + assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006b483045022100b872677f35c9c14ad9c41d83649fb049250f32574e0b2547d67e209ed14ff05d022059b36ad058be54e887a1a311d5c393cb4941f6b93a0b090845ec67094de8972b01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') + + txb = new TransactionBuilder() + txb.setVersion(1) + txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) + txb.addOutput('1111111111111111111114oLvT2', 100000) + txb.setLowR() + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + // low R + assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a473044022012a601efa8756ebe83e9ac7a7db061c3147e3b49d8be67685799fe51a4c8c62f02204d568d301d5ce14af390d566d4fd50e7b8ee48e71ec67786c029e721194dae3601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') + }) + + it('fails when missing required arguments', () => { + let txb = new TransactionBuilder() + txb.setVersion(1) + txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) + txb.addOutput('1111111111111111111114oLvT2', 100000) + assert.throws(() => { + txb.sign() + }, /TransactionBuilder sign first arg must be TxbSignArg or number/) + assert.throws(() => { + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 1, + keyPair, + }) + }, /No input at index: 1/) + assert.throws(() => { + txb.sign({ + prevOutScriptType: 'p2pkh', + keyPair, + }) + }, /sign must include vin parameter as Number \(input index\)/) + assert.throws(() => { + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair: {}, + }) + }, /sign must include keyPair parameter as Signer interface/) + assert.throws(() => { + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + hashType: 'string', + }) + }, /sign hashType parameter must be a number/) + if (useOldSignArgs) { + assert.throws(() => { + txb.sign(0) + }, /sign requires keypair/) + } + }) + + fixtures.invalid.sign.forEach(f => { + it('throws ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), () => { + const txb = construct(f, true) + + let threw = false + f.inputs.forEach((input, index) => { + input.signs.forEach(sign => { + const keyPairNetwork = NETWORKS[sign.network || f.network] + const keyPair2 = ECPair.fromWIF(sign.keyPair, keyPairNetwork) + let redeemScript + let witnessScript + + if (sign.redeemScript) { + redeemScript = bscript.fromASM(sign.redeemScript) + } + + if (sign.witnessScript) { + witnessScript = bscript.fromASM(sign.witnessScript) + } + + if (sign.throws) { + assert.throws(() => { + txb.sign({ + prevOutScriptType: sign.prevOutScriptType, + vin: index, + keyPair: keyPair2, + redeemScript, + hashType: sign.hashType, + witnessValue: sign.value, + witnessScript, + }) + }, new RegExp(f.exception)) + threw = true + } else { + txb.sign({ + prevOutScriptType: sign.prevOutScriptType, + vin: index, + keyPair: keyPair2, + redeemScript, + hashType: sign.hashType, + witnessValue: sign.value, + witnessScript, + }) + } + }) + }) + + assert.strictEqual(threw, true) + }) + }) + }) + + describe('build', () => { + fixtures.valid.build.forEach(f => { + it('builds "' + f.description + '"', () => { + const txb = construct(f, undefined, useOldSignArgs) + const tx = f.incomplete ? txb.buildIncomplete() : txb.build() + + assert.strictEqual(tx.toHex(), f.txHex) + }) + }) + + // TODO: remove duplicate test code + fixtures.invalid.build.forEach(f => { + describe('for ' + (f.description || f.exception), () => { it('throws ' + f.exception, () => { assert.throws(() => { let txb if (f.txHex) { txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) } else { - txb = construct(f) + txb = construct(f, undefined, useOldSignArgs) + } + + txb.build() + }, new RegExp(f.exception)) + }) + + // if throws on incomplete too, enforce that + if (f.incomplete) { + it('throws ' + f.exception, () => { + assert.throws(() => { + let txb + if (f.txHex) { + txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) + } else { + txb = construct(f, undefined, useOldSignArgs) + } + + txb.buildIncomplete() + }, new RegExp(f.exception)) + }) + } else { + it('does not throw if buildIncomplete', () => { + let txb + if (f.txHex) { + txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) + } else { + txb = construct(f, undefined, useOldSignArgs) } txb.buildIncomplete() - }, new RegExp(f.exception)) - }) - } else { - it('does not throw if buildIncomplete', () => { - let txb - if (f.txHex) { - txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) - } else { - txb = construct(f) - } + }) + } + }) + }) - txb.buildIncomplete() + it('for incomplete with 0 signatures', () => { + const randomTxData = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000' + const randomAddress = '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH' + + const randomTx = Transaction.fromHex(randomTxData) + let tx = new TransactionBuilder() + tx.addInput(randomTx, 0) + tx.addOutput(randomAddress, 1000) + tx = tx.buildIncomplete() + assert(tx) + }) + + it('for incomplete P2SH with 0 signatures', () => { + const inp = Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000017a91471a8ec07ff69c6c4fee489184c462a9b1b9237488700000000', 'hex') // arbitrary P2SH input + const inpTx = Transaction.fromBuffer(inp) + + const txb = new TransactionBuilder(NETWORKS.testnet) + txb.addInput(inpTx, 0) + txb.addOutput('2NAkqp5xffoomp5RLBcakuGpZ12GU4twdz4', 1e8) // arbitrary output + + txb.buildIncomplete() + }) + + it('for incomplete P2WPKH with 0 signatures', () => { + const inp = Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a8040000001600141a15805e1f4040c9f68ccc887fca2e63547d794b00000000', 'hex') + const inpTx = Transaction.fromBuffer(inp) + + const txb = new TransactionBuilder(NETWORKS.testnet) + txb.addInput(inpTx, 0) + txb.addOutput('2NAkqp5xffoomp5RLBcakuGpZ12GU4twdz4', 1e8) // arbitrary output + + txb.buildIncomplete() + }) + + it('for incomplete P2WSH with 0 signatures', () => { + const inpTx = Transaction.fromBuffer(Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000022002072df76fcc0b231b94bdf7d8c25d7eef4716597818d211e19ade7813bff7a250200000000', 'hex')) + + const txb = new TransactionBuilder(NETWORKS.testnet) + txb.addInput(inpTx, 0) + txb.addOutput('2NAkqp5xffoomp5RLBcakuGpZ12GU4twdz4', 1e8) // arbitrary output + + txb.buildIncomplete() + }) + }) + + describe('multisig', () => { + fixtures.valid.multisig.forEach(f => { + it(f.description, () => { + const network = NETWORKS[f.network] + let txb = construct(f, true) + let tx + + f.inputs.forEach((input, i) => { + const redeemScript = bscript.fromASM(input.redeemScript) + + input.signs.forEach(sign => { + // rebuild the transaction each-time after the first + if (tx) { + // manually override the scriptSig? + if (sign.scriptSigBefore) { + tx.ins[i].script = bscript.fromASM(sign.scriptSigBefore) + } + + // rebuild + txb = TransactionBuilder.fromTransaction(tx, network) + } + + const keyPair2 = ECPair.fromWIF(sign.keyPair, network) + txb.sign({ + prevOutScriptType: sign.prevOutScriptType, + vin: i, + keyPair: keyPair2, + redeemScript, + hashType: sign.hashType, + }) + + // update the tx + tx = txb.buildIncomplete() + + // now verify the serialized scriptSig is as expected + assert.strictEqual(bscript.toASM(tx.ins[i].script), sign.scriptSig) + }) }) + + tx = txb.build() + assert.strictEqual(tx.toHex(), f.txHex) + }) + }) + }) + + describe('various edge case', () => { + const network = NETWORKS.testnet + + it('should warn of high fee for segwit transaction based on VSize, not Size', () => { + const rawtx = '01000000000104fdaac89627208b4733484ca56bc291f4cf4fa8d7c5f29893c52b46788a0a' + + '1df90000000000fffffffffdaac89627208b4733484ca56bc291f4cf4fa8d7c5f29893c52b46788a0a1df9' + + '0100000000ffffffffa2ef7aaab316a3e5b5b0a78d1d35c774b95a079f9f0c762277a49caf1f26bca40000' + + '000000ffffffffa2ef7aaab316a3e5b5b0a78d1d35c774b95a079f9f0c762277a49caf1f26bca401000000' + + '00ffffffff0100040000000000001976a914cf307285359ab7ef6a2daa0522c7908ddf5fe7a988ac024730' + + '440220113324438816338406841775e079b04c50d04f241da652a4035b1017ea1ecf5502205802191eb49c' + + '54bf2a5667aea72e51c3ca92085efc60f12d1ebda3a64aff343201210283409659355b6d1cc3c32decd5d5' + + '61abaac86c37a353b52895a5e6c196d6f44802483045022100dc2892874e6d8708e3f5a058c5c9263cdf03' + + '969492270f89ee4933caf6daf8bb0220391dfe61a002709b63b9d64422d3db09b727839d1287e10a128a5d' + + 'b52a82309301210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f448024830' + + '450221009e3ed3a6ae93a018f443257b43e47b55cf7f7f3547d8807178072234686b22160220576121cfe6' + + '77c7eddf5575ea0a7c926247df6eca723c4f85df306e8bc08ea2df01210283409659355b6d1cc3c32decd5' + + 'd561abaac86c37a353b52895a5e6c196d6f44802473044022007be81ffd4297441ab10e740fc9bab9545a2' + + '194a565cd6aa4cc38b8eaffa343402201c5b4b61d73fa38e49c1ee68cc0e6dfd2f5dae453dd86eb142e87a' + + '0bafb1bc8401210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f44800000000' + const txb = TransactionBuilder.fromTransaction(Transaction.fromHex(rawtx)) + txb.__INPUTS[0].value = 241530 + txb.__INPUTS[1].value = 241530 + txb.__INPUTS[2].value = 248920 + txb.__INPUTS[3].value = 248920 + + assert.throws(() => { + txb.build() + }, new RegExp('Transaction has absurd fees')) + }) + + it('should classify witness inputs with witness = true during multisigning', () => { + const keyPair = ECPair.fromWIF('cRAwuVuVSBZMPu7hdrYvMCZ8eevzmkExjFbaBLhqnDdrezxN3nTS', network) + const witnessScript = Buffer.from('522102bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e22102d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea1952ae', 'hex') + const redeemScript = Buffer.from('002024376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5af', 'hex') + const scriptPubKey = Buffer.from('a914b64f1a3eacc1c8515592a6f10457e8ff90e4db6a87', 'hex') + const txb = new TransactionBuilder(network) + txb.setVersion(1) + txb.addInput('a4696c4b0cd27ec2e173ab1fa7d1cc639a98ee237cec95a77ca7ff4145791529', 1, 0xffffffff, scriptPubKey) + txb.addOutput(scriptPubKey, 99000) + txb.sign({ + prevOutScriptType: 'p2sh-p2wsh-p2ms', + vin: 0, + keyPair, + redeemScript, + witnessValue: 100000, + witnessScript, + }) + + // 2-of-2 signed only once + const tx = txb.buildIncomplete() + + // Only input is segwit, so txid should be accurate with the final tx + assert.strictEqual(tx.getId(), 'f15d0a65b21b4471405b21a099f8b18e1ae4d46d55efbd0f4766cf11ad6cb821') + + const txHex = tx.toHex() + TransactionBuilder.fromTransaction(Transaction.fromHex(txHex)) + }) + + it('should handle badly pre-filled OP_0s', () => { + // OP_0 is used where a signature is missing + const redeemScripSig = bscript.fromASM('OP_0 OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae') + const redeemScript = bscript.fromASM('OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG') + + const tx = new Transaction() + tx.addInput(Buffer.from('cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f07149', 'hex'), 0, undefined, redeemScripSig) + tx.addOutput(Buffer.from('76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac', 'hex'), 1000) + + // now import the Transaction + const txb = TransactionBuilder.fromTransaction(tx, NETWORKS.testnet) + + const keyPair2 = ECPair.fromWIF('91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe', network) + txb.sign({ + prevOutScriptType: 'p2sh-p2ms', + vin: 0, + keyPair: keyPair2, + redeemScript, + }) + + const tx2 = txb.build() + assert.strictEqual(tx2.getId(), 'eab59618a564e361adef6d918bd792903c3d41bcf1220137364fb847880467f9') + assert.strictEqual(bscript.toASM(tx2.ins[0].script), 'OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae') + }) + + it('should not classify blank scripts as nonstandard', () => { + let txb = new TransactionBuilder() + txb.setVersion(1) + txb.addInput('aa94ab02c182214f090e99a0d57021caffd0f195a81c24602b1028b130b63e31', 0) + + const incomplete = txb.buildIncomplete().toHex() + const keyPair = ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy') + + // sign, as expected + txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + const txId = txb.build().getId() + assert.strictEqual(txId, '54f097315acbaedb92a95455da3368eb45981cdae5ffbc387a9afc872c0f29b3') + + // and, repeat + txb = TransactionBuilder.fromTransaction(Transaction.fromHex(incomplete)) + txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + const txId2 = txb.build().getId() + assert.strictEqual(txId, txId2) + // TODO: Remove me in v6 + if (useOldSignArgs) { + console.warn = consoleWarn; } }) }) - - it('for incomplete with 0 signatures', () => { - const randomTxData = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000' - const randomAddress = '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH' - - const randomTx = Transaction.fromHex(randomTxData) - let tx = new TransactionBuilder() - tx.addInput(randomTx, 0) - tx.addOutput(randomAddress, 1000) - tx = tx.buildIncomplete() - assert(tx) - }) - - it('for incomplete P2SH with 0 signatures', () => { - const inp = Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000017a91471a8ec07ff69c6c4fee489184c462a9b1b9237488700000000', 'hex') // arbitrary P2SH input - const inpTx = Transaction.fromBuffer(inp) - - const txb = new TransactionBuilder(NETWORKS.testnet) - txb.addInput(inpTx, 0) - txb.addOutput('2NAkqp5xffoomp5RLBcakuGpZ12GU4twdz4', 1e8) // arbitrary output - - txb.buildIncomplete() - }) - - it('for incomplete P2WPKH with 0 signatures', () => { - const inp = Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a8040000001600141a15805e1f4040c9f68ccc887fca2e63547d794b00000000', 'hex') - const inpTx = Transaction.fromBuffer(inp) - - const txb = new TransactionBuilder(NETWORKS.testnet) - txb.addInput(inpTx, 0) - txb.addOutput('2NAkqp5xffoomp5RLBcakuGpZ12GU4twdz4', 1e8) // arbitrary output - - txb.buildIncomplete() - }) - - it('for incomplete P2WSH with 0 signatures', () => { - const inpTx = Transaction.fromBuffer(Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000022002072df76fcc0b231b94bdf7d8c25d7eef4716597818d211e19ade7813bff7a250200000000', 'hex')) - - const txb = new TransactionBuilder(NETWORKS.testnet) - txb.addInput(inpTx, 0) - txb.addOutput('2NAkqp5xffoomp5RLBcakuGpZ12GU4twdz4', 1e8) // arbitrary output - - txb.buildIncomplete() - }) }) - - describe('multisig', () => { - fixtures.valid.multisig.forEach(f => { - it(f.description, () => { - const network = NETWORKS[f.network] - let txb = construct(f, true) - let tx - - f.inputs.forEach((input, i) => { - const redeemScript = bscript.fromASM(input.redeemScript) - - input.signs.forEach(sign => { - // rebuild the transaction each-time after the first - if (tx) { - // manually override the scriptSig? - if (sign.scriptSigBefore) { - tx.ins[i].script = bscript.fromASM(sign.scriptSigBefore) - } - - // rebuild - txb = TransactionBuilder.fromTransaction(tx, network) - } - - const keyPair2 = ECPair.fromWIF(sign.keyPair, network) - txb.sign(i, keyPair2, redeemScript, sign.hashType) - - // update the tx - tx = txb.buildIncomplete() - - // now verify the serialized scriptSig is as expected - assert.strictEqual(bscript.toASM(tx.ins[i].script), sign.scriptSig) - }) - }) - - tx = txb.build() - assert.strictEqual(tx.toHex(), f.txHex) - }) - }) - }) - - describe('various edge case', () => { - const network = NETWORKS.testnet - - it('should warn of high fee for segwit transaction based on VSize, not Size', () => { - const rawtx = '01000000000104fdaac89627208b4733484ca56bc291f4cf4fa8d7c5f29893c52b46788a0a' + - '1df90000000000fffffffffdaac89627208b4733484ca56bc291f4cf4fa8d7c5f29893c52b46788a0a1df9' + - '0100000000ffffffffa2ef7aaab316a3e5b5b0a78d1d35c774b95a079f9f0c762277a49caf1f26bca40000' + - '000000ffffffffa2ef7aaab316a3e5b5b0a78d1d35c774b95a079f9f0c762277a49caf1f26bca401000000' + - '00ffffffff0100040000000000001976a914cf307285359ab7ef6a2daa0522c7908ddf5fe7a988ac024730' + - '440220113324438816338406841775e079b04c50d04f241da652a4035b1017ea1ecf5502205802191eb49c' + - '54bf2a5667aea72e51c3ca92085efc60f12d1ebda3a64aff343201210283409659355b6d1cc3c32decd5d5' + - '61abaac86c37a353b52895a5e6c196d6f44802483045022100dc2892874e6d8708e3f5a058c5c9263cdf03' + - '969492270f89ee4933caf6daf8bb0220391dfe61a002709b63b9d64422d3db09b727839d1287e10a128a5d' + - 'b52a82309301210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f448024830' + - '450221009e3ed3a6ae93a018f443257b43e47b55cf7f7f3547d8807178072234686b22160220576121cfe6' + - '77c7eddf5575ea0a7c926247df6eca723c4f85df306e8bc08ea2df01210283409659355b6d1cc3c32decd5' + - 'd561abaac86c37a353b52895a5e6c196d6f44802473044022007be81ffd4297441ab10e740fc9bab9545a2' + - '194a565cd6aa4cc38b8eaffa343402201c5b4b61d73fa38e49c1ee68cc0e6dfd2f5dae453dd86eb142e87a' + - '0bafb1bc8401210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f44800000000' - const txb = TransactionBuilder.fromTransaction(Transaction.fromHex(rawtx)) - txb.__INPUTS[0].value = 241530 - txb.__INPUTS[1].value = 241530 - txb.__INPUTS[2].value = 248920 - txb.__INPUTS[3].value = 248920 - - assert.throws(() => { - txb.build() - }, new RegExp('Transaction has absurd fees')) - }) - - it('should classify witness inputs with witness = true during multisigning', () => { - const keyPair = ECPair.fromWIF('cRAwuVuVSBZMPu7hdrYvMCZ8eevzmkExjFbaBLhqnDdrezxN3nTS', network) - const witnessScript = Buffer.from('522102bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e22102d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea1952ae', 'hex') - const redeemScript = Buffer.from('002024376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5af', 'hex') - const scriptPubKey = Buffer.from('a914b64f1a3eacc1c8515592a6f10457e8ff90e4db6a87', 'hex') - const txb = new TransactionBuilder(network) - txb.setVersion(1) - txb.addInput('a4696c4b0cd27ec2e173ab1fa7d1cc639a98ee237cec95a77ca7ff4145791529', 1, 0xffffffff, scriptPubKey) - txb.addOutput(scriptPubKey, 99000) - txb.sign(0, keyPair, redeemScript, null, 100000, witnessScript) - - // 2-of-2 signed only once - const tx = txb.buildIncomplete() - - // Only input is segwit, so txid should be accurate with the final tx - assert.strictEqual(tx.getId(), 'f15d0a65b21b4471405b21a099f8b18e1ae4d46d55efbd0f4766cf11ad6cb821') - - const txHex = tx.toHex() - TransactionBuilder.fromTransaction(Transaction.fromHex(txHex)) - }) - - it('should handle badly pre-filled OP_0s', () => { - // OP_0 is used where a signature is missing - const redeemScripSig = bscript.fromASM('OP_0 OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae') - const redeemScript = bscript.fromASM('OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG') - - const tx = new Transaction() - tx.addInput(Buffer.from('cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f07149', 'hex'), 0, undefined, redeemScripSig) - tx.addOutput(Buffer.from('76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac', 'hex'), 1000) - - // now import the Transaction - const txb = TransactionBuilder.fromTransaction(tx, NETWORKS.testnet) - - const keyPair2 = ECPair.fromWIF('91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe', network) - txb.sign(0, keyPair2, redeemScript) - - const tx2 = txb.build() - assert.strictEqual(tx2.getId(), 'eab59618a564e361adef6d918bd792903c3d41bcf1220137364fb847880467f9') - assert.strictEqual(bscript.toASM(tx2.ins[0].script), 'OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae') - }) - - it('should not classify blank scripts as nonstandard', () => { - let txb = new TransactionBuilder() - txb.setVersion(1) - txb.addInput('aa94ab02c182214f090e99a0d57021caffd0f195a81c24602b1028b130b63e31', 0) - - const incomplete = txb.buildIncomplete().toHex() - const keyPair = ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy') - - // sign, as expected - txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000) - txb.sign(0, keyPair) - const txId = txb.build().getId() - assert.strictEqual(txId, '54f097315acbaedb92a95455da3368eb45981cdae5ffbc387a9afc872c0f29b3') - - // and, repeat - txb = TransactionBuilder.fromTransaction(Transaction.fromHex(incomplete)) - txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000) - txb.sign(0, keyPair) - const txId2 = txb.build().getId() - assert.strictEqual(txId, txId2) - }) - }) -}) +} diff --git a/ts_src/ecpair.ts b/ts_src/ecpair.ts index 3d74433..c951f76 100644 --- a/ts_src/ecpair.ts +++ b/ts_src/ecpair.ts @@ -19,15 +19,26 @@ interface ECPairOptions { rng?(arg0: number): Buffer; } -export interface ECPairInterface { +export interface Signer { + publicKey: Buffer; + network?: Network; + sign(hash: Buffer, lowR?: boolean): Buffer; + getPublicKey?(): Buffer; +} + +export interface SignerAsync { + publicKey: Buffer; + network?: Network; + sign(hash: Buffer, lowR?: boolean): Promise; + getPublicKey?(): Buffer; +} + +export interface ECPairInterface extends Signer { compressed: boolean; network: Network; - publicKey: Buffer; privateKey?: Buffer; toWIF(): string; - sign(hash: Buffer, lowR?: boolean): Buffer; verify(hash: Buffer, signature: Buffer): boolean; - getPublicKey?(): Buffer; } class ECPair implements ECPairInterface { diff --git a/ts_src/index.ts b/ts_src/index.ts index 1068839..4f2d498 100644 --- a/ts_src/index.ts +++ b/ts_src/index.ts @@ -14,7 +14,7 @@ export { Transaction } from './transaction'; export { TransactionBuilder } from './transaction_builder'; export { BIP32Interface } from 'bip32'; -export { ECPairInterface } from './ecpair'; +export { ECPairInterface, Signer, SignerAsync } from './ecpair'; export { Network } from './networks'; export { Payment, PaymentOpts, Stack, StackElement } from './payments'; export { OpCode } from './script'; diff --git a/ts_src/transaction_builder.ts b/ts_src/transaction_builder.ts index 0665719..c486285 100644 --- a/ts_src/transaction_builder.ts +++ b/ts_src/transaction_builder.ts @@ -2,7 +2,7 @@ import * as baddress from './address'; import { reverseBuffer } from './bufferutils'; import * as classify from './classify'; import * as bcrypto from './crypto'; -import { ECPairInterface } from './ecpair'; +import { Signer } from './ecpair'; import * as ECPair from './ecpair'; import { Network } from './networks'; import * as networks from './networks'; @@ -16,6 +16,27 @@ const typeforce = require('typeforce'); const SCRIPT_TYPES = classify.types; +const PREVOUT_TYPES: Set = new Set([ + // Raw + 'p2pkh', + 'p2pk', + 'p2wpkh', + 'p2ms', + // P2SH wrapped + 'p2sh-p2pkh', + 'p2sh-p2pk', + 'p2sh-p2wpkh', + 'p2sh-p2ms', + // P2WSH wrapped + 'p2wsh-p2pkh', + 'p2wsh-p2pk', + 'p2wsh-p2ms', + // P2SH-P2WSH wrapper + 'p2sh-p2wsh-p2pkh', + 'p2sh-p2wsh-p2pk', + 'p2sh-p2wsh-p2ms', +]); + type MaybeBuffer = Buffer | undefined; type TxbSignatures = Buffer[] | MaybeBuffer[]; type TxbPubkeys = MaybeBuffer[]; @@ -50,6 +71,24 @@ interface TxbOutput { maxSignatures?: number; } +interface TxbSignArg { + prevOutScriptType: string; + vin: number; + keyPair: Signer; + redeemScript?: Buffer; + hashType?: number; + witnessValue?: number; + witnessScript?: Buffer; +} + +function tfMessage(type: any, value: any, message: string): void { + try { + typeforce(type, value); + } catch (err) { + throw new Error(message); + } +} + function txIsString(tx: Buffer | string | Transaction): tx is string { return typeof tx === 'string' || tx instanceof String; } @@ -197,92 +236,28 @@ export class TransactionBuilder { } sign( - vin: number, - keyPair: ECPairInterface, + signParams: number | TxbSignArg, + keyPair?: Signer, redeemScript?: Buffer, hashType?: number, witnessValue?: number, witnessScript?: Buffer, ): void { - // TODO: remove keyPair.network matching in 4.0.0 - if (keyPair.network && keyPair.network !== this.network) - throw new TypeError('Inconsistent network'); - if (!this.__INPUTS[vin]) throw new Error('No input at index: ' + vin); - - hashType = hashType || Transaction.SIGHASH_ALL; - if (this.__needsOutputs(hashType)) - throw new Error('Transaction needs outputs'); - - const input = this.__INPUTS[vin]; - - // if redeemScript was previously provided, enforce consistency - if ( - input.redeemScript !== undefined && - redeemScript && - !input.redeemScript.equals(redeemScript) - ) { - throw new Error('Inconsistent redeemScript'); - } - - const ourPubKey = keyPair.publicKey || keyPair.getPublicKey!(); - if (!canSign(input)) { - if (witnessValue !== undefined) { - if (input.value !== undefined && input.value !== witnessValue) - throw new Error('Input did not match witnessValue'); - typeforce(types.Satoshi, witnessValue); - input.value = witnessValue; - } - - if (!canSign(input)) { - const prepared = prepareInput( - input, - ourPubKey, - redeemScript, - witnessScript, - ); - - // updates inline - Object.assign(input, prepared); - } - - if (!canSign(input)) throw Error(input.prevOutType + ' not supported'); - } - - // ready to sign - let signatureHash: Buffer; - if (input.hasWitness) { - signatureHash = this.__TX.hashForWitnessV0( - vin, - input.signScript as Buffer, - input.value as number, + trySign( + getSigningData( + this.network, + this.__INPUTS, + this.__needsOutputs.bind(this), + this.__TX, + signParams, + keyPair, + redeemScript, hashType, - ); - } else { - signatureHash = this.__TX.hashForSignature( - vin, - input.signScript as Buffer, - hashType, - ); - } - - // enforce in order signing of public keys - const signed = input.pubkeys!.some((pubKey, i) => { - if (!ourPubKey.equals(pubKey!)) return false; - if (input.signatures![i]) throw new Error('Signature already exists'); - - // TODO: add tests - if (ourPubKey.length !== 33 && input.hasWitness) { - throw new Error( - 'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH', - ); - } - - const signature = keyPair.sign(signatureHash, this.__USE_LOW_R); - input.signatures![i] = bscript.signature.encode(signature, hashType!); - return true; - }); - - if (!signed) throw new Error('Key pair cannot sign for this input'); + witnessValue, + witnessScript, + this.__USE_LOW_R, + ), + ); } private __addInputUnsafe( @@ -976,3 +951,361 @@ function canSign(input: TxbInput): boolean { function signatureHashType(buffer: Buffer): number { return buffer.readUInt8(buffer.length - 1); } + +function checkSignArgs(inputs: TxbInput[], signParams: TxbSignArg): void { + if (!PREVOUT_TYPES.has(signParams.prevOutScriptType)) { + throw new TypeError( + `Unknown prevOutScriptType "${signParams.prevOutScriptType}"`, + ); + } + tfMessage( + typeforce.Number, + signParams.vin, + `sign must include vin parameter as Number (input index)`, + ); + tfMessage( + types.Signer, + signParams.keyPair, + `sign must include keyPair parameter as Signer interface`, + ); + tfMessage( + typeforce.maybe(typeforce.Number), + signParams.hashType, + `sign hashType parameter must be a number`, + ); + const prevOutType = (inputs[signParams.vin] || []).prevOutType; + const posType = signParams.prevOutScriptType; + switch (posType) { + case 'p2pkh': + if (prevOutType && prevOutType !== 'pubkeyhash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2pkh: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2pk': + if (prevOutType && prevOutType !== 'pubkey') { + throw new TypeError( + `input #${signParams.vin} is not of type p2pk: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2wpkh': + if (prevOutType && prevOutType !== 'witnesspubkeyhash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2wpkh: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2ms': + if (prevOutType && prevOutType !== 'multisig') { + throw new TypeError( + `input #${signParams.vin} is not of type p2ms: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2sh-p2wpkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2sh-p2wpkh: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2sh-p2ms': + case 'p2sh-p2pk': + case 'p2sh-p2pkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2wsh-p2ms': + case 'p2wsh-p2pk': + case 'p2wsh-p2pkh': + if (prevOutType && prevOutType !== 'witnessscripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.Buffer, + signParams.witnessScript, + `${posType} requires witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2sh-p2wsh-p2ms': + case 'p2sh-p2wsh-p2pk': + case 'p2sh-p2wsh-p2pkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.Buffer, + signParams.witnessScript, + `${posType} requires witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires witnessScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessScript`, + ); + break; + } +} + +function trySign({ + input, + ourPubKey, + keyPair, + signatureHash, + hashType, + useLowR, +}: SigningData): void { + // enforce in order signing of public keys + let signed = false; + for (const [i, pubKey] of input.pubkeys!.entries()) { + if (!ourPubKey.equals(pubKey!)) continue; + if (input.signatures![i]) throw new Error('Signature already exists'); + + // TODO: add tests + if (ourPubKey.length !== 33 && input.hasWitness) { + throw new Error( + 'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH', + ); + } + + const signature = keyPair.sign(signatureHash, useLowR); + input.signatures![i] = bscript.signature.encode(signature, hashType); + signed = true; + } + + if (!signed) throw new Error('Key pair cannot sign for this input'); +} + +interface SigningData { + input: TxbInput; + ourPubKey: Buffer; + keyPair: Signer; + signatureHash: Buffer; + hashType: number; + useLowR: boolean; +} + +type HashTypeCheck = (hashType: number) => boolean; + +function getSigningData( + network: Network, + inputs: TxbInput[], + needsOutputs: HashTypeCheck, + tx: Transaction, + signParams: number | TxbSignArg, + keyPair?: Signer, + redeemScript?: Buffer, + hashType?: number, + witnessValue?: number, + witnessScript?: Buffer, + useLowR?: boolean, +): SigningData { + let vin: number; + if (typeof signParams === 'number') { + console.warn( + 'DEPRECATED: TransactionBuilder sign method arguments ' + + 'will change in v6, please use the TxbSignArg interface', + ); + vin = signParams; + } else if (typeof signParams === 'object') { + checkSignArgs(inputs, signParams); + ({ + vin, + keyPair, + redeemScript, + hashType, + witnessValue, + witnessScript, + } = signParams); + } else { + throw new TypeError( + 'TransactionBuilder sign first arg must be TxbSignArg or number', + ); + } + if (keyPair === undefined) { + throw new Error('sign requires keypair'); + } + // TODO: remove keyPair.network matching in 4.0.0 + if (keyPair.network && keyPair.network !== network) + throw new TypeError('Inconsistent network'); + if (!inputs[vin]) throw new Error('No input at index: ' + vin); + + hashType = hashType || Transaction.SIGHASH_ALL; + if (needsOutputs(hashType)) throw new Error('Transaction needs outputs'); + + const input = inputs[vin]; + + // if redeemScript was previously provided, enforce consistency + if ( + input.redeemScript !== undefined && + redeemScript && + !input.redeemScript.equals(redeemScript) + ) { + throw new Error('Inconsistent redeemScript'); + } + + const ourPubKey = + keyPair.publicKey || (keyPair.getPublicKey && keyPair.getPublicKey()); + if (!canSign(input)) { + if (witnessValue !== undefined) { + if (input.value !== undefined && input.value !== witnessValue) + throw new Error('Input did not match witnessValue'); + typeforce(types.Satoshi, witnessValue); + input.value = witnessValue; + } + + if (!canSign(input)) { + const prepared = prepareInput( + input, + ourPubKey, + redeemScript, + witnessScript, + ); + + // updates inline + Object.assign(input, prepared); + } + + if (!canSign(input)) throw Error(input.prevOutType + ' not supported'); + } + + // ready to sign + let signatureHash: Buffer; + if (input.hasWitness) { + signatureHash = tx.hashForWitnessV0( + vin, + input.signScript as Buffer, + input.value as number, + hashType, + ); + } else { + signatureHash = tx.hashForSignature( + vin, + input.signScript as Buffer, + hashType, + ); + } + + return { + input, + ourPubKey, + keyPair, + signatureHash, + hashType, + useLowR: !!useLowR, + }; +} diff --git a/ts_src/types.ts b/ts_src/types.ts index 06c247d..2e41267 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -12,6 +12,14 @@ BIP32Path.toJSON = (): string => { return 'BIP32 derivation path'; }; +export function Signer(obj: any): boolean { + return ( + (typeforce.Buffer(obj.publicKey) || + typeof obj.getPublicKey === 'function') && + typeof obj.sign === 'function' + ); +} + const SATOSHI_MAX: number = 21 * 1e14; export function Satoshi(value: number): boolean { return typeforce.UInt53(value) && value <= SATOSHI_MAX; diff --git a/types/ecpair.d.ts b/types/ecpair.d.ts index 5f301b2..8b7d193 100644 --- a/types/ecpair.d.ts +++ b/types/ecpair.d.ts @@ -5,15 +5,24 @@ interface ECPairOptions { network?: Network; rng?(arg0: number): Buffer; } -export interface ECPairInterface { +export interface Signer { + publicKey: Buffer; + network?: Network; + sign(hash: Buffer, lowR?: boolean): Buffer; + getPublicKey?(): Buffer; +} +export interface SignerAsync { + publicKey: Buffer; + network?: Network; + sign(hash: Buffer, lowR?: boolean): Promise; + getPublicKey?(): Buffer; +} +export interface ECPairInterface extends Signer { compressed: boolean; network: Network; - publicKey: Buffer; privateKey?: Buffer; toWIF(): string; - sign(hash: Buffer, lowR?: boolean): Buffer; verify(hash: Buffer, signature: Buffer): boolean; - getPublicKey?(): Buffer; } declare class ECPair implements ECPairInterface { private __D?; diff --git a/types/index.d.ts b/types/index.d.ts index 28046df..93d72e4 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -11,7 +11,7 @@ export { OPS as opcodes } from './script'; export { Transaction } from './transaction'; export { TransactionBuilder } from './transaction_builder'; export { BIP32Interface } from 'bip32'; -export { ECPairInterface } from './ecpair'; +export { ECPairInterface, Signer, SignerAsync } from './ecpair'; export { Network } from './networks'; export { Payment, PaymentOpts, Stack, StackElement } from './payments'; export { OpCode } from './script'; diff --git a/types/transaction_builder.d.ts b/types/transaction_builder.d.ts index f993807..2799464 100644 --- a/types/transaction_builder.d.ts +++ b/types/transaction_builder.d.ts @@ -1,7 +1,16 @@ /// -import { ECPairInterface } from './ecpair'; +import { Signer } from './ecpair'; import { Network } from './networks'; import { Transaction } from './transaction'; +interface TxbSignArg { + prevOutScriptType: string; + vin: number; + keyPair: Signer; + redeemScript?: Buffer; + hashType?: number; + witnessValue?: number; + witnessScript?: Buffer; +} export declare class TransactionBuilder { network: Network; maximumFeeRate: number; @@ -18,7 +27,7 @@ export declare class TransactionBuilder { addOutput(scriptPubKey: string | Buffer, value: number): number; build(): Transaction; buildIncomplete(): Transaction; - sign(vin: number, keyPair: ECPairInterface, redeemScript?: Buffer, hashType?: number, witnessValue?: number, witnessScript?: Buffer): void; + sign(signParams: number | TxbSignArg, keyPair?: Signer, redeemScript?: Buffer, hashType?: number, witnessValue?: number, witnessScript?: Buffer): void; private __addInputUnsafe; private __build; private __canModifyInputs; @@ -26,3 +35,4 @@ export declare class TransactionBuilder { private __canModifyOutputs; private __overMaximumFees; } +export {}; diff --git a/types/types.d.ts b/types/types.d.ts index 242bab8..e7c588d 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -3,6 +3,7 @@ export declare function BIP32Path(value: string): boolean; export declare namespace BIP32Path { var toJSON: () => string; } +export declare function Signer(obj: any): boolean; export declare function Satoshi(value: number): boolean; export declare const ECPoint: any; export declare const Network: any;