1
- /// Parses an address into a puzzle hash.
2
- pub fn parse_address ( address : & str ) -> [ u8 ; 32 ] {
3
- if let Ok ( puzzle_hash) = hex:: decode ( strip_prefix ( address) ) {
4
- puzzle_hash. try_into ( ) . expect ( "invalid puzzle hash" )
1
+ use bech32:: { u5, Variant } ;
2
+ use hex:: FromHexError ;
3
+ use thiserror:: Error ;
4
+
5
+ /// Errors you can get while trying to decode an address.
6
+ #[ derive( Error , Debug , Clone , PartialEq , Eq ) ]
7
+ pub enum AddressError {
8
+ /// The wrong HRP prefix was used.
9
+ #[ error( "invalid prefix `{0}`" ) ]
10
+ InvalidPrefix ( String ) ,
11
+
12
+ /// The address was encoded as bech32, rather than bech32m.
13
+ #[ error( "encoding is not bech32m" ) ]
14
+ InvalidFormat ,
15
+
16
+ /// The data was not 32 bytes in length.
17
+ #[ error( "wrong length, expected 32 bytes but found {0}" ) ]
18
+ WrongLength ( usize ) ,
19
+
20
+ /// An error occured while trying to decode the address.
21
+ #[ error( "error when decoding address: {0}" ) ]
22
+ Decode ( #[ from] bech32:: Error ) ,
23
+ }
24
+
25
+ /// Errors you can get while trying to decode a puzzle hash.
26
+ #[ derive( Error , Debug , Clone , PartialEq ) ]
27
+ pub enum PuzzleHashError {
28
+ /// The buffer was not 32 bytes in length.
29
+ #[ error( "wrong length, expected 32 bytes but found {0}" ) ]
30
+ WrongLength ( usize ) ,
31
+
32
+ /// An error occured while trying to decode the puzzle hash.
33
+ #[ error( "error when decoding puzzle hash: {0}" ) ]
34
+ Decode ( #[ from] FromHexError ) ,
35
+ }
36
+
37
+ /// Decodes a puzzle hash from hex, with or without a prefix.
38
+ pub fn decode_puzzle_hash ( puzzle_hash : & str ) -> Result < [ u8 ; 32 ] , PuzzleHashError > {
39
+ let data = hex:: decode ( strip_prefix ( puzzle_hash) ) ?;
40
+ let length = data. len ( ) ;
41
+ data. try_into ( )
42
+ . map_err ( |_| PuzzleHashError :: WrongLength ( length) )
43
+ }
44
+
45
+ /// Encodes a puzzle hash into hex, with or without a prefix.
46
+ pub fn encode_puzzle_hash ( puzzle_hash : [ u8 ; 32 ] , include_0x : bool ) -> String {
47
+ if include_0x {
48
+ format ! ( "0x{}" , hex:: encode( puzzle_hash) )
5
49
} else {
6
- let ( _hrp, data, _variant) = bech32:: decode ( address) . expect ( "invalid address" ) ;
7
- let puzzle_hash = bech32:: convert_bits ( & data, 5 , 8 , false ) . expect ( "invalid address data" ) ;
8
- puzzle_hash
9
- . try_into ( )
10
- . expect ( "invalid address puzzle hash encoding" )
50
+ hex:: encode ( puzzle_hash)
11
51
}
12
52
}
13
53
54
+ /// Decodes an address into a puzzle hash and HRP prefix.
55
+ pub fn decode_address ( address : & str ) -> Result < ( [ u8 ; 32 ] , String ) , AddressError > {
56
+ let ( hrp, data, variant) = bech32:: decode ( address) ?;
57
+ if variant != Variant :: Bech32m {
58
+ return Err ( AddressError :: InvalidFormat ) ;
59
+ }
60
+
61
+ let data = bech32:: convert_bits ( & data, 5 , 8 , false ) ?;
62
+ let length = data. len ( ) ;
63
+ let puzzle_hash = data
64
+ . try_into ( )
65
+ . map_err ( |_| AddressError :: WrongLength ( length) ) ?;
66
+
67
+ Ok ( ( puzzle_hash, hrp) )
68
+ }
69
+
70
+ /// Encodes an address with a given HRP prefix.
71
+ pub fn encode_address ( puzzle_hash : [ u8 ; 32 ] , prefix : & str ) -> Result < String , bech32:: Error > {
72
+ let data = bech32:: convert_bits ( & puzzle_hash, 8 , 5 , true )
73
+ . unwrap ( )
74
+ . into_iter ( )
75
+ . map ( u5:: try_from_u8)
76
+ . collect :: < Result < Vec < _ > , bech32:: Error > > ( ) ?;
77
+ bech32:: encode ( prefix, data, Variant :: Bech32m )
78
+ }
79
+
14
80
/// Removes the `0x` prefix from a puzzle hash in hex format.
15
- fn strip_prefix ( puzzle_hash : & str ) -> & str {
81
+ pub fn strip_prefix ( puzzle_hash : & str ) -> & str {
16
82
if let Some ( puzzle_hash) = puzzle_hash. strip_prefix ( "0x" ) {
17
83
puzzle_hash
18
84
} else if let Some ( puzzle_hash) = puzzle_hash. strip_prefix ( "0X" ) {
@@ -21,3 +87,87 @@ fn strip_prefix(puzzle_hash: &str) -> &str {
21
87
puzzle_hash
22
88
}
23
89
}
90
+
91
+ #[ cfg( test) ]
92
+ mod tests {
93
+ use hex_literal:: hex;
94
+
95
+ use super :: * ;
96
+
97
+ fn check_ph ( expected : & str ) {
98
+ let expected = strip_prefix ( expected) ;
99
+ let puzzle_hash = decode_puzzle_hash ( expected) . unwrap ( ) ;
100
+ let actual = encode_puzzle_hash ( puzzle_hash, false ) ;
101
+ assert_eq ! ( actual, expected) ;
102
+ }
103
+
104
+ fn check_addr ( expected : & str ) {
105
+ let ( puzzle_hash, prefix) = decode_address ( expected) . unwrap ( ) ;
106
+ let actual = encode_address ( puzzle_hash, & prefix) . unwrap ( ) ;
107
+ assert_eq ! ( actual, expected) ;
108
+ }
109
+
110
+ #[ test]
111
+ fn test_strip_prefix ( ) {
112
+ check_ph ( "0x2999682870bd24e7fd0ef6324c69794ff93fc41b016777d2edd5ea8575bdaa31" ) ;
113
+ check_ph ( "0x99619cc6888f1bd30acd6e8c1f4065dafeba2246bfc3465cddda4e6656083791" ) ;
114
+ check_ph ( "0X7cc6494dd96d32c97b5f6ba77caae269acd6c86593ada66f343050ce709e904a" ) ;
115
+ check_ph ( "0X9f057817ad576b24ec60a25ded08f5bde6db0aa0beeb0c099e3ce176866e1c4b" ) ;
116
+ }
117
+
118
+ #[ test]
119
+ fn test_puzzle_hashes ( ) {
120
+ check_ph ( & hex:: encode ( [ 0 ; 32 ] ) ) ;
121
+ check_ph ( & hex:: encode ( [ 255 ; 32 ] ) ) ;
122
+ check_ph ( & hex:: encode ( [ 127 ; 32 ] ) ) ;
123
+ check_ph ( & hex:: encode ( [ 1 ; 32 ] ) ) ;
124
+ check_ph ( "f46ec440aeb9b3baa19968810a8537ec4ff406c09c994dd7d3222b87258a52ff" ) ;
125
+ check_ph ( "2f981b2f9510ef9e62523e6b38fc933e2f060c411cfa64906413ddfd56be8dc1" ) ;
126
+ check_ph ( "3e09bdd6b19659555a7c8456c5af54d004d774f3d44689360d4778ce685201ad" ) ;
127
+ check_ph ( "d16c2ad7c5642532659e424dc0d7e4a85779c6dab801b5e6117a8c8587156472" ) ;
128
+ }
129
+
130
+ #[ test]
131
+ fn test_invalid_puzzle_hashes ( ) {
132
+ assert_eq ! (
133
+ decode_puzzle_hash( "ac4fd55996a1186fffc30c5b60385a88fd78d538f1c9febbfa9c8a9e9a170ad" ) ,
134
+ Err ( PuzzleHashError :: Decode ( FromHexError :: OddLength ) )
135
+ ) ;
136
+ assert_eq ! (
137
+ decode_puzzle_hash( & hex:: encode( hex!(
138
+ "
139
+ dfe399911acc4426f44bf31f4d817f6b69f244bbad138a28
140
+ 25c05550f7d2ab70c35408f764281febd624ac8cdfc91817
141
+ "
142
+ ) ) ) ,
143
+ Err ( PuzzleHashError :: WrongLength ( 48 ) )
144
+ ) ;
145
+ assert_eq ! (
146
+ decode_puzzle_hash( "hello there!" ) ,
147
+ Err ( PuzzleHashError :: Decode ( FromHexError :: InvalidHexCharacter {
148
+ c: 'h' ,
149
+ index: 0
150
+ } ) )
151
+ ) ;
152
+ }
153
+
154
+ #[ test]
155
+ fn test_addresses ( ) {
156
+ check_addr ( "xch1a0t57qn6uhe7tzjlxlhwy2qgmuxvvft8gnfzmg5detg0q9f3yc3s2apz0h" ) ;
157
+ check_addr ( "xch1ftxk2v033kv94ueucp0a34sgt9398vle7l7g3q9k4leedjmmdysqvv6q96" ) ;
158
+ check_addr ( "xch1ay273ctc9c6nxmzmzsup28scrce8ney84j4nlewdlaxqs22v53ksxgf38f" ) ;
159
+ check_addr ( "xch1avnwmy2fuesq7h2jnxehlrs9msrad9uuvrhms35k2pqwmjv56y5qk7zm6v" ) ;
160
+ }
161
+
162
+ #[ test]
163
+ fn test_invalid_addresses ( ) {
164
+ assert_eq ! (
165
+ decode_address( "hello there!" ) ,
166
+ Err ( AddressError :: Decode ( bech32:: Error :: MissingSeparator ) )
167
+ ) ;
168
+ assert_eq ! (
169
+ decode_address( "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq" ) ,
170
+ Err ( AddressError :: InvalidFormat )
171
+ ) ;
172
+ }
173
+ }
0 commit comments