From b5c59ffac82a8d02a342e1df83b7d3eea03f0cae Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Mon, 20 Jul 2026 15:55:11 -0700 Subject: [PATCH 1/2] chore(node): stub W1b-6 seam key_mgmt trait carve (#1285) Co-Authored-By: Claude From ac93ae6bca180bc6423b42e6e0b749b073e7d764 Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Mon, 20 Jul 2026 16:21:01 -0700 Subject: [PATCH 2/2] =?UTF-8?q?refactor(node):=20W1b-6=20=E2=80=94=20seam?= =?UTF-8?q?=20key=5Fmgmt,=20KeyManager=20trait/handle=20carve=20(#1285,=20?= =?UTF-8?q?#1298,=20#1303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LAST W1b seam carve (before W1c). Carves the node's MACHINE identity_seed/ NodeCert lifecycle Node methods (peer_id_hex, identity_seed_for_peer, peer_cert_dir, node_cert_dir) into the KeyManager trait (seam 7's public surface), implemented by Node with the EXISTING method bodies moved unchanged from lib.rs. Structural carve only — the dig-keystore crate adoption (#1024) is a later wave, coordinated with the key-management family, out of scope here. #908 boundary preserved exactly: KeyManager holds ONLY the node's own machine identity_seed/NodeCert lifecycle — no user DID/wallet key path added or touched. The module doc states this invariant explicitly so it stays visible at the seam's public surface, not just in this commit message. The only caller (peer.rs's peer-network bring-up, which reads identity_seed_for_peer/peer_cert_dir/node_cert_dir to mint/load the node's mTLS NodeCert) now brings KeyManager into scope via `use crate::KeyManager` — the only change it needed. No external crate calls these methods directly. No signature or behaviour changed anywhere. Depends on W1b-5 (merged, #1285). Next: W1c (the composition-root split). Co-Authored-By: Claude --- Cargo.lock | 4 +- Cargo.toml | 2 +- crates/dig-node-core/Cargo.toml | 2 +- crates/dig-node-core/src/lib.rs | 37 ++--------- crates/dig-node-core/src/peer.rs | 2 +- .../src/seams/key_mgmt/key_manager.rs | 66 +++++++++++++++++++ .../dig-node-core/src/seams/key_mgmt/mod.rs | 11 ++++ crates/dig-node-core/src/seams/mod.rs | 1 + 8 files changed, 87 insertions(+), 38 deletions(-) create mode 100644 crates/dig-node-core/src/seams/key_mgmt/key_manager.rs create mode 100644 crates/dig-node-core/src/seams/key_mgmt/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 9d7882e..78e3ab9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2067,7 +2067,7 @@ dependencies = [ [[package]] name = "dig-node-core" -version = "0.13.7" +version = "0.13.8" dependencies = [ "async-trait", "axum", @@ -2115,7 +2115,7 @@ dependencies = [ [[package]] name = "dig-node-service" -version = "0.47.7" +version = "0.47.8" dependencies = [ "axum", "axum-server", diff --git a/Cargo.toml b/Cargo.toml index a62f4be..8879025 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ edition = "2021" # the ROOT manifest (`[workspace.package].version`), so it MUST be set here for a # release to fire (§3.6). The library crates (dig-node-core/dig-runtime/dig-wallet) # keep their own independent versions — only the released binary tracks the workspace version. -version = "0.47.7" +version = "0.47.8" # Release hardening, matching digstore: keep integer-overflow checks ON in release. # The node parses untrusted serialized input and does offset/length arithmetic over diff --git a/crates/dig-node-core/Cargo.toml b/crates/dig-node-core/Cargo.toml index 78bad06..f482151 100644 --- a/crates/dig-node-core/Cargo.toml +++ b/crates/dig-node-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dig-node-core" -version = "0.13.7" +version = "0.13.8" edition = "2021" license = "GPL-2.0-only" description = "The canonical DIG node ENGINE library (crate `dig_node_core`): the JSON-RPC dispatch (`handle_rpc`, the same contract as rpc.dig.net), local-first content serve/fetch/redirect from LOCAL .dig store modules (via digstore_host::serve_blind), chain-anchored-root resolution, chain-watch + subscriptions + generation gap-fill, the LRU cache, and the full P2P stack. Shared UNCHANGED by both host shells: the `dig-node` OS-service binary (dig-node-service) and the DIG Browser's in-process cdylib (dig-runtime). Native Rust so the compiled-module serve path works." diff --git a/crates/dig-node-core/src/lib.rs b/crates/dig-node-core/src/lib.rs index 27bbb48..51163dc 100644 --- a/crates/dig-node-core/src/lib.rs +++ b/crates/dig-node-core/src/lib.rs @@ -75,6 +75,10 @@ pub use seams::dig_peer::{address_book, dht, net, pex, session, PeerNetwork}; /// `handle_rpc`/`handle_rpc_json` free functions delegate to it; most callers keep using those /// stable entry points and never need this trait in scope directly. pub use seams::dig_rpc::RpcDispatch; +/// The `KeyManager` trait is seam 7's public surface (#1285 W1b-6) — bring it into scope to call +/// `peer_id_hex`/`identity_seed_for_peer`/`peer_cert_dir`/`node_cert_dir` on a `Node`. #908 +/// boundary: this seam holds ONLY the node's machine identity — never a user key. +pub use seams::key_mgmt::KeyManager; /// Cross-seam shared vocabulary (#1285 W1a) — the ONLY types the node's seams (peer, wallet, rpc, /// local-content, capsule, chain, key-management) are allowed to share; see the module doc. pub mod shared; @@ -1724,17 +1728,6 @@ impl Node { // learns what this node already holds. Every byte a peer fetches carries its own merkle proof // (verified by the caller against the chain-anchored root), so the node is never the trust anchor. - /// The node's own `peer_id` (64-hex) = SHA-256(SPKI DER) of its PERSISTENT, CA-signed - /// [`NodeCert`](dig_nat::NodeCert), or `None` if no identity seed is configured. This is the mTLS - /// identity the node presents on every peer path (loaded from — or minted into — the node's cert - /// dir, so it is stable across restarts; see [`peer::load_or_generate_node_cert`]). - pub fn peer_id_hex(&self) -> Option { - let seed = self.identity_seed?; - peer::load_or_generate_node_cert(self.node_cert_dir(), &seed) - .ok() - .map(|cert| cert.peer_id().to_hex()) - } - /// `dig.getAvailability` — answer one queried item against the local inventory, enriching the /// pure presence answer (`peer::availability_presence`) with the per-resource `total_length` + /// `chunk_count` when the item is at resource granularity (`store_id` + `root` + `retrieval_key`) @@ -2143,28 +2136,6 @@ impl Node { }) } - /// The node's persistent identity seed, if configured — the source of the STABLE mTLS `peer_id` - /// for the L7 peer network (see [`peer::load_or_generate_node_cert`]). `None` disables the peer network - /// (the node still serves the HTTP read path). - pub fn identity_seed_for_peer(&self) -> Option<[u8; 32]> { - self.identity_seed - } - - /// The directory the L7 peer network keeps its TLS cert/key + peer address book under (a - /// `peer-net/` subdir of the cache dir, so it shares the node's data root + writability handling). - pub fn peer_cert_dir(&self) -> PathBuf { - self.cache_dir.join("peer-net") - } - - /// The directory the node's PERSISTENT, CA-signed [`NodeCert`](dig_nat::NodeCert) identity - /// (`node.crt` + `node.key`, 0600) lives under — an `identity/` subdir of [`Self::peer_cert_dir`], - /// kept SEPARATE from dig-gossip's own `node.key` in `peer-net/` so the two never clobber each - /// other. This is the node's stable machine transport identity (#908, #1280); its `peer_id` - /// survives restarts because the cert is loaded back from here. - pub fn node_cert_dir(&self) -> PathBuf { - self.peer_cert_dir().join("identity") - } - /// The node's cache dir root — the data root the P2P content engine's download staging /// (`/downloads`) + `.download.tmp` GC live under (shares the node's writability handling). pub fn cache_dir_path(&self) -> &Path { diff --git a/crates/dig-node-core/src/peer.rs b/crates/dig-node-core/src/peer.rs index 5e88406..4a010a7 100644 --- a/crates/dig-node-core/src/peer.rs +++ b/crates/dig-node-core/src/peer.rs @@ -61,7 +61,7 @@ use std::sync::Arc; use serde_json::{json, Value}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use crate::{CachedCapsule, CapsuleStore, PeerNetwork}; +use crate::{CachedCapsule, CapsuleStore, KeyManager, PeerNetwork}; // -- Constants --------------------------------------------------------------------------------------- diff --git a/crates/dig-node-core/src/seams/key_mgmt/key_manager.rs b/crates/dig-node-core/src/seams/key_mgmt/key_manager.rs new file mode 100644 index 0000000..e5b4de2 --- /dev/null +++ b/crates/dig-node-core/src/seams/key_mgmt/key_manager.rs @@ -0,0 +1,66 @@ +//! Seam 7's public surface (#1285/#1303) — the node's MACHINE-key lifecycle: the persistent +//! §21.9 identity seed, the derived stable mTLS `peer_id`, and the on-disk cert directories the +//! L7 peer network's [`dig_nat::NodeCert`] lives under. +//! +//! **#908 boundary (foundational, preserved exactly by this carve):** this seam holds ONLY the +//! node's own MACHINE identity — never a user's DID/wallet signing key. A dig-app proves +//! possession of a profile's identity key over the IPC session (`crate::session`); that key never +//! crosses into the engine. `KeyManager` does not add, and must never grow, a user-key path. +//! +//! `KeyManager` is implemented by [`Node`] with the EXISTING method bodies (carved unchanged +//! from `lib.rs`, #1285 W1b-6) — a behaviour-preserving trait extraction, not a new +//! implementation, and NOT a `dig-keystore` crate adoption (a later wave, coordinated with the +//! key-management family). Plain `Send + Sync` (no async methods) but kept trait-shaped to match +//! the other seams' pattern, so it stays dyn-compatible for the future `Arc` +//! handle (W1c). + +use std::path::PathBuf; + +use crate::Node; + +/// Seam 7 (key management) — the node's machine identity_seed/NodeCert lifecycle. See the module +/// doc for the #908 boundary this seam enforces. +pub trait KeyManager: Send + Sync { + /// The node's own `peer_id` (64-hex) = SHA-256(SPKI DER) of its PERSISTENT, CA-signed + /// [`NodeCert`](dig_nat::NodeCert), or `None` if no identity seed is configured. This is the mTLS + /// identity the node presents on every peer path (loaded from — or minted into — the node's cert + /// dir, so it is stable across restarts; see [`crate::peer::load_or_generate_node_cert`]). + fn peer_id_hex(&self) -> Option; + + /// The node's persistent identity seed, if configured — the source of the STABLE mTLS `peer_id` + /// for the L7 peer network (see [`crate::peer::load_or_generate_node_cert`]). `None` disables the + /// peer network (the node still serves the HTTP read path). + fn identity_seed_for_peer(&self) -> Option<[u8; 32]>; + + /// The directory the L7 peer network keeps its TLS cert/key + peer address book under (a + /// `peer-net/` subdir of the cache dir, so it shares the node's data root + writability handling). + fn peer_cert_dir(&self) -> PathBuf; + + /// The directory the node's PERSISTENT, CA-signed [`NodeCert`](dig_nat::NodeCert) identity + /// (`node.crt` + `node.key`, 0600) lives under — an `identity/` subdir of [`Self::peer_cert_dir`], + /// kept SEPARATE from dig-gossip's own `node.key` in `peer-net/` so the two never clobber each + /// other. This is the node's stable machine transport identity (#908, #1280); its `peer_id` + /// survives restarts because the cert is loaded back from here. + fn node_cert_dir(&self) -> PathBuf; +} + +impl KeyManager for Node { + fn peer_id_hex(&self) -> Option { + let seed = self.identity_seed?; + crate::peer::load_or_generate_node_cert(self.node_cert_dir(), &seed) + .ok() + .map(|cert| cert.peer_id().to_hex()) + } + + fn identity_seed_for_peer(&self) -> Option<[u8; 32]> { + self.identity_seed + } + + fn peer_cert_dir(&self) -> PathBuf { + self.cache_dir.join("peer-net") + } + + fn node_cert_dir(&self) -> PathBuf { + self.peer_cert_dir().join("identity") + } +} diff --git a/crates/dig-node-core/src/seams/key_mgmt/mod.rs b/crates/dig-node-core/src/seams/key_mgmt/mod.rs new file mode 100644 index 0000000..ba04fe7 --- /dev/null +++ b/crates/dig-node-core/src/seams/key_mgmt/mod.rs @@ -0,0 +1,11 @@ +//! Seam 7 — key management (#1285/#1303). Houses the [`KeyManager`] trait (the seam's public +//! surface — the node's MACHINE identity_seed/NodeCert lifecycle, carved unchanged from +//! `lib.rs`, #1285 W1b-6). The `dig-keystore` crate ADOPTION (the canonical keystore, #1024) is +//! a later wave, coordinated with the key-management family — out of scope here. +//! +//! **#908 boundary:** this seam is the machine-key/user-key boundary point. It NEVER holds a +//! user's DID/wallet signing key — see the module doc on [`key_manager::KeyManager`]. + +mod key_manager; + +pub use key_manager::KeyManager; diff --git a/crates/dig-node-core/src/seams/mod.rs b/crates/dig-node-core/src/seams/mod.rs index cbe25fb..99331e0 100644 --- a/crates/dig-node-core/src/seams/mod.rs +++ b/crates/dig-node-core/src/seams/mod.rs @@ -9,3 +9,4 @@ pub mod chia_peer; pub mod content; pub mod dig_peer; pub mod dig_rpc; +pub mod key_mgmt;