Drops irpc from alknet-vault entirely. The vault's dispatch is now direct method calls on VaultServiceHandle — no VaultProtocol enum, no VaultMessage, no VaultServiceActor, no mpsc channel, no Service trait, no RemoteService trait, no postcard serialization. The vault is local-only by construction. The core security argument: irpc made the vault remote-capable by default (RemoteService generated unless no_rpc is passed). The IrohProtocol handler forwards all messages without auth. The docs framed 'register an ALPN' as a server-setup change. This is the default-insecure anti-pattern — security should be opt-in, not opt-out. ADR-025 inverts the default: local-only is the only mode, and remote access requires building a separate vault-server crate (a visible architectural act, not a flag flip). The actor path was already dead code — service.md said 'prefer VaultServiceHandle directly — no channel, no serialization.' The actor existed only to make irpc's Service trait work, which existed only to make RemoteService work, which was the footgun. VaultServiceHandle's Arc<RwLock> provides concurrent reads and exclusive writes — better throughput than the actor's sequential processing. DerivedKey serialization simplifies: always redact on serialize (for logging safety), reject '[REDACTED]' on deserialize with an error. No 'postcard preserves bytes' path. This resolves review #002 W8 (silent corruption on JSON-deserialized DerivedKey). Resolves: - OQ-21: remote vault access — resolved (not deferred). Not a vault crate feature; if needed, a separate vault-server crate with its own ADR. - C7: vault-server-crate question decided — not created now, not precluded. - C8: operation access policy table dissolved — all operations local-only by default; if a vault-server crate exposes some remotely, that crate defines the policy. - W8: DerivedKey JSON deserialization — resolved (reject redacted payloads). Amends ADR-005 (irpc remains for alknet-call, not for alknet-vault), ADR-018 (vault is even more standalone — zero RPC framework deps), ADR-019 (vault is the only layer, not just the only direct-caller layer), ADR-008 (vault integration point unchanged, but now local-only by construction).
7.7 KiB
ADR-018: Vault as Standalone Crate
Status
Accepted
Context
alknet-vault provides BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, BIP-0032 secp256k1 derivation (feature-gated), and AES-256-GCM encryption. It holds the master seed — the root of trust for all derived keys and encrypted credentials in the alknet system.
The question is: what does alknet-vault depend on? The candidates:
- Depend on alknet-core for shared types (errors, maybe Identity). This pulls QUIC, quinn, iroh, rustls, and tokio runtime dependencies into the vault's dependency tree.
- Stand alone — zero alknet crate dependencies. The vault defines its own types, its own error enum. Other crates depend on the vault; the vault depends on nothing in alknet.
This is a one-way door. Once the vault depends on alknet-core, reversing it requires removing that dependency from every type, error conversion, and test — and the longer it stays, the more entangled it becomes.
Why standalone matters
The vault is used in contexts where QUIC networking does not exist:
- CLI tools: a key-derivation utility that derives an identity key from a mnemonic without starting a network endpoint.
- Test harnesses: integration tests in other crates derive test keys without spinning up a QUIC endpoint.
- WASM key derivation: a future WASM target that derives keys in a browser (the BiStream trait in ADR-007 preserves this door at the transport layer; the vault's independence preserves it at the secret layer).
- Embedded assembly: a binary that only needs the vault to decrypt a config file at startup, with no networking at all.
If the vault depends on alknet-core, all of these contexts pull in quinn, iroh, rustls, and tokio — none of which they need. The vault's job is cryptographic derivation and encryption. It has no networking concern.
What the vault provides without alknet-core
The vault defines its own types and traits:
Mnemonic,Seed— BIP39 root materialExtendedPrivKey(Ed25519),Secp256k1ExtendedPrivKey(Ethereum) — derived key materialDerivedKey,KeyType— protocol-level key representationEncryptedData,EncryptionKey— AES-256-GCM blobsVaultServiceHandle— runtime API (direct method calls; no actor, no message enum — see ADR-025)VaultServiceError— its own error enum (string-wrapped sub-errors; the vault doesn't share an error type with alknet-core)
The vault uses direct method calls on VaultServiceHandle, not irpc
dispatch (ADR-025). The vault is local-only by construction — no remote
dispatch capability, no RemoteService trait, no wire format for vault
messages. If remote vault access is ever needed, it's a separate crate that
wraps the vault (see ADR-025, OQ-021).
Decision
alknet-vault has zero alknet crate dependencies. It depends only on
external crates (bip39, ed25519-bip32, aes-gcm, sha2, hmac,
secp256k1, tokio for RwLock sync primitives, serde,
zeroize, thiserror, base64, rand). ADR-025 dropped irpc,
irpc-derive, and postcard — the vault no longer uses irpc dispatch.
The vault does not depend on:
alknet-core— no shared types, noIdentity, noAuthContextalknet-call— noOperationSpec, noOperationContext, no call protocolalknet-vaultdoes not implementProtocolHandler— it has no ALPN (see ADR-019)
Dependency flow is strictly one-directional:
alknet-vault (standalone)
↑
alknet (CLI binary) — the only crate that depends on alknet-vault
No handler crate depends on alknet-vault directly. Handlers receive derived material through capabilities injected by the assembly layer (ADR-014). The CLI binary is the sole integration point (ADR-008, ADR-019).
Type independence
The vault defines its own types and does not share types with alknet-core:
VaultServiceErroris the vault's error enum. It is a plainthiserror::Error(ADR-025 dropped irpc, so vault errors no longer needSerialize/Deserializefor wire dispatch). It does not implementFromfor alknet-core error types — the CLI binary converts at the assembly boundary.DerivedKeyis the vault's key representation. It is not shared with alknet-core'sIdentitytype. The CLI binary extracts the bytes it needs (private key for signing, public key for TLS identity) and constructs the alknet-core types at the assembly layer.EncryptedDatais the vault's encrypted blob format. It is shared withalknet-storage(a future crate) by type-level agreement, not by a crate dependency — both crates must agree on the serialization format (see encryption.md).
Consequences
Positive:
- The vault compiles and runs without QUIC, quinn, iroh, rustls, or a tokio
runtime (the
VaultServiceHandleworks with juststd::sync::RwLock; the actor usestokio::sync::mpscbut that's a lightweight dependency). - CLI tools, test harnesses, and future WASM targets can use the vault for key derivation without pulling in networking crates.
- The vault's API surface is stable — changes to alknet-core types don't force a vault recompile, and changes to vault types don't force a handler recompile (the CLI is the only consumer).
- No circular dependency risk. The dependency graph is a strict DAG.
- The vault can be published and used independently of alknet — it's a general-purpose local key vault, not an alknet-specific component.
Negative:
- The vault cannot share types with alknet-core. If a type wants to be shared
(e.g., a future
Fingerprinttype), it must live in alknet-core and the vault must define its own equivalent, or a new shared crate must be created. This is a feature, not a bug — it forces explicit boundaries. - The CLI binary must convert between vault types and alknet-core types at
the assembly boundary. This is a small amount of glue code (extract bytes
from
DerivedKey, construct alknet-core types). See ADR-019. - The vault's
VaultServiceErroris separate from alknet-core'sHandlerError. The CLI binary maps vault errors to handler errors or startup failures. This is expected — the vault is a library, not a handler.
Assumptions
-
The vault's API is consumed by one component (the CLI binary) in the alknet system. If a future use case requires multiple crates to depend on the vault directly, the dependency flow still holds — they depend on the vault, the vault depends on nothing. The standalone property is preserved.
-
Shared types between the vault and other crates are agreed by type-level compatibility, not by a crate dependency.
EncryptedDatais the example: both the vault andalknet-storage(future) must agree on the serialization format. This is documented in the type's spec, not enforced by the type system across crates. -
The vault's error type does not need to integrate with alknet-core's error handling. The vault returns
VaultServiceError; the CLI binary handles it at the assembly boundary. If a future use case requires propagating vault errors through alknet-core's error types, the CLI converts at the boundary.
References
- ADR-003: Crate decomposition (alknet-vault is standalone)
- ADR-005: irpc as call protocol foundation (irpc remains the foundation for alknet-call; the vault no longer uses irpc — see ADR-025)
- ADR-025: Vault local-only dispatch (dropped irpc from the vault; the vault uses direct method calls, no actor, no remote capability)
- ADR-008: Vault integration point (CLI-embedded, assembly-layer only)
- ADR-014: Secret material flow and capability injection
- ADR-019: Vault assembly-layer-only access
- crates/vault/README.md
- Implementation:
crates/alknet-vault/