revm_precompile/kzg_point_evaluation/
blst.rs

1//! KZG point evaluation precompile using BLST BLS12-381 implementation.
2use crate::{
3    bls12_381::blst::{
4        p1_add_or_double, p1_from_affine, p1_scalar_mul, p1_to_affine, p2_add_or_double,
5        p2_from_affine, p2_scalar_mul, p2_to_affine, pairing_check,
6    },
7    bls12_381_const::TRUSTED_SETUP_TAU_G2_BYTES,
8    PrecompileError,
9};
10use ::blst::{
11    blst_p1_affine, blst_p1_affine_in_g1, blst_p1_affine_on_curve, blst_p2_affine, blst_scalar,
12    blst_scalar_fr_check, blst_scalar_from_bendian,
13};
14
15/// Verify KZG proof using BLST BLS12-381 implementation.
16///
17/// <https://github.com/ethereum/EIPs/blob/4d2a00692bb131366ede1a16eced2b0e25b1bf99/EIPS/eip-4844.md?plain=1#L203>
18/// <https://github.com/ethereum/consensus-specs/blob/master/specs/deneb/polynomial-commitments.md#verify_kzg_proof_impl>
19#[inline]
20pub fn verify_kzg_proof(
21    commitment: &[u8; 48],
22    z: &[u8; 32],
23    y: &[u8; 32],
24    proof: &[u8; 48],
25) -> bool {
26    // Parse the commitment (G1 point)
27    let Ok(commitment_point) = parse_g1_compressed(commitment) else {
28        return false;
29    };
30
31    // Parse the proof (G1 point)
32    let Ok(proof_point) = parse_g1_compressed(proof) else {
33        return false;
34    };
35
36    // Parse z and y as field elements (Fr, scalar field)
37    let Ok(z_scalar) = read_scalar_canonical(z) else {
38        return false;
39    };
40    let Ok(y_scalar) = read_scalar_canonical(y) else {
41        return false;
42    };
43
44    // Get the trusted setup G2 point [τ]₂
45    let tau_g2 = get_trusted_setup_g2();
46
47    // Get generators
48    let g1 = get_g1_generator();
49    let g2 = get_g2_generator();
50
51    // Compute P_minus_y = commitment - [y]G₁
52    let y_g1 = p1_scalar_mul(&g1, &y_scalar);
53    let p_minus_y = p1_sub_affine(&commitment_point, &y_g1);
54
55    // Compute X_minus_z = [τ]G₂ - [z]G₂
56    let z_g2 = p2_scalar_mul(&g2, &z_scalar);
57    let x_minus_z = p2_sub_affine(&tau_g2, &z_g2);
58
59    // Verify: P - y = Q * (X - z)
60    // Using pairing check: e(P - y, -G₂) * e(proof, X - z) == 1
61    let neg_g2 = p2_neg(&g2);
62
63    pairing_check(&[(p_minus_y, neg_g2), (proof_point, x_minus_z)])
64}
65
66/// Get the trusted setup G2 point `[τ]₂` from the Ethereum KZG ceremony.
67/// This is g2_monomial_1 from trusted_setup_4096.json
68fn get_trusted_setup_g2() -> blst_p2_affine {
69    // For compressed G2, we need to decompress
70    let mut g2_affine = blst_p2_affine::default();
71    unsafe {
72        // The compressed format has x coordinate and a flag bit for y
73        // We use uncompress which handles this automatically
74        let result = blst::blst_p2_uncompress(&mut g2_affine, TRUSTED_SETUP_TAU_G2_BYTES.as_ptr());
75        if result != blst::BLST_ERROR::BLST_SUCCESS {
76            panic!("Failed to deserialize trusted setup G2 point");
77        }
78    }
79    g2_affine
80}
81
82/// Get G1 generator point
83fn get_g1_generator() -> blst_p1_affine {
84    unsafe { ::blst::BLS12_381_G1 }
85}
86
87/// Get G2 generator point
88fn get_g2_generator() -> blst_p2_affine {
89    unsafe { ::blst::BLS12_381_G2 }
90}
91
92/// Parse a G1 point from compressed format (48 bytes)
93fn parse_g1_compressed(bytes: &[u8; 48]) -> Result<blst_p1_affine, PrecompileError> {
94    let mut point = blst_p1_affine::default();
95    unsafe {
96        let result = blst::blst_p1_uncompress(&mut point, bytes.as_ptr());
97        if result != blst::BLST_ERROR::BLST_SUCCESS {
98            return Err(PrecompileError::KzgInvalidG1Point);
99        }
100
101        // Verify the point is on curve
102        if !blst_p1_affine_on_curve(&point) {
103            return Err(PrecompileError::KzgG1PointNotOnCurve);
104        }
105
106        // Verify the point is in the correct subgroup
107        if !blst_p1_affine_in_g1(&point) {
108            return Err(PrecompileError::KzgG1PointNotInSubgroup);
109        }
110    }
111    Ok(point)
112}
113
114/// Read a scalar field element from bytes and verify it's canonical
115fn read_scalar_canonical(bytes: &[u8; 32]) -> Result<blst_scalar, PrecompileError> {
116    let mut scalar = blst_scalar::default();
117
118    // Read scalar from big endian bytes
119    unsafe {
120        blst_scalar_from_bendian(&mut scalar, bytes.as_ptr());
121    }
122
123    if unsafe { !blst_scalar_fr_check(&scalar) } {
124        return Err(PrecompileError::NonCanonicalFp);
125    }
126
127    Ok(scalar)
128}
129
130/// Subtract two G1 points in affine form
131fn p1_sub_affine(a: &blst_p1_affine, b: &blst_p1_affine) -> blst_p1_affine {
132    // Convert first point to Jacobian
133    let a_jacobian = p1_from_affine(a);
134
135    // Negate second point
136    let neg_b = p1_neg(b);
137
138    // Add a + (-b)
139    let result = p1_add_or_double(&a_jacobian, &neg_b);
140
141    p1_to_affine(&result)
142}
143
144/// Subtract two G2 points in affine form
145fn p2_sub_affine(a: &blst_p2_affine, b: &blst_p2_affine) -> blst_p2_affine {
146    // Convert first point to Jacobian
147    let a_jacobian = p2_from_affine(a);
148
149    // Negate second point
150    let neg_b = p2_neg(b);
151
152    // Add a + (-b)
153    let result = p2_add_or_double(&a_jacobian, &neg_b);
154
155    p2_to_affine(&result)
156}
157
158/// Negate a G1 point
159fn p1_neg(p: &blst_p1_affine) -> blst_p1_affine {
160    // Convert to Jacobian, negate, convert back
161    let mut p_jacobian = p1_from_affine(p);
162    unsafe {
163        ::blst::blst_p1_cneg(&mut p_jacobian, true);
164    }
165    p1_to_affine(&p_jacobian)
166}
167
168/// Negate a G2 point
169fn p2_neg(p: &blst_p2_affine) -> blst_p2_affine {
170    // Convert to Jacobian, negate, convert back
171    let mut p_jacobian = p2_from_affine(p);
172    unsafe {
173        ::blst::blst_p2_cneg(&mut p_jacobian, true);
174    }
175    p2_to_affine(&p_jacobian)
176}