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
422#[cfg(test)]
423pub(crate) mod test_helpers {
424 use super::*;
425 use console::{network::MainnetV0, program::Literal};
426
427 type CurrentNetwork = MainnetV0;
428
429 pub(crate) fn sample_outputs() -> Vec<(<CurrentNetwork as Network>::TransitionID, Output<CurrentNetwork>)> {
431 let rng = &mut TestRng::default();
432
433 let transaction = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0);
435 let transition = transaction.transitions().next().unwrap();
436
437 let transition_id = *transition.id();
439 let input = transition.outputs().iter().next().unwrap().clone();
440
441 let plaintext = Plaintext::Literal(Literal::Field(Uniform::rand(rng)), Default::default());
443 let plaintext_hash = CurrentNetwork::hash_bhp1024(&plaintext.to_bits_le()).unwrap();
444 let fields: Vec<_> = (0..10).map(|_| Uniform::rand(rng)).collect();
446 let ciphertext = Ciphertext::from_fields(&fields).unwrap();
447 let ciphertext_hash = CurrentNetwork::hash_bhp1024(&ciphertext.to_bits_le()).unwrap();
448 let randomizer = Uniform::rand(rng);
450 let nonce = CurrentNetwork::g_scalar_multiply(&randomizer);
451 let record = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::from_str(
452 &format!("{{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, token_amount: 100u64.private, _nonce: {nonce}.public }}"),
453 ).unwrap();
454 let record_ciphertext = record.encrypt(randomizer).unwrap();
455 let record_checksum = CurrentNetwork::hash_bhp1024(&record_ciphertext.to_bits_le()).unwrap();
456 let sender_ciphertext = match record_ciphertext.version().is_zero() {
458 true => None,
459 false => Some(Uniform::rand(rng)),
460 };
461
462 vec![
463 (transition_id, input),
464 (Uniform::rand(rng), Output::Constant(Uniform::rand(rng), None)),
465 (Uniform::rand(rng), Output::Constant(plaintext_hash, Some(plaintext.clone()))),
466 (Uniform::rand(rng), Output::Public(Uniform::rand(rng), None)),
467 (Uniform::rand(rng), Output::Public(plaintext_hash, Some(plaintext))),
468 (Uniform::rand(rng), Output::Private(Uniform::rand(rng), None)),
469 (Uniform::rand(rng), Output::Private(ciphertext_hash, Some(ciphertext))),
470 (Uniform::rand(rng), Output::Record(Uniform::rand(rng), Uniform::rand(rng), None, sender_ciphertext)),
471 (
472 Uniform::rand(rng),
473 Output::Record(Uniform::rand(rng), record_checksum, Some(record_ciphertext.clone()), sender_ciphertext),
474 ),
475 (Uniform::rand(rng), Output::ExternalRecord(Uniform::rand(rng))),
476 (
477 Uniform::rand(rng),
478 Output::RecordWithDynamicID(
479 Uniform::rand(rng),
480 record_checksum,
481 Some(record_ciphertext),
482 sender_ciphertext,
483 Uniform::rand(rng),
484 ),
485 ),
486 (
487 Uniform::rand(rng),
488 Output::RecordWithDynamicID(
489 Uniform::rand(rng),
490 Uniform::rand(rng),
491 None,
492 sender_ciphertext,
493 Uniform::rand(rng),
494 ),
495 ),
496 (Uniform::rand(rng), Output::ExternalRecordWithDynamicID(Uniform::rand(rng), Uniform::rand(rng))),
497 (Uniform::rand(rng), Output::DynamicRecord(Uniform::rand(rng))),
498 ]
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use console::network::MainnetV0;
506
507 type CurrentNetwork = MainnetV0;
508
509 #[test]
510 fn test_to_caller_output_record_with_dynamic_id() {
511 let commitment = Field::<CurrentNetwork>::from_u64(1);
513 let checksum = Field::<CurrentNetwork>::from_u64(2);
514 let dynamic_id = Field::<CurrentNetwork>::from_u64(3);
515
516 let output = Output::<CurrentNetwork>::RecordWithDynamicID(commitment, checksum, None, None, dynamic_id);
517 let caller_output = output.to_caller_output();
518
519 assert_eq!(caller_output, Output::<CurrentNetwork>::DynamicRecord(dynamic_id));
520 }
521
522 #[test]
523 fn test_to_caller_output_external_record_with_dynamic_id() {
524 let ext_id = Field::<CurrentNetwork>::from_u64(10);
526 let dynamic_id = Field::<CurrentNetwork>::from_u64(20);
527
528 let output = Output::<CurrentNetwork>::ExternalRecordWithDynamicID(ext_id, dynamic_id);
529 let caller_output = output.to_caller_output();
530
531 assert_eq!(caller_output, Output::<CurrentNetwork>::DynamicRecord(dynamic_id));
532 }
533
534 #[test]
535 fn test_to_caller_output_non_dynamic_variants_unchanged() {
536 let id = Field::<CurrentNetwork>::from_u64(42);
538
539 let constant = Output::<CurrentNetwork>::Constant(id, None);
540 assert_eq!(constant.to_caller_output(), constant);
541
542 let dynamic_record = Output::<CurrentNetwork>::DynamicRecord(id);
543 assert_eq!(dynamic_record.to_caller_output(), dynamic_record);
544
545 let external = Output::<CurrentNetwork>::ExternalRecord(id);
546 assert_eq!(external.to_caller_output(), external);
547 }
548}