]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blobdiff - src/js/jsbip39.js
Korean uses ascii spaces, not ideographic spaces
[perso/Immae/Projets/Cryptomonnaies/BIP39.git] / src / js / jsbip39.js
index 0b88fef949bd65c89088cc20d7faeae4ff1d5c8b..3230e3bb46f275356bf3db523d85f66f36d65f3e 100644 (file)
@@ -22,8 +22,8 @@
 /*
  * Javascript port from python by Ian Coleman
  *
- * Includes code from asmCrypto
- * https://github.com/tresorit/asmcrypto.js
+ * Requires code from sjcl
+ * https://github.com/bitwiseshiftleft/sjcl
  */
 
 var Mnemonic = function(language) {
@@ -34,6 +34,13 @@ var Mnemonic = function(language) {
     var self = this;
     var wordlist = [];
 
+    var hmacSHA512 = function(key) {
+        var hasher = new sjcl.misc.hmac(key, sjcl.hash.sha512);
+        this.encrypt = function() {
+            return hasher.encrypt.apply(hasher, arguments);
+        };
+    };
+
     function init() {
         wordlist = WORDLISTS[language];
         if (wordlist.length != RADIX) {
@@ -57,14 +64,15 @@ var Mnemonic = function(language) {
         return self.toMnemonic(data);
     }
 
-    self.toMnemonic = function(data) {
-        if (data.length % 4 > 0) {
-            throw 'Data length in bits should be divisible by 32, but it is not (' + data.length + ' bytes = ' + data.length*8 + ' bits).'
+    self.toMnemonic = function(byteArray) {
+        if (byteArray.length % 4 > 0) {
+            throw 'Data length in bits should be divisible by 32, but it is not (' + byteArray.length + ' bytes = ' + byteArray.length*8 + ' bits).'
         }
 
         //h = hashlib.sha256(data).hexdigest()
-        var uintArray = new Uint8Array(data);
-        var h = asmCrypto.SHA256.bytes(uintArray);
+        var data = byteArrayToWordArray(byteArray);
+        var hash = sjcl.hash.sha256.hash(data);
+        var h = sjcl.codec.hex.fromBits(hash);
 
         // b is a binary string, eg '00111010101100...'
         //b = bin(int(binascii.hexlify(data), 16))[2:].zfill(len(data) * 8) + \
@@ -73,9 +81,9 @@ var Mnemonic = function(language) {
         // a = bin(int(binascii.hexlify(data), 16))[2:].zfill(len(data) * 8)
         // c = bin(int(h, 16))[2:].zfill(256)
         // d = c[:len(data) * 8 / 32]
-        var a = byteArrayToBinaryString(data);
-        var c = byteArrayToBinaryString(h);
-        var d = c.substring(0, data.length * 8 / 32);
+        var a = byteArrayToBinaryString(byteArray);
+        var c = zfill(hexStringToBinaryString(h), 256);
+        var d = c.substring(0, byteArray.length * 8 / 32);
         // b = line1 + line2
         var b = a + d;
 
@@ -85,12 +93,12 @@ var Mnemonic = function(language) {
             var idx = parseInt(b.substring(i * 11, (i + 1) * 11), 2);
             result.push(wordlist[idx]);
         }
-        return result.join(' ');
+        return self.joinWords(result);
     }
 
     self.check = function(mnemonic) {
-        var mnemonic = mnemonic.split(' ')
-        if (mnemonic.length % 3 > 0) {
+        var mnemonic = self.splitWords(mnemonic);
+        if (mnemonic.length == 0 || mnemonic.length % 3 > 0) {
             return false
         }
         // idx = map(lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic)
@@ -111,32 +119,57 @@ var Mnemonic = function(language) {
         var d = b.substring(0, l / 33 * 32);
         var h = b.substring(l - l / 33, l);
         //nd = binascii.unhexlify(hex(int(d, 2))[2:].rstrip('L').zfill(l / 33 * 8))
+        var nd = binaryStringToWordArray(d);
         //nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[:l / 33]
-        var nd = binaryStringToByteArray(d);
-        var ndHash = asmCrypto.SHA256.bytes(nd);
-        var ndBstr = zfill(byteArrayToBinaryString(ndHash), 256);
+        var ndHash = sjcl.hash.sha256.hash(nd);
+        var ndHex = sjcl.codec.hex.fromBits(ndHash);
+        var ndBstr = zfill(hexStringToBinaryString(ndHex), 256);
         var nh = ndBstr.substring(0,l/33);
         return h == nh;
     }
 
     self.toSeed = function(mnemonic, passphrase) {
         passphrase = passphrase || '';
-        mnemonic = self.normalizeString(mnemonic)
+        mnemonic = self.joinWords(self.splitWords(mnemonic)); // removes duplicate blanks
+        var mnemonicNormalized = self.normalizeString(mnemonic);
         passphrase = self.normalizeString(passphrase)
         passphrase = "mnemonic" + passphrase;
-        //return PBKDF2(mnemonic, 'mnemonic' + passphrase, iterations=PBKDF2_ROUNDS, macmodule=hmac, digestmodule=hashlib.sha512).read(64)
-        return asmCrypto.PBKDF2_HMAC_SHA512.hex(mnemonic, passphrase, PBKDF2_ROUNDS, 512/8);
+        var mnemonicBits = sjcl.codec.utf8String.toBits(mnemonicNormalized);
+        var passphraseBits = sjcl.codec.utf8String.toBits(passphrase);
+        var result = sjcl.misc.pbkdf2(mnemonicBits, passphraseBits, PBKDF2_ROUNDS, 512, hmacSHA512);
+        var hashHex = sjcl.codec.hex.fromBits(result);
+        return hashHex;
     }
 
-    self.normalizeString = function(str) {
-        if (typeof str.normalize == "function") {
-            return str.normalize("NFKD");
+    self.splitWords = function(mnemonic) {
+        return mnemonic.split(/\s/g).filter(function(x) { return x.length; });
+    }
+
+    self.joinWords = function(words) {
+        // Set space correctly depending on the language
+        // see https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
+        var space = " ";
+        if (language == "japanese") {
+            space = "\u3000"; // ideographic space
         }
-        else {
-            // TODO decide how to handle this in the future.
-            // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
-            return str;
+        return words.join(space);
+    }
+
+    self.normalizeString = function(str) {
+        return str.normalize("NFKD");
+    }
+
+    function byteArrayToWordArray(data) {
+        var a = [];
+        for (var i=0; i<data.length/4; i++) {
+            v = 0;
+            v += data[i*4 + 0] << 8 * 3;
+            v += data[i*4 + 1] << 8 * 2;
+            v += data[i*4 + 2] << 8 * 1;
+            v += data[i*4 + 3] << 8 * 0;
+            a.push(v);
         }
+        return a;
     }
 
     function byteArrayToBinaryString(data) {
@@ -147,16 +180,24 @@ var Mnemonic = function(language) {
         return bin;
     }
 
-    function binaryStringToByteArray(str) {
-        var arrayLen = str.length / 8;
-        var array = new Uint8Array(arrayLen);
-        for (var i=0; i<arrayLen; i++) {
-            var valueStr = str.substring(0,8);
+    function hexStringToBinaryString(hexString) {
+        binaryString = "";
+        for (var i=0; i<hexString.length; i++) {
+            binaryString += zfill(parseInt(hexString[i], 16).toString(2),4);
+        }
+        return binaryString;
+    }
+
+    function binaryStringToWordArray(binary) {
+        var aLen = binary.length / 32;
+        var a = [];
+        for (var i=0; i<aLen; i++) {
+            var valueStr = binary.substring(0,32);
             var value = parseInt(valueStr, 2);
-            array[i] = value;
-            str = str.slice(8);
+            a.push(value);
+            binary = binary.slice(32);
         }
-        return array;
+        return a;
     }
 
     // Pad a numeric string on the left with zero digits until the given width