Skip to content
Draft
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4271f95
Add codex32 application to bip85.py
BenWestgate Aug 19, 2025
37e4c9c
fix imports, ValueError, don't assign unused data, remove junk code b…
BenWestgate Aug 19, 2025
0c1bcc7
BIP93: fix t-n = 1 bug, fixup imports, sanity checks
BenWestgate Aug 19, 2025
a505d0d
Create bip93.py
BenWestgate Aug 19, 2025
fbdb997
bip85 vectors: add BIP93 test vectors
BenWestgate Aug 19, 2025
275bf26
WIP: placeholder for the full BIP93 test vectors
BenWestgate Aug 19, 2025
e9d4c11
test_BIP85: add test_codex32 to validate vectors
BenWestgate Aug 19, 2025
535d47b
WIP: Create test_bip93.py need to find test vector
BenWestgate Aug 19, 2025
3abca70
Add INDEX_TO_HRP dict
BenWestgate Oct 6, 2025
276f0e5
Remove electrum, add entropy_to_codex32
BenWestgate Oct 6, 2025
66ca66d
Create settings.json
BenWestgate Oct 6, 2025
bfa13bd
Add cli tool "codex32" for generating codex32
BenWestgate Oct 6, 2025
b1873d5
Update bip93_vectors.py
BenWestgate Oct 6, 2025
193aae3
fix import typo
BenWestgate Oct 6, 2025
33777e0
Update test_bip93.py
BenWestgate Oct 6, 2025
d93858d
fix codex32 help string as it uses token_bytes
BenWestgate Oct 12, 2025
965f020
Add codex32 & recover features & bip85 codex32 app
BenWestgate Oct 13, 2025
9a4ae77
Add codex32 dependency for bip93 encoding.
BenWestgate Oct 13, 2025
65bd0b1
bip85: Import things from codex32 we'll need
BenWestgate Oct 13, 2025
94b15df
add: xprv --codex32 option and `recover` command
BenWestgate Oct 13, 2025
2c63697
Update README.md
BenWestgate Oct 14, 2025
3b34cc7
Update README.md
BenWestgate Oct 14, 2025
86308ba
Update pyproject.toml
BenWestgate Oct 14, 2025
676b864
Merge pull request #3 from BenWestgate/patch-2
BenWestgate Oct 14, 2025
5b2f1e3
Delete test_bip93.py
BenWestgate Oct 14, 2025
f24e6ac
Delete bip93_vectors.py
BenWestgate Oct 14, 2025
9c26d8b
import codex32, delete non-bip85 codex32 commands
BenWestgate Oct 14, 2025
143e81e
Update pyproject.toml to import codex32
BenWestgate Oct 14, 2025
df2a599
doc: add example of the proposed bip85 codex32 app
BenWestgate Oct 14, 2025
be05e2e
Delete bip93.py
BenWestgate Oct 14, 2025
ddb119e
Merge branch 'patch-1' of https://github.com/BenWestgate/bipsea into …
BenWestgate Oct 14, 2025
14fd2d8
Delete settings.json
BenWestgate Oct 14, 2025
38814cb
Update .gitignore
BenWestgate Oct 14, 2025
0a9708c
Update bipsea.py
BenWestgate Oct 14, 2025
a1b860b
new bip85 bip93 derive proposal
BenWestgate Oct 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"python-envs.pythonProjects": [],
"python-envs.defaultEnvManager": "ms-python.python:system"
}
72 changes: 72 additions & 0 deletions src/bipsea/bip85.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .bip32 import derive_key as derive_key_bip32
from .bip32 import hmac_sha512
from .bip39 import LANGUAGES, N_WORDS_META, entropy_to_words, validate_mnemonic_words
from .bip93 import CHARSET, ms32_recover, fingerprint, convertbits, ms32_interpolate, ms32_encode, validate_set
from .util import LOGGER_NAME, to_hex_string

logger = logging.getLogger(LOGGER_NAME)
Expand All @@ -23,6 +24,7 @@
"drng": "0'",
"hex": "128169'",
"mnemonic": "39'",
"codex32": "93'",
"wif": "2'",
"xprv": "32'",
}
Expand Down Expand Up @@ -51,6 +53,11 @@
"9'": "portuguese", # not in BIP-85 but in BIP-39 test vectors
}

