diff --git a/aezeed/bench_test.go b/aezeed/bench_test.go new file mode 100644 index 00000000..c54c1794 --- /dev/null +++ b/aezeed/bench_test.go @@ -0,0 +1,69 @@ +package aezeed + +import ( + "testing" + "time" +) + +var ( + mnemonic Mnemonic + + seed *CipherSeed +) + +// BenchmarkFrommnemonic benchmarks the process of converting a cipher seed +// (given the salt), to an enciphered mnemonic. +func BenchmarkTomnemonic(b *testing.B) { + scryptN = 32768 + scryptR = 8 + scryptP = 1 + + pass := []byte("1234567890abcedfgh") + cipherSeed, err := New(0, nil, time.Now()) + if err != nil { + b.Fatalf("unable to create seed: %v", err) + } + + var r Mnemonic + for i := 0; i < b.N; i++ { + r, err = cipherSeed.ToMnemonic(pass) + if err != nil { + b.Fatalf("unable to encipher: %v", err) + } + } + + b.ReportAllocs() + + mnemonic = r +} + +// BenchmarkToCipherSeed benchmarks the process of deciphering an existing +// enciphered mnemonic. +func BenchmarkToCipherSeed(b *testing.B) { + scryptN = 32768 + scryptR = 8 + scryptP = 1 + + pass := []byte("1234567890abcedfgh") + cipherSeed, err := New(0, nil, time.Now()) + if err != nil { + b.Fatalf("unable to create seed: %v", err) + } + + mnemonic, err := cipherSeed.ToMnemonic(pass) + if err != nil { + b.Fatalf("unable to create mnemonic: %v", err) + } + + var s *CipherSeed + for i := 0; i < b.N; i++ { + s, err = mnemonic.ToCipherSeed(pass) + if err != nil { + b.Fatalf("unable to decipher: %v", err) + } + } + + b.ReportAllocs() + + seed = s +} diff --git a/aezeed/cipherseed.go b/aezeed/cipherseed.go new file mode 100644 index 00000000..3c34754d --- /dev/null +++ b/aezeed/cipherseed.go @@ -0,0 +1,547 @@ +package aezeed + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "hash/crc32" + "io" + "strings" + "time" + + "github.com/Yawning/aez" + "github.com/kkdai/bstream" + + "golang.org/x/crypto/scrypt" +) + +const ( + // CipherSeedVersion is the current version of the aezeed scheme as + // defined in this package. This version indicates the following + // parameters for the deciphered cipher seed: a 1 byte version, 2 bytes + // for the Bitcoin Days Genesis timestamp, and 16 bytes for entropy. It + // also governs how the cipher seed should be enciphered. In this + // version we take the deciphered seed, create a 5 byte salt, use that + // with an optional passphrase to generate a 32-byte key (via scrypt), + // then encipher with aez (using the salt and version as AD). The final + // enciphered seed is: version || ciphertext || salt. + CipherSeedVersion uint8 = 0 + + // DecipheredCipherSeedSize is the size of the plaintext seed resulting + // from deciphering the cipher seed. The size consists of the + // following: + // + // * 1 byte version || 2 bytes timestamp || 16 bytes of entropy. + // + // The version is used by wallets to know how to re-derive relevant + // addresses, the 2 byte timestamp a BDG (Bitcoin Days Genesis) offset, + // and finally, the 16 bytes to be used to generate the HD wallet seed. + DecipheredCipherSeedSize = 19 + + // EncipheredCipherSeedSize is the size of the fully encoded+enciphered + // cipher seed. We first obtain the enciphered plaintext seed by + // carrying out the enciphering as governed in the current version. We + // then take that enciphered seed (now 19+4=23 bytes due to ciphertext + // expansion, essentially a checksum) and prepend a version, then + // append the salt, and then take a checksum of everything. The + // checksum allows us to verify that the user input the correct set of + // words, then we can verify the passphrase due to the internal MAC + // equiv. The final breakdown is: + // + // * 1 byte version || 23 byte enciphered seed || 5 byte salt || 4 byte checksum + // + // With CipherSeedVersion we encipher as follows: we use + // scrypt(n=32768, r=8, p=1) to derive a 32-byte key from an optional + // user passphrase. We then encipher the plaintext seed using a value + // of tau (with aez) of 8-bytes (so essentially a 32-bit MAC). When + // enciphering, we include the version and scrypt salt as the AD. This + // gives us a total of 33 bytes. These 33 bytes fit cleanly into 24 + // mnemonic words. + EncipheredCipherSeedSize = 33 + + // CipherTextExpansion is the number of bytes that will be added as + // redundancy for the enciphering scheme implemented by aez. This can + // be seen as the size of the equivalent MAC. + CipherTextExpansion = 4 + + // EntropySize is the number of bytes of entropy we'll use the generate + // the seed. + EntropySize = 16 + + // NummnemonicWords is the number of words that an encoded cipher seed + // will result in. + NummnemonicWords = 24 + + // 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 + // role of the salt is to make the creation of rainbow tables + // infeasible. + saltSize = 5 + + // adSize is the size of the encoded associated data that will be + // passed into aez when enciphering and deciphering the seed. The AD + // itself (associated data) is just the CipherSeedVersion and salt. + adSize = 6 + + // checkSumSize is the size of the checksum applied to the final + // encoded ciphertext. + checkSumSize = 4 + + // keyLen is the size of the key that we'll use for encryption with + // aez. + keyLen = 32 + + // bitsPerWord is the number of bits each word in the wordlist encodes. + // We encode our mnemonic using 24 words, so 264 bits (33 bytes). + bitsPerWord = 11 + + // saltOffset is the index within an enciphered cipherseed that marks + // the start of the salt. + saltOffset = EncipheredCipherSeedSize - checkSumSize - saltSize + + // checkSumSize is the index within an enciphered cipher seed that + // marks the start of the checksum. + checkSumOffset = EncipheredCipherSeedSize - checkSumSize + + // encipheredSeedSize is the size of the cipherseed before applying the + // external version, salt, and checksum for the final encoding. + encipheredSeedSize = DecipheredCipherSeedSize + CipherTextExpansion +) + +var ( + // Below at the default scrypt parameters that are tied to + // CipherSeedVersion zero. + scryptN = 32768 + scryptR = 8 + scryptP = 1 + + // crcTable is a table that presents the polynomial we'll use for + // computing our checksum. + crcTable = crc32.MakeTable(crc32.Castagnoli) + + // defaultPassphras is the default passphrase that will be used for + // encryption in the case that the user chooses not to specify their + // own passphrase. + defaultPassphrase = []byte("aezeed") +) + +var ( + // bitcoinGenesisDate is the timestamp of Bitcoin's genesis block. + // We'll use this value in order to create a compact birthday for the + // seed. The birthday will be interested as the number of days since + // the genesis date. We refer to this time period as ABE (after Bitcoin + // era). + bitcoinGenesisDate = time.Unix(1231006505, 0) +) + +// 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 +// 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 +// created as the widely used schemes in the space lack two critical traits: a +// version byte, and a birthday timestamp. The version allows us to modify the +// details of the scheme in the future, and the birthday gives wallets a limit +// of how far back in the chain they'll need to start scanning. We also add an +// external version to the enciphering plaintext seed. With this addition, +// seeds are able to be "upgraded" (to diff params, or entirely diff crypt), +// while maintaining the semantics of the plaintext seed. +// +// The core of the scheme is the usage of aez to carefully control the size of +// the final encrypted seed. With the current parameters, this scheme can be +// encoded using a 24 word mnemonic. We use 4 bytes of ciphertext expansion +// when enciphering the raw seed, giving us the equivalent of 40-bit MAC (as we +// check for a particular seed version). Using the external 4 byte checksum, +// we're able to ensure that the user input the correct set of words. Finally, +// the password in the scheme is optional. If not specified, "aezeed" will be +// used as the password. Otherwise, the addition of the password means that +// users can encrypt the raw "plaintext" seed under distinct passwords to +// produce unique mnemonic phrases. +type CipherSeed struct { + // InternalVersion is the version of the plaintext cipherseed. This is + // to be used by wallets to determine if the seed version is compatible + // with the derivation schemes they know. + InternalVersion uint8 + + // Birthday is the time that the seed was created. This is expressed as + // the number of days since the timestamp in the Bitcoin genesis block. + // We use days as seconds gives us wasted granularity. The oldest seed + // that we can encode using this format is through the date 2188. + Birthday uint16 + + // Entropy is a set of bytes generated via a CSPRNG. This is the value + // that should be used to directly generate the HD root, as defined + // within BIP0032. + Entropy [EntropySize]byte + + // salt is the salt that was used to generate the key from the user's + // specified passphrase. + salt [saltSize]byte +} + +// 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 +// place. The final argument should be the time at which the seed was created. +func New(internalVersion uint8, entropy *[EntropySize]byte, + now time.Time) (*CipherSeed, error) { + + // TODO(roasbeef): pass randomness source? to make fully determinsitc? + + // If a set of entropy wasn't provided, then we'll read a set of bytes + // from the CSPRNG of our operating platform. + var seed [EntropySize]byte + if entropy == nil { + if _, err := rand.Read(seed[:]); err != nil { + return nil, err + } + } else { + // Otherwise, we'll copy the set of bytes. + copy(seed[:], entropy[:]) + } + + // To compute our "birthday", we'll first use the current time, then + // subtract that from the Bitcoin Genesis Date. We'll then convert that + // value to days. + birthday := uint16(now.Sub(bitcoinGenesisDate) / (time.Hour * 24)) + + c := &CipherSeed{ + InternalVersion: internalVersion, + Birthday: birthday, + Entropy: seed, + } + + // Next, we'll read a random salt that will be used with scrypt to + // eventually derive our key. + if _, err := rand.Read(c.salt[:]); err != nil { + return nil, err + } + + return c, nil +} + +// encode attempts to encode the target cipherSeed into the passed io.Writer +// instance. +func (c *CipherSeed) encode(w io.Writer) error { + err := binary.Write(w, binary.BigEndian, c.InternalVersion) + if err != nil { + return err + } + + if err := binary.Write(w, binary.BigEndian, c.Birthday); err != nil { + return err + } + + if _, err := w.Write(c.Entropy[:]); err != nil { + return err + } + + return nil +} + +// decode attempts to decode an encoded cipher seed instance into the target +// CipherSeed struct. +func (c *CipherSeed) decode(r io.Reader) error { + err := binary.Read(r, binary.BigEndian, &c.InternalVersion) + if err != nil { + return err + } + + if err := binary.Read(r, binary.BigEndian, &c.Birthday); err != nil { + return err + } + + if _, err := io.ReadFull(r, c.Entropy[:]); err != nil { + return err + } + + return nil +} + +// encodeAD returns the fully encoded associated data for use when performing +// our current enciphering operation. The AD is: version || salt. +func encodeAD(version uint8, salt [saltSize]byte) [adSize]byte { + var ad [adSize]byte + ad[0] = byte(version) + copy(ad[1:], salt[:]) + + return ad +} + +// extractAD extracts an associated data from a fully encoded and enciphered +// cipher seed. This is to be used when attempting to decrypt an enciphered +// cipher seed. +func extractAD(encipheredSeed [EncipheredCipherSeedSize]byte) [adSize]byte { + var ad [adSize]byte + ad[0] = encipheredSeed[0] + + copy(ad[1:], encipheredSeed[saltOffset:checkSumOffset]) + + return ad +} + +// encipher takes a fully populated cipherseed instance, and enciphers 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 +// preceding. +func (c *CipherSeed) encipher(pass []byte) ([EncipheredCipherSeedSize]byte, error) { + var cipherSeedBytes [EncipheredCipherSeedSize]byte + + // If the passphrase wasn't provided, then we'll use the string + // "aezeed" in place. + passphrase := pass + if len(passphrase) == 0 { + passphrase = defaultPassphrase + } + + // With our salt pre-generated, we'll now run the password through a + // KDF to obtain the key we'll use for encryption. + key, err := scrypt.Key( + passphrase, c.salt[:], scryptN, scryptR, scryptP, keyLen, + ) + if err != nil { + return cipherSeedBytes, err + } + + // Next, we'll encode the serialized plaintext cipherseed into a buffer + // that we'll use for encryption. + var seedBytes bytes.Buffer + if err := c.encode(&seedBytes); err != nil { + return cipherSeedBytes, err + } + + // With our plaintext seed encoded, we'll now construct the AD that + // will be passed to the encryption operation. This ensures to + // authenticate both the salt and the external version. + ad := encodeAD(CipherSeedVersion, c.salt) + + // With all items assembled, we'll now encipher the plaintext seed + // with our AD, key, and MAC size. + cipherSeed := seedBytes.Bytes() + cipherText := aez.Encrypt( + key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil, + ) + + // Finally, we'll pack the {version || ciphertext || salt || checksum} + // seed into a byte slice for encoding as a mnemonic. + cipherSeedBytes[0] = byte(CipherSeedVersion) + copy(cipherSeedBytes[1:saltOffset], cipherText) + copy(cipherSeedBytes[saltOffset:], c.salt[:]) + + // With the seed mostly assembled, we'll now compute a checksum all the + // contents. + checkSum := crc32.Checksum(cipherSeedBytes[:checkSumOffset], crcTable) + + // With our checksum computed, we can finish encoding the full cipher + // seed. + var checkSumBytes [4]byte + binary.BigEndian.PutUint32(checkSumBytes[:], checkSum) + copy(cipherSeedBytes[checkSumOffset:], checkSumBytes[:]) + + return cipherSeedBytes, nil +} + +// cipherTextToMnemonic converts the aez ciphertext appended with the salt to a +// 24-word mnemonic pass phrase. +func cipherTextToMnemonic(cipherText [EncipheredCipherSeedSize]byte) (Mnemonic, error) { + var words [NummnemonicWords]string + + // First, we'll convert the ciphertext itself into a bitstream for easy + // manipulation. + cipherBits := bstream.NewBStreamReader(cipherText[:]) + + // With our bitstream obtained, we'll read 11 bits at a time, then use + // that to index into our word list to obtain the next word. + for i := 0; i < NummnemonicWords; i++ { + index, err := cipherBits.ReadBits(bitsPerWord) + if err != nil { + return words, nil + } + + words[i] = defaultWordList[index] + } + + return words, nil +} + +// 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 +// will be used in its place. +func (c *CipherSeed) ToMnemonic(pass []byte) (Mnemonic, error) { + // First, we'll convert the valid seed triple into an aez cipher text + // with our KDF salt appended to it. + cipherText, err := c.encipher(pass) + if err != nil { + return Mnemonic{}, nil + } + + // Now that we have our cipher text, we'll convert it into a mnemonic + // phrase. + return cipherTextToMnemonic(cipherText) +} + +// Encipher maps the cipher seed to an aez ciphertext using an optional +// passphrase. +func (c *CipherSeed) Encipher(pass []byte) ([EncipheredCipherSeedSize]byte, error) { + return c.encipher(pass) +} + +// Mnemonic is a 24-word passphrase as of CipherSeedVersion zero. This +// passphrase encodes an encrypted seed triple (version, birthday, entropy). +// 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 +// to decipher the seed. +type Mnemonic [NummnemonicWords]string + +// mnemonicToCipherText converts a 24-word mnemonic phrase into a 33 byte +// cipher text. +// +// NOTE: This assumes that all words have already been checked to be amongst +// our word list. +func mnemonicToCipherText(mnemonic *Mnemonic) [EncipheredCipherSeedSize]byte { + var cipherText [EncipheredCipherSeedSize]byte + + // We'll now perform the reverse mapping to that of + // cipherTextToMnemonic: we'll get the index of the word, then write + // out that index to the bit stream. + cipherBits := bstream.NewBStreamWriter(EncipheredCipherSeedSize) + for _, word := range mnemonic { + // Using the reverse word map, we'll locate the index of this + // word within the word list. + index := uint64(reverseWordMap[word]) + + // With the index located, we'll now write this out to the + // bitstream, appending to what's already there. + cipherBits.WriteBits(index, bitsPerWord) + } + + copy(cipherText[:], cipherBits.Bytes()) + + return cipherText +} + +// ToCipherSeed attempts to map the mnemonic to the original cipher text byte +// slice. Then we'll attempt to decrypt the ciphertext using aez with the +// passed passphrase, using the last 5 bytes of the ciphertext as a salt for +// the KDF. +func (m *Mnemonic) ToCipherSeed(pass []byte) (*CipherSeed, error) { + // First, we'll attempt to decipher the mnemonic by mapping back into + // our byte slice and applying our deciphering scheme. + plainSeed, err := m.Decipher(pass) + if err != nil { + return nil, err + } + + // If decryption was successful, then we'll decode into a fresh + // CipherSeed struct. + var c CipherSeed + if err := c.decode(bytes.NewReader(plainSeed[:])); err != nil { + return nil, err + } + + return &c, nil +} + +// decipherCipherSeed attempts to decipher the passed cipher seed ciphertext +// using the passed passphrase. This function is the opposite of +// the encipher method. +func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte, + pass []byte) ([DecipheredCipherSeedSize]byte, error) { + + var plainSeed [DecipheredCipherSeedSize]byte + + // 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 + // the cipher seed. + if uint8(cipherSeedBytes[0]) != CipherSeedVersion { + return plainSeed, ErrIncorrectVersion + } + + // 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 + // finally the checksum. + salt := cipherSeedBytes[saltOffset : saltOffset+saltSize] + cipherSeed := cipherSeedBytes[1:saltOffset] + checksum := cipherSeedBytes[checkSumOffset:] + + // 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. + freshChecksum := crc32.Checksum(cipherSeedBytes[:checkSumOffset], crcTable) + if freshChecksum != binary.BigEndian.Uint32(checksum) { + return plainSeed, ErrIncorrectMnemonic + } + + // With the salt separated from the cipher text, we'll now obtain the + // key used for encryption. + key, err := scrypt.Key(pass, salt, scryptN, scryptR, scryptP, keyLen) + if err != nil { + return plainSeed, err + } + + // We'll also extract the AD that will be required to properly pass the + // MAC check. + ad := extractAD(cipherSeedBytes) + + // With the key, we'll attempt to decrypt the plaintext. If the + // ciphertext was altered, or the passphrase is incorrect, then we'll + // error out. + plainSeedBytes, ok := aez.Decrypt( + key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil, + ) + if !ok { + return plainSeed, ErrInvalidPass + } + copy(plainSeed[:], plainSeedBytes) + + return plainSeed, nil + +} + +// Decipher attempts to decipher the encoded mnemonic by first mapping to the +// original chipertext, then applying our deciphering scheme. ErrInvalidPass +// will be returned if the passphrase is incorrect. +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. + for _, word := range m { + if !strings.Contains(englishWordList, word) { + emptySeed := [DecipheredCipherSeedSize]byte{} + return emptySeed, ErrUnknownMnenomicWord{word} + } + } + + // If the passphrase wasn't provided, then we'll use the string + // "aezeed" in place. + passphrase := pass + if len(passphrase) == 0 { + passphrase = defaultPassphrase + } + + // Next, we'll map the mnemonic phrase back into the original cipher + // text. + cipherText := mnemonicToCipherText(m) + + // Finally, we'll attempt to decipher the enciphered seed. The result + // will be the raw seed minus the ciphertext expansion, external + // version, and salt. + return decipherCipherSeed(cipherText, passphrase) +} + +// ChangePass takes an existing mnemonic, and passphrase for said mnemonic and +// 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 +// phrases, or just change the passphrase on an existing seed. +func (m *Mnemonic) ChangePass(oldPass, newPass []byte) (Mnemonic, error) { + var newmnemonic Mnemonic + + // First, we'll try to decrypt the current mnemonic using the existing + // passphrase. If this fails, then we can't proceed any further. + cipherSeed, err := m.ToCipherSeed(oldPass) + if err != nil { + return newmnemonic, err + } + + // If the deciperhing was successful, then we'll now re-encipher using + // the new user provided passphrase. + return cipherSeed.ToMnemonic(newPass) +} diff --git a/aezeed/cipherseeed_test.go b/aezeed/cipherseeed_test.go new file mode 100644 index 00000000..a6215059 --- /dev/null +++ b/aezeed/cipherseeed_test.go @@ -0,0 +1,513 @@ +package aezeed + +import ( + "bytes" + "math/rand" + "testing" + "testing/quick" + "time" +) + +var ( + testEntropy = [EntropySize]byte{ + 0x81, 0xb6, 0x37, 0xd8, + 0x63, 0x59, 0xe6, 0x96, + 0x0d, 0xe7, 0x95, 0xe4, + 0x1e, 0x0b, 0x4c, 0xfd, + } +) + +func assertCipherSeedEqual(t *testing.T, cipherSeed *CipherSeed, + cipherSeed2 *CipherSeed) { + + if cipherSeed.InternalVersion != cipherSeed2.InternalVersion { + t.Fatalf("mismatched versions: expected %v, got %v", + cipherSeed.InternalVersion, cipherSeed2.InternalVersion) + } + if cipherSeed.Birthday != cipherSeed2.Birthday { + t.Fatalf("mismatched birthday: expected %v, got %v", + cipherSeed.Birthday, cipherSeed2.Birthday) + } + if cipherSeed.Entropy != cipherSeed2.Entropy { + t.Fatalf("mismatched versions: expected %x, got %x", + cipherSeed.Entropy[:], cipherSeed2.Entropy[:]) + } +} + +func TestAezeedVersion0TestVectors(t *testing.T) { + t.Parallel() + + // TODO(roasbeef): +} + +// TestEmptyPassphraseDerivation tests that the aezeed scheme is able to derive +// a proper mnemonic, and decipher that mnemonic when the user uses an empty +// passphrase. +func TestEmptyPassphraseDerivation(t *testing.T) { + t.Parallel() + + // Our empty passphrase... + pass := []byte{} + + // We'll now create a new cipher seed with an internal version of zero + // to simulate a wallet that just adopted the scheme. + cipherSeed, err := New(0, &testEntropy, time.Now()) + if err != nil { + t.Fatalf("unable to create seed: %v", err) + } + + // Now that the seed has been created, we'll attempt to convert it to a + // valid mnemonic. + mnemonic, err := cipherSeed.ToMnemonic(pass) + if err != nil { + t.Fatalf("unable to create mnemonic: %v", err) + } + + // Next, we'll try to decrypt the mnemonic with the passphrase that we + // used. + cipherSeed2, err := mnemonic.ToCipherSeed(pass) + if err != nil { + t.Fatalf("unable to decrypt mnemonic: %v", err) + } + + // Finally, we'll ensure that the uncovered cipher seed matches + // precisely. + assertCipherSeedEqual(t, cipherSeed, cipherSeed2) +} + +// TestManualEntropyGeneration tests that if the user doesn't provide a source +// of entropy, then we do so ourselves. +func TestManualEntropyGeneration(t *testing.T) { + t.Parallel() + + // Our empty passphrase... + pass := []byte{} + + // We'll now create a new cipher seed with an internal version of zero + // to simulate a wallet that just adopted the scheme. + cipherSeed, err := New(0, nil, time.Now()) + if err != nil { + t.Fatalf("unable to create seed: %v", err) + } + + // Now that the seed has been created, we'll attempt to convert it to a + // valid mnemonic. + mnemonic, err := cipherSeed.ToMnemonic(pass) + if err != nil { + t.Fatalf("unable to create mnemonic: %v", err) + } + + // Next, we'll try to decrypt the mnemonic with the passphrase that we + // used. + cipherSeed2, err := mnemonic.ToCipherSeed(pass) + if err != nil { + t.Fatalf("unable to decrypt mnemonic: %v", err) + } + + // Finally, we'll ensure that the uncovered cipher seed matches + // precisely. + assertCipherSeedEqual(t, cipherSeed, cipherSeed2) +} + +// TestInvalidPassphraseRejection tests if a caller attempts to use the +// incorrect passprhase for an enciphered seed, then the proper error is +// returned. +func TestInvalidPassphraseRejection(t *testing.T) { + t.Parallel() + + // First, we'll generate a new cipher seed with a test passphrase. + pass := []byte("test") + cipherSeed, err := New(0, &testEntropy, time.Now()) + if err != nil { + t.Fatalf("unable to create seed: %v", err) + } + + // Now that we have our cipher seed, we'll encipher it and request a + // mnemonic that we can use to recover later. + mnemonic, err := cipherSeed.ToMnemonic(pass) + if err != nil { + t.Fatalf("unable to create mnemonic: %v", err) + } + + // If we try to decipher with the wrong passphrase, we should get the + // proper error. + wrongPass := []byte("kek") + if _, err := mnemonic.ToCipherSeed(wrongPass); err != ErrInvalidPass { + t.Fatalf("expected ErrInvalidPass, instead got %v", err) + } +} + +// TestRawEncipherDecipher tests that callers are able to use the raw methods +// to map between ciphertext and the raw plaintext deciphered seed. +func TestRawEncipherDecipher(t *testing.T) { + t.Parallel() + + // First, we'll generate a new cipher seed with a test passphrase. + pass := []byte("test") + cipherSeed, err := New(0, &testEntropy, time.Now()) + if err != nil { + t.Fatalf("unable to create seed: %v", err) + } + + // With the cipherseed obtained, we'll now use the raw encipher method + // to obtain our final cipher text. + cipherText, err := cipherSeed.Encipher(pass) + if err != nil { + t.Fatalf("unable to encipher seed: %v", err) + } + + mnemonic, err := cipherTextToMnemonic(cipherText) + if err != nil { + t.Fatalf("unable to create mnemonic: %v", err) + } + + // Now that we have the ciphertext (mapped to the mnemonic), we'll + // attempt to decipher it raw using the user's passphrase. + plainSeedBytes, err := mnemonic.Decipher(pass) + if err != nil { + t.Fatalf("unable to decipher: %v", err) + } + + // If we deserialize the plaintext seed bytes, it should exactly match + // the original cipher seed. + var newSeed CipherSeed + err = newSeed.decode(bytes.NewReader(plainSeedBytes[:])) + if err != nil { + t.Fatalf("unable to decode cipher seed: %v", err) + } + + assertCipherSeedEqual(t, cipherSeed, &newSeed) +} + +// TestInvalidExternalVersion tests that if we present a ciphertext with the +// incorrect version to decipherCipherSeed, then it fails with the expected +// error. +func TestInvalidExternalVersion(t *testing.T) { + t.Parallel() + + // First, we'll generate a new cipher seed. + cipherSeed, err := New(0, &testEntropy, time.Now()) + if err != nil { + t.Fatalf("unable to create seed: %v", err) + } + + // With the cipherseed obtained, we'll now use the raw encipher method + // to obtain our final cipher text. + pass := []byte("newpasswhodis") + cipherText, err := cipherSeed.Encipher(pass) + if err != nil { + t.Fatalf("unable to encipher seed: %v", err) + } + + // Now that we have the cipher text, we'll modify the first byte to be + // an invalid version. + cipherText[0] = 44 + + // With the version swapped, if we try to decipher it, (no matter the + // passphrase), it should fail. + _, err = decipherCipherSeed(cipherText, []byte("kek")) + if err != ErrIncorrectVersion { + t.Fatalf("wrong error: expected ErrIncorrectVersion, "+ + "got %v", err) + } +} + +// TestChangePassphrase tests that we're able to generate a cipher seed, then +// change the password. If we attempt to decipher the new enciphered seed, then +// we should get the exact same seed back. +func TestChangePassphrase(t *testing.T) { + t.Parallel() + + // First, we'll generate a new cipher seed with a test passphrase. + pass := []byte("test") + cipherSeed, err := New(0, &testEntropy, time.Now()) + if err != nil { + t.Fatalf("unable to create seed: %v", err) + } + + // Now that we have our cipher seed, we'll encipher it and request a + // mnemonic that we can use to recover later. + mnemonic, err := cipherSeed.ToMnemonic(pass) + if err != nil { + t.Fatalf("unable to create mnemonic: %v", err) + } + + // Now that have the mnemonic, we'll attempt to re-encipher the + // passphrase in order to get a brand new mnemonic. + newPass := []byte("strongerpassyeh!") + newmnemonic, err := mnemonic.ChangePass(pass, newPass) + if err != nil { + t.Fatalf("unable to change passphrase: %v", err) + } + + // We'll now attempt to decipher the new mnemonic using the new + // passphrase to arrive at (what should be) the original cipher seed. + newCipherSeed, err := newmnemonic.ToCipherSeed(newPass) + if err != nil { + t.Fatalf("unable to decipher cipher seed: %v", err) + } + + // Now that we have the cipher seed, we'll verify that the plaintext + // seed matches *identically*. + assertCipherSeedEqual(t, cipherSeed, newCipherSeed) +} + +// TestChangePassphraseWrongPass tests that if we have a valid enciphered +// cipherseed, but then try to change the password with the *wrong* password, +// then we get an error. +func TestChangePassphraseWrongPass(t *testing.T) { + t.Parallel() + + // First, we'll generate a new cipher seed with a test passphrase. + pass := []byte("test") + cipherSeed, err := New(0, &testEntropy, time.Now()) + if err != nil { + t.Fatalf("unable to create seed: %v", err) + } + + // Now that we have our cipher seed, we'll encipher it and request a + // mnemonic that we can use to recover later. + mnemonic, err := cipherSeed.ToMnemonic(pass) + if err != nil { + t.Fatalf("unable to create mnemonic: %v", err) + } + + // 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 + // using the *wrong* passphrase. This should result in an + // ErrInvalidPass error. + wrongPass := []byte("kek") + newPass := []byte("strongerpassyeh!") + _, err = mnemonic.ChangePass(wrongPass, newPass) + if err != ErrInvalidPass { + t.Fatalf("expected ErrInvalidPass, instead got %v", err) + } +} + +// TestMnemonicEncoding uses quickcheck like property based testing to ensure +// that we're always able to fully recover the original byte stream encoded +// into the mnemonic phrase. +func TestMnemonicEncoding(t *testing.T) { + t.Parallel() + + // mainScenario is the main driver of our property based test. We'll + // ensure that given a random byte string of length 33 bytes, if we + // convert that to the mnemonic, then we should be able to reverse the + // conversion. + mainScenario := func(cipherSeedBytes [EncipheredCipherSeedSize]byte) bool { + mnemonic, err := cipherTextToMnemonic(cipherSeedBytes) + if err != nil { + t.Fatalf("unable to map cipher text: %v", err) + return false + } + + newCipher := mnemonicToCipherText(&mnemonic) + + if newCipher != cipherSeedBytes { + t.Fatalf("cipherseed doesn't match: expected %v, got %v", + cipherSeedBytes, newCipher) + return false + } + + return true + } + + if err := quick.Check(mainScenario, nil); err != nil { + t.Fatalf("fuzz check failed: %v", err) + } +} + +// 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 +// mnemonic, then back to the original plaintext cipher seed. +func TestEncipherDecipher(t *testing.T) { + t.Parallel() + + // mainScenario is the main driver of our property based test. We'll + // ensure that given a random seed tuple (internal version, entropy, + // and birthday) we're able to convert that to a valid cipher seed. + // Additionally, we should be able to decipher the final mnemonic, and + // recover the original cipherseed. + mainScenario := func(version uint8, entropy [EntropySize]byte, + nowInt int64, pass [20]byte) bool { + + now := time.Unix(nowInt, 0) + + cipherSeed, err := New(version, &entropy, now) + if err != nil { + t.Fatalf("unable to map cipher text: %v", err) + return false + } + + mnemonic, err := cipherSeed.ToMnemonic(pass[:]) + if err != nil { + t.Fatalf("unable to generate mnemonic: %v", err) + return false + } + + cipherSeed2, err := mnemonic.ToCipherSeed(pass[:]) + if err != nil { + t.Fatalf("unable to decrypt cipher seed: %v", err) + return false + } + + if cipherSeed.InternalVersion != cipherSeed2.InternalVersion { + t.Fatalf("mismatched versions: expected %v, got %v", + cipherSeed.InternalVersion, cipherSeed2.InternalVersion) + return false + } + if cipherSeed.Birthday != cipherSeed2.Birthday { + t.Fatalf("mismatched birthday: expected %v, got %v", + cipherSeed.Birthday, cipherSeed2.Birthday) + return false + } + if cipherSeed.Entropy != cipherSeed2.Entropy { + t.Fatalf("mismatched versions: expected %x, got %x", + cipherSeed.Entropy[:], cipherSeed2.Entropy[:]) + return false + } + + return true + } + + if err := quick.Check(mainScenario, nil); err != nil { + t.Fatalf("fuzz check failed: %v", err) + } +} + +// TestSeedEncodeDecode tests that we're able to reverse the encoding of an +// arbitrary raw seed. +func TestSeedEncodeDecode(t *testing.T) { + // 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 + // it precisely. + mainScenario := func(version uint8, nowInt int64, + entropy [EntropySize]byte) bool { + + now := time.Unix(nowInt, 0) + seed := CipherSeed{ + InternalVersion: version, + Birthday: uint16(now.Sub(bitcoinGenesisDate) / (time.Hour * 24)), + Entropy: entropy, + } + + var b bytes.Buffer + if err := seed.encode(&b); err != nil { + t.Fatalf("unable to encode: %v", err) + return false + } + + var newSeed CipherSeed + if err := newSeed.decode(&b); err != nil { + t.Fatalf("unable to decode: %v", err) + return false + } + + if seed.InternalVersion != newSeed.InternalVersion { + t.Fatalf("mismatched versions: expected %v, got %v", + seed.InternalVersion, newSeed.InternalVersion) + return false + } + if seed.Birthday != newSeed.Birthday { + t.Fatalf("mismatched birthday: expected %v, got %v", + seed.Birthday, newSeed.Birthday) + return false + } + if seed.Entropy != newSeed.Entropy { + t.Fatalf("mismatched versions: expected %x, got %x", + seed.Entropy[:], newSeed.Entropy[:]) + return false + } + + return true + } + + if err := quick.Check(mainScenario, nil); err != nil { + t.Fatalf("fuzz check failed: %v", err) + } +} + +// TestDecipherUnknownMnenomicWord tests that if we obtain a mnemonic, the +// 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. +func TestDecipherUnknownMnenomicWord(t *testing.T) { + t.Parallel() + + // First, we'll create a new cipher seed with "test" ass a password. + pass := []byte("test") + cipherSeed, err := New(0, &testEntropy, time.Now()) + if err != nil { + t.Fatalf("unable to create seed: %v", err) + } + + // Now that we have our cipher seed, we'll encipher it and request a + // mnemonic that we can use to recover later. + mnemonic, err := cipherSeed.ToMnemonic(pass) + if err != nil { + t.Fatalf("unable to create mnemonic: %v", err) + } + + // 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. + randIndex := rand.Int31n(int32(len(mnemonic))) + mnemonic[randIndex] = "kek" + + // 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") + } + + wordErr, ok := err.(ErrUnknownMnenomicWord) + if !ok { + t.Fatalf("expected ErrUnknownMnenomicWord instead got %T", err) + } + + if wordErr.word != "kek" { + t.Fatalf("word mismatch: expected %v, got %v", "kek", wordErr.word) + } +} + +// TestDecipherIncorrectMnemonic tests that if we obtain a cipherseed, but then +// swap out words, then checksum fails. +func TestDecipherIncorrectMnemonic(t *testing.T) { + // First, we'll create a new cipher seed with "test" ass a password. + pass := []byte("test") + cipherSeed, err := New(0, &testEntropy, time.Now()) + if err != nil { + t.Fatalf("unable to create seed: %v", err) + } + + // Now that we have our cipher seed, we'll encipher it and request a + // mnemonic that we can use to recover later. + mnemonic, err := cipherSeed.ToMnemonic(pass) + if err != nil { + t.Fatalf("unable to create mnemonic: %v", err) + } + + // We'll now swap out two words from the mnemonic, which should trigger + // a checksum failure. + swapIndex1 := 9 + swapIndex2 := 13 + mnemonic[swapIndex1], mnemonic[swapIndex2] = mnemonic[swapIndex2], mnemonic[swapIndex1] + + // 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 + // should get ErrUnknownMnenomicWord. + _, err = mnemonic.ToCipherSeed(pass) + if err != ErrIncorrectMnemonic { + t.Fatalf("expected ErrIncorrectMnemonic error") + } + +} + +// TODO(roasbeef): add test failure checksum fail is modified, new error + +func init() { + // For the purposes of our test, we'll crank down the scrypt params a + // bit. + scryptN = 16 + scryptR = 8 + scryptP = 1 +} diff --git a/aezeed/errors.go b/aezeed/errors.go new file mode 100644 index 00000000..9b1574ac --- /dev/null +++ b/aezeed/errors.go @@ -0,0 +1,30 @@ +package aezeed + +import "fmt" + +var ( + // ErrIncorrectVersion is returned if a seed bares a mismatched + // external version to that of the package executing the aezeed scheme. + ErrIncorrectVersion = fmt.Errorf("wrong seed version") + + // ErrInvalidPass is returned if the user enters an invalid passphrase + // for a particular enciphered mnemonic. + ErrInvalidPass = fmt.Errorf("invalid passphrase") + + // ErrIncorrectMnemonic is returned if we detect that the checksum of + // the specified mnemonic doesn't match. This indicates the user input + // the wrong mnemonic. + ErrIncorrectMnemonic = fmt.Errorf("mnemonic phrase checksum doesn't" + + "match") +) + +// ErrUnknownMnenomicWord is returned when attempting to decipher and +// enciphered mnemonic, but a word encountered isn't a member of our word list. +type ErrUnknownMnenomicWord struct { + word string +} + +// Error returns a human readable string describing the error. +func (e ErrUnknownMnenomicWord) Error() string { + return fmt.Sprintf("word %v isn't a part of default word list", e.word) +} diff --git a/aezeed/wordlist.go b/aezeed/wordlist.go new file mode 100644 index 00000000..09b53e55 --- /dev/null +++ b/aezeed/wordlist.go @@ -0,0 +1,2073 @@ +package aezeed + +import ( + "strings" +) + +var ( + // reverseWordMap maps a word to its position within the default word list. + reverseWordMap map[string]int +) + +func init() { + reverseWordMap = make(map[string]int) + for i, v := range defaultWordList { + reverseWordMap[v] = i + } +} + +// defaultWordList is a slice of the current default word list that's used to +// encode the enciphered seed into a human readable set of words. +var defaultWordList = strings.Split(englishWordList, "\n") + +// englishWordList is an English wordlist that's used as part of version 0 of +// the cipherseed scheme. This is the *same* word list that's recommend for use +// with BIP0039. +var englishWordList = `abandon +ability +able +about +above +absent +absorb +abstract +absurd +abuse +access +accident +account +accuse +achieve +acid +acoustic +acquire +across +act +action +actor +actress +actual +adapt +add +addict +address +adjust +admit +adult +advance +advice +aerobic +affair +afford +afraid +again +age +agent +agree +ahead +aim +air +airport +aisle +alarm +album +alcohol +alert +alien +all +alley +allow +almost +alone +alpha +already +also +alter +always +amateur +amazing +among +amount +amused +analyst +anchor +ancient +anger +angle +angry +animal +ankle +announce +annual +another +answer +antenna +antique +anxiety +any +apart +apology +appear +apple +approve +april +arch +arctic +area +arena +argue +arm +armed +armor +army +around +arrange +arrest +arrive +arrow +art +artefact +artist +artwork +ask +aspect +assault +asset +assist +assume +asthma +athlete +atom +attack +attend +attitude +attract +auction +audit +august +aunt +author +auto +autumn +average +avocado +avoid +awake +aware +away +awesome +awful +awkward +axis +baby +bachelor +bacon +badge +bag +balance +balcony +ball +bamboo +banana +banner +bar +barely +bargain +barrel +base +basic +basket +battle +beach +bean +beauty +because +become +beef +before +begin +behave +behind +believe +below +belt +bench +benefit +best +betray +better +between +beyond +bicycle +bid +bike +bind +biology +bird +birth +bitter +black +blade +blame +blanket +blast +bleak +bless +blind +blood +blossom +blouse +blue +blur +blush +board +boat +body +boil +bomb +bone +bonus +book +boost +border +boring +borrow +boss +bottom +bounce +box +boy +bracket +brain +brand +brass +brave +bread +breeze +brick +bridge +brief +bright +bring +brisk +broccoli +broken +bronze +broom +brother +brown +brush +bubble +buddy +budget +buffalo +build +bulb +bulk +bullet +bundle +bunker +burden +burger +burst +bus +business +busy +butter +buyer +buzz +cabbage +cabin +cable +cactus +cage +cake +call +calm +camera +camp +can +canal +cancel +candy +cannon +canoe +canvas +canyon +capable +capital +captain +car +carbon +card +cargo +carpet +carry +cart +case +cash +casino +castle +casual +cat +catalog +catch +category +cattle +caught +cause +caution +cave +ceiling +celery +cement +census +century +cereal +certain +chair +chalk +champion +change +chaos +chapter +charge +chase +chat +cheap +check +cheese +chef +cherry +chest +chicken +chief +child +chimney +choice +choose +chronic +chuckle +chunk +churn +cigar +cinnamon +circle +citizen +city +civil +claim +clap +clarify +claw +clay +clean +clerk +clever +click +client +cliff +climb +clinic +clip +clock +clog +close +cloth +cloud +clown +club +clump +cluster +clutch +coach +coast +coconut +code +coffee +coil +coin +collect +color +column +combine +come +comfort +comic +common +company +concert +conduct +confirm +congress +connect +consider +control +convince +cook +cool +copper +copy +coral +core +corn +correct +cost +cotton +couch +country +couple +course +cousin +cover +coyote +crack +cradle +craft +cram +crane +crash +crater +crawl +crazy +cream +credit +creek +crew +cricket +crime +crisp +critic +crop +cross +crouch +crowd +crucial +cruel +cruise +crumble +crunch +crush +cry +crystal +cube +culture +cup +cupboard +curious +current +curtain +curve +cushion +custom +cute +cycle +dad +damage +damp +dance +danger +daring +dash +daughter +dawn +day +deal +debate +debris +decade +december +decide +decline +decorate +decrease +deer +defense +define +defy +degree +delay +deliver +demand +demise +denial +dentist +deny +depart +depend +deposit +depth +deputy +derive +describe +desert +design +desk +despair +destroy +detail +detect +develop +device +devote +diagram +dial +diamond +diary +dice +diesel +diet +differ +digital +dignity +dilemma +dinner +dinosaur +direct +dirt +disagree +discover +disease +dish +dismiss +disorder +display +distance +divert +divide +divorce +dizzy +doctor +document +dog +doll +dolphin +domain +donate +donkey +donor +door +dose +double +dove +draft +dragon +drama +drastic +draw +dream +dress +drift +drill +drink +drip +drive +drop +drum +dry +duck +dumb +dune +during +dust +dutch +duty +dwarf +dynamic +eager +eagle +early +earn +earth +easily +east +easy +echo +ecology +economy +edge +edit +educate +effort +egg +eight +either +elbow +elder +electric +elegant +element +elephant +elevator +elite +else +embark +embody +embrace +emerge +emotion +employ +empower +empty +enable +enact +end +endless +endorse +enemy +energy +enforce +engage +engine +enhance +enjoy +enlist +enough +enrich +enroll +ensure +enter +entire +entry +envelope +episode +equal +equip +era +erase +erode +erosion +error +erupt +escape +essay +essence +estate +eternal +ethics +evidence +evil +evoke +evolve +exact +example +excess +exchange +excite +exclude +excuse +execute +exercise +exhaust +exhibit +exile +exist +exit +exotic +expand +expect +expire +explain +expose +express +extend +extra +eye +eyebrow +fabric +face +faculty +fade +faint +faith +fall +false +fame +family +famous +fan +fancy +fantasy +farm +fashion +fat +fatal +father +fatigue +fault +favorite +feature +february +federal +fee +feed +feel +female +fence +festival +fetch +fever +few +fiber +fiction +field +figure +file +film +filter +final +find +fine +finger +finish +fire +firm +first +fiscal +fish +fit +fitness +fix +flag +flame +flash +flat +flavor +flee +flight +flip +float +flock +floor +flower +fluid +flush +fly +foam +focus +fog +foil +fold +follow +food +foot +force +forest +forget +fork +fortune +forum +forward +fossil +foster +found +fox +fragile +frame +frequent +fresh +friend +fringe +frog +front +frost +frown +frozen +fruit +fuel +fun +funny +furnace +fury +future +gadget +gain +galaxy +gallery +game +gap +garage +garbage +garden +garlic +garment +gas +gasp +gate +gather +gauge +gaze +general +genius +genre +gentle +genuine +gesture +ghost +giant +gift +giggle +ginger +giraffe +girl +give +glad +glance +glare +glass +glide +glimpse +globe +gloom +glory +glove +glow +glue +goat +goddess +gold +good +goose +gorilla +gospel +gossip +govern +gown +grab +grace +grain +grant +grape +grass +gravity +great +green +grid +grief +grit +grocery +group +grow +grunt +guard +guess +guide +guilt +guitar +gun +gym +habit +hair +half +hammer +hamster +hand +happy +harbor +hard +harsh +harvest +hat +have +hawk +hazard +head +health +heart +heavy +hedgehog +height +hello +helmet +help +hen +hero +hidden +high +hill +hint +hip +hire +history +hobby +hockey +hold +hole +holiday +hollow +home +honey +hood +hope +horn +horror +horse +hospital +host +hotel +hour +hover +hub +huge +human +humble +humor +hundred +hungry +hunt +hurdle +hurry +hurt +husband +hybrid +ice +icon +idea +identify +idle +ignore +ill +illegal +illness +image +imitate +immense +immune +impact +impose +improve +impulse +inch +include +income +increase +index +indicate +indoor +industry +infant +inflict +inform +inhale +inherit +initial +inject +injury +inmate +inner +innocent +input +inquiry +insane +insect +inside +inspire +install +intact +interest +into +invest +invite +involve +iron +island +isolate +issue +item +ivory +jacket +jaguar +jar +jazz +jealous +jeans +jelly +jewel +job +join +joke +journey +joy +judge +juice +jump +jungle +junior +junk +just +kangaroo +keen +keep +ketchup +key +kick +kid +kidney +kind +kingdom +kiss +kit +kitchen +kite +kitten +kiwi +knee +knife +knock +know +lab +label +labor +ladder +lady +lake +lamp +language +laptop +large +later +latin +laugh +laundry +lava +law +lawn +lawsuit +layer +lazy +leader +leaf +learn +leave +lecture +left +leg +legal +legend +leisure +lemon +lend +length +lens +leopard +lesson +letter +level +liar +liberty +library +license +life +lift +light +like +limb +limit +link +lion +liquid +list +little +live +lizard +load +loan +lobster +local +lock +logic +lonely +long +loop +lottery +loud +lounge +love +loyal +lucky +luggage +lumber +lunar +lunch +luxury +lyrics +machine +mad +magic +magnet +maid +mail +main +major +make +mammal +man +manage +mandate +mango +mansion +manual +maple +marble +march +margin +marine +market +marriage +mask +mass +master +match +material +math +matrix +matter +maximum +maze +meadow +mean +measure +meat +mechanic +medal +media +melody +melt +member +memory +mention +menu +mercy +merge +merit +merry +mesh +message +metal +method +middle +midnight +milk +million +mimic +mind +minimum +minor +minute +miracle +mirror +misery +miss +mistake +mix +mixed +mixture +mobile +model +modify +mom +moment +monitor +monkey +monster +month +moon +moral +more +morning +mosquito +mother +motion +motor +mountain +mouse +move +movie +much +muffin +mule +multiply +muscle +museum +mushroom +music +must +mutual +myself +mystery +myth +naive +name +napkin +narrow +nasty +nation +nature +near +neck +need +negative +neglect +neither +nephew +nerve +nest +net +network +neutral +never +news +next +nice +night +noble +noise +nominee +noodle +normal +north +nose +notable +note +nothing +notice +novel +now +nuclear +number +nurse +nut +oak +obey +object +oblige +obscure +observe +obtain +obvious +occur +ocean +october +odor +off +offer +office +often +oil +okay +old +olive +olympic +omit +once +one +onion +online +only +open +opera +opinion +oppose +option +orange +orbit +orchard +order +ordinary +organ +orient +original +orphan +ostrich +other +outdoor +outer +output +outside +oval +oven +over +own +owner +oxygen +oyster +ozone +pact +paddle +page +pair +palace +palm +panda +panel +panic +panther +paper +parade +parent +park +parrot +party +pass +patch +path +patient +patrol +pattern +pause +pave +payment +peace +peanut +pear +peasant +pelican +pen +penalty +pencil +people +pepper +perfect +permit +person +pet +phone +photo +phrase +physical +piano +picnic +picture +piece +pig +pigeon +pill +pilot +pink +pioneer +pipe +pistol +pitch +pizza +place +planet +plastic +plate +play +please +pledge +pluck +plug +plunge +poem +poet +point +polar +pole +police +pond +pony +pool +popular +portion +position +possible +post +potato +pottery +poverty +powder +power +practice +praise +predict +prefer +prepare +present +pretty +prevent +price +pride +primary +print +priority +prison +private +prize +problem +process +produce +profit +program +project +promote +proof +property +prosper +protect +proud +provide +public +pudding +pull +pulp +pulse +pumpkin +punch +pupil +puppy +purchase +purity +purpose +purse +push +put +puzzle +pyramid +quality +quantum +quarter +question +quick +quit +quiz +quote +rabbit +raccoon +race +rack +radar +radio +rail +rain +raise +rally +ramp +ranch +random +range +rapid +rare +rate +rather +raven +raw +razor +ready +real +reason +rebel +rebuild +recall +receive +recipe +record +recycle +reduce +reflect +reform +refuse +region +regret +regular +reject +relax +release +relief +rely +remain +remember +remind +remove +render +renew +rent +reopen +repair +repeat +replace +report +require +rescue +resemble +resist +resource +response +result +retire +retreat +return +reunion +reveal +review +reward +rhythm +rib +ribbon +rice +rich +ride +ridge +rifle +right +rigid +ring +riot +ripple +risk +ritual +rival +river +road +roast +robot +robust +rocket +romance +roof +rookie +room +rose +rotate +rough +round +route +royal +rubber +rude +rug +rule +run +runway +rural +sad +saddle +sadness +safe +sail +salad +salmon +salon +salt +salute +same +sample +sand +satisfy +satoshi +sauce +sausage +save +say +scale +scan +scare +scatter +scene +scheme +school +science +scissors +scorpion +scout +scrap +screen +script +scrub +sea +search +season +seat +second +secret +section +security +seed +seek +segment +select +sell +seminar +senior +sense +sentence +series +service +session +settle +setup +seven +shadow +shaft +shallow +share +shed +shell +sheriff +shield +shift +shine +ship +shiver +shock +shoe +shoot +shop +short +shoulder +shove +shrimp +shrug +shuffle +shy +sibling +sick +side +siege +sight +sign +silent +silk +silly +silver +similar +simple +since +sing +siren +sister +situate +six +size +skate +sketch +ski +skill +skin +skirt +skull +slab +slam +sleep +slender +slice +slide +slight +slim +slogan +slot +slow +slush +small +smart +smile +smoke +smooth +snack +snake +snap +sniff +snow +soap +soccer +social +sock +soda +soft +solar +soldier +solid +solution +solve +someone +song +soon +sorry +sort +soul +sound +soup +source +south +space +spare +spatial +spawn +speak +special +speed +spell +spend +sphere +spice +spider +spike +spin +spirit +split +spoil +sponsor +spoon +sport +spot +spray +spread +spring +spy +square +squeeze +squirrel +stable +stadium +staff +stage +stairs +stamp +stand +start +state +stay +steak +steel +stem +step +stereo +stick +still +sting +stock +stomach +stone +stool +story +stove +strategy +street +strike +strong +struggle +student +stuff +stumble +style +subject +submit +subway +success +such +sudden +suffer +sugar +suggest +suit +summer +sun +sunny +sunset +super +supply +supreme +sure +surface +surge +surprise +surround +survey +suspect +sustain +swallow +swamp +swap +swarm +swear +sweet +swift +swim +swing +switch +sword +symbol +symptom +syrup +system +table +tackle +tag +tail +talent +talk +tank +tape +target +task +taste +tattoo +taxi +teach +team +tell +ten +tenant +tennis +tent +term +test +text +thank +that +theme +then +theory +there +they +thing +this +thought +three +thrive +throw +thumb +thunder +ticket +tide +tiger +tilt +timber +time +tiny +tip +tired +tissue +title +toast +tobacco +today +toddler +toe +together +toilet +token +tomato +tomorrow +tone +tongue +tonight +tool +tooth +top +topic +topple +torch +tornado +tortoise +toss +total +tourist +toward +tower +town +toy +track +trade +traffic +tragic +train +transfer +trap +trash +travel +tray +treat +tree +trend +trial +tribe +trick +trigger +trim +trip +trophy +trouble +truck +true +truly +trumpet +trust +truth +try +tube +tuition +tumble +tuna +tunnel +turkey +turn +turtle +twelve +twenty +twice +twin +twist +two +type +typical +ugly +umbrella +unable +unaware +uncle +uncover +under +undo +unfair +unfold +unhappy +uniform +unique +unit +universe +unknown +unlock +until +unusual +unveil +update +upgrade +uphold +upon +upper +upset +urban +urge +usage +use +used +useful +useless +usual +utility +vacant +vacuum +vague +valid +valley +valve +van +vanish +vapor +various +vast +vault +vehicle +velvet +vendor +venture +venue +verb +verify +version +very +vessel +veteran +viable +vibrant +vicious +victory +video +view +village +vintage +violin +virtual +virus +visa +visit +visual +vital +vivid +vocal +voice +void +volcano +volume +vote +voyage +wage +wagon +wait +walk +wall +walnut +want +warfare +warm +warrior +wash +wasp +waste +water +wave +way +wealth +weapon +wear +weasel +weather +web +wedding +weekend +weird +welcome +west +wet +whale +what +wheat +wheel +when +where +whip +whisper +wide +width +wife +wild +will +win +window +wine +wing +wink +winner +winter +wire +wisdom +wise +wish +witness +wolf +woman +wonder +wood +wool +word +work +world +worry +worth +wrap +wreck +wrestle +wrist +write +wrong +yard +year +yellow +you +young +youth +zebra +zero +zone +zoo` diff --git a/glide.lock b/glide.lock index 446f9301..fb4dbd52 100644 --- a/glide.lock +++ b/glide.lock @@ -1,6 +1,13 @@ -hash: b7b9aed5daf9b2fdc4083d310ab2cabfc86e08db6796b9301b2e2d73c9bd6174 -updated: 2018-02-23T15:57:52.662879082-08:00 +hash: 31c7557ef187f50de28557359e5179a47d5f4f153ec9b4f1ad264f771e7d1b5c +updated: 2018-03-01T16:45:01.924542733-08:00 imports: +- name: git.schwanenlied.me/yawning/bsaes.git + version: e06297f34865a50b8e473105e52cb64ad1b55da8 + subpackages: + - ct32 + - ct64 + - ghash + - internal/modes - name: github.com/aead/chacha20 version: d31a916ded42d1640b9d89a26f8abd53cc96790c subpackages: @@ -140,9 +147,12 @@ imports: version: 501572607d0273fc75b3b261fa4904d63f6ffa0e - name: github.com/urfave/cli version: cfb38830724cc34fedffe9a2a29fb54fa9169cd1 +- name: github.com/Yawning/aez + version: 4dad034d9db2caec23fb8f69b9160ae16f8d46a3 - name: golang.org/x/crypto version: 49796115aa4b964c318aad4f3084fdb41e9aa067 subpackages: + - blake2b - chacha20poly1305 - curve25519 - ed25519 diff --git a/glide.yaml b/glide.yaml index 6e6fe6e3..a8b87cd7 100644 --- a/glide.yaml +++ b/glide.yaml @@ -80,3 +80,7 @@ import: - package: github.com/rogpeppe/fastuuid - package: gopkg.in/errgo.v1 - package: github.com/miekg/dns +- package: github.com/Yawning/aez + version: 4dad034d9db2caec23fb8f69b9160ae16f8d46a3 +- package: github.com/kkdai/bstream + version: f391b8402d23024e7c0f624b31267a89998fca95