mirror of https://github.com/qortal/qortal
catbref
5 years ago
1 changed files with 192 additions and 0 deletions
@ -0,0 +1,192 @@
|
||||
<!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> |
Loading…
Reference in new issue