var ninja = { wallets: {} }; ninja.privateKey = { isPrivateKey: function (key) { return ( Bitcoin.ECKey.isWalletImportFormat(key) || Bitcoin.ECKey.isCompressedWalletImportFormat(key) || Bitcoin.ECKey.isHexFormat(key) || Bitcoin.ECKey.isBase64Format(key) || Bitcoin.ECKey.isMiniFormat(key) ); }, getECKeyFromAdding: function (privKey1, privKey2) { var n = EllipticCurve.getSECCurveByName("secp256k1").getN(); var ecKey1 = new Bitcoin.ECKey(privKey1); var ecKey2 = new Bitcoin.ECKey(privKey2); // if both keys are the same return null if (ecKey1.getBitcoinHexFormat() == ecKey2.getBitcoinHexFormat()) return null; if (ecKey1 == null || ecKey2 == null) return null; var combinedPrivateKey = new Bitcoin.ECKey(ecKey1.priv.add(ecKey2.priv).mod(n)); // compressed when both keys are compressed if (ecKey1.compressed && ecKey2.compressed) combinedPrivateKey.setCompressed(true); return combinedPrivateKey; }, getECKeyFromMultiplying: function (privKey1, privKey2) { var n = EllipticCurve.getSECCurveByName("secp256k1").getN(); var ecKey1 = new Bitcoin.ECKey(privKey1); var ecKey2 = new Bitcoin.ECKey(privKey2); // if both keys are the same return null if (ecKey1.getBitcoinHexFormat() == ecKey2.getBitcoinHexFormat()) return null; if (ecKey1 == null || ecKey2 == null) return null; var combinedPrivateKey = new Bitcoin.ECKey(ecKey1.priv.multiply(ecKey2.priv).mod(n)); // 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(); }); }); } } }; ninja.publicKey = { isPublicKeyHexFormat: function (key) { key = key.toString(); return ninja.publicKey.isUncompressedPublicKeyHexFormat(key) || ninja.publicKey.isCompressedPublicKeyHexFormat(key); }, // 130 characters [0-9A-F] starts with 04 isUncompressedPublicKeyHexFormat: function (key) { key = key.toString(); return /^04[A-Fa-f0-9]{128}$/.test(key); }, // 66 characters [0-9A-F] starts with 02 or 03 isCompressedPublicKeyHexFormat: function (key) { key = key.toString(); return /^0[2-3][A-Fa-f0-9]{64}$/.test(key); }, getBitcoinAddressFromByteArray: function (pubKeyByteArray) { var pubKeyHash = Bitcoin.Util.sha256ripe160(pubKeyByteArray); var addr = new Bitcoin.Address(pubKeyHash); return addr.toString(); }, getHexFromByteArray: function (pubKeyByteArray) { return Crypto.util.bytesToHex(pubKeyByteArray).toString().toUpperCase(); }, getByteArrayFromAdding: function (pubKeyHex1, pubKeyHex2) { var ecparams = EllipticCurve.getSECCurveByName("secp256k1"); var curve = ecparams.getCurve(); var ecPoint1 = curve.decodePointHex(pubKeyHex1); var ecPoint2 = curve.decodePointHex(pubKeyHex2); // if both points are the same return null if (ecPoint1.equals(ecPoint2)) return null; var compressed = (ecPoint1.compressed && ecPoint2.compressed); var pubKey = ecPoint1.add(ecPoint2).getEncoded(compressed); return pubKey; }, getByteArrayFromMultiplying: function (pubKeyHex, ecKey) { var ecparams = EllipticCurve.getSECCurveByName("secp256k1"); var ecPoint = ecparams.getCurve().decodePointHex(pubKeyHex); var compressed = (ecPoint.compressed && ecKey.compressed); // if both points are the same return null ecKey.setCompressed(false); if (ecPoint.equals(ecKey.getPubPoint())) { return null; } var bigInt = ecKey.priv; var pubKey = ecPoint.multiply(bigInt).getEncoded(compressed); return pubKey; }, // used by unit test getDecompressedPubKeyHex: function (pubKeyHexComp) { var ecparams = EllipticCurve.getSECCurveByName("secp256k1"); var ecPoint = ecparams.getCurve().decodePointHex(pubKeyHexComp); var pubByteArray = ecPoint.getEncoded(0); var pubHexUncompressed = ninja.publicKey.getHexFromByteArray(pubByteArray); return pubHexUncompressed; } };