light_compressed_account/
compressed_account.rs

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