1use primitive_types::{H160, U256};
2use zk_evm_abstractions::{
3 aux::Timestamp,
4 precompiles::{
5 ecadd::ecadd_function, ecmul::ecmul_function, ecpairing::ecpairing_function,
6 ecrecover::ecrecover_function, keccak256::keccak256_rounds_function,
7 modexp::modexp_function, secp256r1_verify::secp256r1_verify_function,
8 sha256::sha256_rounds_function,
9 },
10 queries::{LogQuery, MemoryQuery},
11 vm::Memory,
12 zkevm_opcode_defs::{
13 ECADD_PRECOMPILE_ADDRESS, ECMUL_PRECOMPILE_ADDRESS, ECPAIRING_PRECOMPILE_ADDRESS,
14 MODEXP_PRECOMPILE_ADDRESS,
15 },
16};
17use zkevm_opcode_defs::{
18 PrecompileCallABI, ECRECOVER_INNER_FUNCTION_PRECOMPILE_ADDRESS,
19 KECCAK256_ROUND_FUNCTION_PRECOMPILE_ADDRESS, SECP256R1_VERIFY_PRECOMPILE_ADDRESS,
20 SHA256_ROUND_FUNCTION_PRECOMPILE_ADDRESS,
21};
22use zksync_vm2_interface::CycleStats;
23
24use super::{PrecompileMemoryReader, PrecompileOutput, Precompiles};
25
26fn create_query(input_offset: u32, input_len: u32, aux_data: u64) -> LogQuery {
27 let abi = PrecompileCallABI {
28 input_memory_offset: input_offset,
29 input_memory_length: input_len,
30 output_memory_offset: 0,
31 output_memory_length: 2, memory_page_to_read: 1,
34 memory_page_to_write: 2,
35 precompile_interpreted_data: aux_data,
36 };
37 LogQuery {
38 timestamp: Timestamp(0),
39 key: abi.to_u256(),
40 tx_number_in_block: Default::default(),
42 aux_byte: Default::default(),
43 shard_id: Default::default(),
44 address: H160::default(),
45 read_value: U256::default(),
46 written_value: U256::default(),
47 rw_flag: Default::default(),
48 rollback: Default::default(),
49 is_service: Default::default(),
50 }
51}
52
53#[derive(Debug)]
54struct LegacyIo<'a> {
55 input: PrecompileMemoryReader<'a>,
56 output: PrecompileOutput,
57}
58
59impl<'a> LegacyIo<'a> {
60 fn new(input: PrecompileMemoryReader<'a>) -> Self {
61 Self {
62 input,
63 output: PrecompileOutput::default(),
64 }
65 }
66}
67
68impl Memory for LegacyIo<'_> {
69 fn execute_partial_query(
70 &mut self,
71 _monotonic_cycle_counter: u32,
72 mut query: MemoryQuery,
73 ) -> MemoryQuery {
74 let start_word = query.location.index.0;
75 if query.rw_flag {
76 assert!(start_word < 3, "standard precompiles never write >3 words");
77 self.output.buffer[start_word as usize] = query.value;
78 self.output.len = self.output.len.max(start_word + 1);
79 } else {
80 query.value = self.input.heap.read_u256(start_word * 32);
82 query.value_is_pointer = false;
83 }
84 query
85 }
86
87 fn specialized_code_query(
88 &mut self,
89 _monotonic_cycle_counter: u32,
90 _query: MemoryQuery,
91 ) -> MemoryQuery {
92 unimplemented!("should not be called")
93 }
94
95 fn read_code_query(&self, _monotonic_cycle_counter: u32, _query: MemoryQuery) -> MemoryQuery {
96 unimplemented!("should not be called")
97 }
98}
99
100#[derive(Debug)]
102pub struct LegacyPrecompiles;
103
104impl Precompiles for LegacyPrecompiles {
105 #[allow(clippy::cast_possible_truncation)]
106 fn call_precompile(
107 &self,
108 address_low: u16,
109 memory: PrecompileMemoryReader<'_>,
110 aux_input: u64,
111 ) -> PrecompileOutput {
112 let query = create_query(memory.offset, memory.len, aux_input);
113 let mut io = LegacyIo::new(memory);
114 match address_low {
115 KECCAK256_ROUND_FUNCTION_PRECOMPILE_ADDRESS => {
116 let cycles = keccak256_rounds_function::<_, false>(0, query, &mut io).0;
117 io.output
118 .with_cycle_stats(CycleStats::Keccak256(cycles as u32))
119 }
120 SHA256_ROUND_FUNCTION_PRECOMPILE_ADDRESS => {
121 let cycles = sha256_rounds_function::<_, false>(0, query, &mut io).0;
122 io.output
123 .with_cycle_stats(CycleStats::Sha256(cycles as u32))
124 }
125 ECRECOVER_INNER_FUNCTION_PRECOMPILE_ADDRESS => {
126 let cycles = ecrecover_function::<_, false>(0, query, &mut io).0;
127 io.output
128 .with_cycle_stats(CycleStats::EcRecover(cycles as u32))
129 }
130 SECP256R1_VERIFY_PRECOMPILE_ADDRESS => {
131 let cycles = secp256r1_verify_function::<_, false>(0, query, &mut io).0;
132 io.output
133 .with_cycle_stats(CycleStats::Secp256r1Verify(cycles as u32))
134 }
135 MODEXP_PRECOMPILE_ADDRESS => {
136 let cycles = modexp_function::<_, false>(0, query, &mut io).0;
137 io.output
138 .with_cycle_stats(CycleStats::ModExp(cycles as u32))
139 }
140 ECADD_PRECOMPILE_ADDRESS => {
141 let cycles = ecadd_function::<_, false>(0, query, &mut io).0;
142 io.output.with_cycle_stats(CycleStats::EcAdd(cycles as u32))
143 }
144 ECMUL_PRECOMPILE_ADDRESS => {
145 let cycles = ecmul_function::<_, false>(0, query, &mut io).0;
146 io.output.with_cycle_stats(CycleStats::EcMul(cycles as u32))
147 }
148 ECPAIRING_PRECOMPILE_ADDRESS => {
149 let cycles = ecpairing_function::<_, false>(0, query, &mut io).0;
150 io.output
151 .with_cycle_stats(CycleStats::EcPairing(cycles as u32))
152 }
153 _ => PrecompileOutput::default(),
154 }
155 }
156}
157
158#[allow(clippy::cast_possible_truncation)] #[cfg(test)]
160mod tests {
161 use proptest::{array, collection, num, option, prelude::*};
162 use zkevm_opcode_defs::{
163 k256::ecdsa::{SigningKey as K256SigningKey, VerifyingKey as K256VerifyingKey},
164 p256::ecdsa::SigningKey as P256SigningKey,
165 sha3::{self, Digest},
166 };
167 use zksync_vm2_interface::HeapId;
168
169 use super::*;
170 use crate::heap::Heaps;
171
172 const MAX_LEN: usize = 2_048;
173
174 fn arbitrary_aligned_bytes(alignment: usize) -> impl Strategy<Value = Vec<u8>> {
175 (0..=(MAX_LEN / alignment)).prop_flat_map(move |len_in_words| {
176 collection::vec(num::u8::ANY, len_in_words * alignment)
177 })
178 }
179
180 fn key_to_address(key: &K256VerifyingKey) -> U256 {
181 let encoded_key = key.to_encoded_point(false);
182 let encoded_key = &encoded_key.as_bytes()[1..];
183 debug_assert_eq!(encoded_key.len(), 64);
184 let address_digest = sha3::Keccak256::digest(encoded_key);
185 let address_u256 = U256::from_big_endian(&address_digest);
186 address_u256 & (U256::MAX >> (256 - 160))
188 }
189
190 fn test_keccak_precompile(input: &[u8], initial_offset: u32) -> Result<(), TestCaseError> {
191 let input_len = input.len() as u32;
192 assert_eq!(input_len % 32, 0);
193
194 let mut heaps = Heaps::new(&[]);
195 for (i, u256_chunk) in input.chunks(32).enumerate() {
196 let offset = i as u32 * 32 + initial_offset;
197 heaps.write_u256(HeapId::FIRST, offset, U256::from_big_endian(u256_chunk));
198 }
199
200 let memory = PrecompileMemoryReader::new(&heaps[HeapId::FIRST], initial_offset, input_len);
201 let output = LegacyPrecompiles.call_precompile(
202 KECCAK256_ROUND_FUNCTION_PRECOMPILE_ADDRESS,
203 memory,
204 0,
205 );
206
207 prop_assert_eq!(output.len, 1);
208 let expected_hash = sha3::Keccak256::digest(input);
209 let expected_hash = U256::from_big_endian(&expected_hash);
210 prop_assert_eq!(output.buffer[0], expected_hash);
211 prop_assert!(matches!(output.cycle_stats, Some(CycleStats::Keccak256(_))));
212 Ok(())
213 }
214
215 fn test_sha256_precompile(
216 input: &[u8],
217 initial_offset_in_words: u32,
218 ) -> Result<(), TestCaseError> {
219 assert_eq!(input.len() % 64, 0);
220 let mut heaps = Heaps::new(&[]);
221
222 for (i, u256_chunk) in input.chunks(32).enumerate() {
223 let offset = i as u32 * 32 + initial_offset_in_words * 32;
224 heaps.write_u256(HeapId::FIRST, offset, U256::from_big_endian(u256_chunk));
225 }
226
227 let max_round_count = input.len() as u32 / 64;
228 for round_count in 0..=max_round_count {
229 let memory = PrecompileMemoryReader::new(
230 &heaps[HeapId::FIRST],
231 initial_offset_in_words,
232 round_count * 2,
233 );
234 let output = LegacyPrecompiles.call_precompile(
235 SHA256_ROUND_FUNCTION_PRECOMPILE_ADDRESS,
236 memory,
237 round_count.into(),
238 );
239
240 if round_count == 0 {
241 prop_assert_eq!(output.len, 0);
242 } else {
243 prop_assert_eq!(output.len, 1);
244 prop_assert_ne!(output.buffer[0], U256::zero());
245 }
246 prop_assert!(matches!(output.cycle_stats, Some(CycleStats::Sha256(_))));
247 }
248 Ok(())
249 }
250
251 #[derive(Debug, Clone, Copy)]
252 enum EcRecoverMutation {
253 RecoveryId,
254 Digest(usize),
255 R(usize),
256 S(usize),
257 }
258
259 impl EcRecoverMutation {
260 fn gen() -> impl Strategy<Value = Self> {
261 (0..4).prop_flat_map(|raw| match raw {
262 0 => Just(Self::RecoveryId).boxed(),
263 1 => (0_usize..32).prop_map(Self::Digest).boxed(),
264 2 => (0_usize..32).prop_map(Self::R).boxed(),
265 3 => (0_usize..32).prop_map(Self::S).boxed(),
266 _ => unreachable!(),
267 })
268 }
269 }
270
271 fn test_ecrecover_precompile(
272 signing_key: &K256SigningKey,
273 mutation: Option<EcRecoverMutation>,
274 initial_offset_in_words: u32,
275 ) -> Result<(), TestCaseError> {
276 let mut heaps = Heaps::new(&[]);
277 let initial_offset = initial_offset_in_words * 32;
278
279 let message = "test message!";
280 let mut message_digest = sha3::Keccak256::digest(message);
281
282 let (signature, recovery_id) = signing_key
283 .sign_prehash_recoverable(&message_digest)
284 .unwrap();
285 if recovery_id.is_x_reduced() {
286 return Ok(());
287 }
288 let mut recovery_id = recovery_id.to_byte();
289
290 println!(
291 "testing key {:?} with mutation {mutation:?}",
292 signing_key.verifying_key().to_encoded_point(true)
293 );
294 let mut signature_bytes = signature.to_bytes();
295
296 match mutation {
297 Some(EcRecoverMutation::Digest(byte)) => {
298 message_digest[byte] ^= 1;
299 }
300 Some(EcRecoverMutation::RecoveryId) => {
301 recovery_id = 1 - recovery_id;
302 }
303 Some(EcRecoverMutation::R(byte)) => {
304 signature_bytes[byte] ^= 1;
305 }
306 Some(EcRecoverMutation::S(byte)) => {
307 signature_bytes[byte + 32] ^= 1;
308 }
309 None => { }
310 }
311
312 heaps.write_u256(
313 HeapId::FIRST,
314 initial_offset,
315 U256::from_big_endian(&message_digest),
316 );
317 heaps.write_u256(HeapId::FIRST, initial_offset + 32, recovery_id.into());
318 heaps.write_u256(
319 HeapId::FIRST,
320 initial_offset + 64,
321 U256::from_big_endian(&signature_bytes[..32]),
322 );
323 heaps.write_u256(
324 HeapId::FIRST,
325 initial_offset + 96,
326 U256::from_big_endian(&signature_bytes[32..]),
327 );
328
329 let memory = PrecompileMemoryReader::new(&heaps[HeapId::FIRST], initial_offset_in_words, 4);
330 let output = LegacyPrecompiles.call_precompile(
331 ECRECOVER_INNER_FUNCTION_PRECOMPILE_ADDRESS,
332 memory,
333 0,
334 );
335
336 prop_assert_eq!(output.len, 2);
337 let expected_address = key_to_address(signing_key.verifying_key());
338 let [is_success, address, _] = output.buffer;
339 if mutation.is_some() {
340 prop_assert_ne!(address, expected_address);
341 } else {
342 prop_assert_eq!(is_success, U256::one());
343 prop_assert_eq!(address, expected_address);
344 }
345 prop_assert!(matches!(output.cycle_stats, Some(CycleStats::EcRecover(1))));
346 Ok(())
347 }
348
349 #[derive(Debug, Clone, Copy)]
350 enum P256Mutation {
351 Digest(usize),
352 R(usize),
353 S(usize),
354 Key(usize),
355 }
356
357 impl P256Mutation {
358 fn gen() -> impl Strategy<Value = Self> {
359 (0..4).prop_flat_map(|raw| match raw {
360 0 => (0_usize..32).prop_map(Self::Digest).boxed(),
361 1 => (0_usize..32).prop_map(Self::R).boxed(),
362 2 => (0_usize..32).prop_map(Self::S).boxed(),
363 3 => (0_usize..64).prop_map(Self::Key).boxed(),
364 _ => unreachable!(),
365 })
366 }
367 }
368
369 fn test_secp256r1_precompile(
370 signing_key: &P256SigningKey,
371 mutation: Option<P256Mutation>,
372 initial_offset_in_words: u32,
373 ) -> Result<(), TestCaseError> {
374 use zkevm_opcode_defs::p256::ecdsa::{signature::hazmat::PrehashSigner, Signature};
375
376 let mut heaps = Heaps::new(&[]);
377 let initial_offset = initial_offset_in_words * 32;
378
379 let message = "test message!";
380 let mut message_digest = sha3::Keccak256::digest(message);
381
382 let signature: Signature = signing_key.sign_prehash(&message_digest).unwrap();
383
384 println!(
385 "testing key {:?} with mutation {mutation:?}",
386 signing_key.verifying_key().to_encoded_point(true)
387 );
388 let mut signature_bytes = signature.to_bytes();
389 let mut key_bytes = signing_key
390 .verifying_key()
391 .to_encoded_point(false)
392 .as_bytes()[1..]
393 .to_vec();
394 assert_eq!(key_bytes.len(), 64);
395
396 match mutation {
397 Some(P256Mutation::Digest(byte)) => {
398 message_digest[byte] ^= 1;
399 }
400 Some(P256Mutation::R(byte)) => {
401 signature_bytes[byte] ^= 1;
402 }
403 Some(P256Mutation::S(byte)) => {
404 signature_bytes[byte + 32] ^= 1;
405 }
406 Some(P256Mutation::Key(byte)) => {
407 key_bytes[byte] ^= 1;
408 }
409 None => { }
410 }
411
412 heaps.write_u256(
413 HeapId::FIRST,
414 initial_offset,
415 U256::from_big_endian(&message_digest),
416 );
417 heaps.write_u256(
418 HeapId::FIRST,
419 initial_offset + 32,
420 U256::from_big_endian(&signature_bytes[..32]),
421 );
422 heaps.write_u256(
423 HeapId::FIRST,
424 initial_offset + 64,
425 U256::from_big_endian(&signature_bytes[32..]),
426 );
427 heaps.write_u256(
428 HeapId::FIRST,
429 initial_offset + 96,
430 U256::from_big_endian(&key_bytes[..32]),
431 );
432 heaps.write_u256(
433 HeapId::FIRST,
434 initial_offset + 128,
435 U256::from_big_endian(&key_bytes[32..]),
436 );
437
438 let memory = PrecompileMemoryReader::new(&heaps[HeapId::FIRST], initial_offset_in_words, 5);
439 let output =
440 LegacyPrecompiles.call_precompile(SECP256R1_VERIFY_PRECOMPILE_ADDRESS, memory, 0);
441
442 prop_assert_eq!(output.len, 2);
443 let [is_ok, is_verified, _] = output.buffer;
444 if mutation.is_none() {
445 prop_assert_eq!(is_ok, U256::one());
446 prop_assert_eq!(is_verified, U256::one());
447 } else {
448 prop_assert!(is_ok.is_zero() || is_verified.is_zero());
449 }
450 prop_assert!(matches!(
451 output.cycle_stats,
452 Some(CycleStats::Secp256r1Verify(1))
453 ));
454 Ok(())
455 }
456
457 proptest! {
458 #[test]
459 fn keccak_precompile_works(
460 bytes in arbitrary_aligned_bytes(32),
461 initial_offset in 0..u32::MAX / 2,
462 ) {
463 test_keccak_precompile(&bytes, initial_offset)?;
464 }
465
466 #[test]
467 fn sha256_precompile_works(
468 bytes in arbitrary_aligned_bytes(64),
469 initial_offset_in_words in 0..u32::MAX / 64,
470 ) {
471 test_sha256_precompile(&bytes, initial_offset_in_words)?;
472 }
473
474 #[test]
475 fn ecrecover_precompile_works(
476 signing_key in array::uniform32(num::u8::ANY)
477 .prop_filter_map("not a key", |bytes| K256SigningKey::from_bytes(&bytes.into()).ok()),
478 mutation in option::of(EcRecoverMutation::gen()),
479 initial_offset_in_words in 0..u32::MAX / 64,
480 ) {
481 test_ecrecover_precompile(&signing_key, mutation, initial_offset_in_words)?;
482 }
483
484 #[test]
485 fn secp256r1_precompile_works(
486 signing_key in array::uniform32(num::u8::ANY)
487 .prop_filter_map("not a key", |bytes| P256SigningKey::from_bytes(&bytes.into()).ok()),
488 mutation in option::of(P256Mutation::gen()),
489 initial_offset_in_words in 0..u32::MAX / 64,
490 ) {
491 test_secp256r1_precompile(&signing_key, mutation, initial_offset_in_words)?;
492 }
493 }
494}