What was wrong: the vault's long-term key was derived from the mnemonic text exactly as typed. init and vault create accept a mnemonic with a double space, a tab or a trailing newline, because the check splits the words on any whitespace, but the key came from the raw text. Such a vault did not open with the same words typed normally, and its key matched no other BIP39 tool.
What the fix does:agehd.DeriveEntropy joins the words with single spaces and puts them in Unicode NFKD, as BIP39 defines the seed's input, before hashing them. init, vault create, vault import, unlocker add and every unlock derive the key through it, so none of them changed. The new test in pkg/agehd derives an identity from the words with a double space and a trailing space, and from the words split by tabs and newlines, and requires the identity of the single-spaced words; it failed before the change.
Worth knowing:
golang.org/x/text moves from indirect to direct in go.mod; go.sum already had it.
NFKD changes nothing for the English word list, the only one secret accepts. It is there because BIP39 and the issue define the seed's input that way.
Judgement call: internal/bip39's NewSeed still hashes its input as given, as upstream does; the normalising is in agehd.
Model: opus-5-5
Fixes https://git.eeqj.de/sneak/secret/issues/135.
**What was wrong:** the vault's long-term key was derived from the mnemonic text exactly as typed. `init` and `vault create` accept a mnemonic with a double space, a tab or a trailing newline, because the check splits the words on any whitespace, but the key came from the raw text. Such a vault did not open with the same words typed normally, and its key matched no other BIP39 tool.
**What the fix does:** `agehd.DeriveEntropy` joins the words with single spaces and puts them in Unicode NFKD, as BIP39 defines the seed's input, before hashing them. `init`, `vault create`, `vault import`, `unlocker add` and every unlock derive the key through it, so none of them changed. The new test in `pkg/agehd` derives an identity from the words with a double space and a trailing space, and from the words split by tabs and newlines, and requires the identity of the single-spaced words; it failed before the change.
Worth knowing:
- `golang.org/x/text` moves from indirect to direct in `go.mod`; `go.sum` already had it.
- NFKD changes nothing for the English word list, the only one `secret` accepts. It is there because BIP39 and the issue define the seed's input that way.
Judgement call: `internal/bip39`'s `NewSeed` still hashes its input as given, as upstream does; the normalising is in `agehd`.
Model: opus-5-5
The long-term key was derived from the mnemonic text exactly as typed,
so a double space, a tab or a trailing newline gave a different key,
and a vault made that way did not open with the same words typed
normally. agehd.DeriveEntropy, which init, vault create, vault import,
unlocker add and every unlock go through, now joins the words with
single spaces and puts them in Unicode NFKD, as BIP39 defines the
seed's input, before hashing them. golang.org/x/text, already an
indirect dependency, becomes a direct one for the NFKD step.
Judgement call: internal/bip39's NewSeed keeps hashing its input as
given, as upstream does; the normalising is in agehd.
Model: opus-5-5
Passes review: a mnemonic typed with extra spaces, tabs or line breaks now gives the same vault key as its single-spaced words everywhere the key is derived, as #135 defines.
Model: opus-5-5
Passes review: a mnemonic typed with extra spaces, tabs or line breaks now gives the same vault key as its single-spaced words everywhere the key is derived, as https://git.eeqj.de/sneak/secret/issues/135 defines.
Model: opus-5-5
clawbot
merged commit fd6c5df1a8 into next2026-10-13 01:09:37 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes #135.
What was wrong: the vault's long-term key was derived from the mnemonic text exactly as typed.
initandvault createaccept a mnemonic with a double space, a tab or a trailing newline, because the check splits the words on any whitespace, but the key came from the raw text. Such a vault did not open with the same words typed normally, and its key matched no other BIP39 tool.What the fix does:
agehd.DeriveEntropyjoins the words with single spaces and puts them in Unicode NFKD, as BIP39 defines the seed's input, before hashing them.init,vault create,vault import,unlocker addand every unlock derive the key through it, so none of them changed. The new test inpkg/agehdderives an identity from the words with a double space and a trailing space, and from the words split by tabs and newlines, and requires the identity of the single-spaced words; it failed before the change.Worth knowing:
golang.org/x/textmoves from indirect to direct ingo.mod;go.sumalready had it.secretaccepts. It is there because BIP39 and the issue define the seed's input that way.Judgement call:
internal/bip39'sNewSeedstill hashes its input as given, as upstream does; the normalising is inagehd.Model: opus-5-5
Passes review: a mnemonic typed with extra spaces, tabs or line breaks now gives the same vault key as its single-spaced words everywhere the key is derived, as #135 defines.
Model: opus-5-5