Skip to content

Commit 53eb67e

Browse files
Ryan Quinn FordRyan Quinn Ford
authored andcommitted
readding groth16 proofs
1 parent fbf51c8 commit 53eb67e

File tree

13 files changed

+1016
-30
lines changed

13 files changed

+1016
-30
lines changed

src/cfg.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use config::{builder::DefaultState, ConfigBuilder, File};
99
use dirs::home_dir;
1010
use dotenvy::dotenv;
1111
use serde::{Deserialize, Serialize};
12-
use std::path::PathBuf;
1312
use std::{fs, path::Path, sync::Arc};
1413

1514
use crate::da::{celestia::CelestiaConnection, DataAvailabilityLayer};

src/circuits/hashchain.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use crate::common::HashchainEntry;
2+
use anyhow::Result;
3+
use bellman::{Circuit, ConstraintSystem, SynthesisError};
4+
use bls12_381::Scalar;
5+
use indexed_merkle_tree::sha256_mod;
6+
7+
/// HashChainEntryCircuit is a circuit that verifies that a given value is present in a hashchain.
8+
#[derive(Clone)]
9+
pub struct HashChainEntryCircuit {
10+
pub value: Scalar,
11+
/// Represents the hashchain in the form of a vector of Scalars.
12+
/// Each Scalar is sha256_mod(hashchain_entry.value())
13+
pub chain: Vec<Scalar>,
14+
}
15+
16+
impl HashChainEntryCircuit {
17+
pub fn create(value: &str, hashchain: Vec<HashchainEntry>) -> Result<HashChainEntryCircuit> {
18+
let hashed_value = sha256_mod(value.as_bytes());
19+
let parsed_value = hashed_value.try_into()?;
20+
let mut parsed_hashchain: Vec<Scalar> = vec![];
21+
for entry in hashchain {
22+
let hashed_entry_value = sha256_mod(entry.operation.value().as_bytes());
23+
parsed_hashchain.push(hashed_entry_value.try_into()?)
24+
}
25+
Ok(HashChainEntryCircuit {
26+
value: parsed_value,
27+
chain: parsed_hashchain,
28+
})
29+
}
30+
}
31+
32+
impl Circuit<Scalar> for HashChainEntryCircuit {
33+
fn synthesize<CS: ConstraintSystem<Scalar>>(self, cs: &mut CS) -> Result<(), SynthesisError> {
34+
if self.chain.is_empty() {
35+
return Err(SynthesisError::AssignmentMissing);
36+
}
37+
38+
let provided_value = cs.alloc_input(|| "provided hashed value", || Ok(self.value))?;
39+
40+
for entry in self.chain {
41+
if entry == self.value {
42+
let found_value = cs.alloc(|| "found hashed value", || Ok(entry))?;
43+
// found_value * (1) = provided_value
44+
cs.enforce(
45+
|| "found value check",
46+
|lc| lc + found_value,
47+
|lc| lc + CS::one(),
48+
|lc| lc + provided_value,
49+
);
50+
return Ok(());
51+
}
52+
}
53+
Err(SynthesisError::Unsatisfiable)
54+
}
55+
}

