diff --git a/src/psbt.js b/src/psbt.js index b80951f..693bfc3 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -305,10 +305,24 @@ class Psbt { const input = utils_1.checkForInput(this.data.inputs, inputIndex); return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE); } + inputHasHDKey(inputIndex, root) { + const input = utils_1.checkForInput(this.data.inputs, inputIndex); + const derivationIsMine = bip32DerivationIsMine(root); + return ( + !!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine) + ); + } outputHasPubkey(outputIndex, pubkey) { const output = utils_1.checkForOutput(this.data.outputs, outputIndex); return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE); } + outputHasHDKey(outputIndex, root) { + const output = utils_1.checkForOutput(this.data.outputs, outputIndex); + const derivationIsMine = bip32DerivationIsMine(root); + return ( + !!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine) + ); + } validateSignaturesOfAllInputs() { utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one const results = range(this.data.inputs.length).map(idx => @@ -696,6 +710,13 @@ const isP2PKH = isPaymentFactory(payments.p2pkh); const isP2WPKH = isPaymentFactory(payments.p2wpkh); const isP2WSHScript = isPaymentFactory(payments.p2wsh); const isP2SHScript = isPaymentFactory(payments.p2sh); +function bip32DerivationIsMine(root) { + return d => { + if (!d.masterFingerprint.equals(root.fingerprint)) return false; + if (!root.derivePath(d.path).publicKey.equals(d.pubkey)) return false; + return true; + }; +} function check32Bit(num) { if ( typeof num !== 'number' || diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index a5a2214..c33e9cf 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -1,4 +1,5 @@ import * as assert from 'assert'; +import * as crypto from 'crypto'; import { describe, it } from 'mocha'; import { bip32, ECPair, networks as NETWORKS, payments, Psbt } from '..'; @@ -641,6 +642,29 @@ describe(`Psbt`, () => { ].forEach(getInputTypeTest); }); + describe('inputHasHDKey', () => { + it('should return true if HD key is present', () => { + const root = bip32.fromSeed(crypto.randomBytes(32)); + const root2 = bip32.fromSeed(crypto.randomBytes(32)); + const path = "m/0'/0"; + const psbt = new Psbt(); + psbt.addInput({ + hash: + '0000000000000000000000000000000000000000000000000000000000000000', + index: 0, + bip32Derivation: [ + { + masterFingerprint: root.fingerprint, + path, + pubkey: root.derivePath(path).publicKey, + }, + ], + }); + assert.strictEqual(psbt.inputHasHDKey(0, root), true); + assert.strictEqual(psbt.inputHasHDKey(0, root2), false); + }); + }); + describe('inputHasPubkey', () => { it('should throw', () => { const psbt = new Psbt(); @@ -712,6 +736,37 @@ describe(`Psbt`, () => { }); }); + describe('outputHasHDKey', () => { + it('should return true if HD key is present', () => { + const root = bip32.fromSeed(crypto.randomBytes(32)); + const root2 = bip32.fromSeed(crypto.randomBytes(32)); + const path = "m/0'/0"; + const psbt = new Psbt(); + psbt + .addInput({ + hash: + '0000000000000000000000000000000000000000000000000000000000000000', + index: 0, + }) + .addOutput({ + script: Buffer.from( + '0014000102030405060708090a0b0c0d0e0f00010203', + 'hex', + ), + value: 2000, + bip32Derivation: [ + { + masterFingerprint: root.fingerprint, + path, + pubkey: root.derivePath(path).publicKey, + }, + ], + }); + assert.strictEqual(psbt.outputHasHDKey(0, root), true); + assert.strictEqual(psbt.outputHasHDKey(0, root2), false); + }); + }); + describe('outputHasPubkey', () => { it('should throw', () => { const psbt = new Psbt(); diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 39d3a4c..8f06e15 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1,6 +1,7 @@ import { Psbt as PsbtBase } from 'bip174'; import * as varuint from 'bip174/src/lib/converter/varint'; import { + Bip32Derivation, KeyValue, PartialSig, PsbtGlobalUpdate, @@ -377,11 +378,27 @@ export class Psbt { return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE); } + inputHasHDKey(inputIndex: number, root: HDSigner): boolean { + const input = checkForInput(this.data.inputs, inputIndex); + const derivationIsMine = bip32DerivationIsMine(root); + return ( + !!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine) + ); + } + outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean { const output = checkForOutput(this.data.outputs, outputIndex); return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE); } + outputHasHDKey(outputIndex: number, root: HDSigner): boolean { + const output = checkForOutput(this.data.outputs, outputIndex); + const derivationIsMine = bip32DerivationIsMine(root); + return ( + !!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine) + ); + } + validateSignaturesOfAllInputs(): boolean { checkForInput(this.data.inputs, 0); // making sure we have at least one const results = range(this.data.inputs.length).map(idx => @@ -905,6 +922,16 @@ const isP2WPKH = isPaymentFactory(payments.p2wpkh); const isP2WSHScript = isPaymentFactory(payments.p2wsh); const isP2SHScript = isPaymentFactory(payments.p2sh); +function bip32DerivationIsMine( + root: HDSigner, +): (d: Bip32Derivation) => boolean { + return (d: Bip32Derivation): boolean => { + if (!d.masterFingerprint.equals(root.fingerprint)) return false; + if (!root.derivePath(d.path).publicKey.equals(d.pubkey)) return false; + return true; + }; +} + function check32Bit(num: number): void { if ( typeof num !== 'number' || diff --git a/types/psbt.d.ts b/types/psbt.d.ts index 4d1c099..eb239dc 100644 --- a/types/psbt.d.ts +++ b/types/psbt.d.ts @@ -71,7 +71,9 @@ export declare class Psbt { finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this; getInputType(inputIndex: number): AllScriptType; inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean; + inputHasHDKey(inputIndex: number, root: HDSigner): boolean; outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean; + outputHasHDKey(outputIndex: number, root: HDSigner): boolean; validateSignaturesOfAllInputs(): boolean; validateSignaturesOfInput(inputIndex: number, pubkey?: Buffer): boolean; signAllInputsHD(hdKeyPair: HDSigner, sighashTypes?: number[]): this;