Skip to content

Commit 2717de0

Browse files
feat(abstract-utxo): add support for taproot in descriptors testutils
TICKET: BTC-1786
1 parent 03a42fe commit 2717de0

File tree

1 file changed

+48
-7
lines changed

1 file changed

+48
-7
lines changed

modules/abstract-utxo/test/core/descriptor/descriptor.utils.ts

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,37 @@ export function getDefaultXPubs(seed?: string): Triple<string> {
99
return getKeyTriple(seed).map((k) => k.neutered().toBase58()) as Triple<string>;
1010
}
1111

12+
export function getUnspendableKey(): string {
13+
/*
14+
https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs
15+
16+
```
17+
If one or more of the spending conditions consist of just a single key (after aggregation), the most likely one should
18+
be made the internal key. If no such condition exists, it may be worthwhile adding one that consists of an aggregation
19+
of all keys participating in all scripts combined; effectively adding an "everyone agrees" branch. If that is
20+
inacceptable, pick as internal key a "Nothing Up My Sleeve" (NUMS) point, i.e., a point with unknown discrete
21+
logarithm.
22+
23+
One example of such a point is H = lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) which is
24+
constructed by taking the hash of the standard uncompressed encoding of the secp256k1 base point G as X coordinate.
25+
In order to avoid leaking the information that key path spending is not possible it is recommended to pick a fresh
26+
integer r in the range 0...n-1 uniformly at random and use H + rG as internal key. It is possible to prove that this
27+
internal key does not have a known discrete logarithm with respect to G by revealing r to a verifier who can then
28+
reconstruct how the internal key was created.
29+
```
30+
31+
We could do the random integer trick here, but for internal testing it is sufficient to use the fixed point.
32+
*/
33+
return '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0';
34+
}
35+
1236
function toDescriptorMap(v: Record<string, string>): DescriptorMap {
1337
return new Map(Object.entries(v).map(([k, v]) => [k, Descriptor.fromString(v, 'derivable')]));
1438
}
1539

1640
export type DescriptorTemplate =
1741
| 'Wsh2Of3'
42+
| 'Tr2Of3-NoKeyPath'
1843
| 'Wsh2Of2'
1944
/*
2045
* This is a wrapped segwit 2of3 multisig that also uses a relative locktime with
@@ -30,21 +55,36 @@ function toXPub(k: BIP32Interface | string): string {
3055
return k.neutered().toBase58();
3156
}
3257

33-
function multi(m: number, n: number, keys: BIP32Interface[] | string[], path: string): string {
58+
function multi(
59+
prefix: 'multi' | 'multi_a',
60+
m: number,
61+
n: number,
62+
keys: BIP32Interface[] | string[],
63+
path: string
64+
): string {
3465
if (n < m) {
3566
throw new Error(`Cannot create ${m} of ${n} multisig`);
3667
}
3768
if (keys.length < n) {
3869
throw new Error(`Not enough keys for ${m} of ${n} multisig: keys.length=${keys.length}`);
3970
}
4071
keys = keys.slice(0, n);
41-
return `multi(${m},${keys.map((k) => `${toXPub(k)}/${path}`).join(',')})`;
72+
return prefix + `(${m},${keys.map((k) => `${toXPub(k)}/${path}`).join(',')})`;
73+
}
74+
75+
function multiWsh(m: number, n: number, keys: BIP32Interface[] | string[], path: string): string {
76+
return multi('multi', m, n, keys, path);
77+
}
78+
79+
function multiTap(m: number, n: number, keys: BIP32Interface[] | string[], path: string): string {
80+
return multi('multi_a', m, n, keys, path);
4281
}
4382

4483
export function getPsbtParams(t: DescriptorTemplate): Partial<PsbtParams> {
4584
switch (t) {
4685
case 'Wsh2Of3':
4786
case 'Wsh2Of2':
87+
case 'Tr2Of3-NoKeyPath':
4888
return {};
4989
case 'ShWsh2Of3CltvDrop':
5090
return { locktime: 1 };
@@ -58,13 +98,14 @@ export function getDescriptorString(
5898
): string {
5999
switch (template) {
60100
case 'Wsh2Of3':
61-
return `wsh(${multi(2, 3, keys, path)})`;
101+
return `wsh(${multiWsh(2, 3, keys, path)})`;
62102
case 'ShWsh2Of3CltvDrop':
63103
const { locktime } = getPsbtParams(template);
64-
return `sh(wsh(and_v(r:after(${locktime}),${multi(2, 3, keys, path)})))`;
65-
case 'Wsh2Of2': {
66-
return `wsh(${multi(2, 2, keys, path)})`;
67-
}
104+
return `sh(wsh(and_v(r:after(${locktime}),${multiWsh(2, 3, keys, path)})))`;
105+
case 'Wsh2Of2':
106+
return `wsh(${multiWsh(2, 2, keys, path)})`;
107+
case 'Tr2Of3-NoKeyPath':
108+
return `tr(${getUnspendableKey()},${multiTap(2, 3, keys, path)})`;
68109
}
69110
throw new Error(`Unknown descriptor template: ${template}`);
70111
}

0 commit comments

Comments
 (0)