Skip to main content

fips_identity/
auth.rs

1//! Authentication challenge-response protocol.
2
3use rand::Rng;
4use secp256k1::{Secp256k1, XOnlyPublicKey};
5use sha2::{Digest, Sha256};
6
7use super::{IdentityError, NodeAddr};
8
9/// Domain separation string for authentication challenges.
10const AUTH_DOMAIN: &[u8] = b"fips-auth-v1";
11
12/// A 32-byte random authentication challenge.
13#[derive(Clone, Copy, Debug)]
14pub struct AuthChallenge([u8; 32]);
15
16impl AuthChallenge {
17    /// Generate a new random challenge.
18    pub fn generate() -> Self {
19        let mut bytes = [0u8; 32];
20        rand::rng().fill_bytes(&mut bytes);
21        Self(bytes)
22    }
23
24    /// Create a challenge from bytes.
25    pub fn from_bytes(bytes: [u8; 32]) -> Self {
26        Self(bytes)
27    }
28
29    /// Return the challenge bytes.
30    pub fn as_bytes(&self) -> &[u8; 32] {
31        &self.0
32    }
33
34    /// Verify a response to this challenge.
35    pub fn verify(&self, response: &AuthResponse) -> Result<NodeAddr, IdentityError> {
36        let digest = auth_challenge_digest(&self.0, response.timestamp);
37        let secp = Secp256k1::new();
38
39        secp.verify_schnorr(&response.signature, &digest, &response.pubkey)
40            .map_err(|_| IdentityError::SignatureVerificationFailed)?;
41
42        Ok(NodeAddr::from_pubkey(&response.pubkey))
43    }
44}
45
46/// Response to an authentication challenge.
47#[derive(Clone, Debug)]
48pub struct AuthResponse {
49    /// The responder's public key.
50    pub pubkey: XOnlyPublicKey,
51    /// Timestamp included in the signed message.
52    pub timestamp: u64,
53    /// Schnorr signature over the challenge digest.
54    pub signature: secp256k1::schnorr::Signature,
55}
56
57/// Compute the digest for an authentication challenge.
58pub(super) fn auth_challenge_digest(challenge: &[u8; 32], timestamp: u64) -> [u8; 32] {
59    let mut hasher = Sha256::new();
60    hasher.update(AUTH_DOMAIN);
61    hasher.update(challenge);
62    hasher.update(timestamp.to_be_bytes());
63    let result = hasher.finalize();
64    let mut digest = [0u8; 32];
65    digest.copy_from_slice(&result);
66    digest
67}