use crate::qzkp::{ProtocolResult, QZKPProtocol};
use rand::Rng;
use sha2::{Digest, Sha256};
use std::collections::BTreeMap;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Clone, Debug)]
pub struct AuthChallenge {
pub timestamp: String,
pub required_proof: String,
pub nonce: u32,
pub service_provider: String,
pub session_id: String,
}
#[derive(Clone, Debug)]
pub struct AuthProof {
pub aadhaar_id_partial: String,
pub session_id: String,
pub timestamp: String,
pub qber: f64,
pub qber_valid: bool,
pub merkle_root: String,
pub session_key_hash: String,
pub status: String,
pub uidai_signature: String,
}
pub struct QAadhaarProtocol {
pub aadhaar_id: String,
pub master_secret: String,
pub attributes: BTreeMap<String, String>,
pub merkle_root: String,
pub qber_threshold: f64,
}
impl QAadhaarProtocol {
pub fn new(
aadhaar_id: String,
master_secret: String,
attributes: BTreeMap<String, String>,
) -> Self {
let merkle_root = Self::build_merkle_root(&attributes);
QAadhaarProtocol {
aadhaar_id,
master_secret,
attributes,
merkle_root,
qber_threshold: 0.11,
}
}
fn hash_hex(input: &str) -> String {
format!("{:x}", Sha256::digest(input.as_bytes()))
}
fn build_merkle_root(attributes: &BTreeMap<String, String>) -> String {
let combined = attributes
.iter()
.map(|(key, value)| Self::hash_hex(&format!("{}:{}", key, value)))
.collect::<Vec<String>>()
.join("");
Self::hash_hex(&combined)
}
fn current_timestamp() -> String {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
.to_string()
}
fn mask_aadhaar(aadhaar_id: &str) -> String {
if aadhaar_id.len() <= 8 {
return "****-****".to_string();
}
let prefix = &aadhaar_id[..4];
let suffix = &aadhaar_id[aadhaar_id.len() - 4..];
format!("{}-****-{}", prefix, suffix)
}
fn sign_proof(proof: &AuthProof) -> String {
let payload = format!("{}{}{}", proof.session_id, proof.timestamp, proof.status);
Self::hash_hex(&payload)[..32].to_string()
}
pub fn request_authentication(
&self,
service_provider: &str,
required_proof: &str,
rng: &mut impl Rng,
) -> AuthChallenge {
let timestamp = Self::current_timestamp();
let nonce = rng.gen_range(100_000..1_000_000);
let session_seed = format!("{}{}{}", timestamp, nonce, service_provider);
let session_id = Self::hash_hex(&session_seed)[..16].to_string();
AuthChallenge {
timestamp,
required_proof: required_proof.to_string(),
nonce,
service_provider: service_provider.to_string(),
session_id,
}
}
pub fn generate_proof(
&self,
challenge: &AuthChallenge,
channel_noise: f64,
rng: &mut impl Rng,
) -> (AuthProof, ProtocolResult) {
let combined = format!(
"{}_{}_{}",
self.master_secret, challenge.timestamp, challenge.nonce
);
let session_key = Self::hash_hex(&combined);
let qzkp = QZKPProtocol::new(
self.master_secret.clone(),
self.master_secret.clone(),
32,
channel_noise,
);
let result = qzkp.run_protocol(rng);
let qber_valid = result.qber < self.qber_threshold;
let mut proof = AuthProof {
aadhaar_id_partial: Self::mask_aadhaar(&self.aadhaar_id),
session_id: challenge.session_id.clone(),
timestamp: challenge.timestamp.clone(),
qber: result.qber,
qber_valid,
merkle_root: self.merkle_root.clone(),
session_key_hash: Self::hash_hex(&session_key)[..16].to_string(),
status: if qber_valid {
"VALID".to_string()
} else {
"INVALID".to_string()
},
uidai_signature: String::new(),
};
proof.uidai_signature = Self::sign_proof(&proof);
(proof, result)
}
}
pub struct ServiceProvider {
pub name: String,
}
impl ServiceProvider {
pub fn new(name: String) -> Self {
ServiceProvider { name }
}
pub fn verify_proof(&self, proof: &AuthProof) -> bool {
proof.status == "VALID" && proof.qber_valid
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::StdRng;
use rand::SeedableRng;
fn seeded_rng() -> StdRng {
StdRng::seed_from_u64(7)
}
fn sample_protocol() -> QAadhaarProtocol {
let mut attrs = BTreeMap::new();
attrs.insert("name".into(), "Test User".into());
attrs.insert("age".into(), "30".into());
attrs.insert("state".into(), "Delhi".into());
QAadhaarProtocol::new("1234-5678-9012".into(), "master_secret_test".into(), attrs)
}
#[test]
fn merkle_root_is_deterministic() {
let p1 = sample_protocol();
let p2 = sample_protocol();
assert_eq!(p1.merkle_root, p2.merkle_root);
assert!(!p1.merkle_root.is_empty());
}
#[test]
fn merkle_root_changes_with_attributes() {
let mut attrs1 = BTreeMap::new();
attrs1.insert("name".into(), "Alice".into());
let p1 = QAadhaarProtocol::new("id".into(), "s".into(), attrs1);
let mut attrs2 = BTreeMap::new();
attrs2.insert("name".into(), "Bob".into());
let p2 = QAadhaarProtocol::new("id".into(), "s".into(), attrs2);
assert_ne!(p1.merkle_root, p2.merkle_root);
}
#[test]
fn aadhaar_id_is_masked() {
let masked = QAadhaarProtocol::mask_aadhaar("1234-5678-9012");
assert_eq!(masked, "1234-****-9012");
}
#[test]
fn short_aadhaar_id_fully_masked() {
let masked = QAadhaarProtocol::mask_aadhaar("12345678");
assert_eq!(masked, "****-****");
}
#[test]
fn challenge_has_unique_session_id() {
let proto = sample_protocol();
let mut rng = seeded_rng();
let c1 = proto.request_authentication("SP", "id", &mut rng);
let c2 = proto.request_authentication("SP", "id", &mut rng);
assert_ne!(c1.session_id, c2.session_id);
}
#[test]
fn full_auth_round_trip() {
let proto = sample_protocol();
let mut rng = seeded_rng();
let challenge =
proto.request_authentication("Gov Portal", "identity_verification", &mut rng);
let (proof, result) = proto.generate_proof(&challenge, 0.025, &mut rng);
assert!(result.verification_status);
assert!(proof.qber_valid);
assert_eq!(proof.status, "VALID");
assert!(!proof.uidai_signature.is_empty());
assert_eq!(proof.session_id, challenge.session_id);
assert_eq!(proof.aadhaar_id_partial, "1234-****-9012");
let sp = ServiceProvider::new("Gov Portal".into());
assert!(sp.verify_proof(&proof));
}
#[test]
fn multiple_round_trips_all_valid() {
let proto = sample_protocol();
for seed in 0..10 {
let mut rng = StdRng::seed_from_u64(seed);
let challenge = proto.request_authentication("SP", "id", &mut rng);
let (proof, _) = proto.generate_proof(&challenge, 0.025, &mut rng);
let sp = ServiceProvider::new("SP".into());
assert!(sp.verify_proof(&proof), "failed at seed {}", seed);
}
}
}