77
88
99CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
10+ HRP_CODES = {
11+ "ms" : 0 , # BIP-0032 master seed
12+ "cl" : 1 , # CLN HSM secret
13+ } # Registry: https://github.com/satoshilabs/slips/blob/master/slip-0173.md#uses-of-codex32
14+ IDX_ORDER = "sacdefghjklmnpqrstuvwxyz023456789" # Canonical BIP93 share indices alphabetical order
1015MS32_CONST = 0x10CE0795C2FD1E62A
1116MS32_LONG_CONST = 0x43381E570BF4798AB26
1217bech32_inv = [
@@ -211,7 +216,7 @@ class SeparatorNotFound(Codex32Error):
211216
212217
213218class InvalidLength (Codex32Error ):
214- msg = "Illegal codex32 length"
219+ msg = "Illegal codex32 data length"
215220
216221
217222class InvalidChar (Codex32Error ):
@@ -353,27 +358,35 @@ def verify_crc(data, pad_val=None):
353358class Codex32String :
354359 """Class representing a Codex32 string."""
355360
361+ @staticmethod
362+ def parse_header (header_str = "" ):
363+ """Parse a codex32 header and return its properties."""
364+ hrp = bech32_decode (header_str )[0 ] if "1" in header_str else header_str
365+ try :
366+ k = int (header_str [len (hrp ) + 1 : len (hrp ) + 2 ])
367+ except ValueError as e :
368+ raise InvalidThreshold (f"'{ header_str [len (hrp )+ 1 ]} ' must be a digit" ) from e
369+ ident = header_str [len (hrp ) + 2 : len (hrp ) + 6 ]
370+ if ident and len (ident ) < 4 :
371+ raise IdNotLength4 (f"{ len (ident )} " )
372+ share_idx = header_str [len (hrp ) + 6 : len (hrp ) + 7 ]
373+ if k == 0 and share_idx .lower () != "s" :
374+ raise InvalidShareIndex (f"'{ share_idx } ' must be 's' when k=0" )
375+ return hrp , k , ident , share_idx
376+
356377 def __init__ (self , s = "" ):
357378 self .s = s
358379 self .hrp , data = bech32_decode (s )
359- _ , s = s .rsplit ("1" , 1 )
360- if len ( s ) < 94 and len (s ) > 44 :
380+ _ , data_part = s .rsplit ("1" , 1 )
381+ if 44 < len (data_part ) < 94 :
361382 checksum_len = 13
362- elif len ( s ) >= 96 and len (s ) < 125 :
383+ elif 95 < len (data_part ) < 125 :
363384 checksum_len = 15
364385 else :
365- raise InvalidLength (f"{ len (s )} must be 45-93 or 96 to 124" )
366- threshold_char = s [0 ]
367- if threshold_char .isdigit () and threshold_char != "1" :
368- k = int (threshold_char )
369- else :
370- raise InvalidThreshold (threshold_char )
371- self .k = k
372- self .ident = s [1 :5 ]
373- self .share_index = s [5 ]
374- self .payload = s [6 : len (s ) - checksum_len ]
375- if not self .k and self .share_index .lower () != "s" :
376- raise InvalidShareIndex (self .share_index + "must be 's' when k=0" )
386+ raise InvalidLength (f"{ len (data_part )} must be 45-93 or 96-124" )
387+ header_str = bech32_encode (data [:6 ], self .hrp )
388+ _ , self .k , self .ident , self .share_idx = self .parse_header (header_str )
389+ self .payload = data_part [6 :- checksum_len ]
377390 incomplete_group = (len (self .payload ) * 5 ) % 8
378391 if incomplete_group > 4 :
379392 raise IncompleteGroup (str (incomplete_group ))
@@ -382,7 +395,7 @@ def __init__(self, s=""):
382395
383396 def __str__ (self ):
384397 return self .from_unchecksummed_string (
385- self .hrp + "1" + str (self .k ) + self .ident + self .share_index + self .payload
398+ self .hrp + "1" + str (self .k ) + self .ident + self .share_idx + self .payload
386399 ).s
387400
388401 def __eq__ (self , other ):
@@ -396,7 +409,7 @@ def __hash__(self):
396409 @property
397410 def checksum (self ):
398411 """Calculate the checksum part of the Codex32 string."""
399- data = bech32_to_u5 (str (self .k ) + self .ident + self .share_index + self .payload )
412+ data = bech32_to_u5 (str (self .k ) + self .ident + self .share_idx + self .payload )
400413 return bech32_encode (ms32_create_checksum (data , self .hrp ), "" )
401414
402415 @property
@@ -435,9 +448,9 @@ def interpolate_at(cls, shares, target="s"):
435448 raise MismatchedThreshold (f"{ s0_parts .k } , { share .k } " )
436449 if s0_parts .ident != share .ident :
437450 raise MismatchedId (f"{ s0_parts .ident } , { share .ident } " )
438- if share .share_index in indices :
439- raise RepeatedIndex (share .share_index )
440- indices .append (share .share_index )
451+ if share .share_idx in indices :
452+ raise RepeatedIndex (share .share_idx )
453+ indices .append (share .share_idx )
441454 ms32_shares .append (bech32_decode (share .s )[1 ])
442455 for i , share in enumerate (shares ):
443456 if indices [i ] == target :
0 commit comments