1mod bytes;
17mod serialize;
18mod string;
19
20use console::{
21 account::{Address, ViewKey},
22 network::prelude::*,
23 program::{Ciphertext, Future, Plaintext, Record, TransitionLeaf},
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}
45
46impl<N: Network> Output<N> {
47 pub const fn variant(&self) -> Variant {
49 match self {
50 Output::Constant(_, _) => 0,
51 Output::Public(_, _) => 1,
52 Output::Private(_, _) => 2,
53 Output::Record(_, _, _, _) => 3,
54 Output::ExternalRecord(_) => 4,
55 Output::Future(_, _) => 5,
56 }
57 }
58
59 pub const fn id(&self) -> &Field<N> {
61 match self {
62 Output::Constant(id, ..) => id,
63 Output::Public(id, ..) => id,
64 Output::Private(id, ..) => id,
65 Output::Record(commitment, ..) => commitment,
66 Output::ExternalRecord(id) => id,
67 Output::Future(id, ..) => id,
68 }
69 }
70
71 pub fn to_transition_leaf(&self, index: u8) -> TransitionLeaf<N> {
73 TransitionLeaf::new_with_version(index, self.variant(), *self.id())
74 }
75
76 #[allow(clippy::type_complexity)]
78 pub const fn record(&self) -> Option<(&Field<N>, &Record<N, Ciphertext<N>>)> {
79 match self {
80 Output::Record(commitment, _, Some(record), _) => Some((commitment, record)),
81 _ => None,
82 }
83 }
84
85 #[allow(clippy::type_complexity)]
87 pub fn into_record(self) -> Option<(Field<N>, Record<N, Ciphertext<N>>)> {
88 match self {
89 Output::Record(commitment, _, Some(record), _) => Some((commitment, record)),
90 _ => None,
91 }
92 }
93
94 pub const fn commitment(&self) -> Option<&Field<N>> {
96 match self {
97 Output::Record(commitment, ..) => Some(commitment),
98 _ => None,
99 }
100 }
101
102 pub fn into_commitment(self) -> Option<Field<N>> {
104 match self {
105 Output::Record(commitment, ..) => Some(commitment),
106 _ => None,
107 }
108 }
109
110 pub const fn nonce(&self) -> Option<&Group<N>> {
112 match self {
113 Output::Record(_, _, Some(record), _) => Some(record.nonce()),
114 _ => None,
115 }
116 }
117
118 pub fn into_nonce(self) -> Option<Group<N>> {
120 match self {
121 Output::Record(_, _, Some(record), _) => Some(record.into_nonce()),
122 _ => None,
123 }
124 }
125
126 pub const fn checksum(&self) -> Option<&Field<N>> {
128 match self {
129 Output::Record(_, checksum, ..) => Some(checksum),
130 _ => None,
131 }
132 }
133
134 pub fn into_checksum(self) -> Option<Field<N>> {
136 match self {
137 Output::Record(_, checksum, ..) => Some(checksum),
138 _ => None,
139 }
140 }
141
142 pub const fn sender_ciphertext(&self) -> Option<&Field<N>> {
144 match self {
145 Output::Record(_, _, _, Some(sender_ciphertext)) => Some(sender_ciphertext),
146 _ => None,
147 }
148 }
149
150 pub fn into_sender_ciphertext(self) -> Option<Field<N>> {
152 match self {
153 Output::Record(_, _, _, Some(sender_ciphertext)) => Some(sender_ciphertext),
154 _ => None,
155 }
156 }
157
158 pub const fn future(&self) -> Option<&Future<N>> {
160 match self {
161 Output::Future(_, Some(future)) => Some(future),
162 _ => None,
163 }
164 }
165}
166
167impl<N: Network> Output<N> {
168 pub fn decrypt_sender_ciphertext(&self, account_view_key: &ViewKey<N>) -> Result<Option<Address<N>>> {
174 let (record_ciphertext, sender_ciphertext) = match self {
176 Output::Record(_, _, Some(record_ciphertext), Some(sender_ciphertext)) => {
177 (record_ciphertext, sender_ciphertext)
178 }
179 _ => return Ok(None),
181 };
182
183 let record_view_key = (*record_ciphertext.nonce() * **account_view_key).to_x_coordinate();
185 let expected_owner = match record_ciphertext.owner().is_public() {
187 true => record_ciphertext.owner().decrypt_with_randomizer(&[])?,
188 false => {
189 let randomizers = N::hash_many_psd8(&[N::encryption_domain(), record_view_key], 1);
191 ensure!(randomizers.len() == 1, "Expected exactly one randomizer for the record owner");
192 record_ciphertext.owner().decrypt_with_randomizer(&[randomizers[0]])?
194 }
195 };
196 ensure!(
198 *expected_owner == account_view_key.to_address(),
199 "The record does not belong to the given account view key"
200 );
201
202 let Ok(randomizer) = N::hash_psd4(&[N::encryption_domain(), record_view_key, Field::one()]) else {
204 bail!("Failed to compute the encryption randomizer for the sender ciphertext");
205 };
206 let sender_x_coordinate = *sender_ciphertext - randomizer;
208 match Address::from_field(&sender_x_coordinate) {
210 Ok(sender_address) => Ok(Some(sender_address)),
211 Err(error) => bail!("Failed to recover the sender address - {error}"),
212 }
213 }
214}
215
216impl<N: Network> Output<N> {
217 pub fn verifier_inputs(&self) -> impl '_ + Iterator<Item = N::Field> {
219 [**self.id()].into_iter()
221 .chain([self.checksum().map(|sum| **sum), self.sender_ciphertext().map(|sender| **sender)].into_iter().flatten())
223 }
224
225 pub fn verify(&self, function_id: Field<N>, tcm: &Field<N>, index: usize) -> bool {
228 let result = || match self {
230 Output::Constant(hash, Some(output)) => {
231 match output.to_fields() {
232 Ok(fields) => {
233 let index = Field::from_u16(index as u16);
235 let mut preimage = Vec::new();
237 preimage.push(function_id);
238 preimage.extend(fields);
239 preimage.push(*tcm);
240 preimage.push(index);
241 match N::hash_psd8(&preimage) {
243 Ok(candidate_hash) => Ok(hash == &candidate_hash),
244 Err(error) => Err(error),
245 }
246 }
247 Err(error) => Err(error),
248 }
249 }
250 Output::Public(hash, Some(output)) => {
251 match output.to_fields() {
252 Ok(fields) => {
253 let index = Field::from_u16(index as u16);
255 let mut preimage = Vec::new();
257 preimage.push(function_id);
258 preimage.extend(fields);
259 preimage.push(*tcm);
260 preimage.push(index);
261 match N::hash_psd8(&preimage) {
263 Ok(candidate_hash) => Ok(hash == &candidate_hash),
264 Err(error) => Err(error),
265 }
266 }
267 Err(error) => Err(error),
268 }
269 }
270 Output::Private(hash, Some(value)) => {
271 match value.to_fields() {
272 Ok(fields) => match N::hash_psd8(&fields) {
274 Ok(candidate_hash) => Ok(hash == &candidate_hash),
275 Err(error) => Err(error),
276 },
277 Err(error) => Err(error),
278 }
279 }
280 Output::Record(_, checksum, Some(record_ciphertext), sender_ciphertext) => {
281 let mut preimage = record_ciphertext.to_bits_le();
283 if **record_ciphertext.version() == 0 {
286 ensure!(sender_ciphertext.is_none(), "The sender ciphertext must be None for Version 0 records");
287 preimage.truncate(preimage.len().saturating_sub(8));
289 } else if **record_ciphertext.version() == 1 {
290 ensure!(sender_ciphertext.is_some(), "The sender ciphertext must be non-empty");
291 ensure!(sender_ciphertext.unwrap() != Field::zero(), "The sender ciphertext must be non-zero");
293 } else {
294 bail!(
295 "The record version must be set to Version 0 or 1, but found Version {}",
296 **record_ciphertext.version()
297 );
298 }
299
300 match N::hash_bhp1024(&preimage) {
302 Ok(candidate_hash) => Ok(checksum == &candidate_hash),
303 Err(error) => Err(error),
304 }
305 }
306 Output::Future(hash, Some(output)) => {
307 match output.to_fields() {
308 Ok(fields) => {
309 let index = Field::from_u16(index as u16);
311 let mut preimage = Vec::new();
313 preimage.push(function_id);
314 preimage.extend(fields);
315 preimage.push(*tcm);
316 preimage.push(index);
317 match N::hash_psd8(&preimage) {
319 Ok(candidate_hash) => Ok(hash == &candidate_hash),
320 Err(error) => Err(error),
321 }
322 }
323 Err(error) => Err(error),
324 }
325 }
326 Output::Constant(_, None)
327 | Output::Public(_, None)
328 | Output::Private(_, None)
329 | Output::Record(_, _, None, _)
330 | Output::Future(_, None) => {
331 bail!("A transition output value is missing")
334 }
335 Output::ExternalRecord(_) => Ok(true),
336 };
337
338 match result() {
339 Ok(is_hash_valid) => is_hash_valid,
340 Err(error) => {
341 eprintln!("{error}");
342 false
343 }
344 }
345 }
346}
347
348#[cfg(test)]
349pub(crate) mod test_helpers {
350 use super::*;
351 use console::{network::MainnetV0, program::Literal};
352
353 type CurrentNetwork = MainnetV0;
354
355 pub(crate) fn sample_outputs() -> Vec<(<CurrentNetwork as Network>::TransitionID, Output<CurrentNetwork>)> {
357 let rng = &mut TestRng::default();
358
359 let transaction = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0);
361 let transition = transaction.transitions().next().unwrap();
362
363 let transition_id = *transition.id();
365 let input = transition.outputs().iter().next().unwrap().clone();
366
367 let plaintext = Plaintext::Literal(Literal::Field(Uniform::rand(rng)), Default::default());
369 let plaintext_hash = CurrentNetwork::hash_bhp1024(&plaintext.to_bits_le()).unwrap();
370 let fields: Vec<_> = (0..10).map(|_| Uniform::rand(rng)).collect();
372 let ciphertext = Ciphertext::from_fields(&fields).unwrap();
373 let ciphertext_hash = CurrentNetwork::hash_bhp1024(&ciphertext.to_bits_le()).unwrap();
374 let randomizer = Uniform::rand(rng);
376 let nonce = CurrentNetwork::g_scalar_multiply(&randomizer);
377 let record = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::from_str(
378 &format!("{{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, token_amount: 100u64.private, _nonce: {nonce}.public }}"),
379 ).unwrap();
380 let record_ciphertext = record.encrypt(randomizer).unwrap();
381 let record_checksum = CurrentNetwork::hash_bhp1024(&record_ciphertext.to_bits_le()).unwrap();
382 let sender_ciphertext = match record_ciphertext.version().is_zero() {
384 true => None,
385 false => Some(Uniform::rand(rng)),
386 };
387
388 vec![
389 (transition_id, input),
390 (Uniform::rand(rng), Output::Constant(Uniform::rand(rng), None)),
391 (Uniform::rand(rng), Output::Constant(plaintext_hash, Some(plaintext.clone()))),
392 (Uniform::rand(rng), Output::Public(Uniform::rand(rng), None)),
393 (Uniform::rand(rng), Output::Public(plaintext_hash, Some(plaintext))),
394 (Uniform::rand(rng), Output::Private(Uniform::rand(rng), None)),
395 (Uniform::rand(rng), Output::Private(ciphertext_hash, Some(ciphertext))),
396 (Uniform::rand(rng), Output::Record(Uniform::rand(rng), Uniform::rand(rng), None, sender_ciphertext)),
397 (
398 Uniform::rand(rng),
399 Output::Record(Uniform::rand(rng), record_checksum, Some(record_ciphertext), sender_ciphertext),
400 ),
401 (Uniform::rand(rng), Output::ExternalRecord(Uniform::rand(rng))),
402 ]
403 }
404}