acir/circuit/opcodes/
black_box_function_call.rs

1//! Black box functions are ACIR opcodes which rely on backends implementing
2//! support for specialized constraints.
3//! This makes certain zk-snark unfriendly computations cheaper than if they were
4//! implemented in more basic constraints.
5
6use std::collections::BTreeSet;
7
8use crate::BlackBoxFunc;
9use crate::native_types::Witness;
10
11use acir_field::AcirField;
12use msgpack_tagged::MsgpackTagged;
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use thiserror::Error;
15
16/// Enumeration for black box function inputs
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18#[derive(Serialize, Deserialize, MsgpackTagged)]
19#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
20pub enum FunctionInput<F> {
21    /// A constant field element
22    #[tag(0)]
23    Constant(F),
24    /// A witness element, representing dynamic inputs
25    #[tag(1)]
26    Witness(Witness),
27}
28
29impl<F: std::fmt::Display> std::fmt::Display for FunctionInput<F> {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match &self {
32            FunctionInput::Constant(constant) => {
33                write!(f, "{constant}")
34            }
35            FunctionInput::Witness(witness) => {
36                write!(f, "w{}", witness.0)
37            }
38        }
39    }
40}
41
42impl<F> FunctionInput<F> {
43    pub fn is_constant(&self) -> bool {
44        match self {
45            FunctionInput::Constant(_) => true,
46            FunctionInput::Witness(_) => false,
47        }
48    }
49
50    pub fn to_witness(&self) -> Witness {
51        match self {
52            FunctionInput::Constant(_) => unreachable!("ICE - Expected Witness"),
53            FunctionInput::Witness(witness) => *witness,
54        }
55    }
56}
57
58#[derive(Clone, PartialEq, Eq, Debug, Error)]
59#[error("FunctionInput value has too many bits: value: {value}, {value_num_bits} >= {max_bits}")]
60pub struct InvalidInputBitSize {
61    pub value: String,
62    pub value_num_bits: u32,
63    pub max_bits: u32,
64}
65
66/// These opcodes represent a specialized computation.
67/// Even if any computation can be done using only assert-zero opcodes,
68/// it is not always efficient.
69/// Some proving systems, can implement several computations more efficiently using
70/// techniques such as custom gates and lookup tables.
71#[derive(Clone, PartialEq, Eq, Hash)]
72#[derive(Serialize, Deserialize, MsgpackTagged)]
73pub enum BlackBoxFuncCall<F> {
74    /// Ciphers (encrypts) the provided plaintext using AES128 in CBC mode,
75    /// padding the input using PKCS#7.
76    /// - inputs: byte array `[u8; N]`
77    /// - iv: initialization vector `[u8; 16]`
78    /// - key: user key `[u8; 16]`
79    /// - outputs: byte vector `[u8]` of length `input.len() + (16 - input.len() % 16)`
80    #[tag(0)]
81    AES128Encrypt {
82        #[tag(0)]
83        inputs: Vec<FunctionInput<F>>,
84        #[tag(1)]
85        iv: Box<[FunctionInput<F>; 16]>,
86        #[tag(2)]
87        key: Box<[FunctionInput<F>; 16]>,
88        #[tag(3)]
89        outputs: Vec<Witness>,
90    },
91    /// Performs the bitwise AND of `lhs` and `rhs`. `bit_size` must be the same for
92    /// both inputs.
93    /// - lhs: (witness, bit_size)
94    /// - rhs: (witness, bit_size)
95    /// - output: a witness whose value is constrained to be lhs AND rhs, as
96    ///   bit_size bit integers
97    #[tag(1)]
98    AND {
99        #[tag(0)]
100        lhs: FunctionInput<F>,
101        #[tag(1)]
102        rhs: FunctionInput<F>,
103        #[tag(2)]
104        num_bits: u32,
105        #[tag(3)]
106        output: Witness,
107    },
108    /// Performs the bitwise XOR of `lhs` and `rhs`. `bit_size` must be the same for
109    /// both inputs.
110    /// - lhs: (witness, bit_size)
111    /// - rhs: (witness, bit_size)
112    /// - output: a witness whose value is constrained to be lhs XOR rhs, as
113    ///   bit_size bit integers
114    #[tag(2)]
115    XOR {
116        #[tag(0)]
117        lhs: FunctionInput<F>,
118        #[tag(1)]
119        rhs: FunctionInput<F>,
120        #[tag(2)]
121        num_bits: u32,
122        #[tag(3)]
123        output: Witness,
124    },
125    /// Range constraint to ensure that a witness
126    /// can be represented in the specified number of bits.
127    /// - input: (witness, bit_size)
128    #[tag(3)]
129    RANGE {
130        #[tag(0)]
131        input: FunctionInput<F>,
132        #[tag(1)]
133        num_bits: u32,
134    },
135    /// Computes the Blake2s hash of the inputs, as specified in
136    /// <https://tools.ietf.org/html/rfc7693>
137    /// - inputs are a byte array, i.e a vector of (witness, 8)
138    /// - output is a byte array of length 32, i.e. an array of 32
139    ///   (witness, 8), constrained to be the blake2s of the inputs.
140    #[tag(4)]
141    Blake2s {
142        #[tag(0)]
143        inputs: Vec<FunctionInput<F>>,
144        #[tag(1)]
145        outputs: Box<[Witness; 32]>,
146    },
147    /// Computes the Blake3 hash of the inputs
148    /// - inputs are a byte array, i.e a vector of (witness, 8)
149    /// - output is a byte array of length 32, i.e an array of 32
150    ///   (witness, 8), constrained to be the blake3 of the inputs.
151    #[tag(5)]
152    Blake3 {
153        #[tag(0)]
154        inputs: Vec<FunctionInput<F>>,
155        #[tag(1)]
156        outputs: Box<[Witness; 32]>,
157    },
158    /// Verifies a ECDSA signature over the secp256k1 curve.
159    /// - inputs:
160    ///     - x coordinate of public key as 32 bytes
161    ///     - y coordinate of public key as 32 bytes
162    ///     - the signature, as a 64 bytes array
163    ///       The signature internally will be represented as `(r, s)`,
164    ///       where `r` and `s` are fixed-sized big endian scalar values.
165    ///       As the `secp256k1` has a 256-bit modulus, we have a 64 byte signature
166    ///       while `r` and `s` will both be 32 bytes.
167    ///       We expect `s` to be normalized. This means given the curve's order,
168    ///       `s` should be less than or equal to `order / 2`.
169    ///       This is done to prevent malleability.
170    ///       For more context regarding malleability you can reference BIP 0062.
171    ///     - the hash of the message, as a vector of bytes
172    /// - output: 0 for failure and 1 for success
173    ///
174    /// Expected backend behavior:
175    /// - The backend MAY fail to prove this opcode if the public key is not on the secp256k1 curve.
176    ///    - Otherwise the backend MUST constrain the output to be false.
177    /// - The backend MUST constrain the output to be false if `s` is not normalized.
178    /// - The backend MUST constrain the output to match the signature's validity.
179    #[tag(6)]
180    EcdsaSecp256k1 {
181        #[tag(0)]
182        public_key_x: Box<[FunctionInput<F>; 32]>,
183        #[tag(1)]
184        public_key_y: Box<[FunctionInput<F>; 32]>,
185        #[serde(
186            serialize_with = "serialize_big_array",
187            deserialize_with = "deserialize_big_array_into_box"
188        )]
189        #[tag(2)]
190        signature: Box<[FunctionInput<F>; 64]>,
191        #[tag(3)]
192        hashed_message: Box<[FunctionInput<F>; 32]>,
193        #[tag(4)]
194        predicate: FunctionInput<F>,
195        #[tag(5)]
196        output: Witness,
197    },
198    /// Verifies a ECDSA signature over the secp256r1 curve.
199    ///
200    /// Same as EcdsaSecp256k1, but done over another curve.
201    #[tag(7)]
202    EcdsaSecp256r1 {
203        #[tag(0)]
204        public_key_x: Box<[FunctionInput<F>; 32]>,
205        #[tag(1)]
206        public_key_y: Box<[FunctionInput<F>; 32]>,
207        #[serde(
208            serialize_with = "serialize_big_array",
209            deserialize_with = "deserialize_big_array_into_box"
210        )]
211        #[tag(2)]
212        signature: Box<[FunctionInput<F>; 64]>,
213        #[tag(3)]
214        hashed_message: Box<[FunctionInput<F>; 32]>,
215        #[tag(4)]
216        predicate: FunctionInput<F>,
217        #[tag(5)]
218        output: Witness,
219    },
220    /// Multiple scalar multiplication (MSM) with a variable base/input point
221    /// (P) of the embedded curve. An MSM multiplies the points and scalars and
222    /// sums the results.
223    /// - input:
224    ///     - points (witness, N) a vector of x and y coordinates of input
225    ///     - points `[x1, y1, x2, y2,...]`.
226    ///     - scalars (witness, N) a vector of low and high limbs of input
227    ///     - scalars `[s1_low, s1_high, s2_low, s2_high, ...]`. (witness, N)
228    ///       For Barretenberg, they must both be less than 128 bits.
229    ///       Barretenberg implementation of the blackbox also ensures that the scalars do not overflow the Grumpkin scalar field modulus.
230    ///    - predicate (witness) a boolean that disable the constraint when false
231    /// - output:
232    ///     - a tuple of `x` and `y` coordinates of output
233    ///       points computed as `s_low*P+s_high*2^{128}*P`
234    ///
235    /// Because the Grumpkin scalar field is bigger than the ACIR field, we
236    /// provide 2 ACIR fields representing the low and high parts of the Grumpkin
237    /// scalar $a$: `a=low+high*2^{128}`, with `low< 2^{128}` and `high< 2^{126}`
238    ///
239    /// Coordinates of each point must be all witnesses or all constants.
240    /// Similarly, both halves (lo, hi) of each scalar must be all witnesses or
241    /// all constants. This is a backend requirement from Barretenberg.
242    #[tag(8)]
243    MultiScalarMul {
244        #[tag(0)]
245        points: Vec<FunctionInput<F>>,
246        #[tag(1)]
247        scalars: Vec<FunctionInput<F>>,
248        #[tag(2)]
249        predicate: FunctionInput<F>,
250        #[tag(3)]
251        outputs: (Witness, Witness),
252    },
253    /// Addition over the embedded curve on which the witness is defined.
254    /// The opcode makes the following assumptions but does not enforce them because
255    /// it is more efficient to do it only when required. For instance, adding two
256    /// points that are on the curve it guarantee to give a point on the curve.
257    ///
258    /// It assumes that the points are on the curve.
259    /// If the inputs are the same witnesses index, it will perform a doubling,
260    /// If not, it assumes that the points' x-coordinates are not equal.
261    /// It also assumes neither point is the infinity point.
262    ///
263    /// The point at infinity is represented as `(0, 0)`.
264    ///
265    /// Coordinates of each point must be all witnesses or all constants.
266    /// This is a backend requirement from Barretenberg.
267    #[tag(9)]
268    EmbeddedCurveAdd {
269        #[tag(0)]
270        input1: Box<[FunctionInput<F>; 2]>,
271        #[tag(1)]
272        input2: Box<[FunctionInput<F>; 2]>,
273        #[tag(2)]
274        predicate: FunctionInput<F>,
275        #[tag(3)]
276        outputs: (Witness, Witness),
277    },
278    /// Keccak Permutation function of width 1600
279    /// - inputs: An array of 25 64-bit Keccak lanes that represent a keccak sponge of 1600 bits
280    /// - outputs: The result of a keccak f1600 permutation on the input state. Also an array of 25 Keccak lanes.
281    #[tag(10)]
282    Keccakf1600 {
283        #[tag(0)]
284        inputs: Box<[FunctionInput<F>; 25]>,
285        #[tag(1)]
286        outputs: Box<[Witness; 25]>,
287    },
288    /// Computes a recursive aggregation object when verifying a proof inside
289    /// another circuit.
290    /// The outputted aggregation object will then be either checked in a
291    /// top-level verifier or aggregated upon again.
292    /// The aggregation object should be maintained by the backend implementer.
293    ///
294    /// This opcode prepares the verification of the final proof.
295    /// In order to fully verify a recursive proof, some operations may still be required
296    /// to be done by the final verifier (e.g. a pairing check).
297    /// This is why this black box function does not say if verification is passing or not.
298    /// It delays the expensive part of verification out of the SNARK
299    /// and leaves it to the final verifier outside of the SNARK circuit.
300    ///
301    /// This opcode also verifies that the key_hash is indeed a hash of verification_key,
302    /// allowing the user to use the verification key as private inputs and only
303    /// have the key_hash as public input, which is more performant.
304    ///
305    /// **Warning: the key hash logic does not need to be part of the black box and subject to be removed.**
306    ///
307    /// If one of the recursive proofs you verify with the black box function fails to
308    /// verify, then the verification of the final proof of the main ACIR program will
309    /// ultimately fail.
310    #[tag(11)]
311    RecursiveAggregation {
312        /// Verification key of the circuit being verified
313        #[tag(0)]
314        verification_key: Vec<FunctionInput<F>>,
315        #[tag(1)]
316        proof: Vec<FunctionInput<F>>,
317        /// These represent the public inputs of the proof we are verifying
318        /// They should be checked against in the circuit after construction
319        /// of a new aggregation state
320        #[tag(2)]
321        public_inputs: Vec<FunctionInput<F>>,
322        /// A key hash is used to check the validity of the verification key.
323        /// The circuit implementing this opcode can use this hash to ensure that the
324        /// key provided to the circuit matches the key produced by the circuit creator
325        #[tag(3)]
326        key_hash: FunctionInput<F>,
327        /// Backend-specific proof type constant.
328        /// The proof field is agnostic and can come from witness inputs.
329        /// However, a backend may have many different verifiers which affect
330        /// the circuit construction.
331        /// In order for a backend to construct the correct recursive verifier
332        /// it expects the user to specify a proof type.
333        #[tag(4)]
334        proof_type: u32,
335        /// A predicate (true or false) to disable the recursive verification
336        #[tag(5)]
337        predicate: FunctionInput<F>,
338    },
339    /// Applies the Poseidon2 permutation function to the given state,
340    /// outputting the permuted state.
341    ///
342    /// This operation will fail if the length of the inputs do not match
343    /// the backend configuration, but a match with the statically configured
344    /// black box solver is enforced by static assertions, to avoid having a
345    /// different outcome between runtimes based on whether the SSA has
346    /// been flattened or not.
347    #[tag(12)]
348    Poseidon2Permutation {
349        /// Input state for the permutation of Poseidon2
350        #[tag(0)]
351        inputs: Vec<FunctionInput<F>>,
352        /// Permuted state
353        #[tag(1)]
354        outputs: Vec<Witness>,
355    },
356    /// Applies the SHA-256 compression function to the input message
357    ///
358    /// # Arguments
359    ///
360    /// * `inputs` - input message block
361    /// * `hash_values` - state from the previous compression
362    /// * `outputs` - result of the input compressed into 256 bits
363    #[tag(13)]
364    Sha256Compression {
365        /// 512 bits of the input message, represented by 16 u32s
366        #[tag(0)]
367        inputs: Box<[FunctionInput<F>; 16]>,
368        /// Vector of 8 u32s used to compress the input
369        #[tag(1)]
370        hash_values: Box<[FunctionInput<F>; 8]>,
371        /// Output of the compression, represented by 8 u32s
372        #[tag(2)]
373        outputs: Box<[Witness; 8]>,
374    },
375}
376
377impl<F> BlackBoxFuncCall<F> {
378    pub fn get_black_box_func(&self) -> BlackBoxFunc {
379        match self {
380            BlackBoxFuncCall::AES128Encrypt { .. } => BlackBoxFunc::AES128Encrypt,
381            BlackBoxFuncCall::AND { .. } => BlackBoxFunc::AND,
382            BlackBoxFuncCall::XOR { .. } => BlackBoxFunc::XOR,
383            BlackBoxFuncCall::RANGE { .. } => BlackBoxFunc::RANGE,
384            BlackBoxFuncCall::Blake2s { .. } => BlackBoxFunc::Blake2s,
385            BlackBoxFuncCall::Blake3 { .. } => BlackBoxFunc::Blake3,
386            BlackBoxFuncCall::EcdsaSecp256k1 { .. } => BlackBoxFunc::EcdsaSecp256k1,
387            BlackBoxFuncCall::EcdsaSecp256r1 { .. } => BlackBoxFunc::EcdsaSecp256r1,
388            BlackBoxFuncCall::MultiScalarMul { .. } => BlackBoxFunc::MultiScalarMul,
389            BlackBoxFuncCall::EmbeddedCurveAdd { .. } => BlackBoxFunc::EmbeddedCurveAdd,
390            BlackBoxFuncCall::Keccakf1600 { .. } => BlackBoxFunc::Keccakf1600,
391            BlackBoxFuncCall::RecursiveAggregation { .. } => BlackBoxFunc::RecursiveAggregation,
392            BlackBoxFuncCall::Poseidon2Permutation { .. } => BlackBoxFunc::Poseidon2Permutation,
393            BlackBoxFuncCall::Sha256Compression { .. } => BlackBoxFunc::Sha256Compression,
394        }
395    }
396
397    pub fn name(&self) -> &str {
398        self.get_black_box_func().name()
399    }
400
401    pub fn bit_size(&self) -> Option<u32> {
402        match self {
403            BlackBoxFuncCall::AND { num_bits, .. }
404            | BlackBoxFuncCall::XOR { num_bits, .. }
405            | BlackBoxFuncCall::RANGE { num_bits, .. } => Some(*num_bits),
406            _ => None,
407        }
408    }
409
410    pub fn get_outputs_vec(&self) -> Vec<Witness> {
411        match self {
412            BlackBoxFuncCall::Blake2s { outputs, .. }
413            | BlackBoxFuncCall::Blake3 { outputs, .. } => outputs.to_vec(),
414
415            BlackBoxFuncCall::Keccakf1600 { outputs, .. } => outputs.to_vec(),
416
417            BlackBoxFuncCall::Sha256Compression { outputs, .. } => outputs.to_vec(),
418
419            BlackBoxFuncCall::AES128Encrypt { outputs, .. }
420            | BlackBoxFuncCall::Poseidon2Permutation { outputs, .. } => outputs.clone(),
421
422            BlackBoxFuncCall::AND { output, .. }
423            | BlackBoxFuncCall::XOR { output, .. }
424            | BlackBoxFuncCall::EcdsaSecp256k1 { output, .. }
425            | BlackBoxFuncCall::EcdsaSecp256r1 { output, .. } => vec![*output],
426            BlackBoxFuncCall::MultiScalarMul { outputs, .. }
427            | BlackBoxFuncCall::EmbeddedCurveAdd { outputs, .. } => {
428                vec![outputs.0, outputs.1]
429            }
430            BlackBoxFuncCall::RANGE { .. } | BlackBoxFuncCall::RecursiveAggregation { .. } => {
431                vec![]
432            }
433        }
434    }
435}
436
437impl<F: Copy + AcirField> BlackBoxFuncCall<F> {
438    pub fn get_inputs_vec(&self) -> Vec<FunctionInput<F>> {
439        match self {
440            BlackBoxFuncCall::Blake2s { inputs, outputs: _ }
441            | BlackBoxFuncCall::Blake3 { inputs, outputs: _ }
442            | BlackBoxFuncCall::Poseidon2Permutation { inputs, outputs: _ } => inputs.clone(),
443
444            BlackBoxFuncCall::Keccakf1600 { inputs, outputs: _ } => inputs.to_vec(),
445            BlackBoxFuncCall::AES128Encrypt { inputs, iv, key, outputs: _ } => {
446                [inputs, iv.as_slice(), key.as_slice()].concat()
447            }
448            BlackBoxFuncCall::Sha256Compression { inputs, hash_values, outputs: _ } => {
449                [inputs.as_slice(), hash_values.as_slice()].concat()
450            }
451            BlackBoxFuncCall::AND { lhs, rhs, output: _, num_bits: _ }
452            | BlackBoxFuncCall::XOR { lhs, rhs, output: _, num_bits: _ } => {
453                vec![*lhs, *rhs]
454            }
455            BlackBoxFuncCall::RANGE { input, num_bits: _ } => vec![*input],
456
457            BlackBoxFuncCall::MultiScalarMul { points, scalars, predicate, outputs: _ } => {
458                [points.as_slice(), scalars.as_slice(), &[*predicate]].concat()
459            }
460            BlackBoxFuncCall::EmbeddedCurveAdd { input1, input2, predicate, outputs: _ } => {
461                vec![input1[0], input1[1], input2[0], input2[1], *predicate]
462            }
463            BlackBoxFuncCall::EcdsaSecp256k1 {
464                public_key_x,
465                public_key_y,
466                signature,
467                hashed_message,
468                predicate,
469                output: _,
470            } => [
471                public_key_x.as_slice(),
472                public_key_y.as_slice(),
473                signature.as_slice(),
474                hashed_message.as_slice(),
475                &[*predicate],
476            ]
477            .concat(),
478            BlackBoxFuncCall::EcdsaSecp256r1 {
479                public_key_x,
480                public_key_y,
481                signature,
482                hashed_message,
483                predicate,
484                output: _,
485            } => [
486                public_key_x.as_slice(),
487                public_key_y.as_slice(),
488                signature.as_slice(),
489                hashed_message.as_slice(),
490                &[*predicate],
491            ]
492            .concat(),
493            BlackBoxFuncCall::RecursiveAggregation {
494                verification_key: key,
495                proof,
496                public_inputs,
497                key_hash,
498                proof_type: _,
499                predicate,
500            } => [key.as_slice(), proof, public_inputs, &[*key_hash], &[*predicate]].concat(),
501        }
502    }
503
504    pub fn get_input_witnesses(&self) -> BTreeSet<Witness> {
505        let mut result = BTreeSet::new();
506        for input in self.get_inputs_vec() {
507            if let FunctionInput::Witness(w) = input {
508                result.insert(w);
509            }
510        }
511        result
512    }
513
514    pub fn get_predicate(&self) -> Option<Witness> {
515        let predicate = match self {
516            BlackBoxFuncCall::AES128Encrypt { .. }
517            | BlackBoxFuncCall::AND { .. }
518            | BlackBoxFuncCall::XOR { .. }
519            | BlackBoxFuncCall::RANGE { .. }
520            | BlackBoxFuncCall::Blake2s { .. }
521            | BlackBoxFuncCall::Blake3 { .. }
522            | BlackBoxFuncCall::Keccakf1600 { .. }
523            | BlackBoxFuncCall::Poseidon2Permutation { .. }
524            | BlackBoxFuncCall::Sha256Compression { .. } => FunctionInput::Constant(F::one()),
525            BlackBoxFuncCall::EcdsaSecp256k1 { predicate, .. }
526            | BlackBoxFuncCall::EcdsaSecp256r1 { predicate, .. }
527            | BlackBoxFuncCall::MultiScalarMul { predicate, .. }
528            | BlackBoxFuncCall::EmbeddedCurveAdd { predicate, .. }
529            | BlackBoxFuncCall::RecursiveAggregation { predicate, .. } => *predicate,
530        };
531        if predicate.is_constant() { None } else { Some(predicate.to_witness()) }
532    }
533}
534
535impl<F: std::fmt::Display + Copy> std::fmt::Display for BlackBoxFuncCall<F> {
536    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
537        let uppercase_name = self.name().to_uppercase();
538        write!(f, "BLACKBOX::{uppercase_name} ")?;
539
540        match self {
541            BlackBoxFuncCall::AES128Encrypt { inputs, iv, key, outputs } => {
542                let inputs = slice_to_string(inputs);
543                let iv = slice_to_string(&iv.to_vec());
544                let key = slice_to_string(&key.to_vec());
545                let outputs = slice_to_string(outputs);
546                write!(f, "inputs: {inputs}, iv: {iv}, key: {key}, outputs: {outputs}")?;
547            }
548            BlackBoxFuncCall::AND { lhs, rhs, num_bits, output }
549            | BlackBoxFuncCall::XOR { lhs, rhs, num_bits, output } => {
550                write!(f, "lhs: {lhs}, rhs: {rhs}, output: {output}, bits: {num_bits}")?;
551            }
552            BlackBoxFuncCall::RANGE { input, num_bits } => {
553                write!(f, "input: {input}, bits: {num_bits}")?;
554            }
555            BlackBoxFuncCall::Blake2s { inputs, outputs }
556            | BlackBoxFuncCall::Blake3 { inputs, outputs } => {
557                let inputs = slice_to_string(inputs);
558                let outputs = slice_to_string(&outputs.to_vec());
559                write!(f, "inputs: {inputs}, outputs: {outputs}")?;
560            }
561            BlackBoxFuncCall::EcdsaSecp256k1 {
562                public_key_x,
563                public_key_y,
564                signature,
565                hashed_message,
566                predicate,
567                output,
568            }
569            | BlackBoxFuncCall::EcdsaSecp256r1 {
570                public_key_x,
571                public_key_y,
572                signature,
573                hashed_message,
574                predicate,
575                output,
576            } => {
577                let public_key_x = slice_to_string(&public_key_x.to_vec());
578                let public_key_y = slice_to_string(&public_key_y.to_vec());
579                let signature = slice_to_string(&signature.to_vec());
580                let hashed_message = slice_to_string(&hashed_message.to_vec());
581                write!(
582                    f,
583                    "public_key_x: {public_key_x}, public_key_y: {public_key_y}, signature: {signature}, hashed_message: {hashed_message}, predicate: {predicate}, output: {output}"
584                )?;
585            }
586            BlackBoxFuncCall::MultiScalarMul { points, scalars, predicate, outputs } => {
587                let points = slice_to_string(points);
588                let scalars = slice_to_string(scalars);
589                write!(
590                    f,
591                    "points: {points}, scalars: {scalars}, predicate: {predicate}, outputs: [{}, {}]",
592                    outputs.0, outputs.1
593                )?;
594            }
595            BlackBoxFuncCall::EmbeddedCurveAdd { input1, input2, predicate, outputs } => {
596                let input1 = slice_to_string(&input1.to_vec());
597                let input2 = slice_to_string(&input2.to_vec());
598                write!(
599                    f,
600                    "input1: {input1}, input2: {input2}, predicate: {predicate}, outputs: [{}, {}]",
601                    outputs.0, outputs.1
602                )?;
603            }
604            BlackBoxFuncCall::Keccakf1600 { inputs, outputs } => {
605                let inputs = slice_to_string(&inputs.to_vec());
606                let outputs = slice_to_string(&outputs.to_vec());
607                write!(f, "inputs: {inputs}, outputs: {outputs}")?;
608            }
609            BlackBoxFuncCall::RecursiveAggregation {
610                verification_key,
611                proof,
612                public_inputs,
613                key_hash,
614                proof_type,
615                predicate,
616            } => {
617                let verification_key = slice_to_string(verification_key);
618                let proof = slice_to_string(proof);
619                let public_inputs = slice_to_string(public_inputs);
620                write!(
621                    f,
622                    "verification_key: {verification_key}, proof: {proof}, public_inputs: {public_inputs}, key_hash: {key_hash}, proof_type: {proof_type}, predicate: {predicate}"
623                )?;
624            }
625            BlackBoxFuncCall::Poseidon2Permutation { inputs, outputs } => {
626                let inputs = slice_to_string(inputs);
627                let outputs = slice_to_string(outputs);
628                write!(f, "inputs: {inputs}, outputs: {outputs}")?;
629            }
630            BlackBoxFuncCall::Sha256Compression { inputs, hash_values, outputs } => {
631                let inputs = slice_to_string(&inputs.to_vec());
632                let hash_values = slice_to_string(&hash_values.to_vec());
633                let outputs = slice_to_string(&outputs.to_vec());
634                write!(f, "inputs: {inputs}, hash_values: {hash_values}, outputs: {outputs}")?;
635            }
636        }
637
638        Ok(())
639    }
640}
641
642fn slice_to_string<D: std::fmt::Display>(inputs: &[D]) -> String {
643    let inputs = inputs.iter().map(|i| i.to_string()).collect::<Vec<String>>().join(", ");
644    format!("[{inputs}]")
645}
646
647impl<F: std::fmt::Display + Copy> std::fmt::Debug for BlackBoxFuncCall<F> {
648    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
649        std::fmt::Display::fmt(self, f)
650    }
651}
652
653fn serialize_big_array<S, F: Serialize>(
654    big_array: &[FunctionInput<F>; 64],
655    s: S,
656) -> Result<S::Ok, S::Error>
657where
658    S: Serializer,
659{
660    use serde_big_array::BigArray;
661
662    (*big_array).serialize(s)
663}
664
665fn deserialize_big_array_into_box<'de, D, F: Deserialize<'de>>(
666    deserializer: D,
667) -> Result<Box<[FunctionInput<F>; 64]>, D::Error>
668where
669    D: Deserializer<'de>,
670{
671    use serde_big_array::BigArray;
672
673    let big_array: [FunctionInput<F>; 64] = BigArray::deserialize(deserializer)?;
674    Ok(Box::new(big_array))
675}
676
677#[cfg(test)]
678mod tests {
679
680    use crate::{circuit::Opcode, native_types::Witness};
681    use acir_field::{AcirField, FieldElement};
682
683    use super::{BlackBoxFuncCall, FunctionInput};
684
685    fn keccakf1600_opcode<F: AcirField>() -> Opcode<F> {
686        let inputs: Box<[FunctionInput<F>; 25]> =
687            Box::new(std::array::from_fn(|i| FunctionInput::Witness(Witness(i as u32 + 1))));
688        let outputs: Box<[Witness; 25]> = Box::new(std::array::from_fn(|i| Witness(i as u32 + 26)));
689
690        Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Keccakf1600 { inputs, outputs })
691    }
692
693    #[test]
694    fn keccakf1600_serialization_roundtrip() {
695        use crate::serialization::{msgpack_deserialize, msgpack_serialize};
696
697        let opcode = keccakf1600_opcode::<FieldElement>();
698        let buf = msgpack_serialize(&opcode, true).unwrap();
699        let recovered_opcode = msgpack_deserialize(&buf).unwrap();
700        assert_eq!(opcode, recovered_opcode);
701    }
702}
703
704#[cfg(feature = "arb")]
705mod arb {
706    use acir_field::AcirField;
707    use proptest::prelude::*;
708
709    use crate::native_types::Witness;
710
711    use super::{BlackBoxFuncCall, FunctionInput};
712
713    // Implementing this separately because trying to derive leads to stack overflow.
714    impl<F> Arbitrary for BlackBoxFuncCall<F>
715    where
716        F: AcirField + Arbitrary,
717    {
718        type Parameters = ();
719        type Strategy = BoxedStrategy<Self>;
720
721        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
722            let input = any::<FunctionInput<F>>();
723            let input_vec = any::<Vec<FunctionInput<F>>>();
724            let input_arr_2 = any::<Box<[FunctionInput<F>; 2]>>();
725            let input_arr_3 = any::<Box<[FunctionInput<F>; 3]>>();
726            let input_arr_8 = any::<Box<[FunctionInput<F>; 8]>>();
727            let input_arr_16 = any::<Box<[FunctionInput<F>; 16]>>();
728            let input_arr_25 = any::<Box<[FunctionInput<F>; 25]>>();
729            let input_arr_32 = any::<Box<[FunctionInput<F>; 32]>>();
730            let input_arr_64 = any::<Box<[FunctionInput<F>; 64]>>();
731            let witness = any::<Witness>();
732            let witness_vec = any::<Vec<Witness>>();
733            let witness_arr_8 = any::<Box<[Witness; 8]>>();
734            let witness_arr_25 = any::<Box<[Witness; 25]>>();
735            let witness_arr_32 = any::<Box<[Witness; 32]>>();
736
737            // Input must be a multiple of 16 bytes, and output length must equal input length
738            // Use fixed 16-byte input/output for simplicity
739            let witness_arr_16 = any::<Box<[Witness; 16]>>();
740            let case_aes128_encrypt =
741                (input_arr_16.clone(), input_arr_16.clone(), input_arr_16.clone(), witness_arr_16)
742                    .prop_map(|(inputs, iv, key, outputs)| BlackBoxFuncCall::AES128Encrypt {
743                        inputs: inputs.to_vec(),
744                        iv,
745                        key,
746                        outputs: outputs.to_vec(),
747                    });
748
749            let case_and = (input_arr_3.clone(), input_arr_8.clone(), witness.clone()).prop_map(
750                |(lhs, rhs, output)| BlackBoxFuncCall::AND {
751                    lhs: lhs[0],
752                    rhs: rhs[1],
753                    num_bits: 8,
754                    output,
755                },
756            );
757
758            let case_xor = (input_arr_3, input_arr_8.clone(), witness.clone()).prop_map(
759                |(lhs, rhs, output)| BlackBoxFuncCall::XOR {
760                    lhs: lhs[0],
761                    rhs: rhs[1],
762                    num_bits: 8,
763                    output,
764                },
765            );
766
767            let case_range = witness.clone().prop_map(|witness| BlackBoxFuncCall::RANGE {
768                input: FunctionInput::Witness(witness),
769                num_bits: 32,
770            });
771
772            let case_blake2s =
773                (input_arr_8.clone(), witness_arr_32.clone()).prop_map(|(inputs, outputs)| {
774                    BlackBoxFuncCall::Blake2s { inputs: inputs.to_vec(), outputs }
775                });
776
777            let case_blake3 =
778                (input_arr_8.clone(), witness_arr_32).prop_map(|(inputs, outputs)| {
779                    BlackBoxFuncCall::Blake3 { inputs: inputs.to_vec(), outputs }
780                });
781
782            let case_ecdsa_secp256k1 = (
783                input_arr_32.clone(),
784                input_arr_32.clone(),
785                input_arr_64.clone(),
786                input_arr_32.clone(),
787                witness.clone(),
788                input.clone(),
789            )
790                .prop_map(
791                    |(public_key_x, public_key_y, signature, hashed_message, output, predicate)| {
792                        BlackBoxFuncCall::EcdsaSecp256k1 {
793                            public_key_x,
794                            public_key_y,
795                            signature,
796                            hashed_message,
797                            output,
798                            predicate,
799                        }
800                    },
801                );
802
803            let case_ecdsa_secp256r1 = (
804                input_arr_32.clone(),
805                input_arr_32.clone(),
806                input_arr_64,
807                input_arr_32,
808                witness.clone(),
809                input.clone(),
810            )
811                .prop_map(
812                    |(public_key_x, public_key_y, signature, hashed_message, output, predicate)| {
813                        BlackBoxFuncCall::EcdsaSecp256r1 {
814                            public_key_x,
815                            public_key_y,
816                            signature,
817                            hashed_message,
818                            output,
819                            predicate,
820                        }
821                    },
822                );
823
824            let case_multi_scalar_mul = (
825                input_vec.clone(),
826                input_vec.clone(),
827                input.clone(),
828                witness.clone(),
829                witness.clone(),
830            )
831                .prop_map(|(points, scalars, predicate, w1, w2)| {
832                    BlackBoxFuncCall::MultiScalarMul {
833                        points,
834                        scalars,
835                        predicate,
836                        outputs: (w1, w2),
837                    }
838                });
839
840            let case_embedded_curve_add =
841                (input_arr_2.clone(), input_arr_2, input.clone(), witness.clone(), witness)
842                    .prop_map(|(input1, input2, predicate, w1, w2)| {
843                        BlackBoxFuncCall::EmbeddedCurveAdd {
844                            input1,
845                            input2,
846                            predicate,
847                            outputs: (w1, w2),
848                        }
849                    });
850
851            let case_keccakf1600 = (input_arr_25, witness_arr_25)
852                .prop_map(|(inputs, outputs)| BlackBoxFuncCall::Keccakf1600 { inputs, outputs });
853
854            let case_recursive_aggregation = (
855                input_vec.clone(),
856                input_vec.clone(),
857                input_vec.clone(),
858                input.clone(),
859                any::<u32>(),
860                input,
861            )
862                .prop_map(
863                    |(verification_key, proof, public_inputs, key_hash, proof_type, predicate)| {
864                        BlackBoxFuncCall::RecursiveAggregation {
865                            verification_key,
866                            proof,
867                            public_inputs,
868                            key_hash,
869                            proof_type,
870                            predicate,
871                        }
872                    },
873                );
874
875            let case_poseidon2_permutation =
876                (input_vec, witness_vec).prop_map(|(inputs, outputs)| {
877                    BlackBoxFuncCall::Poseidon2Permutation { inputs, outputs }
878                });
879
880            let case_sha256_compression = (input_arr_16, input_arr_8, witness_arr_8).prop_map(
881                |(inputs, hash_values, outputs)| BlackBoxFuncCall::Sha256Compression {
882                    inputs,
883                    hash_values,
884                    outputs,
885                },
886            );
887
888            prop_oneof![
889                case_aes128_encrypt,
890                case_and,
891                case_xor,
892                case_range,
893                case_blake2s,
894                case_blake3,
895                case_ecdsa_secp256k1,
896                case_ecdsa_secp256r1,
897                case_multi_scalar_mul,
898                case_embedded_curve_add,
899                case_keccakf1600,
900                case_recursive_aggregation,
901                case_poseidon2_permutation,
902                case_sha256_compression,
903            ]
904            .boxed()
905        }
906    }
907}