1use super::*;
17
18impl<A: Aleo> Response<A> {
19 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>>, output_types: &[console::ValueType<A::Network>], output_registers: &[Option<console::Register<A::Network>>], function_id: Option<Field<A>>,
31 ) -> Vec<Value<A>> {
32 let function_id = match function_id {
34 Some(function_id) => function_id,
35 None => compute_function_id(network_id, program_id, function_name),
36 };
37
38 if outputs.len() != output_types.len() || output_types.len() != output_registers.len() {
39 let msg = format!(
40 "Mismatch in the number of outputs, output types, and output registers: {} vs {} vs {}",
41 outputs.len(),
42 output_types.len(),
43 output_registers.len()
44 );
45 return A::halt(msg);
46 }
47
48 match outputs
49 .iter()
50 .zip_eq(output_types)
51 .zip_eq(output_registers)
52 .enumerate()
53 .map(|(index, ((output, output_types), output_register))| {
54 match output_types {
55 console::ValueType::Constant(..) => {
57 let output = Value::new(Mode::Constant, output.clone());
59 ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
61
62 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
64 let mut preimage = Vec::new();
66 preimage.push(function_id.clone());
67 preimage.extend(output.to_fields());
68 preimage.push(tcm.clone());
69 preimage.push(output_index);
70
71 match &output {
73 Value::Plaintext(..) => Ok((OutputID::constant(A::hash_psd8(&preimage)), output)),
75 Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
77 Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
78 Value::DynamicRecord(..) => {
79 A::halt("Expected a plaintext output, found a dynamic record output")
80 }
81 Value::DynamicFuture(..) => {
82 A::halt("Expected a plaintext output, found a dynamic future output")
83 }
84 }
85 }
86 console::ValueType::Public(..) => {
88 let output = Value::new(Mode::Private, output.clone());
90 ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
92
93 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
95 let mut preimage = Vec::new();
97 preimage.push(function_id.clone());
98 preimage.extend(output.to_fields());
99 preimage.push(tcm.clone());
100 preimage.push(output_index);
101
102 match &output {
104 Value::Plaintext(..) => Ok((OutputID::public(A::hash_psd8(&preimage)), output)),
106 Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
108 Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
109 Value::DynamicRecord(..) => {
110 A::halt("Expected a plaintext output, found a dynamic record output")
111 }
112 Value::DynamicFuture(..) => {
113 A::halt("Expected a plaintext output, found a dynamic future output")
114 }
115 }
116 }
117 console::ValueType::Private(..) => {
119 let output = Value::new(Mode::Private, output.clone());
121 ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
123
124 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
126 let output_view_key = A::hash_psd4(&[function_id.clone(), tvk.clone(), output_index]);
128 let ciphertext = match &output {
130 Value::Plaintext(plaintext) => plaintext.encrypt_symmetric(output_view_key),
131 Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
133 Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
134 Value::DynamicRecord(..) => {
135 A::halt("Expected a plaintext output, found a dynamic record output")
136 }
137 Value::DynamicFuture(..) => {
138 A::halt("Expected a plaintext output, found a dynamic future output")
139 }
140 };
141 Ok((OutputID::private(A::hash_psd8(&ciphertext.to_fields())), output))
143 }
144 console::ValueType::Record(record_name) => {
146 let output = Value::new(Mode::Private, output.clone());
148
149 let record = match &output {
151 Value::Record(record) => record,
152 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 Value::DynamicRecord(..) => {
156 A::halt("Expected a record output, found a dynamic record output")
157 }
158 Value::DynamicFuture(..) => {
159 A::halt("Expected a record output, found a dynamic future output")
160 }
161 };
162
163 let output_register = match output_register {
165 Some(output_register) => output_register,
166 None => A::halt("Expected a register to be paired with a record output"),
167 };
168
169 let output_index = Field::constant(console::Field::from_u64(output_register.locator()));
171
172 let randomizer = A::hash_to_scalar_psd2(&[tvk.clone(), output_index]);
174
175 let record_view_key = ((*record.owner()).to_group() * randomizer).to_x_coordinate();
177
178 let commitment =
180 record.to_commitment(program_id, &Identifier::constant(*record_name), &record_view_key);
181
182 Ok((OutputID::external_record(commitment), output))
185 }
186 console::ValueType::ExternalRecord(..) => {
188 let output = Value::new(Mode::Private, output.clone());
190 ensure!(matches!(output, Value::Record(..)), "Expected a record output");
192
193 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
195 let mut preimage = Vec::new();
197 preimage.push(function_id.clone());
198 preimage.extend(output.to_fields());
199 preimage.push(tvk.clone());
200 preimage.push(output_index);
201
202 match &output {
204 Value::Record(..) => Ok((OutputID::external_record(A::hash_psd8(&preimage)), output)),
205 Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
207 Value::Future(..) => A::halt("Expected a record output, found a future output"),
208 Value::DynamicRecord(..) => {
209 A::halt("Expected a record output, found a dynamic record output")
210 }
211 Value::DynamicFuture(..) => {
212 A::halt("Expected a record output, found a dynamic future output")
213 }
214 }
215 }
216 console::ValueType::Future(..) => {
218 let output = Value::new(Mode::Private, output.clone());
220 ensure!(matches!(output, Value::Future(..)), "Expected a future output");
222
223 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
225 let mut preimage = Vec::new();
227 preimage.push(function_id.clone());
228 preimage.extend(output.to_fields());
229 preimage.push(tcm.clone());
230 preimage.push(output_index);
231
232 match &output {
234 Value::Future(..) => Ok((OutputID::future(A::hash_psd8(&preimage)), output)),
236 Value::Plaintext(..) => A::halt("Expected a future output, found a plaintext output"),
238 Value::Record(..) => A::halt("Expected a future output, found a record output"),
239 Value::DynamicRecord(..) => {
240 A::halt("Expected a future output, found a dynamic record output")
241 }
242 Value::DynamicFuture(..) => {
243 A::halt("Expected a future output, found a dynamic future output")
244 }
245 }
246 }
247 console::ValueType::DynamicRecord => {
249 let output = Value::new(Mode::Private, output.clone());
251 ensure!(matches!(output, Value::DynamicRecord(..)), "Expected a dynamic record output");
253
254 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
256 let mut preimage = Vec::new();
258 preimage.push(function_id.clone());
259 preimage.extend(output.to_fields());
260 preimage.push(tvk.clone());
261 preimage.push(output_index);
262
263 match &output {
265 Value::DynamicRecord(..) => Ok((OutputID::dynamic_record(A::hash_psd8(&preimage)), output)),
266 Value::Plaintext(..) => {
268 A::halt("Expected a dynamic record output, found a plaintext output")
269 }
270 Value::Future(..) => A::halt("Expected a dynamic record output, found a future output"),
271 Value::Record(..) => A::halt("Expected a dynamic record output, found a record output"),
272 Value::DynamicFuture(..) => {
273 A::halt("Expected a dynamic record output, found a dynamic future output")
274 }
275 }
276 }
277 console::ValueType::DynamicFuture => {
279 let output = Value::new(Mode::Private, output.clone());
281 ensure!(matches!(output, Value::DynamicFuture(..)), "Expected a dynamic future output");
283
284 let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
286 let mut preimage = Vec::new();
288 preimage.push(function_id.clone());
289 preimage.extend(output.to_fields());
290 preimage.push(tcm.clone());
291 preimage.push(output_index);
292
293 match &output {
295 Value::DynamicFuture(..) => Ok((OutputID::dynamic_future(A::hash_psd8(&preimage)), output)),
297 Value::Plaintext(..) => {
299 A::halt("Expected a dynamic future output, found a plaintext output")
300 }
301 Value::Record(..) => A::halt("Expected a dynamic future output, found a record output"),
302 Value::DynamicRecord(..) => {
303 A::halt("Expected a dynamic future output, found a dynamic record output")
304 }
305 Value::Future(..) => A::halt("Expected a dynamic future output, found a future output"),
306 }
307 }
308 }
309 })
310 .collect::<Result<Vec<_>>>()
311 {
312 Ok(outputs) => {
313 let (_, outputs): (Vec<OutputID<A>>, _) = outputs.into_iter().unzip();
315 outputs
317 }
318 Err(error) => A::halt(error.to_string()),
319 }
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use crate::Circuit;
327 use snarkvm_circuit_types::environment::UpdatableCount;
328 use snarkvm_utilities::{TestRng, Uniform};
329
330 use anyhow::Result;
331
332 pub(crate) const ITERATIONS: usize = 10;
333
334 fn check_from_callback(
335 mode: Mode,
336 program_id: &str,
337 function_name: &str,
338 is_dynamic: bool,
339 use_record: bool,
340 expected_count: UpdatableCount,
341 ) -> Result<()> {
342 use console::Network;
343
344 let rng = &mut TestRng::default();
345
346 for i in 0..ITERATIONS {
347 let tvk = console::Field::rand(rng);
349 let tcm = <Circuit as Environment>::Network::hash_psd2(&[tvk])?;
351
352 let index = console::Field::from_u64(8);
354 let randomizer = <Circuit as Environment>::Network::hash_to_scalar_psd2(&[tvk, index]).unwrap();
355 let nonce = <Circuit as Environment>::Network::g_scalar_multiply(&randomizer);
356
357 let output_constant = console::Value::<<Circuit as Environment>::Network>::Plaintext(
359 console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
360 );
361 let output_public = console::Value::<<Circuit as Environment>::Network>::Plaintext(
362 console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
363 );
364 let output_private = console::Value::<<Circuit as Environment>::Network>::Plaintext(
365 console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
366 );
367 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());
368 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());
369 let outputs = if use_record {
370 vec![output_constant, output_public, output_private, output_record, output_external_record]
371 } else {
372 vec![output_constant, output_public, output_private, output_external_record]
373 };
374
375 let output_types = if use_record {
377 vec![
378 console::ValueType::from_str("amount.constant").unwrap(),
379 console::ValueType::from_str("amount.public").unwrap(),
380 console::ValueType::from_str("amount.private").unwrap(),
381 console::ValueType::from_str("token.record").unwrap(),
382 console::ValueType::from_str("token.aleo/token.record").unwrap(),
383 ]
384 } else {
385 vec![
386 console::ValueType::from_str("amount.constant").unwrap(),
387 console::ValueType::from_str("amount.public").unwrap(),
388 console::ValueType::from_str("amount.private").unwrap(),
389 console::ValueType::from_str("token.aleo/token.record").unwrap(),
390 ]
391 };
392
393 let mut output_registers = vec![
395 Some(console::Register::Locator(5)),
396 Some(console::Register::Locator(6)),
397 Some(console::Register::Locator(7)),
398 Some(console::Register::Locator(8)),
399 ];
400 if use_record {
401 output_registers.push(Some(console::Register::Locator(9)));
402 }
403
404 let signer = console::Address::rand(rng);
406 let network_id = console::U16::new(<Circuit as Environment>::Network::ID);
408 let program_id = console::ProgramID::from_str(program_id)?;
410 let function_name = console::Identifier::from_str(function_name)?;
412
413 let response = console::Response::new(
415 &signer,
416 &network_id,
417 &program_id,
418 &function_name,
419 4,
420 &tvk,
421 &tcm,
422 outputs.clone(),
423 &output_types,
424 &output_registers,
425 )?;
426
427 let signer = Address::<Circuit>::new(mode, signer);
429 let network_id = U16::<Circuit>::constant(network_id);
430 let program_id = match is_dynamic {
431 false => ProgramID::<Circuit>::constant(program_id),
432 true => ProgramID::<Circuit>::public(program_id),
433 };
434 let function_name = match is_dynamic {
435 false => Identifier::<Circuit>::constant(function_name),
436 true => Identifier::<Circuit>::public(function_name),
437 };
438 let tvk = Field::<Circuit>::new(mode, tvk);
439 let tcm = Field::<Circuit>::new(mode, tcm);
440 let function_id =
441 if is_dynamic { Some(compute_function_id(&network_id, &program_id, &function_name)) } else { None };
442
443 Circuit::scope(format!("Response {i}"), || {
444 let outputs = Response::process_outputs_from_callback(
445 &network_id,
446 &program_id,
447 &function_name,
448 4,
449 &tvk,
450 &tcm,
451 response.outputs().to_vec(),
452 &output_types,
453 &output_registers,
454 function_id,
455 );
456 assert_eq!(response.outputs(), outputs.eject_value());
457 expected_count.assert_matches(
458 Circuit::num_constants_in_scope(),
459 Circuit::num_public_in_scope(),
460 Circuit::num_private_in_scope(),
461 Circuit::num_constraints_in_scope(),
462 );
463 });
464
465 let outputs = Inject::new(mode, response.outputs().to_vec());
467 let candidate_b = Response::from_outputs(
468 &signer,
469 &network_id,
470 &program_id,
471 &function_name,
472 4,
473 &tvk,
474 &tcm,
475 outputs,
476 &output_types,
477 &output_registers,
478 );
479 assert_eq!(response, candidate_b.eject_value());
480
481 Circuit::reset();
482 }
483 Ok(())
484 }
485
486 #[test]
491 #[rustfmt::skip]
492 fn test_from_callback_constant() -> Result<()> {
493 check_from_callback(Mode::Constant, "test.aleo", "foo", false, false, count_less_than!(19917, 4, 2813, 2819))?;
495 check_from_callback(Mode::Constant, "credits.aleo", "transfer_public", false, false, count_less_than!(1531, 4, 2813, 2819))?;
496
497 check_from_callback(Mode::Constant, "test.aleo", "foo", false, true, count_less_than!(16062, 5, 10910, 10923))?;
499 check_from_callback(Mode::Constant, "credits.aleo", "transfer_public", false, true, count_less_than!(4303, 5, 11012, 11025))?;
500
501
502 check_from_callback(Mode::Constant, "test.aleo", "foo", true, false, count_less_than!(1233, 4, 6937, 6949))?;
504 check_from_callback(Mode::Constant, "credits.aleo", "transfer_public", true, false, count_less_than!(1233, 4, 6937, 6949))?;
505
506 check_from_callback(Mode::Constant, "test.aleo", "foo", true, true, count_less_than!(3975, 5, 16167, 16190))?;
508 check_from_callback(Mode::Constant, "credits.aleo", "transfer_public", true, true, count_less_than!(3843, 5, 16339, 16362))?;
509
510 Ok(())
511 }
512
513 #[test]
514 #[rustfmt::skip]
515 fn test_from_callback_public() -> Result<()> {
516 check_from_callback(Mode::Public, "test.aleo", "foo", false, false, count_is!(<=19917, 4, 4108, 4114))?;
518 check_from_callback(Mode::Public, "credits.aleo", "transfer_public", false, false, count_is!(1529, 4, 4108, 4114))?;
519
520 check_from_callback(Mode::Public, "test.aleo", "foo", false, true, count_is!(<=15809, 5, 13475, 13490))?;
522 check_from_callback(Mode::Public, "credits.aleo", "transfer_public", false, true, count_is!(4046, 5, 13577, 13592))?;
523
524 check_from_callback(Mode::Public, "test.aleo", "foo", true, false, count_is!(762, 4, 4128, 4134))?;
526 check_from_callback(Mode::Public, "credits.aleo", "transfer_public", true, false, count_is!(762, 4, 4128, 4134))?;
527
528 check_from_callback(Mode::Public, "test.aleo", "foo", true, true, count_is!(3251, 5, 13618, 13633))?;
530 check_from_callback(Mode::Public, "credits.aleo", "transfer_public", true, true, count_is!(3119, 5, 13790, 13805))?;
531
532 Ok(())
533 }
534
535 #[test]
536 #[rustfmt::skip]
537 fn test_from_callback_private() -> Result<()> {
538 check_from_callback(Mode::Private, "test.aleo", "foo", false, false, count_is!(<=19917, 4, 4108, 4114))?;
540 check_from_callback(Mode::Private, "credits.aleo", "transfer_public", false, false, count_is!(1529, 4, 4108, 4114))?;
541
542 check_from_callback(Mode::Private, "test.aleo", "foo", false, true, count_is!(<=15809, 5, 13475, 13490))?;
544 check_from_callback(Mode::Private, "credits.aleo", "transfer_public", false, true, count_is!(4046, 5, 13577, 13592))?;
545
546 check_from_callback(Mode::Private, "test.aleo", "foo", true, false, count_is!(762, 4, 4128, 4134))?;
548 check_from_callback(Mode::Private, "credits.aleo", "transfer_public", true, false, count_is!(762, 4, 4128, 4134))?;
549
550 check_from_callback(Mode::Private, "test.aleo", "foo", true, true, count_is!(3251, 5, 13618, 13633))?;
552 check_from_callback(Mode::Private, "credits.aleo", "transfer_public", true, true, count_is!(3119, 5, 13790, 13805))?;
553
554 Ok(())
555 }
556}