pessimistic_proof_core/local_state/
commitment.rs

1//! State related commitments involved in the pessimistic proof.
2//!
3//! The pessimistic proof has the "pessimistic root" as part of its public
4//! inputs. Some logic in this file handles the migration on its computation.
5use agglayer_primitives::{keccak::keccak256_combine, Digest};
6use agglayer_tries::roots::{LocalBalanceRoot, LocalExitRoot, LocalNullifierRoot};
7use alloy_primitives::B256;
8use serde::{Deserialize, Serialize};
9use unified_bridge::{
10    ImportedBridgeExitCommitmentValues, ImportedBridgeExitCommitmentVersion, NetworkId,
11};
12
13use crate::{
14    aggchain_data::AggchainHashValues,
15    proof::{ConstrainedValues, EMPTY_PP_ROOT_V2},
16    ProofError,
17};
18
19/// The pessimistic root is a public value of the PP which is settled in the L1.
20#[derive(Debug, Clone, Copy)]
21pub enum PessimisticRootCommitmentVersion {
22    /// Legacy PP root commitment.
23    V2,
24    /// Add the height and the origin network.
25    V3,
26}
27
28/// The state commitment of one [`super::NetworkState`].
29#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
30pub struct StateCommitment {
31    pub exit_root: LocalExitRoot,
32    pub ler_leaf_count: u32,
33    pub balance_root: LocalBalanceRoot,
34    pub nullifier_root: LocalNullifierRoot,
35}
36
37/// The parameters which compose the pessimistic root.
38#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
39pub struct PessimisticRootCommitmentValues {
40    pub balance_root: LocalBalanceRoot,
41    pub nullifier_root: LocalNullifierRoot,
42    pub ler_leaf_count: u32,
43    pub height: u64,
44    pub origin_network: NetworkId,
45}
46
47impl PessimisticRootCommitmentValues {
48    /// Infer the version of the provided settled pessimistic root.
49    pub fn infer_settled_pp_root_version(
50        &self,
51        settled_pp_root: Digest,
52    ) -> Result<PessimisticRootCommitmentVersion, ProofError> {
53        let computed_v3 = self.compute_pp_root(PessimisticRootCommitmentVersion::V3);
54        if computed_v3 == settled_pp_root {
55            return Ok(PessimisticRootCommitmentVersion::V3);
56        }
57
58        let computed_v2 = self.compute_pp_root(PessimisticRootCommitmentVersion::V2);
59        if computed_v2 == settled_pp_root {
60            return Ok(PessimisticRootCommitmentVersion::V2);
61        }
62
63        // NOTE: Return v2 to trigger the migration
64        let is_initial_state = computed_v2 == EMPTY_PP_ROOT_V2 && self.height == 0;
65
66        if settled_pp_root.0 == [0u8; 32] && is_initial_state {
67            return Ok(PessimisticRootCommitmentVersion::V2);
68        }
69
70        Err(ProofError::InvalidPreviousPessimisticRoot {
71            declared: settled_pp_root,
72            computed_v2,
73            computed_v3,
74        })
75    }
76
77    /// Compute the pessimistic root for the provided version.
78    pub fn compute_pp_root(&self, version: PessimisticRootCommitmentVersion) -> Digest {
79        match version {
80            PessimisticRootCommitmentVersion::V2 => keccak256_combine([
81                self.balance_root.as_ref(),
82                self.nullifier_root.as_ref(),
83                self.ler_leaf_count.to_le_bytes().as_slice(),
84            ]),
85            PessimisticRootCommitmentVersion::V3 => keccak256_combine([
86                self.balance_root.as_ref(),
87                self.nullifier_root.as_ref(),
88                self.ler_leaf_count.to_le_bytes().as_slice(),
89                self.height.to_le_bytes().as_slice(),
90                self.origin_network.to_le_bytes().as_slice(),
91            ]),
92        }
93    }
94}
95
96#[derive(Debug, Clone, Copy)]
97pub enum SignatureCommitmentVersion {
98    /// Legacy commitment for the signature.
99    V2,
100    /// Add the height.
101    V3,
102    /// Add the aggchain params.
103    V4,
104    /// Add the certificate id.
105    V5,
106}
107
108/// The values which compose the signature.
109pub struct SignatureCommitmentValues {
110    pub new_local_exit_root: LocalExitRoot,
111    pub commit_imported_bridge_exits: ImportedBridgeExitCommitmentValues,
112    pub height: u64,
113    pub aggchain_params: Option<Digest>,
114    pub certificate_id: Digest,
115}
116
117impl SignatureCommitmentValues {
118    pub fn new(constrained_values: &ConstrainedValues, aggchain_params: Option<Digest>) -> Self {
119        Self {
120            new_local_exit_root: constrained_values.final_state_commitment.exit_root,
121            commit_imported_bridge_exits: constrained_values.commit_imported_bridge_exits.clone(),
122            height: constrained_values.height,
123            aggchain_params,
124            certificate_id: constrained_values.certificate_id,
125        }
126    }
127}
128
129impl SignatureCommitmentValues {
130    /// Returns the expected signed commitment for the provided version.
131    #[inline]
132    pub fn commitment(&self, version: SignatureCommitmentVersion) -> B256 {
133        let commitment = match version {
134            SignatureCommitmentVersion::V2 => keccak256_combine([
135                self.new_local_exit_root.as_ref(),
136                self.commit_imported_bridge_exits
137                    .commitment(ImportedBridgeExitCommitmentVersion::V2)
138                    .as_slice(),
139            ]),
140            SignatureCommitmentVersion::V3 => {
141                // Added the height to avoid replay attack edge cases
142                keccak256_combine([
143                    self.new_local_exit_root.as_ref(),
144                    self.commit_imported_bridge_exits
145                        .commitment(ImportedBridgeExitCommitmentVersion::V3)
146                        .as_slice(),
147                    self.height.to_le_bytes().as_slice(),
148                ])
149            }
150            SignatureCommitmentVersion::V4 => {
151                // Added the aggchain params to support the aggchain proof
152                // NOTE: this commitment isn't used anywhere anymore.
153                // Keeping it to understand the history.
154                keccak256_combine([
155                    self.new_local_exit_root.as_ref(),
156                    self.commit_imported_bridge_exits
157                        .commitment(ImportedBridgeExitCommitmentVersion::V3)
158                        .as_slice(),
159                    self.height.to_le_bytes().as_slice(),
160                    self.aggchain_params
161                        .unwrap_or(AggchainHashValues::EMPTY_AGGCHAIN_PARAMS)
162                        .as_slice(),
163                ])
164            }
165            SignatureCommitmentVersion::V5 => {
166                // Added the certificate id to cover edge cases coming with the multisig
167                keccak256_combine([
168                    self.new_local_exit_root.as_ref(),
169                    self.commit_imported_bridge_exits
170                        .commitment(ImportedBridgeExitCommitmentVersion::V3)
171                        .as_slice(),
172                    self.height.to_le_bytes().as_slice(),
173                    self.aggchain_params
174                        .unwrap_or(AggchainHashValues::EMPTY_AGGCHAIN_PARAMS)
175                        .as_slice(),
176                    self.certificate_id.as_slice(),
177                ])
178            }
179        };
180
181        B256::new(commitment.0)
182    }
183
184    #[inline]
185    pub fn multisig_commitment(&self) -> B256 {
186        self.commitment(SignatureCommitmentVersion::V5)
187    }
188}