Skip to main content

light_compressed_account/
lib.rs

1//! # light-compressed-account
2//!
3//! Compressed account struct and utility types for Light Protocol.
4//!
5//! | Type | Description |
6//! |------|-------------|
7//! | [`CompressedAccountError`] | Error codes 12001–12025 for account operations |
8//! | [`QueueType`] | Nullifier, address, and state queue variants |
9//! | [`TreeType`] | State and address tree version variants |
10//! | [`CpiSigner`] | Program ID, CPI signer pubkey, and bump |
11//! | [`address`] | Address derivation and seed structs |
12//! | [`compressed_account`] | Core compressed account struct |
13//! | [`constants`] | Program IDs and account discriminators as byte arrays |
14//! | [`discriminators`] | Instruction discriminators for `invoke`, `invoke_cpi`, and queue operations |
15//! | [`instruction_data`] | Instruction data types and proof structs |
16//! | [`nullifier`] | Nullifier computation |
17//! | [`pubkey`] | `Pubkey` struct (re-exported at root) and `AsPubkey` conversion trait |
18//! | [`tx_hash`] | Transaction hash computation |
19
20#![allow(unexpected_cfgs)]
21#![cfg_attr(not(feature = "std"), no_std)]
22
23#[cfg(not(feature = "std"))]
24extern crate alloc;
25
26#[cfg(not(feature = "std"))]
27pub use alloc::{vec, vec::Vec};
28use core::fmt::Display;
29#[cfg(feature = "std")]
30pub use std::{vec, vec::Vec};
31
32use light_hasher::HasherError;
33use thiserror::Error;
34
35pub mod address;
36pub mod compressed_account;
37pub mod constants;
38pub mod discriminators;
39pub use light_hasher::hash_chain;
40pub mod instruction_data;
41pub mod nullifier;
42pub mod pubkey;
43pub mod tx_hash;
44pub use instruction_data::traits::{InstructionDiscriminator, LightInstructionData};
45pub use light_hasher::bigint::bigint_to_be_bytes_array;
46#[cfg(feature = "alloc")]
47pub use light_hasher::hash_to_field_size::{
48    hash_to_bn254_field_size_be, hashv_to_bn254_field_size_be,
49};
50pub use pubkey::Pubkey;
51
52#[derive(Debug, Error, PartialEq)]
53pub enum CompressedAccountError {
54    #[error("Invalid input size, expected at most {0}")]
55    InputTooLarge(usize),
56    #[error("Invalid chunk size")]
57    InvalidChunkSize,
58    #[error("Invalid seeds")]
59    InvalidSeeds,
60    #[error("Invalid rollover threshold")]
61    InvalidRolloverThreshold,
62    #[error("Invalid input length")]
63    InvalidInputLength,
64    #[error("Hasher error {0}")]
65    HasherError(#[from] HasherError),
66    #[error("Invalid Account size.")]
67    InvalidAccountSize,
68    #[error("Account is mutable.")]
69    AccountMutable,
70    #[error("Account is already initialized.")]
71    AlreadyInitialized,
72    #[error("Invalid account balance.")]
73    InvalidAccountBalance,
74    #[error("Failed to borrow rent sysvar.")]
75    FailedBorrowRentSysvar,
76    #[error("Derive address error.")]
77    DeriveAddressError,
78    #[error("Invalid argument.")]
79    InvalidArgument,
80    #[error("Expected address for compressed account got None.")]
81    ZeroCopyExpectedAddress,
82    #[error("Expected address for compressed account got None.")]
83    InstructionDataExpectedAddress,
84    #[error("Compressed account data not initialized.")]
85    CompressedAccountDataNotInitialized,
86    #[error(
87        "Invalid CPI context configuration: cannot write to CPI context without valid context"
88    )]
89    InvalidCpiContext,
90    #[error("Expected discriminator for compressed account got None.")]
91    ExpectedDiscriminator,
92    #[error("Expected data hash for compressed account got None.")]
93    ExpectedDataHash,
94    #[error("Expected proof for compressed account got None.")]
95    InstructionDataExpectedProof,
96    #[error("Expected proof for compressed account got None.")]
97    ZeroCopyExpectedProof,
98    #[error("Invalid proof size: expected 128 bytes, got {0}")]
99    InvalidProofSize(usize),
100}
101
102// NOTE(vadorovsky): Unfortunately, we need to do it by hand.
103// `num_derive::ToPrimitive` doesn't support data-carrying enums.
104impl From<CompressedAccountError> for u32 {
105    fn from(e: CompressedAccountError) -> u32 {
106        match e {
107            CompressedAccountError::InputTooLarge(_) => 12001,
108            CompressedAccountError::InvalidChunkSize => 12002,
109            CompressedAccountError::InvalidSeeds => 12003,
110            CompressedAccountError::InvalidRolloverThreshold => 12004,
111            CompressedAccountError::InvalidInputLength => 12005,
112            CompressedAccountError::InvalidAccountSize => 12010,
113            CompressedAccountError::AccountMutable => 12011,
114            CompressedAccountError::AlreadyInitialized => 12012,
115            CompressedAccountError::InvalidAccountBalance => 12013,
116            CompressedAccountError::FailedBorrowRentSysvar => 12014,
117            CompressedAccountError::DeriveAddressError => 12015,
118            CompressedAccountError::InvalidArgument => 12016,
119            CompressedAccountError::ZeroCopyExpectedAddress => 12017,
120            CompressedAccountError::InstructionDataExpectedAddress => 12018,
121            CompressedAccountError::CompressedAccountDataNotInitialized => 12019,
122            CompressedAccountError::ExpectedDiscriminator => 12020,
123            CompressedAccountError::InstructionDataExpectedProof => 12021,
124            CompressedAccountError::ZeroCopyExpectedProof => 12022,
125            CompressedAccountError::ExpectedDataHash => 12023,
126            CompressedAccountError::InvalidCpiContext => 12024,
127            CompressedAccountError::InvalidProofSize(_) => 12025,
128            CompressedAccountError::HasherError(e) => u32::from(e),
129        }
130    }
131}
132
133#[cfg(feature = "solana")]
134impl From<CompressedAccountError> for solana_program_error::ProgramError {
135    fn from(e: CompressedAccountError) -> Self {
136        solana_program_error::ProgramError::Custom(e.into())
137    }
138}
139
140#[cfg(feature = "pinocchio")]
141impl From<CompressedAccountError> for pinocchio::program_error::ProgramError {
142    fn from(e: CompressedAccountError) -> Self {
143        pinocchio::program_error::ProgramError::Custom(e.into())
144    }
145}
146
147pub const NULLIFIER_QUEUE_TYPE_V1: u64 = 1;
148pub const ADDRESS_QUEUE_TYPE_V1: u64 = 2;
149pub const INPUT_STATE_QUEUE_TYPE_V2: u64 = 3;
150pub const ADDRESS_QUEUE_TYPE_V2: u64 = 4;
151pub const OUTPUT_STATE_QUEUE_TYPE_V2: u64 = 5;
152
153#[cfg_attr(
154    all(feature = "std", feature = "anchor"),
155    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
156)]
157#[cfg_attr(
158    not(feature = "anchor"),
159    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
160)]
161#[derive(Debug, PartialEq, Clone, Copy)]
162#[repr(u64)]
163pub enum QueueType {
164    NullifierV1 = NULLIFIER_QUEUE_TYPE_V1,
165    AddressV1 = ADDRESS_QUEUE_TYPE_V1,
166    InputStateV2 = INPUT_STATE_QUEUE_TYPE_V2,
167    AddressV2 = ADDRESS_QUEUE_TYPE_V2,
168    OutputStateV2 = OUTPUT_STATE_QUEUE_TYPE_V2,
169}
170
171impl From<u64> for QueueType {
172    fn from(value: u64) -> Self {
173        match value {
174            1 => QueueType::NullifierV1,
175            2 => QueueType::AddressV1,
176            3 => QueueType::InputStateV2,
177            4 => QueueType::AddressV2,
178            5 => QueueType::OutputStateV2,
179            _ => panic!("Invalid queue type"),
180        }
181    }
182}
183
184pub const STATE_MERKLE_TREE_TYPE_V1: u64 = 1;
185pub const ADDRESS_MERKLE_TREE_TYPE_V1: u64 = 2;
186pub const STATE_MERKLE_TREE_TYPE_V2: u64 = 3;
187pub const ADDRESS_MERKLE_TREE_TYPE_V2: u64 = 4;
188
189#[cfg_attr(
190    all(feature = "std", feature = "anchor"),
191    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
192)]
193#[cfg_attr(
194    not(feature = "anchor"),
195    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
196)]
197#[derive(Debug, Ord, PartialEq, PartialOrd, Eq, Clone, Copy)]
198#[repr(u64)]
199pub enum TreeType {
200    StateV1 = STATE_MERKLE_TREE_TYPE_V1,
201    AddressV1 = ADDRESS_MERKLE_TREE_TYPE_V1,
202    StateV2 = STATE_MERKLE_TREE_TYPE_V2,
203    AddressV2 = ADDRESS_MERKLE_TREE_TYPE_V2,
204    Unknown = 255,
205}
206
207impl Display for TreeType {
208    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
209        match self {
210            TreeType::StateV1 => write!(f, "StateV1"),
211            TreeType::AddressV1 => write!(f, "AddressV1"),
212            TreeType::StateV2 => write!(f, "StateV2"),
213            TreeType::AddressV2 => write!(f, "AddressV2"),
214            TreeType::Unknown => write!(f, "Unknown"),
215        }
216    }
217}
218
219#[allow(clippy::derivable_impls)]
220impl core::default::Default for TreeType {
221    fn default() -> Self {
222        TreeType::StateV2
223    }
224}
225
226// from u64
227impl From<u64> for TreeType {
228    fn from(value: u64) -> Self {
229        match value {
230            1 => TreeType::StateV1,
231            2 => TreeType::AddressV1,
232            3 => TreeType::StateV2,
233            4 => TreeType::AddressV2,
234            255 => TreeType::Unknown,
235            _ => panic!("Invalid TreeType"),
236        }
237    }
238}
239
240/// Configuration struct containing program ID, CPI signer, and bump for Light Protocol
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub struct CpiSigner {
243    pub program_id: [u8; 32],
244    pub cpi_signer: [u8; 32],
245    pub bump: u8,
246}