From 5089cfc1be87a984e87aa178e40a76ee13a617c1 Mon Sep 17 00:00:00 2001 From: positiveblue Date: Fri, 30 Oct 2020 10:10:41 +0100 Subject: [PATCH] aezeed: fix mnemonic word validation A user complained about getting a misleading error after a typo in the mnemonic. The word was `hear` and it passed the check even when it is not in the list of valid words. The reason is that we where checking if the word is in the variable `englishWordList` (which includes all the words) instead of checking if the variable is in the `defaultWordList` (which is basically `englishWoldList` split by spaces). That means that `hear` passed the check because `heart` appears in the list. Related issue [4733](https://github.com/lightningnetwork/lnd/issues/4733) --- aezeed/cipherseed.go | 8 ++++++-- aezeed/cipherseed_test.go | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/aezeed/cipherseed.go b/aezeed/cipherseed.go index 112a5a99..50ad8f2d 100644 --- a/aezeed/cipherseed.go +++ b/aezeed/cipherseed.go @@ -6,7 +6,6 @@ import ( "encoding/binary" "hash/crc32" "io" - "strings" "time" "github.com/Yawning/aez" @@ -506,8 +505,13 @@ func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte, error) // Before we attempt to map the mnemonic back to the original // ciphertext, we'll ensure that all the word are actually a part of // the current default word list. + wordDict := make(map[string]struct{}, len(defaultWordList)) + for _, word := range defaultWordList { + wordDict[word] = struct{}{} + } + for i, word := range m { - if !strings.Contains(englishWordList, word) { + if _, ok := wordDict[word]; !ok { emptySeed := [DecipheredCipherSeedSize]byte{} return emptySeed, ErrUnknownMnenomicWord{ Word: word, diff --git a/aezeed/cipherseed_test.go b/aezeed/cipherseed_test.go index 1d74e88c..cd8e4366 100644 --- a/aezeed/cipherseed_test.go +++ b/aezeed/cipherseed_test.go @@ -550,6 +550,22 @@ func TestDecipherUnknownMnenomicWord(t *testing.T) { t.Fatalf("wrong index detected: expected %v, got %v", randIndex, wordErr.Index) } + + // If the mnemonic includes a word that is not in the englishList + // it fails, even when it is a substring of a valid word + // Example: `heart` is in the list, `hear` is not + mnemonic[randIndex] = "hear" + + // If we attempt to map back to the original cipher seed now, then we + // should get ErrUnknownMnenomicWord. + _, err = mnemonic.ToCipherSeed(pass) + if err == nil { + t.Fatalf("expected ErrUnknownMnenomicWord error") + } + _, ok = err.(ErrUnknownMnenomicWord) + if !ok { + t.Fatalf("expected ErrUnknownMnenomicWord instead got %T", err) + } } // TestDecipherIncorrectMnemonic tests that if we obtain a cipherseed, but then @@ -582,7 +598,6 @@ func TestDecipherIncorrectMnemonic(t *testing.T) { if err != ErrIncorrectMnemonic { t.Fatalf("expected ErrIncorrectMnemonic error") } - } // TODO(roasbeef): add test failure checksum fail is modified, new error