Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 50 additions & 27 deletions src/cjs/psbt.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -559,10 +559,7 @@ class Psbt {
}
return validationResultCount > 0;
}
signAllInputsHD(
hdKeyPair,
sighashTypes = [transaction_js_1.Transaction.SIGHASH_ALL],
) {
signAllInputsHD(hdKeyPair, sighashTypes) {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
throw new Error('Need HDSigner to sign input');
}
Expand All @@ -580,10 +577,7 @@ class Psbt {
}
return this;
}
signAllInputsHDAsync(
hdKeyPair,
sighashTypes = [transaction_js_1.Transaction.SIGHASH_ALL],
) {
signAllInputsHDAsync(hdKeyPair, sighashTypes) {
return new Promise((resolve, reject) => {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
return reject(new Error('Need HDSigner to sign input'));
Expand All @@ -610,23 +604,15 @@ class Psbt {
});
});
}
signInputHD(
inputIndex,
hdKeyPair,
sighashTypes = [transaction_js_1.Transaction.SIGHASH_ALL],
) {
signInputHD(inputIndex, hdKeyPair, sighashTypes) {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
throw new Error('Need HDSigner to sign input');
}
const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes));
return this;
}
signInputHDAsync(
inputIndex,
hdKeyPair,
sighashTypes = [transaction_js_1.Transaction.SIGHASH_ALL],
) {
signInputHDAsync(inputIndex, hdKeyPair, sighashTypes) {
return new Promise((resolve, reject) => {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
return reject(new Error('Need HDSigner to sign input'));
Expand Down Expand Up @@ -1565,18 +1551,16 @@ function getScriptFromInput(inputIndex, input, cache) {
}
function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
const input = (0, bip174_2.checkForInput)(inputs, inputIndex);
if ((0, bip371_js_1.isTaprootInput)(input)) {
return getTaprootSignersFromHD(input, hdKeyPair);
}
if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
throw new Error('Need bip32Derivation to sign with HD');
}
const myDerivations = input.bip32Derivation
.map(bipDv => {
if (tools.compare(bipDv.masterFingerprint, hdKeyPair.fingerprint) === 0) {
return bipDv;
} else {
return;
}
})
.filter(v => !!v);
const myDerivations = input.bip32Derivation.filter(
bipDv =>
tools.compare(bipDv.masterFingerprint, hdKeyPair.fingerprint) === 0,
);
if (myDerivations.length === 0) {
throw new Error(
'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint',
Expand All @@ -1591,6 +1575,45 @@ function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
});
return signers;
}
function getTaprootSignersFromHD(input, hdKeyPair) {
if (!input.tapBip32Derivation || input.tapBip32Derivation.length === 0) {
throw new Error('Need tapBip32Derivation to sign with HD');
}
const myDerivations = input.tapBip32Derivation.filter(
bipDv =>
tools.compare(bipDv.masterFingerprint, hdKeyPair.fingerprint) === 0,
);
if (myDerivations.length === 0) {
throw new Error(
'Need one tapBip32Derivation masterFingerprint to match the HDSigner fingerprint',
);
}
const signers = myDerivations.map(bipDv => {
const node = hdKeyPair.derivePath(bipDv.path);
if (
tools.compare(bipDv.pubkey, (0, bip371_js_1.toXOnly)(node.publicKey)) !==
0
) {
throw new Error('pubkey did not match tapBip32Derivation');
}
// Key-path spend: leafHashes is empty, tweak the derived key
if (!bipDv.leafHashes || bipDv.leafHashes.length === 0) {
if (typeof node.tweak !== 'function') {
throw new Error(
'HDSigner must implement tweak method for Taproot key-path signing',
);
}
const tweakHash = (0, bip341_js_1.tapTweakHash)(
(0, bip371_js_1.toXOnly)(node.publicKey),
input.tapMerkleRoot,
);
return node.tweak(tweakHash);
}
// Script-path spend: return untweaked derived node
return node;
});
return signers;
}
function getSortedSigs(script, partialSig) {
const p2ms = payments.p2ms({ output: script });
// for each pubkey in order of p2ms script
Expand Down
11 changes: 11 additions & 0 deletions src/cjs/psbt.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,24 @@ export interface HDSigner extends HDSignerBase {
* Return a 64 byte signature (32 byte r and 32 byte s in that order)
*/
sign(hash: Uint8Array): Uint8Array;
/**
* Schnorr sign for taproot. Required for taproot HD signing.
*/
signSchnorr?(hash: Uint8Array): Uint8Array;
/**
* Tweak the keypair for taproot key-path spending.
* Applies the tap tweak to derive the output key from the internal key.
*/
tweak?(t: Uint8Array): Signer;
}
/**
* Same as above but with async sign method
*/
export interface HDSignerAsync extends HDSignerBase {
derivePath(path: string): HDSignerAsync;
sign(hash: Uint8Array): Promise<Uint8Array>;
signSchnorr?(hash: Uint8Array): Promise<Uint8Array>;
tweak?(t: Uint8Array): Signer;
}
export interface Signer {
publicKey: Uint8Array;
Expand Down
66 changes: 48 additions & 18 deletions src/esm/psbt.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { fromOutputScript, toOutputScript } from './address.js';
import { cloneBuffer, reverseBuffer } from './bufferutils.js';
import { bitcoin as btcNetwork } from './networks.js';
import * as payments from './payments/index.js';
import { tapleafHash } from './payments/bip341.js';
import { tapleafHash, tapTweakHash } from './payments/bip341.js';
import * as bscript from './script.js';
import { Transaction } from './transaction.js';
import {
Expand Down Expand Up @@ -518,7 +518,7 @@ export class Psbt {
}
return validationResultCount > 0;
}
signAllInputsHD(hdKeyPair, sighashTypes = [Transaction.SIGHASH_ALL]) {
signAllInputsHD(hdKeyPair, sighashTypes) {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
throw new Error('Need HDSigner to sign input');
}
Expand All @@ -536,7 +536,7 @@ export class Psbt {
}
return this;
}
signAllInputsHDAsync(hdKeyPair, sighashTypes = [Transaction.SIGHASH_ALL]) {
signAllInputsHDAsync(hdKeyPair, sighashTypes) {
return new Promise((resolve, reject) => {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
return reject(new Error('Need HDSigner to sign input'));
Expand All @@ -563,19 +563,15 @@ export class Psbt {
});
});
}
signInputHD(inputIndex, hdKeyPair, sighashTypes = [Transaction.SIGHASH_ALL]) {
signInputHD(inputIndex, hdKeyPair, sighashTypes) {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
throw new Error('Need HDSigner to sign input');
}
const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes));
return this;
}
signInputHDAsync(
inputIndex,
hdKeyPair,
sighashTypes = [Transaction.SIGHASH_ALL],
) {
signInputHDAsync(inputIndex, hdKeyPair, sighashTypes) {
return new Promise((resolve, reject) => {
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
return reject(new Error('Need HDSigner to sign input'));
Expand Down Expand Up @@ -1487,18 +1483,16 @@ function getScriptFromInput(inputIndex, input, cache) {
}
function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
const input = checkForInput(inputs, inputIndex);
if (isTaprootInput(input)) {
return getTaprootSignersFromHD(input, hdKeyPair);
}
if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
throw new Error('Need bip32Derivation to sign with HD');
}
const myDerivations = input.bip32Derivation
.map(bipDv => {
if (tools.compare(bipDv.masterFingerprint, hdKeyPair.fingerprint) === 0) {
return bipDv;
} else {
return;
}
})
.filter(v => !!v);
const myDerivations = input.bip32Derivation.filter(
bipDv =>
tools.compare(bipDv.masterFingerprint, hdKeyPair.fingerprint) === 0,
);
if (myDerivations.length === 0) {
throw new Error(
'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint',
Expand All @@ -1513,6 +1507,42 @@ function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
});
return signers;
}
function getTaprootSignersFromHD(input, hdKeyPair) {
if (!input.tapBip32Derivation || input.tapBip32Derivation.length === 0) {
throw new Error('Need tapBip32Derivation to sign with HD');
}
const myDerivations = input.tapBip32Derivation.filter(
bipDv =>
tools.compare(bipDv.masterFingerprint, hdKeyPair.fingerprint) === 0,
);
if (myDerivations.length === 0) {
throw new Error(
'Need one tapBip32Derivation masterFingerprint to match the HDSigner fingerprint',
);
}
const signers = myDerivations.map(bipDv => {
const node = hdKeyPair.derivePath(bipDv.path);
if (tools.compare(bipDv.pubkey, toXOnly(node.publicKey)) !== 0) {
throw new Error('pubkey did not match tapBip32Derivation');
}
// Key-path spend: leafHashes is empty, tweak the derived key
if (!bipDv.leafHashes || bipDv.leafHashes.length === 0) {
if (typeof node.tweak !== 'function') {
throw new Error(
'HDSigner must implement tweak method for Taproot key-path signing',
);
}
const tweakHash = tapTweakHash(
toXOnly(node.publicKey),
input.tapMerkleRoot,
);
return node.tweak(tweakHash);
}
// Script-path spend: return untweaked derived node
return node;
});
return signers;
}
function getSortedSigs(script, partialSig) {
const p2ms = payments.p2ms({ output: script });
// for each pubkey in order of p2ms script
Expand Down
Loading