alkvault 0.1.0

Local key vault: BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, AES-256-GCM encryption for securing provider keys, credentials, and identity material
Documentation
# alkvault

Local key vault: BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key
derivation, and AES-256-GCM encryption for securing provider keys,
credentials, and identity material.

`alkvault` is a standalone crate with **zero network dependencies**. It
holds the master seed — the root of trust for all derived keys and
encrypted credentials — and provides the cryptographic primitives and a
runtime API for managing it. Nothing important goes in env vars.

## What it is

A local key vault built on the principle that **secrets are derived, not
stored**. From a single BIP39 mnemonic, the vault deterministically
derives Ed25519 identity keys, SSH host keys, and AES-256-GCM encryption
keys on demand. External credentials (API keys, OAuth tokens) that can't
be derived are encrypted with a seed-derived key and stored as
`EncryptedData` blobs.

The vault is **local-only by construction** — direct method calls on
`VaultServiceHandle`, no actor, no message enum, no wire format, no
remote dispatch. The master seed and derived private keys never cross a
process boundary.

## Usage

```rust
use alkvault::derivation::PATHS;
use alkvault::{VaultServiceHandle, KeyType};

// Generate a new mnemonic and unlock the vault.
let vault = VaultServiceHandle::new();
let phrase = vault.unlock_new(24)?;  // 24-word BIP39 mnemonic
// Store `phrase` securely — it is the root of trust.

// Derive an Ed25519 identity keypair at m/74'/0'/0'/0'.
let identity = vault.derive_ed25519(PATHS::IDENTITY)?;
assert_eq!(identity.key_type, KeyType::Ed25519);
assert_eq!(identity.private_key.len(), 32);

// Encrypt an external credential with a seed-derived AES-256-GCM key.
let plaintext = "sk-proj-abc123xyz789";
let encrypted = vault.encrypt(plaintext, 2)?;
let decrypted = vault.decrypt(&encrypted)?;
assert_eq!(decrypted, plaintext);

// Lock the vault — purges the seed and all cached derived keys.
vault.lock();
# Ok::<(), Box<dyn std::error::Error>>(())
```

## Features

| Feature | Default | Description |
|---------|---------|-------------|
| `secp256k1` | off | BIP-0032 secp256k1 HD key derivation for Ethereum signing keys (`m/44'/60'/0'/0/0`) |

Without `secp256k1`, `derive_ethereum_key` returns
`UnsupportedKeyType`. The `secp256k1` crate is a heavy dependency (C
library for curve operations); feature-gating it keeps the default build
lightweight.

## Derivation paths

`alkvault` reserves the `74'` coin type (unallocated per SLIP-0044).

| Path | Purpose | Key type |
|------|---------|----------|
| `m/74'/0'/0'/0'` | Primary identity keypair | Ed25519 |
| `m/74'/0'/0'/{n}'` | Worker/device identity | Ed25519 |
| `m/74'/0'/1'/0'` | SSH host key | Ed25519 |
| `m/74'/2'/0'/0'` | Encryption key (v2) | AES-256-GCM |
| `m/74'/2'/0'/{n}'` | Encryption key (v{n+2}) | AES-256-GCM |
| `m/44'/60'/0'/0/0` | Ethereum signing key | secp256k1 (feature-gated) |

Key rotation re-encrypts `EncryptedData` from one version to another via
version-indexed derivation paths — same seed, different keys, no new
mnemonic needed.

## Security model

- **Seed never persisted.** The BIP39 mnemonic is entered at startup or
  via `unlock` and held only in `Zeroize`-protected RAM. `lock()` purges
  the seed and all cached derived keys.
- **Zeroize everything sensitive.** `Mnemonic`, `Seed`,
  `ExtendedPrivKey`, `EncryptionKey`, `DerivedKey`, and `CachedKey` all
  implement `Zeroize` and `ZeroizeOnDrop`. Secret material does not
  linger in freed heap memory.
- **OsRng for nonces.** AES-GCM IVs use the operating system's CSPRNG
  (`SysRng`), never a thread-local RNG. IV reuse under the same key is
  catastrophic for GCM.
- **`DerivedKey` redaction.** `DerivedKey` serializes `private_key` as
  `"[REDACTED]"` in all formats (defense-in-depth for logging accidents)
  and rejects redacted payloads on deserialization. `Debug` impls also
  redact.
- **Move-only, not `Clone`.** `DerivedKey` and `EncryptionKey` are
  move-only — no accidental duplication of secret material.
- **No `unwrap()` outside tests.** Vault operations propagate errors.
  A poisoned lock is recovered with `unwrap_or_else(|e| e.into_inner())`,
  not panicked.

## Crate independence

`alkvault` does **not** depend on any application or networking crate.
It defines its own types (`VaultServiceError`, `DerivedKey`,
`EncryptedData`, etc.) and is usable in contexts where networking doesn't
exist — CLI tools, test harnesses, key-derivation utilities, and future
WASM targets.

## Documentation

Architecture documentation lives under [`docs/architecture/`](docs/architecture/):

- [Mnemonic and key derivation]docs/architecture/mnemonic-derivation.md — BIP39, SLIP-0010, BIP-0032, derivation paths
- [Encryption]docs/architecture/encryption.md — AES-256-GCM, `EncryptedData`, key versioning
- [Service]docs/architecture/service.md — `VaultServiceHandle` lifecycle, cache, error model
- [Protocol]docs/architecture/protocol.md — `DerivedKey` redaction, `KeyType`, serialization
- [Architecture decisions (ADRs)]docs/architecture/decisions/ — standalone crate, local-only dispatch, HD derivation, key rotation

## License

Licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE]LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT]LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.