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