Skip to main content

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