@@ -27,8 +27,13 @@ use std::fmt;
27
27
use std:: str:: { self , FromStr } ;
28
28
29
29
use bitcoin:: blockdata:: { opcodes, script} ;
30
+ use bitcoin:: hashes:: hex:: FromHex ;
31
+ use bitcoin:: util:: bip32;
30
32
use bitcoin:: { self , Script } ;
31
33
34
+ #[ cfg( feature = "serde" ) ]
35
+ use serde:: { de, ser} ;
36
+
32
37
use expression;
33
38
use miniscript;
34
39
use miniscript:: context:: ScriptContextError ;
@@ -68,6 +73,126 @@ pub enum Descriptor<Pk: MiniscriptKey> {
68
73
ShWsh ( Miniscript < Pk , Segwitv0 > ) ,
69
74
}
70
75
76
+ #[ derive( Debug , Eq , PartialEq ) ]
77
+ pub enum DescriptorPublicKey {
78
+ PukKey ( bitcoin:: PublicKey ) ,
79
+ XPub ( DescriptorXPub ) ,
80
+ }
81
+
82
+ #[ derive( Debug , Eq , PartialEq ) ]
83
+ pub struct DescriptorXPub {
84
+ origin : Option < ( bip32:: Fingerprint , bip32:: DerivationPath ) > ,
85
+ xpub : bip32:: ExtendedPubKey ,
86
+ derivation_path : bip32:: DerivationPath ,
87
+ is_wildcard : bool ,
88
+ }
89
+
90
+ #[ derive( Debug , PartialEq ) ]
91
+ pub struct DescriptorKeyParseError ( & ' static str ) ;
92
+
93
+ impl FromStr for DescriptorPublicKey {
94
+ type Err = DescriptorKeyParseError ;
95
+
96
+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
97
+ if s. len ( ) < 66 {
98
+ Err ( DescriptorKeyParseError (
99
+ "Key too short (<66 char), doesn't match any format" ,
100
+ ) )
101
+ } else if s. chars ( ) . next ( ) . unwrap ( ) == '[' {
102
+ let mut parts = s[ 1 ..] . split ( ']' ) ;
103
+ let mut raw_origin = parts
104
+ . next ( )
105
+ . ok_or ( DescriptorKeyParseError ( "Unclosed '['" ) ) ?
106
+ . split ( '/' ) ;
107
+
108
+ let origin_id_hex = raw_origin. next ( ) . ok_or ( DescriptorKeyParseError (
109
+ "No master fingerprint found after '['" ,
110
+ ) ) ?;
111
+
112
+ if origin_id_hex. len ( ) != 8 {
113
+ return Err ( DescriptorKeyParseError (
114
+ "Master fingerprint should be 8 characters long" ,
115
+ ) ) ;
116
+ }
117
+ let parent_fingerprint = bip32:: Fingerprint :: from_hex ( origin_id_hex) . map_err ( |_| {
118
+ DescriptorKeyParseError ( "Malformed master fingerprint, expected 8 hex chars" )
119
+ } ) ?;
120
+
121
+ let origin_path = raw_origin
122
+ . map ( |p| bip32:: ChildNumber :: from_str ( p) )
123
+ . collect :: < Result < bip32:: DerivationPath , bip32:: Error > > ( )
124
+ . map_err ( |_| {
125
+ DescriptorKeyParseError ( "Error while parsing master derivation path" )
126
+ } ) ?;
127
+
128
+ let key_deriv = parts
129
+ . next ( )
130
+ . ok_or ( DescriptorKeyParseError ( "No key after origin." ) ) ?;
131
+
132
+ let ( xpub, derivation_path, is_wildcard) = Self :: parse_xpub_deriv ( key_deriv) ?;
133
+
134
+ Ok ( DescriptorPublicKey :: XPub ( DescriptorXPub {
135
+ origin : Some ( ( parent_fingerprint, origin_path) ) ,
136
+ xpub,
137
+ derivation_path,
138
+ is_wildcard,
139
+ } ) )
140
+ } else if s. starts_with ( "02" ) || s. starts_with ( "03" ) || s. starts_with ( "04" ) {
141
+ let pk = bitcoin:: PublicKey :: from_str ( s)
142
+ . map_err ( |_| DescriptorKeyParseError ( "Error while parsing simple public key" ) ) ?;
143
+ Ok ( DescriptorPublicKey :: PukKey ( pk) )
144
+ } else {
145
+ let ( xpub, derivation_path, is_wildcard) = Self :: parse_xpub_deriv ( s) ?;
146
+ Ok ( DescriptorPublicKey :: XPub ( DescriptorXPub {
147
+ origin : None ,
148
+ xpub,
149
+ derivation_path,
150
+ is_wildcard,
151
+ } ) )
152
+ }
153
+ }
154
+ }
155
+
156
+ impl DescriptorPublicKey {
157
+ /// Parse an extended public key concatenated to a derivation path.
158
+ fn parse_xpub_deriv (
159
+ key_deriv : & str ,
160
+ ) -> Result < ( bip32:: ExtendedPubKey , bip32:: DerivationPath , bool ) , DescriptorKeyParseError > {
161
+ let mut key_deriv = key_deriv. split ( '/' ) ;
162
+ let xpub_str = key_deriv. next ( ) . ok_or ( DescriptorKeyParseError (
163
+ "No key found after origin description" ,
164
+ ) ) ?;
165
+ let xpub = bip32:: ExtendedPubKey :: from_str ( xpub_str)
166
+ . map_err ( |_| DescriptorKeyParseError ( "Error while parsing xpub." ) ) ?;
167
+
168
+ let mut is_wildcard = false ;
169
+ let derivation_path = key_deriv
170
+ . filter_map ( |p| {
171
+ if !is_wildcard && p == "*" {
172
+ is_wildcard = true ;
173
+ None
174
+ } else if is_wildcard {
175
+ Some ( Err ( DescriptorKeyParseError (
176
+ "'*' may only appear as last element in a derivation path." ,
177
+ ) ) )
178
+ } else {
179
+ Some ( bip32:: ChildNumber :: from_str ( p) . map_err ( |_| {
180
+ DescriptorKeyParseError ( "Error while parsing key derivation path" )
181
+ } ) )
182
+ }
183
+ } )
184
+ . collect :: < Result < bip32:: DerivationPath , _ > > ( ) ?;
185
+
186
+ if ( & derivation_path) . into_iter ( ) . all ( |c| c. is_normal ( ) ) {
187
+ Ok ( ( xpub, derivation_path, is_wildcard) )
188
+ } else {
189
+ Err ( DescriptorKeyParseError (
190
+ "Hardened derivation is currently not supported." ,
191
+ ) )
192
+ }
193
+ }
194
+ }
195
+
71
196
impl < Pk : MiniscriptKey > Descriptor < Pk > {
72
197
/// Convert a descriptor using abstract keys to one using specific keys
73
198
/// This will panic if translatefpk returns an uncompressed key when
@@ -553,13 +678,18 @@ serde_string_impl_pk!(Descriptor, "a script descriptor");
553
678
554
679
#[ cfg( test) ]
555
680
mod tests {
681
+ use super :: DescriptorKeyParseError ;
682
+
556
683
use bitcoin:: blockdata:: opcodes:: all:: { OP_CLTV , OP_CSV } ;
557
684
use bitcoin:: blockdata:: script:: Instruction ;
558
685
use bitcoin:: blockdata:: { opcodes, script} ;
559
686
use bitcoin:: hashes:: hex:: FromHex ;
560
687
use bitcoin:: hashes:: { hash160, sha256} ;
688
+ use bitcoin:: util:: bip32;
561
689
use bitcoin:: { self , secp256k1, PublicKey } ;
690
+ use descriptor:: { DescriptorPublicKey , DescriptorXPub } ;
562
691
use miniscript:: satisfy:: BitcoinSig ;
692
+ use policy;
563
693
use std:: collections:: HashMap ;
564
694
use std:: str:: FromStr ;
565
695
use { Descriptor , DummyKey , Miniscript , Satisfier } ;
@@ -1075,4 +1205,133 @@ mod tests {
1075
1205
. unwrap( ) [ ..]
1076
1206
) ;
1077
1207
}
1208
+
1209
+ #[ test]
1210
+ fn parse_descriptor_key ( ) {
1211
+ // With a wildcard
1212
+ let key = "[78412e3a/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*" ;
1213
+ let expected = DescriptorPublicKey :: XPub ( DescriptorXPub {
1214
+ origin : Some ( (
1215
+ bip32:: Fingerprint :: from ( & [ 0x78 , 0x41 , 0x2e , 0x3a ] [ ..] ) ,
1216
+ ( & [
1217
+ bip32:: ChildNumber :: from_hardened_idx ( 44 ) . unwrap ( ) ,
1218
+ bip32:: ChildNumber :: from_hardened_idx ( 0 ) . unwrap ( ) ,
1219
+ bip32:: ChildNumber :: from_hardened_idx ( 0 ) . unwrap ( ) ,
1220
+ ] [ ..] )
1221
+ . into ( ) ,
1222
+ ) ) ,
1223
+ xpub : bip32:: ExtendedPubKey :: from_str ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" ) . unwrap ( ) ,
1224
+ derivation_path : ( & [ bip32:: ChildNumber :: from_normal_idx ( 1 ) . unwrap ( ) ] [ ..] ) . into ( ) ,
1225
+ is_wildcard : true ,
1226
+ } ) ;
1227
+ assert_eq ! ( expected, key. parse( ) . unwrap( ) ) ;
1228
+
1229
+ // Without origin
1230
+ let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1" ;
1231
+ let expected = DescriptorPublicKey :: XPub ( DescriptorXPub {
1232
+ origin : None ,
1233
+ xpub : bip32:: ExtendedPubKey :: from_str ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" ) . unwrap ( ) ,
1234
+ derivation_path : ( & [ bip32:: ChildNumber :: from_normal_idx ( 1 ) . unwrap ( ) ] [ ..] ) . into ( ) ,
1235
+ is_wildcard : false ,
1236
+ } ) ;
1237
+ assert_eq ! ( expected, key. parse( ) . unwrap( ) ) ;
1238
+
1239
+ // Without derivation path
1240
+ let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" ;
1241
+ let expected = DescriptorPublicKey :: XPub ( DescriptorXPub {
1242
+ origin : None ,
1243
+ xpub : bip32:: ExtendedPubKey :: from_str ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" ) . unwrap ( ) ,
1244
+ derivation_path : bip32:: DerivationPath :: from ( & [ ] [ ..] ) ,
1245
+ is_wildcard : false ,
1246
+ } ) ;
1247
+ assert_eq ! ( expected, key. parse( ) . unwrap( ) ) ;
1248
+
1249
+ // Raw (compressed) pubkey
1250
+ let key = "03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8" ;
1251
+ let expected = DescriptorPublicKey :: PukKey (
1252
+ bitcoin:: PublicKey :: from_str (
1253
+ "03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8" ,
1254
+ )
1255
+ . unwrap ( ) ,
1256
+ ) ;
1257
+ assert_eq ! ( expected, key. parse( ) . unwrap( ) ) ;
1258
+
1259
+ // Raw (uncompressed) pubkey
1260
+ let key = "04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a" ;
1261
+ let expected = DescriptorPublicKey :: PukKey (
1262
+ bitcoin:: PublicKey :: from_str (
1263
+ "04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a" ,
1264
+ )
1265
+ . unwrap ( ) ,
1266
+ ) ;
1267
+ assert_eq ! ( expected, key. parse( ) . unwrap( ) ) ;
1268
+ }
1269
+
1270
+ #[ test]
1271
+ fn parse_descriptor_key_errors ( ) {
1272
+ // origin is only supported for xpubs
1273
+ let desc =
1274
+ "[78412e3a/0'/0'/0']0231c7d3fc85c148717848033ce276ae2b464a4e2c367ed33886cc428b8af48ff8" ;
1275
+ assert_eq ! (
1276
+ DescriptorPublicKey :: from_str( desc) ,
1277
+ Err ( DescriptorKeyParseError ( "Error while parsing xpub." ) )
1278
+ ) ;
1279
+
1280
+ // We refuse creating descriptors which claim to be able to derive hardened childs
1281
+ let desc = "[78412e3a/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/42'/*" ;
1282
+ assert_eq ! (
1283
+ DescriptorPublicKey :: from_str( desc) ,
1284
+ Err ( DescriptorKeyParseError (
1285
+ "Hardened derivation is currently not supported."
1286
+ ) )
1287
+ ) ;
1288
+
1289
+ // And ones with misplaced wildcard
1290
+ let desc = "[78412e3a/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*/44" ;
1291
+ assert_eq ! (
1292
+ DescriptorPublicKey :: from_str( desc) ,
1293
+ Err ( DescriptorKeyParseError (
1294
+ "\' *\' may only appear as last element in a derivation path."
1295
+ ) )
1296
+ ) ;
1297
+
1298
+ // And ones with invalid fingerprints
1299
+ let desc = "[NonHexor]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*" ;
1300
+ assert_eq ! (
1301
+ DescriptorPublicKey :: from_str( desc) ,
1302
+ Err ( DescriptorKeyParseError (
1303
+ "Malformed master fingerprint, expected 8 hex chars"
1304
+ ) )
1305
+ ) ;
1306
+
1307
+ // And ones with invalid xpubs
1308
+ let desc = "[78412e3a]xpub1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaLcgJvLJuZZvRcEL/1/*" ;
1309
+ assert_eq ! (
1310
+ DescriptorPublicKey :: from_str( desc) ,
1311
+ Err ( DescriptorKeyParseError ( "Error while parsing xpub." ) )
1312
+ ) ;
1313
+ }
1314
+
1315
+ #[ test]
1316
+ #[ cfg( feature = "compiler" ) ]
1317
+ fn parse_and_derive ( ) {
1318
+ let descriptor_str = "thresh(2,\
1319
+ pk([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*),\
1320
+ pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1),\
1321
+ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";
1322
+ let policy: policy:: concrete:: Policy < DescriptorPublicKey > = descriptor_str. parse ( ) . unwrap ( ) ;
1323
+ let descriptor = Descriptor :: Sh ( policy. compile ( ) . unwrap ( ) ) ;
1324
+ let derived_descriptor =
1325
+ descriptor. derive ( & [ bip32:: ChildNumber :: from_normal_idx ( 42 ) . unwrap ( ) ] ) ;
1326
+
1327
+ let res_descriptor_str = "thresh(2,\
1328
+ pk([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/42),\
1329
+ pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1),\
1330
+ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";
1331
+ let res_policy: policy:: concrete:: Policy < DescriptorPublicKey > =
1332
+ res_descriptor_str. parse ( ) . unwrap ( ) ;
1333
+ let res_descriptor = Descriptor :: Sh ( res_policy. compile ( ) . unwrap ( ) ) ;
1334
+
1335
+ assert_eq ! ( res_descriptor, derived_descriptor) ;
1336
+ }
1078
1337
}
0 commit comments