// Copyright 2017-2018 DERO Project. All rights reserved. // Use of this source code in any form is governed by RESEARCH license. // license can be found in the LICENSE file. // GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 // // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package mnemonics import "fmt" import "strings" import "encoding/binary" import "unicode/utf8" //import "github.com/romana/rlog" import "hash/crc32" import "github.com/arnaucode/derosuite/crypto" type Language struct { Name string // Name of the language Name_English string // Name of the language in english Unique_Prefix_Length int // number of utf8 chars (not bytes) to use for checksum Words []string // 1626 words } // any language needs to be added to this array var Languages = []Language{ Mnemonics_English, Mnemonics_Japanese, Mnemonics_Chinese_Simplified, Mnemonics_Dutch, Mnemonics_Esperanto, Mnemonics_Russian, Mnemonics_Spanish, Mnemonics_Portuguese, Mnemonics_French, Mnemonics_German, Mnemonics_Italian, } const SEED_LENGTH = 24 // checksum seeds are 24 + 1 = 25 words long // while init check whether all languages have necessary data func init() { for i := range Languages { if len(Languages[i].Words) != 1626 { panic(fmt.Sprintf("%s language only has %d words, should have 1626", Languages[i].Name_English, len(Languages[i].Words))) } } } // return all the languages support by us func Language_List() (list []string) { for i := range Languages { list = append(list, Languages[i].Name) } return } //this function converts a list of words to a key func Words_To_Key(words_line string) (language_name string, key crypto.Key, err error) { checksum_present := false words := strings.Fields(words_line) //rlog.Tracef(1, "len of words %d", words) // if seed size is not 24 or 25, return err if len(words) != SEED_LENGTH && len(words) != (SEED_LENGTH+1) { err = fmt.Errorf("Invalid Seed") return } // if checksum is present consider it so if len(words) == (SEED_LENGTH + 1) { checksum_present = true } indices, language_index, wordlist_length, found := Find_indices(words) if !found { return language_name, key, fmt.Errorf("Seed not found in any Language") } language_name = Languages[language_index].Name if checksum_present { // we need language unique prefix to validate checksum if !Verify_Checksum(words, Languages[language_index].Unique_Prefix_Length) { return language_name, key, fmt.Errorf("Seed Checksum failed") } } // key = make([]byte,(SEED_LENGTH/3)*4,(SEED_LENGTH/3)*4) // our keys are 32 bytes // map 3 words to 4 bytes each // so 24 words = 32 bytes for i := 0; i < (SEED_LENGTH / 3); i++ { w1 := indices[i*3] w2 := indices[i*3+1] w3 := indices[i*3+2] val := w1 + wordlist_length*(((wordlist_length-w1)+w2)%wordlist_length) + wordlist_length*wordlist_length*(((wordlist_length-w2)+w3)%wordlist_length) // sanity check, this can never occur if (val % wordlist_length) != w1 { panic("Word list error") } value_32bit := uint32(val) binary.LittleEndian.PutUint32(key[i*4:], value_32bit) // place key into output container //memcpy(dst.data + i * 4, &val, 4); // copy 4 bytes to position } //fmt.Printf("words %+v\n", indices) //fmt.Printf("key %x\n", key) return } // this will map the key to recovery words from the spcific language // language must exist,if not we return english func Key_To_Words(key crypto.Key, language string) (words_line string) { var words []string // all words are appended here l_index := 0 for i := range Languages { if Languages[i].Name == language { l_index = i break } } // total numbers of words in specified language dictionary word_list_length := uint32(len(Languages[l_index].Words)) // 8 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626 // for (unsigned int i=0; i < sizeof(src.data)/4; i++, words += ' ') for i := 0; i < (len(key) / 4); i++ { val := binary.LittleEndian.Uint32(key[i*4:]) w1 := val % word_list_length w2 := ((val / word_list_length) + w1) % word_list_length w3 := (((val / word_list_length) / word_list_length) + w2) % word_list_length words = append(words, Languages[l_index].Words[w1]) words = append(words, Languages[l_index].Words[w2]) words = append(words, Languages[l_index].Words[w3]) } checksum_index, err := Calculate_Checksum_Index(words, Languages[l_index].Unique_Prefix_Length) if err != nil { //fmt.Printf("Checksum index failed") return } else { // append checksum word words = append(words, words[checksum_index]) } words_line = strings.Join(words, " ") //fmt.Printf("words %s \n", words_line) return } // find language and indices // all words should be from the same languages ( words do not cross language boundary ) // indices = position where word was found // language = which language the seed is in // word_list_count = total words in the specified language func Find_indices(words []string) (indices []uint64, language_index int, word_list_count uint64, found bool) { for i := range Languages { var local_indices []uint64 // we build a local copy // create a map from words , for finding the words faster language_map := map[string]int{} for j := 0; j < len(Languages[i].Words); j++ { language_map[Languages[i].Words[j]] = j } // now lets loop through all the user supplied words for j := 0; j < len(words); j++ { if v, ok := language_map[words[j]]; ok { local_indices = append(local_indices, uint64(v)) } else { // word has missed, this cannot be our language goto try_another_language } } // if we have found all the words, this is our language of seed words // stop processing and return all data return local_indices, i, uint64(len(Languages[i].Words)), true try_another_language: } // we are here, means we could locate any language which contains all the seed words // return empty return } // calculate a checksum on first 24 words // checksum is calculated as follows // take prefix_len chars ( not bytes) from first 24 words and concatenate them // calculate crc of resultant concatenated bytes // take mod of SEED_LENGTH 24, to get the checksum word func Calculate_Checksum_Index(words []string, prefix_len int) (uint32, error) { var trimmed_runes []rune if len(words) != SEED_LENGTH { return 0, fmt.Errorf("Words not equal to seed length") } for i := range words { if utf8.RuneCountInString(words[i]) > prefix_len { // take first prefix_len utf8 chars trimmed_runes = append(trimmed_runes, ([]rune(words[i]))[0:prefix_len]...) } else { trimmed_runes = append(trimmed_runes, ([]rune(words[i]))...) /// add entire string } } checksum := crc32.ChecksumIEEE([]byte(string(trimmed_runes))) //fmt.Printf("trimmed words %s %d \n", string(trimmed_runes), checksum) return checksum % SEED_LENGTH, nil } // for verification, we need all 25 words // calculate checksum and verify whether match func Verify_Checksum(words []string, prefix_len int) bool { if len(words) != (SEED_LENGTH + 1) { return false // Checksum word is not present, we cannot verify } checksum_index, err := Calculate_Checksum_Index(words[:len(words)-1], prefix_len) if err != nil { return false } calculated_checksum_word := words[checksum_index] checksum_word := words[SEED_LENGTH] if calculated_checksum_word == checksum_word { return true } return false }