Skip to content

Commit 380bbaf

Browse files
Weston SiegenthalerWeston Siegenthaler
Weston Siegenthaler
authored and
Weston Siegenthaler
committed
Integrated support for creating and verifying BIP38 confirmation codes
1 parent e6402fe commit 380bbaf

File tree

1 file changed

+117
-3
lines changed

1 file changed

+117
-3
lines changed

src/bip38.js

+117-3
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,125 @@ Bitcoin.BIP38 = (function () {
279279
// base58check encode
280280
encryptedKey = encryptedKey.concat(Bitcoin.Util.dsha256(encryptedKey).slice(0,4));
281281

282-
result.address = generatedAddress;
283-
result.bip38PrivateKey = Bitcoin.Base58.encode(encryptedKey);
284-
return result;
282+
// Generate confirmation code for the new address
283+
var confirmation = newAddressConfirmation(addressHash, factorB, derivedBytes, flagByte, ownerEntropy);
284+
285+
return { address: generatedAddress,
286+
bip38PrivateKey: Bitcoin.Base58.encode(encryptedKey),
287+
confirmation: confirmation };
288+
};
289+
290+
/**
291+
* Generates a confirmation code for a key/address generated using an intermediate
292+
* ec point (see BIP38.newAddressFromIntermediate). This certifies that the address
293+
* truly corresponds to the password from which the intermediate ec point was derived
294+
* (see BIP38.verifyNewAddressConfirmation).
295+
*/
296+
var newAddressConfirmation = function(addressHash, factorB, derivedBytes, flagByte, ownerEntropy) {
297+
// 1) ECMultiply factorb by G, call the result pointb. The result is 33 bytes.
298+
var pointb = ecparams.getG().multiply(BigInteger.fromByteArrayUnsigned(factorB)).getEncoded(1);
299+
300+
// 2) he first byte is 0x02 or 0x03. XOR it by (derivedhalf2[31] & 0x01), call the resulting byte pointbprefix.
301+
var pointbprefix = pointb[0] ^ (derivedBytes[63] & 0x01);
302+
303+
// 3) Do AES256Encrypt(pointb[1...16] xor derivedhalf1[0...15], derivedhalf2) and call the result pointbx1.
304+
for(var i = 0; i < 16; ++i) {
305+
pointb[i + 1] ^= derivedBytes[i];
306+
}
307+
var pointbx1 = Crypto.AES.encrypt(pointb.slice(1,17), derivedBytes.slice(32), AES_opts);
308+
309+
// 4) Do AES256Encrypt(pointb[17...32] xor derivedhalf1[16...31], derivedhalf2) and call the result pointbx2.
310+
for(var i = 16; i < 32; ++i) {
311+
pointb[i + 1] ^= derivedBytes[i];
312+
}
313+
var pointbx2 = Crypto.AES.encrypt(pointb.slice(17,33), derivedBytes.slice(32), AES_opts);
314+
315+
var encryptedpointb = [ pointbprefix ].concat(pointbx1).concat(pointbx2);
316+
317+
var confirmationPreChecksum =
318+
[ 0x64, 0x3B, 0xF6, 0xA8, 0x9A, flagByte ]
319+
.concat(addressHash)
320+
.concat(ownerEntropy)
321+
.concat(encryptedpointb);
322+
var confirmationBytes = confirmationPreChecksum.concat(Bitcoin.Util.dsha256(confirmationPreChecksum).slice(0,4));
323+
var confirmation = Bitcoin.Base58.encode(confirmationBytes);
324+
325+
return confirmation;
326+
};
327+
328+
/**
329+
* Certifies that the given address was generated using an intermediate ec point derived
330+
* from the given password (see BIP38.newAddressFromIntermediate).
331+
*/
332+
BIP38.verifyNewAddressConfirmation = function(expectedAddressStr, confirmation, passphrase) {
333+
var confirmationResults = BIP38.verifyConfirmation(confirmation, passphrase);
334+
return (confirmationResults.address == expectedAddressStr);
285335
};
286336

337+
/**
338+
* Certifies that the given BIP38 confirmation code matches the password and
339+
* returns the address the confirmation corresponds to (see BIP38.newAddressFromIntermediate).
340+
*/
341+
BIP38.verifyConfirmation = function(confirmation, passphrase) {
342+
var bytes = Bitcoin.Base58.decode(confirmation);
343+
344+
// Get the flag byte (tells us whether address compression is used and whether lot/sequence values are present).
345+
var flagByte = bytes[5];
346+
347+
// Get the address hash.
348+
var addressHash = bytes.slice(6, 10);
349+
350+
// Get the owner entropy (tells us the lot/sequence values when applicable).
351+
var ownerEntropy = bytes.slice(10, 18);
352+
353+
// Get encryptedpointb
354+
var encryptedpointb = bytes.slice(18, 51);
355+
356+
var compressed = (flagByte & 0x20) == 0x20;
357+
var lotSequencePresent = (flagByte & 0x04) == 0x04;
358+
var ownerSalt = ownerEntropy.slice(0, lotSequencePresent ? 4 : 8)
359+
360+
var prefactor = scrypt(passphrase, ownerSalt, scryptParams.passphrase.N, scryptParams.passphrase.r, scryptParams.passphrase.p, 32);
361+
362+
// Take SHA256(SHA256(prefactor + ownerentropy)) and call this passfactor
363+
var passfactorBytes = !lotSequencePresent? prefactor : Bitcoin.Util.dsha256(prefactor.concat(ownerEntropy));
364+
var passfactor = BigInteger.fromByteArrayUnsigned(passfactorBytes);
365+
366+
var passpoint = ecparams.getG().multiply(passfactor).getEncoded(1);
367+
368+
var addresshashplusownerentropy = addressHash.concat(ownerEntropy);
369+
370+
var derivedBytes = scrypt(passpoint, addresshashplusownerentropy, scryptParams.passpoint.N, scryptParams.passpoint.r, scryptParams.passpoint.p, 64);
371+
372+
// recover the 0x02 or 0x03 prefix
373+
var unencryptedpubkey = [];
374+
unencryptedpubkey[0] = encryptedpointb[0] ^ (derivedBytes[63] & 0x01);
375+
376+
decrypted1 = Crypto.AES.decrypt(encryptedpointb.slice(1,17), derivedBytes.slice(32), AES_opts);
377+
decrypted2 = Crypto.AES.decrypt(encryptedpointb.slice(17,33), derivedBytes.slice(32), AES_opts);
378+
decrypted = unencryptedpubkey.concat(decrypted1).concat(decrypted2);
379+
380+
for (var x = 0; x < 32; x++) {
381+
decrypted[x+1] ^= derivedBytes[x];
382+
}
383+
384+
var ec = ecparams.getCurve();
385+
var generatedPoint = ec.decodePointHex(Crypto.util.bytesToHex(decrypted).toString().toUpperCase());
386+
var generatedBytes = generatedPoint.multiply(BigInteger.fromByteArrayUnsigned(passfactor)).getEncoded(compressed);
387+
var generatedAddress = (new Bitcoin.Address(Bitcoin.Util.sha256ripe160(generatedBytes))).toString();
388+
389+
var generatedAddressHash = Bitcoin.Util.dsha256(generatedAddress).slice(0,4);
390+
391+
var valid = true;
392+
for (var i = 0; i < 4; i++) {
393+
if (addressHash[i] != generatedAddressHash[i]) {
394+
valid = false;
395+
}
396+
}
397+
398+
return { valid: valid, address: generatedAddress };
399+
}
400+
287401
/**
288402
* Detects keys encrypted according to BIP-38 (58 base58 characters starting with 6P)
289403
*/

0 commit comments

Comments
 (0)