v2.5.1 BIP38 passphrase protected paper wallets. Thanks to casascius, scintill, Zeilap.
This commit is contained in:
parent
a19c436fff
commit
5453778cac
8 changed files with 804 additions and 116 deletions
File diff suppressed because one or more lines are too long
|
@ -165,11 +165,19 @@
|
|||
<div id="paperarea">
|
||||
<div id="papercommands" class="commands">
|
||||
<span><label id="paperlabelhideart">Hide Art?</label> <input type="checkbox" id="paperart" onchange="ninja.wallets.paperwallet.toggleArt(this);" /></span>
|
||||
<span><label id="paperlabeladdressesperpage">Addresses per page:</label> <input type="text" id="paperlimitperpage" /></span>
|
||||
<span><label id="paperlabeladdressestogenerate">Addresses to generate:</label> <input type="text" id="paperlimit" /></span>
|
||||
<span><input type="button" id="papergenerate" value="Generate" onclick="ninja.wallets.paperwallet.build(document.getElementById('paperlimit').value * 1, document.getElementById('paperlimitperpage').value * 1, !document.getElementById('paperart').checked);" /></span>
|
||||
<span><input type="button" id="papergenerate" value="Generate" onclick="ninja.wallets.paperwallet.build(document.getElementById('paperlimit').value * 1, document.getElementById('paperlimitperpage').value * 1, !document.getElementById('paperart').checked, document.getElementById('paperpassphrase').value);" /></span>
|
||||
<span class="print"><input type="button" name="print" value="Print" id="paperprint" onclick="window.print();" /></span>
|
||||
</div>
|
||||
<div id="paperadvancedcommandsexpandable" class="commands expandable sideborders" onclick="ninja.wallets.paperwallet.openCloseAdvanced();">
|
||||
<span><label id="paperadvancedcommandslabel">Advanced Options</label></span>
|
||||
<div id="paperadvancedcommandsicon" class="more"></div>
|
||||
</div>
|
||||
<div id="paperadvancedcommands" class="commands sideborders hide">
|
||||
<span><label id="paperlabelencrypt">BIP38 Encrypt?</label> <input type="checkbox" id="paperencrypt" onchange="ninja.wallets.paperwallet.toggleEncrypt(this);" /></span>
|
||||
<span><label id="paperlabelBIPpassphrase">Passphrase:</label> <input type="text" id="paperpassphrase" /></span>
|
||||
<span><label id="paperlabeladdressesperpage">Addresses per page:</label> <input type="text" id="paperlimitperpage" /></span>
|
||||
</div>
|
||||
<div id="paperkeyarea"></div>
|
||||
</div>
|
||||
|
||||
|
@ -380,8 +388,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="authorpgp">
|
||||
<span class="item"><a href="ninja_bitaddress.org.txt" target="_blank" id="footerlabelpgp">PGP Public Key</a></span>
|
||||
<span class="item"><a href="pgpsignedmsg.txt" target="_blank"><span id="footerlabelversion">Signed Version History</span> (v2.5)</a></span>
|
||||
<span class="item">
|
||||
<a href="pgpsignedmsg.txt" target="_blank"><span id="footerlabelversion">Version History</span> (v2.5.1)</a>
|
||||
(<a href="ninja_bitaddress.org.txt" target="_blank" id="footerlabelpgp">PGP</a>)
|
||||
</span>
|
||||
<span class="item">527B 5C82 B1F6 B2DB 72A0<br />ECBF 8749 7B91 6397 4F5A</span>
|
||||
<span class="item"><a href="https://github.com/pointbiz/bitaddress.org" target="_blank" id="footerlabelgithub">GitHub Repository</a></span>
|
||||
</div>
|
||||
<div class="copyright">
|
||||
|
|
13
src/main.css
13
src/main.css
File diff suppressed because one or more lines are too long
130
src/ninja.key.js
130
src/ninja.key.js
|
@ -36,6 +36,7 @@ ninja.privateKey = {
|
|||
},
|
||||
// 58 base58 characters starting with 6P
|
||||
isBIP38Format: function (key) {
|
||||
key = key.toString();
|
||||
return (/^6P[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{56}$/.test(key));
|
||||
},
|
||||
BIP38EncryptedKeyToByteArrayAsync: function (base58Encrypted, passphrase, callback) {
|
||||
|
@ -159,6 +160,135 @@ ninja.privateKey = {
|
|||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
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));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,12 +7,10 @@ if (ninja.getQueryString()["unittests"] == "true" || ninja.getQueryString()["uni
|
|||
if (ninja.getQueryString()["asyncunittests"] == "true" || ninja.getQueryString()["asyncunittests"] == "1") {
|
||||
ninja.unitTests.runAsynchronousTests();
|
||||
}
|
||||
|
||||
// change language
|
||||
if (ninja.getQueryString()["culture"] != undefined) {
|
||||
ninja.translator.translate(ninja.getQueryString()["culture"]);
|
||||
}
|
||||
|
||||
// testnet, check if testnet edition should be activated
|
||||
if (ninja.getQueryString()["testnet"] == "true" || ninja.getQueryString()["testnet"] == "1") {
|
||||
document.getElementById("testnet").innerHTML = ninja.translator.get("testneteditionactivated");
|
||||
|
@ -23,6 +21,5 @@ if (ninja.getQueryString()["testnet"] == "true" || ninja.getQueryString()["testn
|
|||
Bitcoin.ECKey.privateKeyPrefix = 0xEF; // testnet
|
||||
ninja.testnetMode = true;
|
||||
}
|
||||
|
||||
// if users does not move mouse after random amount of time then generate the key anyway.
|
||||
setTimeout(ninja.seeder.forceGenerate, ninja.seeder.seedLimit * 20);
|
File diff suppressed because one or more lines are too long
|
@ -34,6 +34,7 @@ ninja.translator = {
|
|||
"testneteditionactivated": "TESTNET EDITION ACTIVATED",
|
||||
"paperlabelbitcoinaddress": "Bitcoin Address:",
|
||||
"paperlabelprivatekey": "Private Key (Wallet Import Format):",
|
||||
"paperlabelencryptedkey": "Encrypted Private Key (Password required)",
|
||||
"bulkgeneratingaddresses": "Generating addresses... ",
|
||||
"brainalertpassphrasetooshort": "The passphrase you entered is too short.\n\nWarning: Choosing a strong passphrase is important to avoid brute force attempts to guess your passphrase and steal your bitcoins.",
|
||||
"brainalertpassphrasedoesnotmatch": "The passphrase does not match the confirm passphrase.",
|
||||
|
@ -53,6 +54,7 @@ ninja.translator = {
|
|||
"testneteditionactivated": "Testnet se activa",
|
||||
"paperlabelbitcoinaddress": "Dirección Bitcoin:",
|
||||
"paperlabelprivatekey": "Clave privada (formato para importar):",
|
||||
"paperlabelencryptedkey": "Clave privada cifrada (contraseña necesaria)",
|
||||
"bulkgeneratingaddresses": "Generación de direcciones... ",
|
||||
"brainalertpassphrasetooshort": "La contraseña introducida es demasiado corta.\n\nAviso: Es importante escoger una contraseña fuerte para evitar ataques de fuerza bruta a fin de adivinarla y robar tus bitcoins.",
|
||||
"brainalertpassphrasedoesnotmatch": "Las contraseñas no coinciden.",
|
||||
|
@ -80,7 +82,7 @@ ninja.translator = {
|
|||
// footer html
|
||||
"footerlabeldonations": "Donaciones:",
|
||||
"footerlabeltranslatedby": "Traducción: <b>12345</b>Vypv2QSmuRXcciT5oEB27mPbWGeva",
|
||||
"footerlabelpgp": "Clave pública PGP",
|
||||
"footerlabelpgp": "PGP",
|
||||
"footerlabelversion": "Histórico de versiones",
|
||||
"footerlabelgithub": "Repositorio GitHub",
|
||||
"footerlabelcopyright1": "Copyright bitaddress.org.",
|
||||
|
@ -99,6 +101,9 @@ ninja.translator = {
|
|||
"paperlabeladdressestogenerate": "Direcciones en total:",
|
||||
"papergenerate": "Generar",
|
||||
"paperprint": "Imprimir",
|
||||
"paperlabelBIPpassphrase": "Passphrase:", //TODO: please translate
|
||||
"paperlabelencrypt": "BIP38 Encrypt?", //TODO: please translate
|
||||
"paperadvancedcommandslabel": "Advanced Options", //TODO: please translate
|
||||
|
||||
// bulk wallet html
|
||||
"bulklabelstartindex": "Empezar en:",
|
||||
|
@ -173,6 +178,7 @@ ninja.translator = {
|
|||
"testneteditionactivated": "ÉDITION TESTNET ACTIVÉE",
|
||||
"paperlabelbitcoinaddress": "Adresse Bitcoin:",
|
||||
"paperlabelprivatekey": "Clé Privée (Format d'importation de porte-monnaie):",
|
||||
"paperlabelencryptedkey": "Encrypted Private Key (Password required)", //TODO: please translate
|
||||
"bulkgeneratingaddresses": "Création de l'adresse... ",
|
||||
"brainalertpassphrasetooshort": "Le mot de passe que vous avez entré est trop court.\n\nAttention: Choisir un mot de passe solide est important pour vous protéger des attaques bruteforce visant à trouver votre mot de passe et voler vos Bitcoins.",
|
||||
"brainalertpassphrasedoesnotmatch": "Le mot de passe ne correspond pas au mot de passe de vérification.",
|
||||
|
@ -196,8 +202,8 @@ ninja.translator = {
|
|||
"detailwallet": "Détails du Porte-Monnaie",
|
||||
"footerlabeldonations": "Dons:",
|
||||
"footerlabeltranslatedby": "Traduction: 1Gy7NYSJNUYqUdXTBow5d7bCUEJkUFDFSq",
|
||||
"footerlabelpgp": "Clé Publique PGP",
|
||||
"footerlabelversion": "Historique De Version Signé",
|
||||
"footerlabelpgp": "PGP",
|
||||
"footerlabelversion": "Historique De Version",
|
||||
"footerlabelgithub": "Dépôt GitHub",
|
||||
"footerlabelcopyright1": "Copyright bitaddress.org.",
|
||||
"footerlabelcopyright2": "Les droits d'auteurs JavaScript sont inclus dans le code source.",
|
||||
|
@ -211,6 +217,9 @@ ninja.translator = {
|
|||
"paperlabeladdressestogenerate": "Nombre d'adresses à créer:",
|
||||
"papergenerate": "Générer",
|
||||
"paperprint": "Imprimer",
|
||||
"paperlabelBIPpassphrase": "Passphrase:", //TODO: please translate
|
||||
"paperlabelencrypt": "BIP38 Encrypt?", //TODO: please translate
|
||||
"paperadvancedcommandslabel": "Advanced Options", //TODO: please translate
|
||||
"bulklabelstartindex": "Commencer à l'index:",
|
||||
"bulklabelrowstogenerate": "Colonnes à générer:",
|
||||
"bulklabelcompressed": "Compressed addresses?", //TODO: please translate
|
||||
|
@ -278,6 +287,7 @@ ninja.translator = {
|
|||
"testneteditionactivated": "ΕΝΕΡΓΗ ΕΚΔΟΣΗ TESTNET",
|
||||
"paperlabelbitcoinaddress": "Διεύθυνση Bitcoin:",
|
||||
"paperlabelprivatekey": "Προσωπικό Κλειδί (Μορφή εισαγωγής σε πορτοφόλι):",
|
||||
"paperlabelencryptedkey": "Encrypted Private Key (Password required)", //TODO: please translate
|
||||
"bulkgeneratingaddresses": "Δημιουργία διευθύνσεων... ",
|
||||
"brainalertpassphrasetooshort": "Η φράση κωδικός που δώσατε είναι πολύ αδύναμη.\n\nΠροσοχή: Είναι σημαντικό να επιλέξετε μια ισχυρή φράση κωδικό που θα σας προφυλάξει από απόπειρες παραβίασής της τύπου brute force και κλοπή των bitcoins σας.",
|
||||
"brainalertpassphrasedoesnotmatch": "Η φράση κωδικός και η επιβεβαίωση της δε συμφωνούν.",
|
||||
|
@ -305,8 +315,8 @@ ninja.translator = {
|
|||
// footer html
|
||||
"footerlabeldonations": "Δωρεές:",
|
||||
"footerlabeltranslatedby": "Μετάφραση: <a href='http://BitcoinX.gr/'><b>BitcoinX.gr</b></a> 1BitcoiNxkUPcTFxwMqxhRiPEiQRzYskf6",
|
||||
"footerlabelpgp": "Δημόσιο Κλειδί PGP",
|
||||
"footerlabelversion": "Υπογεγραμένο ιστορικό εκδόσεων",
|
||||
"footerlabelpgp": "PGP",
|
||||
"footerlabelversion": "ιστορικό εκδόσεων",
|
||||
"footerlabelgithub": "Αποθετήριο GitHub",
|
||||
"footerlabelcopyright1": "Copyright bitaddress.org.",
|
||||
"footerlabelcopyright2": "Τα πνευματικά δικαιώματα της JavaScript περιλαμβάνονται στον κώδικα.",
|
||||
|
@ -324,6 +334,9 @@ ninja.translator = {
|
|||
"paperlabeladdressestogenerate": "Πλήθος διευθύνσεων:",
|
||||
"papergenerate": "Δημιουργία",
|
||||
"paperprint": "Εκτύπωση",
|
||||
"paperlabelBIPpassphrase": "Passphrase:", //TODO: please translate
|
||||
"paperlabelencrypt": "BIP38 Encrypt?", //TODO: please translate
|
||||
"paperadvancedcommandslabel": "Advanced Options", //TODO: please translate
|
||||
|
||||
// bulk wallet html
|
||||
"bulklabelstartindex": "Ξεκίνημα δείκτη:",
|
||||
|
@ -423,7 +436,7 @@ ninja.translator.showEnglishJson = function () {
|
|||
div.setAttribute("class", "englishjson");
|
||||
div.innerHTML = "<h3>English Json</h3>";
|
||||
var elem = document.createElement("textarea");
|
||||
elem.setAttribute("rows", "35");
|
||||
elem.setAttribute("rows", "15");
|
||||
elem.setAttribute("cols", "110");
|
||||
elem.setAttribute("wrap", "off");
|
||||
var langJson = "{\n";
|
||||
|
|
|
@ -38,12 +38,18 @@
|
|||
},
|
||||
|
||||
runAsynchronousTests: function () {
|
||||
document.getElementById("busyblock").className = "busy";
|
||||
var div = document.createElement("div");
|
||||
div.setAttribute("class", "unittests");
|
||||
div.setAttribute("id", "asyncunittests");
|
||||
div.innerHTML = "<h3>Async Unit Tests</h3><div id=\"asyncunittestresults\"></div><br/><br/><br/><br/>";
|
||||
document.body.appendChild(div);
|
||||
|
||||
// run the asynchronous tests one after another so we don't crash the browser
|
||||
ninja.foreachSerialized(ninja.unitTests.asynchronousTests, function (name, cb) {
|
||||
document.getElementById("busyblock").className = "busy";
|
||||
ninja.unitTests.asynchronousTests[name](cb);
|
||||
}, function () {
|
||||
document.getElementById("unittestresults").innerHTML += "running of asynchronous unit tests complete!<br/>";
|
||||
document.getElementById("asyncunittestresults").innerHTML += "running of asynchronous unit tests complete!<br/>";
|
||||
document.getElementById("busyblock").className = "";
|
||||
});
|
||||
},
|
||||
|
@ -458,44 +464,132 @@
|
|||
},
|
||||
|
||||
asynchronousTests: {
|
||||
//https://en.bitcoin.it/wiki/BIP_0038
|
||||
testBip38: function (done) {
|
||||
var tests = [["6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", "TestingOneTwoThree", "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"],
|
||||
["6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq", "Satoshi", "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"],
|
||||
["6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo", "TestingOneTwoThree", "L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP"],
|
||||
["6PYLtMnXvfG3oJde97zRyLYFZCYizPU5T3LwgdYJz1fRhh16bU7u6PPmY7", "Satoshi", "KwYgW8gcxj1JWJXhPSu4Fqwzfhp5Yfi42mdYmMa4XqK7NJxXUSK7"],
|
||||
["6PfQu77ygVyJLZjfvMLyhLMQbYnu5uguoJJ4kMCLqWwPEdfpwANVS76gTX", "TestingOneTwoThree", "5K4caxezwjGCGfnoPTZ8tMcJBLB7Jvyjv4xxeacadhq8nLisLR2"],
|
||||
["6PfLGnQs6VZnrNpmVKfjotbnQuaJK4KZoPFrAjx1JMJUa1Ft8gnf5WxfKd", "Satoshi", "5KJ51SgxWaAYR13zd9ReMhJpwrcX47xTJh2D3fGPG9CM8vkv5sH"],
|
||||
["6PgNBNNzDkKdhkT6uJntUXwwzQV8Rr2tZcbkDcuC9DZRsS6AtHts4Ypo1j", "MOLON LABE", "5JLdxTtcTHcfYcmJsNVy1v2PMDx432JPoYcBTVVRHpPaxUrdtf8"],
|
||||
["6PgGWtx25kUg8QWvwuJAgorN6k9FbE25rv5dMRwu5SKMnfpfVe5mar2ngH", Crypto.charenc.UTF8.bytesToString([206, 156, 206, 159, 206, 155, 206, 169, 206, 157, 32, 206, 155, 206, 145, 206, 146, 206, 149])/*UTF-8 characters, encoded in source so they don't get corrupted*/, "5KMKKuUmAkiNbA3DazMQiLfDq47qs8MAEThm4yL8R2PhV1ov33D"]];
|
||||
var tests = [
|
||||
//No compression, no EC multiply
|
||||
["6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", "TestingOneTwoThree", "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"],
|
||||
["6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq", "Satoshi", "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"],
|
||||
//Compression, no EC multiply
|
||||
["6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo", "TestingOneTwoThree", "L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP"],
|
||||
["6PYLtMnXvfG3oJde97zRyLYFZCYizPU5T3LwgdYJz1fRhh16bU7u6PPmY7", "Satoshi", "KwYgW8gcxj1JWJXhPSu4Fqwzfhp5Yfi42mdYmMa4XqK7NJxXUSK7"],
|
||||
//EC multiply, no compression, no lot/sequence numbers
|
||||
["6PfQu77ygVyJLZjfvMLyhLMQbYnu5uguoJJ4kMCLqWwPEdfpwANVS76gTX", "TestingOneTwoThree", "5K4caxezwjGCGfnoPTZ8tMcJBLB7Jvyjv4xxeacadhq8nLisLR2"],
|
||||
["6PfLGnQs6VZnrNpmVKfjotbnQuaJK4KZoPFrAjx1JMJUa1Ft8gnf5WxfKd", "Satoshi", "5KJ51SgxWaAYR13zd9ReMhJpwrcX47xTJh2D3fGPG9CM8vkv5sH"],
|
||||
//EC multiply, no compression, lot/sequence numbers
|
||||
["6PgNBNNzDkKdhkT6uJntUXwwzQV8Rr2tZcbkDcuC9DZRsS6AtHts4Ypo1j", "MOLON LABE", "5JLdxTtcTHcfYcmJsNVy1v2PMDx432JPoYcBTVVRHpPaxUrdtf8"],
|
||||
["6PgGWtx25kUg8QWvwuJAgorN6k9FbE25rv5dMRwu5SKMnfpfVe5mar2ngH", Crypto.charenc.UTF8.bytesToString([206, 156, 206, 159, 206, 155, 206, 169, 206, 157, 32, 206, 155, 206, 145, 206, 146, 206, 149])/*UTF-8 characters, encoded in source so they don't get corrupted*/, "5KMKKuUmAkiNbA3DazMQiLfDq47qs8MAEThm4yL8R2PhV1ov33D"]];
|
||||
|
||||
// running each test uses a lot of memory, which isn't freed
|
||||
// immediately, so give the VM a little time to reclaim memory
|
||||
function waitThenCall(callback) {
|
||||
return function () { setTimeout(callback, 6000); }
|
||||
return function () { setTimeout(callback, 10000); }
|
||||
}
|
||||
|
||||
var decryptTest = function (test, i, onComplete) {
|
||||
ninja.privateKey.BIP38EncryptedKeyToByteArrayAsync(test[0], test[1], function (privBytes) {
|
||||
if (privBytes.constructor == Error) {
|
||||
document.getElementById("unittestresults").innerHTML += "fail testDecryptBip38 #" + i + ", error: " + privBytes.message + "<br/>";
|
||||
document.getElementById("asyncunittestresults").innerHTML += "fail testDecryptBip38 #" + i + ", error: " + privBytes.message + "<br/>";
|
||||
} else {
|
||||
var btcKey = new Bitcoin.ECKey(privBytes);
|
||||
var wif = !test[2].substr(0, 1).match(/[LK]/) ? btcKey.setCompressed(false).getBitcoinWalletImportFormat() : btcKey.setCompressed(true).getBitcoinWalletImportFormat();
|
||||
if (wif != test[2]) {
|
||||
document.getElementById("unittestresults").innerHTML += "fail testDecryptBip38 #" + i + "<br/>";
|
||||
document.getElementById("asyncunittestresults").innerHTML += "fail testDecryptBip38 #" + i + "<br/>";
|
||||
} else {
|
||||
document.getElementById("unittestresults").innerHTML += "pass testDecryptBip38 #" + i + "<br/>";
|
||||
document.getElementById("asyncunittestresults").innerHTML += "pass testDecryptBip38 #" + i + "<br/>";
|
||||
}
|
||||
}
|
||||
onComplete();
|
||||
});
|
||||
};
|
||||
|
||||
var encryptTest = function (test, compressed, i, onComplete) {
|
||||
ninja.privateKey.BIP38PrivateKeyToEncryptedKeyAsync(test[2], test[1], compressed, function (encryptedKey) {
|
||||
if (encryptedKey === test[0]) {
|
||||
document.getElementById("asyncunittestresults").innerHTML += "pass testBip38Encrypt #" + i + "<br/>";
|
||||
} else {
|
||||
document.getElementById("asyncunittestresults").innerHTML += "fail testBip38Encrypt #" + i + "<br/>";
|
||||
document.getElementById("asyncunittestresults").innerHTML += "expected " + test[0] + "<br/>received " + encryptedKey + "<br/>";
|
||||
}
|
||||
onComplete();
|
||||
});
|
||||
};
|
||||
|
||||
// test randomly generated encryption-decryption cycle
|
||||
var cycleTest = function (i, compress, onComplete) {
|
||||
// create new private key
|
||||
var privKey = (new Bitcoin.ECKey(false)).getBitcoinWalletImportFormat();
|
||||
|
||||
// encrypt private key
|
||||
ninja.privateKey.BIP38PrivateKeyToEncryptedKeyAsync(privKey, 'testing', compress, function (encryptedKey) {
|
||||
// decrypt encryptedKey
|
||||
ninja.privateKey.BIP38EncryptedKeyToByteArrayAsync(encryptedKey, 'testing', function (decryptedBytes) {
|
||||
var decryptedKey = (new Bitcoin.ECKey(decryptedBytes)).getBitcoinWalletImportFormat();
|
||||
|
||||
if (decryptedKey === privKey) {
|
||||
document.getElementById("asyncunittestresults").innerHTML += "pass cycleBip38 test #" + i + "<br/>";
|
||||
}
|
||||
else {
|
||||
document.getElementById("asyncunittestresults").innerHTML += "fail cycleBip38 test #" + i + " " + privKey + "<br/>";
|
||||
document.getElementById("asyncunittestresults").innerHTML += "encrypted key: " + encryptedKey + "<br/>decrypted key: " + decryptedKey;
|
||||
}
|
||||
onComplete();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// intermediate test - create some encrypted keys from an intermediate
|
||||
// then decrypt them to check that the private keys are recoverable
|
||||
var intermediateTest = function (i, onComplete) {
|
||||
var pass = Math.random().toString(36).substr(2);
|
||||
ninja.privateKey.BIP38GenerateIntermediatePointAsync(pass, null, null, function (intermediatePoint) {
|
||||
ninja.privateKey.BIP38GenerateECAddressAsync(intermediatePoint, false, function (address, encryptedKey) {
|
||||
ninja.privateKey.BIP38EncryptedKeyToByteArrayAsync(encryptedKey, pass, function (privBytes) {
|
||||
if (privBytes.constructor == Error) {
|
||||
document.getElementById("asyncunittestresults").innerHTML += "fail testBip38Intermediate #" + i + ", error: " + privBytes.message + "<br/>";
|
||||
} else {
|
||||
var btcKey = new Bitcoin.ECKey(privBytes);
|
||||
var btcAddress = btcKey.getBitcoinAddress();
|
||||
if (address !== btcKey.getBitcoinAddress()) {
|
||||
document.getElementById("asyncunittestresults").innerHTML += "fail testBip38Intermediate #" + i + "<br/>";
|
||||
} else {
|
||||
document.getElementById("asyncunittestresults").innerHTML += "pass testBip38Intermediate #" + i + "<br/>";
|
||||
}
|
||||
}
|
||||
onComplete();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("unittestresults").innerHTML += "running " + tests.length + " tests named testDecryptBip38<br/>";
|
||||
ninja.runSerialized([function (cb) {
|
||||
ninja.forSerialized(0, tests.length, function (i, callback) {
|
||||
decryptTest(tests[i], i, waitThenCall(callback));
|
||||
}, waitThenCall(cb));
|
||||
} ], done);
|
||||
document.getElementById("asyncunittestresults").innerHTML += "running " + tests.length + " tests named testDecryptBip38<br/>";
|
||||
document.getElementById("asyncunittestresults").innerHTML += "running 4 tests named testBip38Encrypt<br/>";
|
||||
document.getElementById("asyncunittestresults").innerHTML += "running 2 tests named cycleBip38<br/>";
|
||||
document.getElementById("asyncunittestresults").innerHTML += "running 5 tests named testBip38Intermediate<br/>";
|
||||
ninja.runSerialized([
|
||||
function (cb) {
|
||||
ninja.forSerialized(0, tests.length, function (i, callback) {
|
||||
decryptTest(tests[i], i, waitThenCall(callback));
|
||||
}, waitThenCall(cb));
|
||||
},
|
||||
function (cb) {
|
||||
ninja.forSerialized(0, 4, function (i, callback) {
|
||||
// only first 4 test vectors are not EC-multiply,
|
||||
// compression param false for i = 1,2 and true for i = 3,4
|
||||
encryptTest(tests[i], i >= 2, i, waitThenCall(callback));
|
||||
}, waitThenCall(cb));
|
||||
},
|
||||
function (cb) {
|
||||
ninja.forSerialized(0, 2, function (i, callback) {
|
||||
cycleTest(i, i % 2 ? true : false, waitThenCall(callback));
|
||||
}, waitThenCall(cb));
|
||||
},
|
||||
function (cb) {
|
||||
ninja.forSerialized(0, 5, function (i, callback) {
|
||||
intermediateTest(i, waitThenCall(callback));
|
||||
}, cb);
|
||||
}
|
||||
], done);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue