qzkp 0.1.0

Quantum Zero-Knowledge Proof protocol with BB84 simulation and Aadhaar identity verification
Documentation
//! Aadhaar identity verification built on the QZKP protocol.
//!
//! Provides a challenge-response authentication flow where an Aadhaar holder
//! proves identity via QZKP without revealing the raw Aadhaar number. The
//! Aadhaar ID is masked and a Merkle root over identity attributes is included
//! in the proof.

use crate::qzkp::{ProtocolResult, QZKPProtocol};
use rand::Rng;
use sha2::{Digest, Sha256};
use std::collections::BTreeMap;
use std::time::{SystemTime, UNIX_EPOCH};

/// A challenge issued by a service provider to initiate authentication.
#[derive(Clone, Debug)]
pub struct AuthChallenge {
    /// Unix timestamp of challenge creation.
    pub timestamp: String,
    /// Type of proof required (e.g. "identity_verification").
    pub required_proof: String,
    /// Random nonce for replay protection.
    pub nonce: u32,
    /// Name of the requesting service provider.
    pub service_provider: String,
    /// Unique session identifier (truncated SHA-256).
    pub session_id: String,
}

/// A proof generated by the Aadhaar holder in response to a challenge.
#[derive(Clone, Debug)]
pub struct AuthProof {
    /// Masked Aadhaar ID (e.g. "1234-****-9012").
    pub aadhaar_id_partial: String,
    /// Session identifier matching the challenge.
    pub session_id: String,
    /// Timestamp from the challenge.
    pub timestamp: String,
    /// Measured QBER from the quantum protocol run.
    pub qber: f64,
    /// Whether the QBER was below the threshold.
    pub qber_valid: bool,
    /// Merkle root over the holder's identity attributes.
    pub merkle_root: String,
    /// Hash of the ephemeral session key.
    pub session_key_hash: String,
    /// "VALID" or "INVALID".
    pub status: String,
    /// Simulated UIDAI signature (truncated SHA-256).
    pub uidai_signature: String,
}

/// Aadhaar authentication protocol built on QZKP.
///
/// # Example
///
/// ```
/// use std::collections::BTreeMap;
/// use qzkp::qaadhaar::{QAadhaarProtocol, ServiceProvider};
///
/// let mut attrs = BTreeMap::new();
/// attrs.insert("name".into(), "Alice".into());
/// attrs.insert("age".into(), "25".into());
///
/// let proto = QAadhaarProtocol::new("1234-5678-9012".into(), "master_secret".into(), attrs);
/// let mut rng = rand::thread_rng();
/// let challenge = proto.request_authentication("Portal", "id_verify", &mut rng);
/// let (proof, _result) = proto.generate_proof(&challenge, 0.025, &mut rng);
///
/// let verifier = ServiceProvider::new("Portal".into());
/// assert!(verifier.verify_proof(&proof));
/// ```
pub struct QAadhaarProtocol {
    /// The full Aadhaar ID.
    pub aadhaar_id: String,
    /// The holder's master secret (never transmitted).
    pub master_secret: String,
    /// Identity attributes (name, age, state, etc.).
    pub attributes: BTreeMap<String, String>,
    /// SHA-256 Merkle root computed over `attributes`.
    pub merkle_root: String,
    /// QBER acceptance threshold.
    pub qber_threshold: f64,
}

impl QAadhaarProtocol {
    /// Create a new protocol instance for the given Aadhaar holder.
    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()
    }

    /// Generate a challenge for the given service provider.
    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,
        }
    }

    /// Generate a QZKP proof in response to a challenge.
    ///
    /// Returns both the [`AuthProof`] (for transmission to the verifier) and the
    /// underlying [`ProtocolResult`] (for local inspection).
    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)
    }
}

/// A service provider that verifies QZKP Aadhaar proofs.
///
/// # Example
///
/// ```
/// use qzkp::qaadhaar::ServiceProvider;
/// use qzkp::qaadhaar::AuthProof;
///
/// let sp = ServiceProvider::new("Gov Portal".into());
/// // sp.verify_proof(&proof) checks status == "VALID" && qber_valid
/// ```
pub struct ServiceProvider {
    /// Name of the service provider.
    pub name: String,
}

impl ServiceProvider {
    /// Create a new service provider.
    pub fn new(name: String) -> Self {
        ServiceProvider { name }
    }

    /// Verify an [`AuthProof`].
    ///
    /// Returns `true` if the proof status is "VALID" and the QBER was below
    /// the threshold.
    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);
        }
    }
}