Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/types/batch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ host = ["dep:sbv-primitives", "dep:c-kzg"]

[dev-dependencies]
c-kzg = { workspace = true }
rand = { version = "0.9" }
2 changes: 1 addition & 1 deletion crates/types/batch/src/blob_consistency/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod openvm;
mod types;

pub use openvm::point_evaluation;
pub use openvm::{kzg_to_versioned_hash, verify_kzg_proof};
pub use openvm::{is_in_g1_subgroup, kzg_to_versioned_hash, verify_kzg_proof};
pub use types::ToIntrinsic;

// Number of bytes in a u256.
Expand Down
91 changes: 89 additions & 2 deletions crates/types/batch/src/blob_consistency/openvm.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::ops::{AddAssign, MulAssign};
use std::slice;
use std::sync::LazyLock;

use algebra::{Field, IntMod};
use alloy_primitives::U256;
use alloy_primitives::{U256, hex};
use halo2curves_axiom::bls12_381::G2Affine as Bls12_381_G2;
use itertools::Itertools;
use openvm_ecc_guest::{AffinePoint, CyclicGroup, msm, weierstrass::WeierstrassPoint};
use openvm_pairing::bls12_381::{Bls12_381, G1Affine, G2Affine, Scalar};
use openvm_pairing::bls12_381::{Bls12_381, Fp, G1Affine, G2Affine, Scalar};
use openvm_pairing_guest::{algebra, pairing::PairingCheck};

use super::types::ToIntrinsic;
Expand Down Expand Up @@ -51,6 +52,16 @@ static KZG_G2_SETUP: LazyLock<G2Affine> = LazyLock::new(|| {
.to_intrinsic()
});

// Nontrivial BLS12-381 Fp cube root of unity used by the G1 endomorphism;
// bytes are little-endian.
static BETA: Fp = Fp::from_const_bytes(hex!(
"fefffeffffff012e02000a6213d817de8896f8e63ba9b3ddea770f6a07c669ba51ce76df2f67195f0000000000000000"
));
// BLS_X^2 in the BLS12-381 scalar field; bytes are little-endian.
static X_SQUARE: Scalar = Scalar::from_const_bytes(hex!(
"000000000100000002a4010001a445ac00000000000000000000000000000000"
));

/// The version for KZG as per EIP-4844.
const VERSIONED_HASH_VERSION_KZG: u8 = 1;

Expand Down Expand Up @@ -145,6 +156,17 @@ fn interpolate(z: &Scalar, coefficients: &[Scalar; BLOB_WIDTH]) -> Scalar {
* Scalar::from_u64(blob_width).invert()
}

pub fn is_in_g1_subgroup(p: &G1Affine) -> bool {
let expected_x = p.x() * &BETA;
let expected_y = -p.y();

// [x^2] * P
let actual_point = msm(slice::from_ref(&X_SQUARE), slice::from_ref(p));

// [x^2]P == (BETA * x, -y)
actual_point.x() == &expected_x && actual_point.y() == &expected_y
}

#[cfg(test)]
mod test {
use super::*;
Expand Down Expand Up @@ -192,4 +214,69 @@ mod test {
let proof_ok = verify_kzg_proof(z, y, commitment, proof);
assert!(proof_ok, "verify failed");
}

/// BETA is a nontrivial cube root of unity mod p
#[test]
fn test_beta() {
use halo2curves_axiom::bls12_381::Fq;
let beta = Fq::from_bytes_be(&BETA.to_be_bytes()).unwrap();

// BETA != 1
assert_ne!(beta, Fq::one(), "BETA != 1");
// BETA^3 mod p == 1
let beta_cubed = &beta * &beta * &beta;
assert_eq!(beta_cubed, Fq::one(), "BETA^3 == 1");
// BETA^2 + BETA + 1 mod p == 0
assert_eq!(
&beta * &beta + beta + Fq::one(),
Fq::zero(),
"BETA^2 + BETA + 1 == 0"
);
}

#[test]
fn test_x_square() {
use halo2curves_axiom::bls12_381::BLS_X;
let x = -Scalar::from_u64(BLS_X);
let x_square = &x * &x;
assert_eq!(x_square, X_SQUARE);
}

#[test]
fn test_g1_generator_is_in_subgroup() {
assert!(is_in_g1_subgroup(&G1Affine::GENERATOR));
}

#[test]
fn test_point_on_curve_but_not_in_subgroup() {
use halo2curves_axiom::{CurveAffine, bls12_381::Fq};

let four = Fq::one() + Fq::one() + Fq::one() + Fq::one();

for _ in 0..1024 {
let x_bytes: [u8; 48] = rand::random();
let Some(x) = Fq::from_bytes(&x_bytes).into_option() else {
continue;
};
let x3 = x.square() * x.clone();
let x3_plus_4 = x3 + four.clone();

let Some(y) = x3_plus_4.sqrt().into_option() else {
continue;
};

let point_axiom = Bls12_381_G1::from_xy(x, y).unwrap();

// point in group
let is_torsion_free: bool = point_axiom.is_torsion_free().into();
if is_torsion_free {
continue;
}

assert!(!is_in_g1_subgroup(&point_axiom.to_intrinsic()));
return;
}

panic!("failed to find a non-subgroup curve point");
}
}
14 changes: 12 additions & 2 deletions crates/types/batch/src/witness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use types_base::{
};

use crate::{
blob_consistency::{ToIntrinsic, is_in_g1_subgroup},
builder::{
BatchInfoBuilder, BatchInfoBuilderV6, BatchInfoBuilderV7, BuilderArgsV6, BuilderArgsV7,
validium::{ValidiumBatchInfoBuilder, ValidiumBuilderArgs},
Expand Down Expand Up @@ -74,14 +75,23 @@ pub fn build_intrinsic_point(
use openvm_pairing::bls12_381::{Fp, G1Affine};
let x = Fp::from_be_bytes(&x)?;
let y = Fp::from_be_bytes(&y)?;
unsafe { G1Affine::from_xy(x, y) }
// SAFETY: We have already verified the point is in the G1 subgroup via `is_in_g1_subgroup` check.
let p = unsafe { G1Affine::from_xy(x, y)? };
is_in_g1_subgroup(&p).then_some(p)
}

pub fn build_point(x: Bytes48, y: Bytes48) -> Option<halo2curves_axiom::bls12_381::G1Affine> {
use halo2curves_axiom::bls12_381::{Fq, G1Affine};
let x = Fq::from_bytes_be(&x).into_option()?;
let y = Fq::from_bytes_be(&y).into_option()?;
G1Affine::from_xy(x, y).into_option()
let p = G1Affine::from_xy(x, y).into_option()?;

let is_in_g1_subgroup = is_in_g1_subgroup(&p.to_intrinsic());

#[cfg(feature = "host")]
assert_eq!(is_in_g1_subgroup, bool::from(p.is_torsion_free()));

is_in_g1_subgroup.then_some(p)
}

/// Witness to the batch circuit.
Expand Down
Loading