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), 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 candidate_cm = record.to_commitment(&program_id, record_name)?;
210 ensure!(*commitment == candidate_cm, "The output record commitment is incorrect");
212
213 let index = Field::from_u64(output_register.locator());
215 let randomizer = N::hash_to_scalar_psd2(&[*request.tvk(), index])?;
217
218 let record_ciphertext = record.encrypt(randomizer)?;
220 let ciphertext_checksum = N::hash_bhp1024(&record_ciphertext.to_bits_le())?;
222 ensure!(*checksum == ciphertext_checksum, "The output record ciphertext checksum is incorrect");
224
225 Ok(Output::Record(*commitment, *checksum, Some(record_ciphertext)))
227 }
228 (OutputID::ExternalRecord(hash), Value::Record(record)) => {
229 let index = Field::from_u16(u16::try_from(num_inputs + index)?);
231 let mut preimage = Vec::new();
233 preimage.push(function_id);
234 preimage.extend(record.to_fields()?);
235 preimage.push(*request.tvk());
236 preimage.push(index);
237 let candidate_hash = N::hash_psd8(&preimage)?;
239 ensure!(*hash == candidate_hash, "The output external hash is incorrect");
241 Ok(Output::ExternalRecord(*hash))
243 }
244 (OutputID::Future(output_hash), Value::Future(future)) => {
245 let output = Output::Future(*output_hash, Some(future.clone()));
247 match output.verify(function_id, request.tcm(), num_inputs + index) {
249 true => Ok(output),
250 false => bail!("Malformed future transition output: '{output}'"),
251 }
252 }
253 _ => bail!("Malformed response output: {output_id:?}, {output}"),
254 }
255 })
256 .collect::<Result<Vec<_>>>()?;
257
258 let tpk = request.to_tpk();
260 let tcm = *request.tcm();
262 let scm = *request.scm();
264 Self::new(program_id, function_name, inputs, outputs, tpk, tcm, scm)
266 }
267}
268
269impl<N: Network> Transition<N> {
270 pub const fn id(&self) -> &N::TransitionID {
272 &self.id
273 }
274
275 pub const fn program_id(&self) -> &ProgramID<N> {
277 &self.program_id
278 }
279
280 pub const fn function_name(&self) -> &Identifier<N> {
282 &self.function_name
283 }
284
285 pub fn inputs(&self) -> &[Input<N>] {
287 &self.inputs
288 }
289
290 pub fn outputs(&self) -> &[Output<N>] {
292 &self.outputs
293 }
294
295 pub const fn tpk(&self) -> &Group<N> {
297 &self.tpk
298 }
299
300 pub const fn tcm(&self) -> &Field<N> {
302 &self.tcm
303 }
304
305 pub const fn scm(&self) -> &Field<N> {
307 &self.scm
308 }
309}
310
311impl<N: Network> Transition<N> {
312 #[inline]
314 pub fn is_bond_public(&self) -> bool {
315 self.inputs.len() == 3
316 && self.outputs.len() == 1
317 && self.program_id.to_string() == "credits.aleo"
318 && self.function_name.to_string() == "bond_public"
319 }
320
321 #[inline]
323 pub fn is_bond_validator(&self) -> bool {
324 self.inputs.len() == 3
325 && self.outputs.len() == 1
326 && self.program_id.to_string() == "credits.aleo"
327 && self.function_name.to_string() == "bond_validator"
328 }
329
330 #[inline]
332 pub fn is_unbond_public(&self) -> bool {
333 self.inputs.len() == 2
334 && self.outputs.len() == 1
335 && self.program_id.to_string() == "credits.aleo"
336 && self.function_name.to_string() == "unbond_public"
337 }
338
339 #[inline]
341 pub fn is_fee_private(&self) -> bool {
342 self.inputs.len() == 4
343 && self.outputs.len() == 1
344 && self.program_id.to_string() == "credits.aleo"
345 && self.function_name.to_string() == "fee_private"
346 }
347
348 #[inline]
350 pub fn is_fee_public(&self) -> bool {
351 self.inputs.len() == 3
352 && self.outputs.len() == 1
353 && self.program_id.to_string() == "credits.aleo"
354 && self.function_name.to_string() == "fee_public"
355 }
356
357 #[inline]
359 pub fn is_split(&self) -> bool {
360 self.inputs.len() == 2
361 && self.outputs.len() == 2
362 && self.program_id.to_string() == "credits.aleo"
363 && self.function_name.to_string() == "split"
364 }
365}
366
367impl<N: Network> Transition<N> {
368 pub fn contains_serial_number(&self, serial_number: &Field<N>) -> bool {
370 self.inputs.iter().any(|input| match input {
371 Input::Constant(_, _) => false,
372 Input::Public(_, _) => false,
373 Input::Private(_, _) => false,
374 Input::Record(input_sn, _) => input_sn == serial_number,
375 Input::ExternalRecord(_) => false,
376 })
377 }
378
379 pub fn contains_commitment(&self, commitment: &Field<N>) -> bool {
381 self.outputs.iter().any(|output| match output {
382 Output::Constant(_, _) => false,
383 Output::Public(_, _) => false,
384 Output::Private(_, _) => false,
385 Output::Record(output_cm, _, _) => output_cm == commitment,
386 Output::ExternalRecord(_) => false,
387 Output::Future(_, _) => false,
388 })
389 }
390}
391
392impl<N: Network> Transition<N> {
393 pub fn find_record(&self, commitment: &Field<N>) -> Option<&Record<N, Ciphertext<N>>> {
395 self.outputs.iter().find_map(|output| match output {
396 Output::Constant(_, _) => None,
397 Output::Public(_, _) => None,
398 Output::Private(_, _) => None,
399 Output::Record(output_cm, _, Some(record)) if output_cm == commitment => Some(record),
400 Output::Record(_, _, _) => None,
401 Output::ExternalRecord(_) => None,
402 Output::Future(_, _) => None,
403 })
404 }
405}
406
407impl<N: Network> Transition<N> {
408 pub fn input_ids(&self) -> impl '_ + ExactSizeIterator<Item = &Field<N>> {
412 self.inputs.iter().map(Input::id)
413 }
414
415 pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = &Field<N>> {
417 self.inputs.iter().flat_map(Input::serial_number)
418 }
419
420 pub fn tags(&self) -> impl '_ + Iterator<Item = &Field<N>> {
422 self.inputs.iter().flat_map(Input::tag)
423 }
424
425 pub fn output_ids(&self) -> impl '_ + ExactSizeIterator<Item = &Field<N>> {
429 self.outputs.iter().map(Output::id)
430 }
431
432 pub fn commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
434 self.outputs.iter().flat_map(Output::commitment)
435 }
436
437 pub fn nonces(&self) -> impl '_ + Iterator<Item = &Group<N>> {
439 self.outputs.iter().flat_map(Output::nonce)
440 }
441
442 pub fn records(&self) -> impl '_ + Iterator<Item = (&Field<N>, &Record<N, Ciphertext<N>>)> {
444 self.outputs.iter().flat_map(Output::record)
445 }
446}
447
448impl<N: Network> Transition<N> {
449 pub fn into_id(self) -> N::TransitionID {
451 self.id
452 }
453
454 pub fn into_serial_numbers(self) -> impl Iterator<Item = Field<N>> {
458 self.inputs.into_iter().flat_map(Input::into_serial_number)
459 }
460
461 pub fn into_tags(self) -> impl Iterator<Item = Field<N>> {
463 self.inputs.into_iter().flat_map(Input::into_tag)
464 }
465
466 pub fn into_commitments(self) -> impl Iterator<Item = Field<N>> {
470 self.outputs.into_iter().flat_map(Output::into_commitment)
471 }
472
473 pub fn into_nonces(self) -> impl Iterator<Item = Group<N>> {
475 self.outputs.into_iter().flat_map(Output::into_nonce)
476 }
477
478 pub fn into_records(self) -> impl Iterator<Item = (Field<N>, Record<N, Ciphertext<N>>)> {
480 self.outputs.into_iter().flat_map(Output::into_record)
481 }
482
483 pub fn into_tpk(self) -> Group<N> {
485 self.tpk
486 }
487}
488
489#[cfg(test)]
490pub mod test_helpers {
491 use super::*;
492 use crate::Transaction;
493
494 type CurrentNetwork = console::network::MainnetV0;
495
496 pub(crate) fn sample_transition(rng: &mut TestRng) -> Transition<CurrentNetwork> {
498 if let Transaction::Execute(_, _, execution, _) =
499 crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng)
500 {
501 execution.into_transitions().next().unwrap()
502 } else {
503 unreachable!()
504 }
505 }
506}