Skip to main content

fips_identity/
peer.rs

1//! Remote peer identity (public key only, no signing capability).
2
3use secp256k1::{Parity, PublicKey, XOnlyPublicKey};
4use std::fmt;
5
6use super::encoding::{decode_npub, encode_npub};
7use super::{FipsAddress, IdentityError, NodeAddr, sha256};
8
9/// A known peer's identity (public key only, no signing capability).
10///
11/// Use this to represent remote peers whose npub you know. For a local
12/// identity with signing capability, use [`Identity`] instead.
13#[derive(Clone, Copy, PartialEq, Eq)]
14pub struct PeerIdentity {
15    pubkey: XOnlyPublicKey,
16    /// Full public key if known (includes parity for ECDH operations).
17    pubkey_full: Option<PublicKey>,
18    node_addr: NodeAddr,
19    address: FipsAddress,
20}
21
22impl PeerIdentity {
23    /// Create a PeerIdentity from an x-only public key.
24    ///
25    /// Note: When only the x-only key is available, the full public key
26    /// will be derived assuming even parity for ECDH operations.
27    ///
28    /// Precomputes the even-parity full pubkey eagerly so `pubkey_full()`
29    /// is a constant-time field load. Without this, every send-side hot path
30    /// packet re-derived the full key, which spends ~6% of CPU on a secp256k1
31    /// EC point parse for what should be a memoized lookup.
32    pub fn from_pubkey(pubkey: XOnlyPublicKey) -> Self {
33        let node_addr = NodeAddr::from_pubkey(&pubkey);
34        let address = FipsAddress::from_node_addr(&node_addr);
35        let pubkey_full = pubkey.public_key(Parity::Even);
36        Self {
37            pubkey,
38            pubkey_full: Some(pubkey_full),
39            node_addr,
40            address,
41        }
42    }
43
44    /// Create a PeerIdentity from a full public key (includes parity).
45    ///
46    /// Use this when you have the complete public key (e.g., from a Noise
47    /// handshake) to preserve parity information for ECDH operations.
48    pub fn from_pubkey_full(pubkey: PublicKey) -> Self {
49        let (x_only, _parity) = pubkey.x_only_public_key();
50        let node_addr = NodeAddr::from_pubkey(&x_only);
51        let address = FipsAddress::from_node_addr(&node_addr);
52        Self {
53            pubkey: x_only,
54            pubkey_full: Some(pubkey),
55            node_addr,
56            address,
57        }
58    }
59
60    /// Create a PeerIdentity from a bech32-encoded npub string.
61    pub fn from_npub(npub: &str) -> Result<Self, IdentityError> {
62        let pubkey = decode_npub(npub)?;
63        Ok(Self::from_pubkey(pubkey))
64    }
65
66    /// Return the x-only public key.
67    pub fn pubkey(&self) -> XOnlyPublicKey {
68        self.pubkey
69    }
70
71    /// Return the full public key for ECDH operations.
72    ///
73    /// If the full key was provided during construction, it is returned.
74    /// Otherwise, the key is derived from the x-only key assuming even parity.
75    pub fn pubkey_full(&self) -> PublicKey {
76        self.pubkey_full.unwrap_or_else(|| {
77            // Derive full key assuming even parity
78            self.pubkey.public_key(Parity::Even)
79        })
80    }
81
82    /// Return the public key as a bech32-encoded npub string (NIP-19).
83    pub fn npub(&self) -> String {
84        encode_npub(&self.pubkey)
85    }
86
87    /// Return a shortened npub for log display (e.g., `npub1abcd...wxyz`).
88    pub fn short_npub(&self) -> String {
89        let full = self.npub();
90        let data = &full[5..]; // strip "npub1"
91        format!("npub1{}...{}", &data[..4], &data[data.len() - 4..])
92    }
93
94    /// Return the node ID.
95    pub fn node_addr(&self) -> &NodeAddr {
96        &self.node_addr
97    }
98
99    /// Return the FIPS address.
100    pub fn address(&self) -> &FipsAddress {
101        &self.address
102    }
103
104    /// Verify a signature from this peer.
105    pub fn verify(&self, data: &[u8], signature: &secp256k1::schnorr::Signature) -> bool {
106        let digest = sha256(data);
107        super::SECP
108            .verify_schnorr(signature, &digest, &self.pubkey)
109            .is_ok()
110    }
111}
112
113impl fmt::Debug for PeerIdentity {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        f.debug_struct("PeerIdentity")
116            .field("node_addr", &self.node_addr)
117            .field("address", &self.address)
118            .finish()
119    }
120}
121
122impl fmt::Display for PeerIdentity {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        write!(f, "{}", self.npub())
125    }
126}