revm_precompile/
kzg_point_evaluation.rs

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