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:
@@ -7,7 +7,7 @@ last_updated: 2026-06-23
|
||||
|
||||
## Current State
|
||||
|
||||
**Pre-implementation.** The project has completed a pivot from a three-layer model to an ALPN-as-service model. The greenfield workspace contains only `alknet-vault` (stable — implementation exists, pending ADR-025/026 refactor to drop irpc and remove derive_password) and research/reference material. Foundational ADRs (001–026) are in place. ADR-024 resolves the registry mutability question and the `OperationContext.env` type identity crisis by layering the registry by trust boundary. ADR-025 drops irpc from the vault, making it local-only by construction. ADR-026 records the HD-derivation key model as a foundational decision. Review #003 (type/API surface completeness) resolved: `DerivedKey` derive contradiction, `encrypt` prose, return-type divergence, RwLock contradiction, drift table gaps, ADR-022 stale sketches, `Capabilities`/`SessionOverlaySource`/`CallConnection`/`CachedKey` definitions, `CompositeOperationEnv` dispatch contract, `with_local` signature, payload schemas, timeout propagation, and request ID generation. The alknet-core, alknet-call, and alknet-vault crate specs are in draft.
|
||||
**Pre-implementation.** The project has completed a pivot from a three-layer model to an ALPN-as-service model. The greenfield workspace contains only `alknet-vault` (stable — implementation complete and verified, local-only by construction per ADR-025, HD-derivation key model per ADR-026) and research/reference material. Foundational ADRs (001–026) are in place. ADR-024 resolves the registry mutability question and the `OperationContext.env` type identity crisis by layering the registry by trust boundary. ADR-025 drops irpc from the vault, making it local-only by construction. ADR-026 records the HD-derivation key model as a foundational decision. Review #003 (type/API surface completeness) resolved: `DerivedKey` derive contradiction, `encrypt` prose, return-type divergence, RwLock contradiction, drift table gaps, ADR-022 stale sketches, `Capabilities`/`SessionOverlaySource`/`CallConnection`/`CachedKey` definitions, `CompositeOperationEnv` dispatch contract, `with_local` signature, payload schemas, timeout propagation, and request ID generation. The alknet-core and alknet-call crate specs are in draft; the alknet-vault crate specs are stable.
|
||||
|
||||
**Next step**: Implementation. All open questions are resolved. The specs have passed three review passes (#001 governance/security model, #002 cross-document consistency/two-way-door audit, #003 type/API surface completeness).
|
||||
|
||||
@@ -25,11 +25,11 @@ last_updated: 2026-06-23
|
||||
| [crates/call/README.md](crates/call/README.md) | draft | alknet-call crate index |
|
||||
| [crates/call/call-protocol.md](crates/call/call-protocol.md) | draft | CallAdapter, EventEnvelope framing, stream model, PendingRequestMap, bidirectional calls, streaming subscribe example |
|
||||
| [crates/call/operation-registry.md](crates/call/operation-registry.md) | draft | OperationSpec, Handler, OperationRegistry, AccessControl, capability injection, service discovery, irpc integration |
|
||||
| [crates/vault/README.md](crates/vault/README.md) | draft | alknet-vault crate index |
|
||||
| [crates/vault/mnemonic-derivation.md](crates/vault/mnemonic-derivation.md) | draft | BIP39, SLIP-0010, BIP-0032, derivation paths, key types |
|
||||
| [crates/vault/encryption.md](crates/vault/encryption.md) | draft | AES-256-GCM, EncryptedData, key versioning, salt (Phase B reserved) |
|
||||
| [crates/vault/service.md](crates/vault/service.md) | draft | VaultServiceHandle lifecycle, actor dispatch, cache, error model |
|
||||
| [crates/vault/protocol.md](crates/vault/protocol.md) | draft | DerivedKey redaction, KeyType, serialization behavior |
|
||||
| [crates/vault/README.md](crates/vault/README.md) | stable | alknet-vault crate index |
|
||||
| [crates/vault/mnemonic-derivation.md](crates/vault/mnemonic-derivation.md) | stable | BIP39, SLIP-0010, BIP-0032, derivation paths, key types |
|
||||
| [crates/vault/encryption.md](crates/vault/encryption.md) | stable | AES-256-GCM, EncryptedData, key versioning, salt (Phase B reserved) |
|
||||
| [crates/vault/service.md](crates/vault/service.md) | stable | VaultServiceHandle lifecycle, direct dispatch, cache, error model |
|
||||
| [crates/vault/protocol.md](crates/vault/protocol.md) | stable | DerivedKey redaction, KeyType, serialization behavior |
|
||||
|
||||
## ADR Table
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
status: draft
|
||||
status: stable
|
||||
last_updated: 2026-06-23
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user