220 lines
8.3 KiB
JavaScript
220 lines
8.3 KiB
JavaScript
|
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;
|
||
|
}
|
||
|
};
|