Skip to content

Commit c499a61

Browse files
committed
add HRP_CODES, IDX_ORDER, parse_header(), rename share_idx
1 parent 57378a0 commit c499a61

File tree

2 files changed

+35
-22
lines changed

2 files changed

+35
-22
lines changed

src/codex32/codex32.py

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77

88

99
CHARSET = "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
1015
MS32_CONST = 0x10CE0795C2FD1E62A
1116
MS32_LONG_CONST = 0x43381E570BF4798AB26
1217
bech32_inv = [
@@ -211,7 +216,7 @@ class SeparatorNotFound(Codex32Error):
211216

212217

213218
class InvalidLength(Codex32Error):
214-
msg = "Illegal codex32 length"
219+
msg = "Illegal codex32 data length"
215220

216221

217222
class InvalidChar(Codex32Error):
@@ -353,27 +358,35 @@ def verify_crc(data, pad_val=None):
353358
class 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:

tests/test_bip93.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def test_parts():
3636
c32 = Codex32String(VECTOR_1["secret_s"])
3737
assert c32.hrp == VECTOR_1["hrp"]
3838
assert c32.k == VECTOR_1["k"]
39-
assert c32.share_index == VECTOR_1["share_index"]
39+
assert c32.share_idx == VECTOR_1["share_index"]
4040
assert c32.ident == VECTOR_1["identifier"]
4141
assert c32.payload == VECTOR_1["payload"]
4242
assert c32.checksum == VECTOR_1["checksum"]

0 commit comments

Comments
 (0)