]>
Commit | Line | Data |
---|---|---|
ebd8d4e8 IC |
1 | /* |
2 | * Copyright (c) 2013 Pavol Rusnak | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of | |
5 | * this software and associated documentation files (the "Software"), to deal in | |
6 | * the Software without restriction, including without limitation the rights to | |
7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | |
8 | * of the Software, and to permit persons to whom the Software is furnished to do | |
9 | * so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in all | |
12 | * copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
18 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
20 | */ | |
21 | ||
22 | /* | |
23 | * Javascript port from python by Ian Coleman | |
24 | * | |
25 | * Includes code from asmCrypto | |
26 | * https://github.com/tresorit/asmcrypto.js | |
27 | */ | |
28 | ||
29 | var Mnemonic = function(language) { | |
30 | ||
31 | var PBKDF2_ROUNDS = 2048; | |
32 | var RADIX = 2048; | |
33 | ||
34 | var self = this; | |
35 | var wordlist = []; | |
36 | ||
37 | function init() { | |
38 | wordlist = WORDLISTS[language]; | |
39 | if (wordlist.length != RADIX) { | |
40 | err = 'Wordlist should contain ' + RADIX + ' words, but it contains ' + wordlist.length + ' words.'; | |
41 | throw err; | |
42 | } | |
43 | } | |
44 | ||
45 | self.generate = function(strength) { | |
46 | strength = strength || 128; | |
47 | var r = strength % 32; | |
48 | if (r > 0) { | |
49 | throw 'Strength should be divisible by 32, but it is not (' + r + ').'; | |
50 | } | |
51 | var hasStrongCrypto = 'crypto' in window && window['crypto'] !== null; | |
52 | if (!hasStrongCrypto) { | |
53 | throw 'Mnemonic should be generated with strong randomness, but crypto.getRandomValues is unavailable'; | |
54 | } | |
55 | var buffer = new Uint8Array(strength / 8); | |
56 | var data = crypto.getRandomValues(buffer); | |
57 | return self.toMnemonic(data); | |
58 | } | |
59 | ||
60 | self.toMnemonic = function(data) { | |
61 | if (data.length % 4 > 0) { | |
62 | throw 'Data length in bits should be divisible by 32, but it is not (' + data.length + ' bytes = ' + data.length*8 + ' bits).' | |
63 | } | |
64 | ||
65 | //h = hashlib.sha256(data).hexdigest() | |
66 | var uintArray = new Uint8Array(data); | |
67 | var h = asmCrypto.SHA256.bytes(uintArray); | |
68 | ||
69 | // b is a binary string, eg '00111010101100...' | |
70 | //b = bin(int(binascii.hexlify(data), 16))[2:].zfill(len(data) * 8) + \ | |
71 | // bin(int(h, 16))[2:].zfill(256)[:len(data) * 8 / 32] | |
72 | // | |
73 | // a = bin(int(binascii.hexlify(data), 16))[2:].zfill(len(data) * 8) | |
74 | // c = bin(int(h, 16))[2:].zfill(256) | |
75 | // d = c[:len(data) * 8 / 32] | |
76 | var a = byteArrayToBinaryString(data); | |
77 | var c = byteArrayToBinaryString(h); | |
78 | var d = c.substring(0, data.length * 8 / 32); | |
79 | // b = line1 + line2 | |
80 | var b = a + d; | |
81 | ||
82 | var result = []; | |
83 | var blen = b.length / 11; | |
84 | for (var i=0; i<blen; i++) { | |
85 | var idx = parseInt(b.substring(i * 11, (i + 1) * 11), 2); | |
86 | result.push(wordlist[idx]); | |
87 | } | |
88 | return result.join(' '); | |
89 | } | |
90 | ||
91 | self.check = function(mnemonic) { | |
92 | var mnemonic = mnemonic.split(' ') | |
93 | if (mnemonic.length % 3 > 0) { | |
94 | return false | |
95 | } | |
96 | // idx = map(lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic) | |
97 | var idx = []; | |
98 | for (var i=0; i<mnemonic.length; i++) { | |
99 | var word = mnemonic[i]; | |
100 | var wordIndex = wordlist.indexOf(word); | |
101 | if (wordIndex == -1) { | |
102 | return false; | |
103 | } | |
104 | var binaryIndex = zfill(wordIndex.toString(2), 11); | |
105 | idx.push(binaryIndex); | |
106 | } | |
107 | var b = idx.join(''); | |
108 | var l = b.length; | |
109 | //d = b[:l / 33 * 32] | |
110 | //h = b[-l / 33:] | |
111 | var d = b.substring(0, l / 33 * 32); | |
112 | var h = b.substring(l - l / 33, l); | |
113 | //nd = binascii.unhexlify(hex(int(d, 2))[2:].rstrip('L').zfill(l / 33 * 8)) | |
114 | //nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[:l / 33] | |
115 | var nd = binaryStringToByteArray(d); | |
116 | var ndHash = asmCrypto.SHA256.bytes(nd); | |
117 | var ndBstr = zfill(byteArrayToBinaryString(ndHash), 256); | |
118 | var nh = ndBstr.substring(0,l/33); | |
119 | return h == nh; | |
120 | } | |
121 | ||
122 | self.toSeed = function(mnemonic, passphrase) { | |
123 | passphrase = passphrase || ''; | |
be6ba9a8 IC |
124 | mnemonic = self.normalizeString(mnemonic) |
125 | passphrase = self.normalizeString(passphrase) | |
ebd8d4e8 IC |
126 | passphrase = "mnemonic" + passphrase; |
127 | //return PBKDF2(mnemonic, 'mnemonic' + passphrase, iterations=PBKDF2_ROUNDS, macmodule=hmac, digestmodule=hashlib.sha512).read(64) | |
128 | return asmCrypto.PBKDF2_HMAC_SHA512.hex(mnemonic, passphrase, PBKDF2_ROUNDS, 512/8); | |
129 | } | |
130 | ||
be6ba9a8 | 131 | self.normalizeString = function(str) { |
ebd8d4e8 IC |
132 | if (typeof str.normalize == "function") { |
133 | return str.normalize("NFKD"); | |
134 | } | |
135 | else { | |
4dd60506 IC |
136 | // TODO decide how to handle this in the future. |
137 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize | |
ebd8d4e8 IC |
138 | return str; |
139 | } | |
140 | } | |
141 | ||
142 | function byteArrayToBinaryString(data) { | |
143 | var bin = ""; | |
144 | for (var i=0; i<data.length; i++) { | |
145 | bin += zfill(data[i].toString(2), 8); | |
146 | } | |
147 | return bin; | |
148 | } | |
149 | ||
150 | function binaryStringToByteArray(str) { | |
151 | var arrayLen = str.length / 8; | |
152 | var array = new Uint8Array(arrayLen); | |
153 | for (var i=0; i<arrayLen; i++) { | |
154 | var valueStr = str.substring(0,8); | |
155 | var value = parseInt(valueStr, 2); | |
156 | array[i] = value; | |
157 | str = str.slice(8); | |
158 | } | |
159 | return array; | |
160 | } | |
161 | ||
162 | // Pad a numeric string on the left with zero digits until the given width | |
163 | // is reached. | |
164 | // Note this differs to the python implementation because it does not | |
165 | // handle numbers starting with a sign. | |
166 | function zfill(source, length) { | |
167 | source = source.toString(); | |
168 | while (source.length < length) { | |
169 | source = '0' + source; | |
170 | } | |
171 | return source; | |
172 | } | |
173 | ||
174 | init(); | |
175 | ||
176 | } |