# 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:

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

**alknet-vault 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
- `alknet-vault` does not implement `ProtocolHandler` — 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:

- `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](../crates/vault/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

- 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](../crates/vault/README.md)
- Implementation: `crates/alknet-vault/`