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