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  }