src/circuits/less_than.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use anyhow::Result;
2+
use bellman::{gadgets::boolean::Boolean, Circuit, ConstraintSystem, SynthesisError};
3+
use bls12_381::Scalar;
4+
use ff::PrimeFieldBits;
5+
6+
#[derive(Clone)]
7+
pub struct LessThanCircuit {
8+
a: Scalar,
9+
b: Scalar,
10+
}
11+
12+
impl LessThanCircuit {
13+
pub fn new(a: Scalar, b: Scalar) -> LessThanCircuit {
14+
LessThanCircuit { a, b }
15+
}
16+
}
17+
18+
impl Circuit<Scalar> for LessThanCircuit {
19+
fn synthesize<CS: ConstraintSystem<Scalar>>(self, cs: &mut CS) -> Result<(), SynthesisError> {
20+
let a_bits = self.a.to_le_bits();
21+
let b_bits = self.b.to_le_bits();
22+
23+
let mut result = Boolean::constant(false);
24+
25+
// Iterate over the bits from most significant to least significant
26+
for i in (0..a_bits.len()).rev() {
27+
let a_val = Boolean::constant(a_bits[i]);
28+
let b_val = Boolean::constant(b_bits[i]);
29+
let not_a = a_val.not();
30+
let not_b = b_val.not();
31+
32+
// Check if bits are equal (both 1 or both 0)
33+
let a_and_b = Boolean::and(cs.namespace(|| format!("a_and_b_{}", i)), &a_val, &b_val)?;
34+
let not_a_and_not_b = Boolean::and(
35+
cs.namespace(|| format!("not_a_and_not_b_{}", i)),
36+
&not_a,
37+
&not_b,
38+
)?;
39+
40+
// If the bits are equal, continue to the next bit
41+
if not_a_and_not_b.get_value().unwrap() || a_and_b.get_value().unwrap() {
42+
continue;
43+
} else {
44+
// If bits differ: b > a if b_bit = 1 && a_bit = 0
45+
result = Boolean::and(
46+
cs.namespace(|| format!("b_and_not_a_{}", i)),
47+
&b_val,
48+
&not_a,
49+
)?;
50+
break;
51+
}
52+
}
53+
54+
// Enforce the constraint that the result is correct
55+
// If result is true, then a < b, otherwise a >= b
56+
// result * (1) = 1
57+
cs.enforce(
58+
|| "a < b",
59+
|_| result.lc(CS::one(), Scalar::one()),
60+
|lc| lc + CS::one(),
61+
|lc| lc + CS::one(),
62+
);
63+
64+
Ok(())
65+
}
66+
}

