@@ -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+
1236function toDescriptorMap ( v : Record < string , string > ) : DescriptorMap {
1337 return new Map ( Object . entries ( v ) . map ( ( [ k , v ] ) => [ k , Descriptor . fromString ( v , 'derivable' ) ] ) ) ;
1438}
1539
1640export 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
4483export 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