From 56752ebfc08e07266633bb23ef12908337b324e3 Mon Sep 17 00:00:00 2001 From: archipelago Date: Wed, 17 Jun 2026 06:03:25 -0400 Subject: [PATCH] fix(identity): Node npub in Web5 Identities matches Settings (#49) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Settings shows the node-level Nostr key (HKDF derive_node_nostr_key, read via node.nostr-pubkey) while Web5 > Identities showed the identity record's own key — the mirrored "Node" identity stores nostr=None and seed identities use a different BIP-32 NIP-06 key, so the two surfaces disagreed. Resolve the node-level Nostr key once in identity.list and override it onto whichever identity record is the node's own (ed25519 == server_info .pubkey). Display-only — no stored key is rewritten, so it self-applies to existing nodes with no migration and the discovery identity is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/api/rpc/identity/handlers.rs | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/core/archipelago/src/api/rpc/identity/handlers.rs b/core/archipelago/src/api/rpc/identity/handlers.rs index 0fcb62ba..6df602ea 100644 --- a/core/archipelago/src/api/rpc/identity/handlers.rs +++ b/core/archipelago/src/api/rpc/identity/handlers.rs @@ -14,10 +14,39 @@ impl RpcHandler { let manager = IdentityManager::new(&self.config.data_dir).await?; let (identities, default_id) = manager.list().await?; + // #49: The canonical node Nostr key is the node-level HKDF key + // (`derive_node_nostr_key`) that Settings and Nostr discovery both use + // via `node.nostr-pubkey`. The mirrored "Node" identity stores + // nostr=None, and seed identities use a different BIP-32 NIP-06 key, so + // the "Node" entry in Web5 > Identities disagreed with Settings. Resolve + // the node-level key once and override it onto whichever identity record + // is the node's own (its ed25519 matches `server_info.pubkey`), so both + // surfaces always show the same npub. Display-only — no key is rewritten. + let identity_dir = self.config.data_dir.join("identity"); + let node_nostr_hex = crate::nostr_discovery::get_nostr_pubkey(&identity_dir) + .await + .ok(); + let node_nostr_npub = node_nostr_hex.as_ref().and_then(|h| { + nostr_sdk::PublicKey::from_hex(h) + .ok() + .and_then(|pk| pk.to_bech32().ok()) + }); + let (snapshot, _) = self.state_manager.get_snapshot().await; + let node_pubkey_hex = snapshot.server_info.pubkey.clone(); + let items: Vec = identities .into_iter() .map(|id| { let is_default = default_id.as_deref() == Some(&id.id); + let is_node = !node_pubkey_hex.is_empty() && id.pubkey_hex == node_pubkey_hex; + let (nostr_pubkey, nostr_npub) = if is_node { + ( + node_nostr_hex.clone().or(id.nostr_pubkey), + node_nostr_npub.clone().or(id.nostr_npub), + ) + } else { + (id.nostr_pubkey, id.nostr_npub) + }; serde_json::json!({ "id": id.id, "name": id.name, @@ -26,8 +55,8 @@ impl RpcHandler { "did": id.did, "created_at": id.created_at, "is_default": is_default, - "nostr_pubkey": id.nostr_pubkey, - "nostr_npub": id.nostr_npub, + "nostr_pubkey": nostr_pubkey, + "nostr_npub": nostr_npub, "profile": id.profile, }) })