src/circuits/merkle_batch.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use crate::{
2+
circuits::{
3+
merkle_insertion::prove_insertion, merkle_update::prove_update, InsertMerkleProofCircuit,
4+
ProofVariantCircuit, UpdateMerkleProofCircuit,
5+
},
6+
tree::Digest,
7+
utils::create_and_verify_snark,
8+
};
9+
use anyhow::Result;
10+
use bellman::{groth16, Circuit, ConstraintSystem, SynthesisError};
11+
use bls12_381::{Bls12, Scalar};
12+
use indexed_merkle_tree::tree::Proof;
13+
14+
/// BatchMerkleProofCircuit represents a circuit for proving a batch of merkle proof circuits.
15+
#[derive(Clone)]
16+
pub struct BatchMerkleProofCircuit {
17+
pub old_commitment: Scalar,
18+
pub new_commitment: Scalar,
19+
pub proofs: Vec<ProofVariantCircuit>,
20+
}
21+
22+
impl BatchMerkleProofCircuit {
23+
pub fn new(
24+
old_commitment: &Digest,
25+
new_commitment: &Digest,
26+
proofs: Vec<Proof>,
27+
) -> Result<BatchMerkleProofCircuit> {
28+
let parsed_old_commitment: Scalar = (*old_commitment).try_into()?;
29+
let parsed_new_commitment: Scalar = (*new_commitment).try_into()?;
30+
let mut proof_circuit_array: Vec<ProofVariantCircuit> = vec![];
31+
for proof in proofs {
32+
match proof {
33+
Proof::Update(update_proof) => {
34+
proof_circuit_array.push(ProofVariantCircuit::Update(
35+
UpdateMerkleProofCircuit::new(&update_proof)?,
36+
));
37+
}
38+
Proof::Insert(insertion_proof) => {
39+
proof_circuit_array.push(ProofVariantCircuit::Insert(
40+
InsertMerkleProofCircuit::new(&insertion_proof)?,
41+
));
42+
}
43+
}
44+
}
45+
Ok(BatchMerkleProofCircuit {
46+
old_commitment: parsed_old_commitment,
47+
new_commitment: parsed_new_commitment,
48+
proofs: proof_circuit_array,
49+
})
50+
}
51+
52+
pub fn create_and_verify_snark(
53+
&self,
54+
) -> Result<(groth16::Proof<Bls12>, groth16::VerifyingKey<Bls12>)> {
55+
let scalars: Vec<Scalar> = vec![self.old_commitment, self.new_commitment];
56+
57+
create_and_verify_snark(ProofVariantCircuit::Batch(self.clone()), scalars)
58+
}
59+
}
60+
61+
impl Circuit<Scalar> for BatchMerkleProofCircuit {
62+
fn synthesize<CS: ConstraintSystem<Scalar>>(self, cs: &mut CS) -> Result<(), SynthesisError> {
63+
// If the proofs are empty, we just verify that the commitments are equal
64+
if self.proofs.is_empty() {
65+
let provided_old_commitment =
66+
cs.alloc_input(|| "provided old commitment", || Ok(self.old_commitment))?;
67+
let provided_new_commitment =
68+
cs.alloc_input(|| "provided new commitment", || Ok(self.new_commitment))?;
69+
70+
// provided_old_commitment * (1) = provided_new_commitment
71+
cs.enforce(
72+
|| "old commitment check",
73+
|lc| lc + provided_old_commitment,
74+
|lc| lc + CS::one(),
75+
|lc| lc + provided_new_commitment,
76+
);
77+
78+
return Ok(());
79+
}
80+
81+
// before the calculations make sure that the old root is that of the first proof
82+
let old_root = match &self.proofs[0] {
83+
ProofVariantCircuit::Update(update_proof_circuit) => update_proof_circuit.old_root,
84+
ProofVariantCircuit::Insert(insert_proof_circuit) => {
85+
insert_proof_circuit.pre_insertion_root
86+
}
87+
ProofVariantCircuit::Batch(batch_proof_circuit) => batch_proof_circuit.old_commitment,
88+
};
89+
90+
let provided_old_commitment =
91+
cs.alloc_input(|| "provided old commitment", || Ok(self.old_commitment))?;
92+
let old_commitment_from_proofs =
93+
cs.alloc(|| "old commitment from proofs", || Ok(old_root))?;
94+
95+
// old_commitment_from_proofs * (1) = provided_old_commitment
96+
cs.enforce(
97+
|| "old commitment check",
98+
|lc| lc + old_commitment_from_proofs,
99+
|lc| lc + CS::one(),
100+
|lc| lc + provided_old_commitment,
101+
);
102+
103+
let mut new_commitment: Scalar = Scalar::zero();
104+
for proof in self.proofs {
105+
// update the new_commitment for every proof, applying the constraints of the circuit each time
106+
match proof {
107+
ProofVariantCircuit::Update(update_proof_circuit) => {
108+
new_commitment = prove_update(
109+
cs,
110+
update_proof_circuit.old_root,
111+
&update_proof_circuit.old_path,
112+
update_proof_circuit.updated_root,
113+
&update_proof_circuit.updated_path,
114+
)?;
115+
}
116+
ProofVariantCircuit::Insert(insert_proof_circuit) => {
117+
new_commitment = prove_insertion(
118+
cs,
119+
insert_proof_circuit.pre_insertion_root,
120+
&insert_proof_circuit.insertion_path,
121+
insert_proof_circuit.new_leaf_node,
122+
insert_proof_circuit.existing_leaf_update,
123+
insert_proof_circuit.new_leaf_activation,
124+
)?;
125+
}
126+
ProofVariantCircuit::Batch(_) => {
127+
// Batches cannot be recursively constructed
128+
// TODO: Should they be able to?
129+
return Err(SynthesisError::Unsatisfiable);
130+
}
131+
}
132+
}
133+
134+
let provided_new_commitment =
135+
cs.alloc_input(|| "provided commitment", || Ok(self.new_commitment))?;
136+
let recalculated_new_commitment =
137+
cs.alloc(|| "recalculated commitment", || Ok(new_commitment))?;
138+
139+
// recalculated_commitment * (1) = provided_commitment
140+
cs.enforce(
141+
|| "new commitment check",
142+
|lc| lc + recalculated_new_commitment,
143+
|lc| lc + CS::one(),
144+
|lc| lc + provided_new_commitment,
145+
);
146+
147+
Ok(())
148+
}
149+
}

0 commit comments

Comments
 (0)