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