Merge pull request #6524 from guggero/aezeed-bump

aezeed+keychain: bump internal version of seed to 1
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2022-05-12 16:35:49 -07:00
commit 7106ea59db
8 files changed
+272 -263

No files matched your search

+96 -49
View File
@@ -62,7 +62,7 @@ const (
// be seen as the size of the equivalent MAC. // be seen as the size of the equivalent MAC.
CipherTextExpansion = 4 CipherTextExpansion = 4
// EntropySize is the number of bytes of entropy we'll use the generate // EntropySize is the number of bytes of entropy we'll use to generate
// the seed. // the seed.
EntropySize = 16 EntropySize = 16
@@ -70,15 +70,15 @@ const (
// will result in. // will result in.
NumMnemonicWords = 24 NumMnemonicWords = 24
// saltSize is the size of the salt we'll generate to use with scrypt // SaltSize is the size of the salt we'll generate to use with scrypt
// to generate a key for use within aez from the user's passphrase. The // to generate a key for use within aez from the user's passphrase. The
// role of the salt is to make the creation of rainbow tables // role of the salt is to make the creation of rainbow tables
// infeasible. // infeasible.
saltSize = 5 SaltSize = 5
// adSize is the size of the encoded associated data that will be // adSize is the size of the encoded associated data that will be
// passed into aez when enciphering and deciphering the seed. The AD // passed into aez when enciphering and deciphering the seed. The AD
// itself (associated data) is just the CipherSeedVersion and salt. // itself (associated data) is just the cipher seed version and salt.
adSize = 6 adSize = 6
// checkSumSize is the size of the checksum applied to the final // checkSumSize is the size of the checksum applied to the final
@@ -93,9 +93,9 @@ const (
// We encode our mnemonic using 24 words, so 264 bits (33 bytes). // We encode our mnemonic using 24 words, so 264 bits (33 bytes).
BitsPerWord = 11 BitsPerWord = 11
// saltOffset is the index within an enciphered cipherseed that marks // saltOffset is the index within an enciphered cipher seed that marks
// the start of the salt. // the start of the salt.
saltOffset = EncipheredCipherSeedSize - checkSumSize - saltSize saltOffset = EncipheredCipherSeedSize - checkSumSize - SaltSize
// checkSumSize is the index within an enciphered cipher seed that // checkSumSize is the index within an enciphered cipher seed that
// marks the start of the checksum. // marks the start of the checksum.
@@ -103,8 +103,8 @@ const (
) )
var ( var (
// Below at the default scrypt parameters that are tied to // Below at the default scrypt parameters that are tied to cipher seed
// CipherSeedVersion zero. // version zero.
scryptN = 32768 scryptN = 32768
scryptR = 8 scryptR = 8
scryptP = 1 scryptP = 1
@@ -128,8 +128,35 @@ var (
BitcoinGenesisDate = time.Unix(1231006505, 0) BitcoinGenesisDate = time.Unix(1231006505, 0)
) )
// SeedOptions is a type that holds options that configure the generation of a
// new cipher seed.
type SeedOptions struct {
// randomnessSource is the source of randomness that is used to generate
// the salt that is used for encrypting the seed.
randomnessSource io.Reader
}
// DefaultOptions returns the default seed options.
func DefaultOptions() *SeedOptions {
return &SeedOptions{
randomnessSource: rand.Reader,
}
}
// SeedOptionModifier is a function signature for modifying the default
// SeedOptions.
type SeedOptionModifier func(*SeedOptions)
// WithRandomnessSource returns an option modifier that replaces the default
// randomness source with the given reader.
func WithRandomnessSource(src io.Reader) SeedOptionModifier {
return func(opts *SeedOptions) {
opts.randomnessSource = src
}
}
// CipherSeed is a fully decoded instance of the aezeed scheme. At a high // CipherSeed is a fully decoded instance of the aezeed scheme. At a high
// level, the encoded cipherseed is the enciphering of: a version byte, a set // level, the encoded cipher seed is the enciphering of: a version byte, a set
// of bytes for a timestamp, the entropy which will be used to directly // of bytes for a timestamp, the entropy which will be used to directly
// construct the HD seed, and finally a checksum over the rest. This scheme was // construct the HD seed, and finally a checksum over the rest. This scheme was
// created as the widely used schemes in the space lack two critical traits: a // created as the widely used schemes in the space lack two critical traits: a
@@ -151,7 +178,7 @@ var (
// users can encrypt the raw "plaintext" seed under distinct passwords to // users can encrypt the raw "plaintext" seed under distinct passwords to
// produce unique mnemonic phrases. // produce unique mnemonic phrases.
type CipherSeed struct { type CipherSeed struct {
// InternalVersion is the version of the plaintext cipherseed. This is // InternalVersion is the version of the plaintext cipher seed. This is
// to be used by wallets to determine if the seed version is compatible // to be used by wallets to determine if the seed version is compatible
// with the derivation schemes they know. // with the derivation schemes they know.
InternalVersion uint8 InternalVersion uint8
@@ -169,22 +196,27 @@ type CipherSeed struct {
// salt is the salt that was used to generate the key from the user's // salt is the salt that was used to generate the key from the user's
// specified passphrase. // specified passphrase.
salt [saltSize]byte salt [SaltSize]byte
} }
// New generates a new CipherSeed instance from an optional source of entropy. // New generates a new CipherSeed instance from an optional source of entropy.
// If the entropy isn't provided, then a set of random bytes will be used in // If the entropy isn't provided, then a set of random bytes will be used in
// place. The final argument should be the time at which the seed was created. // place. The final fixed argument should be the time at which the seed was
// created, followed by optional seed option modifiers.
func New(internalVersion uint8, entropy *[EntropySize]byte, func New(internalVersion uint8, entropy *[EntropySize]byte,
now time.Time) (*CipherSeed, error) { now time.Time, modifiers ...SeedOptionModifier) (*CipherSeed, error) {
// TODO(roasbeef): pass randomness source? to make fully determinsitc? opts := DefaultOptions()
for _, modifier := range modifiers {
modifier(opts)
}
// If a set of entropy wasn't provided, then we'll read a set of bytes // If a set of entropy wasn't provided, then we'll read a set of bytes
// from the CSPRNG of our operating platform. // from the randomness source provided (which by default is the system's
// CSPRNG).
var seed [EntropySize]byte var seed [EntropySize]byte
if entropy == nil { if entropy == nil {
if _, err := rand.Read(seed[:]); err != nil { if _, err := opts.randomnessSource.Read(seed[:]); err != nil {
return nil, err return nil, err
} }
} else { } else {
@@ -205,7 +237,7 @@ func New(internalVersion uint8, entropy *[EntropySize]byte,
// Next, we'll read a random salt that will be used with scrypt to // Next, we'll read a random salt that will be used with scrypt to
// eventually derive our key. // eventually derive our key.
if _, err := rand.Read(c.salt[:]); err != nil { if _, err := opts.randomnessSource.Read(c.salt[:]); err != nil {
return nil, err return nil, err
} }
@@ -252,9 +284,9 @@ func (c *CipherSeed) decode(r io.Reader) error {
// encodeAD returns the fully encoded associated data for use when performing // encodeAD returns the fully encoded associated data for use when performing
// our current enciphering operation. The AD is: version || salt. // our current enciphering operation. The AD is: version || salt.
func encodeAD(version uint8, salt [saltSize]byte) [adSize]byte { func encodeAD(version uint8, salt [SaltSize]byte) [adSize]byte {
var ad [adSize]byte var ad [adSize]byte
ad[0] = byte(version) ad[0] = version
copy(ad[1:], salt[:]) copy(ad[1:], salt[:])
return ad return ad
@@ -272,11 +304,13 @@ func extractAD(encipheredSeed [EncipheredCipherSeedSize]byte) [adSize]byte {
return ad return ad
} }
// encipher takes a fully populated cipherseed instance, and enciphers the // encipher takes a fully populated cipher seed instance, and enciphers the
// encoded seed, then appends a randomly generated seed used to stretch the // encoded seed, then appends a randomly generated seed used to stretch the
// passphrase out into an appropriate key, then computes a checksum over the // passphrase out into an appropriate key, then computes a checksum over the
// preceding. // preceding.
func (c *CipherSeed) encipher(pass []byte) ([EncipheredCipherSeedSize]byte, error) { func (c *CipherSeed) encipher(pass []byte) ([EncipheredCipherSeedSize]byte,
error) {
var cipherSeedBytes [EncipheredCipherSeedSize]byte var cipherSeedBytes [EncipheredCipherSeedSize]byte
// If the passphrase wasn't provided, then we'll use the string // If the passphrase wasn't provided, then we'll use the string
@@ -295,7 +329,7 @@ func (c *CipherSeed) encipher(pass []byte) ([EncipheredCipherSeedSize]byte, erro
return cipherSeedBytes, err return cipherSeedBytes, err
} }
// Next, we'll encode the serialized plaintext cipherseed into a buffer // Next, we'll encode the serialized plaintext cipher seed into a buffer
// that we'll use for encryption. // that we'll use for encryption.
var seedBytes bytes.Buffer var seedBytes bytes.Buffer
if err := c.encode(&seedBytes); err != nil { if err := c.encode(&seedBytes); err != nil {
@@ -316,7 +350,7 @@ func (c *CipherSeed) encipher(pass []byte) ([EncipheredCipherSeedSize]byte, erro
// Finally, we'll pack the {version || ciphertext || salt || checksum} // Finally, we'll pack the {version || ciphertext || salt || checksum}
// seed into a byte slice for encoding as a mnemonic. // seed into a byte slice for encoding as a mnemonic.
cipherSeedBytes[0] = byte(CipherSeedVersion) cipherSeedBytes[0] = CipherSeedVersion
copy(cipherSeedBytes[1:saltOffset], cipherText) copy(cipherSeedBytes[1:saltOffset], cipherText)
copy(cipherSeedBytes[saltOffset:], c.salt[:]) copy(cipherSeedBytes[saltOffset:], c.salt[:])
@@ -335,7 +369,9 @@ func (c *CipherSeed) encipher(pass []byte) ([EncipheredCipherSeedSize]byte, erro
// cipherTextToMnemonic converts the aez ciphertext appended with the salt to a // cipherTextToMnemonic converts the aez ciphertext appended with the salt to a
// 24-word mnemonic pass phrase. // 24-word mnemonic pass phrase.
func cipherTextToMnemonic(cipherText [EncipheredCipherSeedSize]byte) (Mnemonic, error) { func cipherTextToMnemonic(cipherText [EncipheredCipherSeedSize]byte) (Mnemonic,
error) {
var words [NumMnemonicWords]string var words [NumMnemonicWords]string
// First, we'll convert the ciphertext itself into a bitstream for easy // First, we'll convert the ciphertext itself into a bitstream for easy
@@ -356,7 +392,7 @@ func cipherTextToMnemonic(cipherText [EncipheredCipherSeedSize]byte) (Mnemonic,
return words, nil return words, nil
} }
// ToMnemonic maps the final enciphered cipher seed to a human readable 24-word // ToMnemonic maps the final enciphered cipher seed to a human-readable 24-word
// mnemonic phrase. The password is optional, as if it isn't specified aezeed // mnemonic phrase. The password is optional, as if it isn't specified aezeed
// will be used in its place. // will be used in its place.
func (c *CipherSeed) ToMnemonic(pass []byte) (Mnemonic, error) { func (c *CipherSeed) ToMnemonic(pass []byte) (Mnemonic, error) {
@@ -374,7 +410,9 @@ func (c *CipherSeed) ToMnemonic(pass []byte) (Mnemonic, error) {
// Encipher maps the cipher seed to an aez ciphertext using an optional // Encipher maps the cipher seed to an aez ciphertext using an optional
// passphrase. // passphrase.
func (c *CipherSeed) Encipher(pass []byte) ([EncipheredCipherSeedSize]byte, error) { func (c *CipherSeed) Encipher(pass []byte) ([EncipheredCipherSeedSize]byte,
error) {
return c.encipher(pass) return c.encipher(pass)
} }
@@ -385,7 +423,7 @@ func (c *CipherSeed) BirthdayTime() time.Time {
return BitcoinGenesisDate.Add(offset) return BitcoinGenesisDate.Add(offset)
} }
// Mnemonic is a 24-word passphrase as of CipherSeedVersion zero. This // Mnemonic is a 24-word passphrase as of cipher seed version zero. This
// passphrase encodes an encrypted seed triple (version, birthday, entropy). // passphrase encodes an encrypted seed triple (version, birthday, entropy).
// Additionally, we also encode the salt used with scrypt to derive the key // Additionally, we also encode the salt used with scrypt to derive the key
// that the cipher text is encrypted with, and the version which tells us how // that the cipher text is encrypted with, and the version which tells us how
@@ -426,14 +464,16 @@ func mnemonicToCipherText(mnemonic *Mnemonic) [EncipheredCipherSeedSize]byte {
func (m *Mnemonic) ToCipherSeed(pass []byte) (*CipherSeed, error) { func (m *Mnemonic) ToCipherSeed(pass []byte) (*CipherSeed, error) {
// First, we'll attempt to decipher the mnemonic by mapping back into // First, we'll attempt to decipher the mnemonic by mapping back into
// our byte slice and applying our deciphering scheme. // our byte slice and applying our deciphering scheme.
plainSeed, err := m.Decipher(pass) plainSeed, salt, err := m.Decipher(pass)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// If decryption was successful, then we'll decode into a fresh // If decryption was successful, then we'll decode into a fresh
// CipherSeed struct. // CipherSeed struct.
var c CipherSeed c := CipherSeed{
salt: salt,
}
if err := c.decode(bytes.NewReader(plainSeed[:])); err != nil { if err := c.decode(bytes.NewReader(plainSeed[:])); err != nil {
return nil, err return nil, err
} }
@@ -445,36 +485,41 @@ func (m *Mnemonic) ToCipherSeed(pass []byte) (*CipherSeed, error) {
// using the passed passphrase. This function is the opposite of // using the passed passphrase. This function is the opposite of
// the encipher method. // the encipher method.
func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte, func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte,
pass []byte) ([DecipheredCipherSeedSize]byte, error) { pass []byte) ([DecipheredCipherSeedSize]byte, [SaltSize]byte, error) {
var plainSeed [DecipheredCipherSeedSize]byte var (
plainSeed [DecipheredCipherSeedSize]byte
salt [SaltSize]byte
)
// Before we do anything, we'll ensure that the version is one that we // Before we do anything, we'll ensure that the version is one that we
// understand. Otherwise, we won't be able to decrypt, or even parse // understand. Otherwise, we won't be able to decrypt, or even parse
// the cipher seed. // the cipher seed.
if uint8(cipherSeedBytes[0]) != CipherSeedVersion { if cipherSeedBytes[0] != CipherSeedVersion {
return plainSeed, ErrIncorrectVersion return plainSeed, salt, ErrIncorrectVersion
} }
// Next, we'll slice off the salt from the pass cipher seed, then // Next, we'll slice off the salt from the pass cipher seed, then
// snip off the end of the cipher seed, ignoring the version, and // snip off the end of the cipher seed, ignoring the version, and
// finally the checksum. // finally the checksum.
salt := cipherSeedBytes[saltOffset : saltOffset+saltSize] copy(salt[:], cipherSeedBytes[saltOffset:saltOffset+SaltSize])
cipherSeed := cipherSeedBytes[1:saltOffset] cipherSeed := cipherSeedBytes[1:saltOffset]
checksum := cipherSeedBytes[checkSumOffset:] checksum := cipherSeedBytes[checkSumOffset:]
// Before we perform any crypto operations, we'll re-create and verify // Before we perform any crypto operations, we'll re-create and verify
// the checksum to ensure that the user input the proper set of words. // the checksum to ensure that the user input the proper set of words.
freshChecksum := crc32.Checksum(cipherSeedBytes[:checkSumOffset], crcTable) freshChecksum := crc32.Checksum(
cipherSeedBytes[:checkSumOffset], crcTable,
)
if freshChecksum != binary.BigEndian.Uint32(checksum) { if freshChecksum != binary.BigEndian.Uint32(checksum) {
return plainSeed, ErrIncorrectMnemonic return plainSeed, salt, ErrIncorrectMnemonic
} }
// With the salt separated from the cipher text, we'll now obtain the // With the salt separated from the cipher text, we'll now obtain the
// key used for encryption. // key used for encryption.
key, err := scrypt.Key(pass, salt, scryptN, scryptR, scryptP, keyLen) key, err := scrypt.Key(pass, salt[:], scryptN, scryptR, scryptP, keyLen)
if err != nil { if err != nil {
return plainSeed, err return plainSeed, salt, err
} }
// We'll also extract the AD that will be required to properly pass the // We'll also extract the AD that will be required to properly pass the
@@ -488,18 +533,19 @@ func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte,
key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil, key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil,
) )
if !ok { if !ok {
return plainSeed, ErrInvalidPass return plainSeed, salt, ErrInvalidPass
} }
copy(plainSeed[:], plainSeedBytes) copy(plainSeed[:], plainSeedBytes)
return plainSeed, nil return plainSeed, salt, nil
} }
// Decipher attempts to decipher the encoded mnemonic by first mapping to the // Decipher attempts to decipher the encoded mnemonic by first mapping to the
// original ciphertext, then applying our deciphering scheme. ErrInvalidPass // original ciphertext, then applying our deciphering scheme. ErrInvalidPass
// will be returned if the passphrase is incorrect. // will be returned if the passphrase is incorrect.
func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte, error) { func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte,
[SaltSize]byte, error) {
// Before we attempt to map the mnemonic back to the original // Before we attempt to map the mnemonic back to the original
// ciphertext, we'll ensure that all the word are actually a part of // ciphertext, we'll ensure that all the word are actually a part of
@@ -512,10 +558,11 @@ func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte, error)
for i, word := range m { for i, word := range m {
if _, ok := wordDict[word]; !ok { if _, ok := wordDict[word]; !ok {
emptySeed := [DecipheredCipherSeedSize]byte{} emptySeed := [DecipheredCipherSeedSize]byte{}
return emptySeed, ErrUnknownMnenomicWord{ return emptySeed, [SaltSize]byte{},
Word: word, ErrUnknownMnemonicWord{
Index: uint8(i), Word: word,
} Index: uint8(i),
}
} }
} }
@@ -537,20 +584,20 @@ func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte, error)
} }
// ChangePass takes an existing mnemonic, and passphrase for said mnemonic and // ChangePass takes an existing mnemonic, and passphrase for said mnemonic and
// re-enciphers the plaintext cipher seed into a brand new mnemonic. This can // re-enciphers the plaintext cipher seed into a brand-new mnemonic. This can
// be used to allow users to re-encrypt the same seed with multiple pass // be used to allow users to re-encrypt the same seed with multiple pass
// phrases, or just change the passphrase on an existing seed. // phrases, or just change the passphrase on an existing seed.
func (m *Mnemonic) ChangePass(oldPass, newPass []byte) (Mnemonic, error) { func (m *Mnemonic) ChangePass(oldPass, newPass []byte) (Mnemonic, error) {
var newmnemonic Mnemonic var newMnemonic Mnemonic
// First, we'll try to decrypt the current mnemonic using the existing // First, we'll try to decrypt the current mnemonic using the existing
// passphrase. If this fails, then we can't proceed any further. // passphrase. If this fails, then we can't proceed any further.
cipherSeed, err := m.ToCipherSeed(oldPass) cipherSeed, err := m.ToCipherSeed(oldPass)
if err != nil { if err != nil {
return newmnemonic, err return newMnemonic, err
} }
// If the deciperhing was successful, then we'll now re-encipher using // If the deciphering was successful, then we'll now re-encipher using
// the new user provided passphrase. // the new user provided passphrase.
return cipherSeed.ToMnemonic(newPass) return cipherSeed.ToMnemonic(newPass)
} }
+141 -199
View File
@@ -6,6 +6,8 @@ import (
"testing" "testing"
"testing/quick" "testing/quick"
"time" "time"
"github.com/stretchr/testify/require"
) )
// TestVector defines the values that are used to create a fully initialized // TestVector defines the values that are used to create a fully initialized
@@ -14,7 +16,7 @@ type TestVector struct {
version uint8 version uint8
time time.Time time time.Time
entropy [EntropySize]byte entropy [EntropySize]byte
salt [saltSize]byte salt [SaltSize]byte
password []byte password []byte
expectedMnemonic [NumMnemonicWords]string expectedMnemonic [NumMnemonicWords]string
expectedBirthday uint16 expectedBirthday uint16
@@ -27,56 +29,48 @@ var (
0x0d, 0xe7, 0x95, 0xe4, 0x0d, 0xe7, 0x95, 0xe4,
0x1e, 0x0b, 0x4c, 0xfd, 0x1e, 0x0b, 0x4c, 0xfd,
} }
testSalt = [saltSize]byte{ testSalt = [SaltSize]byte{
0x73, 0x61, 0x6c, 0x74, 0x31, // equal to "salt1" 0x73, 0x61, 0x6c, 0x74, 0x31, // equal to "salt1"
} }
version0TestVectors = []TestVector{ version0TestVectors = []TestVector{{
{ version: 0,
version: 0, time: BitcoinGenesisDate,
time: BitcoinGenesisDate, entropy: testEntropy,
entropy: testEntropy, salt: testSalt,
salt: testSalt, password: []byte{},
password: []byte{}, expectedMnemonic: [NumMnemonicWords]string{
expectedMnemonic: [NumMnemonicWords]string{ "ability", "liquid", "travel", "stem", "barely", "drastic",
"ability", "liquid", "travel", "stem", "barely", "drastic", "pact", "cupboard", "apple", "thrive", "morning", "oak",
"pact", "cupboard", "apple", "thrive", "morning", "oak", "feature", "tissue", "couch", "old", "math", "inform",
"feature", "tissue", "couch", "old", "math", "inform", "success", "suggest", "drink", "motion", "know", "royal",
"success", "suggest", "drink", "motion", "know", "royal",
},
expectedBirthday: 0,
}, },
{ expectedBirthday: 0,
version: 0, }, {
time: time.Unix(1521799345, 0), // 03/23/2018 @ 10:02am (UTC) version: 0,
entropy: testEntropy, time: time.Unix(1521799345, 0), // 03/23/2018 @ 10:02am (UTC)
salt: testSalt, entropy: testEntropy,
password: []byte("!very_safe_55345_password*"), salt: testSalt,
expectedMnemonic: [NumMnemonicWords]string{ password: []byte("!very_safe_55345_password*"),
"able", "tree", "stool", "crush", "transfer", "cloud", expectedMnemonic: [NumMnemonicWords]string{
"cross", "three", "profit", "outside", "hen", "citizen", "able", "tree", "stool", "crush", "transfer", "cloud",
"plate", "ride", "require", "leg", "siren", "drum", "cross", "three", "profit", "outside", "hen", "citizen",
"success", "suggest", "drink", "require", "fiscal", "upgrade", "plate", "ride", "require", "leg", "siren", "drum",
}, "success", "suggest", "drink", "require", "fiscal", "upgrade",
expectedBirthday: 3365,
}, },
} expectedBirthday: 3365,
}}
) )
func assertCipherSeedEqual(t *testing.T, cipherSeed *CipherSeed, func assertCipherSeedEqual(t *testing.T, cipherSeed *CipherSeed,
cipherSeed2 *CipherSeed) { cipherSeed2 *CipherSeed) {
if cipherSeed.InternalVersion != cipherSeed2.InternalVersion { require.Equal(
t.Fatalf("mismatched versions: expected %v, got %v", t, cipherSeed.InternalVersion, cipherSeed2.InternalVersion,
cipherSeed.InternalVersion, cipherSeed2.InternalVersion) "internal version",
} )
if cipherSeed.Birthday != cipherSeed2.Birthday { require.Equal(t, cipherSeed.Birthday, cipherSeed2.Birthday, "birthday")
t.Fatalf("mismatched birthday: expected %v, got %v", require.Equal(t, cipherSeed.Entropy, cipherSeed2.Entropy, "entropy")
cipherSeed.Birthday, cipherSeed2.Birthday) require.Equal(t, cipherSeed.salt, cipherSeed2.salt, "salt")
}
if cipherSeed.Entropy != cipherSeed2.Entropy {
t.Fatalf("mismatched versions: expected %x, got %x",
cipherSeed.Entropy[:], cipherSeed2.Entropy[:])
}
} }
// TestAezeedVersion0TestVectors tests some fixed test vector values against // TestAezeedVersion0TestVectors tests some fixed test vector values against
@@ -84,41 +78,60 @@ func assertCipherSeedEqual(t *testing.T, cipherSeed *CipherSeed,
func TestAezeedVersion0TestVectors(t *testing.T) { func TestAezeedVersion0TestVectors(t *testing.T) {
t.Parallel() t.Parallel()
// To minimize the number of tests that need to be run, // To minimize the number of tests that need to be run, go through all
// go through all test vectors in the same test and also check // test vectors in the same test and also check the birthday calculation
// the birthday calculation while we're at it. // while we're at it.
for _, v := range version0TestVectors { for _, v := range version0TestVectors {
// First, we create new cipher seed with the given values // First, we create new cipher seed with the given values
// from the test vector. // from the test vector.
cipherSeed, err := New(v.version, &v.entropy, v.time) cipherSeed, err := New(v.version, &v.entropy, v.time)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create seed: %v", err)
}
// Then we need to set the salt to the pre-defined value, otherwise // Then we need to set the salt to the pre-defined value,
// we'll end up with randomness in our mnemonics. // otherwise we'll end up with randomness in our mnemonics.
cipherSeed.salt = testSalt cipherSeed.salt = v.salt
// Now that the seed has been created, we'll attempt to convert it to a // Now that the seed has been created, we'll attempt to convert
// valid mnemonic. // it to a valid mnemonic.
mnemonic, err := cipherSeed.ToMnemonic(v.password) mnemonic, err := cipherSeed.ToMnemonic(v.password)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create mnemonic: %v", err)
}
// Finally we compare the generated mnemonic and birthday to the // Finally we compare the generated mnemonic and birthday to the
// expected value. // expected value.
if mnemonic != v.expectedMnemonic { require.Equal(t, v.expectedMnemonic[:], mnemonic[:])
t.Fatalf("mismatched mnemonic: expected %s, got %s", require.Equal(t, v.expectedBirthday, cipherSeed.Birthday)
v.expectedMnemonic, mnemonic)
}
if cipherSeed.Birthday != v.expectedBirthday {
t.Fatalf("mismatched birthday: expected %v, got %v",
v.expectedBirthday, cipherSeed.Birthday)
}
} }
} }
// TestWithRandomnessSource tests that seed generation is fully deterministic
// when a custom static randomness source is provided.
func TestWithRandomnessSource(t *testing.T) {
sourceData := append([]byte{}, testEntropy[:]...)
sourceData = append(sourceData, testSalt[:]...)
src := bytes.NewReader(sourceData)
// First, we create new cipher seed with the given values from the test
// vector but with no entropy.
v := version0TestVectors[0]
cipherSeed, err := New(
v.version, nil, v.time, WithRandomnessSource(src),
)
require.NoError(t, err)
// The salt should be set to our test salt.
require.Equal(t, testSalt, cipherSeed.salt)
// Now that the seed has been created, we'll attempt to convert it to a
// valid mnemonic.
mnemonic, err := cipherSeed.ToMnemonic(v.password)
require.NoError(t, err)
// Finally, we compare the generated mnemonic and birthday to the
// expected value.
require.Equal(t, v.expectedMnemonic[:], mnemonic[:])
require.Equal(t, v.expectedBirthday, cipherSeed.Birthday)
}
// TestEmptyPassphraseDerivation tests that the aezeed scheme is able to derive // TestEmptyPassphraseDerivation tests that the aezeed scheme is able to derive
// a proper mnemonic, and decipher that mnemonic when the user uses an empty // a proper mnemonic, and decipher that mnemonic when the user uses an empty
// passphrase. // passphrase.
@@ -131,23 +144,17 @@ func TestEmptyPassphraseDerivation(t *testing.T) {
// We'll now create a new cipher seed with an internal version of zero // We'll now create a new cipher seed with an internal version of zero
// to simulate a wallet that just adopted the scheme. // to simulate a wallet that just adopted the scheme.
cipherSeed, err := New(0, &testEntropy, time.Now()) cipherSeed, err := New(0, &testEntropy, time.Now())
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create seed: %v", err)
}
// Now that the seed has been created, we'll attempt to convert it to a // Now that the seed has been created, we'll attempt to convert it to a
// valid mnemonic. // valid mnemonic.
mnemonic, err := cipherSeed.ToMnemonic(pass) mnemonic, err := cipherSeed.ToMnemonic(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create mnemonic: %v", err)
}
// Next, we'll try to decrypt the mnemonic with the passphrase that we // Next, we'll try to decrypt the mnemonic with the passphrase that we
// used. // used.
cipherSeed2, err := mnemonic.ToCipherSeed(pass) cipherSeed2, err := mnemonic.ToCipherSeed(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to decrypt mnemonic: %v", err)
}
// Finally, we'll ensure that the uncovered cipher seed matches // Finally, we'll ensure that the uncovered cipher seed matches
// precisely. // precisely.
@@ -165,23 +172,17 @@ func TestManualEntropyGeneration(t *testing.T) {
// We'll now create a new cipher seed with an internal version of zero // We'll now create a new cipher seed with an internal version of zero
// to simulate a wallet that just adopted the scheme. // to simulate a wallet that just adopted the scheme.
cipherSeed, err := New(0, nil, time.Now()) cipherSeed, err := New(0, nil, time.Now())
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create seed: %v", err)
}
// Now that the seed has been created, we'll attempt to convert it to a // Now that the seed has been created, we'll attempt to convert it to a
// valid mnemonic. // valid mnemonic.
mnemonic, err := cipherSeed.ToMnemonic(pass) mnemonic, err := cipherSeed.ToMnemonic(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create mnemonic: %v", err)
}
// Next, we'll try to decrypt the mnemonic with the passphrase that we // Next, we'll try to decrypt the mnemonic with the passphrase that we
// used. // used.
cipherSeed2, err := mnemonic.ToCipherSeed(pass) cipherSeed2, err := mnemonic.ToCipherSeed(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to decrypt mnemonic: %v", err)
}
// Finally, we'll ensure that the uncovered cipher seed matches // Finally, we'll ensure that the uncovered cipher seed matches
// precisely. // precisely.
@@ -189,7 +190,7 @@ func TestManualEntropyGeneration(t *testing.T) {
} }
// TestInvalidPassphraseRejection tests if a caller attempts to use the // TestInvalidPassphraseRejection tests if a caller attempts to use the
// incorrect passprhase for an enciphered seed, then the proper error is // incorrect passphrase for an enciphered seed, then the proper error is
// returned. // returned.
func TestInvalidPassphraseRejection(t *testing.T) { func TestInvalidPassphraseRejection(t *testing.T) {
t.Parallel() t.Parallel()
@@ -197,23 +198,18 @@ func TestInvalidPassphraseRejection(t *testing.T) {
// First, we'll generate a new cipher seed with a test passphrase. // First, we'll generate a new cipher seed with a test passphrase.
pass := []byte("test") pass := []byte("test")
cipherSeed, err := New(0, &testEntropy, time.Now()) cipherSeed, err := New(0, &testEntropy, time.Now())
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create seed: %v", err)
}
// Now that we have our cipher seed, we'll encipher it and request a // Now that we have our cipher seed, we'll encipher it and request a
// mnemonic that we can use to recover later. // mnemonic that we can use to recover later.
mnemonic, err := cipherSeed.ToMnemonic(pass) mnemonic, err := cipherSeed.ToMnemonic(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create mnemonic: %v", err)
}
// If we try to decipher with the wrong passphrase, we should get the // If we try to decipher with the wrong passphrase, we should get the
// proper error. // proper error.
wrongPass := []byte("kek") wrongPass := []byte("kek")
if _, err := mnemonic.ToCipherSeed(wrongPass); err != ErrInvalidPass { _, err = mnemonic.ToCipherSeed(wrongPass)
t.Fatalf("expected ErrInvalidPass, instead got %v", err) require.Equal(t, ErrInvalidPass, err)
}
} }
// TestRawEncipherDecipher tests that callers are able to use the raw methods // TestRawEncipherDecipher tests that callers are able to use the raw methods
@@ -224,36 +220,29 @@ func TestRawEncipherDecipher(t *testing.T) {
// First, we'll generate a new cipher seed with a test passphrase. // First, we'll generate a new cipher seed with a test passphrase.
pass := []byte("test") pass := []byte("test")
cipherSeed, err := New(0, &testEntropy, time.Now()) cipherSeed, err := New(0, &testEntropy, time.Now())
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create seed: %v", err)
}
// With the cipherseed obtained, we'll now use the raw encipher method // With the cipher seed obtained, we'll now use the raw encipher method
// to obtain our final cipher text. // to obtain our final cipher text.
cipherText, err := cipherSeed.Encipher(pass) cipherText, err := cipherSeed.Encipher(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to encipher seed: %v", err)
}
mnemonic, err := cipherTextToMnemonic(cipherText) mnemonic, err := cipherTextToMnemonic(cipherText)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create mnemonic: %v", err)
}
// Now that we have the ciphertext (mapped to the mnemonic), we'll // Now that we have the ciphertext (mapped to the mnemonic), we'll
// attempt to decipher it raw using the user's passphrase. // attempt to decipher it raw using the user's passphrase.
plainSeedBytes, err := mnemonic.Decipher(pass) plainSeedBytes, salt, err := mnemonic.Decipher(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to decipher: %v", err) require.Equal(t, cipherSeed.salt, salt)
}
// If we deserialize the plaintext seed bytes, it should exactly match // If we deserialize the plaintext seed bytes, it should exactly match
// the original cipher seed. // the original cipher seed.
var newSeed CipherSeed newSeed := CipherSeed{
err = newSeed.decode(bytes.NewReader(plainSeedBytes[:])) salt: salt,
if err != nil {
t.Fatalf("unable to decode cipher seed: %v", err)
} }
err = newSeed.decode(bytes.NewReader(plainSeedBytes[:]))
require.NoError(t, err)
assertCipherSeedEqual(t, cipherSeed, &newSeed) assertCipherSeedEqual(t, cipherSeed, &newSeed)
} }
@@ -266,17 +255,13 @@ func TestInvalidExternalVersion(t *testing.T) {
// First, we'll generate a new cipher seed. // First, we'll generate a new cipher seed.
cipherSeed, err := New(0, &testEntropy, time.Now()) cipherSeed, err := New(0, &testEntropy, time.Now())
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create seed: %v", err)
}
// With the cipherseed obtained, we'll now use the raw encipher method // With the cipher seed obtained, we'll now use the raw encipher method
// to obtain our final cipher text. // to obtain our final cipher text.
pass := []byte("newpasswhodis") pass := []byte("newpasswhodis")
cipherText, err := cipherSeed.Encipher(pass) cipherText, err := cipherSeed.Encipher(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to encipher seed: %v", err)
}
// Now that we have the cipher text, we'll modify the first byte to be // Now that we have the cipher text, we'll modify the first byte to be
// an invalid version. // an invalid version.
@@ -284,11 +269,8 @@ func TestInvalidExternalVersion(t *testing.T) {
// With the version swapped, if we try to decipher it, (no matter the // With the version swapped, if we try to decipher it, (no matter the
// passphrase), it should fail. // passphrase), it should fail.
_, err = decipherCipherSeed(cipherText, []byte("kek")) _, _, err = decipherCipherSeed(cipherText, []byte("kek"))
if err != ErrIncorrectVersion { require.Equal(t, ErrIncorrectVersion, err)
t.Fatalf("wrong error: expected ErrIncorrectVersion, "+
"got %v", err)
}
} }
// TestChangePassphrase tests that we're able to generate a cipher seed, then // TestChangePassphrase tests that we're able to generate a cipher seed, then
@@ -300,31 +282,23 @@ func TestChangePassphrase(t *testing.T) {
// First, we'll generate a new cipher seed with a test passphrase. // First, we'll generate a new cipher seed with a test passphrase.
pass := []byte("test") pass := []byte("test")
cipherSeed, err := New(0, &testEntropy, time.Now()) cipherSeed, err := New(0, &testEntropy, time.Now())
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create seed: %v", err)
}
// Now that we have our cipher seed, we'll encipher it and request a // Now that we have our cipher seed, we'll encipher it and request a
// mnemonic that we can use to recover later. // mnemonic that we can use to recover later.
mnemonic, err := cipherSeed.ToMnemonic(pass) mnemonic, err := cipherSeed.ToMnemonic(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create mnemonic: %v", err)
}
// Now that have the mnemonic, we'll attempt to re-encipher the // Now that have the mnemonic, we'll attempt to re-encipher the
// passphrase in order to get a brand new mnemonic. // passphrase in order to get a brand-new mnemonic.
newPass := []byte("strongerpassyeh!") newPass := []byte("strongerpassyeh!")
newmnemonic, err := mnemonic.ChangePass(pass, newPass) newMnemonic, err := mnemonic.ChangePass(pass, newPass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to change passphrase: %v", err)
}
// We'll now attempt to decipher the new mnemonic using the new // We'll now attempt to decipher the new mnemonic using the new
// passphrase to arrive at (what should be) the original cipher seed. // passphrase to arrive at (what should be) the original cipher seed.
newCipherSeed, err := newmnemonic.ToCipherSeed(newPass) newCipherSeed, err := newMnemonic.ToCipherSeed(newPass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to decipher cipher seed: %v", err)
}
// Now that we have the cipher seed, we'll verify that the plaintext // Now that we have the cipher seed, we'll verify that the plaintext
// seed matches *identically*. // seed matches *identically*.
@@ -332,7 +306,7 @@ func TestChangePassphrase(t *testing.T) {
} }
// TestChangePassphraseWrongPass tests that if we have a valid enciphered // TestChangePassphraseWrongPass tests that if we have a valid enciphered
// cipherseed, but then try to change the password with the *wrong* password, // cipher seed, but then try to change the password with the *wrong* password,
// then we get an error. // then we get an error.
func TestChangePassphraseWrongPass(t *testing.T) { func TestChangePassphraseWrongPass(t *testing.T) {
t.Parallel() t.Parallel()
@@ -340,27 +314,21 @@ func TestChangePassphraseWrongPass(t *testing.T) {
// First, we'll generate a new cipher seed with a test passphrase. // First, we'll generate a new cipher seed with a test passphrase.
pass := []byte("test") pass := []byte("test")
cipherSeed, err := New(0, &testEntropy, time.Now()) cipherSeed, err := New(0, &testEntropy, time.Now())
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create seed: %v", err)
}
// Now that we have our cipher seed, we'll encipher it and request a // Now that we have our cipher seed, we'll encipher it and request a
// mnemonic that we can use to recover later. // mnemonic that we can use to recover later.
mnemonic, err := cipherSeed.ToMnemonic(pass) mnemonic, err := cipherSeed.ToMnemonic(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create mnemonic: %v", err)
}
// Now that have the mnemonic, we'll attempt to re-encipher the // Now that have the mnemonic, we'll attempt to re-encipher the
// passphrase in order to get a brand new mnemonic. However, we'll be // passphrase in order to get a brand-new mnemonic. However, we'll be
// using the *wrong* passphrase. This should result in an // using the *wrong* passphrase. This should result in an
// ErrInvalidPass error. // ErrInvalidPass error.
wrongPass := []byte("kek") wrongPass := []byte("kek")
newPass := []byte("strongerpassyeh!") newPass := []byte("strongerpassyeh!")
_, err = mnemonic.ChangePass(wrongPass, newPass) _, err = mnemonic.ChangePass(wrongPass, newPass)
if err != ErrInvalidPass { require.Equal(t, ErrInvalidPass, err)
t.Fatalf("expected ErrInvalidPass, instead got %v", err)
}
} }
// TestMnemonicEncoding uses quickcheck like property based testing to ensure // TestMnemonicEncoding uses quickcheck like property based testing to ensure
@@ -397,7 +365,7 @@ func TestMnemonicEncoding(t *testing.T) {
} }
// TestEncipherDecipher is a property-based test that ensures that given a // TestEncipherDecipher is a property-based test that ensures that given a
// version, entropy, and birthday, then we're able to map that to a cipherseed // version, entropy, and birthday, then we're able to map that to a cipher seed
// mnemonic, then back to the original plaintext cipher seed. // mnemonic, then back to the original plaintext cipher seed.
func TestEncipherDecipher(t *testing.T) { func TestEncipherDecipher(t *testing.T) {
t.Parallel() t.Parallel()
@@ -406,7 +374,7 @@ func TestEncipherDecipher(t *testing.T) {
// ensure that given a random seed tuple (internal version, entropy, // ensure that given a random seed tuple (internal version, entropy,
// and birthday) we're able to convert that to a valid cipher seed. // and birthday) we're able to convert that to a valid cipher seed.
// Additionally, we should be able to decipher the final mnemonic, and // Additionally, we should be able to decipher the final mnemonic, and
// recover the original cipherseed. // recover the original cipher seed.
mainScenario := func(version uint8, entropy [EntropySize]byte, mainScenario := func(version uint8, entropy [EntropySize]byte,
nowInt int64, pass [20]byte) bool { nowInt int64, pass [20]byte) bool {
@@ -458,15 +426,17 @@ func TestEncipherDecipher(t *testing.T) {
// arbitrary raw seed. // arbitrary raw seed.
func TestSeedEncodeDecode(t *testing.T) { func TestSeedEncodeDecode(t *testing.T) {
// mainScenario is the primary driver of our property-based test. We'll // mainScenario is the primary driver of our property-based test. We'll
// ensure that given a random cipher seed, we can encode it an decode // ensure that given a random cipher seed, we can encode it and decode
// it precisely. // it precisely.
mainScenario := func(version uint8, nowInt int64, mainScenario := func(version uint8, nowInt int64,
entropy [EntropySize]byte) bool { entropy [EntropySize]byte) bool {
now := time.Unix(nowInt, 0) now := time.Unix(nowInt, 0)
day := time.Hour * 24
numDaysSinceGenesis := now.Sub(BitcoinGenesisDate) / day
seed := CipherSeed{ seed := CipherSeed{
InternalVersion: version, InternalVersion: version,
Birthday: uint16(now.Sub(BitcoinGenesisDate) / (time.Hour * 24)), Birthday: uint16(numDaysSinceGenesis),
Entropy: entropy, Entropy: entropy,
} }
@@ -506,25 +476,21 @@ func TestSeedEncodeDecode(t *testing.T) {
} }
} }
// TestDecipherUnknownMnenomicWord tests that if we obtain a mnemonic, the // TestDecipherUnknownMnemonicWord tests that if we obtain a mnemonic, then
// modify one of the words to not be within the word list, then it's detected // modify one of the words to not be within the word list, then it's detected
// when we attempt to map it back to the original cipher seed. // when we attempt to map it back to the original cipher seed.
func TestDecipherUnknownMnenomicWord(t *testing.T) { func TestDecipherUnknownMnemonicWord(t *testing.T) {
t.Parallel() t.Parallel()
// First, we'll create a new cipher seed with "test" ass a password. // First, we'll create a new cipher seed with "test" ass a password.
pass := []byte("test") pass := []byte("test")
cipherSeed, err := New(0, &testEntropy, time.Now()) cipherSeed, err := New(0, &testEntropy, time.Now())
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create seed: %v", err)
}
// Now that we have our cipher seed, we'll encipher it and request a // Now that we have our cipher seed, we'll encipher it and request a
// mnemonic that we can use to recover later. // mnemonic that we can use to recover later.
mnemonic, err := cipherSeed.ToMnemonic(pass) mnemonic, err := cipherSeed.ToMnemonic(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create mnemonic: %v", err)
}
// Before we attempt to decrypt the cipher seed, we'll mutate one of // Before we attempt to decrypt the cipher seed, we'll mutate one of
// the word so it isn't actually in our final word list. // the word so it isn't actually in our final word list.
@@ -532,58 +498,36 @@ func TestDecipherUnknownMnenomicWord(t *testing.T) {
mnemonic[randIndex] = "kek" mnemonic[randIndex] = "kek"
// If we attempt to map back to the original cipher seed now, then we // If we attempt to map back to the original cipher seed now, then we
// should get ErrUnknownMnenomicWord. // should get ErrUnknownMnemonicWord.
_, err = mnemonic.ToCipherSeed(pass) _, err = mnemonic.ToCipherSeed(pass)
if err == nil { wordErr := &ErrUnknownMnemonicWord{}
t.Fatalf("expected ErrUnknownMnenomicWord error") require.ErrorAs(t, err, wordErr)
} require.Equal(t, "kek", wordErr.Word)
require.Equal(t, uint8(randIndex), wordErr.Index)
wordErr, ok := err.(ErrUnknownMnenomicWord) // If the mnemonic includes a word that is not in the englishList it
if !ok { // fails, even when it is a substring of a valid word Example: `heart`
t.Fatalf("expected ErrUnknownMnenomicWord instead got %T", err) // is in the list, `hear` is not.
}
if wordErr.Word != "kek" {
t.Fatalf("word mismatch: expected %v, got %v", "kek", wordErr.Word)
}
if int32(wordErr.Index) != randIndex {
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" mnemonic[randIndex] = "hear"
// If we attempt to map back to the original cipher seed now, then we // If we attempt to map back to the original cipher seed now, then we
// should get ErrUnknownMnenomicWord. // should get ErrUnknownMnemonicWord.
_, err = mnemonic.ToCipherSeed(pass) _, err = mnemonic.ToCipherSeed(pass)
if err == nil { require.ErrorAs(t, err, wordErr)
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 // TestDecipherIncorrectMnemonic tests that if we obtain a cipher seed, but then
// swap out words, then checksum fails. // swap out words, then checksum fails.
func TestDecipherIncorrectMnemonic(t *testing.T) { func TestDecipherIncorrectMnemonic(t *testing.T) {
// First, we'll create a new cipher seed with "test" ass a password. // First, we'll create a new cipher seed with "test" ass a password.
pass := []byte("test") pass := []byte("test")
cipherSeed, err := New(0, &testEntropy, time.Now()) cipherSeed, err := New(0, &testEntropy, time.Now())
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create seed: %v", err)
}
// Now that we have our cipher seed, we'll encipher it and request a // Now that we have our cipher seed, we'll encipher it and request a
// mnemonic that we can use to recover later. // mnemonic that we can use to recover later.
mnemonic, err := cipherSeed.ToMnemonic(pass) mnemonic, err := cipherSeed.ToMnemonic(pass)
if err != nil { require.NoError(t, err)
t.Fatalf("unable to create mnemonic: %v", err)
}
// We'll now swap out two words from the mnemonic, which should trigger // We'll now swap out two words from the mnemonic, which should trigger
// a checksum failure. // a checksum failure.
@@ -593,11 +537,9 @@ func TestDecipherIncorrectMnemonic(t *testing.T) {
// If we attempt to decrypt now, we should get a checksum failure. // If we attempt to decrypt now, we should get a checksum failure.
// If we attempt to map back to the original cipher seed now, then we // If we attempt to map back to the original cipher seed now, then we
// should get ErrUnknownMnenomicWord. // should get ErrIncorrectMnemonic.
_, err = mnemonic.ToCipherSeed(pass) _, err = mnemonic.ToCipherSeed(pass)
if err != ErrIncorrectMnemonic { require.Equal(t, ErrIncorrectMnemonic, err)
t.Fatalf("expected ErrIncorrectMnemonic error")
}
} }
// TODO(roasbeef): add test failure checksum fail is modified, new error // TODO(roasbeef): add test failure checksum fail is modified, new error
+4 -4
View File
@@ -18,9 +18,9 @@ var (
"match") "match")
) )
// ErrUnknownMnenomicWord is returned when attempting to decipher and // ErrUnknownMnemonicWord is returned when attempting to decipher and
// enciphered mnemonic, but a word encountered isn't a member of our word list. // enciphered mnemonic, but a word encountered isn't a member of our word list.
type ErrUnknownMnenomicWord struct { type ErrUnknownMnemonicWord struct {
// Word is the unknown word in the mnemonic phrase. // Word is the unknown word in the mnemonic phrase.
Word string Word string
@@ -29,8 +29,8 @@ type ErrUnknownMnenomicWord struct {
Index uint8 Index uint8
} }
// Error returns a human readable string describing the error. // Error returns a human-readable string describing the error.
func (e ErrUnknownMnenomicWord) Error() string { func (e ErrUnknownMnemonicWord) Error() string {
return fmt.Sprintf("word %v isn't a part of default word list "+ return fmt.Sprintf("word %v isn't a part of default word list "+
"(index=%v)", e.Word, e.Index) "(index=%v)", e.Word, e.Index)
} }
+3 -4
View File
@@ -965,14 +965,13 @@ func waitForWalletPassword(cfg *Config,
// seed. If it's greater than the current key derivation // seed. If it's greater than the current key derivation
// version, then we'll return an error as we don't understand // version, then we'll return an error as we don't understand
// this. // this.
const latestVersion = keychain.KeyDerivationVersion
if cipherSeed != nil && if cipherSeed != nil &&
cipherSeed.InternalVersion != latestVersion { !keychain.IsKnownVersion(cipherSeed.InternalVersion) {
return nil, fmt.Errorf("invalid internal "+ return nil, fmt.Errorf("invalid internal "+
"seed version %v, current version is %v", "seed version %v, current max version is %v",
cipherSeed.InternalVersion, cipherSeed.InternalVersion,
keychain.KeyDerivationVersion) keychain.CurrentKeyDerivationVersion)
} }
loader, err := btcwallet.NewWalletLoader( loader, err := btcwallet.NewWalletLoader(
@@ -29,6 +29,11 @@ The `walletrpc.SignPsbt` RPC now also supports [Taproot PSBT
signing](https://github.com/lightningnetwork/lnd/pull/6450) to fully support signing](https://github.com/lightningnetwork/lnd/pull/6450) to fully support
remote signing with Taproot outputs. remote signing with Taproot outputs.
The internal version of the `aezeed` [was bumped to `1` to mark new seeds that
were created after introducing the Taproot key
derivation](https://github.com/lightningnetwork/lnd/pull/6524) to simplify
detecting Taproot compatibility of a seed.
## MuSig2 ## MuSig2
The [`signrpc.Signer` RPC service now supports EXPERIMENTAL MuSig2 The [`signrpc.Signer` RPC service now supports EXPERIMENTAL MuSig2
+21 -5
View File
@@ -8,11 +8,20 @@ import (
) )
const ( const (
// KeyDerivationVersion is the version of the key derivation schema // KeyDerivationVersionLegacy is the previous version of the key
// defined below. We use a version as this means that we'll be able to // derivation schema defined below. We use a version as this means that
// accept new seed in the future and be able to discern if the software // we'll be able to accept new seed in the future and be able to discern
// is compatible with the version of the seed. // if the software is compatible with the version of the seed.
KeyDerivationVersion = 0 KeyDerivationVersionLegacy = 0
// KeyDerivationVersionTaproot is the most recent version of the key
// derivation scheme that marks the introduction of the Taproot
// derivation with BIP0086 support.
KeyDerivationVersionTaproot = 1
// CurrentKeyDerivationVersion is the current default key derivation
// version that is used for new seeds.
CurrentKeyDerivationVersion = KeyDerivationVersionTaproot
// BIP0043Purpose is the "purpose" value that we'll use for the first // BIP0043Purpose is the "purpose" value that we'll use for the first
// version or our key derivation scheme. All keys are expected to be // version or our key derivation scheme. All keys are expected to be
@@ -25,6 +34,13 @@ const (
BIP0043Purpose = 1017 BIP0043Purpose = 1017
) )
// IsKnownVersion returns true if the given version is one of the known
// derivation scheme versions as defined by this package.
func IsKnownVersion(internalVersion uint8) bool {
return internalVersion == KeyDerivationVersionLegacy ||
internalVersion == KeyDerivationVersionTaproot
}
var ( var (
// MaxKeyRangeScan is the maximum number of keys that we'll attempt to // MaxKeyRangeScan is the maximum number of keys that we'll attempt to
// scan with if a caller knows the public key, but not the KeyLocator // scan with if a caller knows the public key, but not the KeyLocator
+1 -1
View File
@@ -309,7 +309,7 @@ func (u *UnlockerService) GenSeed(_ context.Context,
// instance. // instance.
// //
cipherSeed, err := aezeed.New( cipherSeed, err := aezeed.New(
keychain.KeyDerivationVersion, &entropy, time.Now(), keychain.CurrentKeyDerivationVersion, &entropy, time.Now(),
) )
if err != nil { if err != nil {
return nil, err return nil, err
+1 -1
View File
@@ -91,7 +91,7 @@ func createSeedAndMnemonic(t *testing.T,
pass []byte) (*aezeed.CipherSeed, aezeed.Mnemonic) { pass []byte) (*aezeed.CipherSeed, aezeed.Mnemonic) {
cipherSeed, err := aezeed.New( cipherSeed, err := aezeed.New(
keychain.KeyDerivationVersion, &testEntropy, time.Now(), keychain.CurrentKeyDerivationVersion, &testEntropy, time.Now(),
) )
require.NoError(t, err) require.NoError(t, err)