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