snarkvm_synthesizer_process/trace/inclusion/assignment.rs
1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18#[derive(Clone, Debug)]
19pub struct InclusionAssignment<N: Network> {
20 pub(crate) state_path: StatePath<N>,
21 commitment: Field<N>,
22 gamma: Group<N>,
23 serial_number: Field<N>,
24 is_record_block_height_reached: bool,
25 upgrade_block_height: u32,
26 local_state_root: N::TransactionID,
27 is_global: bool,
28}
29
30impl<N: Network> InclusionAssignment<N> {
31 /// Initializes a new inclusion assignment.
32 pub fn new(
33 state_path: StatePath<N>,
34 commitment: Field<N>,
35 gamma: Group<N>,
36 serial_number: Field<N>,
37 is_record_block_height_reached: bool,
38 upgrade_block_height: u32,
39 local_state_root: N::TransactionID,
40 is_global: bool,
41 ) -> Self {
42 Self {
43 state_path,
44 commitment,
45 gamma,
46 serial_number,
47 is_record_block_height_reached,
48 upgrade_block_height,
49 local_state_root,
50 is_global,
51 }
52 }
53
54 /// The circuit for state path verification.
55 ///
56 /// # Diagram
57 /// The `[[ ]]` notation is used to denote public inputs.
58 /// ```ignore
59 /// [[ global_state_root ]] || [[ local_state_root ]] [[ is_record_block_height_reached ]]
60 /// | | |
61 /// -------- is_global -------- |
62 /// | |
63 /// state_path ------- record_block_height ------ compare ------ [[ upgrade_block_height ]]
64 /// | |
65 /// | |
66 /// | is_global ------ check
67 /// |
68 /// [[ serial_number ]] := Commit( commitment || Hash( COFACTOR * gamma ) )
69 /// ```
70 pub fn to_circuit_assignment<A: circuit::Aleo<Network = N>>(&self) -> Result<circuit::Assignment<N::Field>> {
71 use circuit::Inject;
72
73 // Ensure the circuit environment is clean.
74 assert_eq!(A::count(), (0, 1, 0, 0, (0, 0, 0)));
75 A::reset();
76
77 // Inject the state path as `Mode::Private` (with a global state root as `Mode::Public`).
78 let state_path = circuit::StatePath::<A>::new(circuit::Mode::Private, self.state_path.clone());
79 // Inject the commitment as `Mode::Private`.
80 let commitment = circuit::Field::<A>::new(circuit::Mode::Private, self.commitment);
81 // Inject the gamma as `Mode::Private`.
82 let gamma = circuit::Group::<A>::new(circuit::Mode::Private, self.gamma);
83
84 // Inject the local state root as `Mode::Public`.
85 let local_state_root = circuit::Field::<A>::new(circuit::Mode::Public, *self.local_state_root);
86 // Inject the 'is_global' flag as `Mode::Private`.
87 let is_global = circuit::Boolean::<A>::new(circuit::Mode::Private, self.is_global);
88
89 // Inject the serial number as `Mode::Public`.
90 let serial_number = circuit::Field::<A>::new(circuit::Mode::Public, self.serial_number);
91
92 // Inject the `is_record_block_height_reached` flag as `Mode::Public`.
93 let is_record_block_height_reached =
94 circuit::Boolean::<A>::new(circuit::Mode::Public, self.is_record_block_height_reached);
95 // Inject the `upgrade_block_height` as `Mode::Public`.
96 // This is cast into a u32 to prevent requiring 32 field elements as input.
97 let upgrade_block_height_field = circuit::Field::<A>::new(
98 circuit::Mode::Public,
99 console::types::Field::<N>::from_u32(self.upgrade_block_height),
100 );
101 let upgrade_block_height = circuit::U32::from_field(upgrade_block_height_field);
102
103 // Compute the candidate serial number.
104 let candidate_serial_number =
105 circuit::Record::<A, circuit::Plaintext<A>>::serial_number_from_gamma(&gamma, commitment.clone());
106 // Enforce that the candidate serial number is equal to the serial number.
107 A::assert_eq(candidate_serial_number, serial_number);
108
109 // Enforce the starting leaf is the claimed commitment.
110 A::assert_eq(state_path.transition_leaf().id(), commitment);
111 // Enforce the state path from leaf to root is correct.
112 A::assert(state_path.verify(&is_global, &local_state_root));
113
114 // Fetch the record block height from the state path.
115 let record_block_height = U32::<A>::from_bits_le(&state_path.block_path().leaf_index().to_bits_le());
116 // Determine if the record block height is at or past the upgrade height.
117 let is_record_block_height_past_upgrade_block_height =
118 record_block_height.is_greater_than_or_equal(&upgrade_block_height);
119 // Determine if the record block height is correctly evaluated.
120 let is_block_height_check_valid =
121 is_record_block_height_reached.is_equal(&is_record_block_height_past_upgrade_block_height);
122 // Check that the height is valid if the record is from a global state path.
123 A::assert(is_global.not().bitor(is_block_height_check_valid));
124
125 Stack::log_circuit::<A>(format_args!("State Path for {}", self.serial_number));
126
127 // Eject the assignment and reset the circuit environment.
128 Ok(A::eject_assignment_and_reset())
129 }
130}