Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

Blobkzg Circuit for EIP 4844 #106

Closed
wants to merge 3 commits into from
Closed

Blobkzg Circuit for EIP 4844 #106

wants to merge 3 commits into from

Conversation

mabbamOG
Copy link

@mabbamOG mabbamOG commented Jun 5, 2023

Description

This PR adds the circuit required to integrate Taiko with EIP 4844. Basically it takes data stored in the circuit and uses it for function evaluation. This is then compared with the blob opening checked in the smart contract with EIP 4844. Make sure the data fed into this circuit is checked to be the same as all the other claims of the proof.

Issue Link

[link issue here]

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Contents

Contain the new circuit as well as dependencies which support the latest traits along with the latest halo2curves (hence the breaking change...but the circuit has not been integrated into the rest of the zkEVM so everything compiles for now)

Rationale

Required for the L2 to make use of EIP 4844 Blobs, greatly improving costs for publishing new proofs

How Has This Been Tested?

Unit tests (more to come)


fn configure(meta: &mut ConstraintSystem<N>) -> Self::Config {
let integer_config = {
const BIT_LEN_LIMB: usize = 68;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move constant variable declarations BIT_LEN_LIMB and NUMBER_OF_LIMBS to the top of a file?

ctx: &mut RegionCtx<N>,
x: W
) -> Result<AssignedInteger<W, N, NUMBER_OF_LIMBS, BIT_LEN_LIMB>, Error> {
let rc_rns: std::rc::Rc<Rns<W, N, NUMBER_OF_LIMBS, BIT_LEN_LIMB>> = std::rc::Rc::new(Rns::<W, N, NUMBER_OF_LIMBS, BIT_LEN_LIMB>::construct());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we change the code format to

let rc_rns = std::rc::Rc::new(Rns::<W, N, NUMBER_OF_LIMBS, BIT_LEN_LIMB>::construct());

?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, was just using explicit typing bc in this part of the code I had to be really careful to the types

let v: Value<Integer<W, N, NUMBER_OF_LIMBS, BIT_LEN_LIMB>> = Value::known(i);
let u: UnassignedInteger<W, N, NUMBER_OF_LIMBS, BIT_LEN_LIMB> = UnassignedInteger::from(v);
// TODO: is Range::Operand a correct range here?
let a: AssignedInteger<W, N, NUMBER_OF_LIMBS, BIT_LEN_LIMB> = integer_chip.assign_integer(ctx, u, Range::Operand)?;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assigned integer

use halo2_proofs::circuit::{Layouter,SimpleFloorPlanner,Value};
use halo2_proofs::halo2curves::ff::PrimeField as FieldExt;
use halo2wrong_integer::{IntegerConfig, rns::Rns, IntegerChip, IntegerInstructions, rns::Integer, UnassignedInteger, Range, AssignedInteger};
use halo2wrong_maingate::{RangeChip, MainGate, RegionCtx};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation to use halo2wrong_maingate here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's required for some operations, I think to allow me to setup the IntegerConfig with its underlying Range gate Config and Maingate Config. also I think that's where the RegionCtx operations come from.

}
x2is
};
let nbits: [W; 256] = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, I do not no Halo2Wrong API and do not sure that I have correctly understood the related parts. However, if they work as I suppose and bit factorization of n (log2 of the order of w) is predefined, than I think that it should work correctly. This part of the code (the lines 54-71) can be written in a shorter way (defining nbits[j*8+i] directly without the boolean array), but it is not a problem.

Copy link

@Brechtpd Brechtpd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use cargo fmt to format the code. Yeah I know it messes up the code how you want it to look, but it does make it easier to see the code in a nice standard way.

It seems like some tests did not pass as expected:

running 6 tests
test tests::wrong_witness_data ... FAILED
test tests::wrong_x - should panic ... ok
test tests::try_default has been running for over 60 seconds
test tests::try_random_datapoints has been running for over 60 seconds
test tests::wrong_n has been running for over 60 seconds
test tests::wrong_w has been running for over 60 seconds
test tests::try_default ... ok
test tests::wrong_n - should panic ... FAILED
test tests::wrong_w - should panic ... FAILED
test tests::try_random_datapoints ... ok

failures:

---- tests::wrong_witness_data stdout ----
thread 'tests::wrong_witness_data' panicked at 'not implemented', src/main.rs:519:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- tests::wrong_n stdout ----
note: test did not panic as expected
---- tests::wrong_w stdout ----
note: test did not panic as expected

failures:
    tests::wrong_n
    tests::wrong_w
    tests::wrong_witness_data

test result: FAILED. 3 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 144.90s

wrong_witness_data makes sense how it is now (but please change it so all tests pass even if still to do), but the others seems like unexpected.


