aezeed: make seed generation fully deterministic
This commit fixes a TODO to make the seed generation fully deterministic by providing the option to pass in a custom randomness source.
This commit is contained in:
2 files changed
+67
-6
No files matched your search
+38
-6
@@ -128,6 +128,33 @@ 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 cipher seed 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
|
||||||
@@ -174,17 +201,22 @@ type CipherSeed struct {
|
|||||||
|
|
||||||
// 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 deterministic?
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,35 @@ func TestAezeedVersion0TestVectors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|||||||
Reference in new issue
Block a user