Skip to main content

light_client/indexer/types/
proof.rs

1use light_account::PackedAccounts;
2use light_compressed_account::instruction_data::compressed_proof::CompressedProof;
3use light_sdk::instruction::{PackedAddressTreeInfo, PackedStateTreeInfo, ValidityProof};
4use solana_pubkey::Pubkey;
5
6use super::{
7    super::{base58::decode_base58_to_fixed_array, tree_info::QUEUE_TREE_MAPPING, IndexerError},
8    tree::TreeInfo,
9};
10
11/// Convert a Vec<i64> (byte array from JSON) to a fixed-size byte array.
12fn vec_i64_to_fixed_array<const N: usize>(v: &[i64]) -> Result<[u8; N], IndexerError> {
13    if v.len() != N {
14        return Err(IndexerError::decode_error(
15            "proof",
16            format!("expected {} bytes, got {}", N, v.len()),
17        ));
18    }
19    let mut arr = [0u8; N];
20    for (i, &val) in v.iter().enumerate() {
21        arr[i] = val as u8;
22    }
23    Ok(arr)
24}
25
26#[derive(Debug, Clone, PartialEq, Default)]
27pub struct MerkleProofWithContext {
28    pub proof: Vec<[u8; 32]>,
29    pub root: [u8; 32],
30    pub leaf_index: u64,
31    pub leaf: [u8; 32],
32    pub merkle_tree: [u8; 32],
33    pub root_seq: u64,
34    pub tx_hash: Option<[u8; 32]>,
35    pub account_hash: [u8; 32],
36}
37
38#[derive(Debug, Clone, PartialEq, Default)]
39pub struct MerkleProof {
40    pub hash: [u8; 32],
41    pub leaf_index: u64,
42    pub merkle_tree: Pubkey,
43    pub proof: Vec<[u8; 32]>,
44    pub root_seq: u64,
45    pub root: [u8; 32],
46}
47
48#[derive(Debug, Clone, Copy, PartialEq)]
49pub struct AddressWithTree {
50    pub address: super::Address,
51    pub tree: Pubkey,
52}
53
54#[derive(Clone, Default, Debug, PartialEq)]
55pub struct NewAddressProofWithContext {
56    pub merkle_tree: Pubkey,
57    pub root: [u8; 32],
58    pub root_seq: u64,
59    pub low_address_index: u64,
60    pub low_address_value: [u8; 32],
61    pub low_address_next_index: u64,
62    pub low_address_next_value: [u8; 32],
63    pub low_address_proof: Vec<[u8; 32]>,
64    pub new_low_element: Option<light_indexed_merkle_tree::array::IndexedElement<usize>>,
65    pub new_element: Option<light_indexed_merkle_tree::array::IndexedElement<usize>>,
66    pub new_element_next_value: Option<num_bigint::BigUint>,
67}
68
69#[derive(Debug, Default, Clone, PartialEq)]
70pub struct ValidityProofWithContext {
71    pub proof: ValidityProof,
72    pub accounts: Vec<AccountProofInputs>,
73    pub addresses: Vec<AddressProofInputs>,
74}
75
76// TODO: add get_public_inputs
77// -> to make it easier to use light-verifier with get_validity_proof()
78impl ValidityProofWithContext {
79    pub fn get_root_indices(&self) -> Vec<Option<u16>> {
80        self.accounts
81            .iter()
82            .map(|account| account.root_index.root_index())
83            .collect()
84    }
85
86    pub fn get_address_root_indices(&self) -> Vec<u16> {
87        self.addresses
88            .iter()
89            .map(|address| address.root_index)
90            .collect()
91    }
92}
93
94#[derive(Clone, Default, Debug, PartialEq)]
95pub struct AccountProofInputs {
96    pub hash: [u8; 32],
97    pub root: [u8; 32],
98    pub root_index: RootIndex,
99    pub leaf_index: u64,
100    pub tree_info: TreeInfo,
101}
102
103#[derive(Clone, Default, Copy, Debug, PartialEq)]
104pub struct RootIndex {
105    proof_by_index: bool,
106    root_index: u16,
107}
108
109impl RootIndex {
110    pub fn new_none() -> Self {
111        Self {
112            proof_by_index: true,
113            root_index: 0,
114        }
115    }
116
117    pub fn new_some(root_index: u16) -> Self {
118        Self {
119            proof_by_index: false,
120            root_index,
121        }
122    }
123
124    pub fn proof_by_index(&self) -> bool {
125        self.proof_by_index
126    }
127
128    pub fn root_index(&self) -> Option<u16> {
129        if !self.proof_by_index {
130            Some(self.root_index)
131        } else {
132            None
133        }
134    }
135}
136
137impl AccountProofInputs {
138    pub fn from_api_model(
139        value: &photon_api::types::AccountProofInputs,
140    ) -> Result<Self, IndexerError> {
141        let root_index = {
142            if value.root_index.prove_by_index {
143                RootIndex::new_none()
144            } else {
145                RootIndex::new_some(value.root_index.root_index as u16)
146            }
147        };
148        Ok(Self {
149            hash: decode_base58_to_fixed_array(&value.hash)?,
150            root: decode_base58_to_fixed_array(&value.root)?,
151            root_index,
152            leaf_index: value.leaf_index,
153            tree_info: TreeInfo::from_api_model(&value.merkle_context)?,
154        })
155    }
156}
157
158#[derive(Clone, Default, Debug, PartialEq)]
159pub struct AddressProofInputs {
160    pub address: [u8; 32],
161    pub root: [u8; 32],
162    pub root_index: u16,
163    pub tree_info: TreeInfo,
164}
165
166impl AddressProofInputs {
167    pub fn from_api_model(
168        value: &photon_api::types::AddressProofInputs,
169    ) -> Result<Self, IndexerError> {
170        Ok(Self {
171            address: decode_base58_to_fixed_array(&value.address)?,
172            root: decode_base58_to_fixed_array(&value.root)?,
173            root_index: value.root_index,
174            tree_info: TreeInfo::from_api_model(&value.merkle_context)?,
175        })
176    }
177}
178
179#[derive(Clone, Default, Debug, PartialEq)]
180pub struct PackedStateTreeInfos {
181    pub packed_tree_infos: Vec<PackedStateTreeInfo>,
182    pub output_tree_index: u8,
183}
184
185#[derive(Clone, Default, Debug, PartialEq)]
186pub struct PackedTreeInfos {
187    pub state_trees: Option<PackedStateTreeInfos>,
188    pub address_trees: Vec<PackedAddressTreeInfo>,
189}
190
191impl ValidityProofWithContext {
192    pub fn pack_tree_infos(&self, packed_accounts: &mut PackedAccounts) -> PackedTreeInfos {
193        let mut packed_tree_infos = Vec::new();
194        let mut address_trees = Vec::new();
195        let mut output_tree_index = None;
196        for account in self.accounts.iter() {
197            // Pack TreeInfo
198            let merkle_tree_pubkey_index = packed_accounts.insert_or_get(account.tree_info.tree);
199            let queue_pubkey_index = packed_accounts.insert_or_get(account.tree_info.queue);
200            let tree_info_packed = PackedStateTreeInfo {
201                root_index: account.root_index.root_index,
202                merkle_tree_pubkey_index,
203                queue_pubkey_index,
204                leaf_index: account.leaf_index as u32,
205                prove_by_index: account.root_index.proof_by_index(),
206            };
207            packed_tree_infos.push(tree_info_packed);
208
209            // If a next Merkle tree exists the Merkle tree is full -> use the next Merkle tree for new state.
210            // Else use the current Merkle tree for new state.
211            if let Some(next) = account.tree_info.next_tree_info {
212                // SAFETY: account will always have a state Merkle tree context.
213                // pack_output_tree_index only panics on an address Merkle tree context.
214                let index = next.pack_output_tree_index(packed_accounts).unwrap();
215                if output_tree_index.is_none() {
216                    output_tree_index = Some(index);
217                }
218            } else {
219                // SAFETY: account will always have a state Merkle tree context.
220                // pack_output_tree_index only panics on an address Merkle tree context.
221                let index = account
222                    .tree_info
223                    .pack_output_tree_index(packed_accounts)
224                    .unwrap();
225                if output_tree_index.is_none() {
226                    output_tree_index = Some(index);
227                }
228            }
229        }
230
231        for address in self.addresses.iter() {
232            // Pack AddressTreeInfo
233            let address_merkle_tree_pubkey_index =
234                packed_accounts.insert_or_get(address.tree_info.tree);
235            let address_queue_pubkey_index = packed_accounts.insert_or_get(address.tree_info.queue);
236            address_trees.push(PackedAddressTreeInfo {
237                address_merkle_tree_pubkey_index,
238                address_queue_pubkey_index,
239                root_index: address.root_index,
240            });
241        }
242        let packed_tree_infos = if packed_tree_infos.is_empty() {
243            None
244        } else {
245            Some(PackedStateTreeInfos {
246                packed_tree_infos,
247                output_tree_index: output_tree_index.unwrap(),
248            })
249        };
250        PackedTreeInfos {
251            state_trees: packed_tree_infos,
252            address_trees,
253        }
254    }
255
256    pub fn from_api_model(
257        value: photon_api::types::CompressedProofWithContext,
258        num_hashes: usize,
259    ) -> Result<Self, IndexerError> {
260        let proof = ValidityProof::new(Some(CompressedProof {
261            a: vec_i64_to_fixed_array(&value.compressed_proof.a)?,
262            b: vec_i64_to_fixed_array(&value.compressed_proof.b)?,
263            c: vec_i64_to_fixed_array(&value.compressed_proof.c)?,
264        }));
265
266        // Convert account data from V1 flat arrays to V2 structured format
267        let accounts = (0..num_hashes)
268            .map(|i| {
269                let tree_pubkey =
270                    Pubkey::new_from_array(decode_base58_to_fixed_array(&value.merkle_trees[i])?);
271                let tree_info = QUEUE_TREE_MAPPING.get(&value.merkle_trees[i]).ok_or(
272                    IndexerError::MissingResult {
273                        context: "conversion".into(),
274                        message: format!(
275                            "tree not found in QUEUE_TREE_MAPPING: {}",
276                            &value.merkle_trees[i]
277                        ),
278                    },
279                )?;
280
281                Ok(AccountProofInputs {
282                    hash: decode_base58_to_fixed_array(&value.leaves[i])?,
283                    root: decode_base58_to_fixed_array(&value.roots[i])?,
284                    root_index: RootIndex::new_some(value.root_indices[i] as u16),
285                    leaf_index: value.leaf_indices[i] as u64,
286                    tree_info: TreeInfo {
287                        tree_type: tree_info.tree_type,
288                        tree: tree_pubkey,
289                        queue: tree_info.queue,
290                        cpi_context: tree_info.cpi_context,
291                        next_tree_info: None,
292                    },
293                })
294            })
295            .collect::<Result<Vec<_>, IndexerError>>()?;
296
297        // Convert address data from remaining indices (if any)
298        let addresses = if value.root_indices.len() > num_hashes {
299            (num_hashes..value.root_indices.len())
300                .map(|i| {
301                    let tree_pubkey = Pubkey::new_from_array(decode_base58_to_fixed_array(
302                        &value.merkle_trees[i],
303                    )?);
304                    let tree_info = QUEUE_TREE_MAPPING.get(&value.merkle_trees[i]).ok_or(
305                        IndexerError::MissingResult {
306                            context: "conversion".into(),
307                            message: "expected value was None".into(),
308                        },
309                    )?;
310
311                    Ok(AddressProofInputs {
312                        address: decode_base58_to_fixed_array(&value.leaves[i])?, // Address is in leaves
313                        root: decode_base58_to_fixed_array(&value.roots[i])?,
314                        root_index: value.root_indices[i] as u16,
315                        tree_info: TreeInfo {
316                            tree_type: tree_info.tree_type,
317                            tree: tree_pubkey,
318                            queue: tree_info.queue,
319                            cpi_context: tree_info.cpi_context,
320                            next_tree_info: None,
321                        },
322                    })
323                })
324                .collect::<Result<Vec<_>, IndexerError>>()?
325        } else {
326            Vec::new()
327        };
328
329        Ok(Self {
330            proof,
331            accounts,
332            addresses,
333        })
334    }
335
336    pub fn from_api_model_v2(
337        value: photon_api::types::CompressedProofWithContextV2,
338    ) -> Result<Self, IndexerError> {
339        let proof = if let Some(proof) = value.compressed_proof {
340            ValidityProof::new(Some(CompressedProof {
341                a: vec_i64_to_fixed_array(&proof.a)?,
342                b: vec_i64_to_fixed_array(&proof.b)?,
343                c: vec_i64_to_fixed_array(&proof.c)?,
344            }))
345        } else {
346            ValidityProof::new(None)
347        };
348
349        let accounts = value
350            .accounts
351            .iter()
352            .map(AccountProofInputs::from_api_model)
353            .collect::<Result<Vec<_>, IndexerError>>()?;
354
355        let addresses = value
356            .addresses
357            .iter()
358            .map(AddressProofInputs::from_api_model)
359            .collect::<Result<Vec<_>, IndexerError>>()?;
360
361        Ok(Self {
362            proof,
363            accounts,
364            addresses,
365        })
366    }
367}