diff --git a/src/identity/auth.rs b/src/identity/auth.rs index 6e383b7..aedf42d 100644 --- a/src/identity/auth.rs +++ b/src/identity/auth.rs @@ -1,7 +1,7 @@ //! Authentication challenge-response protocol. use rand::Rng; -use secp256k1::{Secp256k1, XOnlyPublicKey}; +use secp256k1::XOnlyPublicKey; use sha2::{Digest, Sha256}; use super::{IdentityError, NodeAddr}; @@ -34,9 +34,9 @@ impl AuthChallenge { /// Verify a response to this challenge. pub fn verify(&self, response: &AuthResponse) -> Result { let digest = auth_challenge_digest(&self.0, response.timestamp); - let secp = Secp256k1::new(); - secp.verify_schnorr(&response.signature, &digest, &response.pubkey) + super::SECP + .verify_schnorr(&response.signature, &digest, &response.pubkey) .map_err(|_| IdentityError::SignatureVerificationFailed)?; Ok(NodeAddr::from_pubkey(&response.pubkey)) diff --git a/src/identity/local.rs b/src/identity/local.rs index f79e7cd..26db8e1 100644 --- a/src/identity/local.rs +++ b/src/identity/local.rs @@ -1,6 +1,6 @@ //! Local node identity with signing capability. -use secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey}; +use secp256k1::{Keypair, PublicKey, SecretKey, XOnlyPublicKey}; use std::fmt; use super::auth::{AuthResponse, auth_challenge_digest}; @@ -42,8 +42,7 @@ impl Identity { /// Create an identity from a secret key. pub fn from_secret_key(secret_key: SecretKey) -> Self { - let secp = Secp256k1::new(); - let keypair = Keypair::from_secret_key(&secp, &secret_key); + let keypair = Keypair::from_secret_key(&super::SECP, &secret_key); Self::from_keypair(keypair) } @@ -93,9 +92,8 @@ impl Identity { /// Sign arbitrary data with this identity's secret key. pub fn sign(&self, data: &[u8]) -> secp256k1::schnorr::Signature { - let secp = Secp256k1::new(); let digest = sha256(data); - secp.sign_schnorr(&digest, &self.keypair) + super::SECP.sign_schnorr(&digest, &self.keypair) } /// Create an authentication response for a challenge. @@ -103,8 +101,7 @@ impl Identity { /// The response signs: SHA256("fips-auth-v1" || challenge || timestamp) pub fn sign_challenge(&self, challenge: &[u8; 32], timestamp: u64) -> AuthResponse { let digest = auth_challenge_digest(challenge, timestamp); - let secp = Secp256k1::new(); - let signature = secp.sign_schnorr(&digest, &self.keypair); + let signature = super::SECP.sign_schnorr(&digest, &self.keypair); AuthResponse { pubkey: self.pubkey(), timestamp, diff --git a/src/identity/mod.rs b/src/identity/mod.rs index e5f7899..6093427 100644 --- a/src/identity/mod.rs +++ b/src/identity/mod.rs @@ -11,6 +11,9 @@ mod local; mod node_addr; mod peer; +use std::sync::LazyLock; + +use secp256k1::{All, Secp256k1}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -21,6 +24,15 @@ pub use local::Identity; pub use node_addr::NodeAddr; pub use peer::PeerIdentity; +/// Shared secp256k1 context reused across all identity operations. +/// +/// `Secp256k1::new()` allocates a `Secp256k1` and runs randomization / +/// blinding table setup; it is designed to be created once and reused rather +/// than rebuilt per sign / verify / key-derive call. This single `All` context +/// serves both signing and verification across the identity module and still +/// performs the standard construction-time blinding. +pub(crate) static SECP: LazyLock> = LazyLock::new(Secp256k1::new); + /// FIPS address prefix (IPv6 ULA range). pub const FIPS_ADDRESS_PREFIX: u8 = 0xfd; diff --git a/src/identity/peer.rs b/src/identity/peer.rs index 06f65f7..b997058 100644 --- a/src/identity/peer.rs +++ b/src/identity/peer.rs @@ -1,6 +1,6 @@ //! Remote peer identity (public key only, no signing capability). -use secp256k1::{Parity, PublicKey, Secp256k1, XOnlyPublicKey}; +use secp256k1::{Parity, PublicKey, XOnlyPublicKey}; use std::fmt; use super::encoding::{decode_npub, encode_npub}; @@ -107,9 +107,9 @@ impl PeerIdentity { /// Verify a signature from this peer. pub fn verify(&self, data: &[u8], signature: &secp256k1::schnorr::Signature) -> bool { - let secp = Secp256k1::new(); let digest = sha256(data); - secp.verify_schnorr(signature, &digest, &self.pubkey) + super::SECP + .verify_schnorr(signature, &digest, &self.pubkey) .is_ok() } } diff --git a/src/identity/tests.rs b/src/identity/tests.rs index 5db8d8d..546c7ff 100644 --- a/src/identity/tests.rs +++ b/src/identity/tests.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use std::net::Ipv6Addr; -use secp256k1::{Keypair, Secp256k1, SecretKey}; +use secp256k1::{Keypair, SecretKey}; use super::*; @@ -161,10 +161,10 @@ fn test_identity_sign() { let sig = identity.sign(data); // Verify the signature manually - let secp = secp256k1::Secp256k1::new(); let digest = super::sha256(data); assert!( - secp.verify_schnorr(&sig, &digest, &identity.pubkey()) + super::SECP + .verify_schnorr(&sig, &digest, &identity.pubkey()) .is_ok() ); } @@ -580,13 +580,12 @@ fn test_peer_identity_pubkey_full_even_parity_fallback() { #[test] fn test_peer_identity_pubkey_full_preserved_parity() { // Create two identities and find one with odd parity to make this test meaningful - let secp = Secp256k1::new(); let secret_bytes: [u8; 32] = [ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, ]; - let keypair = Keypair::from_seckey_slice(&secp, &secret_bytes).unwrap(); + let keypair = Keypair::from_seckey_slice(&super::SECP, &secret_bytes).unwrap(); let full_pubkey = keypair.public_key(); let peer = PeerIdentity::from_pubkey_full(full_pubkey);