mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 00:04:54 +00:00
Every sign/verify/key-derive site built a fresh context via Secp256k1::new(), which allocates a Secp256k1<All> and runs randomization/blinding table setup on each call. Introduce one crate-wide LazyLock<Secp256k1<All>> and reuse it across the local, peer, and auth sites (and their tests). Behavior-neutral: identical secp256k1 API calls, only the context lifetime changes, and the shared All context still performs the standard construction-time blinding.
68 lines
1.9 KiB
Rust
68 lines
1.9 KiB
Rust
//! Authentication challenge-response protocol.
|
|
|
|
use rand::Rng;
|
|
use secp256k1::XOnlyPublicKey;
|
|
use sha2::{Digest, Sha256};
|
|
|
|
use super::{IdentityError, NodeAddr};
|
|
|
|
/// Domain separation string for authentication challenges.
|
|
const AUTH_DOMAIN: &[u8] = b"fips-auth-v1";
|
|
|
|
/// A 32-byte random authentication challenge.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct AuthChallenge([u8; 32]);
|
|
|
|
impl AuthChallenge {
|
|
/// Generate a new random challenge.
|
|
pub fn generate() -> Self {
|
|
let mut bytes = [0u8; 32];
|
|
rand::rng().fill_bytes(&mut bytes);
|
|
Self(bytes)
|
|
}
|
|
|
|
/// Create a challenge from bytes.
|
|
pub fn from_bytes(bytes: [u8; 32]) -> Self {
|
|
Self(bytes)
|
|
}
|
|
|
|
/// Return the challenge bytes.
|
|
pub fn as_bytes(&self) -> &[u8; 32] {
|
|
&self.0
|
|
}
|
|
|
|
/// Verify a response to this challenge.
|
|
pub fn verify(&self, response: &AuthResponse) -> Result<NodeAddr, IdentityError> {
|
|
let digest = auth_challenge_digest(&self.0, response.timestamp);
|
|
|
|
super::SECP
|
|
.verify_schnorr(&response.signature, &digest, &response.pubkey)
|
|
.map_err(|_| IdentityError::SignatureVerificationFailed)?;
|
|
|
|
Ok(NodeAddr::from_pubkey(&response.pubkey))
|
|
}
|
|
}
|
|
|
|
/// Response to an authentication challenge.
|
|
#[derive(Clone, Debug)]
|
|
pub struct AuthResponse {
|
|
/// The responder's public key.
|
|
pub pubkey: XOnlyPublicKey,
|
|
/// Timestamp included in the signed message.
|
|
pub timestamp: u64,
|
|
/// Schnorr signature over the challenge digest.
|
|
pub signature: secp256k1::schnorr::Signature,
|
|
}
|
|
|
|
/// Compute the digest for an authentication challenge.
|
|
pub(super) fn auth_challenge_digest(challenge: &[u8; 32], timestamp: u64) -> [u8; 32] {
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(AUTH_DOMAIN);
|
|
hasher.update(challenge);
|
|
hasher.update(timestamp.to_be_bytes());
|
|
let result = hasher.finalize();
|
|
let mut digest = [0u8; 32];
|
|
digest.copy_from_slice(&result);
|
|
digest
|
|
}
|