1mod bytes;
17mod serialize;
18mod string;
19
20use console::{
21 account::{Address, ViewKey},
22 network::prelude::*,
23 program::{Ciphertext, Future, Plaintext, Record, TransitionLeaf, ValueType},
24 types::{Field, Group},
25};
26
27type Variant = u8;
28
29#[derive(Clone, PartialEq, Eq)]
31pub enum Output<N: Network> {
32 Constant(Field<N>, Option<Plaintext<N>>),
34 Public(Field<N>, Option<Plaintext<N>>),
36 Private(Field<N>, Option<Ciphertext<N>>),
38 Record(Field<N>, Field<N>, Option<Record<N, Ciphertext<N>>>, Option<Field<N>>),
40 ExternalRecord(Field<N>),
42 Future(Field<N>, Option<Future<N>>),
44 DynamicRecord(Field<N>),
46 RecordWithDynamicID(Field<N>, Field<N>, Option<Record<N, Ciphertext<N>>>, Option<Field<N>>, Field<N>),
48 ExternalRecordWithDynamicID(Field<N>, Field<N>),
50}
51
52impl<N: Network> Output<N> {
53 pub const fn variant(&self) -> Variant {
55 match self {
56 Output::Constant(_, _) => 0,
57 Output::Public(_, _) => 1,
58 Output::Private(_, _) => 2,
59 Output::Record(_, _, _, _) => 3,
60 Output::ExternalRecord(_) => 4,
61 Output::Future(_, _) => 5,
62 Output::DynamicRecord(_) => 6,
63 Output::RecordWithDynamicID(..) => 7,
64 Output::ExternalRecordWithDynamicID(..) => 8,
65 }
66 }
67
68 pub const fn id(&self) -> &Field<N> {
70 match self {
71 Output::Constant(id, ..) => id,
72 Output::Public(id, ..) => id,
73 Output::Private(id, ..) => id,
74 Output::Record(commitment, ..) => commitment,
75 Output::ExternalRecord(id) => id,
76 Output::Future(id, ..) => id,
77 Output::DynamicRecord(id) => id,
78 Output::RecordWithDynamicID(commitment, ..) => commitment,
79 Output::ExternalRecordWithDynamicID(id, ..) => id,
80 }
81 }
82
83 pub fn to_transition_leaf(&self, index: u8) -> TransitionLeaf<N> {
87 match self {
88 Output::RecordWithDynamicID(..) => TransitionLeaf::new_record_with_dynamic_id(index, *self.id()),
90 Output::ExternalRecordWithDynamicID(..) => {
92 TransitionLeaf::new_external_record_with_dynamic_id(index, *self.id())
93 }
94 _ => TransitionLeaf::new(index, self.variant(), *self.id()),
96 }
97 }
98
99 #[allow(clippy::type_complexity)]
101 pub const fn record(&self) -> Option<(&Field<N>, &Record<N, Ciphertext<N>>)> {
102 match self {
103 Output::Record(commitment, _, Some(record), _)
104 | Output::RecordWithDynamicID(commitment, _, Some(record), _, _) => Some((commitment, record)),
105 _ => None,
106 }
107 }
108
109 #[allow(clippy::type_complexity)]
111 pub fn into_record(self) -> Option<(Field<N>, Record<N, Ciphertext<N>>)> {
112 match self {
113 Output::Record(commitment, _, Some(record), _)
114 | Output::RecordWithDynamicID(commitment, _, Some(record), _, _) => Some((commitment, record)),
115 _ => None,
116 }
117 }
118
119 pub const fn commitment(&self) -> Option<&Field<N>> {
121 match self {
122 Output::Record(commitment, ..) | Output::RecordWithDynamicID(commitment, ..) => Some(commitment),
123 _ => None,
124 }
125 }
126
127 pub fn into_commitment(self) -> Option<Field<N>> {
129 match self {
130 Output::Record(commitment, ..) | Output::RecordWithDynamicID(commitment, ..) => Some(commitment),
131 _ => None,
132 }
133 }
134
135 pub const fn nonce(&self) -> Option<&Group<N>> {
137 match self {
138 Output::Record(_, _, Some(record), _) | Output::RecordWithDynamicID(_, _, Some(record), _, _) => {
139 Some(record.nonce())
140 }
141 _ => None,
142 }
143 }
144
145 pub fn into_nonce(self) -> Option<Group<N>> {
147 match self {
148 Output::Record(_, _, Some(record), _) | Output::RecordWithDynamicID(_, _, Some(record), _, _) => {
149 Some(record.into_nonce())
150 }
151 _ => None,
152 }
153 }
154
155 pub const fn checksum(&self) -> Option<&Field<N>> {
157 match self {
158 Output::Record(_, checksum, ..) | Output::RecordWithDynamicID(_, checksum, ..) => Some(checksum),
159 _ => None,
160 }
161 }
162
163 pub fn into_checksum(self) -> Option<Field<N>> {
165 match self {
166 Output::Record(_, checksum, ..) | Output::RecordWithDynamicID(_, checksum, ..) => Some(checksum),
167 _ => None,
168 }
169 }
170
171 pub const fn sender_ciphertext(&self) -> Option<&Field<N>> {
173 match self {
174 Output::Record(_, _, _, Some(sender_ciphertext))
175 | Output::RecordWithDynamicID(_, _, _, Some(sender_ciphertext), _) => Some(sender_ciphertext),
176 _ => None,
177 }
178 }
179
180 pub fn into_sender_ciphertext(self) -> Option<Field<N>> {
182 match self {
183 Output::Record(_, _, _, Some(sender_ciphertext))
184 | Output::RecordWithDynamicID(_, _, _, Some(sender_ciphertext), _) => Some(sender_ciphertext),
185 _ => None,
186 }
187 }
188
189 pub const fn future(&self) -> Option<&Future<N>> {
191 match self {
192 Output::Future(_, Some(future)) => Some(future),
193 _ => None,
194 }
195 }
196}
197
198impl<N: Network> Output<N> {
199 pub fn decrypt_sender_ciphertext(&self, account_view_key: &ViewKey<N>) -> Result<Option<Address<N>>> {
205 let (record_ciphertext, sender_ciphertext) = match self {
207 Output::Record(_, _, Some(record_ciphertext), Some(sender_ciphertext))
208 | Output::RecordWithDynamicID(_, _, Some(record_ciphertext), Some(sender_ciphertext), _) => {
209 (record_ciphertext, sender_ciphertext)
210 }
211 _ => return Ok(None),
213 };
214
215 let record_view_key = (*record_ciphertext.nonce() * **account_view_key).to_x_coordinate();
217 let expected_owner = match record_ciphertext.owner().is_public() {
219 true => record_ciphertext.owner().decrypt_with_randomizer(&[])?,
220 false => {
221 let randomizers = N::hash_many_psd8(&[N::encryption_domain(), record_view_key], 1);
223 ensure!(randomizers.len() == 1, "Expected exactly one randomizer for the record owner");
224 record_ciphertext.owner().decrypt_with_randomizer(&[randomizers[0]])?
226 }
227 };
228 ensure!(
230 *expected_owner == account_view_key.to_address(),
231 "The record does not belong to the given account view key"
232 );
233
234 let Ok(randomizer) = N::hash_psd4(&[N::encryption_domain(), record_view_key, Field::one()]) else {
236 bail!("Failed to compute the encryption randomizer for the sender ciphertext");
237 };
238 let sender_x_coordinate = *sender_ciphertext - randomizer;
240 match Address::from_field(&sender_x_coordinate) {
242 Ok(sender_address) => Ok(Some(sender_address)),
243 Err(error) => bail!("Failed to recover the sender address - {error}"),
244 }
245 }
246}
247
248impl<N: Network> Output<N> {
249 pub fn verifier_inputs(&self) -> impl '_ + Iterator<Item = N::Field> {
251 [**self.id()].into_iter()
253 .chain([self.checksum().map(|sum| **sum), self.sender_ciphertext().map(|sender| **sender)].into_iter().flatten())
255 }
256
257 pub const fn dynamic_id(&self) -> Option<&Field<N>> {
259 match self {
260 Output::RecordWithDynamicID(_, _, _, _, dynamic_id)
261 | Output::ExternalRecordWithDynamicID(_, dynamic_id) => Some(dynamic_id),
262 _ => None,
263 }
264 }
265
266 pub fn to_caller_output(&self) -> Self {
268 match self {
269 Self::RecordWithDynamicID(_, _, _, _, dynamic_id) => Self::DynamicRecord(*dynamic_id),
271 Self::ExternalRecordWithDynamicID(_, dynamic_id) => Self::DynamicRecord(*dynamic_id),
273 other => other.clone(),
275 }
276 }
277
278 pub fn verify(&self, function_id: Field<N>, tcm: &Field<N>, index: usize) -> bool {
281 let result = || match self {
283 Output::Constant(hash, Some(output)) => {
284 match output.to_fields() {
285 Ok(fields) => {
286 let index = Field::from_u16(index as u16);
288 let mut preimage = Vec::new();
290 preimage.push(function_id);
291 preimage.extend(fields);
292 preimage.push(*tcm);
293 preimage.push(index);
294 match N::hash_psd8(&preimage) {
296 Ok(candidate_hash) => Ok(hash == &candidate_hash),
297 Err(error) => Err(error),
298 }
299 }
300 Err(error) => Err(error),
301 }
302 }
303 Output::Public(hash, Some(output)) => {
304 match output.to_fields() {
305 Ok(fields) => {
306 let index = Field::from_u16(index as u16);
308 let mut preimage = Vec::new();
310 preimage.push(function_id);
311 preimage.extend(fields);
312 preimage.push(*tcm);
313 preimage.push(index);
314 match N::hash_psd8(&preimage) {
316 Ok(candidate_hash) => Ok(hash == &candidate_hash),
317 Err(error) => Err(error),
318 }
319 }
320 Err(error) => Err(error),
321 }
322 }
323 Output::Private(hash, Some(value)) => {
324 match value.to_fields() {
325 Ok(fields) => match N::hash_psd8(&fields) {
327 Ok(candidate_hash) => Ok(hash == &candidate_hash),
328 Err(error) => Err(error),
329 },
330 Err(error) => Err(error),
331 }
332 }
333 Output::Record(_, checksum, Some(record_ciphertext), sender_ciphertext)
334 | Output::RecordWithDynamicID(_, checksum, Some(record_ciphertext), sender_ciphertext, _) => {
335 let mut preimage = record_ciphertext.to_bits_le();
337 if **record_ciphertext.version() == 0 {
340 ensure!(sender_ciphertext.is_none(), "The sender ciphertext must be None for Version 0 records");
341 preimage.truncate(preimage.len().saturating_sub(8));
343 } else if **record_ciphertext.version() == 1 {
344 ensure!(sender_ciphertext.is_some(), "The sender ciphertext must be non-empty");
345 ensure!(sender_ciphertext.unwrap() != Field::zero(), "The sender ciphertext must be non-zero");
348 } else {
349 bail!(
350 "The record version must be set to Version 0 or 1, but found Version {}",
351 **record_ciphertext.version()
352 );
353 }
354
355 match N::hash_bhp1024(&preimage) {
357 Ok(candidate_hash) => Ok(checksum == &candidate_hash),
358 Err(error) => Err(error),
359 }
360 }
361 Output::Future(hash, Some(output)) => {
362 match output.to_fields() {
363 Ok(fields) => {
364 let index = Field::from_u16(index as u16);
366 let mut preimage = Vec::new();
368 preimage.push(function_id);
369 preimage.extend(fields);
370 preimage.push(*tcm);
371 preimage.push(index);
372 match N::hash_psd8(&preimage) {
374 Ok(candidate_hash) => Ok(hash == &candidate_hash),
375 Err(error) => Err(error),
376 }
377 }
378 Err(error) => Err(error),
379 }
380 }
381 Output::Constant(_, None)
382 | Output::Public(_, None)
383 | Output::Private(_, None)
384 | Output::Record(_, _, None, _)
385 | Output::RecordWithDynamicID(_, _, None, _, _)
386 | Output::Future(_, None) => {
387 bail!("A transition output value is missing")
390 }
391 Output::ExternalRecord(_) => Ok(true),
392 Output::DynamicRecord(_) => Ok(true),
393 Output::ExternalRecordWithDynamicID(_, _) => Ok(true),
394 };
395
396 match result() {
397 Ok(is_hash_valid) => is_hash_valid,
398 Err(error) => {
399 eprintln!("{error}");
400 false
401 }
402 }
403 }
404
405 pub fn is_type(&self, expected_value_type: &ValueType<N>) -> bool {
407 matches!(
408 (self, expected_value_type),
409 (Self::Constant(..), ValueType::Constant(..))
410 | (Self::Public(..), ValueType::Public(..))
411 | (Self::Private(..), ValueType::Private(..))
412 | (Self::Record(..), ValueType::Record(..))
413 | (Self::RecordWithDynamicID(..), ValueType::Record(..))
414 | (Self::ExternalRecord(..), ValueType::ExternalRecord(..))
415 | (Self::ExternalRecordWithDynamicID(..), ValueType::ExternalRecord(..))
416 | (Self::Future(..), ValueType::Future(..))
417 | (Self::DynamicRecord(..), ValueType::DynamicRecord)
418 )
419 }
420
421 pub fn valid_as_output(&self, callee_value_type: &ValueType<N>, is_dynamic: Option<bool>) -> bool {
432 if matches!(
434 (self, callee_value_type),
435 (Self::Constant(..), ValueType::Constant(..))
436 | (Self::Public(..), ValueType::Public(..))
437 | (Self::Private(..), ValueType::Private(..))
438 | (Self::Future(..), ValueType::Future(..))
439 ) {
440 return true;
441 }
442
443 if is_dynamic == Some(true) {
444 matches!(
449 (self, callee_value_type),
450 (Self::RecordWithDynamicID(..), ValueType::Record(..))
452 | (Self::ExternalRecordWithDynamicID(..), ValueType::ExternalRecord(..))
454 | (Self::DynamicRecord(..), ValueType::DynamicRecord)
456 )
457 } else if is_dynamic == Some(false) {
458 matches!(
462 (self, callee_value_type),
463 (Self::Record(..), ValueType::Record(..))
465 | (Self::ExternalRecord(..), ValueType::ExternalRecord(..))
467 | (Self::DynamicRecord(..), ValueType::DynamicRecord)
469 )
470 } else {
471 self.is_type(callee_value_type)
473 }
474 }
475}
476
477#[cfg(test)]
478pub(crate) mod test_helpers {
479 use super::*;
480 use console::{network::MainnetV0, program::Literal};
481
482 type CurrentNetwork = MainnetV0;
483
484 pub(crate) fn sample_outputs() -> Vec<(<CurrentNetwork as Network>::TransitionID, Output<CurrentNetwork>)> {
486 let rng = &mut TestRng::default();
487
488 let transaction = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0);
490 let transition = transaction.transitions().next().unwrap();
491
492 let transition_id = *transition.id();
494 let input = transition.outputs().iter().next().unwrap().clone();
495
496 let plaintext = Plaintext::Literal(Literal::Field(Uniform::rand(rng)), Default::default());
498 let plaintext_hash = CurrentNetwork::hash_bhp1024(&plaintext.to_bits_le()).unwrap();
499 let fields: Vec<_> = (0..10).map(|_| Uniform::rand(rng)).collect();
501 let ciphertext = Ciphertext::from_fields(&fields).unwrap();
502 let ciphertext_hash = CurrentNetwork::hash_bhp1024(&ciphertext.to_bits_le()).unwrap();
503 let randomizer = Uniform::rand(rng);
505 let nonce = CurrentNetwork::g_scalar_multiply(&randomizer);
506 let record = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::from_str(
507 &format!("{{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, token_amount: 100u64.private, _nonce: {nonce}.public }}"),
508 ).unwrap();
509 let record_ciphertext = record.encrypt(randomizer).unwrap();
510 let record_checksum = CurrentNetwork::hash_bhp1024(&record_ciphertext.to_bits_le()).unwrap();
511 let sender_ciphertext = match record_ciphertext.version().is_zero() {
513 true => None,
514 false => Some(Uniform::rand(rng)),
515 };
516
517 vec![
518 (transition_id, input),
519 (Uniform::rand(rng), Output::Constant(Uniform::rand(rng), None)),
520 (Uniform::rand(rng), Output::Constant(plaintext_hash, Some(plaintext.clone()))),
521 (Uniform::rand(rng), Output::Public(Uniform::rand(rng), None)),
522 (Uniform::rand(rng), Output::Public(plaintext_hash, Some(plaintext))),
523 (Uniform::rand(rng), Output::Private(Uniform::rand(rng), None)),
524 (Uniform::rand(rng), Output::Private(ciphertext_hash, Some(ciphertext))),
525 (Uniform::rand(rng), Output::Record(Uniform::rand(rng), Uniform::rand(rng), None, sender_ciphertext)),
526 (
527 Uniform::rand(rng),
528 Output::Record(Uniform::rand(rng), record_checksum, Some(record_ciphertext.clone()), sender_ciphertext),
529 ),
530 (Uniform::rand(rng), Output::ExternalRecord(Uniform::rand(rng))),
531 (
532 Uniform::rand(rng),
533 Output::RecordWithDynamicID(
534 Uniform::rand(rng),
535 record_checksum,
536 Some(record_ciphertext),
537 sender_ciphertext,
538 Uniform::rand(rng),
539 ),
540 ),
541 (
542 Uniform::rand(rng),
543 Output::RecordWithDynamicID(
544 Uniform::rand(rng),
545 Uniform::rand(rng),
546 None,
547 sender_ciphertext,
548 Uniform::rand(rng),
549 ),
550 ),
551 (Uniform::rand(rng), Output::ExternalRecordWithDynamicID(Uniform::rand(rng), Uniform::rand(rng))),
552 (Uniform::rand(rng), Output::DynamicRecord(Uniform::rand(rng))),
553 ]
554 }
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560 use console::network::MainnetV0;
561
562 type CurrentNetwork = MainnetV0;
563
564 #[test]
565 fn test_to_caller_output_record_with_dynamic_id() {
566 let commitment = Field::<CurrentNetwork>::from_u64(1);
568 let checksum = Field::<CurrentNetwork>::from_u64(2);
569 let dynamic_id = Field::<CurrentNetwork>::from_u64(3);
570
571 let output = Output::<CurrentNetwork>::RecordWithDynamicID(commitment, checksum, None, None, dynamic_id);
572 let caller_output = output.to_caller_output();
573
574 assert_eq!(caller_output, Output::<CurrentNetwork>::DynamicRecord(dynamic_id));
575 }
576
577 #[test]
578 fn test_to_caller_output_external_record_with_dynamic_id() {
579 let ext_id = Field::<CurrentNetwork>::from_u64(10);
581 let dynamic_id = Field::<CurrentNetwork>::from_u64(20);
582
583 let output = Output::<CurrentNetwork>::ExternalRecordWithDynamicID(ext_id, dynamic_id);
584 let caller_output = output.to_caller_output();
585
586 assert_eq!(caller_output, Output::<CurrentNetwork>::DynamicRecord(dynamic_id));
587 }
588
589 #[test]
590 fn test_to_caller_output_non_dynamic_variants_unchanged() {
591 let id = Field::<CurrentNetwork>::from_u64(42);
593
594 let constant = Output::<CurrentNetwork>::Constant(id, None);
595 assert_eq!(constant.to_caller_output(), constant);
596
597 let dynamic_record = Output::<CurrentNetwork>::DynamicRecord(id);
598 assert_eq!(dynamic_record.to_caller_output(), dynamic_record);
599
600 let external = Output::<CurrentNetwork>::ExternalRecord(id);
601 assert_eq!(external.to_caller_output(), external);
602 }
603}