v0.2.5-pre.005

This commit is contained in:
2026-08-19 13:09:16 +02:00
parent 3c069347b7
commit 36b98e0abc
27 changed files with 2305 additions and 113 deletions

View File

@@ -1,42 +1,36 @@
// file: crates/ksp-wallet-lib/src/crypto.rs
// version: 1
// version: 2
//! In-memory cryptographic primitives for native `.kspwallet` V1.
//!
//! `pre.004` intentionally lands these crate-private primitives one tranche before `pre.005` wires them into create/open capability flows.
#![allow(dead_code, reason = "pre.004 stages private Wallet crypto primitives before their pre.005 production callers")]
use chacha20poly1305::KeyInit as _;
use chacha20poly1305::aead::Aead as _;
/// Exact V1 content-key and password-derived-key size in bytes.
const SECRET_KEY_BYTES: usize = 32;
pub(crate) const SECRET_KEY_BYTES: usize = 32;
/// Owned 32-byte secret key with redacted diagnostics and drop-time zeroization.
struct SecretKeyV1 {
pub(crate) struct SecretKeyV1 {
bytes: [u8; SECRET_KEY_BYTES],
}
impl SecretKeyV1 {
/// Takes ownership of exact 32-byte secret material.
const fn from_bytes(bytes: [u8; SECRET_KEY_BYTES]) -> Self {
pub(crate) const fn from_bytes(bytes: [u8; SECRET_KEY_BYTES]) -> Self {
return Self { bytes };
}
/// Generates a fresh secret key from the operating-system CSPRNG.
fn random() -> ksp_core_lib::Result<Self> {
let mut bytes = [0_u8; SECRET_KEY_BYTES];
let fill_result = getrandom::fill(bytes.as_mut_slice());
if fill_result.is_err() {
zeroize::Zeroize::zeroize(&mut bytes);
return std::result::Result::Err(randomness_error());
}
pub(crate) fn random() -> ksp_core_lib::Result<Self> {
let bytes = match random_bytes::<SECRET_KEY_BYTES>() {
std::result::Result::Ok(bytes) => bytes,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(Self { bytes });
}
/// Borrows secret bytes internally without allocating or cloning.
const fn as_bytes(&self) -> &[u8; SECRET_KEY_BYTES] {
pub(crate) const fn as_bytes(&self) -> &[u8; SECRET_KEY_BYTES] {
return &self.bytes;
}
}
@@ -53,24 +47,29 @@ impl std::ops::Drop for SecretKeyV1 {
}
}
/// Generates a fresh XChaCha20-Poly1305 nonce from the operating-system CSPRNG.
fn random_nonce() -> ksp_core_lib::Result<[u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES]> {
let mut nonce = [0_u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES];
let fill_result = getrandom::fill(nonce.as_mut_slice());
/// Generates a fresh fixed-size byte array from the operating-system CSPRNG.
pub(crate) fn random_bytes<const LENGTH: usize>() -> ksp_core_lib::Result<[u8; LENGTH]> {
let mut bytes = [0_u8; LENGTH];
let fill_result = getrandom::fill(bytes.as_mut_slice());
if fill_result.is_err() {
zeroize::Zeroize::zeroize(&mut nonce);
zeroize::Zeroize::zeroize(&mut bytes);
return std::result::Result::Err(randomness_error());
}
return std::result::Result::Ok(nonce);
return std::result::Result::Ok(bytes);
}
/// Generates a fresh XChaCha20-Poly1305 nonce from the operating-system CSPRNG.
pub(crate) fn random_nonce() -> ksp_core_lib::Result<[u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES]> {
return random_bytes::<{ crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES }>();
}
/// Derives one V1 password wrapping key from serialized Argon2id parameters.
fn derive_password_key(password: &[u8], kdf: &crate::WalletKdfParametersV1) -> ksp_core_lib::Result<SecretKeyV1> {
pub(crate) fn derive_password_key(password: &[u8], kdf: &crate::WalletKdfParametersV1) -> ksp_core_lib::Result<SecretKeyV1> {
return derive_argon2id(password, kdf.salt(), kdf.memory_kib(), kdf.iterations(), kdf.parallelism());
}
/// Wraps one 32-byte content key with XChaCha20-Poly1305 and caller-provided domain-separated AAD.
fn wrap_key(
pub(crate) fn wrap_key(
wrapping_key: &SecretKeyV1,
key_to_wrap: &SecretKeyV1,
nonce: &[u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES],
@@ -80,7 +79,7 @@ fn wrap_key(
}
/// Unwraps one 32-byte content key and maps every AEAD authentication failure to the generic Wallet authentication error.
fn unwrap_key(
pub(crate) fn unwrap_key(
wrapping_key: &SecretKeyV1,
nonce: &[u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES],
aad: &[u8],
@@ -108,7 +107,7 @@ fn unwrap_key(
}
/// Encrypts bounded plaintext bytes with XChaCha20-Poly1305 and caller-provided domain-separated AAD.
fn encrypt_bytes(
pub(crate) fn encrypt_bytes(
key: &SecretKeyV1,
nonce: &[u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES],
aad: &[u8],
@@ -129,7 +128,7 @@ fn encrypt_bytes(
}
/// Decrypts authenticated ciphertext bytes and returns a generic authentication error on tag failure.
fn decrypt_bytes(
pub(crate) fn decrypt_bytes(
key: &SecretKeyV1,
nonce: &[u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES],
aad: &[u8],
@@ -149,7 +148,7 @@ fn decrypt_bytes(
};
}
fn derive_argon2id(password: &[u8], salt: &[u8], memory_kib: u32, iterations: u32, parallelism: u32) -> ksp_core_lib::Result<SecretKeyV1> {
pub(crate) fn derive_argon2id(password: &[u8], salt: &[u8], memory_kib: u32, iterations: u32, parallelism: u32) -> ksp_core_lib::Result<SecretKeyV1> {
if password.is_empty() || password.len() > crate::KSPWALLET_V1_MAX_PASSWORD_BYTES {
return std::result::Result::Err(crypto_parameter_error());
}