tasks: decompose vault, core, call crates into 28 atomic implementation tasks
Break down the three initial crates (alknet-vault, alknet-core, alknet-call) into dependency-ordered task files for implementation agents. Structure: - tasks/vault/ (10 tasks) — drift fixes from ADR-025/026 refactor, review, spec sync. Vault is independent and can run fully in parallel with core/call. - tasks/core/ (6 tasks) — crate init, core types, config, auth, endpoint, review. Core is foundational; call depends on it. - tasks/call/ (12 tasks) — split into registry/ and protocol/ topic subdirs reflecting the two subsystems. CallAdapter is the merge point. Key decisions: - Drifts 3+9+10 grouped as one task (key-versioning-rotation) — the complete ADR-021 rotation feature that doesn't compile in pieces - Reviews injected at end of each crate phase (vault, core, call) - Vault spec-sync task removes the drift table and bumps doc status to stable - ACME deferred in core/endpoint (noted as TODO; X509 manual certs for now) - OperationEnv kept as a trait (load-bearing for ADR-024 layering) Validated: 28 tasks, no cycles, 11 generations of parallel work. Critical path runs through call (11 tasks). Vault completes by generation 4. 6 high-risk tasks identified (21%): irpc-removal, endpoint, operation-context, operation-env, call-adapter, abort-cascade.
This commit is contained in:
127
tasks/vault/key-versioning-rotation.md
Normal file
127
tasks/vault/key-versioning-rotation.md
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
id: vault/key-versioning-rotation
|
||||
name: Implement version-indexed encryption key paths, bump CURRENT_KEY_VERSION to 2, and add rotate method
|
||||
status: pending
|
||||
depends_on: [vault/irpc-removal]
|
||||
scope: moderate
|
||||
risk: medium
|
||||
impact: component
|
||||
level: implementation
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Fix drift items #3, #9, and #10 as one coherent feature: the version-indexed
|
||||
key rotation mechanism from ADR-021. These three drifts are tightly coupled —
|
||||
`CURRENT_KEY_VERSION = 2` (drift #3), version-aware `encrypt`/`decrypt` via
|
||||
`encryption_path_for_version` (drift #9), and the `rotate` method (drift #10)
|
||||
form the complete key rotation feature. Splitting them would produce tasks that
|
||||
don't compile independently.
|
||||
|
||||
### Drift #3: Bump CURRENT_KEY_VERSION
|
||||
|
||||
Current: `CURRENT_KEY_VERSION = 1` (but the key is HD-derived, and v1 is
|
||||
reserved for the TypeScript PBKDF2 legacy per ADR-020).
|
||||
|
||||
Target: `CURRENT_KEY_VERSION = 2` (HD-derived, per ADR-020).
|
||||
|
||||
Version semantics:
|
||||
- v1: TypeScript predecessor's PBKDF2-encrypted data — the vault **cannot**
|
||||
decrypt it (different key derivation). Migration is a one-time re-encryption.
|
||||
- v2: HD-derived at `m/74'/2'/0'/0'` (PATHS::ENCRYPTION) — current.
|
||||
- v3+: `m/74'/2'/0'/1'`, `m/74'/2'/0'/2'`, etc. — future rotation versions.
|
||||
|
||||
### Drift #9: Version-aware encrypt/decrypt
|
||||
|
||||
Current: `encrypt`/`decrypt` always derive at `PATHS::ENCRYPTION` regardless of
|
||||
the `key_version` parameter.
|
||||
|
||||
Target:
|
||||
- `encrypt(plaintext, key_version)`: derive the encryption key at
|
||||
`encryption_path_for_version(key_version)`, stamp the same `key_version` on
|
||||
the resulting `EncryptedData`.
|
||||
- `decrypt(encrypted)`: derive the key at
|
||||
`encryption_path_for_version(encrypted.key_version)` — the blob carries its
|
||||
own version, and each version maps to a distinct derivation path.
|
||||
|
||||
This requires:
|
||||
1. `encryption_path_for_version(version: u32) -> Result<String, DerivationError>`
|
||||
already exists in `derivation.rs` — verify it returns `InvalidPath` for
|
||||
`version < 2` (v1 is TS legacy, v0 is meaningless).
|
||||
2. `derive_encryption_key_for_version(version: u32) -> Result<DerivedKey, VaultServiceError>`
|
||||
— a new method on `VaultServiceHandle` that maps version → path → derive.
|
||||
Cached by path (same cache as `derive_encryption_key`).
|
||||
3. `encrypt` and `decrypt` use `derive_encryption_key_for_version` instead of
|
||||
deriving at the fixed `PATHS::ENCRYPTION` path.
|
||||
|
||||
### Drift #10: Implement rotate
|
||||
|
||||
Current: no `rotate` method exists.
|
||||
|
||||
Target:
|
||||
```rust
|
||||
pub fn rotate(&self, encrypted: &EncryptedData, to_version: u32) -> Result<EncryptedData, VaultServiceError>;
|
||||
```
|
||||
|
||||
Decrypts with the old version's key (from `encrypted.key_version`), re-encrypts
|
||||
with the new version's key (`to_version`). Returns the new `EncryptedData` —
|
||||
the caller replaces the blob in storage. No new mnemonic needed; the same seed
|
||||
produces all version keys via different derivation paths (ADR-021).
|
||||
|
||||
### Implementation notes
|
||||
|
||||
- `derive_encryption_key(path)` (the path-based API) remains as-is for deriving
|
||||
at arbitrary paths. `derive_encryption_key_for_version(version)` is the
|
||||
version-aware API used by `encrypt`/`decrypt`. Both share the same cache
|
||||
(keyed by derivation path).
|
||||
- `encrypt` and `decrypt` extract the `EncryptionKey` from the `DerivedKey` via
|
||||
`EncryptionKey::from_derived_bytes` (see encryption.md).
|
||||
- `encryption_path_for_version` returns `InvalidPath` for `version < 2`.
|
||||
`derive_encryption_key_for_version` propagates this as
|
||||
`VaultServiceError::InvalidPath`.
|
||||
|
||||
### Scope
|
||||
|
||||
This task touches `encryption.rs` (CURRENT_KEY_VERSION), `service.rs` (encrypt,
|
||||
decrypt, rotate, derive_encryption_key_for_version), and possibly `derivation.rs`
|
||||
(verify `encryption_path_for_version`). It depends on the irpc removal task
|
||||
(drift #4) because both modify `service.rs`.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `CURRENT_KEY_VERSION` is `2` in `encryption.rs`
|
||||
- [ ] `derive_encryption_key_for_version(version)` method added to `VaultServiceHandle`
|
||||
- [ ] `derive_encryption_key_for_version` returns `InvalidPath` for `version < 2`
|
||||
- [ ] `encrypt(plaintext, key_version)` derives at `encryption_path_for_version(key_version)`
|
||||
- [ ] `encrypt` stamps the passed `key_version` on the resulting `EncryptedData`
|
||||
- [ ] `decrypt(encrypted)` derives at `encryption_path_for_version(encrypted.key_version)`
|
||||
- [ ] `rotate(encrypted, to_version)` method implemented: decrypt old, re-encrypt new
|
||||
- [ ] `rotate` returns `EncryptedData` with `key_version = to_version`
|
||||
- [ ] Unit test: encrypt at v2, decrypt at v2 — round-trip succeeds
|
||||
- [ ] Unit test: encrypt at v2, rotate to v3, decrypt at v3 — round-trip succeeds
|
||||
- [ ] Unit test: decrypt v2 blob after rotation — old key still derivable (partial rotation safe)
|
||||
- [ ] Unit test: `derive_encryption_key_for_version(1)` returns `InvalidPath`
|
||||
- [ ] Unit test: `derive_encryption_key_for_version(0)` returns `InvalidPath`
|
||||
- [ ] `cargo test` succeeds
|
||||
- [ ] `cargo clippy` succeeds with no warnings
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/crates/vault/README.md — Known Source Drift table items #3, #9, #10
|
||||
- docs/architecture/crates/vault/encryption.md — Key Versioning, Rotation, EncryptionKey
|
||||
- docs/architecture/crates/vault/service.md — encrypt, decrypt, rotate, derive_encryption_key_for_version
|
||||
- docs/architecture/crates/vault/mnemonic-derivation.md — encryption_path_for_version, PATHS
|
||||
- docs/architecture/decisions/020-hd-derivation-for-encryption-keys.md — ADR-020
|
||||
- docs/architecture/decisions/021-key-rotation-via-version-indexed-paths.md — ADR-021
|
||||
|
||||
## Notes
|
||||
|
||||
> These three drifts are one feature: version-indexed key rotation (ADR-021).
|
||||
> Splitting them would produce tasks that don't compile independently —
|
||||
> bumping the version without version-aware encrypt/decrypt would make v2
|
||||
> blobs undecryptable, and rotate without version-aware encrypt/decrypt has no
|
||||
> keys to work with. Depends on irpc removal because both modify `service.rs`.
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Reference in New Issue
Block a user