The list a recovery phrase's words are of moves from halkyon_keys into
halkyon_core, exported as bip39English, so that the server module can
take a waiting account's matching code from it (Halkyon Learn's ADR
0021, D33) without depending on halkyon_keys and libsodium. Its test
(2048 words, sorted, the published file's SHA-256, unique by their first
four letters) moves along; its MIT licence too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Other spaces become a plain space and the text is composed (NFC) before
Argon2id, so a password typed on another system, accents composed or
not, unlocks the same keys; empty passwords and control characters are
refused. The normalisation (unorm_dart) is pinned exactly, as it must
never change once an account exists. ADR 0017 records both decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
ADR 0017's step 1, on libsodium (sodium 4.1.0+1, whose build hook
compiles the libsodium 1.0.22 it ships: a C compiler and make wherever
it builds):
- Argon2id (64 MiB, 3 passes, kept with the account and never below
that) makes a master key, which libsodium's KDF splits into SRP's
secret (x) and the key-encryption key.
- The account's X25519 and Ed25519 key pairs, their private halves
wrapped with XChaCha20-Poly1305 under the KEK and under the recovery
key, bound to their public keys.
- A 24-word BIP-39 recovery phrase (the English list, MIT) and the
recovery key derived from it.
- Data keys sealed to the account with crypto_box_seal, bound to their
IDs; content in ADR 0002's envelope, encrypted with a data key and
signed with Ed25519 (detached), bound to a context the caller names.
- Secrets in libsodium's guarded memory where its API takes them.
- Tests: libsodium's Argon2id known answers, BIP-39's English vectors,
round trips and every refusal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>