rtvm_precompile/
kzg_point_evaluation.rs1use crate::{Address, Error, Precompile, PrecompileResult, PrecompileWithAddress};
2use c_kzg::{Bytes32, Bytes48, KzgProof, KzgSettings};
3use rtvm_primitives::{hex_literal::hex, Bytes, Env};
4use sha2::{Digest, Sha256};
5
6pub const POINT_EVALUATION: PrecompileWithAddress =
7 PrecompileWithAddress(ADDRESS, Precompile::Env(run));
8
9pub const ADDRESS: Address = crate::u64_to_address(0x0A);
10pub const GAS_COST: u64 = 50_000;
11pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
12
13pub const RETURN_VALUE: &[u8; 64] = &hex!(
15 "0000000000000000000000000000000000000000000000000000000000001000"
16 "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
17);
18
19pub fn run(input: &Bytes, gas_limit: u64, env: &Env) -> PrecompileResult {
28 if gas_limit < GAS_COST {
29 return Err(Error::OutOfGas);
30 }
31
32 if input.len() != 192 {
34 return Err(Error::BlobInvalidInputLength);
35 }
36
37 let versioned_hash = &input[..32];
39 let commitment = &input[96..144];
40 if kzg_to_versioned_hash(commitment) != versioned_hash {
41 return Err(Error::BlobMismatchedVersion);
42 }
43
44 let commitment = as_bytes48(commitment);
46 let z = as_bytes32(&input[32..64]);
47 let y = as_bytes32(&input[64..96]);
48 let proof = as_bytes48(&input[144..192]);
49 if !verify_kzg_proof(commitment, z, y, proof, env.cfg.kzg_settings.get()) {
50 return Err(Error::BlobVerifyKzgProofFailed);
51 }
52
53 Ok((GAS_COST, RETURN_VALUE.into()))
55}
56
57#[inline]
59pub fn kzg_to_versioned_hash(commitment: &[u8]) -> [u8; 32] {
60 let mut hash: [u8; 32] = Sha256::digest(commitment).into();
61 hash[0] = VERSIONED_HASH_VERSION_KZG;
62 hash
63}
64
65#[inline]
66pub fn verify_kzg_proof(
67 commitment: &Bytes48,
68 z: &Bytes32,
69 y: &Bytes32,
70 proof: &Bytes48,
71 kzg_settings: &KzgSettings,
72) -> bool {
73 KzgProof::verify_kzg_proof(commitment, z, y, proof, kzg_settings).unwrap_or(false)
74}
75
76#[inline]
77#[track_caller]
78pub fn as_array<const N: usize>(bytes: &[u8]) -> &[u8; N] {
79 bytes.try_into().expect("slice with incorrect length")
80}
81
82#[inline]
83#[track_caller]
84pub fn as_bytes32(bytes: &[u8]) -> &Bytes32 {
85 unsafe { &*as_array::<32>(bytes).as_ptr().cast() }
87}
88
89#[inline]
90#[track_caller]
91pub fn as_bytes48(bytes: &[u8]) -> &Bytes48 {
92 unsafe { &*as_array::<48>(bytes).as_ptr().cast() }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn basic_test() {
102 let commitment = hex!("8f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7").to_vec();
105 let mut versioned_hash = Sha256::digest(&commitment).to_vec();
106 versioned_hash[0] = VERSIONED_HASH_VERSION_KZG;
107 let z = hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000").to_vec();
108 let y = hex!("1522a4a7f34e1ea350ae07c29c96c7e79655aa926122e95fe69fcbd932ca49e9").to_vec();
109 let proof = hex!("a62ad71d14c5719385c0686f1871430475bf3a00f0aa3f7b8dd99a9abc2160744faf0070725e00b60ad9a026a15b1a8c").to_vec();
110
111 let input = [versioned_hash, z, y, commitment, proof].concat();
112
113 let expected_output = hex!("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");
114 let gas = 50000;
115 let env = Env::default();
116 let (actual_gas, actual_output) = run(&input.into(), gas, &env).unwrap();
117 assert_eq!(actual_gas, gas);
118 assert_eq!(actual_output[..], expected_output);
119 }
120}