Add HD signer methods
This commit is contained in:
parent
1326e0cc42
commit
4366b621d7
5 changed files with 478 additions and 0 deletions
ts_src
166
ts_src/psbt.ts
166
ts_src/psbt.ts
|
@ -332,6 +332,107 @@ export class Psbt {
|
|||
return results.every(res => res === true);
|
||||
}
|
||||
|
||||
signHD(
|
||||
hdKeyPair: HDSigner,
|
||||
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
||||
): this {
|
||||
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
||||
throw new Error('Need HDSigner to sign input');
|
||||
}
|
||||
|
||||
const results: boolean[] = [];
|
||||
for (const i of range(this.data.inputs.length)) {
|
||||
try {
|
||||
this.signInputHD(i, hdKeyPair, sighashTypes);
|
||||
results.push(true);
|
||||
} catch (err) {
|
||||
results.push(false);
|
||||
}
|
||||
}
|
||||
if (results.every(v => v === false)) {
|
||||
throw new Error('No inputs were signed');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
signHDAsync(
|
||||
hdKeyPair: HDSigner | HDSignerAsync,
|
||||
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
||||
): Promise<void> {
|
||||
return new Promise(
|
||||
(resolve, reject): any => {
|
||||
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
||||
return reject(new Error('Need HDSigner to sign input'));
|
||||
}
|
||||
|
||||
const results: boolean[] = [];
|
||||
const promises: Array<Promise<void>> = [];
|
||||
for (const i of range(this.data.inputs.length)) {
|
||||
promises.push(
|
||||
this.signInputHDAsync(i, hdKeyPair, sighashTypes).then(
|
||||
() => {
|
||||
results.push(true);
|
||||
},
|
||||
() => {
|
||||
results.push(false);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
return Promise.all(promises).then(() => {
|
||||
if (results.every(v => v === false)) {
|
||||
return reject(new Error('No inputs were signed'));
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
signInputHD(
|
||||
inputIndex: number,
|
||||
hdKeyPair: HDSigner,
|
||||
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
||||
): this {
|
||||
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
||||
throw new Error('Need HDSigner to sign input');
|
||||
}
|
||||
const signers = getSignersFromHD(
|
||||
inputIndex,
|
||||
this.data.inputs,
|
||||
hdKeyPair,
|
||||
) as Signer[];
|
||||
signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes));
|
||||
return this;
|
||||
}
|
||||
|
||||
signInputHDAsync(
|
||||
inputIndex: number,
|
||||
hdKeyPair: HDSigner | HDSignerAsync,
|
||||
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
||||
): Promise<void> {
|
||||
return new Promise(
|
||||
(resolve, reject): any => {
|
||||
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
||||
return reject(new Error('Need HDSigner to sign input'));
|
||||
}
|
||||
const signers = getSignersFromHD(
|
||||
inputIndex,
|
||||
this.data.inputs,
|
||||
hdKeyPair,
|
||||
);
|
||||
const promises = signers.map(signer =>
|
||||
this.signInputAsync(inputIndex, signer, sighashTypes),
|
||||
);
|
||||
return Promise.all(promises)
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
sign(
|
||||
keyPair: Signer,
|
||||
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
||||
|
@ -525,6 +626,38 @@ interface PsbtOpts {
|
|||
maximumFeeRate: number;
|
||||
}
|
||||
|
||||
interface HDSignerBase {
|
||||
/**
|
||||
* DER format compressed publicKey buffer
|
||||
*/
|
||||
publicKey: Buffer;
|
||||
/**
|
||||
* The first 4 bytes of the sha256-ripemd160 of the publicKey
|
||||
*/
|
||||
fingerprint: Buffer;
|
||||
}
|
||||
|
||||
interface HDSigner extends HDSignerBase {
|
||||
/**
|
||||
* The path string must match /^m(\/\d+'?)+$/
|
||||
* ex. m/44'/0'/0'/1/23 levels with ' must be hard derivations
|
||||
*/
|
||||
derivePath(path: string): HDSigner;
|
||||
/**
|
||||
* Input hash (the "message digest") for the signature algorithm
|
||||
* Return a 64 byte signature (32 byte r and 32 byte s in that order)
|
||||
*/
|
||||
sign(hash: Buffer): Buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as above but with async sign method
|
||||
*/
|
||||
interface HDSignerAsync extends HDSignerBase {
|
||||
derivePath(path: string): HDSignerAsync;
|
||||
sign(hash: Buffer): Promise<Buffer>;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is needed to pass to the bip174 base class's fromBuffer.
|
||||
* It takes the "transaction buffer" portion of the psbt buffer and returns a
|
||||
|
@ -1042,6 +1175,39 @@ function getScriptFromInput(
|
|||
return res;
|
||||
}
|
||||
|
||||
function getSignersFromHD(
|
||||
inputIndex: number,
|
||||
inputs: PsbtInput[],
|
||||
hdKeyPair: HDSigner | HDSignerAsync,
|
||||
): Array<Signer | SignerAsync> {
|
||||
const input = checkForInput(inputs, inputIndex);
|
||||
if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
|
||||
throw new Error('Need bip32Derivation to sign with HD');
|
||||
}
|
||||
const myDerivations = input.bip32Derivation
|
||||
.map(bipDv => {
|
||||
if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) {
|
||||
return bipDv;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
})
|
||||
.filter(v => !!v);
|
||||
if (myDerivations.length === 0) {
|
||||
throw new Error(
|
||||
'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint',
|
||||
);
|
||||
}
|
||||
const signers: Array<Signer | SignerAsync> = myDerivations.map(bipDv => {
|
||||
const node = hdKeyPair.derivePath(bipDv!.path);
|
||||
if (!bipDv!.pubkey.equals(node.publicKey)) {
|
||||
throw new Error('pubkey did not match bip32Derivation');
|
||||
}
|
||||
return node;
|
||||
});
|
||||
return signers;
|
||||
}
|
||||
|
||||
function getSortedSigs(script: Buffer, partialSig: PartialSig[]): Buffer[] {
|
||||
const p2ms = payments.p2ms({ output: script });
|
||||
// for each pubkey in order of p2ms script
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue