All checks were successful
check / check (push) Successful in 14s
vault.js: Argon2id key derivation + XSalsa20-Poly1305 encryption via libsodium-wrappers-sumo. No raw crypto primitives. Wallet creation now requires a password. The mnemonic or private key is encrypted before storage — only the ciphertext blob (salt, nonce, ciphertext) is persisted. The plaintext secret is never stored. Sending requires the password to decrypt the secret, derive the signing key, and construct the transaction. Wrong password is caught and reported.
63 lines
2.1 KiB
JavaScript
63 lines
2.1 KiB
JavaScript
// Vault: password-based encryption of secrets using libsodium.
|
|
// Uses Argon2id for key derivation and XSalsa20-Poly1305 for encryption.
|
|
// All crypto operations are delegated to libsodium — no raw primitives.
|
|
|
|
const sodium = require("libsodium-wrappers-sumo");
|
|
|
|
let ready = false;
|
|
|
|
async function ensureReady() {
|
|
if (!ready) {
|
|
await sodium.ready;
|
|
ready = true;
|
|
}
|
|
}
|
|
|
|
// Returns { salt, nonce, ciphertext } (all base64-encoded strings).
|
|
async function encryptWithPassword(plaintext, password) {
|
|
await ensureReady();
|
|
const salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
|
|
const key = sodium.crypto_pwhash(
|
|
sodium.crypto_secretbox_KEYBYTES,
|
|
password,
|
|
salt,
|
|
sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
|
|
sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
|
|
sodium.crypto_pwhash_ALG_ARGON2ID13,
|
|
);
|
|
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
|
|
const ciphertext = sodium.crypto_secretbox_easy(
|
|
sodium.from_string(plaintext),
|
|
nonce,
|
|
key,
|
|
);
|
|
return {
|
|
salt: sodium.to_base64(salt),
|
|
nonce: sodium.to_base64(nonce),
|
|
ciphertext: sodium.to_base64(ciphertext),
|
|
};
|
|
}
|
|
|
|
// Returns the plaintext string, or throws on wrong password.
|
|
async function decryptWithPassword(encrypted, password) {
|
|
await ensureReady();
|
|
const salt = sodium.from_base64(encrypted.salt);
|
|
const nonce = sodium.from_base64(encrypted.nonce);
|
|
const ciphertext = sodium.from_base64(encrypted.ciphertext);
|
|
const key = sodium.crypto_pwhash(
|
|
sodium.crypto_secretbox_KEYBYTES,
|
|
password,
|
|
salt,
|
|
sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
|
|
sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
|
|
sodium.crypto_pwhash_ALG_ARGON2ID13,
|
|
);
|
|
const plaintext = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
|
|
if (!plaintext) {
|
|
throw new Error("Decryption failed — wrong password.");
|
|
}
|
|
return sodium.to_string(plaintext);
|
|
}
|
|
|
|
module.exports = { encryptWithPassword, decryptWithPassword };
|