Add HD signer methods

This commit is contained in:
junderw 2019-07-19 11:42:45 +09:00
commit 4366b621d7
No known key found for this signature in database
GPG key ID: B256185D3A971908
5 changed files with 478 additions and 0 deletions
ts_src

View file

@ -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