github.com/deroproject/derosuite@v2.1.6-1.0.20200307070847-0f2e589c7a2b+incompatible/walletapi/mnemonics/mnemonics.go (about) 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 17 package mnemonics 18 19 import "fmt" 20 import "strings" 21 import "encoding/binary" 22 import "unicode/utf8" 23 24 //import "github.com/romana/rlog" 25 26 import "hash/crc32" 27 28 import "github.com/deroproject/derosuite/crypto" 29 30 type Language struct { 31 Name string // Name of the language 32 Name_English string // Name of the language in english 33 Unique_Prefix_Length int // number of utf8 chars (not bytes) to use for checksum 34 Words []string // 1626 words 35 } 36 37 // any language needs to be added to this array 38 var Languages = []Language{ 39 Mnemonics_English, 40 Mnemonics_Japanese, 41 Mnemonics_Chinese_Simplified, 42 Mnemonics_Dutch, 43 Mnemonics_Esperanto, 44 Mnemonics_Russian, 45 Mnemonics_Spanish, 46 Mnemonics_Portuguese, 47 Mnemonics_French, 48 Mnemonics_German, 49 Mnemonics_Italian, 50 } 51 52 const SEED_LENGTH = 24 // checksum seeds are 24 + 1 = 25 words long 53 54 // while init check whether all languages have necessary data 55 56 func init() { 57 for i := range Languages { 58 if len(Languages[i].Words) != 1626 { 59 panic(fmt.Sprintf("%s language only has %d words, should have 1626", Languages[i].Name_English, len(Languages[i].Words))) 60 } 61 } 62 } 63 64 // return all the languages support by us 65 func Language_List() (list []string) { 66 for i := range Languages { 67 list = append(list, Languages[i].Name) 68 } 69 return 70 } 71 72 //this function converts a list of words to a key 73 func Words_To_Key(words_line string) (language_name string, key crypto.Key, err error) { 74 75 checksum_present := false 76 words := strings.Fields(words_line) 77 //rlog.Tracef(1, "len of words %d", words) 78 79 // if seed size is not 24 or 25, return err 80 if len(words) != SEED_LENGTH && len(words) != (SEED_LENGTH+1) { 81 err = fmt.Errorf("Invalid Seed") 82 return 83 } 84 85 // if checksum is present consider it so 86 if len(words) == (SEED_LENGTH + 1) { 87 checksum_present = true 88 } 89 90 indices, language_index, wordlist_length, found := Find_indices(words) 91 92 if !found { 93 return language_name, key, fmt.Errorf("Seed not found in any Language") 94 } 95 96 language_name = Languages[language_index].Name 97 98 if checksum_present { // we need language unique prefix to validate checksum 99 if !Verify_Checksum(words, Languages[language_index].Unique_Prefix_Length) { 100 return language_name, key, fmt.Errorf("Seed Checksum failed") 101 } 102 } 103 104 // key = make([]byte,(SEED_LENGTH/3)*4,(SEED_LENGTH/3)*4) // our keys are 32 bytes 105 106 // map 3 words to 4 bytes each 107 // so 24 words = 32 bytes 108 for i := 0; i < (SEED_LENGTH / 3); i++ { 109 w1 := indices[i*3] 110 w2 := indices[i*3+1] 111 w3 := indices[i*3+2] 112 113 val := w1 + wordlist_length*(((wordlist_length-w1)+w2)%wordlist_length) + 114 wordlist_length*wordlist_length*(((wordlist_length-w2)+w3)%wordlist_length) 115 116 // sanity check, this can never occur 117 if (val % wordlist_length) != w1 { 118 panic("Word list error") 119 } 120 121 value_32bit := uint32(val) 122 123 binary.LittleEndian.PutUint32(key[i*4:], value_32bit) // place key into output container 124 125 //memcpy(dst.data + i * 4, &val, 4); // copy 4 bytes to position 126 } 127 //fmt.Printf("words %+v\n", indices) 128 //fmt.Printf("key %x\n", key) 129 130 return 131 } 132 133 // this will map the key to recovery words from the spcific language 134 // language must exist,if not we return english 135 func Key_To_Words(key crypto.Key, language string) (words_line string) { 136 var words []string // all words are appended here 137 138 l_index := 0 139 for i := range Languages { 140 if Languages[i].Name == language { 141 l_index = i 142 break 143 } 144 } 145 146 // total numbers of words in specified language dictionary 147 word_list_length := uint32(len(Languages[l_index].Words)) 148 149 // 8 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626 150 // for (unsigned int i=0; i < sizeof(src.data)/4; i++, words += ' ') 151 for i := 0; i < (len(key) / 4); i++ { 152 153 val := binary.LittleEndian.Uint32(key[i*4:]) 154 155 w1 := val % word_list_length 156 w2 := ((val / word_list_length) + w1) % word_list_length 157 w3 := (((val / word_list_length) / word_list_length) + w2) % word_list_length 158 159 words = append(words, Languages[l_index].Words[w1]) 160 words = append(words, Languages[l_index].Words[w2]) 161 words = append(words, Languages[l_index].Words[w3]) 162 } 163 164 checksum_index, err := Calculate_Checksum_Index(words, Languages[l_index].Unique_Prefix_Length) 165 if err != nil { 166 //fmt.Printf("Checksum index failed") 167 return 168 169 } else { 170 // append checksum word 171 words = append(words, words[checksum_index]) 172 173 } 174 175 words_line = strings.Join(words, " ") 176 177 //fmt.Printf("words %s \n", words_line) 178 179 return 180 181 } 182 183 // find language and indices 184 // all words should be from the same languages ( words do not cross language boundary ) 185 // indices = position where word was found 186 // language = which language the seed is in 187 // word_list_count = total words in the specified language 188 189 func Find_indices(words []string) (indices []uint64, language_index int, word_list_count uint64, found bool) { 190 191 for i := range Languages { 192 var local_indices []uint64 // we build a local copy 193 194 // create a map from words , for finding the words faster 195 language_map := map[string]int{} 196 for j := 0; j < len(Languages[i].Words); j++ { 197 language_map[Languages[i].Words[j]] = j 198 } 199 200 // now lets loop through all the user supplied words 201 for j := 0; j < len(words); j++ { 202 if v, ok := language_map[words[j]]; ok { 203 local_indices = append(local_indices, uint64(v)) 204 } else { // word has missed, this cannot be our language 205 goto try_another_language 206 } 207 } 208 209 // if we have found all the words, this is our language of seed words 210 // stop processing and return all data 211 return local_indices, i, uint64(len(Languages[i].Words)), true 212 213 try_another_language: 214 } 215 216 // we are here, means we could locate any language which contains all the seed words 217 // return empty 218 return 219 } 220 221 // calculate a checksum on first 24 words 222 // checksum is calculated as follows 223 // take prefix_len chars ( not bytes) from first 24 words and concatenate them 224 // calculate crc of resultant concatenated bytes 225 // take mod of SEED_LENGTH 24, to get the checksum word 226 227 func Calculate_Checksum_Index(words []string, prefix_len int) (uint32, error) { 228 var trimmed_runes []rune 229 230 if len(words) != SEED_LENGTH { 231 return 0, fmt.Errorf("Words not equal to seed length") 232 } 233 234 for i := range words { 235 if utf8.RuneCountInString(words[i]) > prefix_len { // take first prefix_len utf8 chars 236 trimmed_runes = append(trimmed_runes, ([]rune(words[i]))[0:prefix_len]...) 237 } else { 238 trimmed_runes = append(trimmed_runes, ([]rune(words[i]))...) /// add entire string 239 } 240 241 } 242 243 checksum := crc32.ChecksumIEEE([]byte(string(trimmed_runes))) 244 245 //fmt.Printf("trimmed words %s %d \n", string(trimmed_runes), checksum) 246 247 return checksum % SEED_LENGTH, nil 248 249 } 250 251 // for verification, we need all 25 words 252 // calculate checksum and verify whether match 253 func Verify_Checksum(words []string, prefix_len int) bool { 254 255 if len(words) != (SEED_LENGTH + 1) { 256 return false // Checksum word is not present, we cannot verify 257 } 258 259 checksum_index, err := Calculate_Checksum_Index(words[:len(words)-1], prefix_len) 260 if err != nil { 261 return false 262 } 263 calculated_checksum_word := words[checksum_index] 264 checksum_word := words[SEED_LENGTH] 265 266 if calculated_checksum_word == checksum_word { 267 return true 268 } 269 270 return false 271 }