snarkvm_circuit_program/response/
process_outputs_from_callback.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18impl<A: Aleo> Response<A> {
19    /// Returns the injected circuit outputs, given the number of inputs, tvk, tcm, outputs, output types, and output registers.
20    pub fn process_outputs_from_callback(
21        network_id: &U16<A>,
22        program_id: &ProgramID<A>,
23        function_name: &Identifier<A>,
24        num_inputs: usize,
25        tvk: &Field<A>,
26        tcm: &Field<A>,
27        outputs: Vec<console::Value<A::Network>>,        // Note: Console type
28        output_types: &[console::ValueType<A::Network>], // Note: Console type
29        output_registers: &[Option<console::Register<A::Network>>], // Note: Console type
30    ) -> Vec<Value<A>> {
31        // Compute the function ID.
32        let function_id = compute_function_id(network_id, program_id, function_name);
33
34        match outputs
35            .iter()
36            .zip_eq(output_types)
37            .zip_eq(output_registers)
38            .enumerate()
39            .map(|(index, ((output, output_types), output_register))| {
40                match output_types {
41                    // For a constant output, compute the hash (using `tcm`) of the output.
42                    console::ValueType::Constant(..) => {
43                        // Inject the output as `Mode::Constant`.
44                        let output = Value::new(Mode::Constant, output.clone());
45                        // Ensure the output is a plaintext.
46                        ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
47
48                        // Prepare the index as a constant field element.
49                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
50                        // Construct the preimage as `(function ID || output || tcm || index)`.
51                        let mut preimage = Vec::new();
52                        preimage.push(function_id.clone());
53                        preimage.extend(output.to_fields());
54                        preimage.push(tcm.clone());
55                        preimage.push(output_index);
56
57                        // Hash the output to a field element.
58                        match &output {
59                            // Return the output ID.
60                            Value::Plaintext(..) => Ok((OutputID::constant(A::hash_psd8(&preimage)), output)),
61                            // Ensure the output is a plaintext.
62                            Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
63                            Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
64                        }
65                    }
66                    // For a public output, compute the hash (using `tcm`) of the output.
67                    console::ValueType::Public(..) => {
68                        // Inject the output as `Mode::Private`.
69                        let output = Value::new(Mode::Private, output.clone());
70                        // Ensure the output is a plaintext.
71                        ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
72
73                        // Prepare the index as a constant field element.
74                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
75                        // Construct the preimage as `(function ID || output || tcm || index)`.
76                        let mut preimage = Vec::new();
77                        preimage.push(function_id.clone());
78                        preimage.extend(output.to_fields());
79                        preimage.push(tcm.clone());
80                        preimage.push(output_index);
81
82                        // Hash the output to a field element.
83                        match &output {
84                            // Return the output ID.
85                            Value::Plaintext(..) => Ok((OutputID::public(A::hash_psd8(&preimage)), output)),
86                            // Ensure the output is a plaintext.
87                            Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
88                            Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
89                        }
90                    }
91                    // For a private output, compute the ciphertext (using `tvk`) and hash the ciphertext.
92                    console::ValueType::Private(..) => {
93                        // Inject the output as `Mode::Private`.
94                        let output = Value::new(Mode::Private, output.clone());
95                        // Ensure the output is a plaintext.
96                        ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
97
98                        // Prepare the index as a constant field element.
99                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
100                        // Compute the output view key as `Hash(function ID || tvk || index)`.
101                        let output_view_key = A::hash_psd4(&[function_id.clone(), tvk.clone(), output_index]);
102                        // Compute the ciphertext.
103                        let ciphertext = match &output {
104                            Value::Plaintext(plaintext) => plaintext.encrypt_symmetric(output_view_key),
105                            // Ensure the output is a plaintext.
106                            Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
107                            Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
108                        };
109                        // Return the output ID.
110                        Ok((OutputID::private(A::hash_psd8(&ciphertext.to_fields())), output))
111                    }
112                    // For a record output, compute the record commitment.
113                    console::ValueType::Record(record_name) => {
114                        // Inject the output as `Mode::Private`.
115                        let output = Value::new(Mode::Private, output.clone());
116
117                        // Retrieve the record.
118                        let record = match &output {
119                            Value::Record(record) => record,
120                            // Ensure the output is a record.
121                            Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
122                            Value::Future(..) => A::halt("Expected a record output, found a future output"),
123                        };
124
125                        // Retrieve the output register.
126                        let output_register = match output_register {
127                            Some(output_register) => output_register,
128                            None => A::halt("Expected a register to be paired with a record output"),
129                        };
130
131                        // Prepare the index as a constant field element.
132                        let output_index = Field::constant(console::Field::from_u64(output_register.locator()));
133                        // Compute the encryption randomizer as `HashToScalar(tvk || index)`.
134                        let randomizer = A::hash_to_scalar_psd2(&[tvk.clone(), output_index]);
135
136                        // Compute the record view key.
137                        let record_view_key = ((*record.owner()).to_group() * randomizer).to_x_coordinate();
138                        // Compute the record commitment.
139                        let commitment =
140                            record.to_commitment(program_id, &Identifier::constant(*record_name), &record_view_key);
141
142                        // Return the output ID.
143                        // Note: Because this is a callback, the output ID is an **external record** ID.
144                        Ok((OutputID::external_record(commitment), output))
145                    }
146                    // For an external record output, compute the hash (using `tvk`) of the output.
147                    console::ValueType::ExternalRecord(..) => {
148                        // Inject the output as `Mode::Private`.
149                        let output = Value::new(Mode::Private, output.clone());
150                        // Ensure the output is a record.
151                        ensure!(matches!(output, Value::Record(..)), "Expected a record output");
152
153                        // Prepare the index as a constant field element.
154                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
155                        // Construct the preimage as `(function ID || output || tvk || index)`.
156                        let mut preimage = Vec::new();
157                        preimage.push(function_id.clone());
158                        preimage.extend(output.to_fields());
159                        preimage.push(tvk.clone());
160                        preimage.push(output_index);
161
162                        // Return the output ID.
163                        match &output {
164                            Value::Record(..) => Ok((OutputID::external_record(A::hash_psd8(&preimage)), output)),
165                            // Ensure the output is a record.
166                            Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
167                            Value::Future(..) => A::halt("Expected a record output, found a future output"),
168                        }
169                    }
170                    // For a future output, compute the hash (using `tcm`) of the output.
171                    console::ValueType::Future(..) => {
172                        // Inject the output as `Mode::Private`.
173                        let output = Value::new(Mode::Private, output.clone());
174                        // Ensure the output is a future.
175                        ensure!(matches!(output, Value::Future(..)), "Expected a future output");
176
177                        // Prepare the index as a constant field element.
178                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
179                        // Construct the preimage as `(function ID || output || tcm || index)`.
180                        let mut preimage = Vec::new();
181                        preimage.push(function_id.clone());
182                        preimage.extend(output.to_fields());
183                        preimage.push(tcm.clone());
184                        preimage.push(output_index);
185
186                        // Hash the output to a field element.
187                        match &output {
188                            // Return the output ID.
189                            Value::Future(..) => Ok((OutputID::future(A::hash_psd8(&preimage)), output)),
190                            // Ensure the output is a future.
191                            Value::Plaintext(..) => A::halt("Expected a future output, found a plaintext output"),
192                            Value::Record(..) => A::halt("Expected a future output, found a record output"),
193                        }
194                    }
195                }
196            })
197            .collect::<Result<Vec<_>>>()
198        {
199            Ok(outputs) => {
200                // Unzip the output IDs from the output values.
201                let (_, outputs): (Vec<OutputID<A>>, _) = outputs.into_iter().unzip();
202                // Return the outputs.
203                outputs
204            }
205            Err(error) => A::halt(error.to_string()),
206        }
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use crate::Circuit;
214    use snarkvm_utilities::{TestRng, Uniform};
215
216    use anyhow::Result;
217
218    pub(crate) const ITERATIONS: usize = 20;
219
220    fn check_from_callback(
221        mode: Mode,
222        num_constants: u64,
223        num_public: u64,
224        num_private: u64,
225        num_constraints: u64,
226    ) -> Result<()> {
227        use console::Network;
228
229        let rng = &mut TestRng::default();
230
231        for i in 0..ITERATIONS {
232            // Sample a `tvk`.
233            let tvk = console::Field::rand(rng);
234            // Compute the transition commitment as `Hash(tvk)`.
235            let tcm = <Circuit as Environment>::Network::hash_psd2(&[tvk])?;
236
237            // Compute the nonce.
238            let index = console::Field::from_u64(8);
239            let randomizer = <Circuit as Environment>::Network::hash_to_scalar_psd2(&[tvk, index]).unwrap();
240            let nonce = <Circuit as Environment>::Network::g_scalar_multiply(&randomizer);
241
242            // Construct the outputs.
243            let output_constant = console::Value::<<Circuit as Environment>::Network>::Plaintext(
244                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
245            );
246            let output_public = console::Value::<<Circuit as Environment>::Network>::Plaintext(
247                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
248            );
249            let output_private = console::Value::<<Circuit as Environment>::Network>::Plaintext(
250                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
251            );
252            let output_record = console::Value::<<Circuit as Environment>::Network>::Record(console::Record::from_str(&format!("{{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, token_amount: 100u64.private, _nonce: {nonce}.public }}")).unwrap());
253            let output_external_record = console::Value::<<Circuit as Environment>::Network>::Record(console::Record::from_str("{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, token_amount: 100u64.private, _nonce: 0group.public }").unwrap());
254            let outputs = vec![output_constant, output_public, output_private, output_record, output_external_record];
255
256            // Construct the output types.
257            let output_types = vec![
258                console::ValueType::from_str("amount.constant").unwrap(),
259                console::ValueType::from_str("amount.public").unwrap(),
260                console::ValueType::from_str("amount.private").unwrap(),
261                console::ValueType::from_str("token.record").unwrap(),
262                console::ValueType::from_str("token.aleo/token.record").unwrap(),
263            ];
264
265            // Construct the output registers.
266            let output_registers = vec![
267                Some(console::Register::Locator(5)),
268                Some(console::Register::Locator(6)),
269                Some(console::Register::Locator(7)),
270                Some(console::Register::Locator(8)),
271                Some(console::Register::Locator(9)),
272            ];
273
274            // Construct a signer.
275            let signer = console::Address::rand(rng);
276            // Construct a network ID.
277            let network_id = console::U16::new(<Circuit as Environment>::Network::ID);
278            // Construct a program ID.
279            let program_id = console::ProgramID::from_str("test.aleo")?;
280            // Construct a function name.
281            let function_name = console::Identifier::from_str("check")?;
282
283            // Construct the response.
284            let response = console::Response::new(
285                &signer,
286                &network_id,
287                &program_id,
288                &function_name,
289                4,
290                &tvk,
291                &tcm,
292                outputs.clone(),
293                &output_types,
294                &output_registers,
295            )?;
296            // assert!(response.verify());
297
298            // Inject the signer, network ID, program ID, function name, `tvk`, `tcm`.
299            let signer = Address::<Circuit>::new(mode, signer);
300            let network_id = U16::<Circuit>::constant(network_id);
301            let program_id = ProgramID::<Circuit>::new(mode, program_id);
302            let function_name = Identifier::<Circuit>::new(mode, function_name);
303            let tvk = Field::<Circuit>::new(mode, tvk);
304            let tcm = Field::<Circuit>::new(mode, tcm);
305
306            Circuit::scope(format!("Response {i}"), || {
307                let outputs = Response::process_outputs_from_callback(
308                    &network_id,
309                    &program_id,
310                    &function_name,
311                    4,
312                    &tvk,
313                    &tcm,
314                    response.outputs().to_vec(),
315                    &output_types,
316                    &output_registers,
317                );
318                assert_eq!(response.outputs(), outputs.eject_value());
319                match mode.is_constant() {
320                    true => assert_scope!(<=num_constants, <=num_public, <=num_private, <=num_constraints),
321                    false => assert_scope!(<=num_constants, num_public, num_private, num_constraints),
322                }
323            });
324
325            // Compute the response using outputs (circuit).
326            let outputs = Inject::new(mode, response.outputs().to_vec());
327            let candidate_b = Response::from_outputs(
328                &signer,
329                &network_id,
330                &program_id,
331                &function_name,
332                4,
333                &tvk,
334                &tcm,
335                outputs,
336                &output_types,
337                &output_registers,
338            );
339            assert_eq!(response, candidate_b.eject_value());
340
341            Circuit::reset();
342        }
343        Ok(())
344    }
345
346    // Note: These counts are correct. At this (high) level of a program, we override the default mode in many cases,
347    // based on the user-defined visibility in the types. Thus, we have nonzero public, private, and constraint values.
348    // These bounds are determined experimentally.
349
350    #[test]
351    fn test_from_callback_constant() -> Result<()> {
352        check_from_callback(Mode::Constant, 35000, 5, 11500, 11500)
353    }
354
355    #[test]
356    fn test_from_callback_public() -> Result<()> {
357        check_from_callback(Mode::Public, 34374, 5, 13475, 13490)
358    }
359
360    #[test]
361    fn test_from_callback_private() -> Result<()> {
362        check_from_callback(Mode::Private, 34374, 5, 13475, 13490)
363    }
364}