diff --git a/bitaddress.org.html b/bitaddress.org.html index 9c9ed11..de92dc2 100644 --- a/bitaddress.org.html +++ b/bitaddress.org.html @@ -1,7 +1,6 @@ - bitaddress.org + + + +
English | @@ -5732,6 +6042,10 @@ +
+ + +
@@ -5845,6 +6159,132 @@ // compressed when both keys are compressed if (ecKey1.compressed && ecKey2.compressed) combinedPrivateKey.setCompressed(true); return combinedPrivateKey; + }, + // 58 base58 characters starting with 6P + isBIP38Format: function (key) { + return (/^6P[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{56}$/.test(key)); + }, + BIP38EncryptedKeyToByteArrayAsync: function (base58Encrypted, passphrase, callback) { + var hex; + try { + hex = Bitcoin.Base58.decode(base58Encrypted); + } catch (e) { + callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey"))); + return; + } + + // 43 bytes: 2 bytes prefix, 37 bytes payload, 4 bytes checksum + if (hex.length != 43) { + callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey"))); + return; + } + // first byte is always 0x01 + else if (hex[0] != 0x01) { + callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey"))); + return; + } + + var expChecksum = hex.slice(-4); + hex = hex.slice(0, -4); + var checksum = Bitcoin.Util.dsha256(hex); + if (checksum[0] != expChecksum[0] || checksum[1] != expChecksum[1] || checksum[2] != expChecksum[2] || checksum[3] != expChecksum[3]) { + callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey"))); + return; + } + + var isCompPoint = false; + var isECMult = false; + var hasLotSeq = false; + // second byte for non-EC-multiplied key + if (hex[1] == 0x42) { + // key should use compression + if (hex[2] == 0xe0) { + isCompPoint = true; + } + // key should NOT use compression + else if (hex[2] != 0xc0) { + callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey"))); + return; + } + } + // second byte for EC-multiplied key + else if (hex[1] == 0x43) { + isECMult = true; + isCompPoint = (hex[2] & 0x20) != 0; + hasLotSeq = (hex[2] & 0x04) != 0; + if ((hex[2] & 0x24) != hex[2]) { + callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey"))); + return; + } + } + else { + callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey"))); + return; + } + + var decrypted; + var AES_opts = { mode: new Crypto.mode.ECB(Crypto.pad.NoPadding), asBytes: true }; + + var verifyHashAndReturn = function () { + var tmpkey = new Bitcoin.ECKey(decrypted); // decrypted using closure + var base58AddrText = tmpkey.setCompressed(isCompPoint).getBitcoinAddress(); // isCompPoint using closure + checksum = Bitcoin.Util.dsha256(base58AddrText); // checksum using closure + + if (checksum[0] != hex[3] || checksum[1] != hex[4] || checksum[2] != hex[5] || checksum[3] != hex[6]) { + callback(new Error(ninja.translator.get("bip38alertincorrectpassphrase"))); // callback using closure + return; + } + callback(tmpkey.getBitcoinPrivateKeyByteArray()); // callback using closure + }; + + if (!isECMult) { + var addresshash = hex.slice(3, 7); + Crypto_scrypt(passphrase, addresshash, 16384, 8, 8, 64, function (derivedBytes) { + var k = derivedBytes.slice(32, 32 + 32); + decrypted = Crypto.AES.decrypt(hex.slice(7, 7 + 32), k, AES_opts); + for (var x = 0; x < 32; x++) decrypted[x] ^= derivedBytes[x]; + verifyHashAndReturn(); //TODO: pass in 'decrypted' as a param + }); + } + else { + var ownerentropy = hex.slice(7, 7 + 8); + var ownersalt = !hasLotSeq ? ownerentropy : ownerentropy.slice(0, 4); + Crypto_scrypt(passphrase, ownersalt, 16384, 8, 8, 32, function (prefactorA) { + var passfactor; + if (!hasLotSeq) { // hasLotSeq using closure + passfactor = prefactorA; + } else { + var prefactorB = prefactorA.concat(ownerentropy); // ownerentropy using closure + passfactor = Bitcoin.Util.dsha256(prefactorB); + } + var kp = new Bitcoin.ECKey(passfactor); + var passpoint = kp.setCompressed(true).getPub(); + + var encryptedpart2 = hex.slice(23, 23 + 16); + + var addresshashplusownerentropy = hex.slice(3, 3 + 12); + Crypto_scrypt(passpoint, addresshashplusownerentropy, 1024, 1, 1, 64, function (derived) { + var k = derived.slice(32); + + var unencryptedpart2 = Crypto.AES.decrypt(encryptedpart2, k, AES_opts); + for (var i = 0; i < 16; i++) { unencryptedpart2[i] ^= derived[i + 16]; } + + var encryptedpart1 = hex.slice(15, 15 + 8).concat(unencryptedpart2.slice(0, 0 + 8)); + var unencryptedpart1 = Crypto.AES.decrypt(encryptedpart1, k, AES_opts); + for (var i = 0; i < 16; i++) { unencryptedpart1[i] ^= derived[i]; } + + var seedb = unencryptedpart1.slice(0, 0 + 16).concat(unencryptedpart2.slice(8, 8 + 8)); + + var factorb = Bitcoin.Util.dsha256(seedb); + + var ps = EllipticCurve.getSECCurveByName("secp256k1"); + var privateKey = BigInteger.fromByteArrayUnsigned(passfactor).multiply(BigInteger.fromByteArrayUnsigned(factorb)).remainder(ps.getN()); + + decrypted = privateKey.toByteArrayUnsigned(); + verifyHashAndReturn(); + }); + }); + } } }; @@ -6059,6 +6499,40 @@ } return result; }; + + // use when passing an Array of Functions + ninja.runSerialized = function (functions, onComplete) { + onComplete = onComplete || function () { }; + + if (functions.length === 0) onComplete(); + else { + // run the first function, and make it call this + // function when finished with the rest of the list + var f = functions.shift(); + f(function () { ninja.runSerialized(functions, onComplete); }); + } + }; + + ninja.forSerialized = function (initial, max, whatToDo, onComplete) { + onComplete = onComplete || function () { }; + + if (initial === max) { onComplete(); } + else { + // same idea as runSerialized + whatToDo(initial, function () { ninja.forSerialized(++initial, max, whatToDo, onComplete); }); + } + }; + + // use when passing an Object (dictionary) of Functions + ninja.foreachSerialized = function (collection, whatToDo, onComplete) { + var keys = []; + for (var name in collection) { + keys.push(name); + } + ninja.forSerialized(0, keys.length, function (i, callback) { + whatToDo(keys[i], callback); + }, onComplete); + }; @@ -6921,17 +7448,19 @@