/*
* 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) {
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) {
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) + \
// 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;
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)
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" || language == "korean") {
+ 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) {
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