Skip to main content

revm_precompile/kzg_point_evaluation/
arkworks.rs

1//! KZG point evaluation precompile using Arkworks BLS12-381 implementation.
2use crate::{
3    bls12_381::arkworks::pairing_check, bls12_381_const::TRUSTED_SETUP_TAU_G2_BYTES,
4    PrecompileError,
5};
6use ark_bls12_381::{Fr, G1Affine, G2Affine};
7use ark_ec::{AffineRepr, CurveGroup};
8use ark_ff::{BigInteger, PrimeField};
9use ark_serialize::CanonicalDeserialize;
10use core::ops::Neg;
11use primitives::OnceLock;
12
13/// Verify KZG proof using BLS12-381 implementation.
14///
15/// <https://github.com/ethereum/EIPs/blob/4d2a00692bb131366ede1a16eced2b0e25b1bf99/EIPS/eip-4844.md?plain=1#L203>
16/// <https://github.com/ethereum/consensus-specs/blob/master/specs/deneb/polynomial-commitments.md#verify_kzg_proof_impl>
17#[inline]
18pub fn verify_kzg_proof(
19    commitment: &[u8; 48],
20    z: &[u8; 32],
21    y: &[u8; 32],
22    proof: &[u8; 48],
23) -> bool {
24    // Parse the commitment (G1 point)
25    let Ok(commitment_point) = parse_g1_compressed(commitment) else {
26        return false;
27    };
28
29    // Parse the proof (G1 point)
30    let Ok(proof_point) = parse_g1_compressed(proof) else {
31        return false;
32    };
33
34    // Parse z and y as field elements (Fr, scalar field)
35    // We expect 32-byte big-endian scalars that must be canonical
36    let Ok(z_fr) = read_scalar_canonical(z) else {
37        return false;
38    };
39    let Ok(y_fr) = read_scalar_canonical(y) else {
40        return false;
41    };
42
43    // Get the trusted setup G2 point [τ]₂
44    let tau_g2 = get_trusted_setup_g2();
45
46    // Get generators
47    let g1 = get_g1_generator();
48    let g2 = get_g2_generator();
49
50    // Compute P_minus_y = commitment - [y]G₁
51    let y_g1 = p1_scalar_mul(&g1, &y_fr);
52    let p_minus_y = p1_sub_affine(&commitment_point, &y_g1);
53
54    // Compute X_minus_z = [τ]G₂ - [z]G₂
55    let z_g2 = p2_scalar_mul(&g2, &z_fr);
56    let x_minus_z = p2_sub_affine(tau_g2, &z_g2);
57
58    // Verify: P - y = Q * (X - z)
59    // Using pairing check: e(P - y, -G₂) * e(proof, X - z) == 1
60    let neg_g2 = p2_neg(&g2);
61
62    pairing_check(&[(p_minus_y, neg_g2), (proof_point, x_minus_z)])
63}
64
65/// Get the trusted setup G2 point `[τ]₂` from the Ethereum KZG ceremony.
66/// This is g2_monomial_1 from trusted_setup_4096.json
67fn get_trusted_setup_g2() -> &'static G2Affine {
68    static TAU_G2: OnceLock<G2Affine> = OnceLock::new();
69    TAU_G2.get_or_init(|| {
70        // Parse the compressed G2 point using unchecked deserialization since we trust this point
71        // This should never fail since we're using a known valid point from the trusted setup
72        G2Affine::deserialize_compressed_unchecked(&TRUSTED_SETUP_TAU_G2_BYTES[..])
73            .expect("Failed to parse trusted setup G2 point")
74    })
75}
76
77/// Parse a G1 point from compressed format (48 bytes)
78fn parse_g1_compressed(bytes: &[u8; 48]) -> Result<G1Affine, PrecompileError> {
79    G1Affine::deserialize_compressed(&bytes[..]).map_err(|_| PrecompileError::KzgInvalidG1Point)
80}
81
82/// Read a scalar field element from bytes and verify it's canonical
83fn read_scalar_canonical(bytes: &[u8; 32]) -> Result<Fr, PrecompileError> {
84    let fr = Fr::from_be_bytes_mod_order(bytes);
85
86    // Check if the field element is canonical by serializing back and comparing
87    let bytes_roundtrip = fr.into_bigint().to_bytes_be();
88
89    if bytes_roundtrip.as_slice() != bytes {
90        return Err(PrecompileError::NonCanonicalFp);
91    }
92
93    Ok(fr)
94}
95
96/// Get G1 generator point
97#[inline]
98fn get_g1_generator() -> G1Affine {
99    G1Affine::generator()
100}
101
102/// Get G2 generator point
103#[inline]
104fn get_g2_generator() -> G2Affine {
105    G2Affine::generator()
106}
107
108/// Scalar multiplication for G1 points
109#[inline]
110fn p1_scalar_mul(point: &G1Affine, scalar: &Fr) -> G1Affine {
111    point.mul_bigint(scalar.into_bigint()).into_affine()
112}
113
114/// Scalar multiplication for G2 points
115#[inline]
116fn p2_scalar_mul(point: &G2Affine, scalar: &Fr) -> G2Affine {
117    point.mul_bigint(scalar.into_bigint()).into_affine()
118}
119
120/// Subtract two G1 points in affine form
121#[inline]
122fn p1_sub_affine(a: &G1Affine, b: &G1Affine) -> G1Affine {
123    (a.into_group() - b.into_group()).into_affine()
124}
125
126/// Subtract two G2 points in affine form
127#[inline]
128fn p2_sub_affine(a: &G2Affine, b: &G2Affine) -> G2Affine {
129    (a.into_group() - b.into_group()).into_affine()
130}
131
132/// Negate a G2 point
133#[inline]
134fn p2_neg(p: &G2Affine) -> G2Affine {
135    p.neg()
136}