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);
|
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]) {
|
sign(keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) {
|
||||||
if (!keyPair || !keyPair.publicKey)
|
if (!keyPair || !keyPair.publicKey)
|
||||||
throw new Error('Need Signer to sign input');
|
throw new Error('Need Signer to sign input');
|
||||||
|
@ -853,6 +933,34 @@ function getScriptFromInput(inputIndex, input, cache) {
|
||||||
}
|
}
|
||||||
return res;
|
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) {
|
function getSortedSigs(script, partialSig) {
|
||||||
const p2ms = payments.p2ms({ output: script });
|
const p2ms = payments.p2ms({ output: script });
|
||||||
// for each pubkey in order of p2ms 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": [
|
"finalizeAllInputs": [
|
||||||
{
|
{
|
||||||
"type": "P2PK",
|
"type": "P2PK",
|
||||||
|
|
125
test/psbt.js
125
test/psbt.js
|
@ -1,6 +1,7 @@
|
||||||
const { describe, it } = require('mocha')
|
const { describe, it } = require('mocha')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
|
|
||||||
|
const bip32 = require('bip32')
|
||||||
const ECPair = require('../src/ecpair')
|
const ECPair = require('../src/ecpair')
|
||||||
const Psbt = require('..').Psbt
|
const Psbt = require('..').Psbt
|
||||||
const NETWORKS = require('../src/networks')
|
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', () => {
|
describe('finalizeAllInputs', () => {
|
||||||
fixtures.finalizeAllInputs.forEach(f => {
|
fixtures.finalizeAllInputs.forEach(f => {
|
||||||
it(`Finalizes inputs of type "${f.type}"`, () => {
|
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);
|
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(
|
sign(
|
||||||
keyPair: Signer,
|
keyPair: Signer,
|
||||||
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
|
||||||
|
@ -525,6 +626,38 @@ interface PsbtOpts {
|
||||||
maximumFeeRate: number;
|
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.
|
* 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
|
* It takes the "transaction buffer" portion of the psbt buffer and returns a
|
||||||
|
@ -1042,6 +1175,39 @@ function getScriptFromInput(
|
||||||
return res;
|
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[] {
|
function getSortedSigs(script: Buffer, partialSig: PartialSig[]): Buffer[] {
|
||||||
const p2ms = payments.p2ms({ output: script });
|
const p2ms = payments.p2ms({ output: script });
|
||||||
// for each pubkey in order of p2ms 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;
|
finalizeInput(inputIndex: number): this;
|
||||||
validateAllSignatures(): boolean;
|
validateAllSignatures(): boolean;
|
||||||
validateSignatures(inputIndex: number, pubkey?: Buffer): 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;
|
sign(keyPair: Signer, sighashTypes?: number[]): this;
|
||||||
signAsync(keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise<void>;
|
signAsync(keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise<void>;
|
||||||
signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this;
|
signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this;
|
||||||
|
@ -80,4 +84,33 @@ interface PsbtOptsOptional {
|
||||||
network?: Network;
|
network?: Network;
|
||||||
maximumFeeRate?: number;
|
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 {};
|
export {};
|
||||||
|
|
Loading…
Reference in a new issue