iroh's Endpoint natively supports ALPN negotiation and set_alpns(). Our
HandlerRegistry dispatches exactly like iroh's own ProtocolMap/Router
pattern, but shared across both quinn and iroh connection sources. We
use iroh::Endpoint directly (not iroh::Router) because our HandlerRegistry
and AuthContext are shared across sources.
Correct the conflation of quinn/TLS/iroh as interchangeable transports.
They are complementary connectivity modes serving different deployment
contexts: quinn (public IP + TLS), iroh (NAT traversal via relay), TCP
(handler-specific, not core). Clarify that TLS cert = network identity,
not auth identity. Map stealth mode to HTTP handler on standard ALPNs
instead of byte-peeking. Resolve OQ-05 as one-way door. SendStream/
RecvStream now use internal enum dispatch for both quinn and iroh
streams.
Rename the crate from alknet-secret to alknet-vault to better reflect its
purpose as a local key vault (seed management, key derivation, encryption)
rather than a network service.
Symbol renames:
- SecretService → VaultService
- SecretServiceHandle → VaultServiceHandle
- SecretServiceActor → VaultServiceActor
- SecretServiceError → VaultServiceError
- SecretProtocol → VaultProtocol
- SecretMessage → VaultMessage
- ServiceLocked → VaultLocked
- alknet_secret → alknet_vault (crate name)
Update ADR-008 with vault access pattern: the vault is a capability source,
not a service endpoint. The CLI injects derived/decrypted material into
operation contexts — handlers never hold vault references.
Phase 5 now references the architect role and SDD process from
docs/sdd_process.md instead of creating ad-hoc spec stubs. Added
key new ADRs and architecture docs the architect will need to produce.
Updated gitserver reference note (MPL concern, archive it).
Kept rudolfs reference (MIT/Apache, fork candidate).
Also removed 'needs-update' status from the lifecycle states since
it's not part of the SDD process — stale docs get annotated with a
note and existing status, not a new status.
The Unlock variant had a single field used as the
mnemonic, with no way to convey the BIP39 password extension (25th word).
The actor handler silently passed for the passphrase, making it
impossible to unlock with a BIP39 passphrase via irpc.
Split into + to match
the spec and SecretServiceHandle::unlock() signature.
Update secret-service.md to reflect the actual alknet-secret implementation:
- Fix dependency names/versions: secp256k1 (not libsecp256k1), version 0.29,
add tokio/irpc-derive/hmac/rand, use workspace refs
- Add SecretServiceActor and CacheConfig to public API
- Add ethereum.rs module to crate structure, fix test_vectors.rs filename
- DerivedKey is move-only (not Clone), matching the stronger security impl
- Update BIP39 pseudocode to actual derive_path_from_seed() API
- Document derive_password_string() convenience method
- Document SecretServiceActor::spawn() in irpc integration model
- Update Unlock variant to target state: { mnemonic, passphrase: Option }
- Add implementation gap note pointing to unlock-passphrase-gap task
Add tasks/integration/phase3/secret-service/unlock-passphrase-gap.md:
- Fix Unlock protocol variant to carry both mnemonic and BIP39 passphrase
- Currently the irpc message only has passphrase: String (used as mnemonic)
- The handle supports both parameters but the protocol can't convey them
Apply #[rpc_requests(message = SecretMessage)] to SecretProtocol enum with
#[rpc(tx=oneshot::Sender<Result<T, SecretServiceError>>)] and #[wrap] attributes
on each variant. Add SecretServiceActor that wraps SecretServiceHandle and
processes SecretMessage variants via mpsc channel. Update DerivedKey
serialization to use is_human_readable() so postcard preserves private_key
bytes while JSON redacts them. Add Serialize/Deserialize to SecretServiceError
for irpc wire format compatibility. Add tokio dependency for actor runtime.
Implement KeyCache in cache.rs with CachedKey (Zeroize-protected),
CacheConfig (TTL + max_entries), and lazy eviction. Wire cache into
SecretServiceInner: derive_* methods check cache before re-deriving,
Lock clears and zeroizes all cache entries, encrypt/decrypt use
cached encryption key. Per ADR-038 and OQ-SVC-04.
Implements deterministic password derivation using SLIP-0010 at the
given path. derive_password returns raw truncated key bytes;
derive_password_string returns Base64url-encoded (no padding) string.
Both require unlocked state (ServiceLocked if locked). Includes unit
tests for determinism, different paths, length truncation, locked
error, and Base64url encoding.
Per ADR-038, DerivedKey.private_key now derives Zeroize with #[zeroize(drop)]
ensuring sensitive key material is zeroized before deallocation. DerivedKey
is now move-only (no Clone), and JSON/debug output redacts private_key as
"[REDACTED]". Deserialization still works for postcard/irpc wire format.
Also fixes clippy needless_borrows_for_generic_args in encryption.rs and
applies cargo fmt to existing code.
Add module-level documentation explaining that the salt field is reserved
for Phase B KDF-based key rotation. Add doc comment on the salt field
clarifying it is not used in v1 key derivation. Add TODO(Phase B) comment
on salt generation in encrypt().
- Add irpc (0.16) and irpc-derive (0.16) as workspace dependencies
- Add irpc, irpc-derive, and secp256k1 (optional) to alknet-secret Cargo.toml
- Clarify encryption-salt-kdf task: Option B (document salt as reserved) is the
chosen path per spec update, removing Option A acceptance criteria
- Update irpc-secret-protocol-integration task with concrete irpc crate details:
real crate on crates.io v0.16, #[rpc_requests] macro, workspace config,
AuthProtocol pattern reference, DerivedKey serialization considerations
- Fix secp256k1-ethereum-derivation task: correct crate name is secp256k1
(not libsecp256k1), add version pin 0.29
9 atomic tasks for alknet-secret spec conformance and gap closure,
derived from architect's implementation review. Dependencies form
a 5-generation graph starting with spec update, then parallel
implementation tasks, ending with a review gate.
Tasks address: DerivedKey zeroize security, key caching with TTL,
irpc protocol integration, password derivation, secp256k1/Ethereum
derivation, encryption salt/KDF, crypto test vectors, and final
spec conformance review.
Create the alknet-secret crate with BIP39 mnemonic generation, SLIP-0010
Ed25519 HD key derivation, AES-256-GCM encryption, and SecretProtocol
irpc service definition. This is Phase 3.1 from the integration plan.
Architecture changes:
- Promote secret-service.md to reviewed status with full spec format
(crate structure, public API, security model, phase progression,
ADR/OQ cross-references, wire format compatibility section)
- Add ADR-038 (seed lifecycle and memory security): zeroize for v1,
mlock deferred to Phase B
- Add OQ-SEC-01 (mlock/VirtualLock for seed RAM) to open-questions.md
- Update README.md with ADR-038 and secret-service status
Crate structure:
- src/mnemonic.rs: BIP39 phrase generation, validation, seed derivation
- src/derivation.rs: SLIP-0010 HD key derivation, path constants (74')
- src/encryption.rs: AES-256-GCM encrypt/decrypt, EncryptedData type
- src/protocol.rs: SecretProtocol irpc enum, DerivedKey, KeyType
- src/service.rs: SecretServiceHandle with Unlock/Lock lifecycle
- 40 passing tests (unit + integration + doc)
Add http feature flag with axum, hyper, hyper-util, tower, and http-body-util
dependencies. Create http module with auth middleware (extracts Bearer token,
calls IdentityProvider::resolve_from_token, attaches Identity to extensions)
and router scaffold (default 404 fallback, no operational routes yet). Replace
send_fake_nginx_404 with axum router handoff when http feature is enabled;
fake 404 behavior preserved when http is disabled. Wire HttpInterface with
build_router() method and pass IdentityProvider through Server to handle_connection.
Implement the SSH session to call protocol bridge:
- Add FrameFramedReader/FrameFramedWriter for async I/O of length-prefixed EventEnvelope frames
- SshSession::recv() reads InterfaceEvent frames from the alknet-control:0 channel via mpsc
- SshSession::send() writes EventEnvelope frames to the alknet-control:0 channel via mpsc
- Add ControlChannelBridge implementing ControlChannelHandler for routing channel data
- SshHandler::channel_open_direct_tcpip routes alknet-control:0 to the bridge task
- Session Identity attached to every InterfaceEvent produced by recv()
- ControlChannelRouter gains take_handler() for non-control alknet-* channel routing