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.

271 lines
8.0 KiB

  1. // Copyright 2017-2018 DERO Project. All rights reserved.
  2. // Use of this source code in any form is governed by RESEARCH license.
  3. // license can be found in the LICENSE file.
  4. // GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
  5. //
  6. //
  7. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  8. // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  9. // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  10. // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  11. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  12. // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  13. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  14. // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
  15. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. package mnemonics
  17. import "fmt"
  18. import "strings"
  19. import "encoding/binary"
  20. import "unicode/utf8"
  21. //import "github.com/romana/rlog"
  22. import "hash/crc32"
  23. import "github.com/deroproject/derosuite/crypto"
  24. type Language struct {
  25. Name string // Name of the language
  26. Name_English string // Name of the language in english
  27. Unique_Prefix_Length int // number of utf8 chars (not bytes) to use for checksum
  28. Words []string // 1626 words
  29. }
  30. // any language needs to be added to this array
  31. var Languages = []Language{
  32. Mnemonics_English,
  33. Mnemonics_Japanese,
  34. Mnemonics_Chinese_Simplified,
  35. Mnemonics_Dutch,
  36. Mnemonics_Esperanto,
  37. Mnemonics_Russian,
  38. Mnemonics_Spanish,
  39. Mnemonics_Portuguese,
  40. Mnemonics_French,
  41. Mnemonics_German,
  42. Mnemonics_Italian,
  43. }
  44. const SEED_LENGTH = 24 // checksum seeds are 24 + 1 = 25 words long
  45. // while init check whether all languages have necessary data
  46. func init() {
  47. for i := range Languages {
  48. if len(Languages[i].Words) != 1626 {
  49. panic(fmt.Sprintf("%s language only has %d words, should have 1626", Languages[i].Name_English, len(Languages[i].Words)))
  50. }
  51. }
  52. }
  53. // return all the languages support by us
  54. func Language_List() (list []string) {
  55. for i := range Languages {
  56. list = append(list, Languages[i].Name)
  57. }
  58. return
  59. }
  60. //this function converts a list of words to a key
  61. func Words_To_Key(words_line string) (language_name string, key crypto.Key, err error) {
  62. checksum_present := false
  63. words := strings.Fields(words_line)
  64. //rlog.Tracef(1, "len of words %d", words)
  65. // if seed size is not 24 or 25, return err
  66. if len(words) != SEED_LENGTH && len(words) != (SEED_LENGTH+1) {
  67. err = fmt.Errorf("Invalid Seed")
  68. return
  69. }
  70. // if checksum is present consider it so
  71. if len(words) == (SEED_LENGTH + 1) {
  72. checksum_present = true
  73. }
  74. indices, language_index, wordlist_length, found := Find_indices(words)
  75. if !found {
  76. return language_name, key, fmt.Errorf("Seed not found in any Language")
  77. }
  78. language_name = Languages[language_index].Name
  79. if checksum_present { // we need language unique prefix to validate checksum
  80. if !Verify_Checksum(words, Languages[language_index].Unique_Prefix_Length) {
  81. return language_name, key, fmt.Errorf("Seed Checksum failed")
  82. }
  83. }
  84. // key = make([]byte,(SEED_LENGTH/3)*4,(SEED_LENGTH/3)*4) // our keys are 32 bytes
  85. // map 3 words to 4 bytes each
  86. // so 24 words = 32 bytes
  87. for i := 0; i < (SEED_LENGTH / 3); i++ {
  88. w1 := indices[i*3]
  89. w2 := indices[i*3+1]
  90. w3 := indices[i*3+2]
  91. val := w1 + wordlist_length*(((wordlist_length-w1)+w2)%wordlist_length) +
  92. wordlist_length*wordlist_length*(((wordlist_length-w2)+w3)%wordlist_length)
  93. // sanity check, this can never occur
  94. if (val % wordlist_length) != w1 {
  95. panic("Word list error")
  96. }
  97. value_32bit := uint32(val)
  98. binary.LittleEndian.PutUint32(key[i*4:], value_32bit) // place key into output container
  99. //memcpy(dst.data + i * 4, &val, 4); // copy 4 bytes to position
  100. }
  101. //fmt.Printf("words %+v\n", indices)
  102. //fmt.Printf("key %x\n", key)
  103. return
  104. }
  105. // this will map the key to recovery words from the spcific language
  106. // language must exist,if not we return english
  107. func Key_To_Words(key crypto.Key, language string) (words_line string) {
  108. var words []string // all words are appended here
  109. l_index := 0
  110. for i := range Languages {
  111. if Languages[i].Name == language {
  112. l_index = i
  113. break
  114. }
  115. }
  116. // total numbers of words in specified language dictionary
  117. word_list_length := uint32(len(Languages[l_index].Words))
  118. // 8 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626
  119. // for (unsigned int i=0; i < sizeof(src.data)/4; i++, words += ' ')
  120. for i := 0; i < (len(key) / 4); i++ {
  121. val := binary.LittleEndian.Uint32(key[i*4:])
  122. w1 := val % word_list_length
  123. w2 := ((val / word_list_length) + w1) % word_list_length
  124. w3 := (((val / word_list_length) / word_list_length) + w2) % word_list_length
  125. words = append(words, Languages[l_index].Words[w1])
  126. words = append(words, Languages[l_index].Words[w2])
  127. words = append(words, Languages[l_index].Words[w3])
  128. }
  129. checksum_index, err := Calculate_Checksum_Index(words, Languages[l_index].Unique_Prefix_Length)
  130. if err != nil {
  131. //fmt.Printf("Checksum index failed")
  132. return
  133. } else {
  134. // append checksum word
  135. words = append(words, words[checksum_index])
  136. }
  137. words_line = strings.Join(words, " ")
  138. //fmt.Printf("words %s \n", words_line)
  139. return
  140. }
  141. // find language and indices
  142. // all words should be from the same languages ( words do not cross language boundary )
  143. // indices = position where word was found
  144. // language = which language the seed is in
  145. // word_list_count = total words in the specified language
  146. func Find_indices(words []string) (indices []uint64, language_index int, word_list_count uint64, found bool) {
  147. for i := range Languages {
  148. var local_indices []uint64 // we build a local copy
  149. // create a map from words , for finding the words faster
  150. language_map := map[string]int{}
  151. for j := 0; j < len(Languages[i].Words); j++ {
  152. language_map[Languages[i].Words[j]] = j
  153. }
  154. // now lets loop through all the user supplied words
  155. for j := 0; j < len(words); j++ {
  156. if v, ok := language_map[words[j]]; ok {
  157. local_indices = append(local_indices, uint64(v))
  158. } else { // word has missed, this cannot be our language
  159. goto try_another_language
  160. }
  161. }
  162. // if we have found all the words, this is our language of seed words
  163. // stop processing and return all data
  164. return local_indices, i, uint64(len(Languages[i].Words)), true
  165. try_another_language:
  166. }
  167. // we are here, means we could locate any language which contains all the seed words
  168. // return empty
  169. return
  170. }
  171. // calculate a checksum on first 24 words
  172. // checksum is calculated as follows
  173. // take prefix_len chars ( not bytes) from first 24 words and concatenate them
  174. // calculate crc of resultant concatenated bytes
  175. // take mod of SEED_LENGTH 24, to get the checksum word
  176. func Calculate_Checksum_Index(words []string, prefix_len int) (uint32, error) {
  177. var trimmed_runes []rune
  178. if len(words) != SEED_LENGTH {
  179. return 0, fmt.Errorf("Words not equal to seed length")
  180. }
  181. for i := range words {
  182. if utf8.RuneCountInString(words[i]) > prefix_len { // take first prefix_len utf8 chars
  183. trimmed_runes = append(trimmed_runes, ([]rune(words[i]))[0:prefix_len]...)
  184. } else {
  185. trimmed_runes = append(trimmed_runes, ([]rune(words[i]))...) /// add entire string
  186. }
  187. }
  188. checksum := crc32.ChecksumIEEE([]byte(string(trimmed_runes)))
  189. //fmt.Printf("trimmed words %s %d \n", string(trimmed_runes), checksum)
  190. return checksum % SEED_LENGTH, nil
  191. }
  192. // for verification, we need all 25 words
  193. // calculate checksum and verify whether match
  194. func Verify_Checksum(words []string, prefix_len int) bool {
  195. if len(words) != (SEED_LENGTH + 1) {
  196. return false // Checksum word is not present, we cannot verify
  197. }
  198. checksum_index, err := Calculate_Checksum_Index(words[:len(words)-1], prefix_len)
  199. if err != nil {
  200. return false
  201. }
  202. calculated_checksum_word := words[checksum_index]
  203. checksum_word := words[SEED_LENGTH]
  204. if calculated_checksum_word == checksum_word {
  205. return true
  206. }
  207. return false
  208. }