@@ -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 @@