Skip to main content

fips_identity/
local.rs

1//! Local node identity with signing capability.
2
3use secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey};
4use std::fmt;
5
6use super::auth::{AuthResponse, auth_challenge_digest};
7use super::encoding::{decode_secret, encode_npub};
8use super::{FipsAddress, IdentityError, NodeAddr, sha256};
9
10/// A FIPS node identity consisting of a keypair and derived identifiers.
11///
12/// The identity holds the secp256k1 keypair and provides methods for signing
13/// and verifying protocol messages.
14pub struct Identity {
15    keypair: Keypair,
16    node_addr: NodeAddr,
17    address: FipsAddress,
18}
19
20impl Identity {
21    /// Create a new random identity.
22    pub fn generate() -> Self {
23        let mut secret_bytes = [0u8; 32];
24        rand::Rng::fill_bytes(&mut rand::rng(), &mut secret_bytes);
25        let secret_key =
26            SecretKey::from_slice(&secret_bytes).expect("32 random bytes is a valid secret key");
27        Self::from_secret_key(secret_key)
28    }
29
30    /// Create an identity from an existing keypair.
31    pub fn from_keypair(keypair: Keypair) -> Self {
32        let (pubkey, _parity) = keypair.x_only_public_key();
33        let node_addr = NodeAddr::from_pubkey(&pubkey);
34        let address = FipsAddress::from_node_addr(&node_addr);
35        Self {
36            keypair,
37            node_addr,
38            address,
39        }
40    }
41
42    /// Create an identity from a secret key.
43    pub fn from_secret_key(secret_key: SecretKey) -> Self {
44        let secp = Secp256k1::new();
45        let keypair = Keypair::from_secret_key(&secp, &secret_key);
46        Self::from_keypair(keypair)
47    }
48
49    /// Create an identity from secret key bytes.
50    pub fn from_secret_bytes(bytes: &[u8; 32]) -> Result<Self, IdentityError> {
51        let secret_key = SecretKey::from_slice(bytes)?;
52        Ok(Self::from_secret_key(secret_key))
53    }
54
55    /// Create an identity from an nsec string (bech32) or hex-encoded secret.
56    pub fn from_secret_str(s: &str) -> Result<Self, IdentityError> {
57        let secret_key = decode_secret(s)?;
58        Ok(Self::from_secret_key(secret_key))
59    }
60
61    /// Return the underlying keypair.
62    ///
63    /// This is needed for cryptographic operations like Noise handshakes.
64    pub fn keypair(&self) -> Keypair {
65        self.keypair
66    }
67
68    /// Return the x-only public key.
69    pub fn pubkey(&self) -> XOnlyPublicKey {
70        self.keypair.x_only_public_key().0
71    }
72
73    /// Return the full public key (includes parity).
74    pub fn pubkey_full(&self) -> PublicKey {
75        self.keypair.public_key()
76    }
77
78    /// Return the public key as a bech32-encoded npub string (NIP-19).
79    pub fn npub(&self) -> String {
80        encode_npub(&self.pubkey())
81    }
82
83    /// Return the node ID.
84    pub fn node_addr(&self) -> &NodeAddr {
85        &self.node_addr
86    }
87
88    /// Return the FIPS address.
89    pub fn address(&self) -> &FipsAddress {
90        &self.address
91    }
92
93    /// Sign arbitrary data with this identity's secret key.
94    pub fn sign(&self, data: &[u8]) -> secp256k1::schnorr::Signature {
95        let secp = Secp256k1::new();
96        let digest = sha256(data);
97        secp.sign_schnorr(&digest, &self.keypair)
98    }
99
100    /// Create an authentication response for a challenge.
101    ///
102    /// The response signs: SHA256("fips-auth-v1" || challenge || timestamp)
103    pub fn sign_challenge(&self, challenge: &[u8; 32], timestamp: u64) -> AuthResponse {
104        let digest = auth_challenge_digest(challenge, timestamp);
105        let secp = Secp256k1::new();
106        let signature = secp.sign_schnorr(&digest, &self.keypair);
107        AuthResponse {
108            pubkey: self.pubkey(),
109            timestamp,
110            signature,
111        }
112    }
113}
114
115impl fmt::Debug for Identity {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        f.debug_struct("Identity")
118            .field("node_addr", &self.node_addr)
119            .field("address", &self.address)
120            .finish_non_exhaustive()
121    }
122}