circles/examples/client_auth_simulation_example.rs
2025-06-19 05:17:14 +03:00

261 lines
8.6 KiB
Rust

//! Authentication simulation example
//!
//! This example simulates the authentication flow without requiring a running server.
//! It demonstrates:
//! 1. Key generation and management
//! 2. Nonce request simulation
//! 3. Message signing and verification
//! 4. Credential management
//! 5. Authentication state checking
use std::time::{SystemTime, UNIX_EPOCH};
use log::info;
// Import authentication modules
use circle_client_ws::CircleWsClientBuilder;
#[cfg(feature = "crypto")]
use circle_client_ws::auth::{
generate_private_key,
derive_public_key,
sign_message,
verify_signature,
AuthCredentials,
NonceResponse
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize logging
env_logger::init();
info!("🔐 Starting authentication simulation example");
// Step 1: Generate cryptographic keys
info!("🔑 Generating cryptographic keys...");
#[cfg(feature = "crypto")]
let (private_key, public_key) = {
let private_key = generate_private_key()?;
let public_key = derive_public_key(&private_key)?;
info!("✅ Generated private key: {}...", &private_key[..10]);
info!("✅ Derived public key: {}...", &public_key[..20]);
(private_key, public_key)
};
#[cfg(not(feature = "crypto"))]
let (private_key, _public_key) = {
let private_key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string();
let public_key = "04abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string();
info!("📝 Using fallback keys (crypto feature disabled)");
(private_key, public_key)
};
// Step 2: Simulate nonce request and response
info!("📡 Simulating nonce request...");
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let simulated_nonce = format!("nonce_{}_{}", current_time, "abcdef123456");
let expires_at = current_time + 300; // 5 minutes from now
#[cfg(feature = "crypto")]
let nonce_response = NonceResponse {
nonce: simulated_nonce.clone(),
expires_at,
};
info!("✅ Simulated nonce response:");
info!(" Nonce: {}", simulated_nonce);
info!(" Expires at: {}", expires_at);
// Step 3: Sign the nonce
info!("✍️ Signing nonce with private key...");
#[cfg(feature = "crypto")]
let signature = {
match sign_message(&private_key, &simulated_nonce) {
Ok(sig) => {
info!("✅ Signature created: {}...", &sig[..20]);
sig
}
Err(e) => {
error!("❌ Failed to sign message: {}", e);
return Err(e.into());
}
}
};
#[cfg(not(feature = "crypto"))]
let _signature = {
info!("📝 Using fallback signature (crypto feature disabled)");
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string()
};
// Step 4: Verify the signature
info!("🔍 Verifying signature...");
#[cfg(feature = "crypto")]
{
match verify_signature(&public_key, &simulated_nonce, &signature) {
Ok(true) => info!("✅ Signature verification successful!"),
Ok(false) => {
error!("❌ Signature verification failed!");
return Err("Signature verification failed".into());
}
Err(e) => {
error!("❌ Signature verification error: {}", e);
return Err(e.into());
}
}
}
#[cfg(not(feature = "crypto"))]
{
info!("📝 Skipping signature verification (crypto feature disabled)");
}
// Step 5: Create authentication credentials
info!("📋 Creating authentication credentials...");
#[cfg(feature = "crypto")]
let credentials = AuthCredentials::new(
public_key.clone(),
signature.clone(),
nonce_response.nonce.clone(),
expires_at
);
#[cfg(feature = "crypto")]
{
info!("✅ Credentials created:");
info!(" Public key: {}...", &credentials.public_key()[..20]);
info!(" Signature: {}...", &credentials.signature()[..20]);
info!(" Nonce: {}", credentials.nonce());
info!(" Expires at: {}", credentials.expires_at);
info!(" Is expired: {}", credentials.is_expired());
info!(" Expires within 60s: {}", credentials.expires_within(60));
info!(" Expires within 400s: {}", credentials.expires_within(400));
}
// Step 6: Create client with authentication
info!("🔌 Creating WebSocket client with authentication...");
let _client = CircleWsClientBuilder::new("ws://localhost:8080/ws".to_string())
.with_keypair(private_key.clone())
.build();
info!("✅ Client created");
// Step 7: Demonstrate key rotation
info!("🔄 Demonstrating key rotation...");
#[cfg(feature = "crypto")]
{
let new_private_key = generate_private_key()?;
let new_public_key = derive_public_key(&new_private_key)?;
info!("✅ Generated new keys:");
info!(" New private key: {}...", &new_private_key[..10]);
info!(" New public key: {}...", &new_public_key[..20]);
// Create new client with rotated keys
let _new_client = CircleWsClientBuilder::new("ws://localhost:8080/ws".to_string())
.with_keypair(new_private_key)
.build();
info!("✅ Created client with rotated keys");
}
#[cfg(not(feature = "crypto"))]
{
info!("📝 Skipping key rotation (crypto feature disabled)");
}
// Step 8: Demonstrate credential expiration
info!("⏰ Demonstrating credential expiration...");
// Create credentials that expire soon
#[cfg(feature = "crypto")]
let short_lived_credentials = AuthCredentials::new(
public_key,
signature,
nonce_response.nonce,
current_time + 5 // Expires in 5 seconds
);
#[cfg(feature = "crypto")]
{
info!("✅ Created short-lived credentials:");
info!(" Expires at: {}", short_lived_credentials.expires_at);
info!(" Is expired: {}", short_lived_credentials.is_expired());
info!(" Expires within 10s: {}", short_lived_credentials.expires_within(10));
// Wait a moment and check again
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
info!("⏳ After 1 second:");
info!(" Is expired: {}", short_lived_credentials.is_expired());
info!(" Expires within 5s: {}", short_lived_credentials.expires_within(5));
}
info!("🎉 Authentication simulation completed successfully!");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_key_generation() {
#[cfg(feature = "crypto")]
{
let private_key = generate_private_key().unwrap();
assert!(private_key.starts_with("0x"));
assert_eq!(private_key.len(), 66); // 0x + 64 hex chars
let public_key = derive_public_key(&private_key).unwrap();
assert!(public_key.starts_with("04"));
assert_eq!(public_key.len(), 130); // 04 + 128 hex chars (uncompressed)
}
}
#[tokio::test]
async fn test_signature_flow() {
#[cfg(feature = "crypto")]
{
let private_key = generate_private_key().unwrap();
let public_key = derive_public_key(&private_key).unwrap();
let message = "test_nonce_12345";
let signature = sign_message(&private_key, message).unwrap();
let is_valid = verify_signature(&public_key, message, &signature).unwrap();
assert!(is_valid);
}
}
#[test]
fn test_credentials() {
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
#[cfg(feature = "crypto")]
let credentials = AuthCredentials::new(
"04abcdef...".to_string(),
"0x123456...".to_string(),
"nonce_123".to_string(),
current_time + 300
);
#[cfg(feature = "crypto")]
{
assert!(!credentials.is_expired());
assert!(credentials.expires_within(400));
assert!(!credentials.expires_within(100));
}
}
}