sigma-protocols 0.5.0

SIGMA zero-knowledge proof protocols
Documentation
//! Discrete Logarithm Equality (DLEQ) proof.
//!
//! Proves that two points share the same discrete logarithm with respect to different bases.
//! Specifically, proves knowledge of α such that X = α * G and Y = α * H.

use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar;
use rand::rngs::OsRng;
use rand::TryRngCore;

use crate::error::{Error, Result};
use crate::sigma::{MultiPointCommitment, ScalarChallenge, ScalarResponse, SigmaProtocol};

/// Public statement for DLEQ proof.
///
/// Contains two base points (g, h) and two result points (x, y).
#[derive(Clone, Debug)]
pub struct DLEQStatement {
    pub g: RistrettoPoint,
    pub h: RistrettoPoint,
    pub x: RistrettoPoint,
    pub y: RistrettoPoint,
}

/// Private witness for DLEQ proof: the common discrete logarithm.
#[derive(Clone, Debug)]
pub struct DLEQWitness {
    pub alpha: Scalar,
}

/// DLEQ proof implementation.
///
/// Proves that log_g(x) = log_h(y) without revealing the common logarithm.
pub struct DLEQProof;

impl SigmaProtocol for DLEQProof {
    type Statement = DLEQStatement;
    type Witness = DLEQWitness;
    type Commitment = MultiPointCommitment;
    type Challenge = ScalarChallenge;
    type Response = ScalarResponse;

    fn prover_commit(
        statement: &Self::Statement,
        _witness: &Self::Witness,
    ) -> (Self::Commitment, Vec<u8>) {
        let mut r_bytes = [0u8; 32];
        OsRng
            .try_fill_bytes(&mut r_bytes)
            .expect("Failed to generate random bytes");
        let r = Scalar::from_bytes_mod_order(r_bytes);

        let a = r * statement.g;
        let b = r * statement.h;

        let state = r.to_bytes().to_vec();

        (MultiPointCommitment(vec![a, b]), state)
    }

    fn prover_response(
        _statement: &Self::Statement,
        witness: &Self::Witness,
        state: &[u8],
        challenge: &Self::Challenge,
    ) -> Result<Self::Response> {
        if state.len() != 32 {
            return Err(Error::InvalidProof);
        }

        let mut r_bytes = [0u8; 32];
        r_bytes.copy_from_slice(state);
        let r_option = Scalar::from_canonical_bytes(r_bytes);

        let r = if r_option.is_some().unwrap_u8() == 1 {
            r_option.unwrap()
        } else {
            return Err(Error::InvalidScalar);
        };

        let response = r + challenge.0 * witness.alpha;

        Ok(ScalarResponse(response))
    }

    fn verifier(
        statement: &Self::Statement,
        commitment: &Self::Commitment,
        challenge: &Self::Challenge,
        response: &Self::Response,
    ) -> Result<()> {
        if commitment.0.len() != 2 {
            return Err(Error::InvalidCommitment);
        }

        let a = commitment.0[0];
        let b = commitment.0[1];

        let lhs_g = response.0 * statement.g;
        let rhs_g = a + challenge.0 * statement.x;

        let lhs_h = response.0 * statement.h;
        let rhs_h = b + challenge.0 * statement.y;

        if lhs_g == rhs_g && lhs_h == rhs_h {
            Ok(())
        } else {
            Err(Error::InvalidProof)
        }
    }
}