docs(architecture): resolve review #002 Tiers 1-3 — mechanical and consistency fixes
Governance (Tier 2):
- Advance ADR-022 and ADR-023 from Proposed to Accepted (specs already
depend on their types as source of truth)
- Amend ADR-015: mark Decision 3 and Assumption 6 as superseded by ADR-022;
update handler_identity type to CompositionAuthority
- Amend ADR-002: note handle() signature revised by ADR-007 (BiStream → Connection)
- Amend ADR-004: note 'enrich/replace' AuthContext language superseded by
ADR-011's immutability model; update to describe set_identity on Connection
- Update main README ADR table to show ADR-022/023 as Accepted
Spec-ADR consistency (Tier 3):
- Add abort_policy: AbortPolicy field to OperationContext struct (ADR-016
Decision 6 mandated this but the spec omitted it)
- Define AbortPolicy enum (AbortDependents | ContinueRunning) with Default impl
- Add abort_policy to build_root_context and LocalOperationEnv::invoke()
- Define the OperationEnv trait explicitly with invoke() and
invoke_with_policy() methods (was referenced as 'must remain a trait'
but never defined)
- Specify From<StreamError> for HandlerError impl with exact variant mapping
- Add Connection::from_quinn() / from_iroh() constructors (was referenced
as Connection::new() but never defined)
- Remove undefined CertAuthorityEntry placeholder from AuthPolicy v1 (will
be added additively when alknet-ssh lands)
- Fix config.md key-differences table: rate limits are in DynamicConfig,
not StaticConfig
Mechanical fixes (Tier 1):
- overview.md: 'closes the QUIC stream' → 'closes the connection' (stale
from pre-ADR-007 model)
- overview.md: OQ-04 entry updated from stale 'defer to implementation'
to 'resolved: static at startup'
- mnemonic-derivation.md: remove duplicate helper functions block (incomplete
first copy, complete second copy)
- ADR-003: add iroh (feature-gated) to alknet-core dependency list, added
by ADR-010
- ADR-021: fix ambiguous 'W1 drift issue from the vault review' cross-reference
- ADR-022: rephrase FromCall 'leaf locally' to 'leaf in the local registry'
- ADR-017: add error_schemas to from_call mirror list and services/schema
step (inconsistency with ADR-023)
- ADR-016: fix self-referential citation ('ADR-016 Assumption 5' → 'Assumption 5')
- Add ScopedOperationEnv::empty(), allows(), new() and
CompositionAuthority::none(), new() impl blocks (referenced but undefined)
- Add call.completed clarification for non-subscription calls
- Add services/schema leading-slash normalization note
- Crate README ADR tables: add missing ADR-013 (call), ADR-015 (core),
ADR-006 + ADR-010 (vault)
- Vault README: add consolidated 'Known Source Drift' table tracking all
four drift items (OsRng, unwrap, CURRENT_KEY_VERSION, spawn bug) in one
place, including the two previously missing from README
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-22
|
||||
last_updated: 2026-06-22-22
|
||||
---
|
||||
|
||||
# alknet-call
|
||||
@@ -21,6 +21,7 @@ Structured RPC over QUIC: operations, request/response, streaming subscriptions,
|
||||
| [001](../../decisions/001-alpn-protocol-dispatch.md) | ALPN-Based Protocol Dispatch | CallAdapter registers on ALPN `alknet/call` |
|
||||
| [002](../../decisions/002-protocol-handler-trait.md) | ProtocolHandler Trait | CallAdapter implements ProtocolHandler |
|
||||
| [003](../../decisions/003-crate-decomposition.md) | Crate Decomposition | alknet-call depends on alknet-core and irpc |
|
||||
| [013](../../decisions/013-rust-canonical-implementation.md) | Rust as Canonical Implementation Language | Adapter traits defined in Rust; TS is reference/browser adaptation |
|
||||
| [004](../../decisions/004-auth-as-shared-core.md) | Auth as Shared Core | AuthContext passed to call handlers |
|
||||
| [005](../../decisions/005-irpc-as-call-protocol-foundation.md) | irpc as Call Protocol Foundation | irpc provides framing and service dispatch |
|
||||
| [006](../../decisions/006-alpn-convention-and-connection-model.md) | ALPN String Convention | `alknet/call` ALPN, one ALPN per connection |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-22
|
||||
last_updated: 2026-06-22-22
|
||||
---
|
||||
|
||||
# Call Protocol
|
||||
@@ -105,6 +105,10 @@ Five event types carry request/response and subscription semantics:
|
||||
|
||||
The `id` field carries the `requestId` for correlation.
|
||||
|
||||
`call.completed` is sent only for subscriptions. A plain `call()` (request/response)
|
||||
is complete after its single `call.responded`; no `call.completed` follows. The
|
||||
`PendingRequestMap` entry for a `Call` is deleted on the first `call.responded`.
|
||||
|
||||
### `call.requested` Payload
|
||||
|
||||
The `payload` of a `call.requested` event has this shape:
|
||||
@@ -297,6 +301,7 @@ fn build_root_context(
|
||||
metadata: HashMap::new(), // fresh per request
|
||||
env: registration.scoped_env.clone()
|
||||
.unwrap_or_else(ScopedOperationEnv::empty), // from the bundle, empty for leaves
|
||||
abort_policy: AbortPolicy::default(), // abort-dependents (ADR-016 Decision 6)
|
||||
internal: false, // external call — ACL against caller identity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-22
|
||||
last_updated: 2026-06-22-22
|
||||
---
|
||||
|
||||
# Operation Registry
|
||||
@@ -116,12 +116,36 @@ pub struct OperationContext {
|
||||
pub capabilities: Capabilities,
|
||||
pub metadata: HashMap<String, Value>,
|
||||
pub env: OperationEnv,
|
||||
/// Abort policy for this call's descendants (ADR-016 Decision 6).
|
||||
/// Default `AbortDependents` — aborting this request aborts all
|
||||
/// non-terminal descendants. `ContinueRunning` is an opt-in for
|
||||
/// long-running work that should survive a parent's abort. Set by the
|
||||
/// composing handler via `OperationEnv::invoke()` (or
|
||||
/// `invoke_with_policy()`), not by the wire caller.
|
||||
pub abort_policy: AbortPolicy,
|
||||
/// Composition-origin flag. Set by `OperationEnv::invoke()` (true) or the
|
||||
/// `CallAdapter` dispatch path (false) — never by handlers. Module-private
|
||||
/// for writes; read via `is_internal()`. See ADR-015.
|
||||
pub(crate) internal: bool,
|
||||
}
|
||||
|
||||
/// Abort cascade policy for a call's descendants (ADR-016).
|
||||
///
|
||||
/// `AbortDependents` (default): aborting this call cascades to all
|
||||
/// non-terminal descendants.
|
||||
///
|
||||
/// `ContinueRunning` (opt-in): descendants that have already started
|
||||
/// continue to completion; descendants that haven't started are aborted;
|
||||
/// no new descendants start.
|
||||
pub enum AbortPolicy {
|
||||
AbortDependents,
|
||||
ContinueRunning,
|
||||
}
|
||||
|
||||
impl Default for AbortPolicy {
|
||||
fn default() -> Self { Self::AbortDependents }
|
||||
}
|
||||
|
||||
impl OperationContext {
|
||||
pub fn is_internal(&self) -> bool { self.internal }
|
||||
}
|
||||
@@ -195,7 +219,45 @@ The CLI binary (or assembly layer) constructs the registry and passes it to the
|
||||
|
||||
### OperationEnv
|
||||
|
||||
`OperationEnv` is the universal composition mechanism. A handler calls `context.env.invoke("fs", "readFile", input, &context)` and gets a `ResponseEnvelope` back — regardless of whether the operation runs locally, via an irpc service, or on a remote node.
|
||||
The `OperationEnv` trait is the universal composition mechanism. A handler calls `context.env.invoke("fs", "readFile", input, &context)` and gets a `ResponseEnvelope` back — regardless of whether the operation runs locally, via an irpc service, or on a remote node.
|
||||
|
||||
```rust
|
||||
/// The composition dispatch trait. A handler composes child operations
|
||||
/// through its `OperationContext.env` (which implements this trait).
|
||||
///
|
||||
/// This must remain a trait, not a concrete type — session-scoped
|
||||
/// registries (OQ-19) depend on wrapping the global env via trait
|
||||
/// layering. Making `OperationEnv` concrete or hardcoding the global
|
||||
/// registry into the dispatch path would close the session-overlay
|
||||
/// pattern.
|
||||
#[async_trait]
|
||||
pub trait OperationEnv: Send + Sync {
|
||||
/// Compose a child operation. The child's `OperationContext` is
|
||||
/// constructed with `internal: true`, inheriting the parent's
|
||||
/// composition authority as the child's caller identity. The abort
|
||||
/// policy defaults to the parent's (ADR-016 Decision 6).
|
||||
async fn invoke(
|
||||
&self,
|
||||
namespace: &str,
|
||||
operation: &str,
|
||||
input: Value,
|
||||
parent: &OperationContext,
|
||||
) -> ResponseEnvelope;
|
||||
|
||||
/// Compose a child with an explicit abort policy (ADR-016 Decision 6).
|
||||
/// Use `AbortPolicy::ContinueRunning` for long-running work that
|
||||
/// should survive a parent's abort. The default `invoke()` inherits
|
||||
/// the parent's policy; this method overrides it for this child.
|
||||
async fn invoke_with_policy(
|
||||
&self,
|
||||
namespace: &str,
|
||||
operation: &str,
|
||||
input: Value,
|
||||
parent: &OperationContext,
|
||||
policy: AbortPolicy,
|
||||
) -> ResponseEnvelope;
|
||||
}
|
||||
```
|
||||
|
||||
The `parent` parameter propagates the calling context: the nested call gets `parent_request_id: Some(parent.request_id)`, inherits `parent.handler_identity` as the caller identity, and is marked `internal: true`.
|
||||
|
||||
@@ -242,6 +304,9 @@ impl OperationEnv for LocalOperationEnv {
|
||||
metadata: HashMap::new(), // Fresh — does NOT propagate parent metadata (ADR-014)
|
||||
env: registration.scoped_env.clone()
|
||||
.unwrap_or_else(ScopedOperationEnv::empty), // Child's own scoped env (empty for leaves)
|
||||
// Abort policy: inherit the parent's policy by default (ADR-016).
|
||||
// The parent handler can override via `invoke_with_policy()`.
|
||||
abort_policy: parent.abort_policy.clone(),
|
||||
internal: true, // Nested calls use handler authority
|
||||
};
|
||||
self.registry.invoke(&name, input, context).await
|
||||
@@ -283,7 +348,14 @@ These are read-only — no admin operations are exposed through the call protoco
|
||||
}
|
||||
```
|
||||
|
||||
`services/schema` accepts `{ "name": "fs/readFile" }` and returns the full `OperationSpec` including input/output JSON Schemas and declared `error_schemas` (ADR-023). This enables client code generation: a client reading the schema can produce typed error enums instead of generic error handling.
|
||||
`services/schema` accepts `{ "name": "fs/readFile" }` (no leading slash —
|
||||
registry form, same as `OperationSpec.name`) and returns the full
|
||||
`OperationSpec` including input/output JSON Schemas and declared
|
||||
`error_schemas` (ADR-023). The `CallAdapter` normalizes the leading slash
|
||||
from wire `operationId`s before lookup, so `services/schema` accepts both
|
||||
`fs/readFile` and `/fs/readFile`. This enables client code generation: a
|
||||
client reading the schema can produce typed error enums instead of generic
|
||||
error handling.
|
||||
|
||||
### irpc Integration
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-21
|
||||
last_updated: 2026-06-22-21
|
||||
---
|
||||
|
||||
# alknet-core
|
||||
@@ -29,6 +29,7 @@ Core library for ALPN-based protocol dispatch. Every handler crate depends on al
|
||||
| [009](../../decisions/009-one-way-door-decision-framework.md) | One-Way Door Framework | Decision classification |
|
||||
| [010](../../decisions/010-alpn-router-and-endpoint.md) | ALPN Router and Endpoint | Endpoint, HandlerRegistry, accept loop |
|
||||
| [011](../../decisions/011-authcontext-structure.md) | AuthContext Structure | AuthContext fields and resolution flow |
|
||||
| [015](../../decisions/015-privilege-model-and-authority-context.md) | Privilege Model and Authority Context | Per-request identity on OperationContext; admin scope for config reload |
|
||||
|
||||
## Relevant Open Questions
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-21
|
||||
last_updated: 2026-06-22-21
|
||||
---
|
||||
|
||||
# Configuration
|
||||
@@ -115,19 +115,17 @@ pub struct AuthPolicy {
|
||||
/// Stored as strings to avoid russh dependency in core.
|
||||
pub authorized_fingerprints: HashSet<String>,
|
||||
|
||||
/// Certificate authorities for certificate-based auth.
|
||||
/// The exact structure is TBD — it will be defined when alknet-ssh
|
||||
/// is implemented. For now, this is a placeholder that reserves
|
||||
/// the field. alknet-ssh will define `CertAuthorityEntry` with
|
||||
/// the necessary fields (public key, principals, options).
|
||||
pub cert_authorities: Vec<CertAuthorityEntry>,
|
||||
|
||||
/// API keys for token-based auth.
|
||||
pub api_keys: Vec<ApiKeyEntry>,
|
||||
}
|
||||
```
|
||||
|
||||
`CertAuthorityEntry` is a placeholder type. Its fields will be defined when alknet-ssh is implemented and the certificate authority validation requirements are clear. For v1, `cert_authorities` will be an empty vector.
|
||||
Certificate authority entries for cert-based auth will be added when
|
||||
alknet-ssh is implemented. The `cert_authorities` field is omitted from v1
|
||||
to avoid referencing an undefined type. Adding it back is additive (a new
|
||||
field on `AuthPolicy` is non-breaking for existing config files that don't
|
||||
use it). alknet-ssh will define `CertAuthorityEntry` with the necessary
|
||||
fields (public key, principals, options).
|
||||
|
||||
This replaces the reference implementation's `AuthPolicy` which depended on `russh::keys::PublicKey`. The new version stores fingerprints as strings, not russh types. This removes the russh dependency from alknet-core.
|
||||
|
||||
@@ -217,7 +215,7 @@ Simplified from the reference implementation. Removes proxy-specific errors (now
|
||||
|
||||
| Aspect | Reference | New Model |
|
||||
|--------|-----------|-----------|
|
||||
| StaticConfig fields | SSH host key, stealth, transport_mode, listeners, proxy | listen_addr, TLS cert/key, drain_timeout, rate limits |
|
||||
| StaticConfig fields | SSH host key, stealth, transport_mode, listeners, proxy | listen_addr, TLS cert/key, drain_timeout |
|
||||
| DynamicConfig.auth | `HashSet<PublicKey>` (russh types) | `HashSet<String>` (fingerprint strings) |
|
||||
| ListenerConfig | Enum with Stream/Http/Dns variants | Eliminated — single endpoint, ALPN dispatch |
|
||||
| TransportMode | Tcp/Tls/Iroh | Eliminated — always QUIC+TLS |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-16
|
||||
last_updated: 2026-06-22-16
|
||||
---
|
||||
|
||||
# Core Types
|
||||
@@ -57,6 +57,14 @@ pub struct Connection {
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
/// Construct from a quinn connection (feature-gated on quinn).
|
||||
#[cfg(feature = "quinn")]
|
||||
pub fn from_quinn(conn: quinn::Connection) -> Self;
|
||||
|
||||
/// Construct from an iroh connection (feature-gated on iroh).
|
||||
#[cfg(feature = "iroh")]
|
||||
pub fn from_iroh(conn: iroh::Connection) -> Self;
|
||||
|
||||
pub async fn accept_bi(&self) -> Result<(SendStream, RecvStream), StreamError>;
|
||||
pub async fn open_bi(&self) -> Result<(SendStream, RecvStream), StreamError>;
|
||||
pub fn remote_alpn(&self) -> &[u8];
|
||||
@@ -110,7 +118,7 @@ impl AsyncRead for RecvStream { ... }
|
||||
- `RecvStream` implements `AsyncRead`. Read bytes from the peer.
|
||||
- These are concrete wrapper types that use internal enum dispatch to delegate to the appropriate QUIC stream type (quinn or iroh) in production, and to test mocks in tests.
|
||||
|
||||
Since the endpoint supports both quinn and iroh connection sources (ADR-010), streams may come from either. `Connection::new()` wraps the appropriate stream source based on where the connection came from.
|
||||
Since the endpoint supports both quinn and iroh connection sources (ADR-010), streams may come from either. `Connection::from_quinn()` / `Connection::from_iroh()` wrap the appropriate stream source based on where the connection came from.
|
||||
|
||||
## StreamError
|
||||
|
||||
@@ -138,6 +146,39 @@ When a handler encounters a `StreamError` and needs to return from `handle()`, i
|
||||
|
||||
Handlers that manage multiple streams (SSH, call) may catch `StreamError::StreamClosed` per-stream and continue serving other streams on the same connection — only `ConnectionClosed` forces `handle()` to return.
|
||||
|
||||
The mapping is provided as a `From` impl so handlers can use the `?` operator:
|
||||
|
||||
```rust
|
||||
impl From<StreamError> for HandlerError {
|
||||
fn from(e: StreamError) -> Self {
|
||||
match e {
|
||||
StreamError::ConnectionClosed => HandlerError::ConnectionClosed,
|
||||
StreamError::StreamClosed => {
|
||||
HandlerError::StreamError(io::Error::new(
|
||||
io::ErrorKind::ConnectionReset,
|
||||
"stream closed",
|
||||
))
|
||||
}
|
||||
StreamError::Timeout => {
|
||||
HandlerError::StreamError(io::Error::new(
|
||||
io::ErrorKind::TimedOut,
|
||||
"stream timed out",
|
||||
))
|
||||
}
|
||||
StreamError::Internal(e) => HandlerError::StreamError(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This `From` impl is the canonical conversion — handler examples that use
|
||||
`.await?` on `accept_bi()` / `open_bi()` rely on it. The `StreamError` →
|
||||
`HandlerError::StreamError(io::Error)` mapping is lossy by design: the
|
||||
distinction between stream-level and connection-level errors is preserved
|
||||
in `StreamError`, but once a handler propagates via `HandlerError`, the
|
||||
endpoint treats all variants as "close the connection" (one-ALPN-per-
|
||||
connection, ADR-006).
|
||||
|
||||
## Design Decisions
|
||||
|
||||
| Decision | ADR | Summary |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-17
|
||||
last_updated: 2026-06-22-17
|
||||
---
|
||||
|
||||
# Endpoint
|
||||
@@ -124,7 +124,7 @@ fn dispatch(connection) {
|
||||
match handlers.get(alpn) {
|
||||
Some(handler) => {
|
||||
let auth = AuthContext::from_connection(&connection);
|
||||
let conn = Connection::new(connection);
|
||||
let conn = Connection::from_quinn(connection); // or from_iroh
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = handler.handle(conn, &auth).await {
|
||||
// log error, connection closes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-19
|
||||
last_updated: 2026-06-22-19
|
||||
---
|
||||
|
||||
# alknet-vault
|
||||
@@ -38,8 +38,10 @@ cross the network.
|
||||
| ADR | Title | Relevance |
|
||||
|-----|-------|-----------|
|
||||
| [003](../../decisions/003-crate-decomposition.md) | Crate Decomposition | alknet-vault's standalone position |
|
||||
| [006](../../decisions/006-alpn-convention-and-connection-model.md) | ALPN String Convention | ALPN versioning pattern for potential `alknet/vault/v2` |
|
||||
| [005](../../decisions/005-irpc-as-call-protocol-foundation.md) | irpc as Call Protocol Foundation | VaultProtocol uses irpc directly |
|
||||
| [008](../../decisions/008-secret-service-integration.md) | Vault Integration Point | CLI-embedded, capability source |
|
||||
| [010](../../decisions/010-alpn-router-and-endpoint.md) | ALPN Router and Endpoint | Ed25519 as default curve for TLS raw key identity |
|
||||
| [014](../../decisions/014-secret-material-flow-and-capability-injection.md) | Secret Material Flow and Capability Injection | Capabilities carry vault-derived material |
|
||||
| [018](../../decisions/018-vault-standalone-crate.md) | Vault as Standalone Crate | Zero alknet crate dependencies |
|
||||
| [019](../../decisions/019-vault-assembly-layer-only.md) | Vault Assembly-Layer-Only Access | The assembly layer is the sole caller |
|
||||
@@ -101,6 +103,21 @@ the full list.
|
||||
depth measure, not the primary control — the primary control is that
|
||||
`DerivedKey` never crosses the call protocol wire (ADR-014).
|
||||
|
||||
## Known Source Drift
|
||||
|
||||
The vault crate carries over source from the POC. The following items are
|
||||
known divergences between the current source and the spec. All must be
|
||||
corrected during implementation sync. This table is the single source of
|
||||
truth for drift tracking — if an item is fixed in source, update this table.
|
||||
|
||||
| # | Item | Current source behavior | Target behavior (per spec) | Source location | Spec reference |
|
||||
|---|------|------------------------|-----------------------------|-----------------|----------------|
|
||||
| 1 | IV generation | `rand::random()` | `OsRng` (CSPRNG) | `encryption.rs` L133 | [encryption.md → Security Constraints](encryption.md#security-constraints), [service.md → Security Constraints](service.md#security-constraints) |
|
||||
| 2 | RwLock `unwrap()` | `unwrap()` on every `RwLock` acquisition (L142, 161, 182, 191, 196, 227, 264, 307, 340, 367) | `unwrap_or_else(\|e\| e.into_inner())` for poisoned lock recovery | `service.rs` (see line numbers) | [service.md → Security Constraints](service.md#security-constraints) |
|
||||
| 3 | `CURRENT_KEY_VERSION` | `1` (HD-derived, but v1 is reserved for TS PBKDF2 legacy per ADR-020) | `2` (HD-derived, per ADR-020) | `encryption.rs` | [encryption.md → Key Versioning](encryption.md#key-versioning), [ADR-020](../../decisions/020-hd-derivation-for-encryption-keys.md) |
|
||||
| 4 | `spawn()` return value | Returns a fresh, unspawned `VaultServiceActor` as the second tuple element (the spawned actor is consumed by `run`) | Either drop the second return value (return only `Client<VaultProtocol>`) or restructure so the returned actor is the one that was spawned | `service.rs` `VaultServiceActor::spawn()` | [service.md → Actor Dispatch](service.md#actor-dispatch) |
|
||||
| 5 | `HashMap::clear` zeroization | `KeyCache::clear()` removes entries and relies on `CachedKey`'s `Drop` impl for zeroization | Verify `HashMap::clear()` actually drops values (it does, but worth a test) | `cache.rs` | [service.md → Security Constraints](service.md#security-constraints) |
|
||||
|
||||
## Public API
|
||||
|
||||
The vault re-exports its primary types from the crate root:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-19
|
||||
last_updated: 2026-06-22-19
|
||||
---
|
||||
|
||||
# Mnemonic and Key Derivation
|
||||
@@ -201,6 +201,7 @@ Helper functions construct parameterized paths:
|
||||
```rust
|
||||
pub fn device_path(index: u32) -> String; // m/74'/0'/0'/{index}'
|
||||
pub fn site_password_path(site_hash: &str) -> String; // m/74'/1'/0'/{site_hash}'
|
||||
pub fn encryption_path_for_version(version: u32) -> String; // m/74'/2'/0'/{version-2}'
|
||||
```
|
||||
|
||||
### Path semantics
|
||||
@@ -214,14 +215,6 @@ pub fn site_password_path(site_hash: &str) -> String; // m/74'/1'/0'/{site_hash}
|
||||
| `m/74'/2'/0'/0'` | Encryption key for external credentials | AES-256-GCM | Credential encryption (v2, see [encryption.md](encryption.md)) |
|
||||
| `m/44'/60'/0'/0/0` | Ethereum signing key | secp256k1 | Ethereum signing (feature-gated) |
|
||||
|
||||
Helper functions construct parameterized paths:
|
||||
|
||||
```rust
|
||||
pub fn device_path(index: u32) -> String; // m/74'/0'/0'/{index}'
|
||||
pub fn site_password_path(site_hash: &str) -> String; // m/74'/1'/0'/{site_hash}'
|
||||
pub fn encryption_path_for_version(version: u32) -> String; // m/74'/2'/0'/{version-2}'
|
||||
```
|
||||
|
||||
`encryption_path_for_version` maps a key version to its derivation path
|
||||
(ADR-021). v2 (current) maps to `m/74'/2'/0'/0'` (which is `PATHS::ENCRYPTION`);
|
||||
v3 maps to `m/74'/2'/0'/1'`; etc. This is the rotation mechanism — each
|
||||
|
||||
Reference in New Issue
Block a user