revm_precompile/kzg_point_evaluation/
blst.rs

1//! KZG point evaluation precompile using BLST BLS12-381 implementation.
2use crate::bls12_381::blst::{
3    p1_add_or_double, p1_from_affine, p1_scalar_mul, p1_to_affine, p2_add_or_double,
4    p2_from_affine, p2_scalar_mul, p2_to_affine, pairing_check,
5};
6use crate::bls12_381_const::TRUSTED_SETUP_TAU_G2_BYTES;
7use crate::PrecompileError;
8use ::blst::{
9    blst_p1_affine, blst_p1_affine_in_g1, blst_p1_affine_on_curve, blst_p2_affine, blst_scalar,
10    blst_scalar_fr_check, blst_scalar_from_bendian,
11};
12
13/// Verify KZG proof using BLST 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    let Ok(z_scalar) = read_scalar_canonical(z) else {
36        return false;
37    };
38    let Ok(y_scalar) = 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_scalar);
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_scalar);
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() -> blst_p2_affine {
67    // For compressed G2, we need to decompress
68    let mut g2_affine = blst_p2_affine::default();
69    unsafe {
70        // The compressed format has x coordinate and a flag bit for y
71        // We use deserialize_compressed which handles this automatically
72        let result = blst::blst_p2_deserialize(&mut g2_affine, TRUSTED_SETUP_TAU_G2_BYTES.as_ptr());
73        if result != blst::BLST_ERROR::BLST_SUCCESS {
74            panic!("Failed to deserialize trusted setup G2 point");
75        }
76    }
77    g2_affine
78}
79
80/// Get G1 generator point
81fn get_g1_generator() -> blst_p1_affine {
82    unsafe { ::blst::BLS12_381_G1 }
83}
84
85/// Get G2 generator point
86fn get_g2_generator() -> blst_p2_affine {
87    unsafe { ::blst::BLS12_381_G2 }
88}
89
90/// Parse a G1 point from compressed format (48 bytes)
91fn parse_g1_compressed(bytes: &[u8; 48]) -> Result<blst_p1_affine, PrecompileError> {
92    let mut point = blst_p1_affine::default();
93    unsafe {
94        let result = blst::blst_p1_deserialize(&mut point, bytes.as_ptr());
95        if result != blst::BLST_ERROR::BLST_SUCCESS {
96            return Err(PrecompileError::KzgInvalidG1Point);
97        }
98
99        // Verify the point is on curve
100        if !blst_p1_affine_on_curve(&point) {
101            return Err(PrecompileError::KzgG1PointNotOnCurve);
102        }
103
104        // Verify the point is in the correct subgroup
105        if !blst_p1_affine_in_g1(&point) {
106            return Err(PrecompileError::KzgG1PointNotInSubgroup);
107        }
108    }
109    Ok(point)
110}
111
112/// Read a scalar field element from bytes and verify it's canonical
113fn read_scalar_canonical(bytes: &[u8; 32]) -> Result<blst_scalar, PrecompileError> {
114    let mut scalar = blst_scalar::default();
115
116    // Read scalar from big endian bytes
117    unsafe {
118        blst_scalar_from_bendian(&mut scalar, bytes.as_ptr());
119    }
120
121    if unsafe { !blst_scalar_fr_check(&scalar) } {
122        return Err(PrecompileError::NonCanonicalFp);
123    }
124
125    Ok(scalar)
126}
127
128/// Subtract two G1 points in affine form
129fn p1_sub_affine(a: &blst_p1_affine, b: &blst_p1_affine) -> blst_p1_affine {
130    // Convert first point to Jacobian
131    let a_jacobian = p1_from_affine(a);
132
133    // Negate second point
134    let neg_b = p1_neg(b);
135
136    // Add a + (-b)
137    let result = p1_add_or_double(&a_jacobian, &neg_b);
138
139    p1_to_affine(&result)
140}
141
142/// Subtract two G2 points in affine form
143fn p2_sub_affine(a: &blst_p2_affine, b: &blst_p2_affine) -> blst_p2_affine {
144    // Convert first point to Jacobian
145    let a_jacobian = p2_from_affine(a);
146
147    // Negate second point
148    let neg_b = p2_neg(b);
149
150    // Add a + (-b)
151    let result = p2_add_or_double(&a_jacobian, &neg_b);
152
153    p2_to_affine(&result)
154}
155
156/// Negate a G1 point
157fn p1_neg(p: &blst_p1_affine) -> blst_p1_affine {
158    // Convert to Jacobian, negate, convert back
159    let mut p_jacobian = p1_from_affine(p);
160    unsafe {
161        ::blst::blst_p1_cneg(&mut p_jacobian, true);
162    }
163    p1_to_affine(&p_jacobian)
164}
165
166/// Negate a G2 point
167fn p2_neg(p: &blst_p2_affine) -> blst_p2_affine {
168    // Convert to Jacobian, negate, convert back
169    let mut p_jacobian = p2_from_affine(p);
170    unsafe {
171        ::blst::blst_p2_cneg(&mut p_jacobian, true);
172    }
173    p2_to_affine(&p_jacobian)
174}