1use acir::brillig::{BlackBoxOp, HeapArray};
3use acir::{AcirField, BlackBoxFunc};
4use acvm_blackbox_solver::{
5 BlackBoxFunctionSolver, BlackBoxResolutionError, aes128_encrypt, blake2s, blake3,
6 ecdsa_secp256k1_verify, ecdsa_secp256r1_verify, keccakf1600, sha256_compression,
7};
8use num_bigint::BigUint;
9use num_traits::Zero;
10
11use crate::Memory;
12use crate::assert_usize;
13use crate::memory::MemoryValue;
14
15fn read_heap_array<'a, F: AcirField>(
19 memory: &'a Memory<F>,
20 array: &HeapArray,
21) -> &'a [MemoryValue<F>] {
22 let items_start = memory.read_ref(array.pointer);
23 memory.read_slice(items_start, assert_usize(array.size.0))
24}
25
26fn write_heap_array<F: AcirField>(
28 memory: &mut Memory<F>,
29 array: &HeapArray,
30 values: &[MemoryValue<F>],
31) {
32 let items_start = memory.read_ref(array.pointer);
33 memory.write_slice(items_start, values);
34}
35
36fn to_u8_vec<F: AcirField>(inputs: &[MemoryValue<F>]) -> Vec<u8> {
38 let mut result = Vec::with_capacity(inputs.len());
39 for &input in inputs {
40 result.push(input.expect_u8().unwrap());
41 }
42 result
43}
44
45fn to_value_vec<F: AcirField>(input: &[u8]) -> Vec<MemoryValue<F>> {
48 input.iter().map(|&x| x.into()).collect()
49}
50
51pub(crate) fn evaluate_black_box<F: AcirField, Solver: BlackBoxFunctionSolver<F>>(
71 op: &BlackBoxOp,
72 solver: &Solver,
73 memory: &mut Memory<F>,
74) -> Result<(), BlackBoxResolutionError> {
75 match op {
76 BlackBoxOp::AES128Encrypt { inputs, iv, key, outputs } => {
77 let bb_func = black_box_function_from_op(op);
78
79 let inputs = to_u8_vec(read_heap_array(memory, inputs));
80
81 let iv: [u8; 16] = to_u8_vec(read_heap_array(memory, iv)).try_into().map_err(|_| {
82 BlackBoxResolutionError::Failed(bb_func, "Invalid iv length".to_string())
83 })?;
84 let key: [u8; 16] =
85 to_u8_vec(read_heap_array(memory, key)).try_into().map_err(|_| {
86 BlackBoxResolutionError::Failed(bb_func, "Invalid key length".to_string())
87 })?;
88 let ciphertext = aes128_encrypt(&inputs, iv, key)?;
89
90 write_heap_array(memory, outputs, &to_value_vec(&ciphertext));
91
92 Ok(())
93 }
94 BlackBoxOp::Blake2s { message, output } => {
95 let message = to_u8_vec(read_heap_array(memory, message));
96 let bytes = blake2s(message.as_slice())?;
97 write_heap_array(memory, output, &to_value_vec(&bytes));
98 Ok(())
99 }
100 BlackBoxOp::Blake3 { message, output } => {
101 let message = to_u8_vec(read_heap_array(memory, message));
102 let bytes = blake3(message.as_slice())?;
103 write_heap_array(memory, output, &to_value_vec(&bytes));
104 Ok(())
105 }
106 BlackBoxOp::Keccakf1600 { input, output } => {
107 let state_vec: Vec<u64> = read_heap_array(memory, input)
108 .iter()
109 .map(|&memory_value| memory_value.expect_u64().unwrap())
110 .collect();
111 let state: [u64; 25] = state_vec.try_into().unwrap();
112
113 let new_state = keccakf1600(state)?;
114
115 let new_state: Vec<MemoryValue<F>> = new_state.into_iter().map(|x| x.into()).collect();
116 write_heap_array(memory, output, &new_state);
117 Ok(())
118 }
119 BlackBoxOp::EcdsaSecp256k1 {
120 hashed_msg,
121 public_key_x,
122 public_key_y,
123 signature,
124 result: result_address,
125 }
126 | BlackBoxOp::EcdsaSecp256r1 {
127 hashed_msg,
128 public_key_x,
129 public_key_y,
130 signature,
131 result: result_address,
132 } => {
133 let bb_func = black_box_function_from_op(op);
134
135 let public_key_x: [u8; 32] =
136 to_u8_vec(read_heap_array(memory, public_key_x)).try_into().map_err(|_| {
137 BlackBoxResolutionError::Failed(
138 bb_func,
139 "Invalid public key x length".to_string(),
140 )
141 })?;
142 let public_key_y: [u8; 32] =
143 to_u8_vec(read_heap_array(memory, public_key_y)).try_into().map_err(|_| {
144 BlackBoxResolutionError::Failed(
145 bb_func,
146 "Invalid public key y length".to_string(),
147 )
148 })?;
149 let signature: [u8; 64] =
150 to_u8_vec(read_heap_array(memory, signature)).try_into().map_err(|_| {
151 BlackBoxResolutionError::Failed(bb_func, "Invalid signature length".to_string())
152 })?;
153
154 let hashed_msg: [u8; 32] =
155 to_u8_vec(read_heap_array(memory, hashed_msg)).try_into().map_err(|_| {
156 BlackBoxResolutionError::Failed(
157 bb_func,
158 "Invalid hashed message length".to_string(),
159 )
160 })?;
161
162 let result = match op {
163 BlackBoxOp::EcdsaSecp256k1 { .. } => {
164 ecdsa_secp256k1_verify(&hashed_msg, &public_key_x, &public_key_y, &signature)?
165 }
166 BlackBoxOp::EcdsaSecp256r1 { .. } => {
167 ecdsa_secp256r1_verify(&hashed_msg, &public_key_x, &public_key_y, &signature)?
168 }
169 _ => unreachable!("`BlackBoxOp` is guarded against being a non-ecdsa operation"),
170 };
171
172 memory.write(*result_address, result.into());
173 Ok(())
174 }
175 BlackBoxOp::MultiScalarMul { points, scalars, outputs: result } => {
176 let points: Vec<F> =
177 read_heap_array(memory, points).iter().map(|x| x.expect_field().unwrap()).collect();
178 let scalars: Vec<F> = read_heap_array(memory, scalars)
179 .iter()
180 .map(|x| x.expect_field().unwrap())
181 .collect();
182 let mut scalars_lo = Vec::with_capacity(scalars.len() / 2);
183 let mut scalars_hi = Vec::with_capacity(scalars.len() / 2);
184 for (i, scalar) in scalars.iter().enumerate() {
185 if i % 2 == 0 {
186 scalars_lo.push(*scalar);
187 } else {
188 scalars_hi.push(*scalar);
189 }
190 }
191 let (x, y) = solver.multi_scalar_mul(
192 &points,
193 &scalars_lo,
194 &scalars_hi,
195 true, )?;
197 write_heap_array(
198 memory,
199 result,
200 &[MemoryValue::new_field(x), MemoryValue::new_field(y)],
201 );
202 Ok(())
203 }
204 BlackBoxOp::EmbeddedCurveAdd { input1_x, input1_y, input2_x, input2_y, result } => {
205 let input1_x = memory.read(*input1_x).expect_field().unwrap();
206 let input1_y = memory.read(*input1_y).expect_field().unwrap();
207 let input2_x = memory.read(*input2_x).expect_field().unwrap();
208 let input2_y = memory.read(*input2_y).expect_field().unwrap();
209 let (x, y) = solver.ec_add(
210 &input1_x, &input1_y, &input2_x, &input2_y,
211 true, )?;
213
214 write_heap_array(
215 memory,
216 result,
217 &[MemoryValue::new_field(x), MemoryValue::new_field(y)],
218 );
219 Ok(())
220 }
221 BlackBoxOp::Poseidon2Permutation { message, output } => {
222 let input = read_heap_array(memory, message);
223 let input: Vec<F> = input.iter().map(|x| x.expect_field().unwrap()).collect();
224 let result = solver.poseidon2_permutation(&input)?;
225 let mut values = Vec::new();
226 for i in result {
227 values.push(MemoryValue::new_field(i));
228 }
229 write_heap_array(memory, output, &values);
230 Ok(())
231 }
232 BlackBoxOp::Sha256Compression { input, hash_values, output } => {
233 let mut message = [0; 16];
234 let inputs = read_heap_array(memory, input);
235 if inputs.len() != 16 {
236 return Err(BlackBoxResolutionError::Failed(
237 BlackBoxFunc::Sha256Compression,
238 format!("Expected 16 inputs but encountered {}", inputs.len()),
239 ));
240 }
241 for (i, &input) in inputs.iter().enumerate() {
242 message[i] = input.expect_u32().unwrap();
243 }
244 let mut state = [0; 8];
245 let values = read_heap_array(memory, hash_values);
246 if values.len() != 8 {
247 return Err(BlackBoxResolutionError::Failed(
248 BlackBoxFunc::Sha256Compression,
249 format!("Expected 8 values but encountered {}", values.len()),
250 ));
251 }
252 for (i, &value) in values.iter().enumerate() {
253 state[i] = value.expect_u32().unwrap();
254 }
255
256 sha256_compression(&mut state, &message);
257 let state = state.map(|x| x.into());
258
259 write_heap_array(memory, output, &state);
260 Ok(())
261 }
262 BlackBoxOp::ToRadix { input, radix, output_pointer, num_limbs, output_bits } => {
263 let input: F = memory.read(*input).expect_field().expect("ToRadix input not a field");
264 let MemoryValue::U32(radix) = memory.read(*radix) else {
265 panic!("ToRadix opcode's radix bit size does not match expected bit size 32")
266 };
267 let num_limbs = memory.read(*num_limbs).to_u32();
268 let MemoryValue::U1(output_bits) = memory.read(*output_bits) else {
269 panic!("ToRadix opcode's output_bits size does not match expected bit size 1")
270 };
271
272 let output = to_be_radix(input, radix, assert_usize(num_limbs), output_bits)?;
273
274 memory.write_slice(memory.read_ref(*output_pointer), &output);
275
276 Ok(())
277 }
278 }
279}
280
281fn black_box_function_from_op(op: &BlackBoxOp) -> BlackBoxFunc {
287 match op {
288 BlackBoxOp::AES128Encrypt { .. } => BlackBoxFunc::AES128Encrypt,
289 BlackBoxOp::Blake2s { .. } => BlackBoxFunc::Blake2s,
290 BlackBoxOp::Blake3 { .. } => BlackBoxFunc::Blake3,
291 BlackBoxOp::Keccakf1600 { .. } => BlackBoxFunc::Keccakf1600,
292 BlackBoxOp::EcdsaSecp256k1 { .. } => BlackBoxFunc::EcdsaSecp256k1,
293 BlackBoxOp::EcdsaSecp256r1 { .. } => BlackBoxFunc::EcdsaSecp256r1,
294 BlackBoxOp::MultiScalarMul { .. } => BlackBoxFunc::MultiScalarMul,
295 BlackBoxOp::EmbeddedCurveAdd { .. } => BlackBoxFunc::EmbeddedCurveAdd,
296 BlackBoxOp::Poseidon2Permutation { .. } => BlackBoxFunc::Poseidon2Permutation,
297 BlackBoxOp::Sha256Compression { .. } => BlackBoxFunc::Sha256Compression,
298 BlackBoxOp::ToRadix { .. } => unreachable!("ToRadix is not an ACIR BlackBoxFunc"),
299 }
300}
301
302fn to_be_radix<F: AcirField>(
303 input: F,
304 radix: u32,
305 num_limbs: usize,
306 output_bits: bool,
307) -> Result<Vec<MemoryValue<F>>, BlackBoxResolutionError> {
308 assert!(
309 (2u32..=256u32).contains(&radix),
310 "Radix out of the valid range [2,256]. Value: {radix}"
311 );
312
313 assert!(
314 !output_bits || radix == 2u32,
315 "Radix {radix} is not equal to 2 and bit mode is activated."
316 );
317
318 let mut input = BigUint::from_bytes_be(&input.to_be_bytes());
319 let radix = BigUint::from(radix);
320
321 let mut limbs: Vec<MemoryValue<F>> = vec![MemoryValue::default(); num_limbs];
322 for i in (0..num_limbs).rev() {
323 let limb = &input % &radix;
324 limbs[i] = if output_bits {
325 MemoryValue::U1(!limb.is_zero())
326 } else {
327 let limb: u8 = limb.try_into().unwrap();
328 MemoryValue::U8(limb)
329 };
330 input /= &radix;
331 }
332
333 if !input.is_zero() {
336 return Err(BlackBoxResolutionError::AssertFailed(format!(
337 "Field failed to decompose into specified {num_limbs} limbs"
338 )));
339 }
340
341 Ok(limbs)
342}
343
344#[cfg(test)]
345mod ecdsa_tests {
346 use acir::brillig::lengths::SemiFlattenedLength;
347 use acir::brillig::{BlackBoxOp, HeapArray, MemoryAddress};
348 use acvm_blackbox_solver::{BlackBoxResolutionError, StubbedBlackBoxSolver};
349
350 use crate::Memory;
351 use crate::black_box::evaluate_black_box;
352 use crate::memory::MemoryValue;
353
354 use acir::FieldElement;
355
356 fn write_heap_array(
362 memory: &mut Memory<FieldElement>,
363 pointer_addr: u32,
364 items_addr: u32,
365 bytes: &[u8],
366 len: u32,
367 ) -> HeapArray {
368 let pointer = MemoryAddress::direct(pointer_addr);
369 memory.write_ref(pointer, MemoryAddress::direct(items_addr));
370 let values: Vec<MemoryValue<FieldElement>> = bytes.iter().map(|&b| b.into()).collect();
371 memory.write_slice(MemoryAddress::direct(items_addr), &values);
372 HeapArray { pointer, size: SemiFlattenedLength(len) }
373 }
374
375 #[test]
378 fn ecdsa_secp256k1_rejects_wrong_hashed_msg_length() {
379 let mut memory = Memory::default();
380
381 let public_key_x = write_heap_array(&mut memory, 0, 1000, &[0u8; 32], 32);
384 let public_key_y = write_heap_array(&mut memory, 1, 2000, &[0u8; 32], 32);
385 let signature = write_heap_array(&mut memory, 2, 3000, &[0u8; 64], 64);
386 let hashed_msg = write_heap_array(&mut memory, 3, 4000, &[0u8; 31], 31);
388
389 let op = BlackBoxOp::EcdsaSecp256k1 {
390 hashed_msg,
391 public_key_x,
392 public_key_y,
393 signature,
394 result: MemoryAddress::direct(5),
395 };
396
397 let result = evaluate_black_box(&op, &StubbedBlackBoxSolver, &mut memory);
398 assert!(
399 matches!(result, Err(BlackBoxResolutionError::Failed(..))),
400 "expected a recoverable error, got {result:?}"
401 );
402 }
403
404 #[test]
405 fn ecdsa_secp256r1_rejects_wrong_hashed_msg_length() {
406 let mut memory = Memory::default();
407
408 let public_key_x = write_heap_array(&mut memory, 0, 1000, &[0u8; 32], 32);
409 let public_key_y = write_heap_array(&mut memory, 1, 2000, &[0u8; 32], 32);
410 let signature = write_heap_array(&mut memory, 2, 3000, &[0u8; 64], 64);
411 let hashed_msg = write_heap_array(&mut memory, 3, 4000, &[0u8; 31], 31);
412
413 let op = BlackBoxOp::EcdsaSecp256r1 {
414 hashed_msg,
415 public_key_x,
416 public_key_y,
417 signature,
418 result: MemoryAddress::direct(5),
419 };
420
421 let result = evaluate_black_box(&op, &StubbedBlackBoxSolver, &mut memory);
422 assert!(
423 matches!(result, Err(BlackBoxResolutionError::Failed(..))),
424 "expected a recoverable error, got {result:?}"
425 );
426 }
427}
428
429#[cfg(test)]
430mod to_be_radix_tests {
431 use crate::black_box::to_be_radix;
432
433 use acir::{AcirField, FieldElement};
434
435 use proptest::prelude::*;
436
437 acir::acir_field::field_wrapper!(TestField, FieldElement);
441
442 impl Arbitrary for TestField {
443 type Parameters = ();
444 type Strategy = BoxedStrategy<Self>;
445
446 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
447 any::<u128>().prop_map(|v| Self(FieldElement::from(v))).boxed()
448 }
449 }
450
451 proptest! {
452 #[test]
453 fn matches_byte_decomposition(param: TestField) {
454 let bytes: Vec<u8> = to_be_radix(param.0, 256, 32, false).unwrap().into_iter().map(|byte| byte.expect_u8().unwrap()).collect();
455 let expected_bytes = param.0.to_be_bytes();
456 prop_assert_eq!(bytes, expected_bytes);
457 }
458 }
459
460 #[test]
461 fn correctly_handles_unusual_radices() {
462 let value = FieldElement::from(65024u128);
463 let expected_limbs = vec![254, 254];
464
465 let limbs: Vec<u8> = to_be_radix(value, 255, 2, false)
466 .unwrap()
467 .into_iter()
468 .map(|byte| byte.expect_u8().unwrap())
469 .collect();
470 assert_eq!(limbs, expected_limbs);
471 }
472
473 #[test]
474 fn matches_decimal_decomposition() {
475 let value = FieldElement::from(123456789u128);
476 let expected_limbs = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
477
478 let limbs: Vec<u8> = to_be_radix(value, 10, 9, false)
479 .unwrap()
480 .into_iter()
481 .map(|byte| byte.expect_u8().unwrap())
482 .collect();
483 assert_eq!(limbs, expected_limbs);
484 }
485
486 #[test]
487 fn rejects_non_zero_field_with_zero_limbs() {
488 let value = FieldElement::from(1u128);
489
490 let error = to_be_radix(value, 256, 0, false).unwrap_err();
491 assert_eq!(
492 error,
493 acvm_blackbox_solver::BlackBoxResolutionError::AssertFailed(
494 "Field failed to decompose into specified 0 limbs".to_string()
495 )
496 );
497 }
498}