revm_precompile/
kzg_point_evaluation.rs

1//! KZG point evaluation precompile added in [`EIP-4844`](https://eips.ethereum.org/EIPS/eip-4844)
2//! For more details check [`run`] function.
3use crate::{Address, PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress};
4cfg_if::cfg_if! {
5    if #[cfg(feature = "c-kzg")] {
6        use c_kzg::{Bytes32, Bytes48};
7    } else if #[cfg(feature = "kzg-rs")] {
8        use kzg_rs::{Bytes32, Bytes48, KzgProof};
9    }
10}
11use primitives::hex_literal::hex;
12use sha2::{Digest, Sha256};
13
14/// KZG point evaluation precompile, containing address and function to run.
15pub const POINT_EVALUATION: PrecompileWithAddress = PrecompileWithAddress(ADDRESS, run);
16
17/// Address of the KZG point evaluation precompile.
18pub const ADDRESS: Address = crate::u64_to_address(0x0A);
19
20/// Gas cost of the KZG point evaluation precompile.
21pub const GAS_COST: u64 = 50_000;
22
23/// Versioned hash version for KZG.
24pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
25
26/// `U256(FIELD_ELEMENTS_PER_BLOB).to_be_bytes() ++ BLS_MODULUS.to_bytes32()`
27pub const RETURN_VALUE: &[u8; 64] = &hex!(
28    "0000000000000000000000000000000000000000000000000000000000001000"
29    "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
30);
31
32/// Run kzg point evaluation precompile.
33///
34/// The Env has the KZGSettings that is needed for evaluation.
35///
36/// The input is encoded as follows:
37/// | versioned_hash |  z  |  y  | commitment | proof |
38/// |     32         | 32  | 32  |     48     |   48  |
39/// with z and y being padded 32 byte big endian values
40pub fn run(input: &[u8], gas_limit: u64) -> PrecompileResult {
41    if gas_limit < GAS_COST {
42        return Err(PrecompileError::OutOfGas);
43    }
44
45    // Verify input length.
46    if input.len() != 192 {
47        return Err(PrecompileError::BlobInvalidInputLength);
48    }
49
50    // Verify commitment matches versioned_hash
51    let versioned_hash = &input[..32];
52    let commitment = &input[96..144];
53    if kzg_to_versioned_hash(commitment) != versioned_hash {
54        return Err(PrecompileError::BlobMismatchedVersion);
55    }
56
57    // Verify KZG proof with z and y in big endian format
58    let commitment = as_bytes48(commitment);
59    let z = as_bytes32(&input[32..64]);
60    let y = as_bytes32(&input[64..96]);
61    let proof = as_bytes48(&input[144..192]);
62    if !verify_kzg_proof(commitment, z, y, proof) {
63        return Err(PrecompileError::BlobVerifyKzgProofFailed);
64    }
65
66    // Return FIELD_ELEMENTS_PER_BLOB and BLS_MODULUS as padded 32 byte big endian values
67    Ok(PrecompileOutput::new(GAS_COST, RETURN_VALUE.into()))
68}
69
70/// `VERSIONED_HASH_VERSION_KZG ++ sha256(commitment)[1..]`
71#[inline]
72pub fn kzg_to_versioned_hash(commitment: &[u8]) -> [u8; 32] {
73    let mut hash: [u8; 32] = Sha256::digest(commitment).into();
74    hash[0] = VERSIONED_HASH_VERSION_KZG;
75    hash
76}
77
78/// Verify KZG proof.
79#[inline]
80pub fn verify_kzg_proof(commitment: &Bytes48, z: &Bytes32, y: &Bytes32, proof: &Bytes48) -> bool {
81    cfg_if::cfg_if! {
82        if #[cfg(feature = "c-kzg")] {
83            let kzg_settings = c_kzg::ethereum_kzg_settings(0);
84            kzg_settings.verify_kzg_proof(commitment, z, y, proof).unwrap_or(false)
85        } else if #[cfg(feature = "kzg-rs")] {
86            let env = kzg_rs::EnvKzgSettings::default();
87            let kzg_settings = env.get();
88            KzgProof::verify_kzg_proof(commitment, z, y, proof, kzg_settings).unwrap_or(false)
89        }
90    }
91}
92
93/// Convert a slice to an array of a specific size.
94#[inline]
95#[track_caller]
96pub fn as_array<const N: usize>(bytes: &[u8]) -> &[u8; N] {
97    bytes.try_into().expect("slice with incorrect length")
98}
99
100/// Convert a slice to a 32 byte big endian array.
101#[inline]
102#[track_caller]
103pub fn as_bytes32(bytes: &[u8]) -> &Bytes32 {
104    // SAFETY: `#[repr(C)] Bytes32([u8; 32])`
105    unsafe { &*as_array::<32>(bytes).as_ptr().cast() }
106}
107
108/// Convert a slice to a 48 byte big endian array.
109#[inline]
110#[track_caller]
111pub fn as_bytes48(bytes: &[u8]) -> &Bytes48 {
112    // SAFETY: `#[repr(C)] Bytes48([u8; 48])`
113    unsafe { &*as_array::<48>(bytes).as_ptr().cast() }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn basic_test() {
122        // Test data from: https://github.com/ethereum/c-kzg-4844/blob/main/tests/verify_kzg_proof/kzg-mainnet/verify_kzg_proof_case_correct_proof_31ebd010e6098750/data.yaml
123
124        let commitment = hex!("8f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7").to_vec();
125        let mut versioned_hash = Sha256::digest(&commitment).to_vec();
126        versioned_hash[0] = VERSIONED_HASH_VERSION_KZG;
127        let z = hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000").to_vec();
128        let y = hex!("1522a4a7f34e1ea350ae07c29c96c7e79655aa926122e95fe69fcbd932ca49e9").to_vec();
129        let proof = hex!("a62ad71d14c5719385c0686f1871430475bf3a00f0aa3f7b8dd99a9abc2160744faf0070725e00b60ad9a026a15b1a8c").to_vec();
130
131        let input = [versioned_hash, z, y, commitment, proof].concat();
132
133        let expected_output = hex!("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");
134        let gas = 50000;
135        let output = run(&input, gas).unwrap();
136        assert_eq!(output.gas_used, gas);
137        assert_eq!(output.bytes[..], expected_output);
138    }
139}