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