feat(litebe): salted Argon2id KDF for new encrypted wallets
New Lite wallets now encrypt their seed and keys with a salted Argon2id key instead of the unsalted double-SHA256 of the passphrase, which was cheap to brute-force from a stolen wallet file. - A shared derive_wallet_key() replaces double-SHA256 across encrypt, unlock, remove-encryption and seed recovery. KDF 0 is the old double-SHA256 (unchanged); KDF 1 is Argon2id (moderate: 3 passes, 256 MiB), using sodiumoxide's existing pwhash -- no new dependency. - The KDF id, salt and parameters are stored in the wallet. A wallet is written at version 10 only while it is actually Argon2id-encrypted; every pre-existing wallet (plaintext or double-SHA256) is read and written byte-for-byte as version 9. - New-wallets-only: a freshly created or restored wallet encrypts with Argon2id; a wallet read from disk never changes its KDF, and removing encryption never changes it. Set DRAGONX_LITE_KDF=legacy to keep new wallets on double-SHA256. - An older build (or SilentDragonXLite) opening a version-10 wallet fails cleanly with "don't know how to read wallet version 10" before parsing anything, never a silent misread; the seed phrase stays the cross-wallet recovery path. Compatibility break (by design, opt-in): a wallet newly encrypted with Argon2id cannot be opened by older ObsidianDragon or SilentDragonXLite. Tests: the existing encryption tests now exercise the Argon2id path, plus the version-10 header, legacy byte-identity and derive_wallet_key parameter bounds. Validated by the lite oracle suite and the old-vs-new serialization differential (legacy wallets byte-identical; version-10 round-trips). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+13
@@ -3983,3 +3983,16 @@ fn test_wallet_read_never_panics_on_corrupt_input() {
|
||||
assert_eq!(kind("a u64::MAX string length", run("a u64::MAX string length", || utils::read_string(&s[..]))),
|
||||
ErrorKind::UnexpectedEof);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_wallet_key_param_bounds() {
|
||||
// C253: kdf 0 is legacy double_sha256 (ignores ops/mem); kdf 1 rejects out-of-bounds Argon2id params
|
||||
// BEFORE deriving, so this test never runs a heavy derivation.
|
||||
let salt = [7u8; 16];
|
||||
assert!(LightWallet::derive_wallet_key(0, b"pw", &salt, 0, 0).is_ok());
|
||||
let bad = |ops: u32, mem: u32| LightWallet::derive_wallet_key(1, b"pw", &salt, ops, mem).unwrap_err().kind();
|
||||
assert_eq!(bad(0, 256 * 1024 * 1024), std::io::ErrorKind::InvalidData); // ops too low
|
||||
assert_eq!(bad(13, 256 * 1024 * 1024), std::io::ErrorKind::InvalidData); // ops too high
|
||||
assert_eq!(bad(3, 4 * 1024 * 1024), std::io::ErrorKind::InvalidData); // mem below 8 MiB
|
||||
assert_eq!(bad(3, (1u32 << 30) + 1), std::io::ErrorKind::InvalidData); // mem above 1 GiB
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user