snarkvm_ledger_block/transaction/merkle.rs
1// Copyright (c) 2019-2026 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
18impl<N: Network> Transaction<N> {
19 /// The maximum number of transitions allowed in a transaction.
20 pub const MAX_TRANSITIONS: usize = usize::pow(2, TRANSACTION_DEPTH as u32);
21
22 /// Returns the transaction root, by computing the root for a Merkle tree of the transition IDs.
23 pub fn to_root(&self) -> Result<Field<N>> {
24 Ok(*self.to_tree()?.root())
25 }
26
27 /// Returns the Merkle leaf for the given ID of a function or transition in the transaction.
28 pub fn to_leaf(&self, id: &Field<N>) -> Result<TransactionLeaf<N>> {
29 match self {
30 Self::Deploy(_, _, _, deployment, fee) => {
31 // Check if the ID is the transition ID for the fee.
32 if *id == **fee.id() {
33 // Return the transaction leaf.
34 return Ok(TransactionLeaf::new_fee(
35 u16::try_from(deployment.program().functions().len())?, // The last index.
36 *id,
37 ));
38 }
39
40 // Iterate through the functions in the deployment.
41 for (index, function) in deployment.program().functions().values().enumerate() {
42 // Check if the function hash matches the given ID.
43 if *id == N::hash_bhp1024(&function.to_bytes_le()?.to_bits_le())? {
44 // Return the transaction leaf.
45 return Ok(TransactionLeaf::new_deployment(u16::try_from(index)?, *id));
46 }
47 }
48 // Error if the function hash was not found.
49 bail!("Function hash not found in deployment transaction");
50 }
51 Self::Execute(_, _, execution, fee) => {
52 // Check if the ID is the transition ID for the fee.
53 if let Some(fee) = fee {
54 if *id == **fee.id() {
55 // Return the transaction leaf.
56 return Ok(TransactionLeaf::new_execution(
57 u16::try_from(execution.len())?, // The last index.
58 *id,
59 ));
60 }
61 }
62
63 // Iterate through the transitions in the execution.
64 for (index, transition) in execution.transitions().enumerate() {
65 // Check if the transition ID matches the given ID.
66 if *id == **transition.id() {
67 // Return the transaction leaf.
68 return Ok(TransactionLeaf::new_execution(u16::try_from(index)?, *id));
69 }
70 }
71 // Error if the transition ID was not found.
72 bail!("Transition ID not found in execution transaction");
73 }
74 Self::Fee(_, fee) => {
75 if *id == **fee.id() {
76 // Return the transaction leaf.
77 return Ok(TransactionLeaf::new_fee(0, **fee.id()));
78 }
79 // Error if the transition ID was not found.
80 bail!("Transition ID not found in fee transaction");
81 }
82 }
83 }
84
85 /// Returns the Merkle path for the transaction leaf.
86 pub fn to_path(&self, leaf: &TransactionLeaf<N>) -> Result<TransactionPath<N>> {
87 // Compute the Merkle path.
88 self.to_tree()?.prove(leaf.index() as usize, &leaf.to_bits_le())
89 }
90
91 /// The Merkle tree of transition IDs for the transaction.
92 pub fn to_tree(&self) -> Result<TransactionTree<N>> {
93 match self {
94 // Compute the deployment tree.
95 Transaction::Deploy(_, _, _, deployment, fee) => {
96 Self::transaction_tree(Self::deployment_tree(deployment)?, Some(fee))
97 }
98 // Compute the execution tree.
99 Transaction::Execute(_, _, execution, fee) => {
100 Self::transaction_tree(Self::execution_tree(execution)?, fee.as_ref())
101 }
102 // Compute the fee tree.
103 Transaction::Fee(_, fee) => Self::fee_tree(fee),
104 }
105 }
106}
107
108impl<N: Network> Transaction<N> {
109 /// Returns the Merkle tree for the given transaction tree, fee index, and fee.
110 pub fn transaction_tree(
111 mut deployment_or_execution_tree: TransactionTree<N>,
112 fee: Option<&Fee<N>>,
113 ) -> Result<TransactionTree<N>> {
114 // Retrieve the fee index, defined as the last index in the transaction tree.
115 let fee_index = deployment_or_execution_tree.number_of_leaves();
116 // Ensure the fee index is within the Merkle tree size.
117 ensure!(
118 fee_index <= N::MAX_FUNCTIONS,
119 "The fee index ('{fee_index}') in the transaction tree must be at most {}",
120 N::MAX_FUNCTIONS
121 );
122 // Ensure the fee index is within the Merkle tree size.
123 ensure!(
124 fee_index < Self::MAX_TRANSITIONS,
125 "The fee index ('{fee_index}') in the transaction tree must be less than {}",
126 Self::MAX_TRANSITIONS
127 );
128
129 // If a fee is provided, append the fee leaf to the transaction tree.
130 if let Some(fee) = fee {
131 // Construct the transaction leaf.
132 let leaf = TransactionLeaf::new_fee(u16::try_from(fee_index)?, **fee.transition_id()).to_bits_le();
133 // Append the fee leaf to the transaction tree.
134 deployment_or_execution_tree.append(&[leaf])?;
135 }
136 // Return the transaction tree.
137 Ok(deployment_or_execution_tree)
138 }
139
140 /// Returns the Merkle tree for the given deployment.
141 pub fn deployment_tree(deployment: &Deployment<N>) -> Result<DeploymentTree<N>> {
142 // Use the V1 or V2 deployment tree based on implicit deployment version.
143 // Note: `ConsensusVersion::V9` requires the program checksum and program owner to be present, while prior versions require it to be absent.
144 // `Deployment::version` checks that this is the case.
145 // Note: After `ConsensusVersion::V9`, the program checksum and owner are used in the header of the hash instead of the program ID.
146 match deployment.version() {
147 Ok(DeploymentVersion::V1) => Self::deployment_tree_v1(deployment),
148 Ok(DeploymentVersion::V2) => Self::deployment_tree_v2(deployment),
149 // Note: We use the same method for computing the deployment tree for V2 and V3.
150 // This is safe because the tree root contains a hash of all bytes in the deployment and V3 is guaranteed to have no owner.
151 Ok(DeploymentVersion::V3) => Self::deployment_tree_v2(deployment),
152 Err(e) => bail!("Malformed deployment - {e}"),
153 }
154 }
155
156 /// Returns the Merkle tree for the given execution.
157 pub fn execution_tree(execution: &Execution<N>) -> Result<ExecutionTree<N>> {
158 Self::transitions_tree(execution.transitions())
159 }
160
161 /// Returns the Merkle tree for the given transitions.
162 pub fn transitions_tree<'a>(
163 transitions: impl ExactSizeIterator<Item = &'a Transition<N>>,
164 ) -> Result<ExecutionTree<N>> {
165 // Retrieve the number of transitions.
166 let num_transitions = transitions.len();
167 // Ensure the number of leaves is within the Merkle tree size.
168 Self::check_execution_size(num_transitions)?;
169 // Prepare the leaves.
170 let leaves = transitions.enumerate().map(|(index, transition)| {
171 // Construct the transaction leaf.
172 Ok::<_, Error>(TransactionLeaf::new_execution(u16::try_from(index)?, **transition.id()).to_bits_le())
173 });
174 // Compute the execution tree.
175 N::merkle_tree_bhp::<TRANSACTION_DEPTH>(&leaves.collect::<Result<Vec<_>, _>>()?)
176 }
177
178 /// Returns the Merkle tree for the given fee.
179 pub fn fee_tree(fee: &Fee<N>) -> Result<TransactionTree<N>> {
180 // Construct the transaction leaf.
181 let leaf = TransactionLeaf::new_fee(0u16, **fee.transition_id()).to_bits_le();
182 // Compute the execution tree.
183 N::merkle_tree_bhp::<TRANSACTION_DEPTH>(&[leaf])
184 }
185
186 /// Returns `true` if the deployment is within the size bounds.
187 pub fn check_deployment_size(deployment: &Deployment<N>) -> Result<()> {
188 // Retrieve the program.
189 let program = deployment.program();
190 // Retrieve the functions.
191 let functions = program.functions();
192 // Retrieve the function verifying keys.
193 let function_verifying_keys = deployment.function_verifying_keys();
194 // Retrieve the number of functions.
195 let num_functions = functions.len();
196
197 // Ensure the number of functions and function verifying keys match.
198 ensure!(
199 num_functions == function_verifying_keys.len(),
200 "Number of functions ('{num_functions}') and function verifying keys ('{}') do not match",
201 function_verifying_keys.len()
202 );
203 // Ensure there are functions.
204 ensure!(num_functions != 0, "Deployment must contain at least one function");
205 // Ensure the number of functions is within the allowed range.
206 ensure!(
207 num_functions <= N::MAX_FUNCTIONS,
208 "Deployment must contain at most {} functions, found {num_functions}",
209 N::MAX_FUNCTIONS,
210 );
211
212 // If translation verifying keys are present, ensure the count is within bounds.
213 if let Some(translation_verifying_keys) = deployment.translation_verifying_keys() {
214 let num_records = translation_verifying_keys.len();
215 ensure!(
216 num_records <= N::MAX_RECORDS,
217 "Deployment must contain at most {} records, found {num_records}",
218 N::MAX_RECORDS,
219 );
220 }
221
222 // Ensure the number of function verifying keys fits in the transaction tree.
223 // Note: Record verifying keys do not occupy transition leaves.
224 ensure!(
225 num_functions < Self::MAX_TRANSITIONS, // Note: Observe we hold back 1 for the fee.
226 "Deployment must contain less than {} function verifying keys, found {num_functions}",
227 Self::MAX_TRANSITIONS,
228 );
229 Ok(())
230 }
231
232 /// Returns `true` if the execution is within the size bounds.
233 pub fn check_execution_size(num_transitions: usize) -> Result<()> {
234 // Ensure there are transitions.
235 ensure!(num_transitions > 0, "Execution must contain at least one transition");
236 // Ensure the number of functions is within the allowed range.
237 ensure!(
238 num_transitions < Self::MAX_TRANSITIONS, // Note: Observe we hold back 1 for the fee.
239 "Execution must contain less than {} transitions, found {num_transitions}",
240 Self::MAX_TRANSITIONS,
241 );
242 Ok(())
243 }
244}
245
246impl<N: Network> Transaction<N> {
247 /// Returns the V1 deployment tree.
248 pub fn deployment_tree_v1(deployment: &Deployment<N>) -> Result<DeploymentTree<N>> {
249 // Ensure the number of leaves is within the Merkle tree size.
250 Self::check_deployment_size(deployment)?;
251 // Prepare the header for the hash.
252 let header = deployment.program().id().to_bits_le();
253 // Prepare the leaves.
254 let leaves = deployment.program().functions().values().enumerate().map(|(index, function)| {
255 // Construct the transaction leaf.
256 Ok(TransactionLeaf::new_deployment(
257 u16::try_from(index)?,
258 N::hash_bhp1024(&to_bits_le![header, function.to_bytes_le()?])?,
259 )
260 .to_bits_le())
261 });
262 // Compute the deployment tree.
263 N::merkle_tree_bhp::<TRANSACTION_DEPTH>(&leaves.collect::<Result<Vec<_>>>()?)
264 }
265
266 /// Returns the V2 deployment tree.
267 pub fn deployment_tree_v2(deployment: &Deployment<N>) -> Result<DeploymentTree<N>> {
268 // Ensure the number of leaves is within the Merkle tree size.
269 Self::check_deployment_size(deployment)?;
270 // Compute a hash of the deployment bytes.
271 let deployment_hash = N::hash_sha3_256(&to_bits_le!(deployment.to_bytes_le()?))?;
272 // Prepare the header for the hash.
273 let header = to_bits_le![deployment.version()? as u8, deployment_hash];
274 // Prepare the leaves.
275 let leaves = deployment.program().functions().values().enumerate().map(|(index, function)| {
276 // Construct the transaction leaf.
277 Ok(TransactionLeaf::new_deployment(
278 u16::try_from(index)?,
279 N::hash_bhp1024(&to_bits_le![header, function.to_bytes_le()?])?,
280 )
281 .to_bits_le())
282 });
283 // Compute the deployment tree.
284 N::merkle_tree_bhp::<TRANSACTION_DEPTH>(&leaves.collect::<Result<Vec<_>>>()?)
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 type CurrentNetwork = console::network::MainnetV0;
293
294 #[test]
295 fn test_transaction_depth_is_correct() {
296 // We ensure 2^TRANSACTION_DEPTH == MAX_FUNCTIONS + 1.
297 // The "1 extra" is for the fee transition.
298 assert_eq!(
299 2u32.checked_pow(TRANSACTION_DEPTH as u32).unwrap() as usize,
300 Transaction::<CurrentNetwork>::MAX_TRANSITIONS
301 );
302 assert_eq!(
303 CurrentNetwork::MAX_FUNCTIONS.checked_add(1).unwrap(),
304 Transaction::<CurrentNetwork>::MAX_TRANSITIONS
305 );
306 }
307}