Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
595 changes: 573 additions & 22 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,13 @@ transcript = {git="https://github.com/sublinearlabs/sl-core.git"}
sum_check = {git="https://github.com/sublinearlabs/sl-core.git"}
circuits = {git="https://github.com/sublinearlabs/sl-core.git"}
p3-mersenne-31 = "0.2.0"


[dev-dependencies]
criterion = "0.7.0"
rand = "0.9.2"


[[bench]]
name = "bench"
harness = false
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ cargo test test_libra_protocol
## Performance

The Libra protocol provides:
- **Prover time**: O(C log C) where C is circuit size
- **Verifier time**: O(log C)
- **Proof size**: O(log C)
- **Prover time**: O(C)
- **Verifier time**: O(d log C)
- **Proof size**: O(d log C)
- **Memory usage**: Linear in circuit size
where C is circuit size and d is the circuit depth

## Contributing

Expand Down
76 changes: 76 additions & 0 deletions benches/bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use circuits::{
interface::CircuitTr,
layered_circuit::{
LayeredCircuit,
primitives::{Evaluation, Gate, GateOp, Layer},
},
};
use criterion::{Criterion, criterion_group, criterion_main};
use libra::{proof::LibraProof, prover::prove, verifier::verify};
use p3_field::{AbstractField, extension::BinomialExtensionField};
use p3_mersenne_31::Mersenne31 as F;
use rand::Rng;

type E = BinomialExtensionField<F, 3>;

// Builds a random circuit given input size
fn build_random_circuit(input_size: usize) -> LayeredCircuit {
assert!(
input_size.is_power_of_two(),
"Input size should be a power of 2"
);

let n_layers = input_size.ilog2();

let mut layers: Vec<Layer> = Vec::with_capacity(n_layers as usize);

let mut gates_in_layer = input_size;

for _ in 0..n_layers {
let mut gates = vec![];
for i in 0..gates_in_layer / 2 {
let op = if rand::rng().random_bool(0.5) {
GateOp::Add
} else {
GateOp::Mul
};
let gate = Gate::new(op, [i * 2, (i * 2) + 1]);
gates.push(gate);
}

layers.push(Layer::new(gates));
gates_in_layer /= 2;
}

LayeredCircuit::new(layers)
}

// Given input size, setup creates a circuit and inputs
// and evaluates the circuit to get the output
// Returns the circuit, input and output
pub fn setup(input_size: usize) -> (LayeredCircuit, Vec<F>, Evaluation<F>) {
let circuit = build_random_circuit(input_size);

let input = rand::random_iter::<u8>()
.take(input_size)
.map(|val| F::from_canonical_usize(val as usize))
.collect::<Vec<F>>();

let output = circuit.excecute(&input);

(circuit, input, output)
}

fn benchmark(c: &mut Criterion) {
c.bench_function("prove_n_verify_input_64", |b| {
// TODO: fix bug for input greater that 8
let (circuit, input, output) = setup(8);
b.iter(|| {
let proof: LibraProof<F, E> = prove(&circuit, output.clone());
let _ = verify(&circuit, proof, input.clone());
});
});
}

