@@ -8,8 +8,12 @@ use light_hasher::{Hasher, Poseidon};
8
8
use solana_program:: pubkey:: Pubkey ;
9
9
10
10
use crate :: {
11
- address:: pack_account, hash_to_bn254_field_size_be,
12
- instruction_data:: data:: OutputCompressedAccountWithPackedContext , CompressedAccountError ,
11
+ address:: pack_account,
12
+ hash_to_bn254_field_size_be,
13
+ instruction_data:: {
14
+ data:: OutputCompressedAccountWithPackedContext , zero_copy:: ZCompressedAccount ,
15
+ } ,
16
+ CompressedAccountError ,
13
17
} ;
14
18
15
19
#[ derive( Debug , PartialEq , Default , Clone , AnchorSerialize , AnchorDeserialize ) ]
@@ -261,6 +265,68 @@ impl CompressedAccount {
261
265
}
262
266
}
263
267
268
+ /// Hashing scheme:
269
+ /// H(owner || leaf_index || merkle_tree_pubkey || lamports || address || data.discriminator || data.data_hash)
270
+ impl ZCompressedAccount < ' _ > {
271
+ pub fn hash_with_hashed_values < H : Hasher > (
272
+ & self ,
273
+ & owner_hashed: & [ u8 ; 32 ] ,
274
+ & merkle_tree_hashed: & [ u8 ; 32 ] ,
275
+ leaf_index : & u32 ,
276
+ ) -> Result < [ u8 ; 32 ] , CompressedAccountError > {
277
+ let capacity = 3
278
+ + std:: cmp:: min ( u64:: from ( self . lamports ) , 1 ) as usize
279
+ + self . address . is_some ( ) as usize
280
+ + self . data . is_some ( ) as usize * 2 ;
281
+ let mut vec: Vec < & [ u8 ] > = Vec :: with_capacity ( capacity) ;
282
+ vec. push ( owner_hashed. as_slice ( ) ) ;
283
+
284
+ // leaf index and merkle tree pubkey are used to make every compressed account hash unique
285
+ let leaf_index = leaf_index. to_le_bytes ( ) ;
286
+ vec. push ( leaf_index. as_slice ( ) ) ;
287
+
288
+ vec. push ( merkle_tree_hashed. as_slice ( ) ) ;
289
+
290
+ // Lamports are only hashed if non-zero to safe CU
291
+ // For safety we prefix the lamports with 1 in 1 byte.
292
+ // Thus even if the discriminator has the same value as the lamports, the hash will be different.
293
+ let mut lamports_bytes = [ 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ;
294
+ if self . lamports != 0 {
295
+ lamports_bytes[ 1 ..] . copy_from_slice ( & ( u64:: from ( self . lamports ) ) . to_le_bytes ( ) ) ;
296
+ vec. push ( lamports_bytes. as_slice ( ) ) ;
297
+ }
298
+
299
+ if self . address . is_some ( ) {
300
+ vec. push ( self . address . as_ref ( ) . unwrap ( ) . as_slice ( ) ) ;
301
+ }
302
+
303
+ let mut discriminator_bytes = [ 2 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ;
304
+ if let Some ( data) = & self . data {
305
+ discriminator_bytes[ 1 ..] . copy_from_slice ( data. discriminator . as_slice ( ) ) ;
306
+ vec. push ( & discriminator_bytes) ;
307
+ vec. push ( data. data_hash . as_slice ( ) ) ;
308
+ }
309
+ let hash = H :: hashv ( & vec) ?;
310
+ Ok ( hash)
311
+ }
312
+
313
+ pub fn hash < H : Hasher > (
314
+ & self ,
315
+ & merkle_tree_pubkey: & Pubkey ,
316
+ leaf_index : & u32 ,
317
+ ) -> Result < [ u8 ; 32 ] , CompressedAccountError > {
318
+ self . hash_with_hashed_values :: < H > (
319
+ & hash_to_bn254_field_size_be ( & self . owner . to_bytes ( ) )
320
+ . unwrap ( )
321
+ . 0 ,
322
+ & hash_to_bn254_field_size_be ( & merkle_tree_pubkey. to_bytes ( ) )
323
+ . unwrap ( )
324
+ . 0 ,
325
+ leaf_index,
326
+ )
327
+ }
328
+ }
329
+
264
330
#[ cfg( test) ]
265
331
mod tests {
266
332
use light_hasher:: Poseidon ;
@@ -468,4 +534,207 @@ mod tests {
468
534
no_address_no_data_no_lamports_hash
469
535
) ;
470
536
}
537
+
538
+ /// Tests:
539
+ /// 1. functional with all inputs set
540
+ /// 2. no data
541
+ /// 3. no address
542
+ /// 4. no address and no lamports
543
+ /// 5. no address and no data
544
+ /// 6. no address, no data, no lamports
545
+ #[ test]
546
+ fn test_zcompressed_account_hash ( ) {
547
+ let owner = Pubkey :: new_unique ( ) ;
548
+ let address = [ 1u8 ; 32 ] ;
549
+ let data = CompressedAccountData {
550
+ discriminator : [ 1u8 ; 8 ] ,
551
+ data : vec ! [ 2u8 ; 32 ] ,
552
+ data_hash : [ 3u8 ; 32 ] ,
553
+ } ;
554
+ let lamports = 100 ;
555
+ let compressed_account = CompressedAccount {
556
+ owner,
557
+ lamports,
558
+ address : Some ( address) ,
559
+ data : Some ( data. clone ( ) ) ,
560
+ } ;
561
+ let merkle_tree_pubkey = Pubkey :: new_unique ( ) ;
562
+ let leaf_index = 1 ;
563
+ let hash = compressed_account
564
+ . hash :: < Poseidon > ( & merkle_tree_pubkey, & leaf_index)
565
+ . unwrap ( ) ;
566
+ let hash_manual = Poseidon :: hashv ( & [
567
+ hash_to_bn254_field_size_be ( & owner. to_bytes ( ) )
568
+ . unwrap ( )
569
+ . 0
570
+ . as_slice ( ) ,
571
+ leaf_index. to_le_bytes ( ) . as_slice ( ) ,
572
+ hash_to_bn254_field_size_be ( & merkle_tree_pubkey. to_bytes ( ) )
573
+ . unwrap ( )
574
+ . 0
575
+ . as_slice ( ) ,
576
+ [ & [ 1u8 ] , lamports. to_le_bytes ( ) . as_slice ( ) ]
577
+ . concat ( )
578
+ . as_slice ( ) ,
579
+ address. as_slice ( ) ,
580
+ [ & [ 2u8 ] , data. discriminator . as_slice ( ) ] . concat ( ) . as_slice ( ) ,
581
+ & data. data_hash ,
582
+ ] )
583
+ . unwrap ( ) ;
584
+ assert_eq ! ( hash, hash_manual) ;
585
+ assert_eq ! ( hash. len( ) , 32 ) ;
586
+
587
+ // no data
588
+ let compressed_account = CompressedAccount {
589
+ owner,
590
+ lamports,
591
+ address : Some ( address) ,
592
+ data : None ,
593
+ } ;
594
+ let no_data_hash = compressed_account
595
+ . hash :: < Poseidon > ( & merkle_tree_pubkey, & leaf_index)
596
+ . unwrap ( ) ;
597
+
598
+ let hash_manual = Poseidon :: hashv ( & [
599
+ hash_to_bn254_field_size_be ( & owner. to_bytes ( ) )
600
+ . unwrap ( )
601
+ . 0
602
+ . as_slice ( ) ,
603
+ leaf_index. to_le_bytes ( ) . as_slice ( ) ,
604
+ hash_to_bn254_field_size_be ( & merkle_tree_pubkey. to_bytes ( ) )
605
+ . unwrap ( )
606
+ . 0
607
+ . as_slice ( ) ,
608
+ [ & [ 1u8 ] , lamports. to_le_bytes ( ) . as_slice ( ) ]
609
+ . concat ( )
610
+ . as_slice ( ) ,
611
+ address. as_slice ( ) ,
612
+ ] )
613
+ . unwrap ( ) ;
614
+ assert_eq ! ( no_data_hash, hash_manual) ;
615
+ assert_ne ! ( hash, no_data_hash) ;
616
+
617
+ // no address
618
+ let compressed_account = CompressedAccount {
619
+ owner,
620
+ lamports,
621
+ address : None ,
622
+ data : Some ( data. clone ( ) ) ,
623
+ } ;
624
+ let no_address_hash = compressed_account
625
+ . hash :: < Poseidon > ( & merkle_tree_pubkey, & leaf_index)
626
+ . unwrap ( ) ;
627
+ let hash_manual = Poseidon :: hashv ( & [
628
+ hash_to_bn254_field_size_be ( & owner. to_bytes ( ) )
629
+ . unwrap ( )
630
+ . 0
631
+ . as_slice ( ) ,
632
+ leaf_index. to_le_bytes ( ) . as_slice ( ) ,
633
+ hash_to_bn254_field_size_be ( & merkle_tree_pubkey. to_bytes ( ) )
634
+ . unwrap ( )
635
+ . 0
636
+ . as_slice ( ) ,
637
+ [ & [ 1u8 ] , lamports. to_le_bytes ( ) . as_slice ( ) ]
638
+ . concat ( )
639
+ . as_slice ( ) ,
640
+ [ & [ 2u8 ] , data. discriminator . as_slice ( ) ] . concat ( ) . as_slice ( ) ,
641
+ & data. data_hash ,
642
+ ] )
643
+ . unwrap ( ) ;
644
+ assert_eq ! ( no_address_hash, hash_manual) ;
645
+ assert_ne ! ( hash, no_address_hash) ;
646
+ assert_ne ! ( no_data_hash, no_address_hash) ;
647
+
648
+ // no address no lamports
649
+ let compressed_account = CompressedAccount {
650
+ owner,
651
+ lamports : 0 ,
652
+ address : None ,
653
+ data : Some ( data. clone ( ) ) ,
654
+ } ;
655
+ let no_address_no_lamports_hash = compressed_account
656
+ . hash :: < Poseidon > ( & merkle_tree_pubkey, & leaf_index)
657
+ . unwrap ( ) ;
658
+ let hash_manual = Poseidon :: hashv ( & [
659
+ hash_to_bn254_field_size_be ( & owner. to_bytes ( ) )
660
+ . unwrap ( )
661
+ . 0
662
+ . as_slice ( ) ,
663
+ leaf_index. to_le_bytes ( ) . as_slice ( ) ,
664
+ hash_to_bn254_field_size_be ( & merkle_tree_pubkey. to_bytes ( ) )
665
+ . unwrap ( )
666
+ . 0
667
+ . as_slice ( ) ,
668
+ [ & [ 2u8 ] , data. discriminator . as_slice ( ) ] . concat ( ) . as_slice ( ) ,
669
+ & data. data_hash ,
670
+ ] )
671
+ . unwrap ( ) ;
672
+ assert_eq ! ( no_address_no_lamports_hash, hash_manual) ;
673
+ assert_ne ! ( hash, no_address_no_lamports_hash) ;
674
+ assert_ne ! ( no_data_hash, no_address_no_lamports_hash) ;
675
+ assert_ne ! ( no_address_hash, no_address_no_lamports_hash) ;
676
+
677
+ // no address and no data
678
+ let compressed_account = CompressedAccount {
679
+ owner,
680
+ lamports,
681
+ address : None ,
682
+ data : None ,
683
+ } ;
684
+ let no_address_no_data_hash = compressed_account
685
+ . hash :: < Poseidon > ( & merkle_tree_pubkey, & leaf_index)
686
+ . unwrap ( ) ;
687
+ let hash_manual = Poseidon :: hashv ( & [
688
+ hash_to_bn254_field_size_be ( & owner. to_bytes ( ) )
689
+ . unwrap ( )
690
+ . 0
691
+ . as_slice ( ) ,
692
+ leaf_index. to_le_bytes ( ) . as_slice ( ) ,
693
+ hash_to_bn254_field_size_be ( & merkle_tree_pubkey. to_bytes ( ) )
694
+ . unwrap ( )
695
+ . 0
696
+ . as_slice ( ) ,
697
+ [ & [ 1u8 ] , lamports. to_le_bytes ( ) . as_slice ( ) ]
698
+ . concat ( )
699
+ . as_slice ( ) ,
700
+ ] )
701
+ . unwrap ( ) ;
702
+ assert_eq ! ( no_address_no_data_hash, hash_manual) ;
703
+ assert_ne ! ( hash, no_address_no_data_hash) ;
704
+ assert_ne ! ( no_data_hash, no_address_no_data_hash) ;
705
+ assert_ne ! ( no_address_hash, no_address_no_data_hash) ;
706
+ assert_ne ! ( no_address_no_lamports_hash, no_address_no_data_hash) ;
707
+
708
+ // no address, no data, no lamports
709
+ let compressed_account = CompressedAccount {
710
+ owner,
711
+ lamports : 0 ,
712
+ address : None ,
713
+ data : None ,
714
+ } ;
715
+ let no_address_no_data_no_lamports_hash = compressed_account
716
+ . hash :: < Poseidon > ( & merkle_tree_pubkey, & leaf_index)
717
+ . unwrap ( ) ;
718
+ let hash_manual = Poseidon :: hashv ( & [
719
+ hash_to_bn254_field_size_be ( & owner. to_bytes ( ) )
720
+ . unwrap ( )
721
+ . 0
722
+ . as_slice ( ) ,
723
+ leaf_index. to_le_bytes ( ) . as_slice ( ) ,
724
+ hash_to_bn254_field_size_be ( & merkle_tree_pubkey. to_bytes ( ) )
725
+ . unwrap ( )
726
+ . 0
727
+ . as_slice ( ) ,
728
+ ] )
729
+ . unwrap ( ) ;
730
+ assert_eq ! ( no_address_no_data_no_lamports_hash, hash_manual) ;
731
+ assert_ne ! ( no_address_no_data_hash, no_address_no_data_no_lamports_hash) ;
732
+ assert_ne ! ( hash, no_address_no_data_no_lamports_hash) ;
733
+ assert_ne ! ( no_data_hash, no_address_no_data_no_lamports_hash) ;
734
+ assert_ne ! ( no_address_hash, no_address_no_data_no_lamports_hash) ;
735
+ assert_ne ! (
736
+ no_address_no_lamports_hash,
737
+ no_address_no_data_no_lamports_hash
738
+ ) ;
739
+ }
471
740
}
0 commit comments