INDEX_TO_HRP = {
"0'": "ms",
"1'": "cl",
}

assert set(INDEX_TO_LANGUAGE.values()) == set(LANGUAGES.keys())


Expand Down Expand Up @@ -83,6 +90,71 @@ def apply_85(derived_key: ExtendedKey, path: str) -> Dict[str, Union[bytes, str]
"entropy": trimmed_entropy,
"application": " ".join(words),
}
elif app == APPLICATIONS["codex32"]:
hrp, threshold, share_count, byte_length, id0, id1, id2, id3 = indexes[:8]
if hrp == "0'":
hrp = 'ms'
elif hrp == "1'":
hrp = 'cl'
else:
raise ValueError(f"Unsupported human-readable prefix: {hrp}.")
threshold = int(threshold.rstrip("'"))
if not threshold or not 9 > threshold > 2:
raise ValueError(f"Threshold '{threshold}' is not an allowed value (2 through 9, or 0).")
k = CHARSET.find(str(threshold))
share_count = int(share_count.rstrip("'"))
if not 1 <= share_count <= 31:
raise ValueError(f"Share count '{share_count}' is not an allowed value (1 through 31).")
if threshold == 0 and share_count != 1:
raise ValueError(f"Share count '{share_count}' is not an allowed value (for threshold=0, share_count must be 1).")
byte_length = int(byte_length.rstrip("'"))
if not 16 <= byte_length <= 64:
raise ValueError(f"Byte length '{byte_length}' is not an allowed value (16 through 64).")
payload_length = byte_length * 8 // 5
identifier = []
for id_char in id0, id1, id2, id3:
id_char = int(id_char.rstrip("'"))
if not 0 <= id_char <= 32:
raise ValueError(f"Identifer value '{id_char}' is not an allowed value (0 through 32).")
identifier.append(id_char)
drng = DRNG(entropy)
alphabetized_charset = 'sacdefghjk' # threshold above 9 is invalid
initial_codex32_data = []
id = [0] * 4 if 32 in identifier else identifier
for i in range(bool(threshold), min(threshold, share_count) + 1):
data = [k] + [id] + [CHARSET.find(alphabetized_charset[i])]
while len(data) < 6 + payload_length:
data.append(int.from_bytes(drng.read(1), "big") >> 3)
initial_codex32_data.append(data)
codex32_secret = ms32_recover(initial_codex32_data) if len(
initial_codex32_data) > 1 else initial_codex32_data[0]
if 32 in identifier:
bip32_fp = fingerprint(convertbits(codex32_secret[6:], 5, 8))
for data in initial_codex32_data:
for i in range(1,5): # relabel shares with the BIP32 fingerprint
data[i] = data[i] if identifier[i - 1] < 32 else bip32_fp[i - 1]
if threshold and share_count >= threshold:
strings = []
existing_share_indexes = [16]
for i in range(share_count):
fresh_share_index = 16
while fresh_share_index in existing_share_indexes:
fresh_share_index = int.from_bytes(drng.read(1), "big") >> 3
existing_share_indexes.append(fresh_share_index)
if fresh_share_index in initial_codex32_data[:][6]:
share_data = initial_codex32_data[
initial_codex32_data[:][6].index(fresh_share_index)]
else:
share_data = ms32_interpolate(initial_codex32_data, fresh_share_index)
strings.append(ms32_encode(hrp, share_data))
else:
strings = [ms32_encode(hrp, data) for data in initial_codex32_data]
assert validate_set(strings, len_must_match_k=False)

return {
"entropy": entropy,
"application": " ".join(strings),
}
Comment on lines +100 to +127
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this new proposed path and codex32 derivation address both yours and scgbckbone feedback:

Fewer derivation levels: {header}, {n_bytes}, {index}
Fewer parameters: {share_idx} and {num_shares} are dropped

Indices output are deterministic based on k. No derived shares, just the initial k

{header} is the first 8 characters of a codex32 string, it would be some serialization of {hrp}|{threshold}{identifier} and fits if converted from bech32 to an int.

I also want to feed the bip85 app {index} into the ident as it should be unique for different seeds

elif app == APPLICATIONS["wif"]:
trimmed_entropy = entropy[: 256 // 8]
prefix = b"\x80" if derived_key.get_network() == "mainnet" else b"\xef"
Expand Down
Loading
Loading