1pub mod input;
17pub use input::Input;
18
19pub mod output;
20pub use output::Output;
21
22mod bytes;
23mod merkle;
24mod serialize;
25mod string;
26
27use console::{
28 network::prelude::*,
29 program::{
30 Ciphertext,
31 Identifier,
32 InputID,
33 OutputID,
34 ProgramID,
35 Record,
36 Register,
37 Request,
38 Response,
39 TRANSITION_DEPTH,
40 TransitionLeaf,
41 TransitionPath,
42 TransitionTree,
43 Value,
44 ValueType,
45 compute_function_id,
46 },
47 types::{Field, Group},
48};
49
50#[derive(Clone, PartialEq, Eq)]
51pub struct Transition<N: Network> {
52 id: N::TransitionID,
54 program_id: ProgramID<N>,
56 function_name: Identifier<N>,
58 inputs: Vec<Input<N>>,
60 outputs: Vec<Output<N>>,
62 tpk: Group<N>,
64 tcm: Field<N>,
66 scm: Field<N>,
68}
69
70impl<N: Network> Transition<N> {
71 #[allow(clippy::too_many_arguments)]
73 pub fn new(
74 program_id: ProgramID<N>,
75 function_name: Identifier<N>,
76 inputs: Vec<Input<N>>,
77 outputs: Vec<Output<N>>,
78 tpk: Group<N>,
79 tcm: Field<N>,
80 scm: Field<N>,
81 ) -> Result<Self> {
82 let function_tree = Self::function_tree(&inputs, &outputs)?;
84 let id = N::hash_bhp512(&(*function_tree.root(), tcm).to_bits_le())?;
85 Ok(Self { id: id.into(), program_id, function_name, inputs, outputs, tpk, tcm, scm })
87 }
88
89 pub fn from(
91 request: &Request<N>,
92 response: &Response<N>,
93 output_types: &[ValueType<N>],
94 output_registers: &[Option<Register<N>>],
95 ) -> Result<Self> {
96 let network_id = *request.network_id();
97 let program_id = *request.program_id();
98 let function_name = *request.function_name();
99 let num_inputs = request.inputs().len();
100
101 let function_id = compute_function_id(&network_id, &program_id, &function_name)?;
103
104 let inputs = request
105 .input_ids()
106 .iter()
107 .zip_eq(request.inputs())
108 .enumerate()
109 .map(|(index, (input_id, input))| {
110 match (input_id, input) {
112 (InputID::Constant(input_hash), Value::Plaintext(plaintext)) => {
113 let input = Input::Constant(*input_hash, Some(plaintext.clone()));
115 match input.verify(function_id, request.tcm(), index) {
117 true => Ok(input),
118 false => bail!("Malformed constant transition input: '{input}'"),
119 }
120 }
121 (InputID::Public(input_hash), Value::Plaintext(plaintext)) => {
122 let input = Input::Public(*input_hash, Some(plaintext.clone()));
124 match input.verify(function_id, request.tcm(), index) {
126 true => Ok(input),
127 false => bail!("Malformed public transition input: '{input}'"),
128 }
129 }
130 (InputID::Private(input_hash), Value::Plaintext(plaintext)) => {
131 let index = Field::from_u16(index as u16);
133 let ciphertext =
135 plaintext.encrypt_symmetric(N::hash_psd4(&[function_id, *request.tvk(), index])?)?;
136 let ciphertext_hash = N::hash_psd8(&ciphertext.to_fields()?)?;
138 ensure!(*input_hash == ciphertext_hash, "The input ciphertext hash is incorrect");
140 Ok(Input::Private(*input_hash, Some(ciphertext)))
142 }
143 (InputID::Record(_, _, _, serial_number, tag), Value::Record(..)) => {
144 Ok(Input::Record(*serial_number, *tag))
146 }
147 (InputID::ExternalRecord(input_hash), Value::Record(..)) => Ok(Input::ExternalRecord(*input_hash)),
148 _ => bail!("Malformed request input: {:?}, {input}", input_id),
149 }
150 })
151 .collect::<Result<Vec<_>>>()?;
152
153 let outputs = response
154 .output_ids()
155 .iter()
156 .zip_eq(response.outputs())
157 .zip_eq(output_types)
158 .zip_eq(output_registers)
159 .enumerate()
160 .map(|(index, (((output_id, output), output_type), output_register))| {
161 match (output_id, output) {
163 (OutputID::Constant(output_hash), Value::Plaintext(plaintext)) => {
164 let output = Output::Constant(*output_hash, Some(plaintext.clone()));
166 match output.verify(function_id, request.tcm(), num_inputs + index) {
168 true => Ok(output),
169 false => bail!("Malformed constant transition output: '{output}'"),
170 }
171 }
172 (OutputID::Public(output_hash), Value::Plaintext(plaintext)) => {
173 let output = Output::Public(*output_hash, Some(plaintext.clone()));
175 match output.verify(function_id, request.tcm(), num_inputs + index) {
177 true => Ok(output),
178 false => bail!("Malformed public transition output: '{output}'"),
179 }
180 }
181 (OutputID::Private(output_hash), Value::Plaintext(plaintext)) => {
182 let index = Field::from_u16(u16::try_from(num_inputs + index)?);
184 let ciphertext =
186 plaintext.encrypt_symmetric(N::hash_psd4(&[function_id, *request.tvk(), index])?)?;
187 let ciphertext_hash = N::hash_psd8(&ciphertext.to_fields()?)?;
189 ensure!(*output_hash == ciphertext_hash, "The output ciphertext hash is incorrect");
191 Ok(Output::Private(*output_hash, Some(ciphertext)))
193 }
194 (OutputID::Record(commitment, checksum, sender_ciphertext), Value::Record(record)) => {
195 let record_name = match output_type {
197 ValueType::Record(record_name) => record_name,
198 _ => bail!("Expected a record type at output {index}"),
200 };
201
202 let output_register = match output_register {
204 Some(output_register) => output_register,
205 None => bail!("Expected a register to be paired with a record output"),
206 };
207
208 let index = Field::from_u64(output_register.locator());
210 let randomizer = N::hash_to_scalar_psd2(&[*request.tvk(), index])?;
212
213 let (record_ciphertext, record_view_key) = record.encrypt_symmetric(randomizer)?;
215
216 let candidate_cm = record.to_commitment(&program_id, record_name, &record_view_key)?;
218 ensure!(*commitment == candidate_cm, "The output record commitment is incorrect");
220
221 let ciphertext_checksum = N::hash_bhp1024(&record_ciphertext.to_bits_le())?;
223 ensure!(*checksum == ciphertext_checksum, "The output record ciphertext checksum is incorrect");
225
226 let randomizer = N::hash_psd4(&[N::encryption_domain(), record_view_key, Field::one()])?;
228 let candidate_sender_ciphertext = (**request.signer()).to_x_coordinate() + randomizer;
230 ensure!(
234 (*sender_ciphertext == candidate_sender_ciphertext) || sender_ciphertext.is_zero(),
235 "The output record sender ciphertext is incorrect"
236 );
237
238 Ok(Output::Record(*commitment, *checksum, Some(record_ciphertext), Some(*sender_ciphertext)))
240 }
241 (OutputID::ExternalRecord(hash), Value::Record(record)) => {
242 let index = Field::from_u16(u16::try_from(num_inputs + index)?);
244 let mut preimage = Vec::new();
246 preimage.push(function_id);
247 preimage.extend(record.to_fields()?);
248 preimage.push(*request.tvk());
249 preimage.push(index);
250 let candidate_hash = N::hash_psd8(&preimage)?;
252 ensure!(*hash == candidate_hash, "The output external hash is incorrect");
254 Ok(Output::ExternalRecord(*hash))
256 }
257 (OutputID::Future(output_hash), Value::Future(future)) => {
258 let output = Output::Future(*output_hash, Some(future.clone()));
260 match output.verify(function_id, request.tcm(), num_inputs + index) {
262 true => Ok(output),
263 false => bail!("Malformed future transition output: '{output}'"),
264 }
265 }
266 _ => bail!("Malformed response output: {output_id:?}, {output}"),
267 }
268 })
269 .collect::<Result<Vec<_>>>()?;
270
271 let tpk = request.to_tpk();
273 let tcm = *request.tcm();
275 let scm = *request.scm();
277 Self::new(program_id, function_name, inputs, outputs, tpk, tcm, scm)
279 }
280}
281
282impl<N: Network> Transition<N> {
283 pub const fn id(&self) -> &N::TransitionID {
285 &self.id
286 }
287
288 pub const fn program_id(&self) -> &ProgramID<N> {
290 &self.program_id
291 }
292
293 pub const fn function_name(&self) -> &Identifier<N> {
295 &self.function_name
296 }
297
298 pub fn inputs(&self) -> &[Input<N>] {
300 &self.inputs
301 }
302
303 pub fn outputs(&self) -> &[Output<N>] {
305 &self.outputs
306 }
307
308 pub const fn tpk(&self) -> &Group<N> {
310 &self.tpk
311 }
312
313 pub const fn tcm(&self) -> &Field<N> {
315 &self.tcm
316 }
317
318 pub const fn scm(&self) -> &Field<N> {
320 &self.scm
321 }
322}
323
324impl<N: Network> Transition<N> {
325 #[inline]
327 pub fn is_credits(&self) -> bool {
328 self.program_id.to_string() == "credits.aleo"
329 }
330
331 #[inline]
333 pub fn is_bond_public(&self) -> bool {
334 self.inputs.len() == 3
335 && self.outputs.len() == 1
336 && self.program_id.to_string() == "credits.aleo"
337 && self.function_name.to_string() == "bond_public"
338 }
339
340 #[inline]
342 pub fn is_bond_validator(&self) -> bool {
343 self.inputs.len() == 3
344 && self.outputs.len() == 1
345 && self.program_id.to_string() == "credits.aleo"
346 && self.function_name.to_string() == "bond_validator"
347 }
348
349 #[inline]
351 pub fn is_unbond_public(&self) -> bool {
352 self.inputs.len() == 2
353 && self.outputs.len() == 1
354 && self.program_id.to_string() == "credits.aleo"
355 && self.function_name.to_string() == "unbond_public"
356 }
357
358 #[inline]
360 pub fn is_fee_private(&self) -> bool {
361 self.inputs.len() == 4
362 && self.outputs.len() == 1
363 && self.program_id.to_string() == "credits.aleo"
364 && self.function_name.to_string() == "fee_private"
365 }
366
367 #[inline]
369 pub fn is_fee_public(&self) -> bool {
370 self.inputs.len() == 3
371 && self.outputs.len() == 1
372 && self.program_id.to_string() == "credits.aleo"
373 && self.function_name.to_string() == "fee_public"
374 }
375
376 #[inline]
378 pub fn is_split(&self) -> bool {
379 self.inputs.len() == 2
380 && self.outputs.len() == 2
381 && self.program_id.to_string() == "credits.aleo"
382 && self.function_name.to_string() == "split"
383 }
384
385 #[inline]
387 pub fn is_upgrade(&self) -> bool {
388 self.inputs.len() == 1
389 && self.outputs.len() == 2
390 && self.program_id.to_string() == "credits.aleo"
391 && self.function_name.to_string() == "upgrade"
392 }
393}
394
395impl<N: Network> Transition<N> {
396 pub fn contains_serial_number(&self, serial_number: &Field<N>) -> bool {
398 self.inputs.iter().any(|input| match input {
399 Input::Constant(_, _) => false,
400 Input::Public(_, _) => false,
401 Input::Private(_, _) => false,
402 Input::Record(input_sn, _) => input_sn == serial_number,
403 Input::ExternalRecord(_) => false,
404 })
405 }
406
407 pub fn contains_commitment(&self, commitment: &Field<N>) -> bool {
409 self.outputs.iter().any(|output| match output {
410 Output::Constant(_, _) => false,
411 Output::Public(_, _) => false,
412 Output::Private(_, _) => false,
413 Output::Record(output_cm, _, _, _) => output_cm == commitment,
414 Output::ExternalRecord(_) => false,
415 Output::Future(_, _) => false,
416 })
417 }
418}
419
420impl<N: Network> Transition<N> {
421 pub fn find_record(&self, commitment: &Field<N>) -> Option<&Record<N, Ciphertext<N>>> {
423 self.outputs.iter().find_map(|output| match output {
424 Output::Constant(_, _) => None,
425 Output::Public(_, _) => None,
426 Output::Private(_, _) => None,
427 Output::Record(output_cm, _, Some(record), _) if output_cm == commitment => Some(record),
428 Output::Record(_, _, _, _) => None,
429 Output::ExternalRecord(_) => None,
430 Output::Future(_, _) => None,
431 })
432 }
433}
434
435impl<N: Network> Transition<N> {
436 pub fn input_ids(&self) -> impl '_ + ExactSizeIterator<Item = &Field<N>> {
440 self.inputs.iter().map(Input::id)
441 }
442
443 pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = &Field<N>> {
445 self.inputs.iter().flat_map(Input::serial_number)
446 }
447
448 pub fn tags(&self) -> impl '_ + Iterator<Item = &Field<N>> {
450 self.inputs.iter().flat_map(Input::tag)
451 }
452
453 pub fn output_ids(&self) -> impl '_ + ExactSizeIterator<Item = &Field<N>> {
457 self.outputs.iter().map(Output::id)
458 }
459
460 pub fn commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
462 self.outputs.iter().flat_map(Output::commitment)
463 }
464
465 pub fn nonces(&self) -> impl '_ + Iterator<Item = &Group<N>> {
467 self.outputs.iter().flat_map(Output::nonce)
468 }
469
470 pub fn records(&self) -> impl '_ + Iterator<Item = (&Field<N>, &Record<N, Ciphertext<N>>)> {
472 self.outputs.iter().flat_map(Output::record)
473 }
474}
475
476impl<N: Network> Transition<N> {
477 pub fn into_id(self) -> N::TransitionID {
479 self.id
480 }
481
482 pub fn into_serial_numbers(self) -> impl Iterator<Item = Field<N>> {
486 self.inputs.into_iter().flat_map(Input::into_serial_number)
487 }
488
489 pub fn into_tags(self) -> impl Iterator<Item = Field<N>> {
491 self.inputs.into_iter().flat_map(Input::into_tag)
492 }
493
494 pub fn into_commitments(self) -> impl Iterator<Item = Field<N>> {
498 self.outputs.into_iter().flat_map(Output::into_commitment)
499 }
500
501 pub fn into_nonces(self) -> impl Iterator<Item = Group<N>> {
503 self.outputs.into_iter().flat_map(Output::into_nonce)
504 }
505
506 pub fn into_records(self) -> impl Iterator<Item = (Field<N>, Record<N, Ciphertext<N>>)> {
508 self.outputs.into_iter().flat_map(Output::into_record)
509 }
510
511 pub fn into_tpk(self) -> Group<N> {
513 self.tpk
514 }
515}
516
517#[cfg(test)]
518pub mod test_helpers {
519 use super::*;
520 use crate::Transaction;
521
522 type CurrentNetwork = console::network::MainnetV0;
523
524 pub(crate) fn sample_transition(rng: &mut TestRng) -> Transition<CurrentNetwork> {
526 if let Transaction::Execute(_, _, execution, _) =
527 crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng)
528 {
529 execution.into_transitions().next().unwrap()
530 } else {
531 unreachable!()
532 }
533 }
534}