Rename the crate from alknet-vault to alkvault across source and docs: - Cargo.toml: package name and lib name (alknet_vault -> alkvault) - src/ doc comments and doc-test use statements - tests/ use statements and one string literal Convert references to non-vault alknet ADRs (003, 005, 008, 010, 014, 064) that were broken local links into @alkdev/alknet: cross-repo references, matching the alktype sibling pattern. Local ADR/OQ references are now proper links. Rewrote monorepo path references (crates/alknet-vault/src/...) to the flat layout (src/...). Fixed sdd_process.md package name (@alkdev/storage -> @alkdev/alkvault). ADR/OQ renumbering is deferred to a subsequent pass per the alknet- origin numbering convention. Generic prose 'vault' and type names (VaultServiceHandle, VaultServiceError, etc.) are unchanged. Build, 108 tests, and clippy all pass clean.
221 lines
10 KiB
Markdown
221 lines
10 KiB
Markdown
# ADR-018: Vault as Standalone Crate
|
|
|
|
## Status
|
|
|
|
Accepted
|
|
|
|
## Context
|
|
|
|
alkvault 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 alkvault depend on? The candidates:
|
|
|
|
1. **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.
|
|
2. **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 material
|
|
- `ExtendedPrivKey` (Ed25519), `Secp256k1ExtendedPrivKey` (Ethereum) —
|
|
derived key material
|
|
- `DerivedKey`, `KeyType` — protocol-level key representation
|
|
- `EncryptedData`, `EncryptionKey` — AES-256-GCM blobs
|
|
- `VaultServiceHandle` — 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
|
|
|
|
**alkvault has zero alknet crate dependencies.** It depends only on
|
|
external crates (`bip39`, `ed25519-bip32`, `aes-gcm`, `sha2`, `hmac`,
|
|
`secp256k1`, `serde`, `zeroize`, `thiserror`, `base64`, `rand`). ADR-025
|
|
dropped `irpc`, `irpc-derive`, `postcard`, and `tokio` — the vault no longer
|
|
uses irpc dispatch or async sync primitives. All vault methods are
|
|
synchronous; `std::sync::RwLock` provides thread safety without a tokio
|
|
dependency.
|
|
|
|
The vault does not depend on:
|
|
- `alknet-core` — no shared types, no `Identity`, no `AuthContext`
|
|
- `alknet-call` — no `OperationSpec`, no `OperationContext`, no call protocol
|
|
- `alkvault` does not implement `ProtocolHandler` — it has no ALPN (see
|
|
ADR-019)
|
|
|
|
Dependency flow is strictly one-directional:
|
|
|
|
```
|
|
alkvault (standalone)
|
|
↑
|
|
alknet (CLI binary) — the only crate that depends on alkvault
|
|
```
|
|
|
|
No handler crate depends on alkvault 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:
|
|
|
|
- `VaultServiceError` is the vault's error enum. It is a plain
|
|
`thiserror::Error` (ADR-025 dropped irpc, so vault errors no longer need
|
|
`Serialize`/`Deserialize` for wire dispatch). It does not implement
|
|
`From` for alknet-core error types — the CLI binary converts at the
|
|
assembly boundary.
|
|
- `DerivedKey` is the vault's key representation. It is not shared with
|
|
alknet-core's `Identity` type. 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.
|
|
- `EncryptedData` is the vault's encrypted blob format. It is shared with
|
|
`alknet-storage` (a future crate) by type-level agreement, not by a crate
|
|
dependency — both crates must agree on the serialization format (see
|
|
[encryption.md](../encryption.md)). The format is **frozen**
|
|
(see Decision below).
|
|
|
|
## `EncryptedData` Wire Format Lock
|
|
|
|
The `EncryptedData` struct is a **stable wire format** shared with
|
|
`alknet-storage` (a future crate) and the TypeScript consumer
|
|
(`@alkdev/storage`) by type-level agreement, not by a crate dependency.
|
|
Both crates and the TypeScript consumer must agree on the serialization
|
|
format. The format is now explicitly **frozen**:
|
|
|
|
```rust
|
|
pub struct EncryptedData {
|
|
pub key_version: u32, // rotation tracking
|
|
pub salt: String, // base64, 32 bytes — unused in v2 (wire-format compat)
|
|
pub iv: String, // base64, 12 bytes — AES-GCM nonce
|
|
pub data: String, // base64 — ciphertext + auth tag
|
|
}
|
|
```
|
|
|
|
The frozen compatibility surface:
|
|
|
|
- **Fields**: `key_version`, `salt`, `iv`, `data` — no fields may be
|
|
removed or renamed. New fields may be added only if they are optional
|
|
(default on deserialization) and do not change the meaning of existing
|
|
fields.
|
|
- **Encoding**: all binary fields are base64-encoded as strings for JSON
|
|
serialization. This is the cross-language wire format.
|
|
- **Field semantics**: `key_version` selects the derivation path
|
|
(ADR-021). `salt` is unused in v2 but is part of the frozen format —
|
|
it cannot be removed without a format-version migration (a future KDF
|
|
in v3 would use the salt for *new* data, not retroactively for v2 data
|
|
— see ADR-020, W6). `iv` is the 12-byte GCM nonce. `data` is the
|
|
ciphertext with the GCM auth tag appended.
|
|
|
|
**Why this needs an explicit lock**: the "type-level agreement, not a
|
|
crate dependency" approach means there is no compiler enforcement of the
|
|
format across crates. The stability contract existed only in prose. An
|
|
implementer modifying `EncryptedData` (e.g., removing the unused `salt`
|
|
field) would find no ADR saying "this format is frozen." This decision
|
|
makes the freeze explicit and enforceable by review.
|
|
|
|
**This resolves review #002 W10.**
|
|
|
|
## Consequences
|
|
|
|
**Positive:**
|
|
- The vault compiles and runs without QUIC, quinn, iroh, rustls, or a tokio
|
|
runtime (the `VaultServiceHandle` works with just `std::sync::RwLock`;
|
|
ADR-025 removed the actor and its `tokio::sync::mpsc` dependency entirely).
|
|
- 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 `Fingerprint` type), 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 `VaultServiceError` is separate from alknet-core's
|
|
`HandlerError`. The CLI binary maps vault errors to handler errors or
|
|
startup failures. This is expected — the vault is a library, not a
|
|
handler.
|
|
|
|
## Assumptions
|
|
|
|
1. **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.
|
|
|
|
2. **Shared types between the vault and other crates are agreed by type-level
|
|
compatibility, not by a crate dependency.** `EncryptedData` is the example:
|
|
both the vault and `alknet-storage` (future) must agree on the
|
|
serialization format. This is documented in the type's spec, not enforced
|
|
by the type system across crates.
|
|
|
|
3. **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
|
|
|
|
- `@alkdev/alknet: ADR-003` — crate decomposition (alkvault is standalone)
|
|
- `@alkdev/alknet: ADR-005` — irpc as call protocol foundation (superseded
|
|
by ADR-064 — irpc was never integrated into alknet-call; the vault no
|
|
longer uses irpc either — see ADR-025)
|
|
- [ADR-025](025-vault-local-only-dispatch.md) — vault local-only dispatch
|
|
(dropped irpc from the vault; the vault uses direct method calls, no
|
|
actor, no remote capability)
|
|
- `@alkdev/alknet: ADR-008` — vault integration point (CLI-embedded,
|
|
assembly-layer only)
|
|
- `@alkdev/alknet: ADR-014` — secret material flow and capability injection
|
|
- [ADR-019](019-vault-assembly-layer-only.md) — vault assembly-layer-only
|
|
access
|
|
- [README.md](../README.md)
|
|
- Implementation: `src/`
|
|
|
|
> Cross-repo references (`@alkdev/alknet: ...`) point to the parent
|
|
> `@alkdev/alknet` workspace where this crate originated. The artifacts
|
|
> are preserved there as historical context; they are not part of this
|
|
> standalone repo. |