OSDN Git Service

new repo
[bytom/vapor.git] / vendor / github.com / tendermint / go-crypto / keys / wordcodec.go
1 package keys
2
3 import (
4         "math/big"
5         "strings"
6
7         "github.com/pkg/errors"
8
9         "github.com/tendermint/go-crypto/keys/wordlist"
10 )
11
12 const BankSize = 2048
13
14 // TODO: add error-checking codecs for invalid phrases
15
16 type Codec interface {
17         BytesToWords([]byte) ([]string, error)
18         WordsToBytes([]string) ([]byte, error)
19 }
20
21 type WordCodec struct {
22         words []string
23         bytes map[string]int
24         check ECC
25 }
26
27 var _ Codec = &WordCodec{}
28
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))
32         }
33
34         res := &WordCodec{
35                 words: words,
36                 // TODO: configure this outside???
37                 check: NewIEEECRC32(),
38                 // check: NewIBMCRC16(),
39         }
40
41         return res, nil
42 }
43
44 // LoadCodec loads a pre-compiled language file
45 func LoadCodec(bank string) (codec *WordCodec, err error) {
46         words, err := loadBank(bank)
47         if err != nil {
48                 return codec, err
49         }
50         return NewCodec(words)
51 }
52
53 // MustLoadCodec panics if word bank is missing, only for tests
54 func MustLoadCodec(bank string) *WordCodec {
55         codec, err := LoadCodec(bank)
56         if err != nil {
57                 panic(err)
58         }
59         return codec
60 }
61
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)
66         if err != nil {
67                 return nil, err
68         }
69         wordsAll := strings.Split(strings.TrimSpace(string(words)), "\n")
70         return wordsAll, nil
71 }
72
73 // // TODO: read from go-bind assets
74 // func getData(filename string) (string, error) {
75 //      f, err := os.Open(filename)
76 //      if err != nil {
77 //              return "", errors.WithStack(err)
78 //      }
79 //      defer f.Close()
80
81 //      data, err := ioutil.ReadAll(f)
82 //      if err != nil {
83 //              return "", errors.WithStack(err)
84 //      }
85
86 //      return string(data), nil
87 // }
88
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
94 }
95
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 {
104                 maybeShorter = true
105         }
106         return
107 }
108
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))
114
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)
122                 rem := nRem.Int64()
123                 w := c.words[rem]
124                 // double-check bank on generation...
125                 _, err := c.GetIndex(w)
126                 if err != nil {
127                         return nil, err
128                 }
129                 words = append(words, w)
130         }
131         return words, nil
132 }
133
134 func (c *WordCodec) WordsToBytes(words []string) ([]byte, error) {
135         l := len(words)
136
137         if l == 0 {
138                 return nil, errors.New("Didn't provide any words")
139         }
140
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])
147                 if err != nil {
148                         return nil, err
149                 }
150                 nRem := big.NewInt(int64(rem))
151                 nData.Mul(nData, n2048)
152                 nData.Add(nData, nRem)
153         }
154
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()
158
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")
164         }
165         copy(toCheck[outLen-len(dataBytes):], dataBytes)
166
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)
173         }
174
175         return output, err
176 }
177
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
183         if c.bytes == nil {
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)
188                         }
189                         b[w] = i
190                 }
191                 c.bytes = b
192         }
193
194         // get the index, or an error
195         rem, ok := c.bytes[word]
196         if !ok {
197                 return -1, errors.Errorf("Unrecognized word: %s", word)
198         }
199         return rem, nil
200 }