]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - src/js/jsbip39.js
UTF-8 strings handled correctly by asmCrypto
[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 * 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 || '';
124 mnemonic = self.normalizeString(mnemonic)
125 passphrase = self.normalizeString(passphrase)
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
131 self.normalizeString = function(str) {
132 if (typeof str.normalize == "function") {
133 return str.normalize("NFKD");
134 }
135 else {
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
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 }