Skip to main content

fips_identity/
peer.rs

1//! Remote peer identity (public key only, no signing capability).
2
3use secp256k1::{Parity, PublicKey, Secp256k1, 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    /// caller (e.g. `send_endpoint_data` per packet) re-derived the full
31    /// key, which spends ~6% of CPU on a secp256k1 EC point parse for what
32    /// should be a memoized lookup.
33    pub fn from_pubkey(pubkey: XOnlyPublicKey) -> Self {
34        let node_addr = NodeAddr::from_pubkey(&pubkey);
35        let address = FipsAddress::from_node_addr(&node_addr);
36        let pubkey_full = pubkey.public_key(Parity::Even);
37        Self {
38            pubkey,
39            pubkey_full: Some(pubkey_full),
40            node_addr,
41            address,
42        }
43    }
44
45    /// Create a PeerIdentity from a full public key (includes parity).
46    ///
47    /// Use this when you have the complete public key (e.g., from a Noise
48    /// handshake) to preserve parity information for ECDH operations.
49    pub fn from_pubkey_full(pubkey: PublicKey) -> Self {
50        let (x_only, _parity) = pubkey.x_only_public_key();
51        let node_addr = NodeAddr::from_pubkey(&x_only);
52        let address = FipsAddress::from_node_addr(&node_addr);
53        Self {
54            pubkey: x_only,
55            pubkey_full: Some(pubkey),
56            node_addr,
57            address,
58        }
59    }
60
61    /// Create a PeerIdentity from a bech32-encoded npub string.
62    pub fn from_npub(npub: &str) -> Result<Self, IdentityError> {
63        let pubkey = decode_npub(npub)?;
64        Ok(Self::from_pubkey(pubkey))
65    }
66
67    /// Return the x-only public key.
68    pub fn pubkey(&self) -> XOnlyPublicKey {
69        self.pubkey
70    }
71
72    /// Return the full public key for ECDH operations.
73    ///
74    /// If the full key was provided during construction, it is returned.
75    /// Otherwise, the key is derived from the x-only key assuming even parity.
76    pub fn pubkey_full(&self) -> PublicKey {
77        self.pubkey_full.unwrap_or_else(|| {
78            // Derive full key assuming even parity
79            self.pubkey.public_key(Parity::Even)
80        })
81    }
82
83    /// Return the public key as a bech32-encoded npub string (NIP-19).
84    pub fn npub(&self) -> String {
85        encode_npub(&self.pubkey)
86    }
87
88    /// Return a shortened npub for log display (e.g., `npub1abcd...wxyz`).
89    pub fn short_npub(&self) -> String {
90        let full = self.npub();
91        let data = &full[5..]; // strip "npub1"
92        format!("npub1{}...{}", &data[..4], &data[data.len() - 4..])
93    }
94
95    /// Return the node ID.
96    pub fn node_addr(&self) -> &NodeAddr {
97        &self.node_addr
98    }
99
100    /// Return the FIPS address.
101    pub fn address(&self) -> &FipsAddress {
102        &self.address
103    }
104
105    /// Verify a signature from this peer.
106    pub fn verify(&self, data: &[u8], signature: &secp256k1::schnorr::Signature) -> bool {
107        let secp = Secp256k1::new();
108        let digest = sha256(data);
109        secp.verify_schnorr(signature, &digest, &self.pubkey)
110            .is_ok()
111    }
112}
113
114impl fmt::Debug for PeerIdentity {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        f.debug_struct("PeerIdentity")
117            .field("node_addr", &self.node_addr)
118            .field("address", &self.address)
119            .finish()
120    }
121}
122
123impl fmt::Display for PeerIdentity {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        write!(f, "{}", self.npub())
126    }
127}