Remove stale Phase B TODO; align encryption comments with ADR-020/021/018

The 'TODO(Phase B): Use salt in HKDF-based key derivation' at
src/encryption.rs:169 described a design direction that ADR-021
(Accepted) explicitly decided against — key rotation uses
version-indexed HD paths (m/74'/2'/0'/{version-2}'), not a KDF over
the salt. ADR-020 §6 W6 confirms v2 data's salt is permanently unused.
The salt field stays populated because ADR-018 locks the wire format.

No behavior or wire-format change. The implementation was already
correct per the ADRs; only the comments and TODO had drifted toward a
rejected design.

Changes (src/encryption.rs, comments/docs only):
- Module 'Salt Field' section: rewritten to match encryption.md and
  ADR-020/021/018
- EncryptedData.salt field doc: removed Phase B framing, added ADR
  references and pointer to encryption.md
- encrypt() doc: removed false claim 'salt allows key rotation'; now
  states IV/salt roles and ADR pointers
- TODO(Phase B) line: removed, replaced with a one-line rationale
  comment matching the neighboring IV comment's style
- Drive-by: fixed stale 'OQ-SVC-03' reference -> 'OQ-20' (the actual
  OQ file; OQ-SVC-03 appears nowhere else in the repo)

Verification (matches 0.1.0 publish baseline):
- cargo test --all-features: 108 passed
- cargo test (default): 101 passed
- cargo clippy --all-features --all-targets: clean
- cargo doc --no-deps --all-features: clean
- cargo publish --dry-run --all-features --allow-dirty: 44 files packaged
This commit is contained in:
glm-5.2 committed 2026-08-10 11:39:45 +00:00
1 parent 31d1991394
commit 44c35a96dd
1 file changed
+33 -19
+33 -19
View File
@@ -4,19 +4,24 @@
//! seed are encrypted using a key derived from the seed at path `m/74'/2'/0'/0'`.
//! The `EncryptedData` type stores the key version, salt, IV, and ciphertext.
//!
//! # Salt Field (Reserved for Future KDF-Based Key Derivation)
//! # Salt Field (Unused in v2 — Wire-Format Compatibility)
//!
//! The `salt` field in `EncryptedData` is **reserved for future KDF-based key
//! derivation** (Phase B). In v2, the encryption key is derived directly from the
//! seed at path `m/74'/2'/0'/0'` without using the salt. The salt is generated
//! randomly (32 bytes) and stored in `EncryptedData.salt` for forward
//! compatibility, but it plays no role in the v2 key derivation process.
//! The `salt` field in `EncryptedData` is **unused for key derivation in v2**
//! (ADR-020). The encryption key is derived from the seed via SLIP-0010 HD
//! derivation at path `m/74'/2'/0'/0'` (`PATHS::ENCRYPTION`); HD derivation
//! doesn't need a salt because the derivation path provides domain separation.
//! A 32-byte salt is generated randomly and stored for wire-format
//! compatibility with the TypeScript `EncryptedDataSchema`, but it plays no
//! cryptographic role.
//!
//! When key rotation is implemented in Phase B, the salt will be used as input to
//! HKDF or PBKDF2 for stretch-based key derivation, allowing the same seed to
//! produce different encryption keys without changing the derivation path. This
//! design ensures that the wire format does not need to change — the `salt` field
//! is already present and populated.
//! Key rotation uses version-indexed derivation paths (ADR-021 —
//! `m/74'/2'/0'/{version-2}'`), **not** a KDF over the salt. v2 data's salt is
//! permanently unused (ADR-020 §6 W6): it was random, never participated in
//! key derivation, and cannot be retroactively made load-bearing. The `salt`
//! field is retained because the wire format is frozen (ADR-018); a future
//! KDF-based derivation family would be a new `key_version` with its own
//! design and v2→vN migration, using a freshly-generated salt for the new
//! data only.
//!
//! # Wire Format
//!
@@ -65,17 +70,21 @@ pub const CURRENT_KEY_VERSION: u32 = 2;
/// from `@alkdev/storage`. Migration path: re-encrypt TypeScript-encrypted data
/// using the Rust vault with a new key version.
///
/// See OQ-SVC-03 for the compatibility tracking.
/// See OQ-20 for the compatibility tracking.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EncryptedData {
/// Key version for rotation support.
pub key_version: u32,
/// Base64-encoded random salt.
/// Base64-encoded random salt (32 bytes).
///
/// **Reserved for future KDF-based key derivation (Phase B).** In v2, the
/// encryption key is derived directly from the seed at path `m/74'/2'/0'/0'`
/// without using the salt. The salt is generated and stored for forward
/// compatibility but does not participate in key derivation.
/// **Unused for key derivation in v2** (ADR-020). HD derivation at
/// `m/74'/2'/0'/0'` doesn't need a salt — the path provides domain
/// separation. The salt is generated randomly and stored for wire-format
/// compatibility with the TypeScript `EncryptedDataSchema`; it plays no
/// cryptographic role. Key rotation uses version-indexed paths (ADR-021),
/// not a KDF over the salt. The field is retained because the wire format
/// is frozen (ADR-018). See `encryption.md` → "Salt field" for full
/// rationale.
pub salt: String,
/// Base64-encoded initialization vector (12 bytes for AES-GCM).
pub iv: String,
@@ -143,7 +152,11 @@ impl fmt::Debug for EncryptionKey {
/// Encrypt plaintext using an AES-256-GCM key.
///
/// Generates a random 12-byte IV and a random 32-byte salt for each encryption.
/// The salt allows key rotation without re-deriving from the seed.
/// The IV is the GCM nonce (fresh per call — IV reuse under the same key is
/// catastrophic). The salt is stored for wire-format compatibility with the
/// TypeScript `EncryptedDataSchema` and is not used in key derivation in v2
/// (ADR-020); key rotation uses version-indexed derivation paths (ADR-021),
/// not the salt.
///
/// # Arguments
///
@@ -166,7 +179,8 @@ pub(crate) fn encrypt(
.try_fill_bytes(&mut iv_bytes)
.map_err(|e| EncryptionError::Encryption(format!("rng failure: {e}")))?;
// TODO(Phase B): Use salt in HKDF-based key derivation
// Generate random salt (32 bytes, wire-format compat only — unused in v2 key
// derivation per ADR-020; rotation uses version-indexed paths per ADR-021).
let mut salt_bytes = [0u8; 32];
SysRng
.try_fill_bytes(&mut salt_bytes)