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