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="paperarea">
|
||||||
<div id="papercommands" class="commands">
|
<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="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><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>
|
<span class="print"><input type="button" name="print" value="Print" id="paperprint" onclick="window.print();" /></span>
|
||||||
</div>
|
</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 id="paperkeyarea"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -380,8 +388,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="authorpgp">
|
<div class="authorpgp">
|
||||||
<span class="item"><a href="ninja_bitaddress.org.txt" target="_blank" id="footerlabelpgp">PGP Public Key</a></span>
|
<span class="item">
|
||||||
<span class="item"><a href="pgpsignedmsg.txt" target="_blank"><span id="footerlabelversion">Signed Version History</span> (v2.5)</a></span>
|
<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>
|
<span class="item"><a href="https://github.com/pointbiz/bitaddress.org" target="_blank" id="footerlabelgithub">GitHub Repository</a></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="copyright">
|
<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
|
// 58 base58 characters starting with 6P
|
||||||
isBIP38Format: function (key) {
|
isBIP38Format: function (key) {
|
||||||
|
key = key.toString();
|
||||||
return (/^6P[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{56}$/.test(key));
|
return (/^6P[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{56}$/.test(key));
|
||||||
},
|
},
|
||||||
BIP38EncryptedKeyToByteArrayAsync: function (base58Encrypted, passphrase, callback) {
|
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") {
|
if (ninja.getQueryString()["asyncunittests"] == "true" || ninja.getQueryString()["asyncunittests"] == "1") {
|
||||||
ninja.unitTests.runAsynchronousTests();
|
ninja.unitTests.runAsynchronousTests();
|
||||||
}
|
}
|
||||||
|
|
||||||
// change language
|
// change language
|
||||||
if (ninja.getQueryString()["culture"] != undefined) {
|
if (ninja.getQueryString()["culture"] != undefined) {
|
||||||
ninja.translator.translate(ninja.getQueryString()["culture"]);
|
ninja.translator.translate(ninja.getQueryString()["culture"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// testnet, check if testnet edition should be activated
|
// testnet, check if testnet edition should be activated
|
||||||
if (ninja.getQueryString()["testnet"] == "true" || ninja.getQueryString()["testnet"] == "1") {
|
if (ninja.getQueryString()["testnet"] == "true" || ninja.getQueryString()["testnet"] == "1") {
|
||||||
document.getElementById("testnet").innerHTML = ninja.translator.get("testneteditionactivated");
|
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
|
Bitcoin.ECKey.privateKeyPrefix = 0xEF; // testnet
|
||||||
ninja.testnetMode = true;
|
ninja.testnetMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if users does not move mouse after random amount of time then generate the key anyway.
|
// if users does not move mouse after random amount of time then generate the key anyway.
|
||||||
setTimeout(ninja.seeder.forceGenerate, ninja.seeder.seedLimit * 20);
|
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",
|
"testneteditionactivated": "TESTNET EDITION ACTIVATED",
|
||||||
"paperlabelbitcoinaddress": "Bitcoin Address:",
|
"paperlabelbitcoinaddress": "Bitcoin Address:",
|
||||||
"paperlabelprivatekey": "Private Key (Wallet Import Format):",
|
"paperlabelprivatekey": "Private Key (Wallet Import Format):",
|
||||||
|
"paperlabelencryptedkey": "Encrypted Private Key (Password required)",
|
||||||
"bulkgeneratingaddresses": "Generating addresses... ",
|
"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.",
|
"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.",
|
"brainalertpassphrasedoesnotmatch": "The passphrase does not match the confirm passphrase.",
|
||||||
|
@ -53,6 +54,7 @@ ninja.translator = {
|
||||||
"testneteditionactivated": "Testnet se activa",
|
"testneteditionactivated": "Testnet se activa",
|
||||||
"paperlabelbitcoinaddress": "Dirección Bitcoin:",
|
"paperlabelbitcoinaddress": "Dirección Bitcoin:",
|
||||||
"paperlabelprivatekey": "Clave privada (formato para importar):",
|
"paperlabelprivatekey": "Clave privada (formato para importar):",
|
||||||
|
"paperlabelencryptedkey": "Clave privada cifrada (contraseña necesaria)",
|
||||||
"bulkgeneratingaddresses": "Generación de direcciones... ",
|
"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.",
|
"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.",
|
"brainalertpassphrasedoesnotmatch": "Las contraseñas no coinciden.",
|
||||||
|
@ -80,7 +82,7 @@ ninja.translator = {
|
||||||
// footer html
|
// footer html
|
||||||
"footerlabeldonations": "Donaciones:",
|
"footerlabeldonations": "Donaciones:",
|
||||||
"footerlabeltranslatedby": "Traducción: <b>12345</b>Vypv2QSmuRXcciT5oEB27mPbWGeva",
|
"footerlabeltranslatedby": "Traducción: <b>12345</b>Vypv2QSmuRXcciT5oEB27mPbWGeva",
|
||||||
"footerlabelpgp": "Clave pública PGP",
|
"footerlabelpgp": "PGP",
|
||||||
"footerlabelversion": "Histórico de versiones",
|
"footerlabelversion": "Histórico de versiones",
|
||||||
"footerlabelgithub": "Repositorio GitHub",
|
"footerlabelgithub": "Repositorio GitHub",
|
||||||
"footerlabelcopyright1": "Copyright bitaddress.org.",
|
"footerlabelcopyright1": "Copyright bitaddress.org.",
|
||||||
|
@ -99,6 +101,9 @@ ninja.translator = {
|
||||||
"paperlabeladdressestogenerate": "Direcciones en total:",
|
"paperlabeladdressestogenerate": "Direcciones en total:",
|
||||||
"papergenerate": "Generar",
|
"papergenerate": "Generar",
|
||||||
"paperprint": "Imprimir",
|
"paperprint": "Imprimir",
|
||||||
|
"paperlabelBIPpassphrase": "Passphrase:", //TODO: please translate
|
||||||
|
"paperlabelencrypt": "BIP38 Encrypt?", //TODO: please translate
|
||||||
|
"paperadvancedcommandslabel": "Advanced Options", //TODO: please translate
|
||||||
|
|
||||||
// bulk wallet html
|
// bulk wallet html
|
||||||
"bulklabelstartindex": "Empezar en:",
|
"bulklabelstartindex": "Empezar en:",
|
||||||
|
@ -173,6 +178,7 @@ ninja.translator = {
|
||||||
"testneteditionactivated": "ÉDITION TESTNET ACTIVÉE",
|
"testneteditionactivated": "ÉDITION TESTNET ACTIVÉE",
|
||||||
"paperlabelbitcoinaddress": "Adresse Bitcoin:",
|
"paperlabelbitcoinaddress": "Adresse Bitcoin:",
|
||||||
"paperlabelprivatekey": "Clé Privée (Format d'importation de porte-monnaie):",
|
"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... ",
|
"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.",
|
"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.",
|
"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",
|
"detailwallet": "Détails du Porte-Monnaie",
|
||||||
"footerlabeldonations": "Dons:",
|
"footerlabeldonations": "Dons:",
|
||||||
"footerlabeltranslatedby": "Traduction: 1Gy7NYSJNUYqUdXTBow5d7bCUEJkUFDFSq",
|
"footerlabeltranslatedby": "Traduction: 1Gy7NYSJNUYqUdXTBow5d7bCUEJkUFDFSq",
|
||||||
"footerlabelpgp": "Clé Publique PGP",
|
"footerlabelpgp": "PGP",
|
||||||
"footerlabelversion": "Historique De Version Signé",
|
"footerlabelversion": "Historique De Version",
|
||||||
"footerlabelgithub": "Dépôt GitHub",
|
"footerlabelgithub": "Dépôt GitHub",
|
||||||
"footerlabelcopyright1": "Copyright bitaddress.org.",
|
"footerlabelcopyright1": "Copyright bitaddress.org.",
|
||||||
"footerlabelcopyright2": "Les droits d'auteurs JavaScript sont inclus dans le code source.",
|
"footerlabelcopyright2": "Les droits d'auteurs JavaScript sont inclus dans le code source.",
|
||||||
|
@ -211,6 +217,9 @@ ninja.translator = {
|
||||||
"paperlabeladdressestogenerate": "Nombre d'adresses à créer:",
|
"paperlabeladdressestogenerate": "Nombre d'adresses à créer:",
|
||||||
"papergenerate": "Générer",
|
"papergenerate": "Générer",
|
||||||
"paperprint": "Imprimer",
|
"paperprint": "Imprimer",
|
||||||
|
"paperlabelBIPpassphrase": "Passphrase:", //TODO: please translate
|
||||||
|
"paperlabelencrypt": "BIP38 Encrypt?", //TODO: please translate
|
||||||
|
"paperadvancedcommandslabel": "Advanced Options", //TODO: please translate
|
||||||
"bulklabelstartindex": "Commencer à l'index:",
|
"bulklabelstartindex": "Commencer à l'index:",
|
||||||
"bulklabelrowstogenerate": "Colonnes à générer:",
|
"bulklabelrowstogenerate": "Colonnes à générer:",
|
||||||
"bulklabelcompressed": "Compressed addresses?", //TODO: please translate
|
"bulklabelcompressed": "Compressed addresses?", //TODO: please translate
|
||||||
|
@ -278,6 +287,7 @@ ninja.translator = {
|
||||||
"testneteditionactivated": "ΕΝΕΡΓΗ ΕΚΔΟΣΗ TESTNET",
|
"testneteditionactivated": "ΕΝΕΡΓΗ ΕΚΔΟΣΗ TESTNET",
|
||||||
"paperlabelbitcoinaddress": "Διεύθυνση Bitcoin:",
|
"paperlabelbitcoinaddress": "Διεύθυνση Bitcoin:",
|
||||||
"paperlabelprivatekey": "Προσωπικό Κλειδί (Μορφή εισαγωγής σε πορτοφόλι):",
|
"paperlabelprivatekey": "Προσωπικό Κλειδί (Μορφή εισαγωγής σε πορτοφόλι):",
|
||||||
|
"paperlabelencryptedkey": "Encrypted Private Key (Password required)", //TODO: please translate
|
||||||
"bulkgeneratingaddresses": "Δημιουργία διευθύνσεων... ",
|
"bulkgeneratingaddresses": "Δημιουργία διευθύνσεων... ",
|
||||||
"brainalertpassphrasetooshort": "Η φράση κωδικός που δώσατε είναι πολύ αδύναμη.\n\nΠροσοχή: Είναι σημαντικό να επιλέξετε μια ισχυρή φράση κωδικό που θα σας προφυλάξει από απόπειρες παραβίασής της τύπου brute force και κλοπή των bitcoins σας.",
|
"brainalertpassphrasetooshort": "Η φράση κωδικός που δώσατε είναι πολύ αδύναμη.\n\nΠροσοχή: Είναι σημαντικό να επιλέξετε μια ισχυρή φράση κωδικό που θα σας προφυλάξει από απόπειρες παραβίασής της τύπου brute force και κλοπή των bitcoins σας.",
|
||||||
"brainalertpassphrasedoesnotmatch": "Η φράση κωδικός και η επιβεβαίωση της δε συμφωνούν.",
|
"brainalertpassphrasedoesnotmatch": "Η φράση κωδικός και η επιβεβαίωση της δε συμφωνούν.",
|
||||||
|
@ -305,8 +315,8 @@ ninja.translator = {
|
||||||
// footer html
|
// footer html
|
||||||
"footerlabeldonations": "Δωρεές:",
|
"footerlabeldonations": "Δωρεές:",
|
||||||
"footerlabeltranslatedby": "Μετάφραση: <a href='http://BitcoinX.gr/'><b>BitcoinX.gr</b></a> 1BitcoiNxkUPcTFxwMqxhRiPEiQRzYskf6",
|
"footerlabeltranslatedby": "Μετάφραση: <a href='http://BitcoinX.gr/'><b>BitcoinX.gr</b></a> 1BitcoiNxkUPcTFxwMqxhRiPEiQRzYskf6",
|
||||||
"footerlabelpgp": "Δημόσιο Κλειδί PGP",
|
"footerlabelpgp": "PGP",
|
||||||
"footerlabelversion": "Υπογεγραμένο ιστορικό εκδόσεων",
|
"footerlabelversion": "ιστορικό εκδόσεων",
|
||||||
"footerlabelgithub": "Αποθετήριο GitHub",
|
"footerlabelgithub": "Αποθετήριο GitHub",
|
||||||
"footerlabelcopyright1": "Copyright bitaddress.org.",
|
"footerlabelcopyright1": "Copyright bitaddress.org.",
|
||||||
"footerlabelcopyright2": "Τα πνευματικά δικαιώματα της JavaScript περιλαμβάνονται στον κώδικα.",
|
"footerlabelcopyright2": "Τα πνευματικά δικαιώματα της JavaScript περιλαμβάνονται στον κώδικα.",
|
||||||
|
@ -324,6 +334,9 @@ ninja.translator = {
|
||||||
"paperlabeladdressestogenerate": "Πλήθος διευθύνσεων:",
|
"paperlabeladdressestogenerate": "Πλήθος διευθύνσεων:",
|
||||||
"papergenerate": "Δημιουργία",
|
"papergenerate": "Δημιουργία",
|
||||||
"paperprint": "Εκτύπωση",
|
"paperprint": "Εκτύπωση",
|
||||||
|
"paperlabelBIPpassphrase": "Passphrase:", //TODO: please translate
|
||||||
|
"paperlabelencrypt": "BIP38 Encrypt?", //TODO: please translate
|
||||||
|
"paperadvancedcommandslabel": "Advanced Options", //TODO: please translate
|
||||||
|
|
||||||
// bulk wallet html
|
// bulk wallet html
|
||||||
"bulklabelstartindex": "Ξεκίνημα δείκτη:",
|
"bulklabelstartindex": "Ξεκίνημα δείκτη:",
|
||||||
|
@ -423,7 +436,7 @@ ninja.translator.showEnglishJson = function () {
|
||||||
div.setAttribute("class", "englishjson");
|
div.setAttribute("class", "englishjson");
|
||||||
div.innerHTML = "<h3>English Json</h3>";
|
div.innerHTML = "<h3>English Json</h3>";
|
||||||
var elem = document.createElement("textarea");
|
var elem = document.createElement("textarea");
|
||||||
elem.setAttribute("rows", "35");
|
elem.setAttribute("rows", "15");
|
||||||
elem.setAttribute("cols", "110");
|
elem.setAttribute("cols", "110");
|
||||||
elem.setAttribute("wrap", "off");
|
elem.setAttribute("wrap", "off");
|
||||||
var langJson = "{\n";
|
var langJson = "{\n";
|
||||||
|
|
|
@ -38,12 +38,18 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
runAsynchronousTests: function () {
|
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
|
// run the asynchronous tests one after another so we don't crash the browser
|
||||||
ninja.foreachSerialized(ninja.unitTests.asynchronousTests, function (name, cb) {
|
ninja.foreachSerialized(ninja.unitTests.asynchronousTests, function (name, cb) {
|
||||||
|
document.getElementById("busyblock").className = "busy";
|
||||||
ninja.unitTests.asynchronousTests[name](cb);
|
ninja.unitTests.asynchronousTests[name](cb);
|
||||||
}, function () {
|
}, 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 = "";
|
document.getElementById("busyblock").className = "";
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -458,44 +464,132 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
asynchronousTests: {
|
asynchronousTests: {
|
||||||
|
//https://en.bitcoin.it/wiki/BIP_0038
|
||||||
testBip38: function (done) {
|
testBip38: function (done) {
|
||||||
var tests = [["6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", "TestingOneTwoThree", "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"],
|
var tests = [
|
||||||
["6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq", "Satoshi", "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"],
|
//No compression, no EC multiply
|
||||||
["6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo", "TestingOneTwoThree", "L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP"],
|
["6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", "TestingOneTwoThree", "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"],
|
||||||
["6PYLtMnXvfG3oJde97zRyLYFZCYizPU5T3LwgdYJz1fRhh16bU7u6PPmY7", "Satoshi", "KwYgW8gcxj1JWJXhPSu4Fqwzfhp5Yfi42mdYmMa4XqK7NJxXUSK7"],
|
["6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq", "Satoshi", "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"],
|
||||||
["6PfQu77ygVyJLZjfvMLyhLMQbYnu5uguoJJ4kMCLqWwPEdfpwANVS76gTX", "TestingOneTwoThree", "5K4caxezwjGCGfnoPTZ8tMcJBLB7Jvyjv4xxeacadhq8nLisLR2"],
|
//Compression, no EC multiply
|
||||||
["6PfLGnQs6VZnrNpmVKfjotbnQuaJK4KZoPFrAjx1JMJUa1Ft8gnf5WxfKd", "Satoshi", "5KJ51SgxWaAYR13zd9ReMhJpwrcX47xTJh2D3fGPG9CM8vkv5sH"],
|
["6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo", "TestingOneTwoThree", "L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP"],
|
||||||
["6PgNBNNzDkKdhkT6uJntUXwwzQV8Rr2tZcbkDcuC9DZRsS6AtHts4Ypo1j", "MOLON LABE", "5JLdxTtcTHcfYcmJsNVy1v2PMDx432JPoYcBTVVRHpPaxUrdtf8"],
|
["6PYLtMnXvfG3oJde97zRyLYFZCYizPU5T3LwgdYJz1fRhh16bU7u6PPmY7", "Satoshi", "KwYgW8gcxj1JWJXhPSu4Fqwzfhp5Yfi42mdYmMa4XqK7NJxXUSK7"],
|
||||||
["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"]];
|
//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
|
// running each test uses a lot of memory, which isn't freed
|
||||||
// immediately, so give the VM a little time to reclaim memory
|
// immediately, so give the VM a little time to reclaim memory
|
||||||
function waitThenCall(callback) {
|
function waitThenCall(callback) {
|
||||||
return function () { setTimeout(callback, 6000); }
|
return function () { setTimeout(callback, 10000); }
|
||||||
}
|
}
|
||||||
|
|
||||||
var decryptTest = function (test, i, onComplete) {
|
var decryptTest = function (test, i, onComplete) {
|
||||||
ninja.privateKey.BIP38EncryptedKeyToByteArrayAsync(test[0], test[1], function (privBytes) {
|
ninja.privateKey.BIP38EncryptedKeyToByteArrayAsync(test[0], test[1], function (privBytes) {
|
||||||
if (privBytes.constructor == Error) {
|
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 {
|
} else {
|
||||||
var btcKey = new Bitcoin.ECKey(privBytes);
|
var btcKey = new Bitcoin.ECKey(privBytes);
|
||||||
var wif = !test[2].substr(0, 1).match(/[LK]/) ? btcKey.setCompressed(false).getBitcoinWalletImportFormat() : btcKey.setCompressed(true).getBitcoinWalletImportFormat();
|
var wif = !test[2].substr(0, 1).match(/[LK]/) ? btcKey.setCompressed(false).getBitcoinWalletImportFormat() : btcKey.setCompressed(true).getBitcoinWalletImportFormat();
|
||||||
if (wif != test[2]) {
|
if (wif != test[2]) {
|
||||||
document.getElementById("unittestresults").innerHTML += "fail testDecryptBip38 #" + i + "<br/>";
|
document.getElementById("asyncunittestresults").innerHTML += "fail testDecryptBip38 #" + i + "<br/>";
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("unittestresults").innerHTML += "pass testDecryptBip38 #" + i + "<br/>";
|
document.getElementById("asyncunittestresults").innerHTML += "pass testDecryptBip38 #" + i + "<br/>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onComplete();
|
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/>";
|
document.getElementById("asyncunittestresults").innerHTML += "running " + tests.length + " tests named testDecryptBip38<br/>";
|
||||||
ninja.runSerialized([function (cb) {
|
document.getElementById("asyncunittestresults").innerHTML += "running 4 tests named testBip38Encrypt<br/>";
|
||||||
ninja.forSerialized(0, tests.length, function (i, callback) {
|
document.getElementById("asyncunittestresults").innerHTML += "running 2 tests named cycleBip38<br/>";
|
||||||
decryptTest(tests[i], i, waitThenCall(callback));
|
document.getElementById("asyncunittestresults").innerHTML += "running 5 tests named testBip38Intermediate<br/>";
|
||||||
}, waitThenCall(cb));
|
ninja.runSerialized([
|
||||||
} ], done);
|
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