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