2013-08-28 04:05:31 +02:00
|
|
|
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) {
|
2013-10-25 05:28:53 +02:00
|
|
|
key = key.toString();
|
2013-08-28 04:05:31 +02:00
|
|
|
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);
|
|
|
|
}
|
2015-10-25 19:54:59 +01:00
|
|
|
// remove this ECKey from the pool (because user does not see it)
|
|
|
|
var userKeyPool = Bitcoin.KeyPool.getArray();
|
2013-08-28 04:05:31 +02:00
|
|
|
var kp = new Bitcoin.ECKey(passfactor);
|
|
|
|
var passpoint = kp.setCompressed(true).getPub();
|
2015-10-25 19:54:59 +01:00
|
|
|
Bitcoin.KeyPool.setArray(userKeyPool);
|
2013-08-28 04:05:31 +02:00
|
|
|
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();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2013-10-25 05:28:53 +02:00
|
|
|
},
|
|
|
|
BIP38PrivateKeyToEncryptedKeyAsync: function (base58Key, passphrase, compressed, callback) {
|
|
|
|
var privKey = new Bitcoin.ECKey(base58Key);
|
|
|
|
var privKeyBytes = privKey.getBitcoinPrivateKeyByteArray();
|
|
|
|
var address = privKey.setCompressed(compressed).getBitcoinAddress();
|
|
|
|
|
|
|
|
// compute sha256(sha256(address)) and take first 4 bytes
|
|
|
|
var salt = Bitcoin.Util.dsha256(address).slice(0, 4);
|
|
|
|
|
|
|
|
// derive key using scrypt
|
|
|
|
var AES_opts = { mode: new Crypto.mode.ECB(Crypto.pad.NoPadding), asBytes: true };
|
|
|
|
|
|
|
|
Crypto_scrypt(passphrase, salt, 16384, 8, 8, 64, function (derivedBytes) {
|
|
|
|
for (var i = 0; i < 32; ++i) {
|
|
|
|
privKeyBytes[i] ^= derivedBytes[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
// 0x01 0x42 + flagbyte + salt + encryptedhalf1 + encryptedhalf2
|
|
|
|
var flagByte = compressed ? 0xe0 : 0xc0;
|
|
|
|
var encryptedKey = [0x01, 0x42, flagByte].concat(salt);
|
|
|
|
encryptedKey = encryptedKey.concat(Crypto.AES.encrypt(privKeyBytes, derivedBytes.slice(32), AES_opts));
|
|
|
|
encryptedKey = encryptedKey.concat(Bitcoin.Util.dsha256(encryptedKey).slice(0, 4));
|
|
|
|
callback(Bitcoin.Base58.encode(encryptedKey));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
BIP38GenerateIntermediatePointAsync: function (passphrase, lotNum, sequenceNum, callback) {
|
|
|
|
var noNumbers = lotNum === null || sequenceNum === null;
|
|
|
|
var rng = new SecureRandom();
|
|
|
|
var ownerEntropy, ownerSalt;
|
|
|
|
|
|
|
|
if (noNumbers) {
|
|
|
|
ownerSalt = ownerEntropy = new Array(8);
|
|
|
|
rng.nextBytes(ownerEntropy);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// 1) generate 4 random bytes
|
|
|
|
ownerSalt = new Array(4);
|
|
|
|
|
|
|
|
rng.nextBytes(ownerSalt);
|
|
|
|
|
|
|
|
// 2) Encode the lot and sequence numbers as a 4 byte quantity (big-endian):
|
|
|
|
// lotnumber * 4096 + sequencenumber. Call these four bytes lotsequence.
|
|
|
|
var lotSequence = BigInteger(4096 * lotNum + sequenceNum).toByteArrayUnsigned();
|
|
|
|
|
|
|
|
// 3) Concatenate ownersalt + lotsequence and call this ownerentropy.
|
|
|
|
var ownerEntropy = ownerSalt.concat(lotSequence);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4) Derive a key from the passphrase using scrypt
|
|
|
|
Crypto_scrypt(passphrase, ownerSalt, 16384, 8, 8, 32, function (prefactor) {
|
|
|
|
// Take SHA256(SHA256(prefactor + ownerentropy)) and call this passfactor
|
|
|
|
var passfactorBytes = noNumbers ? prefactor : Bitcoin.Util.dsha256(prefactor.concat(ownerEntropy));
|
|
|
|
var passfactor = BigInteger.fromByteArrayUnsigned(passfactorBytes);
|
|
|
|
|
|
|
|
// 5) Compute the elliptic curve point G * passfactor, and convert the result to compressed notation (33 bytes)
|
|
|
|
var ellipticCurve = EllipticCurve.getSECCurveByName("secp256k1");
|
|
|
|
var passpoint = ellipticCurve.getG().multiply(passfactor).getEncoded(1);
|
|
|
|
|
|
|
|
// 6) Convey ownersalt and passpoint to the party generating the keys, along with a checksum to ensure integrity.
|
|
|
|
// magic bytes "2C E9 B3 E1 FF 39 E2 51" followed by ownerentropy, and then passpoint
|
|
|
|
var magicBytes = [0x2C, 0xE9, 0xB3, 0xE1, 0xFF, 0x39, 0xE2, 0x51];
|
|
|
|
if (noNumbers) magicBytes[7] = 0x53;
|
|
|
|
|
|
|
|
var intermediate = magicBytes.concat(ownerEntropy).concat(passpoint);
|
|
|
|
|
|
|
|
// base58check encode
|
|
|
|
intermediate = intermediate.concat(Bitcoin.Util.dsha256(intermediate).slice(0, 4));
|
|
|
|
callback(Bitcoin.Base58.encode(intermediate));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
BIP38GenerateECAddressAsync: function (intermediate, compressed, callback) {
|
|
|
|
// decode IPS
|
|
|
|
var x = Bitcoin.Base58.decode(intermediate);
|
|
|
|
//if(x.slice(49, 4) !== Bitcoin.Util.dsha256(x.slice(0,49)).slice(0,4)) {
|
|
|
|
// callback({error: 'Invalid intermediate passphrase string'});
|
|
|
|
//}
|
|
|
|
var noNumbers = (x[7] === 0x53);
|
|
|
|
var ownerEntropy = x.slice(8, 8 + 8);
|
|
|
|
var passpoint = x.slice(16, 16 + 33);
|
|
|
|
|
|
|
|
// 1) Set flagbyte.
|
|
|
|
// set bit 0x20 for compressed key
|
|
|
|
// set bit 0x04 if ownerentropy contains a value for lotsequence
|
|
|
|
var flagByte = (compressed ? 0x20 : 0x00) | (noNumbers ? 0x00 : 0x04);
|
|
|
|
|
|
|
|
|
|
|
|
// 2) Generate 24 random bytes, call this seedb.
|
|
|
|
var seedB = new Array(24);
|
|
|
|
var rng = new SecureRandom();
|
|
|
|
rng.nextBytes(seedB);
|
|
|
|
|
|
|
|
// Take SHA256(SHA256(seedb)) to yield 32 bytes, call this factorb.
|
|
|
|
var factorB = Bitcoin.Util.dsha256(seedB);
|
|
|
|
|
|
|
|
// 3) ECMultiply passpoint by factorb. Use the resulting EC point as a public key and hash it into a Bitcoin
|
|
|
|
// address using either compressed or uncompressed public key methodology (specify which methodology is used
|
|
|
|
// inside flagbyte). This is the generated Bitcoin address, call it generatedaddress.
|
|
|
|
var ec = EllipticCurve.getSECCurveByName("secp256k1").getCurve();
|
|
|
|
var generatedPoint = ec.decodePointHex(ninja.publicKey.getHexFromByteArray(passpoint));
|
|
|
|
var generatedBytes = generatedPoint.multiply(BigInteger.fromByteArrayUnsigned(factorB)).getEncoded(compressed);
|
|
|
|
var generatedAddress = (new Bitcoin.Address(Bitcoin.Util.sha256ripe160(generatedBytes))).toString();
|
|
|
|
|
|
|
|
// 4) Take the first four bytes of SHA256(SHA256(generatedaddress)) and call it addresshash.
|
|
|
|
var addressHash = Bitcoin.Util.dsha256(generatedAddress).slice(0, 4);
|
|
|
|
|
|
|
|
// 5) Now we will encrypt seedb. Derive a second key from passpoint using scrypt
|
|
|
|
Crypto_scrypt(passpoint, addressHash.concat(ownerEntropy), 1024, 1, 1, 64, function (derivedBytes) {
|
|
|
|
// 6) Do AES256Encrypt(seedb[0...15]] xor derivedhalf1[0...15], derivedhalf2), call the 16-byte result encryptedpart1
|
|
|
|
for (var i = 0; i < 16; ++i) {
|
|
|
|
seedB[i] ^= derivedBytes[i];
|
|
|
|
}
|
|
|
|
var AES_opts = { mode: new Crypto.mode.ECB(Crypto.pad.NoPadding), asBytes: true };
|
|
|
|
var encryptedPart1 = Crypto.AES.encrypt(seedB.slice(0, 16), derivedBytes.slice(32), AES_opts);
|
|
|
|
|
|
|
|
// 7) Do AES256Encrypt((encryptedpart1[8...15] + seedb[16...23]) xor derivedhalf1[16...31], derivedhalf2), call the 16-byte result encryptedseedb.
|
|
|
|
var message2 = encryptedPart1.slice(8, 8 + 8).concat(seedB.slice(16, 16 + 8));
|
|
|
|
for (var i = 0; i < 16; ++i) {
|
|
|
|
message2[i] ^= derivedBytes[i + 16];
|
|
|
|
}
|
|
|
|
var encryptedSeedB = Crypto.AES.encrypt(message2, derivedBytes.slice(32), AES_opts);
|
|
|
|
|
|
|
|
// 0x01 0x43 + flagbyte + addresshash + ownerentropy + encryptedpart1[0...7] + encryptedpart2
|
|
|
|
var encryptedKey = [0x01, 0x43, flagByte].concat(addressHash).concat(ownerEntropy).concat(encryptedPart1.slice(0, 8)).concat(encryptedSeedB);
|
|
|
|
|
|
|
|
// base58check encode
|
|
|
|
encryptedKey = encryptedKey.concat(Bitcoin.Util.dsha256(encryptedKey).slice(0, 4));
|
|
|
|
callback(generatedAddress, Bitcoin.Base58.encode(encryptedKey));
|
|
|
|
});
|
2013-08-28 04:05:31 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|