|
// 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/deroproject/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
|
|
}
|