Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ultraviolet
GitHub Repository: ultraviolet/bitaddress.org
Path: blob/master/src/ninja.key.js
248 views
1
var ninja = { wallets: {} };
2
3
ninja.privateKey = {
4
isPrivateKey: function (key) {
5
return (
6
Bitcoin.ECKey.isWalletImportFormat(key) ||
7
Bitcoin.ECKey.isCompressedWalletImportFormat(key) ||
8
Bitcoin.ECKey.isHexFormat(key) ||
9
Bitcoin.ECKey.isBase64Format(key) ||
10
Bitcoin.ECKey.isMiniFormat(key)
11
);
12
},
13
getECKeyFromAdding: function (privKey1, privKey2) {
14
var n = EllipticCurve.getSECCurveByName("secp256k1").getN();
15
var ecKey1 = new Bitcoin.ECKey(privKey1);
16
var ecKey2 = new Bitcoin.ECKey(privKey2);
17
// if both keys are the same return null
18
if (ecKey1.getBitcoinHexFormat() == ecKey2.getBitcoinHexFormat()) return null;
19
if (ecKey1 == null || ecKey2 == null) return null;
20
var combinedPrivateKey = new Bitcoin.ECKey(ecKey1.priv.add(ecKey2.priv).mod(n));
21
// compressed when both keys are compressed
22
if (ecKey1.compressed && ecKey2.compressed) combinedPrivateKey.setCompressed(true);
23
return combinedPrivateKey;
24
},
25
getECKeyFromMultiplying: function (privKey1, privKey2) {
26
var n = EllipticCurve.getSECCurveByName("secp256k1").getN();
27
var ecKey1 = new Bitcoin.ECKey(privKey1);
28
var ecKey2 = new Bitcoin.ECKey(privKey2);
29
// if both keys are the same return null
30
if (ecKey1.getBitcoinHexFormat() == ecKey2.getBitcoinHexFormat()) return null;
31
if (ecKey1 == null || ecKey2 == null) return null;
32
var combinedPrivateKey = new Bitcoin.ECKey(ecKey1.priv.multiply(ecKey2.priv).mod(n));
33
// compressed when both keys are compressed
34
if (ecKey1.compressed && ecKey2.compressed) combinedPrivateKey.setCompressed(true);
35
return combinedPrivateKey;
36
},
37
// 58 base58 characters starting with 6P
38
isBIP38Format: function (key) {
39
key = key.toString();
40
return (/^6P[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{56}$/.test(key));
41
},
42
BIP38EncryptedKeyToByteArrayAsync: function (base58Encrypted, passphrase, callback) {
43
var hex;
44
try {
45
hex = Bitcoin.Base58.decode(base58Encrypted);
46
} catch (e) {
47
callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey")));
48
return;
49
}
50
51
// 43 bytes: 2 bytes prefix, 37 bytes payload, 4 bytes checksum
52
if (hex.length != 43) {
53
callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey")));
54
return;
55
}
56
// first byte is always 0x01
57
else if (hex[0] != 0x01) {
58
callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey")));
59
return;
60
}
61
62
var expChecksum = hex.slice(-4);
63
hex = hex.slice(0, -4);
64
var checksum = Bitcoin.Util.dsha256(hex);
65
if (checksum[0] != expChecksum[0] || checksum[1] != expChecksum[1] || checksum[2] != expChecksum[2] || checksum[3] != expChecksum[3]) {
66
callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey")));
67
return;
68
}
69
70
var isCompPoint = false;
71
var isECMult = false;
72
var hasLotSeq = false;
73
// second byte for non-EC-multiplied key
74
if (hex[1] == 0x42) {
75
// key should use compression
76
if (hex[2] == 0xe0) {
77
isCompPoint = true;
78
}
79
// key should NOT use compression
80
else if (hex[2] != 0xc0) {
81
callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey")));
82
return;
83
}
84
}
85
// second byte for EC-multiplied key
86
else if (hex[1] == 0x43) {
87
isECMult = true;
88
isCompPoint = (hex[2] & 0x20) != 0;
89
hasLotSeq = (hex[2] & 0x04) != 0;
90
if ((hex[2] & 0x24) != hex[2]) {
91
callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey")));
92
return;
93
}
94
}
95
else {
96
callback(new Error(ninja.translator.get("detailalertnotvalidprivatekey")));
97
return;
98
}
99
100
var decrypted;
101
var AES_opts = { mode: new Crypto.mode.ECB(Crypto.pad.NoPadding), asBytes: true };
102
103
var verifyHashAndReturn = function () {
104
var tmpkey = new Bitcoin.ECKey(decrypted); // decrypted using closure
105
var base58AddrText = tmpkey.setCompressed(isCompPoint).getBitcoinAddress(); // isCompPoint using closure
106
checksum = Bitcoin.Util.dsha256(base58AddrText); // checksum using closure
107
108
if (checksum[0] != hex[3] || checksum[1] != hex[4] || checksum[2] != hex[5] || checksum[3] != hex[6]) {
109
callback(new Error(ninja.translator.get("bip38alertincorrectpassphrase"))); // callback using closure
110
return;
111
}
112
callback(tmpkey.getBitcoinPrivateKeyByteArray()); // callback using closure
113
};
114
115
if (!isECMult) {
116
var addresshash = hex.slice(3, 7);
117
Crypto_scrypt(passphrase, addresshash, 16384, 8, 8, 64, function (derivedBytes) {
118
var k = derivedBytes.slice(32, 32 + 32);
119
decrypted = Crypto.AES.decrypt(hex.slice(7, 7 + 32), k, AES_opts);
120
for (var x = 0; x < 32; x++) decrypted[x] ^= derivedBytes[x];
121
verifyHashAndReturn(); //TODO: pass in 'decrypted' as a param
122
});
123
}
124
else {
125
var ownerentropy = hex.slice(7, 7 + 8);
126
var ownersalt = !hasLotSeq ? ownerentropy : ownerentropy.slice(0, 4);
127
Crypto_scrypt(passphrase, ownersalt, 16384, 8, 8, 32, function (prefactorA) {
128
var passfactor;
129
if (!hasLotSeq) { // hasLotSeq using closure
130
passfactor = prefactorA;
131
} else {
132
var prefactorB = prefactorA.concat(ownerentropy); // ownerentropy using closure
133
passfactor = Bitcoin.Util.dsha256(prefactorB);
134
}
135
// remove this ECKey from the pool (because user does not see it)
136
var userKeyPool = Bitcoin.KeyPool.getArray();
137
var kp = new Bitcoin.ECKey(passfactor);
138
var passpoint = kp.setCompressed(true).getPub();
139
Bitcoin.KeyPool.setArray(userKeyPool);
140
var encryptedpart2 = hex.slice(23, 23 + 16);
141
142
var addresshashplusownerentropy = hex.slice(3, 3 + 12);
143
Crypto_scrypt(passpoint, addresshashplusownerentropy, 1024, 1, 1, 64, function (derived) {
144
var k = derived.slice(32);
145
146
var unencryptedpart2 = Crypto.AES.decrypt(encryptedpart2, k, AES_opts);
147
for (var i = 0; i < 16; i++) { unencryptedpart2[i] ^= derived[i + 16]; }
148
149
var encryptedpart1 = hex.slice(15, 15 + 8).concat(unencryptedpart2.slice(0, 0 + 8));
150
var unencryptedpart1 = Crypto.AES.decrypt(encryptedpart1, k, AES_opts);
151
for (var i = 0; i < 16; i++) { unencryptedpart1[i] ^= derived[i]; }
152
153
var seedb = unencryptedpart1.slice(0, 0 + 16).concat(unencryptedpart2.slice(8, 8 + 8));
154
155
var factorb = Bitcoin.Util.dsha256(seedb);
156
157
var ps = EllipticCurve.getSECCurveByName("secp256k1");
158
var privateKey = BigInteger.fromByteArrayUnsigned(passfactor).multiply(BigInteger.fromByteArrayUnsigned(factorb)).remainder(ps.getN());
159
160
decrypted = privateKey.toByteArrayUnsigned();
161
verifyHashAndReturn();
162
});
163
});
164
}
165
},
166
BIP38PrivateKeyToEncryptedKeyAsync: function (base58Key, passphrase, compressed, callback) {
167
var privKey = new Bitcoin.ECKey(base58Key);
168
var privKeyBytes = privKey.getBitcoinPrivateKeyByteArray();
169
var address = privKey.setCompressed(compressed).getBitcoinAddress();
170
171
// compute sha256(sha256(address)) and take first 4 bytes
172
var salt = Bitcoin.Util.dsha256(address).slice(0, 4);
173
174
// derive key using scrypt
175
var AES_opts = { mode: new Crypto.mode.ECB(Crypto.pad.NoPadding), asBytes: true };
176
177
Crypto_scrypt(passphrase, salt, 16384, 8, 8, 64, function (derivedBytes) {
178
for (var i = 0; i < 32; ++i) {
179
privKeyBytes[i] ^= derivedBytes[i];
180
}
181
182
// 0x01 0x42 + flagbyte + salt + encryptedhalf1 + encryptedhalf2
183
var flagByte = compressed ? 0xe0 : 0xc0;
184
var encryptedKey = [0x01, 0x42, flagByte].concat(salt);
185
encryptedKey = encryptedKey.concat(Crypto.AES.encrypt(privKeyBytes, derivedBytes.slice(32), AES_opts));
186
encryptedKey = encryptedKey.concat(Bitcoin.Util.dsha256(encryptedKey).slice(0, 4));
187
callback(Bitcoin.Base58.encode(encryptedKey));
188
});
189
},
190
BIP38GenerateIntermediatePointAsync: function (passphrase, lotNum, sequenceNum, callback) {
191
var noNumbers = lotNum === null || sequenceNum === null;
192
var rng = new SecureRandom();
193
var ownerEntropy, ownerSalt;
194
195
if (noNumbers) {
196
ownerSalt = ownerEntropy = new Array(8);
197
rng.nextBytes(ownerEntropy);
198
}
199
else {
200
// 1) generate 4 random bytes
201
ownerSalt = new Array(4);
202
203
rng.nextBytes(ownerSalt);
204
205
// 2) Encode the lot and sequence numbers as a 4 byte quantity (big-endian):
206
// lotnumber * 4096 + sequencenumber. Call these four bytes lotsequence.
207
var lotSequence = BigInteger(4096 * lotNum + sequenceNum).toByteArrayUnsigned();
208
209
// 3) Concatenate ownersalt + lotsequence and call this ownerentropy.
210
var ownerEntropy = ownerSalt.concat(lotSequence);
211
}
212
213
214
// 4) Derive a key from the passphrase using scrypt
215
Crypto_scrypt(passphrase, ownerSalt, 16384, 8, 8, 32, function (prefactor) {
216
// Take SHA256(SHA256(prefactor + ownerentropy)) and call this passfactor
217
var passfactorBytes = noNumbers ? prefactor : Bitcoin.Util.dsha256(prefactor.concat(ownerEntropy));
218
var passfactor = BigInteger.fromByteArrayUnsigned(passfactorBytes);
219
220
// 5) Compute the elliptic curve point G * passfactor, and convert the result to compressed notation (33 bytes)
221
var ellipticCurve = EllipticCurve.getSECCurveByName("secp256k1");
222
var passpoint = ellipticCurve.getG().multiply(passfactor).getEncoded(1);
223
224
// 6) Convey ownersalt and passpoint to the party generating the keys, along with a checksum to ensure integrity.
225
// magic bytes "2C E9 B3 E1 FF 39 E2 51" followed by ownerentropy, and then passpoint
226
var magicBytes = [0x2C, 0xE9, 0xB3, 0xE1, 0xFF, 0x39, 0xE2, 0x51];
227
if (noNumbers) magicBytes[7] = 0x53;
228
229
var intermediate = magicBytes.concat(ownerEntropy).concat(passpoint);
230
231
// base58check encode
232
intermediate = intermediate.concat(Bitcoin.Util.dsha256(intermediate).slice(0, 4));
233
callback(Bitcoin.Base58.encode(intermediate));
234
});
235
},
236
BIP38GenerateECAddressAsync: function (intermediate, compressed, callback) {
237
// decode IPS
238
var x = Bitcoin.Base58.decode(intermediate);
239
//if(x.slice(49, 4) !== Bitcoin.Util.dsha256(x.slice(0,49)).slice(0,4)) {
240
// callback({error: 'Invalid intermediate passphrase string'});
241
//}
242
var noNumbers = (x[7] === 0x53);
243
var ownerEntropy = x.slice(8, 8 + 8);
244
var passpoint = x.slice(16, 16 + 33);
245
246
// 1) Set flagbyte.
247
// set bit 0x20 for compressed key
248
// set bit 0x04 if ownerentropy contains a value for lotsequence
249
var flagByte = (compressed ? 0x20 : 0x00) | (noNumbers ? 0x00 : 0x04);
250
251
252
// 2) Generate 24 random bytes, call this seedb.
253
var seedB = new Array(24);
254
var rng = new SecureRandom();
255
rng.nextBytes(seedB);
256
257
// Take SHA256(SHA256(seedb)) to yield 32 bytes, call this factorb.
258
var factorB = Bitcoin.Util.dsha256(seedB);
259
260
// 3) ECMultiply passpoint by factorb. Use the resulting EC point as a public key and hash it into a Bitcoin
261
// address using either compressed or uncompressed public key methodology (specify which methodology is used
262
// inside flagbyte). This is the generated Bitcoin address, call it generatedaddress.
263
var ec = EllipticCurve.getSECCurveByName("secp256k1").getCurve();
264
var generatedPoint = ec.decodePointHex(ninja.publicKey.getHexFromByteArray(passpoint));
265
var generatedBytes = generatedPoint.multiply(BigInteger.fromByteArrayUnsigned(factorB)).getEncoded(compressed);
266
var generatedAddress = (new Bitcoin.Address(Bitcoin.Util.sha256ripe160(generatedBytes))).toString();
267
268
// 4) Take the first four bytes of SHA256(SHA256(generatedaddress)) and call it addresshash.
269
var addressHash = Bitcoin.Util.dsha256(generatedAddress).slice(0, 4);
270
271
// 5) Now we will encrypt seedb. Derive a second key from passpoint using scrypt
272
Crypto_scrypt(passpoint, addressHash.concat(ownerEntropy), 1024, 1, 1, 64, function (derivedBytes) {
273
// 6) Do AES256Encrypt(seedb[0...15]] xor derivedhalf1[0...15], derivedhalf2), call the 16-byte result encryptedpart1
274
for (var i = 0; i < 16; ++i) {
275
seedB[i] ^= derivedBytes[i];
276
}
277
var AES_opts = { mode: new Crypto.mode.ECB(Crypto.pad.NoPadding), asBytes: true };
278
var encryptedPart1 = Crypto.AES.encrypt(seedB.slice(0, 16), derivedBytes.slice(32), AES_opts);
279
280
// 7) Do AES256Encrypt((encryptedpart1[8...15] + seedb[16...23]) xor derivedhalf1[16...31], derivedhalf2), call the 16-byte result encryptedseedb.
281
var message2 = encryptedPart1.slice(8, 8 + 8).concat(seedB.slice(16, 16 + 8));
282
for (var i = 0; i < 16; ++i) {
283
message2[i] ^= derivedBytes[i + 16];
284
}
285
var encryptedSeedB = Crypto.AES.encrypt(message2, derivedBytes.slice(32), AES_opts);
286
287
// 0x01 0x43 + flagbyte + addresshash + ownerentropy + encryptedpart1[0...7] + encryptedpart2
288
var encryptedKey = [0x01, 0x43, flagByte].concat(addressHash).concat(ownerEntropy).concat(encryptedPart1.slice(0, 8)).concat(encryptedSeedB);
289
290
// base58check encode
291
encryptedKey = encryptedKey.concat(Bitcoin.Util.dsha256(encryptedKey).slice(0, 4));
292
callback(generatedAddress, Bitcoin.Base58.encode(encryptedKey));
293
});
294
}
295
};
296
297
ninja.publicKey = {
298
isPublicKeyHexFormat: function (key) {
299
key = key.toString();
300
return ninja.publicKey.isUncompressedPublicKeyHexFormat(key) || ninja.publicKey.isCompressedPublicKeyHexFormat(key);
301
},
302
// 130 characters [0-9A-F] starts with 04
303
isUncompressedPublicKeyHexFormat: function (key) {
304
key = key.toString();
305
return /^04[A-Fa-f0-9]{128}$/.test(key);
306
},
307
// 66 characters [0-9A-F] starts with 02 or 03
308
isCompressedPublicKeyHexFormat: function (key) {
309
key = key.toString();
310
return /^0[2-3][A-Fa-f0-9]{64}$/.test(key);
311
},
312
getBitcoinAddressFromByteArray: function (pubKeyByteArray) {
313
var pubKeyHash = Bitcoin.Util.sha256ripe160(pubKeyByteArray);
314
var addr = new Bitcoin.Address(pubKeyHash);
315
return addr.toString();
316
},
317
getHexFromByteArray: function (pubKeyByteArray) {
318
return Crypto.util.bytesToHex(pubKeyByteArray).toString().toUpperCase();
319
},
320
getByteArrayFromAdding: function (pubKeyHex1, pubKeyHex2) {
321
var ecparams = EllipticCurve.getSECCurveByName("secp256k1");
322
var curve = ecparams.getCurve();
323
var ecPoint1 = curve.decodePointHex(pubKeyHex1);
324
var ecPoint2 = curve.decodePointHex(pubKeyHex2);
325
// if both points are the same return null
326
if (ecPoint1.equals(ecPoint2)) return null;
327
var compressed = (ecPoint1.compressed && ecPoint2.compressed);
328
var pubKey = ecPoint1.add(ecPoint2).getEncoded(compressed);
329
return pubKey;
330
},
331
getByteArrayFromMultiplying: function (pubKeyHex, ecKey) {
332
var ecparams = EllipticCurve.getSECCurveByName("secp256k1");
333
var ecPoint = ecparams.getCurve().decodePointHex(pubKeyHex);
334
var compressed = (ecPoint.compressed && ecKey.compressed);
335
// if both points are the same return null
336
ecKey.setCompressed(false);
337
if (ecPoint.equals(ecKey.getPubPoint())) {
338
return null;
339
}
340
var bigInt = ecKey.priv;
341
var pubKey = ecPoint.multiply(bigInt).getEncoded(compressed);
342
return pubKey;
343
},
344
// used by unit test
345
getDecompressedPubKeyHex: function (pubKeyHexComp) {
346
var ecparams = EllipticCurve.getSECCurveByName("secp256k1");
347
var ecPoint = ecparams.getCurve().decodePointHex(pubKeyHexComp);
348
var pubByteArray = ecPoint.getEncoded(0);
349
var pubHexUncompressed = ninja.publicKey.getHexFromByteArray(pubByteArray);
350
return pubHexUncompressed;
351
}
352
};
353