|
| 1 | +#![allow(clippy::module_inception, clippy::large_stack_arrays)] |
| 2 | + |
| 3 | +use aurora_engine_precompiles::{Context, EthGas, ExitError, Precompile, PrecompileOutput}; |
| 4 | +use primitive_types::H160; |
| 5 | +use std::borrow::Cow::Borrowed; |
| 6 | + |
| 7 | +mod kzg { |
| 8 | + use c_kzg::{Bytes32, Bytes48, KzgProof, KzgSettings, BYTES_PER_G1_POINT, BYTES_PER_G2_POINT}; |
| 9 | + use core::convert::TryInto; |
| 10 | + use core::hash::{Hash, Hasher}; |
| 11 | + use derive_more::{AsMut, AsRef, Deref, DerefMut}; |
| 12 | + use hex_literal::hex; |
| 13 | + use sha2::Digest; |
| 14 | + use std::convert::TryFrom; |
| 15 | + use std::rc::Rc; |
| 16 | + |
| 17 | + pub const RETURN_VALUE: &[u8; 64] = &hex!( |
| 18 | + "0000000000000000000000000000000000000000000000000000000000001000" |
| 19 | + "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" |
| 20 | + ); |
| 21 | + |
| 22 | + /// Number of G1 Points. |
| 23 | + const NUM_G1_POINTS: usize = 4096; |
| 24 | + |
| 25 | + /// Number of G2 Points. |
| 26 | + const NUM_G2_POINTS: usize = 65; |
| 27 | + |
| 28 | + /// A newtype over list of G1 point from kzg trusted setup. |
| 29 | + #[derive(Debug, Clone, PartialEq, AsRef, AsMut, Deref, DerefMut)] |
| 30 | + #[repr(transparent)] |
| 31 | + struct G1Points(pub [[u8; BYTES_PER_G1_POINT]; NUM_G1_POINTS]); |
| 32 | + |
| 33 | + impl Default for G1Points { |
| 34 | + fn default() -> Self { |
| 35 | + Self([[0; BYTES_PER_G1_POINT]; NUM_G1_POINTS]) |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + /// A newtype over list of G2 point from kzg trusted setup. |
| 40 | + #[derive(Debug, Clone, Eq, PartialEq, AsRef, AsMut, Deref, DerefMut)] |
| 41 | + #[repr(transparent)] |
| 42 | + struct G2Points(pub [[u8; BYTES_PER_G2_POINT]; NUM_G2_POINTS]); |
| 43 | + |
| 44 | + impl Default for G2Points { |
| 45 | + fn default() -> Self { |
| 46 | + Self([[0; BYTES_PER_G2_POINT]; NUM_G2_POINTS]) |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + /// Default G1 points. |
| 51 | + const G1_POINTS: &G1Points = { |
| 52 | + const BYTES: &[u8] = include_bytes!("assets/g1_points.bin"); |
| 53 | + assert!(BYTES.len() == size_of::<G1Points>()); |
| 54 | + unsafe { &*BYTES.as_ptr().cast::<G1Points>() } |
| 55 | + }; |
| 56 | + |
| 57 | + /// Default G2 points. |
| 58 | + const G2_POINTS: &G2Points = { |
| 59 | + const BYTES: &[u8] = include_bytes!("assets/g2_points.bin"); |
| 60 | + assert!(BYTES.len() == size_of::<G2Points>()); |
| 61 | + unsafe { &*BYTES.as_ptr().cast::<G2Points>() } |
| 62 | + }; |
| 63 | + |
| 64 | + /// Parse contents of a KZG trusted setup file into a list of G1 and G2 points. |
| 65 | + /// |
| 66 | + /// These can then be used to create a KZG settings object with |
| 67 | + /// [`KzgSettings::load_trusted_setup`](c_kzg::KzgSettings::load_trusted_setup). |
| 68 | + #[allow(dead_code)] |
| 69 | + fn parse_kzg_trusted_setup( |
| 70 | + trusted_setup: &str, |
| 71 | + ) -> Result<(Box<G1Points>, Box<G2Points>), &'static str> { |
| 72 | + let mut lines = trusted_setup.lines(); |
| 73 | + |
| 74 | + // load number of points |
| 75 | + let n_g1 = lines |
| 76 | + .next() |
| 77 | + .ok_or("KzgFileFormatError")? |
| 78 | + .parse::<usize>() |
| 79 | + .map_err(|_| "KzgParseError")?; |
| 80 | + let n_g2 = lines |
| 81 | + .next() |
| 82 | + .ok_or("KzgFileFormatError")? |
| 83 | + .parse::<usize>() |
| 84 | + .map_err(|_| "KzgParseError")?; |
| 85 | + |
| 86 | + if n_g1 != NUM_G1_POINTS { |
| 87 | + return Err("KzgMismatchedNumberOfPoints"); |
| 88 | + } |
| 89 | + |
| 90 | + if n_g2 != NUM_G2_POINTS { |
| 91 | + return Err("KzgMismatchedNumberOfPoints"); |
| 92 | + } |
| 93 | + |
| 94 | + // load g1 points |
| 95 | + let mut g1_points = Box::<G1Points>::default(); |
| 96 | + for bytes in &mut g1_points.0 { |
| 97 | + let line = lines.next().ok_or("KzgFileFormatError")?; |
| 98 | + hex::decode_to_slice(line, bytes).map_err(|_| "KzgParseError")?; |
| 99 | + } |
| 100 | + |
| 101 | + // load g2 points |
| 102 | + let mut g2_points = Box::<G2Points>::default(); |
| 103 | + for bytes in &mut g2_points.0 { |
| 104 | + let line = lines.next().ok_or("KzgFileFormatError")?; |
| 105 | + hex::decode_to_slice(line, bytes).map_err(|_| "KzgParseError")?; |
| 106 | + } |
| 107 | + |
| 108 | + if lines.next().is_some() { |
| 109 | + return Err("KzgFileFormatError"); |
| 110 | + } |
| 111 | + |
| 112 | + Ok((g1_points, g2_points)) |
| 113 | + } |
| 114 | + |
| 115 | + /// KZG Settings that allow us to specify a custom trusted setup. |
| 116 | + /// or use hardcoded default settings. |
| 117 | + #[allow(dead_code)] |
| 118 | + #[derive(Debug, Clone, Default)] |
| 119 | + pub enum EnvKzgSettings { |
| 120 | + /// Default mainnet trusted setup |
| 121 | + #[default] |
| 122 | + Default, |
| 123 | + /// Custom trusted setup. |
| 124 | + Custom(Rc<KzgSettings>), |
| 125 | + } |
| 126 | + |
| 127 | + // Implement PartialEq and Hash manually because `c_kzg::KzgSettings` does not implement them |
| 128 | + impl PartialEq for EnvKzgSettings { |
| 129 | + fn eq(&self, other: &Self) -> bool { |
| 130 | + match (self, other) { |
| 131 | + (Self::Default, Self::Default) => true, |
| 132 | + (Self::Custom(a), Self::Custom(b)) => Rc::ptr_eq(a, b), |
| 133 | + _ => false, |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + impl Hash for EnvKzgSettings { |
| 139 | + fn hash<H: Hasher>(&self, state: &mut H) { |
| 140 | + core::mem::discriminant(self).hash(state); |
| 141 | + match self { |
| 142 | + Self::Default => {} |
| 143 | + Self::Custom(settings) => Rc::as_ptr(settings).hash(state), |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + impl EnvKzgSettings { |
| 149 | + /// Return set KZG settings. |
| 150 | + /// |
| 151 | + /// In will initialize the default settings if it is not already loaded. |
| 152 | + pub fn get(&self) -> Rc<KzgSettings> { |
| 153 | + match self { |
| 154 | + Self::Default => { |
| 155 | + let res = |
| 156 | + KzgSettings::load_trusted_setup(G1_POINTS.as_ref(), G2_POINTS.as_ref()) |
| 157 | + .expect("failed to load default trusted setup"); |
| 158 | + Rc::new(res) |
| 159 | + } |
| 160 | + Self::Custom(settings) => settings.clone(), |
| 161 | + } |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + /// `VERSIONED_HASH_VERSION_KZG ++ sha256(commitment)[1..]` |
| 166 | + #[inline] |
| 167 | + pub fn kzg_to_versioned_hash(commitment: &[u8]) -> [u8; 32] { |
| 168 | + const VERSIONED_HASH_VERSION_KZG: u8 = 0x01; |
| 169 | + let mut hash: [u8; 32] = sha2::Sha256::digest(commitment).into(); |
| 170 | + hash[0] = VERSIONED_HASH_VERSION_KZG; |
| 171 | + hash |
| 172 | + } |
| 173 | + |
| 174 | + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] |
| 175 | + pub struct KzgInput { |
| 176 | + commitment: Bytes48, |
| 177 | + z: Bytes32, |
| 178 | + y: Bytes32, |
| 179 | + proof: Bytes48, |
| 180 | + } |
| 181 | + |
| 182 | + impl KzgInput { |
| 183 | + #[inline] |
| 184 | + pub fn verify_kzg_proof(&self, kzg_settings: &KzgSettings) -> bool { |
| 185 | + KzgProof::verify_kzg_proof( |
| 186 | + &self.commitment, |
| 187 | + &self.z, |
| 188 | + &self.y, |
| 189 | + &self.proof, |
| 190 | + kzg_settings, |
| 191 | + ) |
| 192 | + .unwrap_or(false) |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + impl TryFrom<&[u8]> for KzgInput { |
| 197 | + type Error = &'static str; |
| 198 | + |
| 199 | + fn try_from(input: &[u8]) -> Result<Self, Self::Error> { |
| 200 | + if input.len() != 192 { |
| 201 | + return Err("BlobInvalidInputLength"); |
| 202 | + } |
| 203 | + // Verify commitment matches versioned_hash |
| 204 | + let versioned_hash = &input[..32]; |
| 205 | + let commitment = &input[96..144]; |
| 206 | + if kzg_to_versioned_hash(commitment) != versioned_hash { |
| 207 | + return Err("BlobMismatchedVersion"); |
| 208 | + } |
| 209 | + let commitment = *as_bytes48(commitment); |
| 210 | + let z = *as_bytes32(&input[32..64]); |
| 211 | + let y = *as_bytes32(&input[64..96]); |
| 212 | + let proof = *as_bytes48(&input[144..192]); |
| 213 | + Ok(Self { |
| 214 | + commitment, |
| 215 | + z, |
| 216 | + y, |
| 217 | + proof, |
| 218 | + }) |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + #[inline] |
| 223 | + fn as_array<const N: usize>(bytes: &[u8]) -> &[u8; N] { |
| 224 | + bytes.try_into().expect("slice with incorrect length") |
| 225 | + } |
| 226 | + |
| 227 | + #[inline] |
| 228 | + fn as_bytes32(bytes: &[u8]) -> &Bytes32 { |
| 229 | + // SAFETY: `#[repr(C)] Bytes32([u8; 32])` |
| 230 | + unsafe { &*as_array::<32>(bytes).as_ptr().cast() } |
| 231 | + } |
| 232 | + |
| 233 | + #[inline] |
| 234 | + fn as_bytes48(bytes: &[u8]) -> &Bytes48 { |
| 235 | + // SAFETY: `#[repr(C)] Bytes48([u8; 48])` |
| 236 | + unsafe { &*as_array::<48>(bytes).as_ptr().cast() } |
| 237 | + } |
| 238 | +} |
| 239 | + |
| 240 | +const KZG_BASE_GAS_FEE: u64 = 50_000; |
| 241 | + |
| 242 | +pub struct Kzg; |
| 243 | + |
| 244 | +impl Kzg { |
| 245 | + pub const ADDRESS: H160 = H160([ |
| 246 | + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 247 | + 0x00, 0x00, 0x00, 0x00, 0x0A, |
| 248 | + ]); |
| 249 | + |
| 250 | + fn execute(input: &[u8]) -> Result<Vec<u8>, ExitError> { |
| 251 | + // Get and verify KZG input. |
| 252 | + let kzg_input: kzg::KzgInput = input |
| 253 | + .try_into() |
| 254 | + .map_err(|e| ExitError::Other(Borrowed(e)))?; |
| 255 | + let kzg_settings = kzg::EnvKzgSettings::Default; |
| 256 | + if !kzg_input.verify_kzg_proof(&kzg_settings.get()) { |
| 257 | + return Err(ExitError::Other(Borrowed("BlobVerifyKzgProofFailed"))); |
| 258 | + } |
| 259 | + Ok(kzg::RETURN_VALUE.to_vec()) |
| 260 | + } |
| 261 | +} |
| 262 | + |
| 263 | +impl Precompile for Kzg { |
| 264 | + fn required_gas(_input: &[u8]) -> Result<EthGas, ExitError> { |
| 265 | + Ok(EthGas::new(KZG_BASE_GAS_FEE)) |
| 266 | + } |
| 267 | + |
| 268 | + fn run( |
| 269 | + &self, |
| 270 | + input: &[u8], |
| 271 | + target_gas: Option<EthGas>, |
| 272 | + _context: &Context, |
| 273 | + _is_static: bool, |
| 274 | + ) -> Result<PrecompileOutput, ExitError> { |
| 275 | + let cost = Self::required_gas(input)?; |
| 276 | + if let Some(target_gas) = target_gas { |
| 277 | + if cost > target_gas { |
| 278 | + return Err(ExitError::OutOfGas); |
| 279 | + } |
| 280 | + } |
| 281 | + |
| 282 | + let output = Self::execute(input)?; |
| 283 | + Ok(PrecompileOutput::without_logs(cost, output)) |
| 284 | + } |
| 285 | +} |
0 commit comments