forked from Qortal/qortal
192 lines
6.4 KiB
HTML
192 lines
6.4 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<script src="Base58.js"></script>
|
|
<script src="nacl_factory.js"></script>
|
|
<script>
|
|
var cachedPrivateKey;
|
|
|
|
// Returns private key, or null if unable to decrypt or password is missing, etc.
|
|
function getPrivateKey() {
|
|
// If we have decrypted private key from local storage already then return it.
|
|
if (cachedPrivateKey != undefined)
|
|
return cachedPrivateKey;
|
|
|
|
var storedData = getLocallyStoredData();
|
|
|
|
// If there is nothing in local storage then we can't provide a private key.
|
|
// Prompt user for mnemonic phrase and new password so private key can be stored.
|
|
if (storedData == undefined)
|
|
return savePrivateKey();
|
|
|
|
// We need also password to decrypt private key.
|
|
|
|
// Prompting user for password and checking password isn't empty, etc. goes here
|
|
var password = document.getElementById('password').value;
|
|
if (password == "") {
|
|
document.getElementById('passwordContainer').style.display = "";
|
|
return null;
|
|
}
|
|
|
|
// Use password to create encryption key used to decrypt private key
|
|
var naclPromise = nacl_factory.instantiate(function (nacl) {
|
|
// Stored data is made up of salt(hex) + ':' + encrypted private key(hex)
|
|
var storedDataParts = storedData.split(':');
|
|
|
|
var salt = nacl.from_hex(storedDataParts[0]);
|
|
var encryptedPrivateKey = nacl.from_hex(storedDataParts[1]);
|
|
|
|
// Convert password to Uint8Array
|
|
var passwordArray = nacl.encode_utf8(password);
|
|
|
|
// Use salt & password to calculate decryption key
|
|
var decryptionKey = deriveStorageKey(nacl, salt, passwordArray);
|
|
|
|
// Decrypt private key - throws exception on failure (e.g. wrong password)
|
|
var decryptedPrivateKey = nacl.crypto_secretbox_open(encryptedPrivateKey, salt, decryptionKey);
|
|
|
|
// Decryption worked so we can hide password input
|
|
document.getElementById('passwordContainer').style.display = "none";
|
|
|
|
// Convert decrypted private key to string
|
|
cachedPrivateKey = nacl.decode_utf8(decryptedPrivateKey);
|
|
});
|
|
|
|
return naclPromise;
|
|
}
|
|
|
|
function savePrivateKey() {
|
|
var phrase = document.getElementById('phrase').value;
|
|
var newPassword = document.getElementById('newPassword').value;
|
|
|
|
if (phrase == "" || newPassword == "") {
|
|
document.getElementById('setupContainer').style.display = "";
|
|
return null;
|
|
}
|
|
|
|
// Converting mnemonic phrase to private key goes here
|
|
|
|
// For demo we use fake private key
|
|
var privateKey = 'FakePrivateKey from mnemonic: ' + phrase;
|
|
|
|
// Use password to create encryption key used to encrypt private key
|
|
var naclPromise = nacl_factory.instantiate(function (nacl) {
|
|
// Generate "salt" used to protect encrypted key
|
|
var salt = nacl.crypto_secretbox_random_nonce();
|
|
|
|
// Convert password to Uint8Array
|
|
var passwordArray = nacl.encode_utf8(newPassword);
|
|
|
|
// Use salt & password to calculate encryption key
|
|
var encryptionKey = deriveStorageKey(nacl, salt, passwordArray);
|
|
|
|
// Convert private key to Uint8Array
|
|
var privateKeyArray = nacl.encode_utf8(privateKey);
|
|
|
|
var encryptedPrivateKey = nacl.crypto_secretbox(privateKeyArray, salt, encryptionKey);
|
|
|
|
// Stored data is made up of salt(hex) + ':' + encrypted private key(hex)
|
|
var dataToStore = nacl.to_hex(salt) + ':' + nacl.to_hex(encryptedPrivateKey);
|
|
|
|
setLocallyStoredData(dataToStore);
|
|
|
|
// Save worked so we can hide inputs for mnemonic phrase and password
|
|
document.getElementById('setupContainer').style.display = "none";
|
|
|
|
cachedPrivateKey = privateKey;
|
|
});
|
|
|
|
return naclPromise;
|
|
}
|
|
|
|
|
|
function deriveStorageKey(nacl, salt, passwordArray) {
|
|
// First round
|
|
var toBeHashed = concatUint8Arrays(salt, passwordArray);
|
|
|
|
var hash = nacl.crypto_hash_sha256(toBeHashed);
|
|
|
|
// Many rounds of further hashing
|
|
|
|
// To be more efficient, we only need to do this once
|
|
toBeHashed = new Uint8Array(passwordArray.length + hash.length);
|
|
toBeHashed.set(passwordArray, 0);
|
|
|
|
for (var rounds = 0; rounds < 100; ++rounds) {
|
|
toBeHashed.set(hash, passwordArray.length);
|
|
|
|
hash = nacl.crypto_hash_sha256(toBeHashed);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
function clearPrivateKey() {
|
|
clearLocallyStoredData();
|
|
|
|
cachedPrivateKey = null;
|
|
|
|
document.getElementById('privateKey').innerHTML = "";
|
|
|
|
document.getElementById('passwordContainer').style.display = "none";
|
|
document.getElementById('setupContainer').style.display = "";
|
|
}
|
|
|
|
function concatUint8Arrays(array1, array2) {
|
|
var bigArray = new Uint8Array(array1.length + array2.length);
|
|
bigArray.set(array1, 0);
|
|
bigArray.set(array2, array1.length);
|
|
return bigArray;
|
|
}
|
|
|
|
function getLocallyStoredData() {
|
|
return window.localStorage.getItem("encryptedPrivateKey");
|
|
}
|
|
|
|
function setLocallyStoredData(data) {
|
|
window.localStorage.setItem("encryptedPrivateKey", data);
|
|
}
|
|
|
|
function clearLocallyStoredData() {
|
|
window.localStorage.removeItem("encryptedPrivateKey");
|
|
}
|
|
|
|
window.addEventListener('load', getPrivateKey, false);
|
|
</script>
|
|
<style>
|
|
DIV {
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
#privateKey {
|
|
background: #eeeeee;
|
|
width: 600px;
|
|
display: inline-block;
|
|
}
|
|
|
|
#privateKey:empty:before {
|
|
content: "\200b"; /* Unicode zero-width space character to force span to have content */
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="activeContainer">
|
|
Decrypted private key: <span id="privateKey"></span><br>
|
|
<button onclick="clearPrivateKey(); return false">Clear Storage</button><br>
|
|
<button onclick="window.location = window.location; return false">Reload page</button><br>
|
|
</div>
|
|
|
|
<div id="passwordContainer" style="display: none">
|
|
For decrypting stored private key:<br>
|
|
Password: <input type="password" name="password" id="password"><br>
|
|
<button onclick="Promise.resolve(getPrivateKey()).then(function() { document.getElementById('privateKey').innerHTML = cachedPrivateKey; }).catch(function() { window.alert('wrong password'); }); return false">Decrypt</button>
|
|
</div>
|
|
|
|
<div id="setupContainer" style="display: none">
|
|
For storing private key:<br>
|
|
Mnemonic phrase: <input type="text" name="phrase" id="phrase"><br>
|
|
New password: <input type="password" name="newPassword" id="newPassword"><br>
|
|
<button onclick="Promise.resolve(savePrivateKey()).then(function() { document.getElementById('privateKey').innerHTML = cachedPrivateKey; }); return false">Save</button>
|
|
</div>
|
|
</body>
|
|
</html> |