Skip to content

Commit d46fecf

Browse files
OttoAllmendingerllm-git
andcommitted
feat(utxo-core): add PSBT signing utilities
Add helper functions for signing PSBTs with Schnorr and ECDSA signatures. Include utilities to count new signatures and sign with different key types. Issue: BTC-1966 Co-authored-by: llm-git <[email protected]>
1 parent 89fad6b commit d46fecf

File tree

3 files changed

+50
-12
lines changed

3 files changed

+50
-12
lines changed

modules/utxo-core/src/descriptor/psbt/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './createPsbt';
33
export * from './parse';
44
export * from './findDescriptors';
55
export * from './wrap';
6+
export * from './sign';
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import assert from 'assert';
2+
3+
import * as utxolib from '@bitgo/utxo-lib';
4+
import { Psbt as WasmPsbt } from '@bitgo/wasm-miniscript';
5+
6+
/** These can be replaced when @bitgo/wasm-miniscript is updated */
7+
export type SignPsbtInputResult = { Schnorr: string[] } | { Ecdsa: string[] };
8+
export type SignPsbtResult = {
9+
[inputIndex: number]: SignPsbtInputResult;
10+
};
11+
12+
/**
13+
* @param signResult
14+
* @return the number of new signatures created by the signResult for a single input
15+
*/
16+
export function getNewSignatureCountForInput(signResult: SignPsbtInputResult): number {
17+
if ('Schnorr' in signResult) {
18+
return signResult.Schnorr.length;
19+
}
20+
if ('Ecdsa' in signResult) {
21+
return signResult.Ecdsa.length;
22+
}
23+
throw new Error(`Unknown signature type ${Object.keys(signResult).join(', ')}`);
24+
}
25+
26+
/**
27+
* @param signResult
28+
* @return the number of new signatures created by the signResult
29+
*/
30+
export function getNewSignatureCount(signResult: SignPsbtResult): number {
31+
return Object.values(signResult).reduce((sum, signatures) => sum + getNewSignatureCountForInput(signatures), 0);
32+
}
33+
34+
type Key = Buffer | utxolib.BIP32Interface | utxolib.ECPairInterface;
35+
36+
/** Convenience function to sign a PSBT with a key */
37+
export function signWithKey(psbt: WasmPsbt, key: Key): SignPsbtResult {
38+
// we need to do casting here because the type definitions in wasm-miniscript are a little bit buggy
39+
if (Buffer.isBuffer(key)) {
40+
return psbt.signWithPrv(key) as unknown as SignPsbtResult;
41+
}
42+
if ('toBase58' in key) {
43+
return psbt.signWithXprv(key.toBase58()) as unknown as SignPsbtResult;
44+
}
45+
assert(key.privateKey);
46+
return psbt.signWithPrv(key.privateKey) as unknown as SignPsbtResult;
47+
}

modules/utxo-core/test/descriptor/psbt/psbt.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
toPlainObjectFromPsbt,
1616
toPlainObjectFromTx,
1717
} from '../../../src/testutil/descriptor';
18+
import { getNewSignatureCount, signWithKey } from '../../../src/descriptor/psbt/sign';
1819

1920
function normalize(v: unknown): unknown {
2021
if (typeof v === 'bigint') {
@@ -55,12 +56,6 @@ function toPlain(k: BIP32Interface): ECPairInterface {
5556
return ECPair.fromPrivateKey(k.privateKey);
5657
}
5758

58-
function isBIP32Interface(k: BIP32Interface | ECPairInterface): k is BIP32Interface {
59-
const { name } = k.constructor;
60-
assert(name === 'BIP32' || name === 'ECPair');
61-
return k.constructor.name === 'BIP32';
62-
}
63-
6459
type PsbtStage = {
6560
name: string;
6661
keys: (BIP32Interface | ECPairInterface)[];
@@ -82,12 +77,7 @@ function getStages(
8277
stages.map((stage) => {
8378
const psbtStageWrapped = toWrappedPsbt(psbt);
8479
for (const key of stage.keys) {
85-
if (isBIP32Interface(key)) {
86-
psbtStageWrapped.signWithXprv(key.toBase58());
87-
} else {
88-
assert(key.privateKey);
89-
psbtStageWrapped.signWithPrv(key.privateKey);
90-
}
80+
assert(getNewSignatureCount(signWithKey(psbtStageWrapped, key)) > 0, 'No new signatures were created');
9181
}
9282
const psbtStage = toUtxoPsbt(psbtStageWrapped, utxolib.networks.bitcoin);
9383
let psbtFinal: utxolib.bitgo.UtxoPsbt | undefined;

0 commit comments

Comments
 (0)