docs(architecture): resolve review #003 — type/API surface completeness
Review #003 found 11 critical, 14 warning, and 6 suggestion findings after reviews #001 (governance/security) and #002 (cross-document consistency/two-way-door audit) were resolved. The theme: types and APIs that were *referenced* but never *defined*, and stale ADR sketches that didn't match the now-updated spec docs. Critical fixes (11): - C1: DerivedKey #[derive(Deserialize)] contradicted the custom Deserialize that rejects "[REDACTED]" — dropped the derive, added explicit manual Serialize/Deserialize impls (protocol.md). - C2: encrypt prose said "derived at PATHS::ENCRYPTION" but the signature takes key_version — updated to encryption_path_for_version (service.md). - C3: derive_encryption_key returned DerivedKey, derive_encryption_key _for_version returned EncryptionKey (same cache) — unified on DerivedKey, defined CachedKey (service.md). - C4: tokio vs std::sync::RwLock contradiction — specified std::sync::RwLock, dropped tokio from vault deps (ADR-018, ADR-025, service.md). - C5: Missing drift rows in vault README — added #9 (key_version ignored) and #10 (rotate not implemented). - C6: ADR-022 build_root_context and invoke() sketches omitted abort_policy (9 fields vs 10) — added the field to both sketches. - C7: Capabilities type referenced 20+ times, never defined — added struct definition to core-types.md with Clone+Send+Sync, Zeroize, sealed builder API, immutability guard. - C8: SessionOverlaySource on CallAdapter but never defined, crate violation (alknet-call can't depend on alknet-agent) — defined the trait in alknet-call (call-protocol.md), matching the IdentityProvider pattern. - C9: CompositeOperationEnv dispatch fall-through was "a two-way door" — added contains() to OperationEnv trait, made the composite probe before dispatching, eliminating the sentinel ambiguity. - C10: No API for Layer 2 (connection overlay) registration, CallConnection undefined — defined CallConnection struct + register_imported() API (call-protocol.md). - C11: with_local signature diverged between two examples (4 args vs 5) — added capabilities as the 5th arg, made both examples consistent. Warning fixes (14): - W1: invoke_with_policy restructured as required method, invoke gets a default impl delegating to it — eliminates duplication across impls. - W2: CachedKey defined (service.md). - W3: EncryptionKey constructor/glue specified, added to re-export list. - W4: Secp256k1ExtendedPrivKey defined, derive_ethereum_key glue shown. - W5: encryption_path_for_version rejects version < 2 (v1 is TS PBKDF2). - W6: Wire payload schemas for all event types + ResponseEnvelope → EventEnvelope conversion table (call-protocol.md). - W7: Timeout section — deadline on OperationContext, composed calls inherit parent's deadline, CallAdapter::with_timeout(). - W8: Request ID generation spec — UUID v4 for composed calls, wire ID vs internal ID relationship for abort cascade. - W9: unlock_new already-unlocked behavior specified (returns AlreadyUnlocked). - W10: KeyType Serialize/Deserialize justification corrected (stale irpc reference removed). - W11: OperationProvenance and CompositionAuthority defined inline in operation-registry.md (were only in ADR-022). - W12: encrypt/decrypt free functions marked pub(crate), relationship to VaultServiceHandle methods stated. - W13: rotate signature removed from encryption.md (it's a VaultServiceHandle method, not a free function). - W14: CallAdapter::new() + with_session_source() + with_timeout() constructors shown. Suggestion fixes (6): Seed: Clone note, VaultServiceInner invariant, ExtendedPrivKey accessor signatures, CURRENT_KEY_VERSION location, ADR-018 stale actor text, derivation helpers re-export note.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-22-19
|
||||
last_updated: 2026-06-23
|
||||
---
|
||||
|
||||
# Mnemonic and Key Derivation
|
||||
@@ -91,6 +91,12 @@ pub struct Seed {
|
||||
The 64-byte seed from which all HD keys are derived. Zeroized on drop.
|
||||
This is the input to SLIP-0010 / BIP-0032 master key derivation.
|
||||
|
||||
`Seed` derives `Clone` for convenience (derivation functions take `&[u8]`,
|
||||
and the cache rebuild may need to reference the seed multiple times).
|
||||
Callers should prefer `&Seed` and avoid cloning — the seed is the root of
|
||||
trust, and each clone duplicates it into heap memory that lingers until
|
||||
zeroized.
|
||||
|
||||
## SLIP-0010 Ed25519 Derivation
|
||||
|
||||
The default derivation scheme. SLIP-0010 specifies Ed25519 HD key
|
||||
@@ -149,6 +155,15 @@ pub struct ExtendedPrivKey {
|
||||
The result of SLIP-0010 derivation. Zeroized on drop. Accessors return
|
||||
slices — the caller copies what it needs.
|
||||
|
||||
```rust
|
||||
impl ExtendedPrivKey {
|
||||
pub fn private_key(&self) -> &[u8]; // 32 bytes
|
||||
pub fn public_key(&self) -> &[u8]; // 32 bytes
|
||||
pub fn chain_code(&self) -> &[u8]; // 32 bytes
|
||||
pub fn path(&self) -> &str;
|
||||
}
|
||||
```
|
||||
|
||||
## BIP-0032 secp256k1 Derivation (Ethereum)
|
||||
|
||||
Feature-gated behind `secp256k1`. Implements BIP-0032 HD key derivation for
|
||||
@@ -163,6 +178,33 @@ Unlike SLIP-0010 (Ed25519), BIP-0032 supports both hardened and
|
||||
unhardened child derivation. The standard Ethereum path
|
||||
`m/44'/60'/0'/0/0` uses unhardened indices for the last two levels.
|
||||
|
||||
```rust
|
||||
#[derive(Clone, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
#[cfg(feature = "secp256k1")]
|
||||
pub struct Secp256k1ExtendedPrivKey {
|
||||
private_key: Vec<u8>, // 32 bytes
|
||||
public_key: Vec<u8>, // 33 bytes (compressed)
|
||||
chain_code: Vec<u8>, // 32 bytes
|
||||
path: String, // the path that produced this key
|
||||
}
|
||||
|
||||
#[cfg(feature = "secp256k1")]
|
||||
impl Secp256k1ExtendedPrivKey {
|
||||
pub fn private_key(&self) -> &[u8];
|
||||
pub fn public_key(&self) -> &[u8];
|
||||
pub fn chain_code(&self) -> &[u8];
|
||||
pub fn path(&self) -> &str;
|
||||
}
|
||||
```
|
||||
|
||||
The `VaultServiceHandle::derive_ethereum_key` method calls
|
||||
`derive_secp256k1_path` and wraps the result into a `DerivedKey`:
|
||||
`DerivedKey { key_type: KeyType::Secp256k1, private_key:
|
||||
extended.private_key().to_vec(), public_key:
|
||||
extended.public_key().to_vec() }`. The `Secp256k1ExtendedPrivKey` is then
|
||||
dropped and zeroized; the `DerivedKey` is the caller-facing type.
|
||||
|
||||
### Why a separate module
|
||||
|
||||
SLIP-0010 and BIP-0032 differ in:
|
||||
@@ -200,9 +242,17 @@ Helper functions construct parameterized paths:
|
||||
|
||||
```rust
|
||||
pub fn device_path(index: u32) -> String; // m/74'/0'/0'/{index}'
|
||||
pub fn encryption_path_for_version(version: u32) -> String; // m/74'/2'/0'/{version-2}'
|
||||
pub fn encryption_path_for_version(version: u32) -> Result<String, DerivationError>;
|
||||
// m/74'/2'/0'/{version-2}' — returns InvalidPath for version < 2
|
||||
```
|
||||
|
||||
`encryption_path_for_version` returns `DerivationError::InvalidPath` for
|
||||
`version < 2`. v1 is reserved for the TS PBKDF2 legacy (ADR-020) — the vault
|
||||
cannot derive it, and silently mapping v1 to the v2 path would produce the
|
||||
wrong key (making v1 blobs appear to "decrypt" with a corrupted key). v0 is
|
||||
meaningless. `derive_encryption_key_for_version` propagates this error
|
||||
(`VaultServiceError::InvalidPath`).
|
||||
|
||||
### Path semantics
|
||||
|
||||
| Path | Purpose | Key type | Used by |
|
||||
@@ -216,7 +266,9 @@ pub fn encryption_path_for_version(version: u32) -> String; // m/74'/2'/0'/{v
|
||||
`encryption_path_for_version` maps a key version to its derivation path
|
||||
(ADR-021). v2 (current) maps to `m/74'/2'/0'/0'` (which is `PATHS::ENCRYPTION`);
|
||||
v3 maps to `m/74'/2'/0'/1'`; etc. This is the rotation mechanism — each
|
||||
version gets a cryptographically independent key from the same seed.
|
||||
version gets a cryptographically independent key from the same seed. Returns
|
||||
`InvalidPath` for `version < 2` (v1 is TS PBKDF2 legacy — undecryptable by
|
||||
the vault by design).
|
||||
|
||||
`KeyType` tags `DerivedKey` (see [protocol.md](protocol.md)) and
|
||||
`CachedKey` (see [service.md](service.md)) so consumers know what they
|
||||
|
||||
Reference in New Issue
Block a user