provekit-r1cs-compiler 1.0.0

R1CS compiler for ProveKit, translating Noir programs to R1CS constraints
Documentation
use {
    provekit_common::{utils::next_power_of_two, WhirR1CSScheme, WhirZkConfig, R1CS},
    whir::parameters::ProtocolParameters,
};

const MIN_WHIR_NUM_VARIABLES: usize = 14;
const MIN_SUMCHECK_NUM_VARIABLES: usize = 1;

pub trait WhirR1CSSchemeBuilder {
    fn new_for_r1cs(
        r1cs: &R1CS,
        w1_size: usize,
        num_challenges: usize,
        challenge_offsets: Vec<usize>,
        has_public_inputs: bool,
    ) -> Self;

    fn new_whir_zk_config_for_size(num_variables: usize) -> WhirZkConfig;
}

impl WhirR1CSSchemeBuilder for WhirR1CSScheme {
    fn new_for_r1cs(
        r1cs: &R1CS,
        w1_size: usize,
        num_challenges: usize,
        challenge_offsets: Vec<usize>,
        has_public_inputs: bool,
    ) -> Self {
        assert_eq!(
            num_challenges,
            challenge_offsets.len(),
            "num_challenges ({num_challenges}) must equal challenge_offsets.len() ({})",
            challenge_offsets.len()
        );
        let total_witnesses = r1cs.num_witnesses();
        assert!(
            w1_size <= total_witnesses,
            "w1_size exceeds total witnesses"
        );
        let w2_size = total_witnesses - w1_size;

        let m1_raw = next_power_of_two(w1_size);
        let m2_raw = next_power_of_two(w2_size);
        let m0_raw = next_power_of_two(r1cs.num_constraints());

        let mut m_raw = m1_raw.max(m2_raw).max(MIN_WHIR_NUM_VARIABLES);
        let m_0 = m0_raw.max(MIN_SUMCHECK_NUM_VARIABLES);

        // Ensure w1's zero-padding has room for the blinding polynomial coefficients.
        if (1usize << m_raw) - w1_size < 4 * m_0 {
            m_raw += 1;
        }

        Self {
            m: m_raw,
            w1_size,
            m_0,
            a_num_terms: next_power_of_two(r1cs.a().iter().count()),
            num_challenges,
            challenge_offsets,
            whir_witness: Self::new_whir_zk_config_for_size(m_raw),
            has_public_inputs,
            r1cs_hash: r1cs.hash(),
        }
    }

    fn new_whir_zk_config_for_size(num_variables: usize) -> WhirZkConfig {
        let nv = num_variables.max(MIN_WHIR_NUM_VARIABLES);

        // Parameters tuned for 128-bit security under the Johnson bound (the old
        // ConjectureList soundness was disproven). Rate=2 balances query count vs
        // codeword size; ff=3 keeps blinding polynomials small; pow_bits=10 shifts
        // security budget toward algebraic hardness (118 bits) with light PoW per
        // round, which is faster than the default ~18-bit grinding.
        let whir_params = ProtocolParameters {
            unique_decoding:        false,
            security_level:         128,
            pow_bits:               10,
            initial_folding_factor: 3,
            folding_factor:         3,
            starting_log_inv_rate:  2,
            batch_size:             1,
            hash_id:                whir::hash::SHA2,
        };
        WhirZkConfig::new(nv, &whir_params)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn verify_security_level() {
        let config = WhirR1CSScheme::new_whir_zk_config_for_size(20);
        let sec_blinded = config
            .blinded_polynomial
            .security_level(config.blinded_polynomial.initial_committer.num_vectors, 1);
        let sec_blinding = config
            .blinding_polynomial
            .security_level(config.blinding_polynomial.initial_committer.num_vectors, 1);
        assert!(
            sec_blinded >= 128.0,
            "Blinded commitment security {sec_blinded:.2} < 128 bits"
        );
        assert!(
            sec_blinding >= 128.0,
            "Blinding commitment security {sec_blinding:.2} < 128 bits"
        );
    }

    #[test]
    fn verify_security_level_min_variables() {
        let config = WhirR1CSScheme::new_whir_zk_config_for_size(MIN_WHIR_NUM_VARIABLES);
        let sec_blinded = config
            .blinded_polynomial
            .security_level(config.blinded_polynomial.initial_committer.num_vectors, 1);
        let sec_blinding = config
            .blinding_polynomial
            .security_level(config.blinding_polynomial.initial_committer.num_vectors, 1);
        assert!(
            sec_blinded >= 128.0,
            "Blinded commitment security {sec_blinded:.2} < 128 bits at nv={}",
            MIN_WHIR_NUM_VARIABLES
        );
        assert!(
            sec_blinding >= 128.0,
            "Blinding commitment security {sec_blinding:.2} < 128 bits at nv={}",
            MIN_WHIR_NUM_VARIABLES
        );
    }
}