criterion_group!(benches, benchmark);
criterion_main!(benches);
155 changes: 46 additions & 109 deletions src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,123 +26,72 @@ pub fn prove<F: Field + PrimeField32, E: ExtensionField<F>>(
let mut wcs = vec![];

// Get the output vector
let mut output_evals: Vec<Fields<F, E>> = output.layers[circuit.layers.len()]
let output_evals: Vec<Fields<F, E>> = output.layers[circuit.layers.len()]
.iter()
.map(|val| Fields::<F, E>::Base(*val))
.collect();

if output_evals.len() == 1 {
output_evals.push(Fields::Base(F::zero()));
}

// Build the output polynomial
let output_mle =
MultilinearPoly::new_from_vec((output_evals.len() as f64).log2() as usize, output_evals);
MultilinearPoly::new_extend_to_power_of_two(output_evals, Fields::Base(F::zero()));

// Adds the output to the transcript
transcript.observe_base_element(&output.layers[circuit.layers.len()]);

// Gets the addi and muli for the output layer
let (add_i, mul_i) =
LibraGKRLayeredCircuitTr::<F, E>::add_and_mul_mle(circuit, circuit.layers.len() - 1);

// Gets w_i+1
let mut w_i_plus_one_poly = MultilinearPoly::new_from_vec(
(output.layers[circuit.layers.len() - 1].len() as f64).log2() as usize,
output.layers[circuit.layers.len() - 1]
.iter()
.map(|val| Fields::Base(*val))
.collect::<Vec<Fields<F, E>>>(),
);

// Sample random challenge for the first round
let g = transcript
.sample_n_challenges(output_mle.num_vars())
.iter()
.map(|val| Fields::Extension(*val))
.into_iter()
.map(Fields::Extension)
.collect::<Vec<Fields<F, E>>>();

let mut igz = generate_eq(&g);

// Prepares parameters for phase one of Libra
let (mut mul_ahg, mut add_b_ahg, mut add_c_ahg) =
prepare_phase_one_params(&igz, &add_i, &mul_i, &w_i_plus_one_poly.evaluations);

let mut claimed_sum = output_mle.evaluate(&g);

// Proves the sumcheck relation using Libra algorithms
let (mut sumcheck_proof, mut wb, mut wc) = prove_libra_sumcheck(
ProveLibraInput {
claimed_sum: &claimed_sum,
igz: &igz,
mul_ahg: &mul_ahg,
add_b_ahg: &add_b_ahg,
add_c_ahg: &add_c_ahg,
add_i: &add_i,
mul_i: &mul_i,
w_i_plus_one_poly: &w_i_plus_one_poly,
},
&mut transcript,
);

let (mut rb, mut rc) = (
sumcheck_proof.challenges[..sumcheck_proof.challenges.len() / 2].to_vec(),
sumcheck_proof.challenges[sumcheck_proof.challenges.len() / 2..].to_vec(),
);

// Add messages to the transcript
transcript.observe_ext_element(&[wb.to_extension_field()]);
transcript.observe_ext_element(&[wc.to_extension_field()]);
transcript.observe_ext_element(&[sumcheck_proof.claimed_sum.to_extension_field()]);
transcript.observe_ext_element(&sumcheck_proof.round_polynomials.iter().fold(
vec![],
|mut acc, val| {
acc.extend(
val.iter()
.map(|val| val.to_extension_field())
.collect::<Vec<E>>(),
);
acc
},
));

// Adds messages to the proof
sumcheck_proofs.push(sumcheck_proof);
wbs.push(wb);
wcs.push(wc);

// Samples alpha and beta for folding
let mut alpha_n_beta = transcript
.sample_n_challenges(2)
.iter()
.map(|val| Fields::Extension(*val))
.collect::<Vec<Fields<F, E>>>();
let mut challenges = vec![];
let mut wb = Fields::Extension(E::zero());
let mut wc = Fields::Extension(E::zero());
let mut alpha_n_beta = vec![];

for i in (1..=circuit.layers.len()).rev() {
let claimed_sum = if i == circuit.layers.len() {
output_mle.evaluate(&g)
} else {
// Sample alpha and beta
alpha_n_beta = transcript
.sample_n_challenges(2)
.into_iter()
.map(Fields::Extension)
.collect::<Vec<Fields<F, E>>>();
(alpha_n_beta[0] * wb) + (alpha_n_beta[1] * wc)
};

for i in (1..circuit.layers.len()).rev() {
// Get addi and muli
let (add_i, mul_i) = LibraGKRLayeredCircuitTr::<F, E>::add_and_mul_mle(circuit, i - 1);

claimed_sum = alpha_n_beta[0] * w_i_plus_one_poly.evaluate(&rb)
+ alpha_n_beta[1] * w_i_plus_one_poly.evaluate(&rc);
let w_i_plus_one_eval = output.layers[i - 1]
.iter()
.map(|val| Fields::<F, E>::Base(*val))
.collect();

// Gets w_i+1
w_i_plus_one_poly = MultilinearPoly::new_from_vec(
(output.layers[i - 1].len() as f64).log2() as usize,
output.layers[i - 1]
.iter()
.map(|val| Fields::<F, E>::Base(*val))
.collect(),
);
let w_i_plus_one_poly =
MultilinearPoly::new_extend_to_power_of_two(w_i_plus_one_eval, Fields::Base(F::zero()));

// Fold Igz for rb and rc using alpha and beta
igz = igz_n_to_1_fold(&[&rb, &rc], &alpha_n_beta);
let igz = if i == circuit.layers.len() {
generate_eq(&g)
} else {
let (rb, rc) = (
challenges[..challenges.len() / 2].to_vec(),
challenges[challenges.len() / 2..].to_vec(),
);
igz_n_to_1_fold(&[&rb, &rc], &alpha_n_beta)
};

// Gets new addi and muli based on rb, rc, alpha and beta
(mul_ahg, add_b_ahg, add_c_ahg) =
let (mul_ahg, add_b_ahg, add_c_ahg) =
prepare_phase_one_params(&igz, &add_i, &mul_i, &w_i_plus_one_poly.evaluations);

// Proves sumcheck relation using Libra algorithms
(sumcheck_proof, wb, wc) = prove_libra_sumcheck(
let (sumcheck_proof, wb_eval, wc_eval) = prove_libra_sumcheck(
ProveLibraInput {
claimed_sum: &claimed_sum,
igz: &igz,
Expand All @@ -156,23 +105,18 @@ pub fn prove<F: Field + PrimeField32, E: ExtensionField<F>>(
&mut transcript,
);

(rb, rc) = (
sumcheck_proof.challenges[..sumcheck_proof.challenges.len() / 2].to_vec(),
sumcheck_proof.challenges[sumcheck_proof.challenges.len() / 2..].to_vec(),
);
wb = wb_eval;
wc = wc_eval;
challenges = sumcheck_proof.challenges.to_vec();

// Adds the messages to the transcript
transcript.observe_ext_element(&[wb.to_extension_field()]);
transcript.observe_ext_element(&[wc.to_extension_field()]);
transcript.observe_ext_element(&[sumcheck_proof.claimed_sum.to_extension_field()]);
transcript.observe_ext_element(&sumcheck_proof.round_polynomials.iter().fold(
transcript.observe(&[wb]);
transcript.observe(&[wc]);
transcript.observe(&[sumcheck_proof.claimed_sum]);
transcript.observe(&sumcheck_proof.round_polynomials.iter().fold(
vec![],
|mut acc, val| {
acc.extend(
val.iter()
.map(|val| val.to_extension_field())
.collect::<Vec<E>>(),
);
acc.extend(val);
acc
},
));
Expand All @@ -181,13 +125,6 @@ pub fn prove<F: Field + PrimeField32, E: ExtensionField<F>>(
sumcheck_proofs.push(sumcheck_proof);
wbs.push(wb);
wcs.push(wc);

// Sample alpha and beta
alpha_n_beta = transcript
.sample_n_challenges(2)
.iter()
.map(|val| Fields::Extension(*val))
.collect::<Vec<Fields<F, E>>>();
}

LibraProof::new(
Expand Down
Loading
Loading