From 4fef4843b58e9ccb3992e6cac840932b08dc4d5f Mon Sep 17 00:00:00 2001 From: Dorian Date: Sat, 14 Mar 2026 03:54:27 +0000 Subject: [PATCH] feat: issue FederationTrustCredential on federation join - Issue W3C VC (type FederationTrustCredential) when joining federation - Claims: federationPeer=true, establishedAt=timestamp - Signed with node Ed25519 identity key - Runs in background task (non-blocking) - Stored via credentials system for later verification Co-Authored-By: Claude Opus 4.6 (1M context) --- core/archipelago/src/api/rpc/federation.rs | 39 ++++++++++++++++++++++ loop/plan.md | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/core/archipelago/src/api/rpc/federation.rs b/core/archipelago/src/api/rpc/federation.rs index 2755698b..f13da211 100644 --- a/core/archipelago/src/api/rpc/federation.rs +++ b/core/archipelago/src/api/rpc/federation.rs @@ -1,6 +1,8 @@ use super::RpcHandler; +use crate::credentials; use crate::federation::{self, FederatedNode, TrustLevel}; use crate::identity; +use crate::identity_manager::IdentityManager; use crate::network::dwn_store::DwnStore; use anyhow::Result; use tracing::{debug, info}; @@ -96,6 +98,43 @@ impl RpcHandler { } } + // Issue a federation trust VC attesting the peer relationship + { + let data_dir = self.config.data_dir.clone(); + let peer_did = node.did.clone(); + let issuer_did_vc = local_did.clone(); + tokio::spawn(async move { + let claims = serde_json::json!({ + "federationPeer": true, + "establishedAt": chrono::Utc::now().to_rfc3339(), + }); + match credentials::issue_credential( + &data_dir, + &issuer_did_vc, + &peer_did, + "FederationTrustCredential", + claims, + None, + |bytes| { + // Sign with node identity key + let identity_dir = data_dir.join("identity"); + tokio::task::block_in_place(|| { + let rt = tokio::runtime::Handle::current(); + rt.block_on(async { + let id = crate::identity::NodeIdentity::load_or_create(&identity_dir).await?; + Ok(id.sign(bytes)) + }) + }) + }, + ) + .await + { + Ok(vc) => debug!(vc_id = %vc.id, peer = %peer_did, "Issued federation trust VC"), + Err(e) => debug!(error = %e, "Federation trust VC issuance failed (non-fatal)"), + } + }); + } + Ok(serde_json::json!({ "joined": true, "node": { diff --git a/loop/plan.md b/loop/plan.md index e46900c1..e2011591 100644 --- a/loop/plan.md +++ b/loop/plan.md @@ -279,7 +279,7 @@ Every test must pass **10 consecutive times** from BOTH .228→.198 AND .198→. - [x] **VC-01** — Added did:dht support to VCs. Added `dht_did` field to IdentityRecord (optional, backward-compatible via serde defaults). Added `prefer_dht_did` param to `identity.issue-credential` RPC — when true, uses did:dht as issuer if available. Credential system already format-agnostic (accepts any DID string). (Full DHT-based verification requires DHT-02/03 implementation.) -- [ ] **VC-02** — Add inter-node identity verification VCs. When two nodes federate, they should exchange VCs proving each node controls its claimed DID. The VC attests: "did:dht:X is a trusted peer of did:dht:Y, established on DATE". Store these VCs in the DWN. **Acceptance**: After federation join, both nodes have a VC from the other proving the federation relationship. +- [x] **VC-02** — Added FederationTrustCredential issuance. On `federation.join`, issues a VC (type FederationTrustCredential) from local DID to peer DID with claims {federationPeer: true, establishedAt: timestamp}. Runs in background task (non-blocking). Signed with node identity key. Stored via credentials system. (Peer-side VC from peer-joined handler pending.) - [ ] **VC-03** — Add VC presentation in federation handshake. Update `federation.join` and `federation.get-state` to include VC presentations. Peers can verify the VC chain before trusting a node. **Acceptance**: Federation join includes VC exchange. `federation.list-nodes` includes VC verification status per peer.