From 196ee79eb06b2c21b2eb68fec1e81fe2b667482b Mon Sep 17 00:00:00 2001 From: yoshiyuki Date: Thu, 4 Jun 2026 20:33:26 +0800 Subject: [PATCH 1/2] Reserve generation suffix for child keys --- .../src/handlers/agent/claim.rs | 11 ++- .../src/handlers/agent/poll.rs | 6 +- .../tests/agent_bootstrap_flow.rs | 5 +- crates/agentkeys-core/src/actor_omni.rs | 88 ++++++++++++++++++- docs/arch.md | 28 +++--- docs/wiki/blockchain-tee-architecture.md | 2 +- 6 files changed, 116 insertions(+), 24 deletions(-) diff --git a/crates/agentkeys-broker-server/src/handlers/agent/claim.rs b/crates/agentkeys-broker-server/src/handlers/agent/claim.rs index 5ccbe3a7..84b2ad3c 100644 --- a/crates/agentkeys-broker-server/src/handlers/agent/claim.rs +++ b/crates/agentkeys-broker-server/src/handlers/agent/claim.rs @@ -6,7 +6,8 @@ //! named a master, so an unclaimed request is inert (Sybil-safe). On claim the //! broker: //! -//! 1. derives the HDKD child omni `O_agent = SHA256(HDKD_DOMAIN || O_master || "//label")` +//! 1. derives the HDKD child omni for the initial generation path +//! `//label/0`; //! — the master "adopts" the agent under its own omni tree; //! 2. assigns `operator_omni` + `child_omni` + `label` + `requested_scope` onto //! the (previously unbound) row, marking it claimed; @@ -52,8 +53,12 @@ pub async fn pairing_claim( agentkeys_core::actor_omni::validate_label(&body.label) .map_err(|e| BrokerError::BadRequest(format!("invalid label: {e}")))?; - let child_omni = agentkeys_core::actor_omni::child_omni_hex(&master_omni, &body.label) - .map_err(|e| BrokerError::BadRequest(format!("derive child omni: {e}")))?; + let child_omni = agentkeys_core::actor_omni::child_omni_generation_hex( + &master_omni, + &body.label, + agentkeys_core::actor_omni::INITIAL_CHILD_GENERATION, + ) + .map_err(|e| BrokerError::BadRequest(format!("derive child omni: {e}")))?; let requested_scope = body .requested_scope diff --git a/crates/agentkeys-broker-server/src/handlers/agent/poll.rs b/crates/agentkeys-broker-server/src/handlers/agent/poll.rs index 78c23b86..473faf44 100644 --- a/crates/agentkeys-broker-server/src/handlers/agent/poll.rs +++ b/crates/agentkeys-broker-server/src/handlers/agent/poll.rs @@ -96,7 +96,11 @@ pub async fn pairing_poll( // 3. Mint J1_agent fresh (HDKD omni + lineage). The agent authenticates with // this immediately, but has NO scope until the master approves the binding. - let derivation_path = format!("//{label}"); + let derivation_path = agentkeys_core::actor_omni::generation_derivation_path( + &label, + agentkeys_core::actor_omni::INITIAL_CHILD_GENERATION, + ) + .map_err(|e| BrokerError::Internal(format!("derive J1_agent path: {e}")))?; let session_jwt = mint_agent_session_jwt( &state.session_keypair, &state.config.oidc_issuer, diff --git a/crates/agentkeys-broker-server/tests/agent_bootstrap_flow.rs b/crates/agentkeys-broker-server/tests/agent_bootstrap_flow.rs index 651209b3..2e471143 100644 --- a/crates/agentkeys-broker-server/tests/agent_bootstrap_flow.rs +++ b/crates/agentkeys-broker-server/tests/agent_bootstrap_flow.rs @@ -244,7 +244,7 @@ async fn full_request_claim_poll_pending_flow() { // Public recomputability (acceptance criterion). assert_eq!( child_omni, - agentkeys_core::actor_omni::child_omni_hex(&master_omni, "agent-a").unwrap() + agentkeys_core::actor_omni::child_omni_generation_hex(&master_omni, "agent-a", 0).unwrap() ); assert_eq!(claim["operator_omni"], master_omni); assert_eq!(claim["request_id"], request_id); @@ -277,8 +277,9 @@ async fn full_request_claim_poll_pending_flow() { ); assert_eq!( claims.agentkeys.derivation_path.as_deref(), - Some("//agent-a") + Some("//agent-a/0") ); + assert_eq!(claimed_poll["derivation_path"], "//agent-a/0"); assert_eq!( claims.agentkeys.device_pubkey.as_deref(), Some(dk.address()) diff --git a/crates/agentkeys-core/src/actor_omni.rs b/crates/agentkeys-core/src/actor_omni.rs index 8f83bcc3..6c2e7f8d 100644 --- a/crates/agentkeys-core/src/actor_omni.rs +++ b/crates/agentkeys-core/src/actor_omni.rs @@ -66,9 +66,13 @@ pub fn actor_omni_hex(wallet: &WalletAddress) -> String { /// Distinct from `DOMAIN` so a wallet-omni and a child-omni can never collide. const HDKD_DOMAIN: &[u8] = b"agentkeys-hdkd-v1"; -/// Validate an HDKD child label (`^[a-z0-9-]{1,32}$`). The label is spliced into -/// the child-omni digest AND stored/echoed on chain + in JWT claims, so it must -/// be a tight charset (no path separators, no whitespace, no uppercase). +/// Initial child-key generation. The first pair flow always reserves the +/// generation suffix by deriving at `