rust_ev_crypto_primitives 0.8.4

Crypto Primitives necessary for E-Voting Applications.
Documentation
// Copyright © 2023 Denis Morel

// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License and
// a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>.

use crate::{
    EmptyContext, HashError, HashableMessage, Integer, IntegerOperationError, OperationsTrait,
    RecursiveHashTrait, VerifyDomainTrait,
    elgamal::{EncryptionParameterDomainError, EncryptionParameters},
    integer::ModExponentiateError,
    number_theory::{QuadraticResidueError, QuadraticResidueTrait},
};
use thiserror::Error;

#[derive(Error, Debug)]
#[error(transparent)]
/// Error during Schnorr proofs
pub struct SchnorrProofError(#[from] SchnorrProofErrorRepr);

#[derive(Error, Debug)]
enum PhiSchnorrError {
    #[error("Error calculating g^x mod p")]
    GExpXModP { source: ModExponentiateError },
}

#[derive(Error, Debug)]
enum SchnorrProofErrorRepr {
    #[error("Error checking the elgamal parameters")]
    CheckElgamal(Vec<EncryptionParameterDomainError>),
    #[error("Error Compute Phi Schnorr")]
    PhiSchnorrError {
        #[from]
        source: PhiSchnorrError,
    },

    #[error("y is not quadratic residue of p (in verify_schnorr)")]
    NotQudraticResidue(#[from] QuadraticResidueError),
    #[error("Error calculating y^e mod p")]
    YExpEModP { source: ModExponentiateError },
    #[error("Error in v^(-1) mod p claculting cs'")]
    InverseVModP { source: IntegerOperationError },
    #[error("Error hashing e'")]
    EPrimeHash { source: HashError },
}

/// Compute Phi Schnorr according to specifications of Swiss Post (Algorithm 10.1)
fn compute_phi_schnorr(ep: &EncryptionParameters, x: &Integer) -> Result<Integer, PhiSchnorrError> {
    ep.g()
        .mod_exponentiate(x, ep.p())
        .map_err(|e| PhiSchnorrError::GExpXModP { source: e })
}

/// Verify Schnorr Proof according to specifications of Swiss Post (Algorithm 10.3)
///
/// # Error
/// Return an error if preconditions are not satisfied
pub fn verify_schnorr(
    ep: &EncryptionParameters,
    (e, z): (&Integer, &Integer),
    y: &Integer,
    i_aux: &[&str],
) -> Result<bool, SchnorrProofError> {
    verify_schnorr_impl(ep, (e, z), y, i_aux).map_err(SchnorrProofError::from)
}

fn verify_schnorr_impl(
    ep: &EncryptionParameters,
    (e, z): (&Integer, &Integer),
    y: &Integer,
    i_aux: &[&str],
) -> Result<bool, SchnorrProofErrorRepr> {
    if cfg!(feature = "checks") {
        let domain_errs = ep.verifiy_domain(&EmptyContext::default());
        if !domain_errs.is_empty() {
            return Err(SchnorrProofErrorRepr::CheckElgamal(domain_errs));
        }
        y.result_is_quadratic_residue_unchecked(ep.p())
            .map_err(SchnorrProofErrorRepr::NotQudraticResidue)?;
    }
    let x = compute_phi_schnorr(ep, z).map_err(SchnorrProofErrorRepr::from)?;
    let f = HashableMessage::from(vec![ep.p(), ep.q(), ep.g()]);
    // e in Z_q => modulo q
    // x, y in G_q => modulo p
    let c_prime = x.mod_multiply(
        &y.mod_exponentiate(e, ep.p())
            .map_err(|e| SchnorrProofErrorRepr::YExpEModP { source: e })?
            .mod_inverse(ep.p())
            .map_err(|e| SchnorrProofErrorRepr::InverseVModP { source: e })?,
        ep.p(),
    );
    let mut l: Vec<HashableMessage> = vec![];
    l.push(HashableMessage::from("SchnorrProof"));
    if !i_aux.is_empty() {
        l.push(HashableMessage::from(i_aux));
    }
    let h_aux = HashableMessage::from(l);
    let l_final: Vec<HashableMessage> = vec![
        f,
        HashableMessage::from(y),
        HashableMessage::from(&c_prime),
        h_aux,
    ];
    let e_prime = HashableMessage::from(&l_final)
        .recursive_hash()
        .map_err(|e| SchnorrProofErrorRepr::EPrimeHash { source: e })?
        .into_integer();
    Ok(&e_prime == e)
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::{
        Hexa,
        test_json_data::{
            get_test_cases_from_json_file, json_64_value_to_integer,
            json_array_value_to_array_string, json_value_to_encryption_parameters,
        },
        zero_knowledge_proofs::test::{Proof, proof_from_json_values},
    };
    use serde_json::Value;

    struct Input {
        proof: Proof,
        statement: Integer,
        additional_information: Vec<String>,
    }

    fn get_input(input: &Value) -> Input {
        Input {
            proof: proof_from_json_values(&input["proof"]),
            statement: json_64_value_to_integer(&input["statement"]),
            additional_information: json_array_value_to_array_string(
                &input["additional_information"],
            ),
        }
    }

    #[test]
    fn test_verify_schnorr_check_wrong() {
        let p = Integer::from(13u8);
        let q=  Integer::from_hexa_string("0x5BF0A8B1457695355FB8AC404E7A79E3B1738B079C5A6D2B53C26C8228C867F799273B9C49367DF2FA5FC6C6C618EBB1ED0364055D88C2F5A7BE3DABABFACAC24867EA3EBE0CDDA10AC6CAAA7BDA35E76AAE26BCFEAF926B309E18E1C1CD16EFC54D13B5E7DFD0E43BE2B1426D5BCE6A6159949E9074F2F5781563056649F6C3A21152976591C7F772D5B56EC1AFE8D03A9E8547BC729BE95CADDBCEC6E57632160F4F91DC14DAE13C05F9C39BEFC5D98068099A50685EC322E5FD39D30B07FF1C9E2465DDE5030787FC763698DF5AE6776BF9785D84400B8B1DE306FA2D07658DE6944D8365DFF510D68470C23F9FB9BC6AB676CA3206B77869E9BDF34E8031").unwrap();
        let g = Integer::from_hexa_string("0x4").unwrap();
        let e = Integer::from_hexa_string(
            "0x29D551590009AC768A674AC0C42416FDDCF261DE93CA78ED53632929169406E1",
        )
        .unwrap();
        let z = Integer::from_hexa_string("0x4BC63F12E01FBE31246E7EF85292D8ABAAF850C9531E78B5EF7DE7692259E2DF5F0AC93A8BC7F262FA8ACCC5352C6B81976FB8A470FCB80696EFB46DF4EF9E86326AC801B692E4BCD0DAA452A2D749EEE277358EBA0C141187088DCF1CEF6DDAB8FEE47299671BF7AB411AF8E792787471B74DF3866187808685E2FF169AAC4AE55B6CA7152EB29BD82317F1BD26680C6BC15DA734E1E19153253A8D2AFA0C11B08B20A2D334EDE3D29460DA359306B4B7DD4DB65B3CE4F18FDEC6FBE5328C319C5847F8DC7B9FB97E997416CA58DCF286A3D8992B2453F4924152C34687579E1D3E8AACA94F24D24C2810C70AF14BD78BDF6F528BC8167364329685F7F5D60A").unwrap();
        let y = Integer::from_hexa_string("0x6AC7B188F3C0AB80238FEA40C71A3BA9C8E438F549CC113C1FA23B0893C0C63157C2E4E147CD69BAEBF2EB464F64131F99D7E23D939972D7E6E60FEF27068E34B84CF011129AF98B0F82C78859F890F6312652BD162477A23ACC3516B2945F52E3FE0168000B3F62B04823418F1B1D3D3BE030586B39174EB1BACB832FC8E86A151DFDC11106B484530B1F9F6E4E072EDFDED5E4C564D75978B05CB797256C225901F31DD2DE56709509BDAE1DFBECA410AEFC94D87A7D585012E70EA977A812744CFF03E50A7FD5B74B7BC232D2318A384E19C0BBAA5D1100DFFD903B9FDE5D86DCDF6541444AA8983F297F9C94E50D2273B020881A600CA5B0FBCB9A17ACD3").unwrap();
        let add_info = vec!["test-0"];
        if cfg!(feature = "checks") {
            assert!(
                verify_schnorr(
                    &EncryptionParameters::from((&p, &q, &g)),
                    (&e, &z),
                    &y,
                    &add_info
                )
                .is_err()
            )
        } else {
            assert!(
                verify_schnorr(
                    &EncryptionParameters::from((&p, &q, &g)),
                    (&e, &z),
                    &y,
                    &add_info
                )
                .is_ok()
            )
        }
    }

    #[test]
    fn test_verify() {
        for tc in get_test_cases_from_json_file("zeroknowledgeproofs", "verify-schnorr.json") {
            let ep = json_value_to_encryption_parameters(&tc["context"]);
            let input = get_input(&tc["input"]);
            let add_info_str = input
                .additional_information
                .iter()
                .map(|s| s.as_str())
                .collect::<Vec<&str>>();
            assert!(
                verify_schnorr(
                    &ep,
                    (&input.proof.e, &input.proof.z),
                    &input.statement,
                    &add_info_str
                )
                .unwrap(),
                "{}",
                &tc["description"]
            )
        }
    }
}