From 80128a56e51335969781ca47471504f584d01fc1 Mon Sep 17 00:00:00 2001 From: "glm-5.1" Date: Tue, 16 Jun 2026 11:10:07 +0000 Subject: [PATCH] refactor: rename alknet-secret to alknet-vault MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the crate from alknet-secret to alknet-vault to better reflect its purpose as a local key vault (seed management, key derivation, encryption) rather than a network service. Symbol renames: - SecretService → VaultService - SecretServiceHandle → VaultServiceHandle - SecretServiceActor → VaultServiceActor - SecretServiceError → VaultServiceError - SecretProtocol → VaultProtocol - SecretMessage → VaultMessage - ServiceLocked → VaultLocked - alknet_secret → alknet_vault (crate name) Update ADR-008 with vault access pattern: the vault is a capability source, not a service endpoint. The CLI injects derived/decrypted material into operation contexts — handlers never hold vault references. --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 2 +- .../Cargo.toml | 6 +- .../src/cache.rs | 2 +- .../src/derivation.rs | 6 +- .../src/encryption.rs | 2 +- .../src/ethereum.rs | 4 +- .../src/lib.rs | 27 +- .../src/mnemonic.rs | 0 .../src/protocol.rs | 42 +-- .../src/service.rs | 258 +++++++++--------- .../tests/derivation_tests.rs | 16 +- .../tests/encryption_tests.rs | 12 +- .../tests/service_tests.rs | 28 +- .../tests/test_vectors.rs | 16 +- docs/architecture/README.md | 6 +- .../decisions/003-crate-decomposition.md | 8 +- .../008-secret-service-integration.md | 57 ++-- .../009-one-way-door-decision-framework.md | 2 +- docs/architecture/open-questions.md | 4 +- docs/architecture/overview.md | 16 +- 22 files changed, 262 insertions(+), 256 deletions(-) rename crates/{alknet-secret => alknet-vault}/Cargo.toml (76%) rename crates/{alknet-secret => alknet-vault}/src/cache.rs (99%) rename crates/{alknet-secret => alknet-vault}/src/derivation.rs (98%) rename crates/{alknet-secret => alknet-vault}/src/encryption.rs (99%) rename crates/{alknet-secret => alknet-vault}/src/ethereum.rs (98%) rename crates/{alknet-secret => alknet-vault}/src/lib.rs (52%) rename crates/{alknet-secret => alknet-vault}/src/mnemonic.rs (100%) rename crates/{alknet-secret => alknet-vault}/src/protocol.rs (89%) rename crates/{alknet-secret => alknet-vault}/src/service.rs (79%) rename crates/{alknet-secret => alknet-vault}/tests/derivation_tests.rs (77%) rename crates/{alknet-secret => alknet-vault}/tests/encryption_tests.rs (84%) rename crates/{alknet-secret => alknet-vault}/tests/service_tests.rs (72%) rename crates/{alknet-secret => alknet-vault}/tests/test_vectors.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index 57bc182..1c67965 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,7 +47,7 @@ dependencies = [ ] [[package]] -name = "alknet-secret" +name = "alknet-vault" version = "0.1.0" dependencies = [ "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index 986ddf4..e087b9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "crates/alknet-secret", + "crates/alknet-vault", ] resolver = "2" diff --git a/README.md b/README.md index 421cbab..a14f2de 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A self-hostable networking toolkit built on QUIC+TLS with ALPN-based protocol di | Crate | Status | Description | |-------|--------|-------------| -| `alknet-secret` | stable | BIP39/SLIP-0010/AES-GCM key derivation and encryption | +| `alknet-vault` | stable | Local key vault: BIP39/SLIP-0010/AES-GCM key derivation and encryption | | `alknet-core` | planned | ProtocolHandler trait, ALPN router, auth/identity, config | | `alknet-ssh` | planned | SSH handler (russh), SOCKS5, port forwarding | | `alknet-call` | planned | JSON-RPC call protocol (EventEnvelope framing) | diff --git a/crates/alknet-secret/Cargo.toml b/crates/alknet-vault/Cargo.toml similarity index 76% rename from crates/alknet-secret/Cargo.toml rename to crates/alknet-vault/Cargo.toml index 7bece4c..b92b81f 100644 --- a/crates/alknet-secret/Cargo.toml +++ b/crates/alknet-vault/Cargo.toml @@ -1,13 +1,13 @@ [package] -name = "alknet-secret" +name = "alknet-vault" version.workspace = true edition.workspace = true license.workspace = true -description = "BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, AES-256-GCM encryption, and SecretProtocol irpc service for alknet" +description = "Local key vault: BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, AES-256-GCM encryption for securing provider keys, credentials, and identity material" repository.workspace = true [lib] -name = "alknet_secret" +name = "alknet_vault" [features] default = [] diff --git a/crates/alknet-secret/src/cache.rs b/crates/alknet-vault/src/cache.rs similarity index 99% rename from crates/alknet-secret/src/cache.rs rename to crates/alknet-vault/src/cache.rs index 383f4bb..1b3e11b 100644 --- a/crates/alknet-secret/src/cache.rs +++ b/crates/alknet-vault/src/cache.rs @@ -1,4 +1,4 @@ -//! TTL-based key cache with LRU eviction for SecretService. +//! TTL-based key cache with LRU eviction for VaultService. //! //! The `KeyCache` stores derived key material keyed by derivation path. Entries //! expire after a configurable TTL (default: 1 hour) and are evicted lazily on diff --git a/crates/alknet-secret/src/derivation.rs b/crates/alknet-vault/src/derivation.rs similarity index 98% rename from crates/alknet-secret/src/derivation.rs rename to crates/alknet-vault/src/derivation.rs index 71c7b55..dcbd73e 100644 --- a/crates/alknet-secret/src/derivation.rs +++ b/crates/alknet-vault/src/derivation.rs @@ -24,7 +24,7 @@ type HmacSha512 = Hmac; /// Well-known derivation path constants for alknet key material. /// -/// These paths are defined once and referenced by both the secret service and +/// These paths are defined once and referenced by both the vault service and /// external consumers that need to request specific key types. #[allow(non_snake_case)] pub mod PATHS { @@ -101,8 +101,8 @@ impl ExtendedPrivKey { /// # Example /// /// ``` -/// use alknet_secret::derivation::{derive_path_from_seed, PATHS}; -/// use alknet_secret::mnemonic::Mnemonic; +/// use alknet_vault::derivation::{derive_path_from_seed, PATHS}; +/// use alknet_vault::mnemonic::Mnemonic; /// /// let mnemonic = Mnemonic::generate(24).unwrap(); /// let seed = mnemonic.to_seed(None); diff --git a/crates/alknet-secret/src/encryption.rs b/crates/alknet-vault/src/encryption.rs similarity index 99% rename from crates/alknet-secret/src/encryption.rs rename to crates/alknet-vault/src/encryption.rs index 7322624..b1616a0 100644 --- a/crates/alknet-secret/src/encryption.rs +++ b/crates/alknet-vault/src/encryption.rs @@ -52,7 +52,7 @@ pub const CURRENT_KEY_VERSION: u32 = 1; /// /// The Rust `EncryptedData` is a superset of the TypeScript `EncryptedDataSchema` /// from `@alkdev/storage`. Migration path: re-encrypt TypeScript-encrypted data -/// using the Rust secret service with a new key version. +/// using the Rust vault with a new key version. /// /// See OQ-SVC-03 for the compatibility tracking. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] diff --git a/crates/alknet-secret/src/ethereum.rs b/crates/alknet-vault/src/ethereum.rs similarity index 98% rename from crates/alknet-secret/src/ethereum.rs rename to crates/alknet-vault/src/ethereum.rs index 83d6cb3..1abe631 100644 --- a/crates/alknet-secret/src/ethereum.rs +++ b/crates/alknet-vault/src/ethereum.rs @@ -138,8 +138,8 @@ fn derive_child( /// # Example /// /// ```ignore -/// use alknet_secret::ethereum::derive_secp256k1_path; -/// use alknet_secret::derivation::PATHS; +/// use alknet_vault::ethereum::derive_secp256k1_path; +/// use alknet_vault::derivation::PATHS; /// /// let key = derive_secp256k1_path(seed, PATHS::ETHEREUM).unwrap(); /// assert_eq!(key.private_key().len(), 32); diff --git a/crates/alknet-secret/src/lib.rs b/crates/alknet-vault/src/lib.rs similarity index 52% rename from crates/alknet-secret/src/lib.rs rename to crates/alknet-vault/src/lib.rs index 60baf85..dfbaacd 100644 --- a/crates/alknet-secret/src/lib.rs +++ b/crates/alknet-vault/src/lib.rs @@ -1,17 +1,18 @@ -//! # alknet-secret +//! # alknet-vault //! -//! BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, AES-256-GCM -//! encryption for external credentials, and the `SecretProtocol` irpc service. +//! Local key vault: BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, +//! AES-256-GCM encryption for securing provider keys, credentials, and identity material. //! -//! This crate is the only component that holds the master seed phrase. All other -//! crates request derived keys through the `SecretProtocol` irpc service or the -//! `SecretServiceHandle` local API. +//! This crate is the only component that holds the master seed phrase. The CLI binary +//! unlocks the vault at startup and injects derived/decrypted material into operation +//! contexts. Other crates never access the vault directly — they receive keys through +//! their operation context or via the call protocol. //! //! ## Crate Independence //! -//! alknet-secret does **not** depend on alknet-core or alknet-storage. Per ADR-027, -//! it is fully independent. The `EncryptedData` wire format is shared with -//! alknet-storage by type-level compatibility, not a crate dependency. +//! alknet-vault does **not** depend on alknet-core or any other alknet crate. It is +//! fully independent and usable in contexts where QUIC networking doesn't exist (CLI +//! tools, test harnesses, WASM key derivation). //! //! ## Security Model //! @@ -24,8 +25,8 @@ //! - [`mnemonic`] — BIP39 mnemonic generation, validation, and seed derivation //! - [`derivation`] — SLIP-0010 Ed25519 HD key derivation and path constants //! - [`encryption`] — AES-256-GCM encrypt/decrypt and `EncryptedData` type -//! - [`protocol`] — `SecretProtocol` irpc service enum, `DerivedKey`, `KeyType` -//! - [`service`] — `SecretService` implementation with Unlock/Lock lifecycle +//! - [`protocol`] — `VaultProtocol` irpc message enum, `DerivedKey`, `KeyType` +//! - [`service`] — `VaultService` implementation with Unlock/Lock lifecycle //! - [`ethereum`] — BIP-0032 secp256k1 HD key derivation (behind `secp256k1` feature) pub mod cache; @@ -43,5 +44,5 @@ pub use cache::CacheConfig; pub use derivation::{DerivationError, ExtendedPrivKey, PATHS}; pub use encryption::{EncryptedData, EncryptionError}; pub use mnemonic::{Language, Mnemonic, Seed}; -pub use protocol::{DerivedKey, KeyType, SecretMessage, SecretProtocol}; -pub use service::{SecretService, SecretServiceActor, SecretServiceError, SecretServiceHandle}; +pub use protocol::{DerivedKey, KeyType, VaultMessage, VaultProtocol}; +pub use service::{VaultService, VaultServiceActor, VaultServiceError, VaultServiceHandle}; diff --git a/crates/alknet-secret/src/mnemonic.rs b/crates/alknet-vault/src/mnemonic.rs similarity index 100% rename from crates/alknet-secret/src/mnemonic.rs rename to crates/alknet-vault/src/mnemonic.rs diff --git a/crates/alknet-secret/src/protocol.rs b/crates/alknet-vault/src/protocol.rs similarity index 89% rename from crates/alknet-secret/src/protocol.rs rename to crates/alknet-vault/src/protocol.rs index 2c2a6be..dd148c4 100644 --- a/crates/alknet-secret/src/protocol.rs +++ b/crates/alknet-vault/src/protocol.rs @@ -1,12 +1,12 @@ -//! SecretProtocol irpc service definition and associated types. +//! VaultProtocol irpc message definition and associated types. //! -//! This module defines the `SecretProtocol` enum for irpc-based inter-service -//! communication. The protocol supports unlock/lock lifecycle, key derivation, +//! This module defines the `VaultProtocol` enum for irpc-based message dispatch. +//! The protocol supports unlock/lock lifecycle, key derivation, //! and encryption/decryption operations. //! //! # Protocol Operation //! -//! The SecretProtocol follows a lifecycle: the service starts in a **locked** +//! The VaultProtocol follows a lifecycle: the vault starts in a **locked** //! state where no derivation or encryption operations are possible. The `Unlock` //! call loads the seed into memory (derived from the mnemonic passphrase). After //! that, derive and encrypt/decrypt operations are available. The `Lock` call @@ -16,7 +16,7 @@ //! //! For local (in-process) calls, the protocol uses tokio channels directly. //! For remote (in-cluster) calls, the protocol is serialized with postcard. -//! For cross-node (call protocol) exposure, the service is wrapped in an +//! For cross-node (call protocol) exposure, the vault is wrapped in an //! operation that serializes to JSON. use std::fmt; @@ -98,27 +98,27 @@ impl Serialize for DerivedKey { } } -/// SecretProtocol service definition. +/// VaultProtocol message definition. /// -/// This is the irpc protocol enum that defines all secret service operations. +/// This is the irpc protocol enum that defines all vault operations. /// The `#[rpc_requests]` macro generates: -/// - **`SecretMessage`**: message enum with `WithChannels` wrappers for each variant -/// - **`Channels`** impls for each wrapper type +/// - **`VaultMessage`**: message enum with `WithChannels` wrappers for each variant +/// - **`Channels`** impls for each wrapper type /// - **`From`** impls for protocol enum and message enum conversions /// - **`Service`** and **`RemoteService`** trait impls for remote dispatch /// /// # State Requirements /// -/// All operations except `Unlock` require the service to be in an **unlocked** -/// state. Calling derive/encrypt/decrypt on a locked service returns an error. -#[rpc_requests(message = SecretMessage, no_spans)] +/// All operations except `Unlock` require the vault to be in an **unlocked** +/// state. Calling derive/encrypt/decrypt on a locked vault returns an error. +#[rpc_requests(message = VaultMessage, no_spans)] #[derive(Debug, Serialize, Deserialize)] -pub enum SecretProtocol { +pub enum VaultProtocol { /// Derive an Ed25519 keypair at the given path. /// /// Path format: `m/74'/0'/0'/0'` (SLIP-0010 hardened-only notation). /// Returns a `DerivedKey` with `KeyType::Ed25519`. - #[rpc(tx = irpc::channel::oneshot::Sender>)] + #[rpc(tx = irpc::channel::oneshot::Sender>)] #[wrap(DeriveEd25519)] DeriveEd25519 { /// SLIP-0010 derivation path (e.g., "m/74'/0'/0'/0'"). @@ -129,7 +129,7 @@ pub enum SecretProtocol { /// /// The default encryption path is `m/74'/2'/0'/0'`. /// Returns a `DerivedKey` with `KeyType::Aes256Gcm`. - #[rpc(tx = irpc::channel::oneshot::Sender>)] + #[rpc(tx = irpc::channel::oneshot::Sender>)] #[wrap(DeriveEncryptionKey)] DeriveEncryptionKey { /// SLIP-0010 derivation path for the encryption key. @@ -140,7 +140,7 @@ pub enum SecretProtocol { /// /// The default Ethereum path is `m/44'/60'/0'/0/0`. /// Returns a `DerivedKey` with `KeyType::Secp256k1`. - #[rpc(tx = irpc::channel::oneshot::Sender>)] + #[rpc(tx = irpc::channel::oneshot::Sender>)] #[wrap(DeriveEthereumKey)] DeriveEthereumKey { /// BIP-0032 derivation path (e.g., "m/44'/60'/0'/0/0"). @@ -151,7 +151,7 @@ pub enum SecretProtocol { /// /// Path format: `m/74'/1'/0'/{hash}'` (SLIP-0010 hardened notation). /// The `length` parameter controls the output length. - #[rpc(tx = irpc::channel::oneshot::Sender, crate::service::SecretServiceError>>)] + #[rpc(tx = irpc::channel::oneshot::Sender, crate::service::VaultServiceError>>)] #[wrap(DerivePassword)] DerivePassword { /// SLIP-0010 derivation path for the password. @@ -164,7 +164,7 @@ pub enum SecretProtocol { /// /// The key is derived at the path `m/74'/2'/0'/0'` with the given version. /// Returns an `EncryptedData` blob suitable for storage. - #[rpc(tx = irpc::channel::oneshot::Sender>)] + #[rpc(tx = irpc::channel::oneshot::Sender>)] #[wrap(Encrypt)] Encrypt { /// The plaintext string to encrypt. @@ -176,7 +176,7 @@ pub enum SecretProtocol { /// Decrypt an `EncryptedData` blob back to plaintext. /// /// The key is derived from the seed at the path indicated by the key version. - #[rpc(tx = irpc::channel::oneshot::Sender>)] + #[rpc(tx = irpc::channel::oneshot::Sender>)] #[wrap(Decrypt)] Decrypt { /// The encrypted data blob to decrypt. @@ -188,7 +188,7 @@ pub enum SecretProtocol { /// After locking, no derive/encrypt/decrypt operations are possible /// until `Unlock` is called again. Calls `zeroize()` on all sensitive /// material (ADR-038). - #[rpc(tx = irpc::channel::oneshot::Sender>)] + #[rpc(tx = irpc::channel::oneshot::Sender>)] #[wrap(Lock)] Lock, @@ -197,7 +197,7 @@ pub enum SecretProtocol { /// The mnemonic is the space-separated BIP39 word list. The passphrase is /// the optional BIP39 password extension (the "25th word"). After unlocking, /// derive and encrypt/decrypt operations are available. - #[rpc(tx = irpc::channel::oneshot::Sender>)] + #[rpc(tx = irpc::channel::oneshot::Sender>)] #[wrap(Unlock)] Unlock { /// The BIP39 mnemonic phrase (space-separated word list). diff --git a/crates/alknet-secret/src/service.rs b/crates/alknet-vault/src/service.rs similarity index 79% rename from crates/alknet-secret/src/service.rs rename to crates/alknet-vault/src/service.rs index 9c39391..c3ffb17 100644 --- a/crates/alknet-secret/src/service.rs +++ b/crates/alknet-vault/src/service.rs @@ -1,6 +1,6 @@ -//! SecretService implementation with Unlock/Lock lifecycle. +//! VaultService implementation with Unlock/Lock lifecycle. //! -//! The `SecretService` is the primary runtime interface for key management. +//! The `VaultService` is the primary runtime interface for key management. //! It holds the master seed in `Zeroize`-protected memory and provides methods //! for the Unlock/Lock lifecycle, key derivation, and encryption/decryption. //! @@ -14,7 +14,7 @@ //! → cache empty (keys derived on demand) //! //! DeriveEd25519/DeriveEncryptionKey/Encrypt/Decrypt -//! → require unlocked state (ServiceLocked error if locked) +//! → require unlocked state (VaultLocked error if locked) //! → derive key, return result //! → optionally cache derived key //! @@ -22,24 +22,24 @@ //! → zeroize all cached derived keys //! → zeroize seed //! → drop all sensitive material -//! → service returns to locked state +//! → vault returns to locked state //! ``` //! //! # Dispatch Paths //! -//! There are two ways to interact with the secret service: +//! There are two ways to interact with the vault: //! -//! 1. **Local (in-process)**: `SecretServiceHandle` wraps `SecretServiceInner` +//! 1. **Local (in-process)**: `VaultServiceHandle` wraps `VaultServiceInner` //! behind `Arc>` and provides direct method calls without serialization. -//! 2. **Remote (in-cluster)**: `SecretServiceActor` processes `SecretMessage` +//! 2. **Remote (in-cluster)**: `VaultServiceActor` processes `VaultMessage` //! variants from an mpsc channel and dispatches to the handle methods. //! //! # Assembly //! -//! The `SecretService` is assembled by the CLI binary or NAPI layer. Per ADR-027, -//! alknet-core never sees the secret service directly — it is wired through the -//! `OperationEnv` dispatch mechanism. For minimal deployments, no secret service -//! is available (the `SecretStoreCredentialProvider` returns `None`). +//! The `VaultService` is assembled by the CLI binary. The CLI unlocks the vault +//! at startup and injects derived/decrypted material into operation contexts. +//! No handler crate accesses the vault directly — they receive keys through +//! their operation context or via the call protocol. use std::sync::{Arc, RwLock}; @@ -54,21 +54,21 @@ use crate::encryption::{self, EncryptedData, EncryptionKey}; use crate::mnemonic::{Language, Mnemonic, Seed}; use crate::protocol::{ Decrypt, DeriveEd25519, DeriveEncryptionKey, DeriveEthereumKey, DerivePassword, Encrypt, - SecretMessage, SecretProtocol, Unlock, + VaultMessage, VaultProtocol, Unlock, }; use crate::protocol::{DerivedKey, KeyType}; -/// Handle to a running SecretService for local (in-process) use. +/// Handle to a running VaultService for local (in-process) use. /// /// This is the primary API for local secret operations. It wraps the /// service state in an `Arc>` for thread-safe access. #[derive(Clone)] -pub struct SecretServiceHandle { - inner: Arc>, +pub struct VaultServiceHandle { + inner: Arc>, } /// Internal state of the secret service. -struct SecretServiceInner { +struct VaultServiceInner { /// The mnemonic phrase, if unlocked. None if locked. mnemonic: Option, /// The master seed, if unlocked. None if locked. @@ -79,12 +79,12 @@ struct SecretServiceInner { cache: KeyCache, } -/// Errors that can occur during secret service operations. +/// Errors that can occur during vault operations. #[derive(Debug, thiserror::Error, Serialize, Deserialize)] -pub enum SecretServiceError { - #[error("service is locked; call Unlock first")] - ServiceLocked, - #[error("service is already unlocked")] +pub enum VaultServiceError { + #[error("vault is locked; call Unlock first")] + VaultLocked, + #[error("vault is already unlocked")] AlreadyUnlocked, #[error("mnemonic error: {0}")] Mnemonic(String), @@ -98,34 +98,34 @@ pub enum SecretServiceError { UnsupportedKeyType, } -impl From for SecretServiceError { +impl From for VaultServiceError { fn from(e: crate::mnemonic::MnemonicError) -> Self { - SecretServiceError::Mnemonic(e.to_string()) + VaultServiceError::Mnemonic(e.to_string()) } } -impl From for SecretServiceError { +impl From for VaultServiceError { fn from(e: DerivationError) -> Self { - SecretServiceError::Derivation(e.to_string()) + VaultServiceError::Derivation(e.to_string()) } } -impl From for SecretServiceError { +impl From for VaultServiceError { fn from(e: encryption::EncryptionError) -> Self { - SecretServiceError::Encryption(e.to_string()) + VaultServiceError::Encryption(e.to_string()) } } -impl SecretServiceHandle { - /// Create a new SecretServiceHandle in the locked state with default cache config. +impl VaultServiceHandle { + /// Create a new VaultServiceHandle in the locked state with default cache config. pub fn new() -> Self { Self::with_cache_config(CacheConfig::default()) } - /// Create a new SecretServiceHandle with the given cache configuration. + /// Create a new VaultServiceHandle with the given cache configuration. pub fn with_cache_config(config: CacheConfig) -> Self { Self { - inner: Arc::new(RwLock::new(SecretServiceInner { + inner: Arc::new(RwLock::new(VaultServiceInner { mnemonic: None, seed: None, unlocked: false, @@ -138,10 +138,10 @@ impl SecretServiceHandle { /// /// The passphrase is the BIP39 password (may be empty string for none). /// After unlocking, derive and encrypt/decrypt operations are available. - pub fn unlock(&self, phrase: &str, passphrase: Option<&str>) -> Result<(), SecretServiceError> { + pub fn unlock(&self, phrase: &str, passphrase: Option<&str>) -> Result<(), VaultServiceError> { let mut inner = self.inner.write().unwrap(); if inner.unlocked { - return Err(SecretServiceError::AlreadyUnlocked); + return Err(VaultServiceError::AlreadyUnlocked); } let mnemonic = Mnemonic::from_phrase(phrase, Language::English)?; @@ -157,10 +157,10 @@ impl SecretServiceHandle { /// /// Returns the generated mnemonic phrase. Store this phrase securely — /// it is the root of trust for all derived keys. - pub fn unlock_new(&self, word_count: usize) -> Result { + pub fn unlock_new(&self, word_count: usize) -> Result { let mut inner = self.inner.write().unwrap(); if inner.unlocked { - return Err(SecretServiceError::AlreadyUnlocked); + return Err(VaultServiceError::AlreadyUnlocked); } let mnemonic = Mnemonic::generate(word_count)?; @@ -192,10 +192,10 @@ impl SecretServiceHandle { } /// Derive an Ed25519 keypair at the given path. - pub fn derive_ed25519(&self, path: &str) -> Result { + pub fn derive_ed25519(&self, path: &str) -> Result { let mut inner = self.inner.write().unwrap(); if !inner.unlocked { - return Err(SecretServiceError::ServiceLocked); + return Err(VaultServiceError::VaultLocked); } if let Some(cached) = inner.cache.get(path) { @@ -209,7 +209,7 @@ impl SecretServiceHandle { let seed = inner .seed .as_ref() - .ok_or(SecretServiceError::ServiceLocked)?; + .ok_or(VaultServiceError::VaultLocked)?; let key = derivation::derive_path_from_seed(seed.as_bytes(), path)?; let private_key = key.private_key().to_vec(); let public_key = key.public_key().to_vec(); @@ -223,10 +223,10 @@ impl SecretServiceHandle { } /// Derive an AES-256-GCM encryption key at the given path. - pub fn derive_encryption_key(&self, path: &str) -> Result { + pub fn derive_encryption_key(&self, path: &str) -> Result { let mut inner = self.inner.write().unwrap(); if !inner.unlocked { - return Err(SecretServiceError::ServiceLocked); + return Err(VaultServiceError::VaultLocked); } if let Some(cached) = inner.cache.get(path) { @@ -240,7 +240,7 @@ impl SecretServiceHandle { let seed = inner .seed .as_ref() - .ok_or(SecretServiceError::ServiceLocked)?; + .ok_or(VaultServiceError::VaultLocked)?; let key = derivation::derive_path_from_seed(seed.as_bytes(), path)?; let private_key = key.private_key().to_vec(); let public_key = key.public_key().to_vec(); @@ -258,12 +258,12 @@ impl SecretServiceHandle { /// Uses BIP-0032 derivation (HMAC-SHA512 with "Bitcoin seed") when the /// `secp256k1` feature is enabled. Returns `UnsupportedKeyType` when the /// feature is disabled. - pub fn derive_ethereum_key(&self, path: &str) -> Result { + pub fn derive_ethereum_key(&self, path: &str) -> Result { #[cfg(feature = "secp256k1")] { let mut inner = self.inner.write().unwrap(); if !inner.unlocked { - return Err(SecretServiceError::ServiceLocked); + return Err(VaultServiceError::VaultLocked); } if let Some(cached) = inner.cache.get(path) { @@ -277,7 +277,7 @@ impl SecretServiceHandle { let seed = inner .seed .as_ref() - .ok_or(SecretServiceError::ServiceLocked)?; + .ok_or(VaultServiceError::VaultLocked)?; let key = crate::ethereum::derive_secp256k1_path(seed.as_bytes(), path)?; let private_key = key.private_key().to_vec(); @@ -295,7 +295,7 @@ impl SecretServiceHandle { #[cfg(not(feature = "secp256k1"))] { let _ = path; - Err(SecretServiceError::UnsupportedKeyType) + Err(VaultServiceError::UnsupportedKeyType) } } @@ -303,15 +303,15 @@ impl SecretServiceHandle { &self, path: &str, length: usize, - ) -> Result, SecretServiceError> { + ) -> Result, VaultServiceError> { let inner = self.inner.read().unwrap(); if !inner.unlocked { - return Err(SecretServiceError::ServiceLocked); + return Err(VaultServiceError::VaultLocked); } let seed = inner .seed .as_ref() - .ok_or(SecretServiceError::ServiceLocked)?; + .ok_or(VaultServiceError::VaultLocked)?; let key = derivation::derive_path_from_seed(seed.as_bytes(), path)?; let private_key = key.private_key(); @@ -324,7 +324,7 @@ impl SecretServiceHandle { &self, path: &str, length: usize, - ) -> Result { + ) -> Result { let bytes = self.derive_password(path, length)?; Ok(URL_SAFE_NO_PAD.encode(&bytes)) } @@ -336,10 +336,10 @@ impl SecretServiceHandle { &self, plaintext: &str, key_version: u32, - ) -> Result { + ) -> Result { let mut inner = self.inner.write().unwrap(); if !inner.unlocked { - return Err(SecretServiceError::ServiceLocked); + return Err(VaultServiceError::VaultLocked); } let private_key = if let Some(cached) = inner.cache.get(PATHS::ENCRYPTION) { @@ -348,7 +348,7 @@ impl SecretServiceHandle { let seed = inner .seed .as_ref() - .ok_or(SecretServiceError::ServiceLocked)?; + .ok_or(VaultServiceError::VaultLocked)?; let derived = derivation::derive_path_from_seed(seed.as_bytes(), PATHS::ENCRYPTION)?; let pk = derived.private_key().to_vec(); let pubk = derived.public_key().to_vec(); @@ -363,10 +363,10 @@ impl SecretServiceHandle { } /// Decrypt an EncryptedData blob using the derived encryption key. - pub fn decrypt(&self, encrypted: &EncryptedData) -> Result { + pub fn decrypt(&self, encrypted: &EncryptedData) -> Result { let mut inner = self.inner.write().unwrap(); if !inner.unlocked { - return Err(SecretServiceError::ServiceLocked); + return Err(VaultServiceError::VaultLocked); } let private_key = if let Some(cached) = inner.cache.get(PATHS::ENCRYPTION) { @@ -375,7 +375,7 @@ impl SecretServiceHandle { let seed = inner .seed .as_ref() - .ok_or(SecretServiceError::ServiceLocked)?; + .ok_or(VaultServiceError::VaultLocked)?; let derived = derivation::derive_path_from_seed(seed.as_bytes(), PATHS::ENCRYPTION)?; let pk = derived.private_key().to_vec(); let pubk = derived.public_key().to_vec(); @@ -390,42 +390,42 @@ impl SecretServiceHandle { } } -impl Default for SecretServiceHandle { +impl Default for VaultServiceHandle { fn default() -> Self { Self::new() } } -/// The SecretService manages the lifecycle of the master seed and provides +/// The VaultService manages the lifecycle of the master seed and provides /// secret operations. This is the type used by the irpc service handler. /// -/// For local (in-process) use, prefer `SecretServiceHandle` which wraps +/// For local (in-process) use, prefer `VaultServiceHandle` which wraps /// this in thread-safe locks. -pub struct SecretService { - handle: SecretServiceHandle, +pub struct VaultService { + handle: VaultServiceHandle, } -impl SecretService { - /// Create a new SecretService in the locked state. +impl VaultService { + /// Create a new VaultService in the locked state. pub fn new() -> Self { Self { - handle: SecretServiceHandle::new(), + handle: VaultServiceHandle::new(), } } /// Get a handle for local (in-process) use. - pub fn handle(&self) -> &SecretServiceHandle { + pub fn handle(&self) -> &VaultServiceHandle { &self.handle } } -impl Default for SecretService { +impl Default for VaultService { fn default() -> Self { Self::new() } } -/// Actor that processes `SecretMessage` variants and dispatches to `SecretServiceHandle`. +/// Actor that processes `VaultMessage` variants and dispatches to `VaultServiceHandle`. /// /// The actor runs as a `tokio::task`, receives messages from an mpsc channel, /// dispatches to the handle methods, and sends responses through oneshot channels. @@ -433,40 +433,40 @@ impl Default for SecretService { /// # Usage /// /// ```ignore -/// let handle = SecretServiceHandle::new(); -/// let (client, actor) = SecretServiceActor::spawn(handle); +/// let handle = VaultServiceHandle::new(); +/// let (client, actor) = VaultServiceActor::spawn(handle); /// tokio::task::spawn(actor.run(rx)); /// // Use client to send messages /// ``` -pub struct SecretServiceActor { - handle: SecretServiceHandle, +pub struct VaultServiceActor { + handle: VaultServiceHandle, } -impl SecretServiceActor { +impl VaultServiceActor { /// Create a new actor wrapping the given handle. - pub fn new(handle: SecretServiceHandle) -> Self { + pub fn new(handle: VaultServiceHandle) -> Self { Self { handle } } - /// Run the actor message loop, processing `SecretMessage` variants. + /// Run the actor message loop, processing `VaultMessage` variants. /// /// This method runs until the receiver channel is closed. Each message - /// variant is dispatched to the corresponding `SecretServiceHandle` method + /// variant is dispatched to the corresponding `VaultServiceHandle` method /// and the response is sent through the oneshot channel embedded in the message. - pub async fn run(mut self, mut rx: tokio::sync::mpsc::Receiver) { + pub async fn run(mut self, mut rx: tokio::sync::mpsc::Receiver) { while let Some(msg) = rx.recv().await { self.handle_message(msg); } } - /// Spawn the actor as a `tokio::task` and return a `Client` for sending messages. + /// Spawn the actor as a `tokio::task` and return a `Client` for sending messages. /// /// The actor runs on a tokio task and processes messages from the mpsc channel. - /// The returned `Client` can be used to send `SecretMessage` variants + /// The returned `Client` can be used to send `VaultMessage` variants /// to the actor. pub fn spawn( - handle: SecretServiceHandle, - ) -> (irpc::Client, SecretServiceActor) { + handle: VaultServiceHandle, + ) -> (irpc::Client, VaultServiceActor) { let (tx, rx) = tokio::sync::mpsc::channel(64); let client = irpc::Client::local(tx); let actor = Self::new(handle.clone()); @@ -474,10 +474,10 @@ impl SecretServiceActor { (client, Self::new(handle)) } - /// Handle a single `SecretMessage` by dispatching to the appropriate handle method. - fn handle_message(&mut self, msg: SecretMessage) { + /// Handle a single `VaultMessage` by dispatching to the appropriate handle method. + fn handle_message(&mut self, msg: VaultMessage) { match msg { - SecretMessage::DeriveEd25519(msg) => { + VaultMessage::DeriveEd25519(msg) => { let WithChannels { inner, tx, .. } = msg; let DeriveEd25519 { path } = inner; let result = self.handle.derive_ed25519(&path); @@ -485,7 +485,7 @@ impl SecretServiceActor { let _ = tx.send(result).await; }); } - SecretMessage::DeriveEncryptionKey(msg) => { + VaultMessage::DeriveEncryptionKey(msg) => { let WithChannels { inner, tx, .. } = msg; let DeriveEncryptionKey { path } = inner; let result = self.handle.derive_encryption_key(&path); @@ -493,7 +493,7 @@ impl SecretServiceActor { let _ = tx.send(result).await; }); } - SecretMessage::DeriveEthereumKey(msg) => { + VaultMessage::DeriveEthereumKey(msg) => { let WithChannels { inner, tx, .. } = msg; let DeriveEthereumKey { path } = inner; let result = self.handle.derive_ethereum_key(&path); @@ -501,7 +501,7 @@ impl SecretServiceActor { let _ = tx.send(result).await; }); } - SecretMessage::DerivePassword(msg) => { + VaultMessage::DerivePassword(msg) => { let WithChannels { inner, tx, .. } = msg; let DerivePassword { path, length } = inner; let result = self.handle.derive_password(&path, length); @@ -509,7 +509,7 @@ impl SecretServiceActor { let _ = tx.send(result).await; }); } - SecretMessage::Encrypt(msg) => { + VaultMessage::Encrypt(msg) => { let WithChannels { inner, tx, .. } = msg; let Encrypt { plaintext, @@ -520,7 +520,7 @@ impl SecretServiceActor { let _ = tx.send(result).await; }); } - SecretMessage::Decrypt(msg) => { + VaultMessage::Decrypt(msg) => { let WithChannels { inner, tx, .. } = msg; let Decrypt { encrypted } = inner; let result = self.handle.decrypt(&encrypted); @@ -528,14 +528,14 @@ impl SecretServiceActor { let _ = tx.send(result).await; }); } - SecretMessage::Lock(msg) => { + VaultMessage::Lock(msg) => { let WithChannels { inner: _, tx, .. } = msg; self.handle.lock(); tokio::spawn(async move { let _ = tx.send(Ok(())).await; }); } - SecretMessage::Unlock(msg) => { + VaultMessage::Unlock(msg) => { let WithChannels { inner, tx, .. } = msg; let Unlock { mnemonic, @@ -559,13 +559,13 @@ mod tests { #[test] fn test_service_starts_locked() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); assert!(!service.is_unlocked()); } #[test] fn test_unlock_new_generates_mnemonic() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); let phrase = service.unlock_new(24).unwrap(); assert!(!phrase.is_empty()); assert!(service.is_unlocked()); @@ -573,7 +573,7 @@ mod tests { #[test] fn test_lock_purges_state() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); assert!(service.is_unlocked()); @@ -583,21 +583,21 @@ mod tests { #[test] fn test_derive_on_locked_fails() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); let result = service.derive_ed25519(PATHS::IDENTITY); assert!(result.is_err()); } #[test] fn test_encrypt_on_locked_fails() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); let result = service.encrypt("secret", 1); assert!(result.is_err()); } #[test] fn test_full_lifecycle() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); assert!(!service.is_unlocked()); @@ -617,7 +617,7 @@ mod tests { #[test] fn test_unlock_with_known_phrase() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); let phrase = service.unlock_new(24).unwrap(); service.lock(); @@ -628,7 +628,7 @@ mod tests { #[test] fn test_double_unlock_fails() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let result = service.unlock_new(12); @@ -637,7 +637,7 @@ mod tests { #[test] fn test_encrypt_decrypt_lifecycle() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let plaintext = "my-api-key-12345"; @@ -651,7 +651,7 @@ mod tests { #[test] fn test_derive_password_deterministic() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let path = "m/74'/1'/0'/12345'"; @@ -662,7 +662,7 @@ mod tests { #[test] fn test_derive_password_different_paths() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let pw_a = service.derive_password("m/74'/1'/0'/100'", 16).unwrap(); @@ -675,7 +675,7 @@ mod tests { #[test] fn test_derive_password_length_truncation() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let path = "m/74'/1'/0'/999'"; @@ -693,14 +693,14 @@ mod tests { #[test] fn test_derive_password_locked_error() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); let result = service.derive_password("m/74'/1'/0'/1'", 16); - assert!(matches!(result, Err(SecretServiceError::ServiceLocked))); + assert!(matches!(result, Err(VaultServiceError::VaultLocked))); } #[test] fn test_derive_password_string_base64url() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let path = "m/74'/1'/0'/42'"; @@ -722,7 +722,7 @@ mod tests { #[cfg(feature = "secp256k1")] #[test] fn test_derive_ethereum_key_bip32() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let key = service.derive_ethereum_key(PATHS::ETHEREUM).unwrap(); @@ -734,7 +734,7 @@ mod tests { #[cfg(feature = "secp256k1")] #[test] fn test_ethereum_key_differs_from_ed25519() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let eth_key = service.derive_ethereum_key(PATHS::ETHEREUM).unwrap(); @@ -746,19 +746,19 @@ mod tests { #[cfg(not(feature = "secp256k1"))] #[test] fn test_derive_ethereum_key_unsupported_without_feature() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let result = service.derive_ethereum_key(PATHS::ETHEREUM); assert!(matches!( result, - Err(SecretServiceError::UnsupportedKeyType) + Err(VaultServiceError::UnsupportedKeyType) )); } #[test] fn test_cache_hit_avoids_re_derivation() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let key1 = service.derive_ed25519(PATHS::IDENTITY).unwrap(); @@ -773,7 +773,7 @@ mod tests { #[test] fn test_cache_miss_derives_and_caches() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); assert_eq!(service.inner.read().unwrap().cache.len(), 0); @@ -786,7 +786,7 @@ mod tests { #[test] fn test_expired_entry_evicted_on_access() { let config = crate::cache::CacheConfig::new(std::time::Duration::from_millis(5), 64); - let service = SecretServiceHandle::with_cache_config(config); + let service = VaultServiceHandle::with_cache_config(config); service.unlock_new(24).unwrap(); let key1 = service.derive_ed25519(PATHS::IDENTITY).unwrap(); @@ -802,7 +802,7 @@ mod tests { #[test] fn test_lru_eviction_when_over_max_entries() { let config = crate::cache::CacheConfig::new(std::time::Duration::from_secs(3600), 2); - let service = SecretServiceHandle::with_cache_config(config); + let service = VaultServiceHandle::with_cache_config(config); service.unlock_new(24).unwrap(); service.derive_ed25519(PATHS::IDENTITY).unwrap(); @@ -820,7 +820,7 @@ mod tests { #[test] fn test_lock_clears_all_cache_entries() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); service.derive_ed25519(PATHS::IDENTITY).unwrap(); @@ -834,7 +834,7 @@ mod tests { #[test] fn test_encrypt_decrypt_uses_cached_encryption_key() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let plaintext = "cached-encryption-test"; @@ -849,13 +849,13 @@ mod tests { #[tokio::test] async fn test_actor_unlock_responds_successfully() { - let handle = SecretServiceHandle::new(); + let handle = VaultServiceHandle::new(); let (tx, rx) = tokio::sync::mpsc::channel(64); - let actor = SecretServiceActor::new(handle); + let actor = VaultServiceActor::new(handle); tokio::task::spawn(actor.run(rx)); let (resp_tx, resp_rx) = oneshot::channel(); - let msg = SecretMessage::Unlock(WithChannels::from(( + let msg = VaultMessage::Unlock(WithChannels::from(( Unlock { mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".to_string(), passphrase: None, @@ -870,14 +870,14 @@ mod tests { #[tokio::test] async fn test_actor_derive_ed25519_returns_key() { - let handle = SecretServiceHandle::new(); + let handle = VaultServiceHandle::new(); handle.unlock_new(24).unwrap(); let (tx, rx) = tokio::sync::mpsc::channel(64); - let actor = SecretServiceActor::new(handle); + let actor = VaultServiceActor::new(handle); tokio::task::spawn(actor.run(rx)); let (resp_tx, resp_rx) = oneshot::channel(); - let msg = SecretMessage::DeriveEd25519(WithChannels::from(( + let msg = VaultMessage::DeriveEd25519(WithChannels::from(( DeriveEd25519 { path: PATHS::IDENTITY.to_string(), }, @@ -897,15 +897,15 @@ mod tests { #[tokio::test] async fn test_actor_lock_clears_state() { - let handle = SecretServiceHandle::new(); + let handle = VaultServiceHandle::new(); handle.unlock_new(24).unwrap(); let (tx, rx) = tokio::sync::mpsc::channel(64); - let actor = SecretServiceActor::new(handle.clone()); + let actor = VaultServiceActor::new(handle.clone()); tokio::task::spawn(actor.run(rx)); - let (resp_tx, resp_rx): (oneshot::Sender>, _) = + let (resp_tx, resp_rx): (oneshot::Sender>, _) = oneshot::channel(); - let msg = SecretMessage::Lock(WithChannels::from((Lock, resp_tx))); + let msg = VaultMessage::Lock(WithChannels::from((Lock, resp_tx))); tx.send(msg).await.unwrap(); let result = resp_rx.await.unwrap(); @@ -915,8 +915,8 @@ mod tests { #[test] fn test_unlock_with_passphrase_produces_different_seed() { - let service_a = SecretServiceHandle::new(); - let service_b = SecretServiceHandle::new(); + let service_a = VaultServiceHandle::new(); + let service_b = VaultServiceHandle::new(); let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; @@ -946,15 +946,15 @@ mod tests { #[tokio::test] async fn test_actor_unlock_with_passphrase() { - let handle = SecretServiceHandle::new(); + let handle = VaultServiceHandle::new(); let (tx, rx) = tokio::sync::mpsc::channel(64); - let actor = SecretServiceActor::new(handle); + let actor = VaultServiceActor::new(handle); tokio::task::spawn(actor.run(rx)); let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; let (resp_tx, resp_rx) = oneshot::channel(); - let msg = SecretMessage::Unlock(WithChannels::from(( + let msg = VaultMessage::Unlock(WithChannels::from(( Unlock { mnemonic: mnemonic.to_string(), passphrase: Some("TREZOR".to_string()), diff --git a/crates/alknet-secret/tests/derivation_tests.rs b/crates/alknet-vault/tests/derivation_tests.rs similarity index 77% rename from crates/alknet-secret/tests/derivation_tests.rs rename to crates/alknet-vault/tests/derivation_tests.rs index 66219c5..894a4fc 100644 --- a/crates/alknet-secret/tests/derivation_tests.rs +++ b/crates/alknet-vault/tests/derivation_tests.rs @@ -3,33 +3,33 @@ //! These tests verify that SLIP-0010 derivation produces correct results //! against known test vectors and that path constants produce expected key types. -use alknet_secret::derivation::PATHS; -use alknet_secret::service::SecretServiceHandle; +use alknet_vault::derivation::PATHS; +use alknet_vault::service::VaultServiceHandle; #[test] fn test_identity_key_derivation() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); let _phrase = service.unlock_new(24).unwrap(); let key = service.derive_ed25519(PATHS::IDENTITY).unwrap(); - assert_eq!(key.key_type, alknet_secret::protocol::KeyType::Ed25519); + assert_eq!(key.key_type, alknet_vault::protocol::KeyType::Ed25519); assert!(!key.private_key.is_empty()); assert!(!key.public_key.is_empty()); } #[test] fn test_encryption_key_derivation() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let key = service.derive_encryption_key(PATHS::ENCRYPTION).unwrap(); - assert_eq!(key.key_type, alknet_secret::protocol::KeyType::Aes256Gcm); + assert_eq!(key.key_type, alknet_vault::protocol::KeyType::Aes256Gcm); } #[test] fn test_deterministic_derivation() { // Same seed + same path = same key - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); let phrase = service.unlock_new(24).unwrap(); let key1 = service.derive_ed25519(PATHS::IDENTITY).unwrap(); @@ -46,7 +46,7 @@ fn test_deterministic_derivation() { #[test] fn test_different_paths_different_keys() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let identity_key = service.derive_ed25519(PATHS::IDENTITY).unwrap(); diff --git a/crates/alknet-secret/tests/encryption_tests.rs b/crates/alknet-vault/tests/encryption_tests.rs similarity index 84% rename from crates/alknet-secret/tests/encryption_tests.rs rename to crates/alknet-vault/tests/encryption_tests.rs index 3b33509..48867f3 100644 --- a/crates/alknet-secret/tests/encryption_tests.rs +++ b/crates/alknet-vault/tests/encryption_tests.rs @@ -3,12 +3,12 @@ //! These tests verify round-trip encryption, key version handling, //! and wire format compatibility. -use alknet_secret::encryption::CURRENT_KEY_VERSION; -use alknet_secret::service::SecretServiceHandle; +use alknet_vault::encryption::CURRENT_KEY_VERSION; +use alknet_vault::service::VaultServiceHandle; #[test] fn test_encrypt_decrypt_round_trip_via_service() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let plaintext = "sk-proj-abc123xyz789"; @@ -21,7 +21,7 @@ fn test_encrypt_decrypt_round_trip_via_service() { #[test] fn test_encrypt_produces_different_ciphertext_each_time() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let plaintext = "same input different ciphertexts"; @@ -38,7 +38,7 @@ fn test_encrypt_produces_different_ciphertext_each_time() { #[test] fn test_encrypted_data_serialization() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let plaintext = "test serialization"; @@ -52,7 +52,7 @@ fn test_encrypted_data_serialization() { assert!(json.contains("data")); // Verify round-trip through JSON - let deserialized: alknet_secret::encryption::EncryptedData = + let deserialized: alknet_vault::encryption::EncryptedData = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized, encrypted); } diff --git a/crates/alknet-secret/tests/service_tests.rs b/crates/alknet-vault/tests/service_tests.rs similarity index 72% rename from crates/alknet-secret/tests/service_tests.rs rename to crates/alknet-vault/tests/service_tests.rs index a517dfd..62ebc5f 100644 --- a/crates/alknet-secret/tests/service_tests.rs +++ b/crates/alknet-vault/tests/service_tests.rs @@ -1,21 +1,21 @@ -//! Integration tests for the SecretService lifecycle. +//! Integration tests for the VaultService lifecycle. //! //! These tests verify the unlock/lock lifecycle, error conditions, -//! and that the service correctly manages state transitions. +//! and that the vault correctly manages state transitions. -use alknet_secret::derivation::PATHS; -use alknet_secret::service::{SecretServiceError, SecretServiceHandle}; +use alknet_vault::derivation::PATHS; +use alknet_vault::service::{VaultServiceError, VaultServiceHandle}; #[test] fn test_full_lifecycle() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); // Starts locked assert!(!service.is_unlocked()); // Can't derive while locked let result = service.derive_ed25519(PATHS::IDENTITY); - assert!(matches!(result, Err(SecretServiceError::ServiceLocked))); + assert!(matches!(result, Err(VaultServiceError::VaultLocked))); // Unlock let phrase = service.unlock_new(24).unwrap(); @@ -32,12 +32,12 @@ fn test_full_lifecycle() { // Can't derive again let result = service.derive_ed25519(PATHS::IDENTITY); - assert!(matches!(result, Err(SecretServiceError::ServiceLocked))); + assert!(matches!(result, Err(VaultServiceError::VaultLocked))); } #[test] fn test_unlock_with_known_phrase() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); // Generate a phrase let phrase = service.unlock_new(24).unwrap(); @@ -53,16 +53,16 @@ fn test_unlock_with_known_phrase() { #[test] fn test_double_unlock_fails() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let result = service.unlock_new(12); - assert!(matches!(result, Err(SecretServiceError::AlreadyUnlocked))); + assert!(matches!(result, Err(VaultServiceError::AlreadyUnlocked))); } #[test] fn test_lock_when_already_locked_is_noop() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); assert!(!service.is_unlocked()); // Lock on already-locked service is a no-op @@ -72,7 +72,7 @@ fn test_lock_when_already_locked_is_noop() { #[test] fn test_encrypt_decrypt_lifecycle() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); let plaintext = "my-api-key-12345"; @@ -83,12 +83,12 @@ fn test_encrypt_decrypt_lifecycle() { // After lock, can't decrypt service.lock(); let result = service.decrypt(&encrypted); - assert!(matches!(result, Err(SecretServiceError::ServiceLocked))); + assert!(matches!(result, Err(VaultServiceError::VaultLocked))); } #[test] fn test_multiple_derive_paths_succeed() { - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); service.unlock_new(24).unwrap(); // All standard paths should work diff --git a/crates/alknet-secret/tests/test_vectors.rs b/crates/alknet-vault/tests/test_vectors.rs similarity index 97% rename from crates/alknet-secret/tests/test_vectors.rs rename to crates/alknet-vault/tests/test_vectors.rs index 3a5f3bb..825be11 100644 --- a/crates/alknet-secret/tests/test_vectors.rs +++ b/crates/alknet-vault/tests/test_vectors.rs @@ -17,10 +17,10 @@ //! byte-for-byte matching against SLIP-0010 raw hex, since the crate's internal //! representation handles clamping differently. -use alknet_secret::derivation::{derive_path_from_seed, PATHS}; -use alknet_secret::encryption::{decrypt, encrypt, EncryptionKey, CURRENT_KEY_VERSION}; -use alknet_secret::mnemonic::{Language, Mnemonic}; -use alknet_secret::protocol::KeyType; +use alknet_vault::derivation::{derive_path_from_seed, PATHS}; +use alknet_vault::encryption::{decrypt, encrypt, EncryptionKey, CURRENT_KEY_VERSION}; +use alknet_vault::mnemonic::{Language, Mnemonic}; +use alknet_vault::protocol::KeyType; // --------------------------------------------------------------------------- // BIP39 Test Vectors @@ -291,7 +291,7 @@ fn test_aes256gcm_known_key_encrypt_decrypt() { ]; let nonce = Nonce::from_slice(&nonce_bytes); - let plaintext = b"hello, alknet secret service!"; + let plaintext = b"hello, alknet vault!"; // Encrypt with known key and nonce let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap(); @@ -396,13 +396,13 @@ fn test_alknet_encryption_path_regression() { assert_ne!(key.private_key(), identity.private_key()); } -/// Verify that the SecretServiceHandle produces keys consistent with +/// Verify that the VaultServiceHandle produces keys consistent with /// direct derivation (integration test). #[test] fn test_service_derive_matches_direct_derivation() { - use alknet_secret::service::SecretServiceHandle; + use alknet_vault::service::VaultServiceHandle; - let service = SecretServiceHandle::new(); + let service = VaultServiceHandle::new(); let phrase = service.unlock_new(24).unwrap(); // Derive via service (which uses Mnemonic + Seed internally) diff --git a/docs/architecture/README.md b/docs/architecture/README.md index bd65bd0..23ae498 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -7,7 +7,7 @@ last_updated: 2026-06-16 ## 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-secret` (stable) and research/reference material. Foundational ADRs (001–009) are in place, including the BiStream type definition (ADR-007), secret service integration (ADR-008), and the one-way door decision framework (ADR-009). Architecture specs are ready for Phase 1 implementation planning. +**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) and research/reference material. Foundational ADRs (001–009) are in place, including the BiStream type definition (ADR-007), vault integration (ADR-008), and the one-way door decision framework (ADR-009). Architecture specs are ready for Phase 1 implementation planning. **Next step**: Resolve remaining two-way-door questions during implementation. Start with alknet-core (ProtocolHandler trait, Connection, endpoint, router, auth types, config). @@ -31,7 +31,7 @@ Crate-specific specs will be created when each crate is ready for Phase 1 archit | [005](decisions/005-irpc-as-call-protocol-foundation.md) | irpc as Call Protocol Foundation | Accepted | | [006](decisions/006-alpn-convention-and-connection-model.md) | ALPN String Convention and Connection Model | Accepted | | [007](decisions/007-bistream-type-definition.md) | BiStream Type Definition | Accepted | -| [008](decisions/008-secret-service-integration.md) | Secret Service Integration Point | Accepted | +| [008](decisions/008-secret-service-integration.md) | Vault Integration Point | Accepted | | [009](decisions/009-one-way-door-decision-framework.md) | One-Way Door Decision Framework | Accepted | ## Open Questions @@ -43,7 +43,7 @@ See [open-questions.md](open-questions.md) for the full tracker. - **OQ-02**: AuthContext timing — hybrid model (ADR-004) - **OQ-03**: ALPN naming — `alknet/` prefix, no version (ADR-006) - **OQ-06**: ALPN per connection, not per stream (ADR-006) -- **OQ-08**: Secret service — CLI-embedded via call protocol (ADR-008) +- **OQ-08**: Vault integration — CLI-embedded via call protocol (ADR-008) **Two-way doors (deferred to implementation):** - **OQ-04**: Dynamic handler registration — start static, add ArcSwap later diff --git a/docs/architecture/decisions/003-crate-decomposition.md b/docs/architecture/decisions/003-crate-decomposition.md index f58f2ed..f7c9d88 100644 --- a/docs/architecture/decisions/003-crate-decomposition.md +++ b/docs/architecture/decisions/003-crate-decomposition.md @@ -12,7 +12,7 @@ The new ALPN dispatch model eliminates the need for a shared interface layer. Ea Key constraints: - Protocol crates must depend on alknet-core for auth/identity/config — but not on each other -- alknet-secret is already standalone (no alknet-core dependency) and must remain so +- alknet-secret is already standalone (no alknet-core dependency) and must remain so (renamed to alknet-vault — see ADR-008) - The CLI binary assembles everything — it's the only crate that depends on all handler crates - Some handlers (SFTP, call protocol) need to compile to WASM for browser/client use - irpc is the foundation for the call protocol — it provides the operation registry, framing, and pub/sub patterns @@ -24,7 +24,7 @@ The workspace decomposes into the following crates: | Crate | Responsibility | Depends on | |-------|---------------|------------| | `alknet-core` | ProtocolHandler trait, ALPN router, endpoint, BiStream, AuthContext, IdentityProvider, config, ArcSwap dynamic config | tokio, quinn, rustls, irpc | -| `alknet-secret` | BIP39/SLIP-0010/AES-GCM key derivation and encryption, SecretProtocol service | (standalone, no alknet-core) | +| `alknet-vault` | Local key vault: BIP39/SLIP-0010/AES-GCM key derivation, encryption, VaultProtocol dispatch | (standalone, no alknet-core) | | `alknet-ssh` | SshAdapter (russh, SOCKS5, port forwarding) | alknet-core, russh | | `alknet-call` | CallAdapter (JSON-RPC via irpc, operation registry, pub/sub, access control) | alknet-core, irpc | | `alknet-git` | GitAdapter (gix, pkt-line protocol) | alknet-core, gix | @@ -37,7 +37,7 @@ The workspace decomposes into the following crates: Dependency flow: ``` -alknet-secret (standalone) +alknet-vault (standalone) alknet-core ← all handler crates ← alknet (CLI) alknet-call ← alknet-napi ``` @@ -49,7 +49,7 @@ No handler crate depends on another handler crate. Cross-handler communication g **Positive:** - Each handler can be developed, tested, and versioned independently - WASM-compatible handlers (sftp, call) don't pull in heavy dependencies (russh, axum) -- alknet-secret remains standalone — no circular dependency risk +- alknet-vault remains standalone — no circular dependency risk - New handlers are added by creating a crate and registering it with the endpoint - Clean separation of concerns — each crate has one job diff --git a/docs/architecture/decisions/008-secret-service-integration.md b/docs/architecture/decisions/008-secret-service-integration.md index 8c25d35..b71af32 100644 --- a/docs/architecture/decisions/008-secret-service-integration.md +++ b/docs/architecture/decisions/008-secret-service-integration.md @@ -1,4 +1,4 @@ -# ADR-008: Secret Service Integration Point +# ADR-008: Vault Integration Point ## Status @@ -6,19 +6,25 @@ Accepted ## Context -alknet-secret is a standalone crate with zero alknet crate dependencies. It provides BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, AES-256-GCM encryption, and an irpc-based `SecretProtocol` service. It is already implemented and stable. +alknet-vault (formerly alknet-secret) is a standalone crate with zero alknet crate dependencies. It provides BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, AES-256-GCM encryption, and an irpc-based `VaultProtocol` for message dispatch. It is already implemented and stable. -The question (OQ-08) is: how does the rest of the alknet system access alknet-secret's capabilities? The options are: +The question (OQ-08) was: how does the rest of the alknet system access alknet-vault's capabilities? The options were: -1. **irpc service over `alknet/call`**: Other services call SecretProtocol operations through the call protocol on `alknet/call`. The secret service is just another set of operations registered in the call protocol's operation registry. +1. **irpc service over `alknet/call`**: Other services call vault operations through the call protocol. +2. **ALPN handler on `alknet/secret`**: alknet-vault implements ProtocolHandler and gets its own ALPN. +3. **Direct library dependency**: alknet-core or handler crates depend on alknet-vault directly, breaking its independence. +4. **CLI-embedded with call protocol exposure**: The CLI binary instantiates VaultServiceHandle locally and registers vault operations in the call protocol's registry. -2. **ALPN handler on `alknet/secret`**: alknet-secret implements `ProtocolHandler` and gets its own ALPN. Remote nodes call it over a dedicated QUIC connection. +This is a one-way door because if alknet-vault gets pulled into alknet-core as a dependency, its independence is permanently lost. The standalone property is valuable — alknet-vault has no QUIC, no tokio runtime requirement (the handle works without it), and no alknet crate dependencies. It can be used in contexts where QUIC networking doesn't exist (CLI tools, test harnesses, WASM key derivation). -3. **Direct library dependency**: alknet-core or handler crates depend on alknet-secret directly, breaking its independence. +Beyond the integration point, there's a question of access patterns. The vault holds the master seed and can derive keys and encrypt/decrypt arbitrary data. This is used for: -4. **CLI-embedded with call protocol exposure**: The CLI binary instantiates SecretServiceHandle locally and registers secret operations in the call protocol's registry. +- Identity key derivation (SSH host keys, node identity) +- Provider API key storage (Vast.ai, LLM providers) — encrypted at rest, decrypted on demand +- Credential encryption for storage +- Multi-tenant key derivation (different derivation paths per tenant) -This is a one-way door because if alknet-secret gets pulled into alknet-core as a dependency, its independence is permanently lost. The standalone property is valuable — alknet-secret has no QUIC, no tokio runtime requirement (the handle works without it), and no alknet crate dependencies. It can be used in contexts where QUIC networking doesn't exist (CLI tools, test harnesses, WASM key derivation). +The vault is a capability source, not a service endpoint. Operations that need provider keys don't hold a reference to the vault — they receive the derived/decrypted material through their operation context. The vault is unlocked at startup by the CLI, and the CLI injects material into operation contexts as needed. ## Decision @@ -26,38 +32,37 @@ This is a one-way door because if alknet-secret gets pulled into alknet-core as The CLI binary (the `alknet` crate) is the integration point. It: -1. Instantiates `SecretServiceHandle` locally at startup (or on-demand with Unlock/Lock lifecycle). -2. Registers secret operations (DeriveEd25519, DeriveEncryptionKey, Encrypt, Decrypt, etc.) in the call protocol's operation registry. -3. Other handlers access secret capabilities by calling operations on `alknet/call` — they don't import alknet-secret directly. +1. Instantiates `VaultServiceHandle` locally at startup (or on-demand with Unlock/Lock lifecycle). +2. Registers vault operations (DeriveEd25519, DeriveEncryptionKey, Encrypt, Decrypt, etc.) in the call protocol's operation registry. +3. Other handlers access vault capabilities by calling operations on `alknet/call` — they don't import alknet-vault directly. -alknet-secret remains standalone with no alknet crate dependencies. Its `SecretServiceHandle` is used directly (in-process, no serialization) by the CLI binary. Its `SecretProtocol` irpc service is available for remote access through the call protocol. +**alknet-vault does NOT get its own ALPN.** Key derivation is a local operation — the master seed never crosses the network. If a remote node needs derived public keys (e.g., for identity verification), they're shared through the call protocol, not through direct vault access. -**alknet-secret does NOT get its own ALPN.** Here's why: +**The vault is accessed at the assembly layer, not by individual handlers.** The CLI (or a configuration middleware it sets up) is the only component that talks to the vault directly. Derived keys and decrypted credentials are injected into operation contexts — handlers receive the material they need, not a vault reference. -- `alknet/secret` as a separate ALPN would mean a remote node opens a QUIC connection to access key derivation — this is architecturally wrong. Key derivation is a local operation that should never cross the network in its raw form. -- If a remote node needs derived keys (e.g., for end-to-end encryption), the local node derives them and sends only the public component over `alknet/call` — never the seed or private key. -- The secret service's Unlock/Lock lifecycle (holding the master seed in RAM) is inherently local. There's no safe way to expose Unlock/Lock over the network. - -**What if a handler needs a key at runtime?** The handler calls through the call protocol. The CLI registers secret operations in the call registry at startup. The call protocol routes the request to the locally-running SecretServiceHandle. No handler crate depends on alknet-secret. +This is analogous to the reverse-proxy admin key pattern (ADR-028 in the reverse-proxy project): the proxy reads the key file once at startup, hashes it, and individual handlers never see the file. Here, the CLI unlocks the vault once at startup, and individual handlers receive the results of vault operations through their contexts. ## Consequences **Positive:** -- alknet-secret remains fully standalone — no QUIC dependency, no tokio runtime requirement for the handle +- alknet-vault remains fully standalone — no QUIC dependency, no tokio runtime requirement for the handle - Key derivation and encryption are local-only by default — the master seed never leaves the node - Remote access to public key material (not secrets) flows through the existing call protocol — no separate ALPN needed - The CLI binary is the single integration point — clean dependency graph, no circular dependencies -- The `SecretServiceHandle` is used in-process with zero serialization overhead — direct method calls, not irpc messages -- Test harnesses can use `SecretServiceHandle` directly without any QUIC infrastructure +- The `VaultServiceHandle` is used in-process with zero serialization overhead — direct method calls, not irpc messages +- Test harnesses can use `VaultServiceHandle` directly without any QUIC infrastructure +- Access pattern is clear: vault → CLI → operation contexts, not vault ← handlers **Negative:** -- Handlers that need keys must go through the call protocol — this adds a hop even for local calls (mitigated: local call protocol calls can be short-circuited to direct method calls via irpc's local dispatch) -- The CLI binary has a larger dependency tree since it imports both alknet-call and alknet-secret (expected: the CLI assembles everything) -- If the call protocol is not yet running when a handler needs a key, the handler must wait for initialization (mitigated: the CLI starts SecretServiceHandle before accepting connections) +- Handlers that need keys must receive them through their operation context — this requires the CLI or call protocol to mediate +- The CLI binary has a larger dependency tree since it imports both alknet-call and alknet-vault (expected: the CLI assembles everything) +- If the call protocol is not yet running when a handler needs a key, the handler must wait for initialization (mitigated: the CLI starts VaultServiceHandle before accepting connections) ## References -- ADR-003: Crate decomposition (alknet-secret is standalone) +- ADR-003: Crate decomposition (alknet-vault is standalone) - ADR-005: irpc as call protocol foundation +- ADR-009: One-way door decision framework - OQ-08: Secret service integration point (resolved by this ADR) -- alknet-secret implementation: `crates/alknet-secret/` \ No newline at end of file +- alknet-vault implementation: `crates/alknet-vault/` +- Reverse-proxy ADR-028: Admin HTTP API (analogous key management pattern) \ No newline at end of file diff --git a/docs/architecture/decisions/009-one-way-door-decision-framework.md b/docs/architecture/decisions/009-one-way-door-decision-framework.md index 62186fe..102fae7 100644 --- a/docs/architecture/decisions/009-one-way-door-decision-framework.md +++ b/docs/architecture/decisions/009-one-way-door-decision-framework.md @@ -20,7 +20,7 @@ Every architectural decision is classified as one of: **One-way door** — Reversing this decision requires rewriting significant code across multiple crates or permanently closes a capability door. Examples: - BiStream as a concrete quinn type (closes WASM door permanently) -- alknet-secret pulled into alknet-core as a dependency (loses standalone property permanently) +- alknet-vault pulled into alknet-core as a dependency (loses standalone property permanently) - ProtocolHandler signature changes (every handler must be rewritten) **Two-way door** — Reversing this decision is cheap or additive. Examples: diff --git a/docs/architecture/open-questions.md b/docs/architecture/open-questions.md index 5bdb252..cdebf56 100644 --- a/docs/architecture/open-questions.md +++ b/docs/architecture/open-questions.md @@ -84,13 +84,13 @@ Door type classifications follow ADR-009: ## Theme: Security -### OQ-08: Secret Service Integration Point +### OQ-08: Vault Integration Point - **Origin**: [overview.md](overview.md) - **Status**: resolved - **Door type**: One-way - **Priority**: medium -- **Resolution**: CLI-embedded with call protocol exposure. The CLI binary instantiates `SecretServiceHandle` locally and registers secret operations in the call protocol's operation registry. alknet-secret has no ALPN and no alknet-core dependency. Key derivation is local-only; only public key material crosses the network via `alknet/call`. See ADR-008. +- **Resolution**: CLI-embedded with call protocol exposure. The CLI binary instantiates `VaultServiceHandle` locally and registers vault operations in the call protocol's operation registry. alknet-vault has no ALPN and no alknet-core dependency. Key derivation is local-only; only public key material crosses the network via `alknet/call`. The vault is a capability source — derived keys and decrypted credentials are injected into operation contexts at the assembly layer, not passed as vault references to handlers. See ADR-008. - **Cross-references**: ADR-003, ADR-005, ADR-008 ## Deferred Questions diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index da25820..589881e 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -25,12 +25,12 @@ See [ADR-001](decisions/001-alpn-protocol-dispatch.md) for the full rationale. ## Crate Graph ``` -alknet-secret (standalone, no alknet-core dependency) +alknet-vault (standalone, no alknet-core dependency) │ alknet-core │ ├── ProtocolHandler trait │ ├── ALPN router / endpoint -│ ├── BiStream type +│ ├── BiStream trait, Connection type │ ├── AuthContext, IdentityProvider │ └── StaticConfig, DynamicConfig (ArcSwap) │ @@ -44,15 +44,15 @@ alknet-core │ ├── alknet-napi (depends on alknet-call, napi-rs) │ -└── alknet (CLI binary, depends on all handler crates) +└── alknet (CLI binary, depends on all handler crates + alknet-vault) ``` Dependency rules: - No handler crate depends on another handler crate - All handler crates depend on alknet-core -- alknet-secret has zero alknet crate dependencies +- alknet-vault has zero alknet crate dependencies - alknet-napi depends only on alknet-call (call protocol client) -- alknet (CLI) is the only crate that depends on all handler crates +- alknet (CLI) is the only crate that depends on all handler crates and alknet-vault See [ADR-003](decisions/003-crate-decomposition.md) for the full decomposition rationale. @@ -91,7 +91,7 @@ See [ADR-002](decisions/002-protocol-handler-trait.md) and [ADR-007](decisions/0 | `h3` | HttpAdapter (WebTransport upgrade) | Browser-compatible WebTransport, then ALPN upgrade | | `h2` / `http/1.1` | HttpAdapter | Standard HTTP for browsers, curl | -> **Note**: `alknet/secret` is not in the ALPN registry. alknet-secret is a standalone crate with no alknet-core dependency. The CLI binary embeds it and exposes its operations through `alknet/call`. See ADR-008 for the integration rationale. +> **Note**: `alknet/vault` is not in the ALPN registry. alknet-vault is a standalone local key vault with no alknet-core dependency. The CLI binary embeds it and exposes its operations through `alknet/call`. The vault is a capability source — derived keys and decrypted credentials are injected into operation contexts at the assembly layer, not passed as vault references to handlers. See ADR-008 for the integration rationale. ## Authentication @@ -181,7 +181,7 @@ All design decisions are documented as ADRs in [decisions/](decisions/). | [005](decisions/005-irpc-as-call-protocol-foundation.md) | irpc as Call Protocol Foundation | Call protocol uses irpc for registry, framing, dispatch | | [006](decisions/006-alpn-convention-and-connection-model.md) | ALPN String Convention and Connection Model | `alknet/` prefix, one ALPN per connection | | [007](decisions/007-bistream-type-definition.md) | BiStream Type Definition | BiStream is a trait, handlers receive Connection not BiStream | -| [008](decisions/008-secret-service-integration.md) | Secret Service Integration Point | CLI-embedded, exposed via call protocol, no ALPN for secrets | +| [008](decisions/008-secret-service-integration.md) | Vault Integration Point | CLI-embedded, exposed via call protocol, vault is a capability source | | [009](decisions/009-one-way-door-decision-framework.md) | One-Way Door Decision Framework | Classify decisions by reversal cost; one-way doors need ADRs | ## Open Questions @@ -192,7 +192,7 @@ Open questions are tracked in [open-questions.md](open-questions.md). Key questi - **OQ-02**: AuthContext resolution timing (resolved: hybrid — see ADR-004) - **OQ-03**: ALPN string naming convention (resolved: see ADR-006) - **OQ-04**: Dynamic handler registration at runtime vs static at startup (two-way door, defer to implementation) -- **OQ-08**: Secret service integration point (resolved: CLI-embedded via call protocol — see ADR-008) +- **OQ-08**: Vault integration point (resolved: CLI-embedded via call protocol — see ADR-008) ## Failure Modes