1use std::collections::HashMap;
2
3use anchor_lang::prelude::*;
4use light_hasher::{Hasher, Poseidon};
5use light_utils::hash_to_bn254_field_size_be;
6
7#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)]
8pub struct PackedCompressedAccountWithMerkleContext {
9 pub compressed_account: CompressedAccount,
10 pub merkle_context: PackedMerkleContext,
11 pub root_index: u16,
13 pub read_only: bool,
15}
16
17#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)]
18pub struct CompressedAccountWithMerkleContext {
19 pub compressed_account: CompressedAccount,
20 pub merkle_context: MerkleContext,
21}
22impl CompressedAccountWithMerkleContext {
23 pub fn hash(&self) -> Result<[u8; 32]> {
24 self.compressed_account.hash::<Poseidon>(
25 &self.merkle_context.merkle_tree_pubkey,
26 &self.merkle_context.leaf_index,
27 )
28 }
29}
30
31#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Default)]
32pub struct MerkleContext {
33 pub merkle_tree_pubkey: Pubkey,
34 pub nullifier_queue_pubkey: Pubkey,
35 pub leaf_index: u32,
36 pub queue_index: Option<QueueIndex>,
39}
40
41#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Default)]
42pub struct PackedMerkleContext {
43 pub merkle_tree_pubkey_index: u8,
44 pub nullifier_queue_pubkey_index: u8,
45 pub leaf_index: u32,
46 pub queue_index: Option<QueueIndex>,
49}
50
51#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Default)]
52pub struct QueueIndex {
53 pub queue_id: u8,
55 pub index: u16,
57}
58
59pub fn pack_merkle_context(
60 merkle_context: &[MerkleContext],
61 remaining_accounts: &mut HashMap<Pubkey, usize>,
62) -> Vec<PackedMerkleContext> {
63 let mut merkle_context_packed = merkle_context
64 .iter()
65 .map(|x| PackedMerkleContext {
66 leaf_index: x.leaf_index,
67 merkle_tree_pubkey_index: 0, nullifier_queue_pubkey_index: 0, queue_index: None,
70 })
71 .collect::<Vec<PackedMerkleContext>>();
72 let mut index: usize = remaining_accounts.len();
73 for (i, params) in merkle_context.iter().enumerate() {
74 match remaining_accounts.get(¶ms.merkle_tree_pubkey) {
75 Some(_) => {}
76 None => {
77 remaining_accounts.insert(params.merkle_tree_pubkey, index);
78 index += 1;
79 }
80 };
81 merkle_context_packed[i].merkle_tree_pubkey_index =
82 *remaining_accounts.get(¶ms.merkle_tree_pubkey).unwrap() as u8;
83 }
84
85 for (i, params) in merkle_context.iter().enumerate() {
86 match remaining_accounts.get(¶ms.nullifier_queue_pubkey) {
87 Some(_) => {}
88 None => {
89 remaining_accounts.insert(params.nullifier_queue_pubkey, index);
90 index += 1;
91 }
92 };
93 merkle_context_packed[i].nullifier_queue_pubkey_index = *remaining_accounts
94 .get(¶ms.nullifier_queue_pubkey)
95 .unwrap() as u8;
96 }
97 merkle_context_packed
98}
99
100#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)]
101pub struct CompressedAccount {
102 pub owner: Pubkey,
103 pub lamports: u64,
104 pub address: Option<[u8; 32]>,
105 pub data: Option<CompressedAccountData>,
106}
107
108#[derive(Debug, PartialEq, Default, Clone, AnchorSerialize, AnchorDeserialize)]
109pub struct CompressedAccountData {
110 pub discriminator: [u8; 8],
111 pub data: Vec<u8>,
112 pub data_hash: [u8; 32],
113}
114
115impl CompressedAccount {
118 pub fn hash_with_hashed_values<H: Hasher>(
119 &self,
120 &owner_hashed: &[u8; 32],
121 &merkle_tree_hashed: &[u8; 32],
122 leaf_index: &u32,
123 ) -> Result<[u8; 32]> {
124 let capacity = 3
125 + std::cmp::min(self.lamports, 1) as usize
126 + self.address.is_some() as usize
127 + self.data.is_some() as usize * 2;
128 let mut vec: Vec<&[u8]> = Vec::with_capacity(capacity);
129 vec.push(owner_hashed.as_slice());
130
131 let leaf_index = leaf_index.to_le_bytes();
133 vec.push(leaf_index.as_slice());
134
135 vec.push(merkle_tree_hashed.as_slice());
136
137 let mut lamports_bytes = [1, 0, 0, 0, 0, 0, 0, 0, 0];
141 if self.lamports != 0 {
142 lamports_bytes[1..].copy_from_slice(&self.lamports.to_le_bytes());
143 vec.push(lamports_bytes.as_slice());
144 }
145
146 if self.address.is_some() {
147 vec.push(self.address.as_ref().unwrap().as_slice());
148 }
149
150 let mut discriminator_bytes = [2, 0, 0, 0, 0, 0, 0, 0, 0];
151 if let Some(data) = &self.data {
152 discriminator_bytes[1..].copy_from_slice(&data.discriminator);
153 vec.push(&discriminator_bytes);
154 vec.push(&data.data_hash);
155 }
156 let hash = H::hashv(&vec).map_err(ProgramError::from)?;
157 Ok(hash)
158 }
159
160 pub fn hash<H: Hasher>(
161 &self,
162 &merkle_tree_pubkey: &Pubkey,
163 leaf_index: &u32,
164 ) -> Result<[u8; 32]> {
165 self.hash_with_hashed_values::<H>(
166 &hash_to_bn254_field_size_be(&self.owner.to_bytes())
167 .unwrap()
168 .0,
169 &hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes())
170 .unwrap()
171 .0,
172 leaf_index,
173 )
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use light_hasher::Poseidon;
180 use solana_sdk::signature::{Keypair, Signer};
181
182 use super::*;
183 #[test]
191 fn test_compressed_account_hash() {
192 let owner = Keypair::new().pubkey();
193 let address = [1u8; 32];
194 let data = CompressedAccountData {
195 discriminator: [1u8; 8],
196 data: vec![2u8; 32],
197 data_hash: [3u8; 32],
198 };
199 let lamports = 100;
200 let compressed_account = CompressedAccount {
201 owner,
202 lamports,
203 address: Some(address),
204 data: Some(data.clone()),
205 };
206 let merkle_tree_pubkey = Keypair::new().pubkey();
207 let leaf_index = 1;
208 let hash = compressed_account
209 .hash::<Poseidon>(&merkle_tree_pubkey, &leaf_index)
210 .unwrap();
211 let hash_manual = Poseidon::hashv(&[
212 hash_to_bn254_field_size_be(&owner.to_bytes())
213 .unwrap()
214 .0
215 .as_slice(),
216 leaf_index.to_le_bytes().as_slice(),
217 hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes())
218 .unwrap()
219 .0
220 .as_slice(),
221 [&[1u8], lamports.to_le_bytes().as_slice()]
222 .concat()
223 .as_slice(),
224 address.as_slice(),
225 [&[2u8], data.discriminator.as_slice()].concat().as_slice(),
226 &data.data_hash,
227 ])
228 .unwrap();
229 assert_eq!(hash, hash_manual);
230 assert_eq!(hash.len(), 32);
231
232 let compressed_account = CompressedAccount {
234 owner,
235 lamports,
236 address: Some(address),
237 data: None,
238 };
239 let no_data_hash = compressed_account
240 .hash::<Poseidon>(&merkle_tree_pubkey, &leaf_index)
241 .unwrap();
242
243 let hash_manual = Poseidon::hashv(&[
244 hash_to_bn254_field_size_be(&owner.to_bytes())
245 .unwrap()
246 .0
247 .as_slice(),
248 leaf_index.to_le_bytes().as_slice(),
249 hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes())
250 .unwrap()
251 .0
252 .as_slice(),
253 [&[1u8], lamports.to_le_bytes().as_slice()]
254 .concat()
255 .as_slice(),
256 address.as_slice(),
257 ])
258 .unwrap();
259 assert_eq!(no_data_hash, hash_manual);
260 assert_ne!(hash, no_data_hash);
261
262 let compressed_account = CompressedAccount {
264 owner,
265 lamports,
266 address: None,
267 data: Some(data.clone()),
268 };
269 let no_address_hash = compressed_account
270 .hash::<Poseidon>(&merkle_tree_pubkey, &leaf_index)
271 .unwrap();
272 let hash_manual = Poseidon::hashv(&[
273 hash_to_bn254_field_size_be(&owner.to_bytes())
274 .unwrap()
275 .0
276 .as_slice(),
277 leaf_index.to_le_bytes().as_slice(),
278 hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes())
279 .unwrap()
280 .0
281 .as_slice(),
282 [&[1u8], lamports.to_le_bytes().as_slice()]
283 .concat()
284 .as_slice(),
285 [&[2u8], data.discriminator.as_slice()].concat().as_slice(),
286 &data.data_hash,
287 ])
288 .unwrap();
289 assert_eq!(no_address_hash, hash_manual);
290 assert_ne!(hash, no_address_hash);
291 assert_ne!(no_data_hash, no_address_hash);
292
293 let compressed_account = CompressedAccount {
295 owner,
296 lamports: 0,
297 address: None,
298 data: Some(data.clone()),
299 };
300 let no_address_no_lamports_hash = compressed_account
301 .hash::<Poseidon>(&merkle_tree_pubkey, &leaf_index)
302 .unwrap();
303 let hash_manual = Poseidon::hashv(&[
304 hash_to_bn254_field_size_be(&owner.to_bytes())
305 .unwrap()
306 .0
307 .as_slice(),
308 leaf_index.to_le_bytes().as_slice(),
309 hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes())
310 .unwrap()
311 .0
312 .as_slice(),
313 [&[2u8], data.discriminator.as_slice()].concat().as_slice(),
314 &data.data_hash,
315 ])
316 .unwrap();
317 assert_eq!(no_address_no_lamports_hash, hash_manual);
318 assert_ne!(hash, no_address_no_lamports_hash);
319 assert_ne!(no_data_hash, no_address_no_lamports_hash);
320 assert_ne!(no_address_hash, no_address_no_lamports_hash);
321
322 let compressed_account = CompressedAccount {
324 owner,
325 lamports,
326 address: None,
327 data: None,
328 };
329 let no_address_no_data_hash = compressed_account
330 .hash::<Poseidon>(&merkle_tree_pubkey, &leaf_index)
331 .unwrap();
332 let hash_manual = Poseidon::hashv(&[
333 hash_to_bn254_field_size_be(&owner.to_bytes())
334 .unwrap()
335 .0
336 .as_slice(),
337 leaf_index.to_le_bytes().as_slice(),
338 hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes())
339 .unwrap()
340 .0
341 .as_slice(),
342 [&[1u8], lamports.to_le_bytes().as_slice()]
343 .concat()
344 .as_slice(),
345 ])
346 .unwrap();
347 assert_eq!(no_address_no_data_hash, hash_manual);
348 assert_ne!(hash, no_address_no_data_hash);
349 assert_ne!(no_data_hash, no_address_no_data_hash);
350 assert_ne!(no_address_hash, no_address_no_data_hash);
351 assert_ne!(no_address_no_lamports_hash, no_address_no_data_hash);
352
353 let compressed_account = CompressedAccount {
355 owner,
356 lamports: 0,
357 address: None,
358 data: None,
359 };
360 let no_address_no_data_no_lamports_hash = compressed_account
361 .hash::<Poseidon>(&merkle_tree_pubkey, &leaf_index)
362 .unwrap();
363 let hash_manual = Poseidon::hashv(&[
364 hash_to_bn254_field_size_be(&owner.to_bytes())
365 .unwrap()
366 .0
367 .as_slice(),
368 leaf_index.to_le_bytes().as_slice(),
369 hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes())
370 .unwrap()
371 .0
372 .as_slice(),
373 ])
374 .unwrap();
375 assert_eq!(no_address_no_data_no_lamports_hash, hash_manual);
376 assert_ne!(no_address_no_data_hash, no_address_no_data_no_lamports_hash);
377 assert_ne!(hash, no_address_no_data_no_lamports_hash);
378 assert_ne!(no_data_hash, no_address_no_data_no_lamports_hash);
379 assert_ne!(no_address_hash, no_address_no_data_no_lamports_hash);
380 assert_ne!(
381 no_address_no_lamports_hash,
382 no_address_no_data_no_lamports_hash
383 );
384 }
385}