docs(vault): remove drift tracking artifacts, bump vault docs to stable

The vault spec-to-implementation sync is complete. Remove the drift
tracking tools that were only needed during sync:

- Remove the Known Source Drift table from vault/README.md
- Remove 'known drift' / 'current source uses X' prose from Security
  Constraints sections in vault/README.md, encryption.md, and service.md.
  The permanent constraint statements (OsRng for IVs, zeroized drop,
  no unwrap, etc.) are preserved.
- Remove the drift paragraph in encryption.md Key Versioning.
- Remove stale 'to be updated per ADR-025' / 'postcard tests to be
  removed' notes in protocol.md References.
- Bump status: draft -> stable in the frontmatter of all vault docs
  (README, mnemonic-derivation, encryption, service, protocol).
- Update architecture/README.md: vault doc status entries to stable,
  Current State paragraph reflects vault implementation complete (no
  'pending ADR-025/026 refactor' language).
This commit is contained in:
2026-06-23 14:15:13 +00:00
parent b93a85a280
commit 323ee85d40
6 changed files with 28 additions and 70 deletions

View File

@@ -1,5 +1,5 @@
---
status: draft
status: stable
last_updated: 2026-06-23
---
@@ -30,10 +30,10 @@ seed and derived private keys never cross the network.
| Document | Status | Description |
|----------|--------|-------------|
| [mnemonic-derivation.md](mnemonic-derivation.md) | draft | BIP39, SLIP-0010, BIP-0032, derivation paths, key types |
| [encryption.md](encryption.md) | draft | AES-256-GCM, EncryptedData, key versioning, HD derivation (ADR-020) |
| [service.md](service.md) | draft | VaultServiceHandle lifecycle, direct dispatch, cache, error model |
| [protocol.md](protocol.md) | draft | DerivedKey redaction, KeyType, serialization behavior |
| [mnemonic-derivation.md](mnemonic-derivation.md) | stable | BIP39, SLIP-0010, BIP-0032, derivation paths, key types |
| [encryption.md](encryption.md) | stable | AES-256-GCM, EncryptedData, key versioning, HD derivation (ADR-020) |
| [service.md](service.md) | stable | VaultServiceHandle lifecycle, direct dispatch, cache, error model |
| [protocol.md](protocol.md) | stable | DerivedKey redaction, KeyType, serialization behavior |
## Applicable ADRs
@@ -92,17 +92,13 @@ documented here so implementation agents don't miss them. See
[service.md → Security Constraints](service.md#security-constraints) for
the full list.
- **OsRng for IVs**: AES-GCM IVs must use `OsRng`, not `rand::random()`. The
current source uses `rand::random()` — this is a known drift from the
spec and must be corrected during implementation sync.
- **OsRng for IVs**: AES-GCM IVs must use `OsRng`, not `rand::random()`.
- **Zeroized drop**: `Seed`, `Mnemonic`, `ExtendedPrivKey`,
`Secp256k1ExtendedPrivKey`, `EncryptionKey`, `CachedKey`, and
`DerivedKey` all derive `Zeroize` and `ZeroizeOnDrop`. The cache must
clear on drop, not just on explicit `lock()`.
- **No `unwrap()` outside tests**: poisoned lock recovery uses
`unwrap_or_else(|e| e.into_inner())` or explicit error propagation. The
current source uses `unwrap()` in `VaultServiceHandle` methods — this
is a known drift and must be corrected.
`unwrap_or_else(|e| e.into_inner())` or explicit error propagation.
- **DerivedKey redaction in serialization**: `DerivedKey` serializes the
`private_key` as `"[REDACTED]"` in all formats (ADR-025 dropped the
postcard/remote path that previously preserved bytes in binary formats).
@@ -111,26 +107,6 @@ the full list.
not the primary control — the primary control is that `DerivedKey` never
crosses the call protocol wire (ADR-014).
## Known Source Drift
The vault crate carries over source from the POC. The following items are
known divergences between the current source and the spec. All must be
corrected during implementation sync. This table is the single source of
truth for drift tracking — if an item is fixed in source, update this table.
| # | Item | Current source behavior | Target behavior (per spec) | Source location | Spec reference |
|---|------|------------------------|-----------------------------|-----------------|----------------|
| 1 | IV generation | `rand::random()` | `OsRng` (CSPRNG) | `encryption.rs` L133 | [encryption.md → Security Constraints](encryption.md#security-constraints), [service.md → Security Constraints](service.md#security-constraints) |
| 2 | RwLock `unwrap()` | `unwrap()` on every `RwLock` acquisition (L142, 161, 182, 191, 196, 227, 264, 307, 340, 367) | `unwrap_or_else(\|e\| e.into_inner())` for poisoned lock recovery | `service.rs` (see line numbers) | [service.md → Security Constraints](service.md#security-constraints) |
| 3 | `CURRENT_KEY_VERSION` | `1` (HD-derived, but v1 is reserved for TS PBKDF2 legacy per ADR-020) | `2` (HD-derived, per ADR-020) | `encryption.rs` | [encryption.md → Key Versioning](encryption.md#key-versioning), [ADR-020](../../decisions/020-hd-derivation-for-encryption-keys.md) |
| 4 | irpc dependency | `VaultProtocol` enum with `#[rpc_requests]`, `VaultServiceActor`, `Client<VaultProtocol>`, irpc/postcard deps | Remove entirely — direct method calls on `VaultServiceHandle` (ADR-025) | `protocol.rs`, `service.rs`, `Cargo.toml` | [ADR-025](../../decisions/025-vault-local-only-dispatch.md) |
| 5 | `DerivedKey` dual serialization | JSON redacts, postcard preserves bytes | Always redact on serialize; reject `"[REDACTED]"` on deserialize with error (ADR-025, resolves W8) | `protocol.rs` | [protocol.md → Serialization Redaction](protocol.md#serialization-redaction), [ADR-025](../../decisions/025-vault-local-only-dispatch.md) |
| 6 | `HashMap::clear` zeroization | `KeyCache::clear()` removes entries and relies on `CachedKey`'s `Drop` impl for zeroization | Verify `HashMap::clear()` actually drops values (it does, but worth a test) | `cache.rs` | [service.md → Security Constraints](service.md#security-constraints) |
| 7 | `derive_password` / `site_password_path` | `derive_password`, `derive_password_string`, `site_password_path` methods exist | Remove entirely — password-manager pattern not relevant to RPC system's vault (ADR-025, resolves C9) | `service.rs`, `mnemonic-derivation.rs` | [ADR-025](../../decisions/025-vault-local-only-dispatch.md) |
| 8 | `unlock_new` return type | Returns `String` (not zeroized on drop) | Return `Zeroizing<String>` — the mnemonic is the root of trust and must not linger in freed memory (resolves W7) | `service.rs` | [service.md → unlock_new](service.md#unlock_newword_count--phrase) |
| 9 | `key_version` ignored in encrypt/decrypt | `encrypt`/`decrypt` always derive at `PATHS::ENCRYPTION` regardless of `key_version` | Derive at `encryption_path_for_version(key_version)` — encrypt stamps the passed version, decrypt selects the key by the blob's version (ADR-021) | `service.rs` | [service.md → encrypt](service.md#encryptplaintext-key_version--encrypteddata), [ADR-021](../../decisions/021-key-rotation-via-version-indexed-paths.md) |
| 10 | `rotate` not implemented | No `rotate` method exists | Implement `rotate(encrypted, to_version)` — decrypt with old version's key, re-encrypt with new version's key (ADR-021) | `service.rs` | [service.md → rotate](service.md#rotateencrypted-to_version--encrypteddata), [ADR-021](../../decisions/021-key-rotation-via-version-indexed-paths.md) |
## Public API
The vault re-exports its primary types from the crate root:

View File

@@ -1,5 +1,5 @@
---
status: draft
status: stable
last_updated: 2026-06-23
---
@@ -219,12 +219,6 @@ Rotation decrypts with the old version's key and re-encrypts with the new
version's key. No new mnemonic needed — the same seed produces all version
keys via different paths. See ADR-021 for the full mechanism.
**The current source uses `CURRENT_KEY_VERSION = 1` with HD derivation and
does not implement version-indexed paths or `rotate`.** These are drift
items to be corrected during implementation sync. See ADR-020 (version
bump to 2) and ADR-021 (rotation mechanism). See the [Known Source
Drift](README.md#known-source-drift) table in the vault README.
## Errors
```rust
@@ -281,12 +275,10 @@ These are security-critical implementation requirements.
- **OsRng for IVs**: The IV must be generated with `OsRng` (or an
equivalent CSPRNG), never `rand::random()`. IV reuse under the same key
is catastrophic for GCM — it breaks authenticity and creates a
two-time-pad on the plaintext. **The current source uses
`rand::random()` for IV generation (`encryption.rs` line 133) — this is a
known drift from the spec and must be corrected during implementation
sync.** `rand::random()` uses the thread-local RNG which may not be a
CSPRNG on all platforms; `OsRng` reads from the operating system's
entropy source and is the correct choice for cryptographic nonces.
two-time-pad on the plaintext. `rand::random()` uses the thread-local RNG
which may not be a CSPRNG on all platforms; `OsRng` reads from the
operating system's entropy source and is the correct choice for
cryptographic nonces.
- **Zeroized drop**: `EncryptionKey` derives `Zeroize` and
`ZeroizeOnDrop`. The key bytes are zeroized before deallocation. Do not
store key material in types that don't zeroize.

View File

@@ -1,5 +1,5 @@
---
status: draft
status: stable
last_updated: 2026-06-23
---

View File

@@ -1,5 +1,5 @@
---
status: draft
status: stable
last_updated: 2026-06-23
---
@@ -236,9 +236,8 @@ ADR-025 and [open-questions.md](../../open-questions.md).
## References
- Implementation: `crates/alknet-vault/src/protocol.rs` (to be updated
per ADR-025 — remove `VaultProtocol` enum and irpc usage)
- Implementation: `crates/alknet-vault/src/protocol.rs`
- Tests: `crates/alknet-vault/src/protocol.rs` (unit tests for redaction
and zeroize behavior; postcard tests to be removed)
and zeroize behavior)
- [service.md](service.md) — `VaultServiceHandle` runtime API
- [mnemonic-derivation.md](mnemonic-derivation.md) — what `KeyType` means

View File

@@ -1,5 +1,5 @@
---
status: draft
status: stable
last_updated: 2026-06-23
---
@@ -356,29 +356,20 @@ don't miss them.
- **OsRng for IVs**: AES-GCM IVs and any cryptographic nonces must use
`OsRng` (or equivalent CSPRNG), not `rand::random()`. IV reuse under the
same key is catastrophic for GCM (authenticity breaks, two-time-pad on
plaintext). **The current source uses `rand::random()` for IV generation
in `encryption::encrypt()` — this is a known drift and must be corrected
during implementation sync.**
plaintext).
- **Zeroized drop**: `Seed`, `Mnemonic`, `CachedKey`, `EncryptionKey`,
`ExtendedPrivKey`, `Secp256k1ExtendedPrivKey`, and `DerivedKey` all
derive `Zeroize` and `ZeroizeOnDrop`. The cache must clear on drop, not
just on explicit `lock()`. **The current `KeyCache::clear()` removes
entries but relies on `CachedKey`'s `Drop` impl for zeroization —
verify that `HashMap::clear()` actually drops the values (it does, but
this is worth a test).**
just on explicit `lock()`.
- **No `unwrap()` or `expect()` outside tests**: poisoned lock recovery
uses `unwrap_or_else(|e| e.into_inner())` or explicit error propagation.
A panic in one vault operation must not brick the vault for all other
operations. **The current source uses `unwrap()` on every `RwLock`
acquisition in `VaultServiceHandle` (lines 142, 161, 182, 191, 196, 227,
264, 307, 340, 367) — this is a known drift and must be corrected. A
poisoned lock should be recovered with `unwrap_or_else(|e|
e.into_inner())`, not panicked.**
operations. A poisoned lock should be recovered with
`unwrap_or_else(|e| e.into_inner())`, not panicked.
- **`DerivedKey` is move-only, not `Clone`**: `DerivedKey` does not derive
`Clone`. It is move-only — consumers receive it by value and zeroize it
when done (handled by `#[zeroize(drop)]`). This prevents accidental
duplication of secret material. **The current source does not derive
`Clone` on `DerivedKey` — this is correct.**
duplication of secret material.
- **Cache eviction zeroizes**: when the cache evicts an entry (LRU or
TTL), the `CachedKey` is dropped, which triggers `ZeroizeOnDrop`. Do not
replace `CachedKey` with a type that doesn't zeroize.