@@ -18,69 +18,111 @@ use alloc::vec::Vec;
18
18
19
19
pub use pb:: btc_pub_request:: XPubType ;
20
20
21
- /// Serializes a protobuf XPub to bytes according to the BIP32 specification. If xpub_type is None,
22
- /// the four version bytes are skipped.
23
- pub fn serialize_xpub ( xpub : & pb:: XPub , xpub_type : Option < XPubType > ) -> Result < Vec < u8 > , ( ) > {
24
- if xpub. depth . len ( ) != 1
25
- || xpub. parent_fingerprint . len ( ) != 4
26
- || xpub. chain_code . len ( ) != 32
27
- || xpub. public_key . len ( ) != 33
28
- {
29
- return Err ( ( ) ) ;
21
+ #[ derive( Clone ) ]
22
+ pub struct Xpub {
23
+ xpub : pb:: XPub ,
24
+ }
25
+
26
+ impl core:: convert:: From < pb:: XPub > for Xpub {
27
+ fn from ( xpub : pb:: XPub ) -> Self {
28
+ Xpub { xpub }
30
29
}
30
+ }
31
31
32
- // Version bytes for mainnet public, see BIP32.
33
- let mut result: Vec < u8 > = Vec :: new ( ) ;
34
- if let Some ( xpub_type) = xpub_type {
35
- let version = match xpub_type {
36
- XPubType :: Tpub => b"\x04 \x35 \x87 \xcf " ,
37
- XPubType :: Xpub => b"\x04 \x88 \xb2 \x1e " ,
38
- XPubType :: Ypub => b"\x04 \x9d \x7c \xb2 " ,
39
- XPubType :: Zpub => b"\x04 \xb2 \x47 \x46 " ,
40
- XPubType :: Vpub => b"\x04 \x5f \x1c \xf6 " ,
41
- XPubType :: Upub => b"\x04 \x4a \x52 \x62 " ,
42
- XPubType :: CapitalVpub => b"\x02 \x57 \x54 \x83 " ,
43
- XPubType :: CapitalZpub => b"\x02 \xaa \x7e \xd3 " ,
44
- XPubType :: CapitalUpub => b"\x02 \x42 \x89 \xef " ,
45
- XPubType :: CapitalYpub => b"\x02 \x95 \xb4 \x3f " ,
46
- } ;
47
- result. extend_from_slice ( version) ;
32
+ impl core:: convert:: From < & pb:: XPub > for Xpub {
33
+ fn from ( xpub : & pb:: XPub ) -> Self {
34
+ Xpub { xpub : xpub. clone ( ) }
48
35
}
49
- result. extend_from_slice ( & xpub. depth ) ;
50
- result. extend_from_slice ( & xpub. parent_fingerprint ) ;
51
- result. extend_from_slice ( & xpub. child_num . to_be_bytes ( ) ) ;
52
- result. extend_from_slice ( & xpub. chain_code ) ;
53
- result. extend_from_slice ( & xpub. public_key ) ;
54
- Ok ( result)
55
36
}
56
37
57
- /// Serialize an xpub as a Base58Check encoded string according to BIP32.
58
- pub fn serialize_xpub_str ( xpub : & pb:: XPub , xpub_type : XPubType ) -> Result < String , ( ) > {
59
- Ok ( bs58:: encode ( serialize_xpub ( xpub, Some ( xpub_type) ) ?)
60
- . with_check ( )
61
- . into_string ( ) )
38
+ impl core:: convert:: From < Xpub > for pb:: XPub {
39
+ fn from ( xpub : Xpub ) -> Self {
40
+ xpub. xpub
41
+ }
62
42
}
63
43
64
- /// Parses an 78-ytes xpub bytestring, encoded according to BIP32. The 4 version bytes are not
65
- /// checked and discarded.
66
- pub fn parse_xpub_bytes ( xpub : & [ u8 ] ) -> Result < pb:: XPub , ( ) > {
67
- if xpub. len ( ) != 78 {
68
- return Err ( ( ) ) ;
44
+ impl Xpub {
45
+ /// Parses an 78-ytes xpub bytestring, encoded according to BIP32. The 4 version bytes are not
46
+ /// checked and discarded.
47
+ pub fn from_bytes ( xpub : & [ u8 ] ) -> Result < Self , ( ) > {
48
+ if xpub. len ( ) != 78 {
49
+ return Err ( ( ) ) ;
50
+ }
51
+ Ok ( Self :: from ( pb:: XPub {
52
+ depth : xpub[ 4 ..5 ] . to_vec ( ) ,
53
+ parent_fingerprint : xpub[ 5 ..9 ] . to_vec ( ) ,
54
+ child_num : u32:: from_be_bytes ( core:: convert:: TryInto :: try_into ( & xpub[ 9 ..13 ] ) . unwrap ( ) ) ,
55
+ chain_code : xpub[ 13 ..45 ] . to_vec ( ) ,
56
+ public_key : xpub[ 45 ..78 ] . to_vec ( ) ,
57
+ } ) )
58
+ }
59
+ /// Serializes a protobuf XPub to bytes according to the BIP32 specification. If xpub_type is
60
+ /// None, the four version bytes are skipped.
61
+ pub fn serialize ( & self , xpub_type : Option < XPubType > ) -> Result < Vec < u8 > , ( ) > {
62
+ let xpub = & self . xpub ;
63
+ if xpub. depth . len ( ) != 1
64
+ || xpub. parent_fingerprint . len ( ) != 4
65
+ || xpub. chain_code . len ( ) != 32
66
+ || xpub. public_key . len ( ) != 33
67
+ {
68
+ return Err ( ( ) ) ;
69
+ }
70
+
71
+ // Version bytes for mainnet public, see BIP32.
72
+ let mut result: Vec < u8 > = Vec :: new ( ) ;
73
+ if let Some ( xpub_type) = xpub_type {
74
+ let version = match xpub_type {
75
+ XPubType :: Tpub => b"\x04 \x35 \x87 \xcf " ,
76
+ XPubType :: Xpub => b"\x04 \x88 \xb2 \x1e " ,
77
+ XPubType :: Ypub => b"\x04 \x9d \x7c \xb2 " ,
78
+ XPubType :: Zpub => b"\x04 \xb2 \x47 \x46 " ,
79
+ XPubType :: Vpub => b"\x04 \x5f \x1c \xf6 " ,
80
+ XPubType :: Upub => b"\x04 \x4a \x52 \x62 " ,
81
+ XPubType :: CapitalVpub => b"\x02 \x57 \x54 \x83 " ,
82
+ XPubType :: CapitalZpub => b"\x02 \xaa \x7e \xd3 " ,
83
+ XPubType :: CapitalUpub => b"\x02 \x42 \x89 \xef " ,
84
+ XPubType :: CapitalYpub => b"\x02 \x95 \xb4 \x3f " ,
85
+ } ;
86
+ result. extend_from_slice ( version) ;
87
+ }
88
+ result. extend_from_slice ( & xpub. depth ) ;
89
+ result. extend_from_slice ( & xpub. parent_fingerprint ) ;
90
+ result. extend_from_slice ( & xpub. child_num . to_be_bytes ( ) ) ;
91
+ result. extend_from_slice ( & xpub. chain_code ) ;
92
+ result. extend_from_slice ( & xpub. public_key ) ;
93
+ Ok ( result)
94
+ }
95
+
96
+ /// Serialize an xpub as a Base58Check encoded string according to BIP32.
97
+ pub fn serialize_str ( & self , xpub_type : XPubType ) -> Result < String , ( ) > {
98
+ Ok ( bs58:: encode ( self . serialize ( Some ( xpub_type) ) ?)
99
+ . with_check ( )
100
+ . into_string ( ) )
101
+ }
102
+
103
+ /// Returns the 33 bytes secp256k1 compressed pubkey.
104
+ pub fn public_key ( & self ) -> & [ u8 ] {
105
+ self . xpub . public_key . as_slice ( )
106
+ }
107
+
108
+ /// Return the hash160 of the secp256k1 public key.
109
+ pub fn pubkey_hash160 ( & self ) -> Vec < u8 > {
110
+ bitbox02:: hash160 ( self . public_key ( ) ) . to_vec ( )
111
+ }
112
+
113
+ /// Return the 65 byte secp256k1 compressed pubkey:
114
+ ///
115
+ /// (<0x04><64 bytes X><64 bytes Y>).
116
+ pub fn pubkey_uncompressed ( & self ) -> Result < [ u8 ; 65 ] , ( ) > {
117
+ bitbox02:: keystore:: secp256k1_pubkey_compressed_to_uncompressed ( self . public_key ( ) )
69
118
}
70
- Ok ( pb:: XPub {
71
- depth : xpub[ 4 ..5 ] . to_vec ( ) ,
72
- parent_fingerprint : xpub[ 5 ..9 ] . to_vec ( ) ,
73
- child_num : u32:: from_be_bytes ( core:: convert:: TryInto :: try_into ( & xpub[ 9 ..13 ] ) . unwrap ( ) ) ,
74
- chain_code : xpub[ 13 ..45 ] . to_vec ( ) ,
75
- public_key : xpub[ 45 ..78 ] . to_vec ( ) ,
76
- } )
77
119
}
78
120
79
121
/// Parses a Base58Check-encoded xpub string. The 4 version bytes are not checked and discarded.
80
122
#[ cfg( test) ]
81
123
pub fn parse_xpub ( xpub : & str ) -> Result < pb:: XPub , ( ) > {
82
124
let decoded = bs58:: decode ( xpub) . with_check ( None ) . into_vec ( ) . or ( Err ( ( ) ) ) ?;
83
- parse_xpub_bytes ( & decoded)
125
+ Ok ( Xpub :: from_bytes ( & decoded) ? . into ( ) )
84
126
}
85
127
86
128
#[ cfg( test) ]
@@ -89,50 +131,74 @@ mod tests {
89
131
90
132
#[ test]
91
133
fn test_parse_serialize_xpub ( ) {
92
- let xpub = parse_xpub ( "xpub6Eu7xJRyXRCi4eLYhJPnfZVjgAQtM7qFaEZwUhvgxGf4enEZMxevGzWvZTawCj9USP2MFTEhKQAwnqHwoaPHetTLqGuvq5r5uaLKyGx5QDZ" ) . unwrap ( ) ;
134
+ let xpub = Xpub :: from ( parse_xpub ( "xpub6Eu7xJRyXRCi4eLYhJPnfZVjgAQtM7qFaEZwUhvgxGf4enEZMxevGzWvZTawCj9USP2MFTEhKQAwnqHwoaPHetTLqGuvq5r5uaLKyGx5QDZ" ) . unwrap ( ) ) ;
93
135
assert_eq ! (
94
- serialize_xpub ( & xpub, None ) . unwrap( ) ,
136
+ xpub. serialize ( None ) . unwrap( ) ,
95
137
hex:: decode( "04b9d184d180000002b5b571ead68edac616c38491d9fd78d4697077e7675333452b586e3282705a3a0281bec7de8d182945744445948b54800e95267a5ac039bab6218a03b8e6f4b38a" ) . unwrap( ) ,
96
138
) ;
97
139
assert_eq ! (
98
- serialize_xpub_str ( & xpub, XPubType :: Tpub ) . unwrap( ) . as_str( ) ,
140
+ xpub. serialize_str ( XPubType :: Tpub ) . unwrap( ) . as_str( ) ,
99
141
"tpubDFGkUYFfEhAALSXQ9VNssUq71HWYLWLK7sAEqFyqJBQxQ4uGSBW1RSBkoVfijE6iEHZFs2kZrVzzV1nZCSEXYKudtsfEWcWKVXvjjLeRyd8" ,
100
142
) ;
101
143
assert_eq ! (
102
- serialize_xpub_str ( & xpub, XPubType :: Xpub ) . unwrap( ) . as_str( ) ,
144
+ xpub. serialize_str ( XPubType :: Xpub ) . unwrap( ) . as_str( ) ,
103
145
"xpub6Eu7xJRyXRCi4eLYhJPnfZVjgAQtM7qFaEZwUhvgxGf4enEZMxevGzWvZTawCj9USP2MFTEhKQAwnqHwoaPHetTLqGuvq5r5uaLKyGx5QDZ" ,
104
146
) ;
105
147
assert_eq ! (
106
- serialize_xpub_str ( & xpub, XPubType :: Ypub ) . unwrap( ) . as_str( ) ,
148
+ xpub. serialize_str ( XPubType :: Ypub ) . unwrap( ) . as_str( ) ,
107
149
"ypub6ZjPFy6tg6kBuwXfXfBQsebEr8ZLHjpkVM6AG6paLH2wht3nccpUu4B4afYXCdoPr299zvqFn4XVg7uWXGoJT88whccMQzfaBJPyMrVtPLb" ,
108
150
) ;
109
151
assert_eq ! (
110
- serialize_xpub_str ( & xpub, XPubType :: Zpub ) . unwrap( ) . as_str( ) ,
152
+ xpub. serialize_str ( XPubType :: Zpub ) . unwrap( ) . as_str( ) ,
111
153
"zpub6tZeZdmopnHfmEinN1y35jgk26hnEMpFQTcP3ViTiHQpkys1sGz3X7qCbsW7CYTKFfFxkQRpEit3ZQX5EyDKFMpYZxJmzuV4T2TckTXCeKB" ,
112
154
) ;
113
155
assert_eq ! (
114
- serialize_xpub_str ( & xpub, XPubType :: Vpub ) . unwrap( ) . as_str( ) ,
156
+ xpub. serialize_str ( XPubType :: Vpub ) . unwrap( ) . as_str( ) ,
115
157
"vpub5bEbLy69E47kN3xK2apYFPJjLE7zTsrFk1XVuv8vCFuJYac6reKo2sCeX3fmCuqdd6njkW3aQ5Tr2G4pNBZG4R696bX5fGD7N8D3CBtsPoq" ,
116
158
) ;
117
159
assert_eq ! (
118
- serialize_xpub_str ( & xpub, XPubType :: Upub ) . unwrap( ) . as_str( ) ,
160
+ xpub. serialize_str ( XPubType :: Upub ) . unwrap( ) . as_str( ) ,
119
161
"upub5GQL3JRE5NaGWkmCCE2v3JDEAFyYXFrkpu1H8XF2pFXRVUnsbzAEQoYWVqiBD1BiDTfw12T1wR7J8yTFeV9FGBQYEFpf5MPd6Q9PoXnSeMz" ,
120
162
) ;
121
163
assert_eq ! (
122
- serialize_xpub_str ( & xpub, XPubType :: CapitalVpub ) . unwrap( ) . as_str( ) ,
164
+ xpub. serialize_str ( XPubType :: CapitalVpub ) . unwrap( ) . as_str( ) ,
123
165
"Vpub5n8gUCpao1g7nd7gyFHX5TeY42AFgEXr4HBAqBQTa2jiAmB1d3i57z4aKkdEmM4XrZrid63hHHrM9RgafQiDCuCow4dV4fg7FrUun5qYdcZ" ,
124
166
) ;
125
167
assert_eq ! (
126
- serialize_xpub_str ( & xpub, XPubType :: CapitalZpub ) . unwrap( ) . as_str( ) ,
168
+ xpub. serialize_str ( XPubType :: CapitalZpub ) . unwrap( ) . as_str( ) ,
127
169
"Zpub75TjgsWFPjr3BotAJgS1up2Yjtk3SiVqijG3xkz164FEPARvdgNKcEh8QaTakygDV8KwczRw7wGYga8qYCNGPqwDQRRBQJx4LkjVLLY6Yta" ,
128
170
) ;
129
171
assert_eq ! (
130
- serialize_xpub_str ( & xpub, XPubType :: CapitalUpub ) . unwrap( ) . as_str( ) ,
172
+ xpub. serialize_str ( XPubType :: CapitalUpub ) . unwrap( ) . as_str( ) ,
131
173
"Upub5TJRAY9feL8dwKva8tVtsNZ2t41ojcYM9Aex3nWaC2Mq7fMnNPYWVvQSJYfemSQcSvjuscT8pdVoG951wiJCQfXD4iw4Ukrcz8RGPZ1BoeD" ,
132
174
) ;
133
175
assert_eq ! (
134
- serialize_xpub_str ( & xpub, XPubType :: CapitalYpub ) . unwrap( ) . as_str( ) ,
176
+ xpub. serialize_str ( XPubType :: CapitalYpub ) . unwrap( ) . as_str( ) ,
135
177
"Ypub6kdUPCqLF4JZLWh3UKePhiw3ZvbbW6WLocjqBN67i3sML4chP2CkzB2zPNVzm52J5VD8sWqNfGuzoHXGpVxFbcFcY5ikpQ8a52fqwpAmYX2" ,
136
178
) ;
137
179
}
180
+
181
+ #[ test]
182
+ fn test_pubkey_hash160 ( ) {
183
+ let xpub = Xpub :: from ( parse_xpub ( "xpub6GugPDcUhrSudznFss7wXvQV3gwFTEanxHdCyoNoHnZEr3PTbh2Fosg4JjfphaYAsqjBhmtTZ3Yo8tmGjSHtaPhExNiMCSvPzreqjrX4Wr7" ) . unwrap ( ) ) ;
184
+ assert_eq ! (
185
+ xpub. pubkey_hash160( ) ,
186
+ * b"\xb5 \x12 \x5c \xec \xa0 \xc1 \xc8 \x90 \xda \x07 \x9a \x12 \x88 \xdc \xf7 \x7a \xa6 \xac \xc4 \x99 "
187
+ ) ;
188
+
189
+ let xpub = Xpub :: from ( parse_xpub ( "xpub6FiMwSqu98LjKsbGy1PfgGRQA9XH7k6dfsyPedsyrdBRJDPwc658JA3qGc7DV2dWUYVGEqzRicztwzCj1NprQSRbSubWcnkKxM3Gwnyh4xo" ) . unwrap ( ) ) ;
190
+ assert_eq ! (
191
+ xpub. pubkey_hash160( ) ,
192
+ * b"\xe5 \xf8 \x9a \xb6 \x54 \x37 \x44 \xf7 \x8f \x15 \x86 \x7c \x43 \x06 \xee \x86 \x6b \xb1 \x1d \xf9 "
193
+ ) ;
194
+ }
195
+
196
+ #[ test]
197
+ fn test_secp256k1_pubkey_uncompressed ( ) {
198
+ let xpub = Xpub :: from ( parse_xpub ( "xpub6FiMwSqu98LjKsbGy1PfgGRQA9XH7k6dfsyPedsyrdBRJDPwc658JA3qGc7DV2dWUYVGEqzRicztwzCj1NprQSRbSubWcnkKxM3Gwnyh4xo" ) . unwrap ( ) ) ;
199
+ assert_eq ! (
200
+ xpub. pubkey_uncompressed( ) . unwrap( ) ,
201
+ * b"\x04 \x77 \xa4 \x4a \xa9 \xe8 \xc8 \xfb \x51 \x05 \xef \x5e \xe2 \x39 \x4e \x8a \xed \x89 \xad \x73 \xfc \x74 \x36 \x14 \x25 \xf0 \x63 \x47 \xec \xfe \x32 \x61 \x31 \xe1 \x33 \x93 \x67 \xee \x3c \xbe \x87 \x71 \x92 \x85 \xa0 \x7f \x77 \x4b \x17 \xeb \x93 \x3e \xcf \x0b \x9b \x82 \xac \xeb \xc1 \x95 \x22 \x6d \x63 \x42 \x44 " ,
202
+ ) ;
203
+ }
138
204
}
0 commit comments