1use super::*;
17
18impl<A: Aleo> Response<A> {
19 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>], output_registers: &[Option<console::Register<A::Network>>], ) -> Self {
32 let function_id = compute_function_id(network_id, program_id, function_name);
34
35 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 console::ValueType::Constant(..) => {
45 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
47 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 match &output {
56 Value::Plaintext(..) => OutputID::constant(A::hash_psd8(&preimage)),
58 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 console::ValueType::Public(..) => {
71 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
73 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 match &output {
82 Value::Plaintext(..) => OutputID::public(A::hash_psd8(&preimage)),
84 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 console::ValueType::Private(..) => {
97 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
99 let output_view_key = A::hash_psd4(&[function_id.clone(), tvk.clone(), output_index]);
101 let ciphertext = match &output {
103 Value::Plaintext(plaintext) => plaintext.encrypt_symmetric(output_view_key),
104 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 OutputID::private(A::hash_psd8(&ciphertext.to_fields()))
116 }
117 console::ValueType::Record(record_name) => {
119 let record = match &output {
121 Value::Record(record) => record,
122 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 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 let output_index = Field::constant(console::Field::from_u64(output_register.locator()));
141 let randomizer = A::hash_to_scalar_psd2(&[tvk.clone(), output_index]);
143
144 let (encrypted_record, record_view_key) = record.encrypt_symmetric(&randomizer);
146
147 let commitment =
149 record.to_commitment(program_id, &Identifier::constant(*record_name), &record_view_key);
150
151 let checksum = A::hash_bhp1024(&encrypted_record.to_bits_le());
153
154 let randomizer = A::hash_psd4(&[A::encryption_domain(), record_view_key, Field::one()]);
156 let sender_ciphertext = signer.to_group().to_x_coordinate() + randomizer;
158
159 OutputID::record(commitment, checksum, sender_ciphertext)
161 }
162 console::ValueType::ExternalRecord(..) => {
164 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
166 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 match &output {
175 Value::Record(..) => OutputID::external_record(A::hash_psd8(&preimage)),
176 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 console::ValueType::Future(..) => {
189 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
191 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 match &output {
200 Value::Future(..) => OutputID::future(A::hash_psd8(&preimage)),
202 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 console::ValueType::DynamicRecord => {
215 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
217 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 match &output {
226 Value::DynamicRecord(..) => OutputID::dynamic_record(A::hash_psd8(&preimage)),
227 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 console::ValueType::DynamicFuture => {
240 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
242 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 match &output {
251 Value::DynamicFuture(..) => OutputID::dynamic_future(A::hash_psd8(&preimage)),
253 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 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 let tvk = console::Field::rand(rng);
299 let tcm = <Circuit as Environment>::Network::hash_psd2(&[tvk])?;
301
302 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 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 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 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 let signer = console::Address::rand(rng);
356 let network_id = console::U16::new(<Circuit as Environment>::Network::ID);
358 let program_id = console::ProgramID::from_str(program_id)?;
360 let function_name = console::Identifier::from_str(function_name)?;
362
363 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 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 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 #[test]
425 #[rustfmt::skip]
426 fn test_from_outputs_constant() -> Result<()> {
427 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 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 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 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 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 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 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 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 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 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 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 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}