Merge pull request #773 from Roasbeef/aezeed
aezeed: add new package implementing the aezeed cipher seed scheme
This commit is contained in:
commit
1ba399267b
69
aezeed/bench_test.go
Normal file
69
aezeed/bench_test.go
Normal file
@ -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
|
||||||
|
}
|
547
aezeed/cipherseed.go
Normal file
547
aezeed/cipherseed.go
Normal file
@ -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)
|
||||||
|
}
|
513
aezeed/cipherseeed_test.go
Normal file
513
aezeed/cipherseeed_test.go
Normal file
@ -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
|
||||||
|
}
|
30
aezeed/errors.go
Normal file
30
aezeed/errors.go
Normal file
@ -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)
|
||||||
|
}
|
2073
aezeed/wordlist.go
Normal file
2073
aezeed/wordlist.go
Normal file
@ -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`
|
14
glide.lock
generated
14
glide.lock
generated
@ -1,6 +1,13 @@
|
|||||||
hash: b7b9aed5daf9b2fdc4083d310ab2cabfc86e08db6796b9301b2e2d73c9bd6174
|
hash: 31c7557ef187f50de28557359e5179a47d5f4f153ec9b4f1ad264f771e7d1b5c
|
||||||
updated: 2018-02-23T15:57:52.662879082-08:00
|
updated: 2018-03-01T16:45:01.924542733-08:00
|
||||||
imports:
|
imports:
|
||||||
|
- name: git.schwanenlied.me/yawning/bsaes.git
|
||||||
|
version: e06297f34865a50b8e473105e52cb64ad1b55da8
|
||||||
|
subpackages:
|
||||||
|
- ct32
|
||||||
|
- ct64
|
||||||
|
- ghash
|
||||||
|
- internal/modes
|
||||||
- name: github.com/aead/chacha20
|
- name: github.com/aead/chacha20
|
||||||
version: d31a916ded42d1640b9d89a26f8abd53cc96790c
|
version: d31a916ded42d1640b9d89a26f8abd53cc96790c
|
||||||
subpackages:
|
subpackages:
|
||||||
@ -140,9 +147,12 @@ imports:
|
|||||||
version: 501572607d0273fc75b3b261fa4904d63f6ffa0e
|
version: 501572607d0273fc75b3b261fa4904d63f6ffa0e
|
||||||
- name: github.com/urfave/cli
|
- name: github.com/urfave/cli
|
||||||
version: cfb38830724cc34fedffe9a2a29fb54fa9169cd1
|
version: cfb38830724cc34fedffe9a2a29fb54fa9169cd1
|
||||||
|
- name: github.com/Yawning/aez
|
||||||
|
version: 4dad034d9db2caec23fb8f69b9160ae16f8d46a3
|
||||||
- name: golang.org/x/crypto
|
- name: golang.org/x/crypto
|
||||||
version: 49796115aa4b964c318aad4f3084fdb41e9aa067
|
version: 49796115aa4b964c318aad4f3084fdb41e9aa067
|
||||||
subpackages:
|
subpackages:
|
||||||
|
- blake2b
|
||||||
- chacha20poly1305
|
- chacha20poly1305
|
||||||
- curve25519
|
- curve25519
|
||||||
- ed25519
|
- ed25519
|
||||||
|
@ -80,3 +80,7 @@ import:
|
|||||||
- package: github.com/rogpeppe/fastuuid
|
- package: github.com/rogpeppe/fastuuid
|
||||||
- package: gopkg.in/errgo.v1
|
- package: gopkg.in/errgo.v1
|
||||||
- package: github.com/miekg/dns
|
- package: github.com/miekg/dns
|
||||||
|
- package: github.com/Yawning/aez
|
||||||
|
version: 4dad034d9db2caec23fb8f69b9160ae16f8d46a3
|
||||||
|
- package: github.com/kkdai/bstream
|
||||||
|
version: f391b8402d23024e7c0f624b31267a89998fca95
|
||||||
|
Loading…
Reference in New Issue
Block a user