Derive the vault key from the mnemonic's words however they are spaced (closes #135)
check / check (push) Failing after 4s
check / check (push) Failing after 4s
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
This commit was merged in pull request #160.
This commit is contained in:
+7
-2
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcutil/bech32"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
"sneak.berlin/go/secret/internal/bip39"
|
||||
"sneak.berlin/go/secret/pkg/bip85"
|
||||
)
|
||||
@@ -73,8 +74,12 @@ func IdentityFromEntropy(ent []byte) (*age.X25519Identity, error) {
|
||||
// DeriveEntropy derives 32 bytes of application-scoped entropy from the
|
||||
// supplied BIP-39 mnemonic and index n using BIP85.
|
||||
func DeriveEntropy(mnemonic string, n uint32) ([]byte, error) {
|
||||
// Convert mnemonic to seed
|
||||
seed := bip39.NewSeed(mnemonic, "")
|
||||
// BIP-39 makes the seed from the words joined by single spaces, in
|
||||
// Unicode NFKD. NewSeed hashes its input as given, so the mnemonic is
|
||||
// brought to that form first: the same words, however they were
|
||||
// typed, then give the same seed.
|
||||
sentence := norm.NFKD.String(strings.Join(strings.Fields(mnemonic), " "))
|
||||
seed := bip39.NewSeed(sentence, "")
|
||||
|
||||
// Create master key from seed
|
||||
masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
|
||||
|
||||
@@ -175,6 +175,34 @@ func TestDeterministicDerivation(t *testing.T) {
|
||||
t.Logf("Index 1: %s", id3.String())
|
||||
}
|
||||
|
||||
// TestDeriveIdentityIgnoresExtraWhitespace checks that the same words give
|
||||
// the same identity however they are spaced, as when a mnemonic is typed or
|
||||
// pasted with a double space or a trailing newline.
|
||||
func TestDeriveIdentityIgnoresExtraWhitespace(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want, err := DeriveIdentity(mnemonic, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("derive: %v", err)
|
||||
}
|
||||
|
||||
typedMnemonics := []string{
|
||||
strings.Replace(mnemonic, " ", " ", 1) + " ",
|
||||
"\t" + strings.ReplaceAll(mnemonic, " ", "\n") + "\n",
|
||||
}
|
||||
|
||||
for _, typed := range typedMnemonics {
|
||||
got, err := DeriveIdentity(typed, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("derive from %q: %v", typed, err)
|
||||
}
|
||||
|
||||
if got.String() != want.String() {
|
||||
t.Errorf("%q derives a different identity from %q", typed, mnemonic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeterministicXPRVDerivation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user