]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - src/js/jsbip39.js
Merge pull request #65 from mikeyb/game
[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 mnemonic = self.splitWords(mnemonic);
101 if (mnemonic.length == 0 || mnemonic.length % 3 > 0) {
102 return false
103 }
104 // idx = map(lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic)
105 var idx = [];
106 for (var i=0; i<mnemonic.length; i++) {
107 var word = mnemonic[i];
108 var wordIndex = wordlist.indexOf(word);
109 if (wordIndex == -1) {
110 return false;
111 }
112 var binaryIndex = zfill(wordIndex.toString(2), 11);
113 idx.push(binaryIndex);
114 }
115 var b = idx.join('');
116 var l = b.length;
117 //d = b[:l / 33 * 32]
118 //h = b[-l / 33:]
119 var d = b.substring(0, l / 33 * 32);
120 var h = b.substring(l - l / 33, l);
121 //nd = binascii.unhexlify(hex(int(d, 2))[2:].rstrip('L').zfill(l / 33 * 8))
122 var nd = binaryStringToWordArray(d);
123 //nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[:l / 33]
124 var ndHash = sjcl.hash.sha256.hash(nd);
125 var ndHex = sjcl.codec.hex.fromBits(ndHash);
126 var ndBstr = zfill(hexStringToBinaryString(ndHex), 256);
127 var nh = ndBstr.substring(0,l/33);
128 return h == nh;
129 }
130
131 self.toSeed = function(mnemonic, passphrase) {
132 passphrase = passphrase || '';
133 mnemonic = self.joinWords(self.splitWords(mnemonic)); // removes duplicate blanks
134 var mnemonicNormalized = self.normalizeString(mnemonic);
135 passphrase = self.normalizeString(passphrase)
136 passphrase = "mnemonic" + passphrase;
137 var mnemonicBits = sjcl.codec.utf8String.toBits(mnemonicNormalized);
138 var passphraseBits = sjcl.codec.utf8String.toBits(passphrase);
139 var result = sjcl.misc.pbkdf2(mnemonicBits, passphraseBits, PBKDF2_ROUNDS, 512, hmacSHA512);
140 var hashHex = sjcl.codec.hex.fromBits(result);
141 return hashHex;
142 }
143
144 self.splitWords = function(mnemonic) {
145 return mnemonic.split(/\s/g).filter(function(x) { return x.length; });
146 }
147
148 self.joinWords = function(words) {
149 // Set space correctly depending on the language
150 // see https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
151 var space = " ";
152 if (language == "japanese") {
153 space = "\u3000"; // ideographic space
154 }
155 return words.join(space);
156 }
157
158 self.normalizeString = function(str) {
159 if (typeof str.normalize == "function") {
160 return str.normalize("NFKD");
161 }
162 else {
163 // TODO decide how to handle this in the future.
164 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
165 return str;
166 }
167 }
168
169 function byteArrayToWordArray(data) {
170 var a = [];
171 for (var i=0; i<data.length/4; i++) {
172 v = 0;
173 v += data[i*4 + 0] << 8 * 3;
174 v += data[i*4 + 1] << 8 * 2;
175 v += data[i*4 + 2] << 8 * 1;
176 v += data[i*4 + 3] << 8 * 0;
177 a.push(v);
178 }
179 return a;
180 }
181
182 function byteArrayToBinaryString(data) {
183 var bin = "";
184 for (var i=0; i<data.length; i++) {
185 bin += zfill(data[i].toString(2), 8);
186 }
187 return bin;
188 }
189
190 function hexStringToBinaryString(hexString) {
191 binaryString = "";
192 for (var i=0; i<hexString.length; i++) {
193 binaryString += zfill(parseInt(hexString[i], 16).toString(2),4);
194 }
195 return binaryString;
196 }
197
198 function binaryStringToWordArray(binary) {
199 var aLen = binary.length / 32;
200 var a = [];
201 for (var i=0; i<aLen; i++) {
202 var valueStr = binary.substring(0,32);
203 var value = parseInt(valueStr, 2);
204 a.push(value);
205 binary = binary.slice(32);
206 }
207 return a;
208 }
209
210 // Pad a numeric string on the left with zero digits until the given width
211 // is reached.
212 // Note this differs to the python implementation because it does not
213 // handle numbers starting with a sign.
214 function zfill(source, length) {
215 source = source.toString();
216 while (source.length < length) {
217 source = '0' + source;
218 }
219 return source;
220 }
221
222 init();
223
224 }