snarkvm_circuit_program/response/
process_outputs_from_callback.rs

1// Copyright 2024-2025 Aleo Network Foundation
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, and output types.
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    ) -> Vec<Value<A>> {
30        // Compute the function ID.
31        let function_id = compute_function_id(network_id, program_id, function_name);
32
33        match outputs
34            .iter()
35            .zip_eq(output_types)
36            .enumerate()
37            .map(|(index, (output, output_types))| {
38                match output_types {
39                    // For a constant output, compute the hash (using `tcm`) of the output.
40                    console::ValueType::Constant(..) => {
41                        // Inject the output as `Mode::Constant`.
42                        let output = Value::new(Mode::Constant, output.clone());
43                        // Ensure the output is a plaintext.
44                        ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
45
46                        // Prepare the index as a constant field element.
47                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
48                        // Construct the preimage as `(function ID || output || tcm || index)`.
49                        let mut preimage = Vec::new();
50                        preimage.push(function_id.clone());
51                        preimage.extend(output.to_fields());
52                        preimage.push(tcm.clone());
53                        preimage.push(output_index);
54
55                        // Hash the output to a field element.
56                        match &output {
57                            // Return the output ID.
58                            Value::Plaintext(..) => Ok((OutputID::constant(A::hash_psd8(&preimage)), output)),
59                            // Ensure the output is a plaintext.
60                            Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
61                            Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
62                        }
63                    }
64                    // For a public output, compute the hash (using `tcm`) of the output.
65                    console::ValueType::Public(..) => {
66                        // Inject the output as `Mode::Private`.
67                        let output = Value::new(Mode::Private, output.clone());
68                        // Ensure the output is a plaintext.
69                        ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
70
71                        // Prepare the index as a constant field element.
72                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
73                        // Construct the preimage as `(function ID || output || tcm || index)`.
74                        let mut preimage = Vec::new();
75                        preimage.push(function_id.clone());
76                        preimage.extend(output.to_fields());
77                        preimage.push(tcm.clone());
78                        preimage.push(output_index);
79
80                        // Hash the output to a field element.
81                        match &output {
82                            // Return the output ID.
83                            Value::Plaintext(..) => Ok((OutputID::public(A::hash_psd8(&preimage)), output)),
84                            // Ensure the output is a plaintext.
85                            Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
86                            Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
87                        }
88                    }
89                    // For a private output, compute the ciphertext (using `tvk`) and hash the ciphertext.
90                    console::ValueType::Private(..) => {
91                        // Inject the output as `Mode::Private`.
92                        let output = Value::new(Mode::Private, output.clone());
93                        // Ensure the output is a plaintext.
94                        ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
95
96                        // Prepare the index as a constant field element.
97                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
98                        // Compute the output view key as `Hash(function ID || tvk || index)`.
99                        let output_view_key = A::hash_psd4(&[function_id.clone(), tvk.clone(), output_index]);
100                        // Compute the ciphertext.
101                        let ciphertext = match &output {
102                            Value::Plaintext(plaintext) => plaintext.encrypt_symmetric(output_view_key),
103                            // Ensure the output is a plaintext.
104                            Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
105                            Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
106                        };
107                        // Return the output ID.
108                        Ok((OutputID::private(A::hash_psd8(&ciphertext.to_fields())), output))
109                    }
110                    // For a record output, compute the record commitment.
111                    console::ValueType::Record(record_name) => {
112                        // Inject the output as `Mode::Private`.
113                        let output = Value::new(Mode::Private, output.clone());
114
115                        // Retrieve the record.
116                        let record = match &output {
117                            Value::Record(record) => record,
118                            // Ensure the output is a record.
119                            Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
120                            Value::Future(..) => A::halt("Expected a record output, found a future output"),
121                        };
122                        // Compute the record commitment.
123                        let commitment = record.to_commitment(program_id, &Identifier::constant(*record_name));
124
125                        // Return the output ID.
126                        // Note: Because this is a callback, the output ID is an **external record** ID.
127                        Ok((OutputID::external_record(commitment), output))
128                    }
129                    // For an external record output, compute the hash (using `tvk`) of the output.
130                    console::ValueType::ExternalRecord(..) => {
131                        // Inject the output as `Mode::Private`.
132                        let output = Value::new(Mode::Private, output.clone());
133                        // Ensure the output is a record.
134                        ensure!(matches!(output, Value::Record(..)), "Expected a record output");
135
136                        // Prepare the index as a constant field element.
137                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
138                        // Construct the preimage as `(function ID || output || tvk || index)`.
139                        let mut preimage = Vec::new();
140                        preimage.push(function_id.clone());
141                        preimage.extend(output.to_fields());
142                        preimage.push(tvk.clone());
143                        preimage.push(output_index);
144
145                        // Return the output ID.
146                        match &output {
147                            Value::Record(..) => Ok((OutputID::external_record(A::hash_psd8(&preimage)), output)),
148                            // Ensure the output is a record.
149                            Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
150                            Value::Future(..) => A::halt("Expected a record output, found a future output"),
151                        }
152                    }
153                    // For a future output, compute the hash (using `tcm`) of the output.
154                    console::ValueType::Future(..) => {
155                        // Inject the output as `Mode::Private`.
156                        let output = Value::new(Mode::Private, output.clone());
157                        // Ensure the output is a future.
158                        ensure!(matches!(output, Value::Future(..)), "Expected a future output");
159
160                        // Prepare the index as a constant field element.
161                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
162                        // Construct the preimage as `(function ID || output || tcm || index)`.
163                        let mut preimage = Vec::new();
164                        preimage.push(function_id.clone());
165                        preimage.extend(output.to_fields());
166                        preimage.push(tcm.clone());
167                        preimage.push(output_index);
168
169                        // Hash the output to a field element.
170                        match &output {
171                            // Return the output ID.
172                            Value::Future(..) => Ok((OutputID::future(A::hash_psd8(&preimage)), output)),
173                            // Ensure the output is a future.
174                            Value::Plaintext(..) => A::halt("Expected a future output, found a plaintext output"),
175                            Value::Record(..) => A::halt("Expected a future output, found a record output"),
176                        }
177                    }
178                }
179            })
180            .collect::<Result<Vec<_>>>()
181        {
182            Ok(outputs) => {
183                // Unzip the output IDs from the output values.
184                let (_, outputs): (Vec<OutputID<A>>, _) = outputs.into_iter().unzip();
185                // Return the outputs.
186                outputs
187            }
188            Err(error) => A::halt(error.to_string()),
189        }
190    }
191}
192
193#[cfg(all(test, feature = "console"))]
194mod tests {
195    use super::*;
196    use crate::Circuit;
197    use snarkvm_utilities::{TestRng, Uniform};
198
199    use anyhow::Result;
200
201    pub(crate) const ITERATIONS: usize = 20;
202
203    fn check_from_callback(
204        mode: Mode,
205        num_constants: u64,
206        num_public: u64,
207        num_private: u64,
208        num_constraints: u64,
209    ) -> Result<()> {
210        use console::Network;
211
212        let rng = &mut TestRng::default();
213
214        for i in 0..ITERATIONS {
215            // Sample a `tvk`.
216            let tvk = console::Field::rand(rng);
217            // Compute the transition commitment as `Hash(tvk)`.
218            let tcm = <Circuit as Environment>::Network::hash_psd2(&[tvk])?;
219
220            // Compute the nonce.
221            let index = console::Field::from_u64(8);
222            let randomizer = <Circuit as Environment>::Network::hash_to_scalar_psd2(&[tvk, index]).unwrap();
223            let nonce = <Circuit as Environment>::Network::g_scalar_multiply(&randomizer);
224
225            // Construct the outputs.
226            let output_constant = console::Value::<<Circuit as Environment>::Network>::Plaintext(
227                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
228            );
229            let output_public = console::Value::<<Circuit as Environment>::Network>::Plaintext(
230                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
231            );
232            let output_private = console::Value::<<Circuit as Environment>::Network>::Plaintext(
233                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
234            );
235            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());
236            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());
237            let outputs = vec![output_constant, output_public, output_private, output_record, output_external_record];
238
239            // Construct the output types.
240            let output_types = vec![
241                console::ValueType::from_str("amount.constant").unwrap(),
242                console::ValueType::from_str("amount.public").unwrap(),
243                console::ValueType::from_str("amount.private").unwrap(),
244                console::ValueType::from_str("token.record").unwrap(),
245                console::ValueType::from_str("token.aleo/token.record").unwrap(),
246            ];
247
248            // Construct the output registers.
249            let output_registers = vec![
250                Some(console::Register::Locator(5)),
251                Some(console::Register::Locator(6)),
252                Some(console::Register::Locator(7)),
253                Some(console::Register::Locator(8)),
254                Some(console::Register::Locator(9)),
255            ];
256
257            // Construct a network ID.
258            let network_id = console::U16::new(<Circuit as Environment>::Network::ID);
259            // Construct a program ID.
260            let program_id = console::ProgramID::from_str("test.aleo")?;
261            // Construct a function name.
262            let function_name = console::Identifier::from_str("check")?;
263
264            // Construct the response.
265            let response = console::Response::new(
266                &network_id,
267                &program_id,
268                &function_name,
269                4,
270                &tvk,
271                &tcm,
272                outputs.clone(),
273                &output_types,
274                &output_registers,
275            )?;
276            // assert!(response.verify());
277
278            // Inject the network ID, program ID, function name, `tvk`, `tcm`.
279            let network_id = U16::<Circuit>::constant(network_id);
280            let program_id = ProgramID::<Circuit>::new(mode, program_id);
281            let function_name = Identifier::<Circuit>::new(mode, function_name);
282            let tvk = Field::<Circuit>::new(mode, tvk);
283            let tcm = Field::<Circuit>::new(mode, tcm);
284
285            Circuit::scope(format!("Response {i}"), || {
286                let outputs = Response::process_outputs_from_callback(
287                    &network_id,
288                    &program_id,
289                    &function_name,
290                    4,
291                    &tvk,
292                    &tcm,
293                    response.outputs().to_vec(),
294                    &output_types,
295                );
296                assert_eq!(response.outputs(), outputs.eject_value());
297                match mode.is_constant() {
298                    true => assert_scope!(<=num_constants, num_public, num_private, num_constraints),
299                    false => assert_scope!(<=num_constants, num_public, num_private, num_constraints),
300                }
301            });
302
303            // Compute the response using outputs (circuit).
304            let outputs = Inject::new(mode, response.outputs().to_vec());
305            let candidate_b = Response::from_outputs(
306                &network_id,
307                &program_id,
308                &function_name,
309                4,
310                &tvk,
311                &tcm,
312                outputs,
313                &output_types,
314                &output_registers,
315            );
316            assert_eq!(response, candidate_b.eject_value());
317
318            Circuit::reset();
319        }
320        Ok(())
321    }
322
323    // Note: These counts are correct. At this (high) level of a program, we override the default mode in many cases,
324    // based on the user-defined visibility in the types. Thus, we have nonzero public, private, and constraint values.
325    // These bounds are determined experimentally.
326
327    #[test]
328    fn test_from_callback_constant() -> Result<()> {
329        check_from_callback(Mode::Constant, 20844, 5, 4922, 4931)
330    }
331
332    #[test]
333    fn test_from_callback_public() -> Result<()> {
334        check_from_callback(Mode::Public, 20844, 5, 6217, 6226)
335    }
336
337    #[test]
338    fn test_from_callback_private() -> Result<()> {
339        check_from_callback(Mode::Private, 20844, 5, 6217, 6226)
340    }
341}