Add HD signer methods
This commit is contained in:
parent
1326e0cc42
commit
4366b621d7
5 changed files with 478 additions and 0 deletions
108
src/psbt.js
108
src/psbt.js
|
@ -278,6 +278,86 @@ class Psbt {
|
|||
}
|
||||
return results.every(res => res === true);
|
||||
}
|
||||
signHD(hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) {
|
||||
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
||||
throw new Error('Need HDSigner to sign input');
|
||||
}
|
||||
const results = [];
|
||||
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,
|
||||
sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
||||
return reject(new Error('Need HDSigner to sign input'));
|
||||
}
|
||||
const results = [];
|
||||
const promises = [];
|
||||
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,
|
||||
hdKeyPair,
|
||||
sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
|
||||
) {
|
||||
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_1.Transaction.SIGHASH_ALL],
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
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, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) {
|
||||
if (!keyPair || !keyPair.publicKey)
|
||||
throw new Error('Need Signer to sign input');
|
||||
|
@ -853,6 +933,34 @@ function getScriptFromInput(inputIndex, input, cache) {
|
|||
}
|
||||
return res;
|
||||
}
|
||||
function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
|
||||
const input = utils_1.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 = 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, partialSig) {
|
||||
const p2ms = payments.p2ms({ output: script });
|
||||
// for each pubkey in order of p2ms script
|
||||
|
|
46
test/fixtures/psbt.json
vendored
46
test/fixtures/psbt.json
vendored
|
@ -448,6 +448,52 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"signInputHD": {
|
||||
"checks": [
|
||||
{
|
||||
"description": "checks the bip32Derivation exists",
|
||||
"shouldSign": {
|
||||
"psbt": "cHNidP8BADMBAAAAARtEptsZNydT9Bh9A5ptwIZz87yH8NXwzr1bjJorAZEAAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFJWXNY2Vp7E5pNOey64rnhhgUlohiKwAAAAAIgYDn85vSg5rlR25fd4MOU2ANsFoO+q828zuOI/5b8tj89kYBCppsiwAAIAAAACAAAAAgAAAAAAAAAAAAAA=",
|
||||
"inputToCheck": 0,
|
||||
"xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev"
|
||||
},
|
||||
"shouldThrow": {
|
||||
"errorMessage": "Need bip32Derivation to sign with HD",
|
||||
"psbt": "cHNidP8BADMBAAAAAXVa+rWvBGNyifYXEMlTten9+qC0xuHcAMxQYrQTwX1dAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFC8spHIOpiw9giaEPd5RGkMYvXRHiKwAAAAAAAA=",
|
||||
"inputToCheck": 0,
|
||||
"xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "checks the bip32Derivation exists",
|
||||
"shouldSign": {
|
||||
"psbt": "cHNidP8BADMBAAAAARtEptsZNydT9Bh9A5ptwIZz87yH8NXwzr1bjJorAZEAAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFJWXNY2Vp7E5pNOey64rnhhgUlohiKwAAAAAIgYDn85vSg5rlR25fd4MOU2ANsFoO+q828zuOI/5b8tj89kYBCppsiwAAIAAAACAAAAAgAAAAAAAAAAAAAA=",
|
||||
"inputToCheck": 0,
|
||||
"xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev"
|
||||
},
|
||||
"shouldThrow": {
|
||||
"errorMessage": "Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint",
|
||||
"psbt": "cHNidP8BADMBAAAAARtEptsZNydT9Bh9A5ptwIZz87yH8NXwzr1bjJorAZEAAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFJWXNY2Vp7E5pNOey64rnhhgUlohiKwAAAAAIgYD/85vSg5rlR25fd4MOU2ANsFoO+q828zuOI/5b8tj89kY/////ywAAIAAAACAAAAAgAAAAAAAAAAAAAA=",
|
||||
"inputToCheck": 0,
|
||||
"xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "checks the bip32Derivation exists",
|
||||
"shouldSign": {
|
||||
"psbt": "cHNidP8BADMBAAAAARtEptsZNydT9Bh9A5ptwIZz87yH8NXwzr1bjJorAZEAAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFJWXNY2Vp7E5pNOey64rnhhgUlohiKwAAAAAIgYDn85vSg5rlR25fd4MOU2ANsFoO+q828zuOI/5b8tj89kYBCppsiwAAIAAAACAAAAAgAAAAAAAAAAAAAA=",
|
||||
"inputToCheck": 0,
|
||||
"xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev"
|
||||
},
|
||||
"shouldThrow": {
|
||||
"errorMessage": "pubkey did not match bip32Derivation",
|
||||
"psbt": "cHNidP8BADMBAAAAARtEptsZNydT9Bh9A5ptwIZz87yH8NXwzr1bjJorAZEAAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFJWXNY2Vp7E5pNOey64rnhhgUlohiKwAAAAAIgYD/85vSg5rlR25fd4MOU2ANsFoO+q828zuOI/5b8tj89kYBCppsiwAAIAAAACAAAAAgAAAAAAAAAAAAAA=",
|
||||
"inputToCheck": 0,
|
||||
"xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"finalizeAllInputs": [
|
||||
{
|
||||
"type": "P2PK",
|
||||
|
|
125
test/psbt.js
125
test/psbt.js
|
@ -1,6 +1,7 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
|
||||
const bip32 = require('bip32')
|
||||
const ECPair = require('../src/ecpair')
|
||||
const Psbt = require('..').Psbt
|
||||
const NETWORKS = require('../src/networks')
|
||||
|
@ -278,6 +279,130 @@ describe(`Psbt`, () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('signInputHDAsync', () => {
|
||||
fixtures.signInputHD.checks.forEach(f => {
|
||||
it(f.description, async () => {
|
||||
if (f.shouldSign) {
|
||||
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt)
|
||||
assert.doesNotReject(async () => {
|
||||
await psbtThatShouldsign.signInputHDAsync(
|
||||
f.shouldSign.inputToCheck,
|
||||
bip32.fromBase58(f.shouldSign.xprv),
|
||||
f.shouldSign.sighashTypes || undefined,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (f.shouldThrow) {
|
||||
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt)
|
||||
assert.rejects(async () => {
|
||||
await psbtThatShouldThrow.signInputHDAsync(
|
||||
f.shouldThrow.inputToCheck,
|
||||
bip32.fromBase58(f.shouldThrow.xprv),
|
||||
f.shouldThrow.sighashTypes || undefined,
|
||||
)
|
||||
}, new RegExp(f.shouldThrow.errorMessage))
|
||||
assert.rejects(async () => {
|
||||
await psbtThatShouldThrow.signInputHDAsync(
|
||||
f.shouldThrow.inputToCheck,
|
||||
)
|
||||
}, new RegExp('Need HDSigner to sign input'))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('signInputHD', () => {
|
||||
fixtures.signInputHD.checks.forEach(f => {
|
||||
it(f.description, () => {
|
||||
if (f.shouldSign) {
|
||||
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt)
|
||||
assert.doesNotThrow(() => {
|
||||
psbtThatShouldsign.signInputHD(
|
||||
f.shouldSign.inputToCheck,
|
||||
bip32.fromBase58(f.shouldSign.xprv),
|
||||
f.shouldSign.sighashTypes || undefined,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (f.shouldThrow) {
|
||||
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt)
|
||||
assert.throws(() => {
|
||||
psbtThatShouldThrow.signInputHD(
|
||||
f.shouldThrow.inputToCheck,
|
||||
bip32.fromBase58(f.shouldThrow.xprv),
|
||||
f.shouldThrow.sighashTypes || undefined,
|
||||
)
|
||||
}, new RegExp(f.shouldThrow.errorMessage))
|
||||
assert.throws(() => {
|
||||
psbtThatShouldThrow.signInputHD(
|
||||
f.shouldThrow.inputToCheck,
|
||||
)
|
||||
}, new RegExp('Need HDSigner to sign input'))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('signHDAsync', () => {
|
||||
fixtures.signInputHD.checks.forEach(f => {
|
||||
it(f.description, async () => {
|
||||
if (f.shouldSign) {
|
||||
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt)
|
||||
assert.doesNotReject(async () => {
|
||||
await psbtThatShouldsign.signHDAsync(
|
||||
bip32.fromBase58(f.shouldSign.xprv),
|
||||
f.shouldSign.sighashTypes || undefined,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (f.shouldThrow) {
|
||||
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt)
|
||||
assert.rejects(async () => {
|
||||
await psbtThatShouldThrow.signHDAsync(
|
||||
bip32.fromBase58(f.shouldThrow.xprv),
|
||||
f.shouldThrow.sighashTypes || undefined,
|
||||
)
|
||||
}, new RegExp('No inputs were signed'))
|
||||
assert.rejects(async () => {
|
||||
await psbtThatShouldThrow.signHDAsync()
|
||||
}, new RegExp('Need HDSigner to sign input'))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('signHD', () => {
|
||||
fixtures.signInputHD.checks.forEach(f => {
|
||||
it(f.description, () => {
|
||||
if (f.shouldSign) {
|
||||
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt)
|
||||
assert.doesNotThrow(() => {
|
||||
psbtThatShouldsign.signHD(
|
||||
bip32.fromBase58(f.shouldSign.xprv),
|
||||
f.shouldSign.sighashTypes || undefined,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (f.shouldThrow) {
|
||||
const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt)
|
||||
assert.throws(() => {
|
||||
psbtThatShouldThrow.signHD(
|
||||
bip32.fromBase58(f.shouldThrow.xprv),
|
||||
f.shouldThrow.sighashTypes || undefined,
|
||||
)
|
||||
}, new RegExp('No inputs were signed'))
|
||||
assert.throws(() => {
|
||||
psbtThatShouldThrow.signHD()
|
||||
}, new RegExp('Need HDSigner to sign input'))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('finalizeAllInputs', () => {
|
||||
fixtures.finalizeAllInputs.forEach(f => {
|
||||
it(`Finalizes inputs of type "${f.type}"`, () => {
|
||||
|
|
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
|
||||
|
|
33
types/psbt.d.ts
vendored
33
types/psbt.d.ts
vendored
|
@ -61,6 +61,10 @@ export declare class Psbt {
|
|||
finalizeInput(inputIndex: number): this;
|
||||
validateAllSignatures(): boolean;
|
||||
validateSignatures(inputIndex: number, pubkey?: Buffer): boolean;
|
||||
signHD(hdKeyPair: HDSigner, sighashTypes?: number[]): this;
|
||||
signHDAsync(hdKeyPair: HDSigner | HDSignerAsync, sighashTypes?: number[]): Promise<void>;
|
||||
signInputHD(inputIndex: number, hdKeyPair: HDSigner, sighashTypes?: number[]): this;
|
||||
signInputHDAsync(inputIndex: number, hdKeyPair: HDSigner | HDSignerAsync, sighashTypes?: number[]): Promise<void>;
|
||||
sign(keyPair: Signer, sighashTypes?: number[]): this;
|
||||
signAsync(keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise<void>;
|
||||
signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this;
|
||||
|
@ -80,4 +84,33 @@ interface PsbtOptsOptional {
|
|||
network?: Network;
|
||||
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>;
|
||||
}
|
||||
export {};
|
||||
|
|
Loading…
Reference in a new issue