Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 docs/developer/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ requires interactive input and output, preflights local Bitcoin Core before
entropy or recovery input, and initializes a user-selected wallet after every
share is confirmed. CLI creation does not accept Core Lightning profiles; CL
generation and sharing remain API-only.
Supplied Bitcoin seed bytes and existing Bitcoin secrets must form a valid
BIP32 root before a creation ceremony selects entropy or emits recovery cards.
Parsing a codex32 string remains a format check; Core Lightning has no BIP32
root requirement.
Without `--existing`, omitting the Bitcoin header creates an unshared master
seed. With `--existing` and no sharing threshold, a supplied codex32 secret is
emitted and confirmed unchanged, and the original validated artifact initializes
Expand Down
7 changes: 6 additions & 1 deletion docs/security/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,12 @@ multiplication. The original ceremony result, not re-entered text, remains the
source for automatic wallet setup.

Sharing an existing secret generates and confirms *k−1* random initial shares
before deriving the remaining shares. Recovery requires exactly the declared
before deriving the remaining shares. Supplied Bitcoin seed bytes and existing
Bitcoin secrets must form a valid BIP32 root. `CreationCeremony.from_secret`
checks this before selecting an identifier or drawing entropy; a parsed
`MasterSeed` alone establishes format validity, not this creation precondition.
Core Lightning secrets have no BIP32-root requirement.
Recovery requires exactly the declared
threshold of compatible shares with distinct indices. Derivation requires a
new share index not used by its inputs. Every output is reparsed before release.

Expand Down
7 changes: 3 additions & 4 deletions src/codex32/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,13 +295,12 @@ def from_secret(
"""Start a ceremony that shares an existing validated secret."""
if not isinstance(secret, (MasterSeed, CoreLightningSecret)):
raise TypeError("from_secret accepts only MasterSeed or CoreLightningSecret")
if isinstance(secret, MasterSeed) and not _valid_root(secret.seed_bytes):
raise CodexError("master seed does not form a valid BIP32 root")
Comment thread
BenWestgate marked this conversation as resolved.
threshold = _threshold(threshold, allow_zero=False)
random_identifier = identifier is None
identifier = _random_identifier() if random_identifier else _identifier(identifier)
while (threshold, identifier) == (
secret.header.threshold,
secret.header.identifier,
):
while (threshold, identifier) == (secret.header.threshold, secret.header.identifier):
if not random_identifier:
raise HeaderCollision("new share set must use a different set header")
identifier = _random_identifier()
Expand Down
17 changes: 17 additions & 0 deletions tests/test_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,23 @@ def test_supplied_seed_must_form_a_valid_bip32_root(monkeypatch: pytest.MonkeyPa
generate_master_seed(bytes(16), identifier="test")


def test_existing_bitcoin_secret_requires_a_valid_root_before_any_entropy(
monkeypatch: pytest.MonkeyPatch,
) -> None:
source = MasterSeed.from_seed(bytes(range(16)), identifier="test")
lightning = generate_core_lightning_secret(bytes(range(32)), identifier="test")
monkeypatch.setattr(generation_module, "_valid_root", lambda _seed: False)

def no_entropy(_length: int) -> bytes:
pytest.fail("invalid Bitcoin root reached entropy selection")

monkeypatch.setattr(generation_module.secrets, "token_bytes", no_entropy)
with pytest.raises(CodexError, match="master seed does not form a valid BIP32 root"):
CreationCeremony.from_secret(source, threshold=2, indices="ac")
# BIP32 root validity does not apply to Core Lightning's HSM secret.
CreationCeremony.from_secret(lightning, threshold=2, indices="ac", identifier="name")


def test_explicit_and_random_output_order_contracts() -> None:
source = generate_master_seed(bytes(range(16)), identifier="test")
_secret, shares = _complete(
Expand Down
Loading