diff --git a/package.json b/package.json index b8ec5f3..7882308 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "lint": "tslint -p tsconfig.json -c tslint.json", "nobuild:coverage-report": "nyc report --reporter=lcov", "nobuild:coverage-html": "nyc report --reporter=html", - "nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha", + "nobuild:coverage": "nyc --check-coverage --branches 85 --functions 90 --lines 90 mocha", "nobuild:integration": "mocha --timeout 50000 test/integration/", "nobuild:unit": "mocha", "prettier": "prettier 'ts_src/**/*.ts' --ignore-path ./.prettierignore", diff --git a/src/transaction_builder.js b/src/transaction_builder.js index 9abf242..73acd3a 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -13,6 +13,20 @@ 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-p2wpkh', + 'p2sh-p2ms', + // P2WSH wrapped + 'p2wsh-p2ms', + // P2SH-P2WSH wrapper + 'p2sh-p2wsh-p2ms', +]); function txIsString(tx) { return typeof tx === 'string' || tx instanceof String; } @@ -118,7 +132,103 @@ class TransactionBuilder { buildIncomplete() { return this.__build(true); } - sign(vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) { + sign( + signParams, + keyPair, + redeemScript, + hashType, + witnessValue, + witnessScript, + ) { + 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') { + if (!PREVOUT_TYPES.has(signParams.prevOutScriptType)) { + throw new TypeError( + `Unknown prevOutScriptType "${signParams.prevOutScriptType}"`, + ); + } + typeforce(typeforce.tuple(typeforce.Number, typeforce.Object), [ + signParams.vin, + signParams.keyPair, + ]); + vin = signParams.vin; + keyPair = signParams.keyPair; + const prevOutType = (this.__INPUTS[vin] || []).prevOutType; + switch (signParams.prevOutScriptType) { + case 'p2pkh': + if (prevOutType !== 'pubkeyhash') { + throw new TypeError(`input #${vin} is not of type p2pkh`); + } + break; + case 'p2pk': + if (prevOutType !== 'pubkey') { + throw new TypeError(`input #${vin} is not of type p2pk`); + } + break; + case 'p2wpkh': + if (prevOutType !== 'witnesspubkeyhash') { + throw new TypeError(`input #${vin} is not of type p2wpkh`); + } + typeforce(typeforce.Buffer, signParams.witnessScript); + typeforce(typeforce.Satoshi, signParams.witnessValue); + witnessScript = signParams.witnessScript; + witnessValue = signParams.witnessValue; + break; + case 'p2ms': + if (prevOutType !== 'multisig') { + throw new TypeError(`input #${vin} is not of type p2ms`); + } + break; + case 'p2sh-p2wpkh': + if (prevOutType !== 'scripthash') { + throw new TypeError(`input #${vin} is not of type p2sh-p2wpkh`); + } + typeforce(typeforce.Buffer, signParams.witnessScript); + typeforce(typeforce.Buffer, signParams.redeemScript); + typeforce(typeforce.Satoshi, signParams.witnessValue); + witnessScript = signParams.witnessScript; + redeemScript = signParams.redeemScript; + witnessValue = signParams.witnessValue; + break; + case 'p2sh-p2ms': + if (prevOutType !== 'scripthash') { + throw new TypeError(`input #${vin} is not of type p2sh-p2ms`); + } + typeforce(typeforce.Buffer, signParams.redeemScript); + redeemScript = signParams.redeemScript; + break; + case 'p2wsh-p2ms': + if (prevOutType !== 'witnessscripthash') { + throw new TypeError(`input #${vin} is not of type p2wsh-p2ms`); + } + typeforce(typeforce.Buffer, signParams.witnessScript); + typeforce(typeforce.Satoshi, signParams.witnessValue); + witnessScript = signParams.witnessScript; + witnessValue = signParams.witnessValue; + break; + case 'p2sh-p2wsh-p2ms': + if (prevOutType !== 'scripthash') { + throw new TypeError(`input #${vin} is not of type p2sh-p2wsh-p2ms`); + } + typeforce(typeforce.Buffer, signParams.witnessScript); + typeforce(typeforce.Buffer, signParams.redeemScript); + typeforce(typeforce.Satoshi, signParams.witnessValue); + witnessScript = signParams.witnessScript; + redeemScript = signParams.redeemScript; + witnessValue = signParams.witnessValue; + break; + } + } else { + throw new TypeError( + 'TransactionBuilder sign first arg must be TxbSignArg or number', + ); + } // TODO: remove keyPair.network matching in 4.0.0 if (keyPair.network && keyPair.network !== this.network) throw new TypeError('Inconsistent network'); diff --git a/ts_src/transaction_builder.ts b/ts_src/transaction_builder.ts index 0665719..b4d794d 100644 --- a/ts_src/transaction_builder.ts +++ b/ts_src/transaction_builder.ts @@ -16,6 +16,21 @@ const typeforce = require('typeforce'); const SCRIPT_TYPES = classify.types; +const PREVOUT_TYPES: Set = new Set([ + // Raw + 'p2pkh', + 'p2pk', + 'p2wpkh', + 'p2ms', + // P2SH wrapped + 'p2sh-p2wpkh', + 'p2sh-p2ms', + // P2WSH wrapped + 'p2wsh-p2ms', + // P2SH-P2WSH wrapper + 'p2sh-p2wsh-p2ms', +]); + type MaybeBuffer = Buffer | undefined; type TxbSignatures = Buffer[] | MaybeBuffer[]; type TxbPubkeys = MaybeBuffer[]; @@ -50,6 +65,16 @@ interface TxbOutput { maxSignatures?: number; } +interface TxbSignArg { + prevOutScriptType: string; + vin: number; + keyPair: ECPairInterface; + redeemScript?: Buffer; + hashType?: number; + witnessValue?: number; + witnessScript?: Buffer; +} + function txIsString(tx: Buffer | string | Transaction): tx is string { return typeof tx === 'string' || tx instanceof String; } @@ -197,13 +222,102 @@ export class TransactionBuilder { } sign( - vin: number, + signParams: number | TxbSignArg, keyPair: ECPairInterface, redeemScript?: Buffer, hashType?: number, witnessValue?: number, witnessScript?: Buffer, ): void { + 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') { + if (!PREVOUT_TYPES.has(signParams.prevOutScriptType)) { + throw new TypeError( + `Unknown prevOutScriptType "${signParams.prevOutScriptType}"`, + ); + } + typeforce(typeforce.tuple(typeforce.Number, typeforce.Object), [ + signParams.vin, + signParams.keyPair, + ]); + vin = signParams.vin; + keyPair = signParams.keyPair; + const prevOutType = (this.__INPUTS[vin] || []).prevOutType; + switch (signParams.prevOutScriptType) { + case 'p2pkh': + if (prevOutType !== 'pubkeyhash') { + throw new TypeError(`input #${vin} is not of type p2pkh`); + } + break; + case 'p2pk': + if (prevOutType !== 'pubkey') { + throw new TypeError(`input #${vin} is not of type p2pk`); + } + break; + case 'p2wpkh': + if (prevOutType !== 'witnesspubkeyhash') { + throw new TypeError(`input #${vin} is not of type p2wpkh`); + } + typeforce(typeforce.Buffer, signParams.witnessScript); + typeforce(typeforce.Satoshi, signParams.witnessValue); + witnessScript = signParams.witnessScript; + witnessValue = signParams.witnessValue; + break; + case 'p2ms': + if (prevOutType !== 'multisig') { + throw new TypeError(`input #${vin} is not of type p2ms`); + } + break; + case 'p2sh-p2wpkh': + if (prevOutType !== 'scripthash') { + throw new TypeError(`input #${vin} is not of type p2sh-p2wpkh`); + } + typeforce(typeforce.Buffer, signParams.witnessScript); + typeforce(typeforce.Buffer, signParams.redeemScript); + typeforce(typeforce.Satoshi, signParams.witnessValue); + witnessScript = signParams.witnessScript; + redeemScript = signParams.redeemScript; + witnessValue = signParams.witnessValue; + break; + case 'p2sh-p2ms': + if (prevOutType !== 'scripthash') { + throw new TypeError(`input #${vin} is not of type p2sh-p2ms`); + } + typeforce(typeforce.Buffer, signParams.redeemScript); + redeemScript = signParams.redeemScript; + break; + case 'p2wsh-p2ms': + if (prevOutType !== 'witnessscripthash') { + throw new TypeError(`input #${vin} is not of type p2wsh-p2ms`); + } + typeforce(typeforce.Buffer, signParams.witnessScript); + typeforce(typeforce.Satoshi, signParams.witnessValue); + witnessScript = signParams.witnessScript; + witnessValue = signParams.witnessValue; + break; + case 'p2sh-p2wsh-p2ms': + if (prevOutType !== 'scripthash') { + throw new TypeError(`input #${vin} is not of type p2sh-p2wsh-p2ms`); + } + typeforce(typeforce.Buffer, signParams.witnessScript); + typeforce(typeforce.Buffer, signParams.redeemScript); + typeforce(typeforce.Satoshi, signParams.witnessValue); + witnessScript = signParams.witnessScript; + redeemScript = signParams.redeemScript; + witnessValue = signParams.witnessValue; + break; + } + } else { + throw new TypeError( + 'TransactionBuilder sign first arg must be TxbSignArg or number', + ); + } // TODO: remove keyPair.network matching in 4.0.0 if (keyPair.network && keyPair.network !== this.network) throw new TypeError('Inconsistent network'); diff --git a/types/transaction_builder.d.ts b/types/transaction_builder.d.ts index f993807..9441d6b 100644 --- a/types/transaction_builder.d.ts +++ b/types/transaction_builder.d.ts @@ -2,6 +2,15 @@ import { ECPairInterface } from './ecpair'; import { Network } from './networks'; import { Transaction } from './transaction'; +interface TxbSignArg { + prevOutScriptType: string; + vin: number; + keyPair: ECPairInterface; + 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: ECPairInterface, 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 {};