40
40
# A Revault Unvault policy with the older() replaced by an after()
41
41
f"andor(multi(2,{ TPUBS [0 ]} /*,{ TPUBS [1 ]} /*),and_v(v:multi(4,{ PUBKEYS [0 ]} ,{ PUBKEYS [1 ]} ,{ PUBKEYS [2 ]} ,{ PUBKEYS [3 ]} ),after(424242)),thresh(4,pkh({ TPUBS [2 ]} /*),a:pkh({ TPUBS [3 ]} /*),a:pkh({ TPUBS [4 ]} /*),a:pkh({ TPUBS [5 ]} /*)))" ,
42
42
# Liquid-like federated pegin with emergency recovery keys
43
- "or_i(and_b(pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),a:and_b(pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),a:and_b(pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),a:and_b(pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),s:pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2))))),and_v(v:thresh(2,pkh(tpubD6NzVbkrYhZ4YK67cd5fDe4fBVmGB2waTDrAt1q4ey9HPq9veHjWkw3VpbaCHCcWozjkhgAkWpFrxuPMUrmXVrLHMfEJ9auoZA6AS1g3grC/*),a:pkh(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),a:pkh(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068)),older(4209713)))" ,
43
+ f"or_i(and_b(pk({ PUBKEYS [0 ]} ),a:and_b(pk({ PUBKEYS [1 ]} ),a:and_b(pk({ PUBKEYS [2 ]} ),a:and_b(pk({ PUBKEYS [3 ]} ),s:pk({ PUBKEYS [4 ]} ))))),and_v(v:thresh(2,pkh({ TPUBS [0 ]} /*),a:pkh({ PUBKEYS [5 ]} ),a:pkh({ PUBKEYS [6 ]} )),older(4209713)))" ,
44
+ ]
45
+
46
+ MINISCRIPTS_PRIV = [
47
+ # One of two keys, of which one private key is known
48
+ {
49
+ "ms" : f"or_i(pk({ TPRVS [0 ]} /*),pk({ TPUBS [0 ]} /*))" ,
50
+ "sequence" : None ,
51
+ "locktime" : None ,
52
+ "sigs_count" : 1 ,
53
+ "stack_size" : 3 ,
54
+ },
55
+ # A more complex policy, that can't be satisfied through the first branch (need for a preimage)
56
+ {
57
+ "ms" : f"andor(ndv:older(2),and_v(v:pk({ TPRVS [0 ]} ),sha256(2a8ce30189b2ec3200b47aeb4feaac8fcad7c0ba170389729f4898b0b7933bcb)),and_v(v:pkh({ TPRVS [1 ]} ),pk({ TPRVS [2 ]} /*)))" ,
58
+ "sequence" : 2 ,
59
+ "locktime" : None ,
60
+ "sigs_count" : 3 ,
61
+ "stack_size" : 5 ,
62
+ },
63
+ # Signature with a relative timelock
64
+ {
65
+ "ms" : f"and_v(v:older(2),pk({ TPRVS [0 ]} /*))" ,
66
+ "sequence" : 2 ,
67
+ "locktime" : None ,
68
+ "sigs_count" : 1 ,
69
+ "stack_size" : 2 ,
70
+ },
71
+ # Signature with an absolute timelock
72
+ {
73
+ "ms" : f"and_v(v:after(20),pk({ TPRVS [0 ]} /*))" ,
74
+ "sequence" : None ,
75
+ "locktime" : 20 ,
76
+ "sigs_count" : 1 ,
77
+ "stack_size" : 2 ,
78
+ },
79
+ # Signature with both
80
+ {
81
+ "ms" : f"and_v(v:older(4),and_v(v:after(30),pk({ TPRVS [0 ]} /*)))" ,
82
+ "sequence" : 4 ,
83
+ "locktime" : 30 ,
84
+ "sigs_count" : 1 ,
85
+ "stack_size" : 2 ,
86
+ },
87
+ # We have one key on each branch; Core signs both (can't finalize)
88
+ {
89
+ "ms" : f"c:andor(pk({ TPRVS [0 ]} /*),pk_k({ TPUBS [0 ]} ),and_v(v:pk({ TPRVS [1 ]} ),pk_k({ TPUBS [1 ]} )))" ,
90
+ "sequence" : None ,
91
+ "locktime" : None ,
92
+ "sigs_count" : 2 ,
93
+ "stack_size" : None ,
94
+ },
95
+ # We have all the keys, wallet selects the timeout path to sign since it's smaller and sequence is set
96
+ {
97
+ "ms" : f"andor(pk({ TPRVS [0 ]} /*),pk({ TPRVS [2 ]} ),and_v(v:pk({ TPRVS [1 ]} ),older(10)))" ,
98
+ "sequence" : 10 ,
99
+ "locktime" : None ,
100
+ "sigs_count" : 3 ,
101
+ "stack_size" : 3 ,
102
+ },
103
+ # We have all the keys, wallet selects the primary path to sign unconditionally since nsequence wasn't set to be valid for timeout path
104
+ {
105
+ "ms" : f"andor(pk({ TPRVS [0 ]} /*),pk({ TPRVS [2 ]} ),and_v(v:pkh({ TPRVS [1 ]} ),older(10)))" ,
106
+ "sequence" : None ,
107
+ "locktime" : None ,
108
+ "sigs_count" : 3 ,
109
+ "stack_size" : 3 ,
110
+ },
111
+ # Finalizes to the smallest valid witness, regardless of sequence
112
+ {
113
+ "ms" : f"or_d(pk({ TPRVS [0 ]} /*),and_v(v:pk({ TPRVS [1 ]} ),and_v(v:pk({ TPRVS [2 ]} ),older(10))))" ,
114
+ "sequence" : 12 ,
115
+ "locktime" : None ,
116
+ "sigs_count" : 3 ,
117
+ "stack_size" : 2 ,
118
+ },
119
+ # Liquid-like federated pegin with emergency recovery privkeys
120
+ {
121
+ "ms" : f"or_i(and_b(pk({ TPUBS [0 ]} /*),a:and_b(pk({ TPUBS [1 ]} ),a:and_b(pk({ TPUBS [2 ]} ),a:and_b(pk({ TPUBS [3 ]} ),s:pk({ PUBKEYS [0 ]} ))))),and_v(v:thresh(2,pkh({ TPRVS [0 ]} ),a:pkh({ TPRVS [1 ]} ),a:pkh({ TPUBS [4 ]} )),older(42)))" ,
122
+ "sequence" : 42 ,
123
+ "locktime" : None ,
124
+ "sigs_count" : 2 ,
125
+ "stack_size" : 8 ,
126
+ },
44
127
]
45
128
46
129
@@ -84,13 +167,77 @@ def watchonly_test(self, ms):
84
167
utxo = self .ms_wo_wallet .listunspent (minconf = 0 , addresses = [addr ])[0 ]
85
168
assert utxo ["txid" ] == txid and utxo ["solvable" ]
86
169
170
+ def signing_test (self , ms , sequence , locktime , sigs_count , stack_size ):
171
+ self .log .info (f"Importing private Miniscript '{ ms } '" )
172
+ desc = descsum_create (f"wsh({ ms } )" )
173
+ res = self .ms_sig_wallet .importdescriptors (
174
+ [
175
+ {
176
+ "desc" : desc ,
177
+ "active" : True ,
178
+ "range" : 0 ,
179
+ "next_index" : 0 ,
180
+ "timestamp" : "now" ,
181
+ }
182
+ ]
183
+ )
184
+ assert res [0 ]["success" ], res
185
+
186
+ self .log .info ("Generating an address for it and testing it detects funds" )
187
+ addr = self .ms_sig_wallet .getnewaddress ()
188
+ txid = self .funder .sendtoaddress (addr , 0.01 )
189
+ self .wait_until (lambda : txid in self .funder .getrawmempool ())
190
+ self .funder .generatetoaddress (1 , self .funder .getnewaddress ())
191
+ utxo = self .ms_sig_wallet .listunspent (addresses = [addr ])[0 ]
192
+ assert txid == utxo ["txid" ] and utxo ["solvable" ]
193
+
194
+ self .log .info ("Creating a transaction spending these funds" )
195
+ dest_addr = self .funder .getnewaddress ()
196
+ seq = sequence if sequence is not None else 0xFFFFFFFF - 2
197
+ lt = locktime if locktime is not None else 0
198
+ psbt = self .ms_sig_wallet .createpsbt (
199
+ [
200
+ {
201
+ "txid" : txid ,
202
+ "vout" : utxo ["vout" ],
203
+ "sequence" : seq ,
204
+ }
205
+ ],
206
+ [{dest_addr : 0.009 }],
207
+ lt ,
208
+ )
209
+
210
+ self .log .info ("Signing it and checking the satisfaction." )
211
+ res = self .ms_sig_wallet .walletprocesspsbt (psbt = psbt , finalize = False )
212
+ psbtin = self .nodes [0 ].rpc .decodepsbt (res ["psbt" ])["inputs" ][0 ]
213
+ assert len (psbtin ["partial_signatures" ]) == sigs_count
214
+ res = self .ms_sig_wallet .finalizepsbt (res ["psbt" ])
215
+ assert res ["complete" ] == (stack_size is not None )
216
+
217
+ if stack_size is not None :
218
+ txin = self .nodes [0 ].rpc .decoderawtransaction (res ["hex" ])["vin" ][0 ]
219
+ assert len (txin ["txinwitness" ]) == stack_size , txin ["txinwitness" ]
220
+ self .log .info ("Broadcasting the transaction." )
221
+ # If necessary, satisfy a relative timelock
222
+ if sequence is not None :
223
+ self .funder .generatetoaddress (sequence , self .funder .getnewaddress ())
224
+ # If necessary, satisfy an absolute timelock
225
+ height = self .funder .getblockcount ()
226
+ if locktime is not None and height < locktime :
227
+ self .funder .generatetoaddress (
228
+ locktime - height , self .funder .getnewaddress ()
229
+ )
230
+ self .ms_sig_wallet .sendrawtransaction (res ["hex" ])
231
+
87
232
def run_test (self ):
88
233
self .log .info ("Making a descriptor wallet" )
89
234
self .funder = self .nodes [0 ].get_wallet_rpc (self .default_wallet_name )
90
235
self .nodes [0 ].createwallet (
91
236
wallet_name = "ms_wo" , descriptors = True , disable_private_keys = True
92
237
)
93
238
self .ms_wo_wallet = self .nodes [0 ].get_wallet_rpc ("ms_wo" )
239
+ self .nodes [0 ].createwallet (wallet_name = "ms_sig" , descriptors = True )
240
+ self .ms_sig_wallet = self .nodes [0 ].get_wallet_rpc ("ms_sig" )
94
241
95
242
# Sanity check we wouldn't let an insane Miniscript descriptor in
96
243
res = self .ms_wo_wallet .importdescriptors (
@@ -111,6 +258,16 @@ def run_test(self):
111
258
for ms in MINISCRIPTS :
112
259
self .watchonly_test (ms )
113
260
261
+ # Test we can sign most Miniscript (all but ones requiring preimages, for now)
262
+ for ms in MINISCRIPTS_PRIV :
263
+ self .signing_test (
264
+ ms ["ms" ],
265
+ ms ["sequence" ],
266
+ ms ["locktime" ],
267
+ ms ["sigs_count" ],
268
+ ms ["stack_size" ],
269
+ )
270
+
114
271
115
272
if __name__ == "__main__" :
116
273
WalletMiniscriptTest ().main ()
0 commit comments