revm_precompile/kzg_point_evaluation/
arkworks.rs

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