cfx_executor/builtin/
kzg_point_evaluations.rs

1// Based on source code from the revm project (https://github.com/bluealloy/revm) under the MIT License.
2
3use c_kzg::{ethereum_kzg_settings, Bytes32, Bytes48, KzgSettings};
4
5use cfx_crypto::crypto::digest;
6use hex_literal::hex;
7use std::convert::TryInto;
8
9use super::Error;
10
11pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
12
13/// `U256(FIELD_ELEMENTS_PER_BLOB).to_be_bytes() ++ BLS_MODULUS.to_bytes32()`
14pub const RETURN_VALUE: &[u8; 64] = &hex!(
15    "0000000000000000000000000000000000000000000000000000000000001000"
16    "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
17);
18
19/// Run kzg point evaluation precompile.
20///
21/// The Env has the KZGSettings that is needed for evaluation.
22///
23/// The input is encoded as follows:
24/// | versioned_hash |  z  |  y  | commitment | proof |
25/// |     32         | 32  | 32  |     48     |   48  |
26/// with z and y being padded 32 byte big endian values
27pub fn run(input: &[u8]) -> Result<(), Error> {
28    // Verify input length.
29    if input.len() != 192 {
30        return Err("Blob invalid input length".into());
31    }
32
33    // Verify commitment matches versioned_hash
34    let versioned_hash = &input[..32];
35    let commitment = &input[96..144];
36    if kzg_to_versioned_hash(commitment) != versioned_hash {
37        return Err("Blob mismatched version".into());
38    }
39
40    // Verify KZG proof with z and y in big endian format
41    let commitment = as_bytes48(commitment);
42    let z = as_bytes32(&input[32..64]);
43    let y = as_bytes32(&input[64..96]);
44    let proof = as_bytes48(&input[144..192]);
45    if !verify_kzg_proof(commitment, z, y, proof, ethereum_kzg_settings(8)) {
46        return Err("Blob verify kzg proof failed".into());
47    }
48    Ok(())
49}
50
51/// `VERSIONED_HASH_VERSION_KZG ++ sha256(commitment)[1..]`
52#[inline]
53pub fn kzg_to_versioned_hash(commitment: &[u8]) -> [u8; 32] {
54    let mut hash = [0u8; 32];
55    hash.copy_from_slice(&*digest::sha256(commitment));
56    hash[0] = VERSIONED_HASH_VERSION_KZG;
57    hash
58}
59
60#[inline]
61pub fn verify_kzg_proof(
62    commitment: &Bytes48, z: &Bytes32, y: &Bytes32, proof: &Bytes48,
63    kzg_settings: &KzgSettings,
64) -> bool {
65    kzg_settings
66        .verify_kzg_proof(commitment, z, y, proof)
67        .unwrap_or(false)
68}
69
70#[inline]
71#[track_caller]
72pub fn as_array<const N: usize>(bytes: &[u8]) -> &[u8; N] {
73    bytes.try_into().expect("slice with incorrect length")
74}
75
76#[inline]
77#[track_caller]
78pub fn as_bytes32(bytes: &[u8]) -> &Bytes32 {
79    // SAFETY: `#[repr(C)] Bytes32([u8; 32])`
80    unsafe { &*as_array::<32>(bytes).as_ptr().cast() }
81}
82
83#[inline]
84#[track_caller]
85pub fn as_bytes48(bytes: &[u8]) -> &Bytes48 {
86    // SAFETY: `#[repr(C)] Bytes48([u8; 48])`
87    unsafe { &*as_array::<48>(bytes).as_ptr().cast() }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn basic_test() {
96        // 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
97
98        let commitment = hex!("8f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7").to_vec();
99        let mut versioned_hash = digest::sha256(&commitment).to_vec();
100        versioned_hash[0] = VERSIONED_HASH_VERSION_KZG;
101        let z = hex!(
102            "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000"
103        )
104        .to_vec();
105        let y = hex!(
106            "1522a4a7f34e1ea350ae07c29c96c7e79655aa926122e95fe69fcbd932ca49e9"
107        )
108        .to_vec();
109        let proof = hex!("a62ad71d14c5719385c0686f1871430475bf3a00f0aa3f7b8dd99a9abc2160744faf0070725e00b60ad9a026a15b1a8c").to_vec();
110
111        let input = [versioned_hash, z, y, commitment, proof].concat();
112        run(&input).unwrap();
113
114        let expected_output = hex!("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");
115
116        assert_eq!(RETURN_VALUE[..], expected_output);
117    }
118}