You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

162 lines
4.0 KiB

  1. package keys
  2. import (
  3. "io/ioutil"
  4. "math/big"
  5. "os"
  6. "strings"
  7. "github.com/pkg/errors"
  8. )
  9. const BankSize = 2048
  10. // TODO: add error-checking codecs for invalid phrases
  11. type Codec interface {
  12. BytesToWords([]byte) ([]string, error)
  13. WordsToBytes([]string) ([]byte, error)
  14. }
  15. type WordCodec struct {
  16. words []string
  17. bytes map[string]int
  18. }
  19. var _ Codec = WordCodec{}
  20. func NewCodec(words []string) (codec WordCodec, err error) {
  21. if len(words) != BankSize {
  22. return codec, errors.Errorf("Bank must have %d words, found %d", BankSize, len(words))
  23. }
  24. return WordCodec{words: words}, nil
  25. }
  26. func LoadCodec(bank string) (codec WordCodec, err error) {
  27. words, err := loadBank(bank)
  28. if err != nil {
  29. return codec, err
  30. }
  31. return NewCodec(words)
  32. }
  33. // loadBank opens a wordlist file and returns all words inside
  34. func loadBank(bank string) ([]string, error) {
  35. filename := "wordlist/" + bank + ".txt"
  36. words, err := getData(filename)
  37. if err != nil {
  38. return nil, err
  39. }
  40. wordsAll := strings.Split(strings.TrimSpace(words), "\n")
  41. return wordsAll, nil
  42. }
  43. // TODO: read from go-bind assets
  44. func getData(filename string) (string, error) {
  45. f, err := os.Open(filename)
  46. if err != nil {
  47. return "", errors.WithStack(err)
  48. }
  49. defer f.Close()
  50. data, err := ioutil.ReadAll(f)
  51. if err != nil {
  52. return "", errors.WithStack(err)
  53. }
  54. return string(data), nil
  55. }
  56. // given this many bytes, we will produce this many words
  57. func wordlenFromBytes(numBytes int) int {
  58. // 8 bits per byte, and we add +10 so it rounds up
  59. return (8*numBytes + 10) / 11
  60. }
  61. // given this many words, we will produce this many bytes.
  62. // sometimes there are two possibilities.
  63. // if maybeShorter is true, then represents len OR len-1 bytes
  64. func bytelenFromWords(numWords int) (length int, maybeShorter bool) {
  65. // calculate the max number of complete bytes we could store in this word
  66. length = 11 * numWords / 8
  67. // if one less byte would also generate this length, set maybeShorter
  68. if wordlenFromBytes(length-1) == numWords {
  69. maybeShorter = true
  70. }
  71. return
  72. }
  73. // TODO: add checksum
  74. func (c WordCodec) BytesToWords(data []byte) (words []string, err error) {
  75. // 2048 words per bank, which is 2^11.
  76. numWords := wordlenFromBytes(len(data))
  77. n2048 := big.NewInt(2048)
  78. nData := big.NewInt(0).SetBytes(data)
  79. nRem := big.NewInt(0)
  80. // Alternative, use condition "nData.BitLen() > 0"
  81. // to allow for shorter words when data has leading 0's
  82. for i := 0; i < numWords; i++ {
  83. nData.DivMod(nData, n2048, nRem)
  84. rem := nRem.Int64()
  85. words = append(words, c.words[rem])
  86. }
  87. return words, nil
  88. }
  89. func (c WordCodec) WordsToBytes(words []string) ([]byte, error) {
  90. // // 2048 words per bank, which is 2^11.
  91. // numWords := (8*len(dest) + 10) / 11
  92. // if numWords != len(words) {
  93. // return errors.New(Fmt("Expected %v words for %v dest bytes", numWords, len(dest)))
  94. // }
  95. l := len(words)
  96. n2048 := big.NewInt(2048)
  97. nData := big.NewInt(0)
  98. // since we output words based on the remainder, the first word has the lowest
  99. // value... we must load them in reverse order
  100. for i := 1; i <= l; i++ {
  101. rem, err := c.GetIndex(words[l-i])
  102. if err != nil {
  103. return nil, err
  104. }
  105. nRem := big.NewInt(int64(rem))
  106. nData.Mul(nData, n2048)
  107. nData.Add(nData, nRem)
  108. }
  109. // we copy into a slice of the expected size, so it is not shorter if there
  110. // are lots of leading 0s
  111. dataBytes := nData.Bytes()
  112. outLen, _ := bytelenFromWords(len(words))
  113. output := make([]byte, outLen)
  114. copy(output[outLen-len(dataBytes):], dataBytes)
  115. return output, nil
  116. }
  117. // GetIndex finds the index of the words to create bytes
  118. // Generates a map the first time it is loaded, to avoid needless
  119. // computation when list is not used.
  120. func (c WordCodec) GetIndex(word string) (int, error) {
  121. // generate the first time
  122. if c.bytes == nil {
  123. b := map[string]int{}
  124. for i, w := range c.words {
  125. if _, ok := b[w]; ok {
  126. return -1, errors.Errorf("Duplicate word in list: %s", w)
  127. }
  128. b[w] = i
  129. }
  130. c.bytes = b
  131. }
  132. // get the index, or an error
  133. rem, ok := c.bytes[word]
  134. if !ok {
  135. return -1, errors.Errorf("Unrecognized word: %s", word)
  136. }
  137. return rem, nil
  138. }