]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - src/js/jsbip39.js
Merge pull request #466 from RitoProject/ritocoin
[perso/Immae/Projets/Cryptomonnaies/BIP39.git] / src / js / jsbip39.js
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 * Requires code from sjcl
26 * https://github.com/bitwiseshiftleft/sjcl
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 var hmacSHA512 = function(key) {
38 var hasher = new sjcl.misc.hmac(key, sjcl.hash.sha512);
39 this.encrypt = function() {
40 return hasher.encrypt.apply(hasher, arguments);
41 };
42 };
43
44 function init() {
45 wordlist = WORDLISTS[language];
46 if (wordlist.length != RADIX) {
47 err = 'Wordlist should contain ' + RADIX + ' words, but it contains ' + wordlist.length + ' words.';
48 throw err;
49 }
50 }
51
52 self.generate = function(strength) {
53 strength = strength || 128;
54 var r = strength % 32;
55 if (r > 0) {
56 throw 'Strength should be divisible by 32, but it is not (' + r + ').';
57 }
58 var hasStrongCrypto = 'crypto' in window && window['crypto'] !== null;
59 if (!hasStrongCrypto) {
60 throw 'Mnemonic should be generated with strong randomness, but crypto.getRandomValues is unavailable';
61 }
62 var buffer = new Uint8Array(strength / 8);
63 var data = crypto.getRandomValues(buffer);
64 return self.toMnemonic(data);
65 }
66
67 self.toMnemonic = function(byteArray) {
68 if (byteArray.length % 4 > 0) {
69 throw 'Data length in bits should be divisible by 32, but it is not (' + byteArray.length + ' bytes = ' + byteArray.length*8 + ' bits).'
70 }
71
72 //h = hashlib.sha256(data).hexdigest()
73 var data = byteArrayToWordArray(byteArray);
74 var hash = sjcl.hash.sha256.hash(data);
75 var h = sjcl.codec.hex.fromBits(hash);
76
77 // b is a binary string, eg '00111010101100...'
78 //b = bin(int(binascii.hexlify(data), 16))[2:].zfill(len(data) * 8) + \
79 // bin(int(h, 16))[2:].zfill(256)[:len(data) * 8 / 32]
80 //
81 // a = bin(int(binascii.hexlify(data), 16))[2:].zfill(len(data) * 8)
82 // c = bin(int(h, 16))[2:].zfill(256)
83 // d = c[:len(data) * 8 / 32]
84 var a = byteArrayToBinaryString(byteArray);
85 var c = zfill(hexStringToBinaryString(h), 256);
86 var d = c.substring(0, byteArray.length * 8 / 32);
87 // b = line1 + line2
88 var b = a + d;
89
90 var result = [];
91 var blen = b.length / 11;
92 for (var i=0; i<blen; i++) {
93 var idx = parseInt(b.substring(i * 11, (i + 1) * 11), 2);
94 result.push(wordlist[idx]);
95 }
96 return self.joinWords(result);
97 }
98
99 self.check = function(mnemonic) {
100 var b = mnemonicToBinaryString(mnemonic);
101 if (b === null) {
102 return false;
103 }
104 var l = b.length;
105 //d = b[:l / 33 * 32]
106 //h = b[-l / 33:]
107 var d = b.substring(0, l / 33 * 32);
108 var h = b.substring(l - l / 33, l);
109 //nd = binascii.unhexlify(hex(int(d, 2))[2:].rstrip('L').zfill(l / 33 * 8))
110 var nd = binaryStringToWordArray(d);
111 //nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[:l / 33]
112 var ndHash = sjcl.hash.sha256.hash(nd);
113 var ndHex = sjcl.codec.hex.fromBits(ndHash);
114 var ndBstr = zfill(hexStringToBinaryString(ndHex), 256);
115 var nh = ndBstr.substring(0,l/33);
116 return h == nh;
117 }
118
119 self.toRawEntropyHex = function(mnemonic) {
120 var b = mnemonicToBinaryString(mnemonic);
121 if (b === null)
122 return null;
123 var d = b.substring(0, b.length / 33 * 32);
124 var nd = binaryStringToWordArray(d);
125
126 var h = "";
127 for (var i=0; i<nd.length; i++) {
128 h += ('0000000' + nd[i].toString(16)).slice(-8);
129 }
130 return h;
131 }
132
133 self.toRawEntropyBin = function(mnemonic) {
134 var b = mnemonicToBinaryString(mnemonic);
135 var d = b.substring(0, b.length / 33 * 32);
136 return d;
137 }
138
139 self.toSeed = function(mnemonic, passphrase) {
140 passphrase = passphrase || '';
141 mnemonic = self.joinWords(self.splitWords(mnemonic)); // removes duplicate blanks
142 var mnemonicNormalized = self.normalizeString(mnemonic);
143 passphrase = self.normalizeString(passphrase)
144 passphrase = "mnemonic" + passphrase;
145 var mnemonicBits = sjcl.codec.utf8String.toBits(mnemonicNormalized);
146 var passphraseBits = sjcl.codec.utf8String.toBits(passphrase);
147 var result = sjcl.misc.pbkdf2(mnemonicBits, passphraseBits, PBKDF2_ROUNDS, 512, hmacSHA512);
148 var hashHex = sjcl.codec.hex.fromBits(result);
149 return hashHex;
150 }
151
152 self.splitWords = function(mnemonic) {
153 return mnemonic.split(/\s/g).filter(function(x) { return x.length; });
154 }
155
156 self.joinWords = function(words) {
157 // Set space correctly depending on the language
158 // see https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
159 var space = " ";
160 if (language == "japanese") {
161 space = "\u3000"; // ideographic space
162 }
163 return words.join(space);
164 }
165
166 self.normalizeString = function(str) {
167 return str.normalize("NFKD");
168 }
169
170 function byteArrayToWordArray(data) {
171 var a = [];
172 for (var i=0; i<data.length/4; i++) {
173 v = 0;
174 v += data[i*4 + 0] << 8 * 3;
175 v += data[i*4 + 1] << 8 * 2;
176 v += data[i*4 + 2] << 8 * 1;
177 v += data[i*4 + 3] << 8 * 0;
178 a.push(v);
179 }
180 return a;
181 }
182
183 function byteArrayToBinaryString(data) {
184 var bin = "";
185 for (var i=0; i<data.length; i++) {
186 bin += zfill(data[i].toString(2), 8);
187 }
188 return bin;
189 }
190
191 function hexStringToBinaryString(hexString) {
192 binaryString = "";
193 for (var i=0; i<hexString.length; i++) {
194 binaryString += zfill(parseInt(hexString[i], 16).toString(2),4);
195 }
196 return binaryString;
197 }
198
199 function binaryStringToWordArray(binary) {
200 var aLen = binary.length / 32;
201 var a = [];
202 for (var i=0; i<aLen; i++) {
203 var valueStr = binary.substring(0,32);
204 var value = parseInt(valueStr, 2);
205 a.push(value);
206 binary = binary.slice(32);
207 }
208 return a;
209 }
210
211 function mnemonicToBinaryString(mnemonic) {
212 var mnemonic = self.splitWords(mnemonic);
213 if (mnemonic.length == 0 || mnemonic.length % 3 > 0) {
214 return null;
215 }
216 // idx = map(lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic)
217 var idx = [];
218 for (var i=0; i<mnemonic.length; i++) {
219 var word = mnemonic[i];
220 var wordIndex = wordlist.indexOf(word);
221 if (wordIndex == -1) {
222 return null;
223 }
224 var binaryIndex = zfill(wordIndex.toString(2), 11);
225 idx.push(binaryIndex);
226 }
227 return idx.join('');
228 }
229
230 // Pad a numeric string on the left with zero digits until the given width
231 // is reached.
232 // Note this differs to the python implementation because it does not
233 // handle numbers starting with a sign.
234 function zfill(source, length) {
235 source = source.toString();
236 while (source.length < length) {
237 source = '0' + source;
238 }
239 return source;
240 }
241
242 init();
243
244 }