#[derive(Debug)]
pub struct BlobKZGCircuit<W: PrimeField, N: PrimeField>{ // W is the wrong field, N is the native field
datapoints: [W; 4096],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be good to use const variables everywhere like NUM_POINTS instead of the constants like 4096/256/... themselves.

use halo2wrong_maingate::{RangeChip, MainGate, RegionCtx};

/// THIS CIRCUIT PERFORMS A NON-NATIVE FUNCTION EVAL USING BARYCENTRIC-FORMULA
/// P(X) = (const) * sum (d_i * (w^i/x-w^i)) where const = (x^N - 1)/N with w^N = 1 and X^N != 1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be nice to have some more comments explaining about the exact approach, and which values are calculated in the circuit and how, and which values are precomputed. Some comments which variable is what because the same variable names are used in the code would also be nice.

impl<W: PrimeField> WitnessValues<W> {
fn new(x:&W, n:&W, w:&W, datapoints: &[W; 4096]) -> WitnessValues<W> {
let x2is: [W; 256] = {
let mut x2is = [W::default(); 256];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of W::default(), let's just use W::ZERO everywhere, that makes things more clear.

}
}
impl<W: PrimeField> WitnessValues<W> {
fn new(x:&W, n:&W, w:&W, datapoints: &[W; 4096]) -> WitnessValues<W> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can use some simple comments for each step here so it's more clear what it being done and why. For example one big one could be // Split up n into bits so we can calculate the exponent using the square and multiply. and the x2is code would be // Calculate the exponent for each bit position. You get the idea.

fn new(x:&W, n:&W, w:&W, datapoints: &[W; 4096]) -> WitnessValues<W> {
let x2is: [W; 256] = {
let mut x2is = [W::default(); 256];
let mut state = *x;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of the name state, acc or something like that is a more standard name I think.

Comment on lines +197 to +201
let assigned_x = assign_wrong_integer(&integer_chip, &mut ctx, self.x)?;
let assigned_w = assign_wrong_integer(&integer_chip, &mut ctx, self.w)?;
let assigned_zero = assign_wrong_integer(&integer_chip, &mut ctx, W::ZERO)?;
let assigned_one = assign_wrong_integer(&integer_chip, &mut ctx, W::ONE)?;
let assigned_n = assign_wrong_integer(&integer_chip, &mut ctx, self.n)?;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these values (and some below here as well) are being assigned using assign_wrong_integer which internally will use assign_integer. But some of these are constants and need to have a fixed value, so shouldn't those use assign_constant?

}).collect::<Result<Vec<_>, Error>>()?;
assert!(assigned_datapoints.len() == 4096);

// ctx = RegionCtx::new(ctx.into_region(), 0);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this is some old comment?

// TODO: check _div_result?
integer_chip.add(&mut ctx, &assigned_evals[i], &summand)?
};
integer_chip.assert_equal(&mut ctx, &assigned_evals[i+1], &eval_next)?;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do all these assigned_evals? Can't we just use eval_next in the next loop iteration instead of assigned_evals[i]?

Comment on lines +265 to +266
// TODO: integer_chip operations (.mul, .sub) actually assigned NEW values to the circuit!!! how to fix? Because .assert_equal only seems to work between AssignedInteger<> values...
// ctx = RegionCtx::new(ctx.into_region(), 0);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still a TODO?

use halo2wrong_maingate::RangeInstructions;
integer_chip.range_chip().load_table(&mut layouter)?;

layouter.assign_region(|| "BlobKZGCircuit Region", | region| {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more comments here on what does what would also be nice.

@mabbamOG mabbamOG deleted the branch taikoxyz:main June 26, 2023 12:15
@mabbamOG mabbamOG closed this Jun 26, 2023
@mabbamOG mabbamOG deleted the main branch June 26, 2023 12:15
@Brechtpd Brechtpd mentioned this pull request Jul 4, 2023
4 tasks
@AlekseiVambol
Copy link

AlekseiVambol commented Oct 9, 2023

An important remark about the correct way of choosing the pseudorandom evaluation point x for the polynomial encoding the data. The PR is aimed at implementing protocol on the screenshot.
image
Source: https://notes.ethereum.org/@vbuterin/proto_danksharding_faq#Moderate-approach-works-with-any-ZK-SNARK

Note that the first 2 steps insist on choosing the value of x as the hash of both the polynomial outside the circuit and its evaluations inside the circuit. This means that (in the case of one-phase synthesis) the circuit must contain the constraints representing the hashing of the data polynomial evaluations. This inner hash value should be passed as a public input to the circuit to make the result of the in-circuit hashing accessible to the verifier. When a smart contract verifies the proof for the circuit, it will be given this inner hash and use it along with the hash of the polynomial outside the circuit to compute the evaluation point x, which is also a public input.

Why do we need this inner hashing? To force the prover to choose the value of x strictly after the data polynomial evaluations in the circuit are fixed. If prover is allowed to change these evaluations after x is chosen, he can interpolate the polynomial, which is not equal to the one outside the circuit, but coincide with it at x. He can do it with polynomial time and space complexity and is free to choose the values at any 4095 of 4096 evaluation points!

How could we simplify this approach? I think we can avoid writing the hash constraints, if we have a tricky proving system that allows two-phase synthesis, has the challenge API (returning the hash of the witnesses committed at the first phase) and allows us to generate some public input data between the phases of the synthesis. In this case the challenge value can be used instead of the aforesaid inner hash value - it can be used (along with the public input part representing the hash of the polynomial outside the circuit) to compute x between the synthesis phases. The value of x will be added to the public input before the second phase of synthesis. Unfortunately, it seems that Halo2 does not support this dynamic public input generation.

@Brechtpd
Copy link

@AlekseiVambol Could we reuse the KZG commitment for the column storing the data that could already be available for the verifier (?) instead of explicitly committing to the data in-circuit?

@AlekseiVambol
Copy link

Yes.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants