5
5
from test_framework .bitcoind import Bitcoind
6
6
from test_framework .lianad import Lianad
7
7
from test_framework .signer import SingleSigner , MultiSigner
8
- from test_framework .utils import (
9
- EXECUTOR_WORKERS ,
10
- )
8
+ from test_framework .utils import EXECUTOR_WORKERS , USE_TAPROOT
11
9
10
+ import hashlib
12
11
import os
13
12
import pytest
14
13
import shutil
@@ -120,22 +119,36 @@ def xpub_fingerprint(hd):
120
119
return _pubkey_to_fingerprint (hd .pubkey ).hex ()
121
120
122
121
122
+ def single_key_desc (prim_fg , prim_xpub , reco_fg , reco_xpub , csv_value , is_taproot ):
123
+ if is_taproot :
124
+ return f"tr([{ prim_fg } ]{ prim_xpub } /<0;1>/*,and_v(v:pk([{ reco_fg } ]{ reco_xpub } /<0;1>/*),older({ csv_value } )))"
125
+ else :
126
+ return f"wsh(or_d(pk([{ prim_fg } ]{ prim_xpub } /<0;1>/*),and_v(v:pkh([{ reco_fg } ]{ reco_xpub } /<0;1>/*),older({ csv_value } ))))"
127
+
128
+
123
129
@pytest .fixture
124
130
def lianad (bitcoind , directory ):
125
131
datadir = os .path .join (directory , "lianad" )
126
132
os .makedirs (datadir , exist_ok = True )
127
133
bitcoind_cookie = os .path .join (bitcoind .bitcoin_dir , "regtest" , ".cookie" )
128
134
129
- signer = SingleSigner ()
135
+ signer = SingleSigner (is_taproot = USE_TAPROOT )
130
136
(prim_fingerprint , primary_xpub ), (reco_fingerprint , recovery_xpub ) = (
131
137
(xpub_fingerprint (signer .primary_hd ), signer .primary_hd .get_xpub ()),
132
138
(xpub_fingerprint (signer .recovery_hd ), signer .recovery_hd .get_xpub ()),
133
139
)
134
140
csv_value = 10
135
- # NOTE: origins are the actual xpub themselves which is incorrect but make it
141
+ # NOTE: origins are the actual xpub themselves which is incorrect but makes it
136
142
# possible to differentiate them.
137
143
main_desc = Descriptor .from_str (
138
- f"wsh(or_d(pk([{ prim_fingerprint } ]{ primary_xpub } /<0;1>/*),and_v(v:pkh([{ reco_fingerprint } ]{ recovery_xpub } /<0;1>/*),older({ csv_value } ))))"
144
+ single_key_desc (
145
+ prim_fingerprint ,
146
+ primary_xpub ,
147
+ reco_fingerprint ,
148
+ recovery_xpub ,
149
+ csv_value ,
150
+ is_taproot = USE_TAPROOT ,
151
+ )
139
152
)
140
153
141
154
lianad = Lianad (
@@ -156,8 +169,19 @@ def lianad(bitcoind, directory):
156
169
lianad .cleanup ()
157
170
158
171
159
- def multi_expression (thresh , keys ):
160
- exp = f"multi({ thresh } ,"
172
+ def unspendable_internal_xpub (xpubs ):
173
+ """Deterministic, unique, unspendable internal key.
174
+ See See https://delvingbitcoin.org/t/unspendable-keys-in-descriptors/304/21
175
+ """
176
+ chaincode = hashlib .sha256 (b"" .join (xpub .pubkey for xpub in xpubs )).digest ()
177
+ bip341_nums = bytes .fromhex (
178
+ "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"
179
+ )
180
+ return BIP32 (chaincode , pubkey = bip341_nums , network = "test" )
181
+
182
+
183
+ def multi_expression (thresh , keys , is_taproot ):
184
+ exp = f"multi_a({ thresh } ," if is_taproot else f"multi({ thresh } ,"
161
185
for i , key in enumerate (keys ):
162
186
# NOTE: origins are the actual xpub themselves which is incorrect but make it
163
187
# possible to differentiate them.
@@ -168,6 +192,21 @@ def multi_expression(thresh, keys):
168
192
return exp + ")"
169
193
170
194
195
+ def multisig_desc (multi_signer , csv_value , is_taproot ):
196
+ prim_multi , recov_multi = (
197
+ multi_expression (3 , multi_signer .prim_hds , is_taproot ),
198
+ multi_expression (2 , multi_signer .recov_hds [csv_value ], is_taproot ),
199
+ )
200
+ if is_taproot :
201
+ all_xpubs = [
202
+ hd for hd in multi_signer .prim_hds + multi_signer .recov_hds [csv_value ]
203
+ ]
204
+ internal_key = unspendable_internal_xpub (all_xpubs ).get_xpub ()
205
+ return f"tr([00000000]{ internal_key } /<0;1>/*,{{{ prim_multi } ,and_v(v:{ recov_multi } ,older({ csv_value } ))}})"
206
+ else :
207
+ return f"wsh(or_d({ prim_multi } ,and_v(v:{ recov_multi } ,older({ csv_value } ))))"
208
+
209
+
171
210
@pytest .fixture
172
211
def lianad_multisig (bitcoind , directory ):
173
212
datadir = os .path .join (directory , "lianad" )
@@ -176,13 +215,9 @@ def lianad_multisig(bitcoind, directory):
176
215
177
216
# A 3-of-4 that degrades into a 2-of-5 after 10 blocks
178
217
csv_value = 10
179
- signer = MultiSigner (4 , {csv_value : 5 })
180
- prim_multi , recov_multi = (
181
- multi_expression (3 , signer .prim_hds ),
182
- multi_expression (2 , signer .recov_hds [csv_value ]),
183
- )
218
+ signer = MultiSigner (4 , {csv_value : 5 }, is_taproot = USE_TAPROOT )
184
219
main_desc = Descriptor .from_str (
185
- f"wsh(or_d( { prim_multi } ,and_v(v: { recov_multi } ,older( { csv_value } ))))"
220
+ multisig_desc ( signer , csv_value , is_taproot = USE_TAPROOT )
186
221
)
187
222
188
223
lianad = Lianad (
@@ -203,6 +238,28 @@ def lianad_multisig(bitcoind, directory):
203
238
lianad .cleanup ()
204
239
205
240
241
+ def multipath_desc (multi_signer , csv_values , is_taproot ):
242
+ prim_multi = multi_expression (3 , multi_signer .prim_hds , is_taproot )
243
+ first_recov_multi = multi_expression (
244
+ 3 , multi_signer .recov_hds [csv_values [0 ]], is_taproot
245
+ )
246
+ second_recov_multi = multi_expression (
247
+ 1 , multi_signer .recov_hds [csv_values [1 ]], is_taproot
248
+ )
249
+ if is_taproot :
250
+ all_xpubs = [
251
+ hd
252
+ for hd in multi_signer .prim_hds
253
+ + multi_signer .recov_hds [csv_values [0 ]]
254
+ + multi_signer .recov_hds [csv_values [1 ]]
255
+ ]
256
+ internal_key = unspendable_internal_xpub (all_xpubs ).get_xpub ()
257
+ # On purpose we use a single leaf instead of 3 different ones. It shouldn't be an issue.
258
+ return f"tr([00000000]{ internal_key } /<0;1>/*,or_d({ prim_multi } ,or_i(and_v(v:{ first_recov_multi } ,older({ csv_values [0 ]} )),and_v(v:{ second_recov_multi } ,older({ csv_values [1 ]} )))))"
259
+ else :
260
+ return f"wsh(or_d({ prim_multi } ,or_i(and_v(v:{ first_recov_multi } ,older({ csv_values [0 ]} )),and_v(v:{ second_recov_multi } ,older({ csv_values [1 ]} )))))"
261
+
262
+
206
263
@pytest .fixture
207
264
def lianad_multipath (bitcoind , directory ):
208
265
datadir = os .path .join (directory , "lianad" )
@@ -211,12 +268,11 @@ def lianad_multipath(bitcoind, directory):
211
268
212
269
# A 3-of-4 that degrades into a 3-of-5 after 10 blocks and into a 1-of-10 after 20 blocks.
213
270
csv_values = [10 , 20 ]
214
- signer = MultiSigner (4 , {csv_values [0 ]: 5 , csv_values [1 ]: 10 })
215
- prim_multi = multi_expression (3 , signer .prim_hds )
216
- first_recov_multi = multi_expression (3 , signer .recov_hds [csv_values [0 ]])
217
- second_recov_multi = multi_expression (1 , signer .recov_hds [csv_values [1 ]])
271
+ signer = MultiSigner (
272
+ 4 , {csv_values [0 ]: 5 , csv_values [1 ]: 10 }, is_taproot = USE_TAPROOT
273
+ )
218
274
main_desc = Descriptor .from_str (
219
- f"wsh(or_d( { prim_multi } ,or_i(and_v(v: { first_recov_multi } ,older( { csv_values [ 0 ] } )),and_v(v: { second_recov_multi } ,older( { csv_values [ 1 ] } )))))"
275
+ multipath_desc ( signer , csv_values , is_taproot = USE_TAPROOT )
220
276
)
221
277
222
278
lianad = Lianad (
0 commit comments