7 "github.com/pkg/errors"
9 "github.com/tendermint/go-crypto/keys/wordlist"
14 // TODO: add error-checking codecs for invalid phrases
16 type Codec interface {
17 BytesToWords([]byte) ([]string, error)
18 WordsToBytes([]string) ([]byte, error)
21 type WordCodec struct {
27 var _ Codec = &WordCodec{}
29 func NewCodec(words []string) (codec *WordCodec, err error) {
30 if len(words) != BankSize {
31 return codec, errors.Errorf("Bank must have %d words, found %d", BankSize, len(words))
36 // TODO: configure this outside???
37 check: NewIEEECRC32(),
38 // check: NewIBMCRC16(),
44 // LoadCodec loads a pre-compiled language file
45 func LoadCodec(bank string) (codec *WordCodec, err error) {
46 words, err := loadBank(bank)
50 return NewCodec(words)
53 // MustLoadCodec panics if word bank is missing, only for tests
54 func MustLoadCodec(bank string) *WordCodec {
55 codec, err := LoadCodec(bank)
62 // loadBank opens a wordlist file and returns all words inside
63 func loadBank(bank string) ([]string, error) {
64 filename := "keys/wordlist/" + bank + ".txt"
65 words, err := wordlist.Asset(filename)
69 wordsAll := strings.Split(strings.TrimSpace(string(words)), "\n")
73 // // TODO: read from go-bind assets
74 // func getData(filename string) (string, error) {
75 // f, err := os.Open(filename)
77 // return "", errors.WithStack(err)
81 // data, err := ioutil.ReadAll(f)
83 // return "", errors.WithStack(err)
86 // return string(data), nil
89 // given this many bytes, we will produce this many words
90 func wordlenFromBytes(numBytes int) int {
91 // 2048 words per bank, which is 2^11.
92 // 8 bits per byte, and we add +10 so it rounds up
93 return (8*numBytes + 10) / 11
96 // given this many words, we will produce this many bytes.
97 // sometimes there are two possibilities.
98 // if maybeShorter is true, then represents len OR len-1 bytes
99 func bytelenFromWords(numWords int) (length int, maybeShorter bool) {
100 // calculate the max number of complete bytes we could store in this word
101 length = 11 * numWords / 8
102 // if one less byte would also generate this length, set maybeShorter
103 if wordlenFromBytes(length-1) == numWords {
109 // TODO: add checksum
110 func (c *WordCodec) BytesToWords(raw []byte) (words []string, err error) {
111 // always add a checksum to the data
112 data := c.check.AddECC(raw)
113 numWords := wordlenFromBytes(len(data))
115 n2048 := big.NewInt(2048)
116 nData := big.NewInt(0).SetBytes(data)
117 nRem := big.NewInt(0)
118 // Alternative, use condition "nData.BitLen() > 0"
119 // to allow for shorter words when data has leading 0's
120 for i := 0; i < numWords; i++ {
121 nData.DivMod(nData, n2048, nRem)
124 // double-check bank on generation...
125 _, err := c.GetIndex(w)
129 words = append(words, w)
134 func (c *WordCodec) WordsToBytes(words []string) ([]byte, error) {
138 return nil, errors.New("Didn't provide any words")
141 n2048 := big.NewInt(2048)
142 nData := big.NewInt(0)
143 // since we output words based on the remainder, the first word has the lowest
144 // value... we must load them in reverse order
145 for i := 1; i <= l; i++ {
146 rem, err := c.GetIndex(words[l-i])
150 nRem := big.NewInt(int64(rem))
151 nData.Mul(nData, n2048)
152 nData.Add(nData, nRem)
155 // we copy into a slice of the expected size, so it is not shorter if there
156 // are lots of leading 0s
157 dataBytes := nData.Bytes()
159 // copy into the container we have with the expected size
160 outLen, flex := bytelenFromWords(len(words))
161 toCheck := make([]byte, outLen)
162 if len(dataBytes) > outLen {
163 return nil, errors.New("Invalid data, could not have been generated by this codec")
165 copy(toCheck[outLen-len(dataBytes):], dataBytes)
167 // validate the checksum...
168 output, err := c.check.CheckECC(toCheck)
169 if flex && err != nil {
170 // if flex, try again one shorter....
171 toCheck = toCheck[1:]
172 output, err = c.check.CheckECC(toCheck)
178 // GetIndex finds the index of the words to create bytes
179 // Generates a map the first time it is loaded, to avoid needless
180 // computation when list is not used.
181 func (c *WordCodec) GetIndex(word string) (int, error) {
182 // generate the first time
184 b := map[string]int{}
185 for i, w := range c.words {
186 if _, ok := b[w]; ok {
187 return -1, errors.Errorf("Duplicate word in list: %s", w)
194 // get the index, or an error
195 rem, ok := c.bytes[word]
197 return -1, errors.Errorf("Unrecognized word: %s", word)