Skip to main content

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}