@@ -2,6 +2,7 @@ const std = @import("std");
22const expect = std .testing .expect ;
33const expectEqualSlices = std .testing .expectEqualSlices ;
44const expectError = std .testing .expectError ;
5+ const expectEqualStrings = std .testing .expectEqualStrings ;
56
67/// The human readable part of a bech32 address is limited to 83 US-ASCII characters.
78const MAX_HRP_LEN : usize = 83 ;
@@ -12,6 +13,60 @@ const MIN_ASCII: u8 = 33;
1213/// The maximum ASCII value for a valid character in the human readable part.
1314const MAX_ASCII : u8 = 126 ;
1415
16+ /// The human-readable part (HRP) for the Bitcoin mainnet.
17+ ///
18+ /// This corresponds to `bc` prefix.
19+ ///
20+ /// Example:
21+ /// - Mainnet P2WPKH: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
22+ const BC : Hrp = .{
23+ .buf = [_ ]u8 {
24+ 98 , 99 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
25+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
26+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
27+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
28+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
29+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
30+ 0 , 0 , 0 , 0 , 0 ,
31+ },
32+ .size = 2 ,
33+ };
34+
35+ /// The human-readable part (HRP) for Bitcoin testnet networks (testnet and signet).
36+ ///
37+ /// This corresponds to `tb` prefix.
38+ ///
39+ /// Example:
40+ /// - Testnet P2WPKH: tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx
41+ const TB : Hrp = .{
42+ .buf = [_ ]u8 {
43+ 116 , 98 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
44+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
45+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
46+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
47+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
48+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
49+ 0 , 0 , 0 , 0 , 0 ,
50+ },
51+ .size = 2 ,
52+ };
53+
54+ /// The human-readable part (HRP) for the Bitcoin regtest network.
55+ ///
56+ /// This corresponds to `bcrt` prefix.
57+ const BCRT : Hrp = .{
58+ .buf = [_ ]u8 {
59+ 98 , 99 , 114 , 116 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
60+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
61+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
62+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
63+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
64+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
65+ 0 , 0 , 0 , 0 , 0 ,
66+ },
67+ .size = 4 ,
68+ };
69+
1570/// Various errors that can occur during HRP processing.
1671///
1772/// These errors help in validating and debugging issues with bech32 address formatting.
@@ -146,6 +201,110 @@ pub const Hrp = struct {
146201 // Return the constructed and validated `Hrp` instance.
147202 return new ;
148203 }
204+
205+ /// Converts the human-readable part (HRP) to a lowercase representation.
206+ pub fn toLowerCase (self : * const Self , output : []u8 ) []const u8 {
207+ std .debug .assert (output .len >= self .size );
208+
209+ // Loop through each character of the HRP and convert it to lowercase.
210+ for (self .buf [0.. self .size ], 0.. ) | b , i | {
211+ output [i ] = std .ascii .toLower (b );
212+ }
213+
214+ return output [0.. self .size ];
215+ }
216+
217+ /// Converts the human-readable part (HRP) to bytes.
218+ pub fn asBytes (self : * const Self ) []const u8 {
219+ return self .buf [0.. self .size ];
220+ }
221+
222+ /// Checks whether two HRPs are equal.
223+ pub fn eql (self : * const Self , rhs : * const Self ) bool {
224+ // If the HRPs have different sizes, they are not equal.
225+ if (self .size != rhs .size ) return false ;
226+
227+ // Create buffers to store the lowercase versions of the HRPs.
228+ var buf_lhs : [MAX_HRP_LEN ]u8 = undefined ;
229+ var buf_rhs : [MAX_HRP_LEN ]u8 = undefined ;
230+
231+ // Convert both HRPs to lowercase.
232+ const l = self .toLowerCase (& buf_lhs );
233+ const r = rhs .toLowerCase (& buf_rhs );
234+
235+ // Compare each byte of the lowercase HRPs for equality.
236+ for (l , r ) | a , b |
237+ if (a != b ) return false ;
238+
239+ return true ;
240+ }
241+
242+ /// Checks whether a given Segwit address is valid on either the mainnet or testnet.
243+ ///
244+ /// A Segwit address must follow the Bech32 encoding format, with the human-readable
245+ /// part "bc" for mainnet or "tb" for testnet. This function combines the logic of
246+ /// validating an address on both networks.
247+ ///
248+ /// # Returns
249+ /// - `true` if the Segwit address is valid on either the mainnet or testnet.
250+ /// - `false` otherwise.
251+ ///
252+ /// # Segwit Address Requirements:
253+ /// - The human-readable part must be "bc" (mainnet) or "tb" (testnet).
254+ /// - The witness program must follow the rules outlined in BIP141.
255+ pub fn isValidSegwit (self : * const Self ) bool {
256+ return self .isValidOnMainnet () or self .isValidOnTestnet ();
257+ }
258+
259+ /// Checks whether a given Segwit address is valid on the Bitcoin mainnet.
260+ ///
261+ /// Segwit addresses on the mainnet use the human-readable part "bc". This function
262+ /// verifies that the provided address corresponds to the mainnet format.
263+ ///
264+ /// # Returns
265+ /// - `true` if the Segwit address is valid on the mainnet (with the "bc" prefix).
266+ /// - `false` otherwise.
267+ pub fn isValidOnMainnet (self : * const Self ) bool {
268+ return self .eql (& BC );
269+ }
270+
271+ /// Checks whether a given Segwit address is valid on the Bitcoin testnet.
272+ ///
273+ /// Segwit addresses on the testnet use the human-readable part "tb". This function
274+ /// verifies that the provided address corresponds to the testnet format.
275+ ///
276+ /// # Returns
277+ /// - `true` if the Segwit address is valid on the testnet (with the "tb" prefix).
278+ /// - `false` otherwise.
279+ pub fn isValidOnTestnet (self : * const Self ) bool {
280+ return self .eql (& TB );
281+ }
282+
283+ /// Checks whether a given Segwit address is valid on the Bitcoin signet.
284+ ///
285+ /// Segwit addresses on signet also use the human-readable part "tb", similar to
286+ /// testnet addresses. This function verifies that the provided address corresponds
287+ /// to the signet format.
288+ ///
289+ /// # Returns
290+ /// - `true` if the Segwit address is valid on signet (with the "tb" prefix).
291+ /// - `false` otherwise.
292+ pub fn isValidOnSignet (self : * const Self ) bool {
293+ return self .eql (& TB );
294+ }
295+
296+ /// Checks whether a given Segwit address is valid on the Bitcoin regtest network.
297+ ///
298+ /// Segwit addresses on the regtest network use the human-readable part "bcrt".
299+ /// This function verifies that the provided address corresponds to the regtest
300+ /// format.
301+ ///
302+ /// # Returns
303+ /// - `true` if the Segwit address is valid on regtest (with the "bcrt" prefix).
304+ /// - `false` otherwise.
305+ pub fn isValidOnRegtest (self : * const Self ) bool {
306+ return self .eql (& BCRT );
307+ }
149308};
150309
151310test "Hrp: check parse is ok" {
@@ -210,3 +369,91 @@ test "Hrp: Hrp with invalid ASCII byte should fail parsing" {
210369 // Attempt to parse the HRP with invalid characters, expecting an `InvalidAsciiByte` error.
211370 try expectError (HrpError .InvalidAsciiByte , Hrp .parse (case ));
212371}
372+
373+ test "Hrp: Hrp to lower case" {
374+ // Some valid human readable parts.
375+ const cases = [_ ][]const u8 {
376+ "a" ,
377+ "A" ,
378+ "abcdefg" ,
379+ "ABCDEFG" ,
380+ "abc123def" ,
381+ "ABC123DEF" ,
382+ "!\" #$%&'()*+,-./" ,
383+ "1234567890" ,
384+ };
385+
386+ // The expected results for the human readable parts in lowercase.
387+ const expected_results = [_ ][]const u8 {
388+ "a" ,
389+ "a" ,
390+ "abcdefg" ,
391+ "abcdefg" ,
392+ "abc123def" ,
393+ "abc123def" ,
394+ "!\" #$%&'()*+,-./" ,
395+ "1234567890" ,
396+ };
397+
398+ // Go through all the test cases.
399+ for (cases , expected_results ) | case , expected | {
400+ // Parse the human readable part.
401+ const hrp = try Hrp .parse (case );
402+ var buf : [MAX_HRP_LEN ]u8 = undefined ;
403+
404+ // Convert the human readable part to lowercase.
405+ try expectEqualStrings (expected , hrp .toLowerCase (& buf ));
406+ }
407+ }
408+
409+ test "Hrp: as bytes should return the proper bytes" {
410+ // Some valid human readable parts.
411+ const cases = [_ ][]const u8 {
412+ "a" ,
413+ "A" ,
414+ "abcdefg" ,
415+ "ABCDEFG" ,
416+ "abc123def" ,
417+ "ABC123DEF" ,
418+ "!\" #$%&'()*+,-./" ,
419+ "1234567890" ,
420+ };
421+
422+ // Go through all the test cases.
423+ for (cases ) | case | {
424+ // Parse the human readable part.
425+ const hrp = try Hrp .parse (case );
426+ // Convert the human readable part to lowercase.
427+ try expectEqualSlices (u8 , case , hrp .asBytes ());
428+ }
429+ }
430+
431+ test "Hrp: ensure eql function works properly" {
432+ // Parse two human readable parts which are equal.
433+ const lhs1 = try Hrp .parse ("!\" #$%&'()*+,-./" );
434+ const rhs1 = try Hrp .parse ("!\" #$%&'()*+,-./" );
435+ // Assert that the two human readable parts are equal.
436+ try expect (lhs1 .eql (& rhs1 ));
437+
438+ // Generate another human readable part which is different.
439+ const rhs2 = try Hrp .parse ("!\" #$%&'()*+,-.a" );
440+ // Assert that the two human readable parts are not equal.
441+ try expect (! lhs1 .eql (& rhs2 ));
442+
443+ // Generate another human readable part with a different size.
444+ const rhs3 = try Hrp .parse ("!\" #$%&'()*+,-." );
445+ // Assert that the two human readable parts are not equal (different size).
446+ try expect (! lhs1 .eql (& rhs3 ));
447+
448+ // Parse two human readable parts which are equal, but with different case.
449+ const lhs_case_insensitive = try Hrp .parse ("abcdefg" );
450+ const rhs_case_insensitive = try Hrp .parse ("ABCDEFG" );
451+ // Assert that the two human readable parts are equal.
452+ try expect (lhs_case_insensitive .eql (& rhs_case_insensitive ));
453+ }
454+
455+ test "Hrp: ensure constants are properly setup" {
456+ try expect (BC .eql (&(try Hrp .parse ("bc" ))));
457+ try expect (TB .eql (&(try Hrp .parse ("tb" ))));
458+ try expect (BCRT .eql (&(try Hrp .parse ("bcrt" ))));
459+ }
0 commit comments