Skip to main content

snarkvm_circuit_program/response/
from_outputs.rs

1// Copyright (c) 2019-2026 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    /// Initializes a response, given the number of inputs, tvk, tcm, outputs, output types, and output registers.
20    pub fn from_outputs(
21        signer: &Address<A>,
22        network_id: &U16<A>,
23        program_id: &ProgramID<A>,
24        function_name: &Identifier<A>,
25        num_inputs: usize,
26        tvk: &Field<A>,
27        tcm: &Field<A>,
28        outputs: Vec<Value<A>>,
29        output_types: &[console::ValueType<A::Network>], // Note: Console type
30        output_registers: &[Option<console::Register<A::Network>>], // Note: Console type
31    ) -> Self {
32        // Compute the function ID.
33        let function_id = compute_function_id(network_id, program_id, function_name);
34
35        // Compute the output IDs.
36        let output_ids = outputs
37            .iter()
38            .zip_eq(output_types)
39            .zip_eq(output_registers)
40            .enumerate()
41            .map(|(index, ((output, output_type), output_register))| {
42                match output_type {
43                    // For a constant output, compute the hash (using `tcm`) of the output.
44                    console::ValueType::Constant(..) => {
45                        // Prepare the index as a constant field element.
46                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
47                        // Construct the preimage as `(function ID || output || tcm || index)`.
48                        let mut preimage = Vec::new();
49                        preimage.push(function_id.clone());
50                        preimage.extend(output.to_fields());
51                        preimage.push(tcm.clone());
52                        preimage.push(output_index);
53
54                        // Hash the output to a field element.
55                        match &output {
56                            // Return the output ID.
57                            Value::Plaintext(..) => OutputID::constant(A::hash_psd8(&preimage)),
58                            // Ensure the output is a plaintext.
59                            Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
60                            Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
61                            Value::DynamicRecord(..) => {
62                                A::halt("Expected a plaintext output, found a dynamic record output")
63                            }
64                            Value::DynamicFuture(..) => {
65                                A::halt("Expected a plaintext output, found a dynamic future output")
66                            }
67                        }
68                    }
69                    // For a public output, compute the hash (using `tcm`) of the output.
70                    console::ValueType::Public(..) => {
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(..) => OutputID::public(A::hash_psd8(&preimage)),
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                            Value::DynamicRecord(..) => {
88                                A::halt("Expected a plaintext output, found a dynamic record output")
89                            }
90                            Value::DynamicFuture(..) => {
91                                A::halt("Expected a plaintext output, found a dynamic future output")
92                            }
93                        }
94                    }
95                    // For a private output, compute the ciphertext (using `tvk`) and hash the ciphertext.
96                    console::ValueType::Private(..) => {
97                        // Prepare the index as a constant field element.
98                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
99                        // Compute the output view key as `Hash(function ID || tvk || index)`.
100                        let output_view_key = A::hash_psd4(&[function_id.clone(), tvk.clone(), output_index]);
101                        // Compute the ciphertext.
102                        let ciphertext = match &output {
103                            Value::Plaintext(plaintext) => plaintext.encrypt_symmetric(output_view_key),
104                            // Ensure the output is a plaintext.
105                            Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
106                            Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
107                            Value::DynamicRecord(..) => {
108                                A::halt("Expected a plaintext output, found a dynamic record output")
109                            }
110                            Value::DynamicFuture(..) => {
111                                A::halt("Expected a plaintext output, found a dynamic future output")
112                            }
113                        };
114                        // Return the output ID.
115                        OutputID::private(A::hash_psd8(&ciphertext.to_fields()))
116                    }
117                    // For a record output, compute the record commitment, and encrypt the record (using `tvk`).
118                    console::ValueType::Record(record_name) => {
119                        // Retrieve the record.
120                        let record = match &output {
121                            Value::Record(record) => record,
122                            // Ensure the output is a record.
123                            Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
124                            Value::Future(..) => A::halt("Expected a record output, found a future output"),
125                            Value::DynamicRecord(..) => {
126                                A::halt("Expected a record output, found a dynamic record output")
127                            }
128                            Value::DynamicFuture(..) => {
129                                A::halt("Expected a record output, found a dynamic future output")
130                            }
131                        };
132
133                        // Retrieve the output register.
134                        let output_register = match output_register {
135                            Some(output_register) => output_register,
136                            None => A::halt("Expected a register to be paired with a record output"),
137                        };
138
139                        // Prepare the index as a constant field element.
140                        let output_index = Field::constant(console::Field::from_u64(output_register.locator()));
141                        // Compute the encryption randomizer as `HashToScalar(tvk || index)`.
142                        let randomizer = A::hash_to_scalar_psd2(&[tvk.clone(), output_index]);
143
144                        // Encrypt the record, using the randomizer.
145                        let (encrypted_record, record_view_key) = record.encrypt_symmetric(&randomizer);
146
147                        // Compute the record commitment.
148                        let commitment =
149                            record.to_commitment(program_id, &Identifier::constant(*record_name), &record_view_key);
150
151                        // Compute the record checksum, as the hash of the encrypted record.
152                        let checksum = A::hash_bhp1024(&encrypted_record.to_bits_le());
153
154                        // Prepare a randomizer for the sender ciphertext.
155                        let randomizer = A::hash_psd4(&[A::encryption_domain(), record_view_key, Field::one()]);
156                        // Encrypt the signer address using the randomizer.
157                        let sender_ciphertext = signer.to_group().to_x_coordinate() + randomizer;
158
159                        // Return the output ID.
160                        OutputID::record(commitment, checksum, sender_ciphertext)
161                    }
162                    // For an external record output, compute the hash (using `tvk`) of the output.
163                    console::ValueType::ExternalRecord(..) => {
164                        // Prepare the index as a constant field element.
165                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
166                        // Construct the preimage as `(function ID || output || tvk || index)`.
167                        let mut preimage = Vec::new();
168                        preimage.push(function_id.clone());
169                        preimage.extend(output.to_fields());
170                        preimage.push(tvk.clone());
171                        preimage.push(output_index);
172
173                        // Return the output ID.
174                        match &output {
175                            Value::Record(..) => OutputID::external_record(A::hash_psd8(&preimage)),
176                            // Ensure the output is a record.
177                            Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
178                            Value::Future(..) => A::halt("Expected a record output, found a future output"),
179                            Value::DynamicRecord(..) => {
180                                A::halt("Expected a record output, found a dynamic record output")
181                            }
182                            Value::DynamicFuture(..) => {
183                                A::halt("Expected a record output, found a dynamic future output")
184                            }
185                        }
186                    }
187                    // For a future output, compute the hash (using `tcm`) of the output.
188                    console::ValueType::Future(..) => {
189                        // Prepare the index as a constant field element.
190                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
191                        // Construct the preimage as `(function ID || output || tcm || index)`.
192                        let mut preimage = Vec::new();
193                        preimage.push(function_id.clone());
194                        preimage.extend(output.to_fields());
195                        preimage.push(tcm.clone());
196                        preimage.push(output_index);
197
198                        // Hash the output to a field element.
199                        match &output {
200                            // Return the output ID.
201                            Value::Future(..) => OutputID::future(A::hash_psd8(&preimage)),
202                            // Ensure the output is a future.
203                            Value::Plaintext(..) => A::halt("Expected a future output, found a plaintext output"),
204                            Value::Record(..) => A::halt("Expected a future output, found a record output"),
205                            Value::DynamicRecord(..) => {
206                                A::halt("Expected a future output, found a dynamic record output")
207                            }
208                            Value::DynamicFuture(..) => {
209                                A::halt("Expected a future output, found a dynamic future output")
210                            }
211                        }
212                    }
213                    // For a dynamic record output, compute the hash (using `tvk`) of the output.
214                    console::ValueType::DynamicRecord => {
215                        // Prepare the index as a constant field element.
216                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
217                        // Construct the preimage as `(function ID || output || tvk || index)`.
218                        let mut preimage = Vec::new();
219                        preimage.push(function_id.clone());
220                        preimage.extend(output.to_fields());
221                        preimage.push(tvk.clone());
222                        preimage.push(output_index);
223
224                        // Return the output ID.
225                        match &output {
226                            Value::DynamicRecord(..) => OutputID::dynamic_record(A::hash_psd8(&preimage)),
227                            // Ensure the output is a dynamic record.
228                            Value::Plaintext(..) => {
229                                A::halt("Expected a dynamic record output, found a plaintext output")
230                            }
231                            Value::Future(..) => A::halt("Expected a dynamic record output, found a future output"),
232                            Value::Record(..) => A::halt("Expected a dynamic record output, found a record output"),
233                            Value::DynamicFuture(..) => {
234                                A::halt("Expected a dynamic record output, found a dynamic future output")
235                            }
236                        }
237                    }
238                    // For a dynamic future output, compute the hash (using `tcm`) of the output.
239                    console::ValueType::DynamicFuture => {
240                        // Prepare the index as a constant field element.
241                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
242                        // Construct the preimage as `(function ID || output || tcm || index)`.
243                        let mut preimage = Vec::new();
244                        preimage.push(function_id.clone());
245                        preimage.extend(output.to_fields());
246                        preimage.push(tcm.clone());
247                        preimage.push(output_index);
248
249                        // Hash the output to a field element.
250                        match &output {
251                            // Return the output ID.
252                            Value::DynamicFuture(..) => OutputID::dynamic_future(A::hash_psd8(&preimage)),
253                            // Ensure the output is a dynamic future.
254                            Value::Plaintext(..) => {
255                                A::halt("Expected a dynamic future output, found a plaintext output")
256                            }
257                            Value::Record(..) => A::halt("Expected a dynamic future output, found a record output"),
258                            Value::DynamicRecord(..) => {
259                                A::halt("Expected a dynamic future output, found a dynamic record output")
260                            }
261                            Value::Future(..) => A::halt("Expected a dynamic future output, found a future output"),
262                        }
263                    }
264                }
265            })
266            .collect();
267
268        // Return the response.
269        Self { output_ids, outputs }
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use crate::Circuit;
277    use snarkvm_circuit_types::{U16, environment::UpdatableCount};
278    use snarkvm_utilities::{TestRng, Uniform};
279
280    use anyhow::Result;
281
282    pub(crate) const ITERATIONS: usize = 10;
283
284    fn check_from_outputs(
285        mode: Mode,
286        program_id: &str,
287        function_name: &str,
288        is_dynamic: bool,
289        use_record: bool,
290        expected_count: UpdatableCount,
291    ) -> Result<()> {
292        use console::Network;
293
294        let rng = &mut TestRng::default();
295
296        for i in 0..ITERATIONS {
297            // Sample a `tvk`.
298            let tvk = console::Field::rand(rng);
299            // Compute the transition commitment as `Hash(tvk)`.
300            let tcm = <Circuit as Environment>::Network::hash_psd2(&[tvk])?;
301
302            // Compute the nonce.
303            let index = console::Field::from_u64(8);
304            let randomizer = <Circuit as Environment>::Network::hash_to_scalar_psd2(&[tvk, index]).unwrap();
305            let nonce = <Circuit as Environment>::Network::g_scalar_multiply(&randomizer);
306
307            // Construct the outputs.
308            let output_constant = console::Value::<<Circuit as Environment>::Network>::Plaintext(
309                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
310            );
311            let output_public = console::Value::<<Circuit as Environment>::Network>::Plaintext(
312                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
313            );
314            let output_private = console::Value::<<Circuit as Environment>::Network>::Plaintext(
315                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
316            );
317            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());
318            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());
319            let outputs = if use_record {
320                vec![output_constant, output_public, output_private, output_record, output_external_record]
321            } else {
322                vec![output_constant, output_public, output_private, output_external_record]
323            };
324
325            // Construct the output types.
326            let output_types = if use_record {
327                vec![
328                    console::ValueType::from_str("amount.constant").unwrap(),
329                    console::ValueType::from_str("amount.public").unwrap(),
330                    console::ValueType::from_str("amount.private").unwrap(),
331                    console::ValueType::from_str("token.record").unwrap(),
332                    console::ValueType::from_str("token.aleo/token.record").unwrap(),
333                ]
334            } else {
335                vec![
336                    console::ValueType::from_str("amount.constant").unwrap(),
337                    console::ValueType::from_str("amount.public").unwrap(),
338                    console::ValueType::from_str("amount.private").unwrap(),
339                    console::ValueType::from_str("token.aleo/token.record").unwrap(),
340                ]
341            };
342
343            // Construct the output registers.
344            let mut output_registers = vec![
345                Some(console::Register::Locator(5)),
346                Some(console::Register::Locator(6)),
347                Some(console::Register::Locator(7)),
348                Some(console::Register::Locator(8)),
349            ];
350            if use_record {
351                output_registers.push(Some(console::Register::Locator(9)));
352            }
353
354            // Construct a signer.
355            let signer = console::Address::rand(rng);
356            // Construct a network ID.
357            let network_id = console::U16::new(<Circuit as Environment>::Network::ID);
358            // Construct a program ID.
359            let program_id = console::ProgramID::from_str(program_id)?;
360            // Construct a function name.
361            let function_name = console::Identifier::from_str(function_name)?;
362
363            // Construct the response.
364            let response = console::Response::new(
365                &signer,
366                &network_id,
367                &program_id,
368                &function_name,
369                4,
370                &tvk,
371                &tcm,
372                outputs.clone(),
373                &output_types,
374                &output_registers,
375            )?;
376
377            // Inject the signer, network ID, program ID, function name, `tvk`, `tcm`, and outputs.
378            let signer = Address::<Circuit>::new(mode, signer);
379            let network_id = U16::<Circuit>::constant(network_id);
380            let program_id = match is_dynamic {
381                false => ProgramID::<Circuit>::constant(program_id),
382                true => ProgramID::<Circuit>::public(program_id),
383            };
384            let function_name = match is_dynamic {
385                false => Identifier::<Circuit>::constant(function_name),
386                true => Identifier::<Circuit>::public(function_name),
387            };
388            let tvk = Field::<Circuit>::new(mode, tvk);
389            let tcm = Field::<Circuit>::new(mode, tcm);
390            let outputs = Inject::new(mode, outputs);
391
392            Circuit::scope(format!("Response {i}"), || {
393                // Compute the response using outputs (circuit).
394                let candidate = Response::from_outputs(
395                    &signer,
396                    &network_id,
397                    &program_id,
398                    &function_name,
399                    4,
400                    &tvk,
401                    &tcm,
402                    outputs,
403                    &output_types,
404                    &output_registers,
405                );
406                assert_eq!(response, candidate.eject_value());
407                expected_count.assert_matches(
408                    Circuit::num_constants_in_scope(),
409                    Circuit::num_public_in_scope(),
410                    Circuit::num_private_in_scope(),
411                    Circuit::num_constraints_in_scope(),
412                );
413            });
414            Circuit::reset();
415        }
416        Ok(())
417    }
418
419    // Note: These counts are correct. At this (high) level of a program, we override the default mode in many cases,
420    // based on the user-defined visibility in the types. Thus, we have nonzero public, private, and constraint values.
421
422    // TODO: Explain why the first runs of the tests for `Public` and `Private` modes yield a large number of constants
423
424    #[test]
425    #[rustfmt::skip]
426    fn test_from_outputs_constant() -> Result<()> {
427        // Static response without records.
428        check_from_outputs(Mode::Constant, "test.aleo", "foo", false, false, count_less_than!(19397, 4, 1497, 1505))?;
429        check_from_outputs(Mode::Constant, "credits.aleo", "transfer_public", false, false, count_less_than!(1011, 4, 1497, 1505))?; 
430
431        // Static response with records.
432        check_from_outputs(Mode::Constant, "test.aleo", "foo", false, true, count_less_than!(19445, 7, 13274, 13300))?;
433        check_from_outputs(Mode::Constant, "credits.aleo", "transfer_public", false, true, count_less_than!(5176, 7, 13376, 13402))?;
434
435
436        // Dynamic response without records.
437        check_from_outputs(Mode::Constant, "test.aleo", "foo", true, false, count_less_than!(713, 4, 6571, 6585))?;
438        check_from_outputs(Mode::Constant, "credits.aleo", "transfer_public", true, false, count_less_than!(713, 4, 6571, 6585))?;
439
440        // Dynamic response with records.
441        check_from_outputs(Mode::Constant, "test.aleo", "foo", true, true, count_less_than!(4848, 7, 19481, 19517))?;
442        check_from_outputs(Mode::Constant, "credits.aleo", "transfer_public", true, true, count_less_than!(4716, 7, 19653, 19689))?;
443
444        Ok(())
445    }
446
447    #[test]
448    #[rustfmt::skip]
449    fn test_from_outputs_public() -> Result<()> {
450        // Static response without records.
451        check_from_outputs(Mode::Public, "test.aleo", "foo", false, false, count_is!(<=19397, 4, 3762, 3770))?;
452        check_from_outputs(Mode::Public, "credits.aleo", "transfer_public", false, false, count_is!(1009, 4, 3762, 3770))?;
453
454        // Static response with records.
455        check_from_outputs(Mode::Public, "test.aleo", "foo", false, true, count_is!(<=18692, 7, 18057, 18085))?;
456        check_from_outputs(Mode::Public, "credits.aleo", "transfer_public", false, true, count_is!(4419, 7, 18159, 18187))?;
457
458        // Dynamic response without records.
459        check_from_outputs(Mode::Public, "test.aleo", "foo", true, false, count_is!(705, 4, 5472, 5486))?;
460        check_from_outputs(Mode::Public, "credits.aleo", "transfer_public", true, false, count_is!(707, 4, 5677, 5691))?;
461
462        // Dynamic response with records.
463        check_from_outputs(Mode::Public, "test.aleo", "foo", true, true, count_is!(4087, 7, 19890, 19924))?;
464        check_from_outputs(Mode::Public, "credits.aleo", "transfer_public", true, true, count_is!(3957, 7, 20267, 20301))?;
465
466        Ok(())
467    }
468
469    #[test]
470    #[rustfmt::skip]
471    fn test_from_outputs_private() -> Result<()> {
472        // Static response without records.
473        check_from_outputs(Mode::Private, "test.aleo", "foo", false, false, count_is!(<=19397, 4, 3762, 3770))?;
474        check_from_outputs(Mode::Private, "credits.aleo", "transfer_public", false, false, count_is!(1009, 4, 3762, 3770))?;
475
476        // Static response with records.
477        check_from_outputs(Mode::Private, "test.aleo", "foo", false, true, count_is!(<=18692, 7, 18057, 18085))?;
478        check_from_outputs(Mode::Private, "credits.aleo", "transfer_public", false, true, count_is!(4419, 7, 18159, 18187))?;
479
480        // Dynamic response without records.
481        check_from_outputs(Mode::Private, "test.aleo", "foo", true, false, count_is!(705, 4, 5472, 5486))?;
482        check_from_outputs(Mode::Private, "credits.aleo", "transfer_public", true, false, count_is!(707, 4, 5677, 5691))?;
483
484        // Dynamic response with records.
485        check_from_outputs(Mode::Private, "test.aleo", "foo", true, true, count_is!(4087, 7, 19890, 19924))?;
486        check_from_outputs(Mode::Private, "credits.aleo", "transfer_public", true, true, count_is!(3957, 7, 20267, 20301))?;
487
488        Ok(())
489    }
490}