Skip to main content

light_compressed_account/
compressed_account.rs

1use light_hasher::{Hasher, Poseidon};
2use light_zero_copy::{ZeroCopy, ZeroCopyMut};
3
4use crate::{
5    instruction_data::zero_copy::ZCompressedAccount, CompressedAccountError, Pubkey, TreeType, Vec,
6};
7
8#[repr(C)]
9#[cfg_attr(
10    all(feature = "std", feature = "anchor"),
11    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
12)]
13#[cfg_attr(
14    not(feature = "anchor"),
15    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
16)]
17#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)]
18pub struct PackedCompressedAccountWithMerkleContext {
19    pub compressed_account: CompressedAccount,
20    pub merkle_context: PackedMerkleContext,
21    /// Index of root used in inclusion validity proof.
22    pub root_index: u16,
23    pub read_only: bool,
24}
25
26#[cfg_attr(
27    all(feature = "std", feature = "anchor"),
28    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
29)]
30#[cfg_attr(
31    not(feature = "anchor"),
32    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
33)]
34#[derive(Debug, PartialEq, Default, Clone)]
35pub struct InCompressedAccountWithMerkleContext {
36    pub compressed_account: InCompressedAccount,
37    pub merkle_context: MerkleContext,
38}
39
40impl From<CompressedAccount> for InCompressedAccount {
41    fn from(value: CompressedAccount) -> Self {
42        let data = value.data.unwrap_or_default();
43        InCompressedAccount {
44            owner: value.owner,
45            lamports: value.lamports,
46            address: value.address,
47            discriminator: data.discriminator,
48            data_hash: data.data_hash,
49        }
50    }
51}
52
53#[cfg_attr(
54    all(feature = "std", feature = "anchor"),
55    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
56)]
57#[cfg_attr(
58    not(feature = "anchor"),
59    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
60)]
61#[derive(Debug, PartialEq, Default, Clone)]
62pub struct PackedInCompressedAccountWithMerkleContext {
63    pub compressed_account: InCompressedAccount,
64    pub merkle_context: PackedMerkleContext,
65    /// Index of root used in inclusion validity proof.
66    pub root_index: u16,
67}
68
69impl From<PackedCompressedAccountWithMerkleContext> for PackedInCompressedAccountWithMerkleContext {
70    fn from(value: PackedCompressedAccountWithMerkleContext) -> Self {
71        Self {
72            compressed_account: value.compressed_account.into(),
73            merkle_context: value.merkle_context,
74            root_index: value.root_index,
75        }
76    }
77}
78
79impl From<CompressedAccountWithMerkleContext> for InCompressedAccountWithMerkleContext {
80    fn from(value: CompressedAccountWithMerkleContext) -> Self {
81        Self {
82            compressed_account: value.compressed_account.into(),
83            merkle_context: value.merkle_context,
84        }
85    }
86}
87
88#[cfg_attr(
89    all(feature = "std", feature = "anchor"),
90    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
91)]
92#[cfg_attr(
93    not(feature = "anchor"),
94    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
95)]
96#[derive(Debug, PartialEq, Default, Clone)]
97pub struct CompressedAccountWithMerkleContext {
98    pub compressed_account: CompressedAccount,
99    pub merkle_context: MerkleContext,
100}
101
102impl CompressedAccountWithMerkleContext {
103    pub fn hash(&self) -> Result<[u8; 32], CompressedAccountError> {
104        self.compressed_account.hash(
105            &self.merkle_context.merkle_tree_pubkey,
106            &self.merkle_context.leaf_index,
107            self.merkle_context.tree_type == TreeType::StateV2,
108        )
109    }
110}
111
112impl CompressedAccountWithMerkleContext {
113    pub fn into_read_only(
114        &self,
115        root_index: Option<u16>,
116    ) -> Result<ReadOnlyCompressedAccount, CompressedAccountError> {
117        let account_hash = self.hash()?;
118        let merkle_context = if root_index.is_none() {
119            let mut merkle_context = self.merkle_context;
120            merkle_context.prove_by_index = true;
121            merkle_context
122        } else {
123            self.merkle_context
124        };
125        Ok(ReadOnlyCompressedAccount {
126            account_hash,
127            merkle_context,
128            root_index: root_index.unwrap_or_default(),
129        })
130    }
131}
132
133#[cfg_attr(
134    all(feature = "std", feature = "anchor"),
135    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
136)]
137#[cfg_attr(
138    not(feature = "anchor"),
139    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
140)]
141#[derive(Debug, PartialEq, Default, Clone)]
142pub struct ReadOnlyCompressedAccount {
143    pub account_hash: [u8; 32],
144    pub merkle_context: MerkleContext,
145    pub root_index: u16,
146}
147
148#[repr(C)]
149#[cfg_attr(
150    all(feature = "std", feature = "anchor"),
151    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
152)]
153#[cfg_attr(
154    not(feature = "anchor"),
155    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
156)]
157#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)]
158pub struct PackedReadOnlyCompressedAccount {
159    pub account_hash: [u8; 32],
160    pub merkle_context: PackedMerkleContext,
161    pub root_index: u16,
162}
163
164#[cfg_attr(
165    all(feature = "std", feature = "anchor"),
166    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
167)]
168#[cfg_attr(
169    not(feature = "anchor"),
170    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
171)]
172#[derive(Debug, Clone, Copy, PartialEq, Default)]
173pub struct MerkleContext {
174    pub merkle_tree_pubkey: Pubkey,
175    pub queue_pubkey: Pubkey,
176    pub leaf_index: u32,
177    pub prove_by_index: bool,
178    pub tree_type: TreeType,
179}
180
181#[repr(C)]
182#[cfg_attr(
183    all(feature = "std", feature = "anchor"),
184    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
185)]
186#[cfg_attr(
187    not(feature = "anchor"),
188    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
189)]
190#[derive(Debug, Clone, Copy, PartialEq, Default, ZeroCopy, ZeroCopyMut)]
191pub struct PackedMerkleContext {
192    pub merkle_tree_pubkey_index: u8,
193    pub queue_pubkey_index: u8,
194    pub leaf_index: u32,
195    pub prove_by_index: bool,
196}
197
198#[repr(C)]
199#[cfg_attr(
200    all(feature = "std", feature = "anchor"),
201    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
202)]
203#[cfg_attr(
204    not(feature = "anchor"),
205    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
206)]
207#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)]
208pub struct CompressedAccount {
209    pub owner: Pubkey,
210    pub lamports: u64,
211    pub address: Option<[u8; 32]>,
212    pub data: Option<CompressedAccountData>,
213}
214
215#[cfg_attr(
216    all(feature = "std", feature = "anchor"),
217    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
218)]
219#[cfg_attr(
220    not(feature = "anchor"),
221    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
222)]
223#[derive(Debug, PartialEq, Default, Clone)]
224pub struct InCompressedAccount {
225    pub owner: Pubkey,
226    pub lamports: u64,
227    pub discriminator: [u8; 8],
228    pub data_hash: [u8; 32],
229    pub address: Option<[u8; 32]>,
230}
231
232#[repr(C)]
233#[cfg_attr(
234    all(feature = "std", feature = "anchor"),
235    derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
236)]
237#[cfg_attr(
238    not(feature = "anchor"),
239    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
240)]
241#[derive(Debug, PartialEq, Default, Clone, ZeroCopyMut)]
242pub struct CompressedAccountData {
243    pub discriminator: [u8; 8],
244    pub data: Vec<u8>,
245    pub data_hash: [u8; 32],
246}
247
248pub fn hash_with_hashed_values(
249    lamports: &u64,
250    address: Option<&[u8]>,
251    data: Option<(&[u8], &[u8])>,
252    owner_hashed: &[u8; 32],
253    merkle_tree_hashed: &[u8; 32],
254    leaf_index: &u32,
255    is_batched: bool,
256) -> Result<[u8; 32], CompressedAccountError> {
257    // Use ArrayVec with max capacity of 7 elements:
258    // owner_hashed + leaf_index + merkle_tree_hashed + lamports + address + discriminator + data_hash
259    let mut vec: tinyvec::ArrayVec<[&[u8]; 7]> = tinyvec::ArrayVec::new();
260    vec.push(owner_hashed.as_slice());
261
262    // leaf index and merkle tree pubkey are used to make every compressed account hash unique
263    let mut leaf_index_bytes = [0u8; 32];
264    if is_batched {
265        leaf_index_bytes[28..].copy_from_slice(&leaf_index.to_be_bytes());
266    } else {
267        leaf_index_bytes[28..].copy_from_slice(&leaf_index.to_le_bytes());
268    };
269    vec.push(leaf_index_bytes.as_slice());
270
271    vec.push(merkle_tree_hashed.as_slice());
272
273    // Lamports are only hashed if non-zero to save CU.
274    // For safety, we prefix lamports with 1 in 1 byte.
275    // Thus, even if the discriminator has the same value as the lamports, the hash will be different.
276    let mut lamports_bytes = [0u8; 32];
277    if *lamports != 0 {
278        if is_batched {
279            lamports_bytes[24..].copy_from_slice(&lamports.to_be_bytes());
280        } else {
281            lamports_bytes[24..].copy_from_slice(&lamports.to_le_bytes());
282        };
283        lamports_bytes[23] = 1;
284
285        vec.push(lamports_bytes.as_slice());
286    }
287    if let Some(address) = address {
288        vec.push(address);
289    }
290
291    let mut discriminator_bytes = [0u8; 32];
292    if let Some((discriminator, data_hash)) = data {
293        discriminator_bytes[24..].copy_from_slice(discriminator);
294        discriminator_bytes[23] = 2;
295        vec.push(&discriminator_bytes);
296        vec.push(data_hash);
297    }
298    Ok(Poseidon::hashv(&vec)?)
299}
300
301/// Hashing scheme:
302/// H(owner || leaf_index || merkle_tree_pubkey || lamports || address || data.discriminator || data.data_hash)
303impl CompressedAccount {
304    pub fn hash_with_hashed_values(
305        &self,
306        owner_hashed: &[u8; 32],
307        merkle_tree_hashed: &[u8; 32],
308        leaf_index: &u32,
309        is_batched: bool,
310    ) -> Result<[u8; 32], CompressedAccountError> {
311        hash_with_hashed_values(
312            &self.lamports,
313            self.address.as_ref().map(|x| x.as_slice()),
314            self.data
315                .as_ref()
316                .map(|x| (x.discriminator.as_slice(), x.data_hash.as_slice())),
317            owner_hashed,
318            merkle_tree_hashed,
319            leaf_index,
320            is_batched,
321        )
322    }
323
324    pub fn hash(
325        &self,
326        &merkle_tree_pubkey: &Pubkey,
327        leaf_index: &u32,
328        is_batched: bool,
329    ) -> Result<[u8; 32], CompressedAccountError> {
330        use light_hasher::hash_to_field_size::hash_to_bn254_field_size_be;
331        let hashed_mt = hash_to_bn254_field_size_be(merkle_tree_pubkey.as_ref());
332
333        self.hash_with_hashed_values(
334            &hash_to_bn254_field_size_be(self.owner.as_ref()),
335            &hashed_mt,
336            leaf_index,
337            is_batched,
338        )
339    }
340}
341
342/// Hashing scheme:
343/// H(owner || leaf_index || merkle_tree_pubkey || lamports || address || data.discriminator || data.data_hash)
344impl ZCompressedAccount<'_> {
345    pub fn hash_with_hashed_values(
346        &self,
347        owner_hashed: &[u8; 32],
348        merkle_tree_hashed: &[u8; 32],
349        leaf_index: &u32,
350        is_batched: bool,
351    ) -> Result<[u8; 32], CompressedAccountError> {
352        hash_with_hashed_values(
353            &(self.lamports.into()),
354            self.address.as_ref().map(|x| x.as_slice()),
355            self.data
356                .as_ref()
357                .map(|x| (x.discriminator.as_slice(), x.data_hash.as_slice())),
358            owner_hashed,
359            merkle_tree_hashed,
360            leaf_index,
361            is_batched,
362        )
363    }
364    pub fn hash(
365        &self,
366        &merkle_tree_pubkey: &[u8; 32],
367        leaf_index: &u32,
368        is_batched: bool,
369    ) -> Result<[u8; 32], CompressedAccountError> {
370        use light_hasher::hash_to_field_size::hash_to_bn254_field_size_be;
371        self.hash_with_hashed_values(
372            &hash_to_bn254_field_size_be(&self.owner.to_bytes()),
373            &hash_to_bn254_field_size_be(merkle_tree_pubkey.as_slice()),
374            leaf_index,
375            is_batched,
376        )
377    }
378}
379
380#[cfg(all(not(feature = "pinocchio"), test, feature = "poseidon"))]
381mod tests {
382    use borsh::BorshSerialize;
383    use light_hasher::{hash_to_field_size::hash_to_bn254_field_size_be, Poseidon};
384    use light_zero_copy::traits::ZeroCopyAt;
385    use num_bigint::BigUint;
386    use rand::Rng;
387
388    use super::*;
389    /// Tests:
390    /// 1. functional with all inputs set
391    /// 2. no data
392    /// 3. no address
393    /// 4. no address and no lamports
394    /// 5. no address and no data
395    /// 6. no address, no data, no lamports
396    #[test]
397    fn test_compressed_account_hash() {
398        let owner = Pubkey::new_unique();
399        let address = [1u8; 32];
400        let data = CompressedAccountData {
401            discriminator: [1u8; 8],
402            data: vec![2u8; 32],
403            data_hash: [3u8; 32],
404        };
405        let lamports = 100;
406        let compressed_account = CompressedAccount {
407            owner,
408            lamports,
409            address: Some(address),
410            data: Some(data.clone()),
411        };
412        let merkle_tree_pubkey = Pubkey::new_unique();
413        let leaf_index: u32 = 1;
414        let hash = compressed_account
415            .hash(&merkle_tree_pubkey, &leaf_index, false)
416            .unwrap();
417        let hash_manual = Poseidon::hashv(&[
418            hash_to_bn254_field_size_be(&owner.to_bytes()).as_slice(),
419            leaf_index.to_le_bytes().as_slice(),
420            hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes()).as_slice(),
421            [&[1u8], lamports.to_le_bytes().as_slice()]
422                .concat()
423                .as_slice(),
424            address.as_slice(),
425            [&[2u8], data.discriminator.as_slice()].concat().as_slice(),
426            &data.data_hash,
427        ])
428        .unwrap();
429        assert_eq!(hash, hash_manual);
430        assert_eq!(hash.len(), 32);
431
432        // no data
433        let compressed_account = CompressedAccount {
434            owner,
435            lamports,
436            address: Some(address),
437            data: None,
438        };
439        let no_data_hash = compressed_account
440            .hash(&merkle_tree_pubkey, &leaf_index, false)
441            .unwrap();
442
443        let hash_manual = Poseidon::hashv(&[
444            hash_to_bn254_field_size_be(&owner.to_bytes()).as_slice(),
445            leaf_index.to_le_bytes().as_slice(),
446            hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes()).as_slice(),
447            [&[1u8], lamports.to_le_bytes().as_slice()]
448                .concat()
449                .as_slice(),
450            address.as_slice(),
451        ])
452        .unwrap();
453        assert_eq!(no_data_hash, hash_manual);
454        assert_ne!(hash, no_data_hash);
455
456        // no address
457        let compressed_account = CompressedAccount {
458            owner,
459            lamports,
460            address: None,
461            data: Some(data.clone()),
462        };
463        let no_address_hash = compressed_account
464            .hash(&merkle_tree_pubkey, &leaf_index, false)
465            .unwrap();
466        let hash_manual = Poseidon::hashv(&[
467            hash_to_bn254_field_size_be(&owner.to_bytes()).as_slice(),
468            leaf_index.to_le_bytes().as_slice(),
469            hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes()).as_slice(),
470            [&[1u8], lamports.to_le_bytes().as_slice()]
471                .concat()
472                .as_slice(),
473            [&[2u8], data.discriminator.as_slice()].concat().as_slice(),
474            &data.data_hash,
475        ])
476        .unwrap();
477        assert_eq!(no_address_hash, hash_manual);
478        assert_ne!(hash, no_address_hash);
479        assert_ne!(no_data_hash, no_address_hash);
480
481        // no address no lamports
482        let compressed_account = CompressedAccount {
483            owner,
484            lamports: 0,
485            address: None,
486            data: Some(data.clone()),
487        };
488        let no_address_no_lamports_hash = compressed_account
489            .hash(&merkle_tree_pubkey, &leaf_index, false)
490            .unwrap();
491        let hash_manual = Poseidon::hashv(&[
492            hash_to_bn254_field_size_be(&owner.to_bytes()).as_slice(),
493            leaf_index.to_le_bytes().as_slice(),
494            hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes()).as_slice(),
495            [&[2u8], data.discriminator.as_slice()].concat().as_slice(),
496            &data.data_hash,
497        ])
498        .unwrap();
499        assert_eq!(no_address_no_lamports_hash, hash_manual);
500        assert_ne!(hash, no_address_no_lamports_hash);
501        assert_ne!(no_data_hash, no_address_no_lamports_hash);
502        assert_ne!(no_address_hash, no_address_no_lamports_hash);
503
504        // no address and no data
505        let compressed_account = CompressedAccount {
506            owner,
507            lamports,
508            address: None,
509            data: None,
510        };
511        let no_address_no_data_hash = compressed_account
512            .hash(&merkle_tree_pubkey, &leaf_index, false)
513            .unwrap();
514        let hash_manual = Poseidon::hashv(&[
515            hash_to_bn254_field_size_be(&owner.to_bytes()).as_slice(),
516            leaf_index.to_le_bytes().as_slice(),
517            hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes()).as_slice(),
518            [&[1u8], lamports.to_le_bytes().as_slice()]
519                .concat()
520                .as_slice(),
521        ])
522        .unwrap();
523        assert_eq!(no_address_no_data_hash, hash_manual);
524        assert_ne!(hash, no_address_no_data_hash);
525        assert_ne!(no_data_hash, no_address_no_data_hash);
526        assert_ne!(no_address_hash, no_address_no_data_hash);
527        assert_ne!(no_address_no_lamports_hash, no_address_no_data_hash);
528
529        // no address, no data, no lamports
530        let compressed_account = CompressedAccount {
531            owner,
532            lamports: 0,
533            address: None,
534            data: None,
535        };
536        let no_address_no_data_no_lamports_hash = compressed_account
537            .hash(&merkle_tree_pubkey, &leaf_index, false)
538            .unwrap();
539        let hash_manual = Poseidon::hashv(&[
540            hash_to_bn254_field_size_be(&owner.to_bytes()).as_slice(),
541            leaf_index.to_le_bytes().as_slice(),
542            hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes()).as_slice(),
543        ])
544        .unwrap();
545        assert_eq!(no_address_no_data_no_lamports_hash, hash_manual);
546        assert_ne!(no_address_no_data_hash, no_address_no_data_no_lamports_hash);
547        assert_ne!(hash, no_address_no_data_no_lamports_hash);
548        assert_ne!(no_data_hash, no_address_no_data_no_lamports_hash);
549        assert_ne!(no_address_hash, no_address_no_data_no_lamports_hash);
550        assert_ne!(
551            no_address_no_lamports_hash,
552            no_address_no_data_no_lamports_hash
553        );
554    }
555
556    #[test]
557    fn reference() {
558        let owner = Pubkey::new_from_array([
559            0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
560            0, 0, 0,
561        ]);
562        let address = [
563            0, 21, 245, 15, 61, 157, 224, 84, 69, 48, 190, 72, 43, 19, 47, 25, 14, 118, 20, 147,
564            40, 141, 175, 33, 233, 58, 36, 179, 73, 137, 84, 99,
565        ];
566
567        let data = CompressedAccountData {
568            discriminator: [0, 0, 0, 0, 0, 0, 0, 1],
569            data: vec![2u8; 31],
570            data_hash: Poseidon::hash(&[vec![2u8; 31], vec![0u8]].concat()).unwrap(),
571        };
572        let lamports = 100;
573        let compressed_account = CompressedAccount {
574            owner,
575            lamports,
576            address: Some(address),
577            data: Some(data.clone()),
578        };
579        let bytes: Vec<u8> = compressed_account.try_to_vec().unwrap();
580        let merkle_tree_pubkey = Pubkey::new_from_array([
581            0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
582            0, 0, 0,
583        ]);
584
585        let leaf_index: u32 = 1;
586        let hash = compressed_account
587            .hash(&merkle_tree_pubkey, &leaf_index, false)
588            .unwrap();
589        let (z_account, _) = ZCompressedAccount::zero_copy_at(&bytes).unwrap();
590        let z_hash = z_account
591            .hash(&merkle_tree_pubkey.to_bytes(), &leaf_index, false)
592            .unwrap();
593        let manual_hash = {
594            let mut hasher = light_poseidon::Poseidon::<Fr>::new_circom(7).unwrap();
595            use ark_bn254::Fr;
596            use ark_ff::{BigInteger, PrimeField};
597            let hashed_owner = hash_to_bn254_field_size_be(&owner.to_bytes());
598            let owner = Fr::from_be_bytes_mod_order(hashed_owner.as_slice());
599            let leaf_index = Fr::from_be_bytes_mod_order(leaf_index.to_le_bytes().as_ref());
600            let hashed_mt = hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes());
601            let merkle_tree_pubkey = Fr::from_be_bytes_mod_order(hashed_mt.as_slice());
602            let lamports = Fr::from_be_bytes_mod_order(lamports.to_le_bytes().as_ref())
603                + Fr::from_be_bytes_mod_order(&[1u8, 0, 0, 0, 0, 0, 0, 0, 0]);
604            let address = Fr::from_be_bytes_mod_order(address.as_slice());
605            let discriminator = Fr::from_be_bytes_mod_order(data.discriminator.as_ref());
606            let domain_separated_discriminator =
607                Fr::from_be_bytes_mod_order(&[2, 0, 0, 0, 0, 0, 0, 0, 0]);
608            let data_discriminator = discriminator + domain_separated_discriminator;
609            use light_poseidon::PoseidonHasher;
610            let inputs = [
611                owner,
612                leaf_index,
613                merkle_tree_pubkey,
614                lamports,
615                address,
616                data_discriminator,
617                Fr::from_be_bytes_mod_order(data.data_hash.as_ref()),
618            ];
619            hasher.hash(&inputs).unwrap().into_bigint().to_bytes_be()
620        };
621        assert_eq!(hash.to_vec(), manual_hash);
622        assert_eq!(z_hash.to_vec(), manual_hash);
623        assert_eq!(hash.len(), 32);
624
625        let manual_hash_new = {
626            let mut hasher = light_poseidon::Poseidon::<Fr>::new_circom(7).unwrap();
627            use ark_bn254::Fr;
628            use ark_ff::{BigInteger, PrimeField};
629            let hashed_owner = hash_to_bn254_field_size_be(&owner.to_bytes());
630            let owner = Fr::from_be_bytes_mod_order(hashed_owner.as_slice());
631            let leaf_index = Fr::from_be_bytes_mod_order(leaf_index.to_be_bytes().as_ref());
632            let hashed_mt = hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes());
633            let merkle_tree_pubkey = Fr::from_be_bytes_mod_order(hashed_mt.as_slice());
634            let lamports = Fr::from_be_bytes_mod_order(lamports.to_be_bytes().as_ref())
635                + Fr::from_be_bytes_mod_order(&[1u8, 0, 0, 0, 0, 0, 0, 0, 0]);
636            let address = Fr::from_be_bytes_mod_order(address.as_slice());
637            let discriminator = Fr::from_be_bytes_mod_order(data.discriminator.as_ref());
638            let domain_separated_discriminator =
639                Fr::from_be_bytes_mod_order(&[2, 0, 0, 0, 0, 0, 0, 0, 0]);
640            let data_discriminator = discriminator + domain_separated_discriminator;
641            use light_poseidon::PoseidonHasher;
642            let inputs = [
643                owner,
644                leaf_index,
645                merkle_tree_pubkey,
646                lamports,
647                address,
648                data_discriminator,
649                Fr::from_be_bytes_mod_order(data.data_hash.as_ref()),
650            ];
651            hasher.hash(&inputs).unwrap().into_bigint().to_bytes_be()
652        };
653        let hash = compressed_account
654            .hash(&merkle_tree_pubkey, &leaf_index, true)
655            .unwrap();
656        let z_hash = z_account
657            .hash(&merkle_tree_pubkey.to_bytes(), &leaf_index, true)
658            .unwrap();
659        assert_ne!(hash.to_vec(), manual_hash);
660        assert_eq!(hash.to_vec(), manual_hash_new);
661        assert_eq!(z_hash.to_vec(), manual_hash_new);
662        assert_eq!(hash.len(), 32);
663        use std::str::FromStr;
664        let circuit_reference_value = BigUint::from_str(
665            "15638319165413000277907073391141043184436601830909724248083671155000605125280",
666        )
667        .unwrap()
668        .to_bytes_be();
669        println!(
670            "lamports domain: {:?}",
671            BigUint::from_bytes_be(&[1u8, 0, 0, 0, 0, 0, 0, 0, 0]).to_string()
672        );
673        println!(
674            "discriminator domain: {:?}",
675            BigUint::from_bytes_be(&[2u8, 0, 0, 0, 0, 0, 0, 0, 0]).to_string()
676        );
677        assert_eq!(hash.to_vec(), circuit_reference_value);
678    }
679
680    impl CompressedAccount {
681        pub fn legacy_hash_with_values<H: Hasher>(
682            &self,
683            &owner_hashed: &[u8; 32],
684            &merkle_tree_hashed: &[u8; 32],
685            leaf_index: &u32,
686        ) -> Result<[u8; 32], CompressedAccountError> {
687            let capacity = 3
688                + std::cmp::min(self.lamports, 1) as usize
689                + self.address.is_some() as usize
690                + self.data.is_some() as usize * 2;
691            let mut vec: Vec<&[u8]> = Vec::with_capacity(capacity);
692            vec.push(owner_hashed.as_slice());
693
694            // leaf index and merkle tree pubkey are used to make every compressed account hash unique
695            let leaf_index = leaf_index.to_le_bytes();
696            vec.push(leaf_index.as_slice());
697
698            vec.push(merkle_tree_hashed.as_slice());
699
700            // Lamports are only hashed if non-zero to save CU.
701            // For safety, we prefix lamports with 1 in 1 byte.
702            // Thus, even if the discriminator has the same value as the lamports, the hash will be different.
703            let mut lamports_bytes = [1, 0, 0, 0, 0, 0, 0, 0, 0];
704            if self.lamports != 0 {
705                lamports_bytes[1..].copy_from_slice(&self.lamports.to_le_bytes());
706                vec.push(lamports_bytes.as_slice());
707            }
708
709            if self.address.is_some() {
710                vec.push(self.address.as_ref().unwrap().as_slice());
711            }
712
713            let mut discriminator_bytes = [2, 0, 0, 0, 0, 0, 0, 0, 0];
714            if let Some(data) = &self.data {
715                discriminator_bytes[1..].copy_from_slice(&data.discriminator);
716                vec.push(&discriminator_bytes);
717                vec.push(&data.data_hash);
718            }
719            let hash = H::hashv(&vec)?;
720            Ok(hash)
721        }
722
723        pub fn hash_legacy<H: Hasher>(
724            &self,
725            &merkle_tree_pubkey: &Pubkey,
726            leaf_index: &u32,
727        ) -> Result<[u8; 32], CompressedAccountError> {
728            let hashed_mt = hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes());
729            self.legacy_hash_with_values::<H>(
730                &hash_to_bn254_field_size_be(&self.owner.to_bytes()),
731                &hashed_mt,
732                leaf_index,
733            )
734        }
735    }
736
737    fn equivalency_of_hash_functions_rnd_iters<const ITERS: usize>() {
738        let mut rng = rand::thread_rng();
739
740        for _ in 0..ITERS {
741            let account = CompressedAccount {
742                owner: Pubkey::new_unique(),
743                lamports: rng.gen::<u64>(),
744                address: if rng.gen_bool(0.5) {
745                    let mut address = rng.gen::<[u8; 32]>();
746                    address[0] = 0;
747                    Some(address)
748                } else {
749                    None
750                },
751                data: if rng.gen_bool(0.5) {
752                    Some(CompressedAccountData {
753                        discriminator: rng.gen(),
754                        data: Vec::new(), // not used in hash
755                        data_hash: Poseidon::hash(rng.gen::<u64>().to_be_bytes().as_slice())
756                            .unwrap(),
757                    })
758                } else {
759                    None
760                },
761            };
762            let leaf_index = rng.gen::<u32>();
763            let merkle_tree_pubkey = Pubkey::new_unique();
764            let hash_legacy = account
765                .hash_legacy::<Poseidon>(&merkle_tree_pubkey, &leaf_index)
766                .unwrap();
767            let hash = account
768                .hash(&merkle_tree_pubkey, &leaf_index, false)
769                .unwrap();
770            let bytes: Vec<u8> = account.try_to_vec().unwrap();
771            let (z_account, _) = ZCompressedAccount::zero_copy_at(bytes.as_slice()).unwrap();
772            let z_hash = z_account
773                .hash(&merkle_tree_pubkey.to_bytes(), &leaf_index, false)
774                .unwrap();
775            assert_eq!(hash_legacy, hash);
776            assert_eq!(hash, z_hash);
777        }
778    }
779
780    #[test]
781    fn equivalency_of_hash_functions() {
782        equivalency_of_hash_functions_rnd_iters::<100>();
783    }
784}