@@ -5,6 +5,7 @@ defmodule LambdaEthereumConsensus.SszEx do
5
5
alias LambdaEthereumConsensus.Utils.BitList
6
6
alias LambdaEthereumConsensus.Utils.BitVector
7
7
import alias LambdaEthereumConsensus.Utils.BitVector
8
+ alias LambdaEthereumConsensus.Utils.ZeroHashes
8
9
9
10
#################
10
11
### Public API
@@ -15,6 +16,7 @@ defmodule LambdaEthereumConsensus.SszEx do
15
16
@ bits_per_byte 8
16
17
@ bits_per_chunk @ bytes_per_chunk * @ bits_per_byte
17
18
@ zero_chunk << 0 :: size ( @ bits_per_chunk ) >>
19
+ @ zero_hashes ZeroHashes . compute_zero_hashes ( )
18
20
19
21
@ spec hash ( iodata ( ) ) :: binary ( )
20
22
def hash ( data ) , do: :crypto . hash ( :sha256 , data )
@@ -109,7 +111,7 @@ defmodule LambdaEthereumConsensus.SszEx do
109
111
if chunks_len > limit do
110
112
{ :error , "chunk size exceeds limit" }
111
113
else
112
- root = merkleize_chunks ( chunks , limit ) |> mix_in_length ( len )
114
+ root = merkleize_chunks_with_virtual_padding ( chunks , limit ) |> mix_in_length ( len )
113
115
{ :ok , root }
114
116
end
115
117
end
@@ -118,7 +120,7 @@ defmodule LambdaEthereumConsensus.SszEx do
118
120
{ :ok , Types . root ( ) } | { :error , String . t ( ) }
119
121
def hash_tree_root_vector_basic_type ( chunks ) do
120
122
leaf_count = chunks |> get_chunks_len ( ) |> next_pow_of_two ( )
121
- root = merkleize_chunks ( chunks , leaf_count )
123
+ root = merkleize_chunks_with_virtual_padding ( chunks , leaf_count )
122
124
{ :ok , root }
123
125
end
124
126
@@ -164,6 +166,38 @@ defmodule LambdaEthereumConsensus.SszEx do
164
166
end
165
167
end
166
168
169
+ def merkleize_chunks_with_virtual_padding ( chunks , leaf_count ) do
170
+ chunks_len = chunks |> get_chunks_len ( )
171
+ power = leaf_count |> compute_pow ( )
172
+ height = power + 1
173
+
174
+ cond do
175
+ chunks_len == 0 ->
176
+ depth = height - 1
177
+ get_zero_hash ( depth )
178
+
179
+ chunks_len == 1 and leaf_count == 1 ->
180
+ chunks
181
+
182
+ true ->
183
+ power = leaf_count |> compute_pow ( )
184
+ height = power + 1
185
+ layers = chunks
186
+ last_index = chunks_len - 1
187
+
188
+ { _ , final_layer } =
189
+ 1 .. ( height - 1 )
190
+ |> Enum . reverse ( )
191
+ |> Enum . reduce ( { last_index , layers } , fn i , { acc_last_index , acc_layers } ->
192
+ updated_layers = update_layers ( i , height , acc_layers , acc_last_index )
193
+ { acc_last_index |> div ( 2 ) , updated_layers }
194
+ end )
195
+
196
+ << root :: binary - size ( @ bytes_per_chunk ) , _ :: binary >> = final_layer
197
+ root
198
+ end
199
+ end
200
+
167
201
@ spec pack ( boolean , :bool ) :: binary ( )
168
202
def pack ( true , :bool ) , do: << 1 :: @ bits_per_chunk - little >>
169
203
def pack ( false , :bool ) , do: @ zero_chunk
@@ -184,6 +218,11 @@ defmodule LambdaEthereumConsensus.SszEx do
184
218
end
185
219
end
186
220
221
+ def chunk_count ( { :list , type , max_size } ) do
222
+ size = size_of ( type )
223
+ ( max_size * size + 31 ) |> div ( 32 )
224
+ end
225
+
187
226
#################
188
227
### Private functions
189
228
#################
@@ -592,16 +631,6 @@ defmodule LambdaEthereumConsensus.SszEx do
592
631
593
632
defp size_of ( { :int , size } ) , do: size |> div ( @ bits_per_byte )
594
633
595
- defp chunk_count ( { :list , { :int , size } , max_size } ) do
596
- size = size_of ( { :int , size } )
597
- ( max_size * size + 31 ) |> div ( 32 )
598
- end
599
-
600
- defp chunk_count ( { :list , :bool , max_size } ) do
601
- size = size_of ( :bool )
602
- ( max_size * size + 31 ) |> div ( 32 )
603
- end
604
-
605
634
defp pack_basic_type_list ( list , schema ) do
606
635
list
607
636
|> Enum . reduce ( << >> , fn x , acc ->
@@ -635,12 +664,69 @@ defmodule LambdaEthereumConsensus.SszEx do
635
664
end
636
665
end
637
666
638
- defp next_pow_of_two ( len ) when is_integer ( len ) and len >= 0 do
639
- if len == 0 do
640
- 0
641
- else
642
- n = ( ( len <<< 1 ) - 1 ) |> :math . log2 ( ) |> trunc ( )
643
- 2 ** n
667
+ defp next_pow_of_two ( 0 ) , do: 0
668
+
669
+ defp next_pow_of_two ( len ) when is_integer ( len ) and len > 0 do
670
+ n = ( ( len <<< 1 ) - 1 ) |> compute_pow ( )
671
+ 2 ** n
672
+ end
673
+
674
+ defp get_chunks_len ( chunks ) do
675
+ chunks |> byte_size ( ) |> div ( @ bytes_per_chunk )
676
+ end
677
+
678
+ defp compute_pow ( value ) do
679
+ :math . log2 ( value ) |> trunc ( )
680
+ end
681
+
682
+ defp update_layers ( i , height , acc_layers , acc_last_index ) do
683
+ 0 .. ( 2 ** i - 1 )
684
+ |> Enum . filter ( fn x -> rem ( x , 2 ) == 0 end )
685
+ |> Enum . reduce_while ( acc_layers , fn j , acc_layers ->
686
+ parent_index = j |> div ( 2 )
687
+ nodes = get_nodes ( parent_index , i , j , height , acc_layers , acc_last_index )
688
+ hash_nodes_and_replace ( nodes , acc_layers )
689
+ end )
690
+ end
691
+
692
+ defp get_nodes ( parent_index , _i , j , _height , acc_layers , acc_last_index )
693
+ when j < acc_last_index do
694
+ start = parent_index * @ bytes_per_chunk
695
+ stop = ( j + 2 ) * @ bytes_per_chunk
696
+ focus = acc_layers |> :binary . part ( start , stop - start )
697
+ focus_len = focus |> byte_size ( )
698
+ children_index = focus_len - 2 * @ bytes_per_chunk
699
+ << _ :: binary - size ( children_index ) , children :: binary >> = focus
700
+
701
+ << left :: binary - size ( @ bytes_per_chunk ) , right :: binary - size ( @ bytes_per_chunk ) >> =
702
+ children
703
+
704
+ { children_index , left , right }
705
+ end
706
+
707
+ defp get_nodes ( parent_index , i , j , height , acc_layers , acc_last_index )
708
+ when j == acc_last_index do
709
+ start = parent_index * @ bytes_per_chunk
710
+ stop = ( j + 1 ) * @ bytes_per_chunk
711
+ focus = acc_layers |> :binary . part ( start , stop - start )
712
+ focus_len = focus |> byte_size ( )
713
+ children_index = focus_len - @ bytes_per_chunk
714
+ << _ :: binary - size ( children_index ) , left :: binary >> = focus
715
+ depth = height - i - 1
716
+ right = get_zero_hash ( depth )
717
+ { children_index , left , right }
718
+ end
719
+
720
+ defp get_nodes ( _ , _ , _ , _ , _ , _ ) , do: :error
721
+
722
+ defp hash_nodes_and_replace ( nodes , layers ) do
723
+ case nodes do
724
+ :error ->
725
+ { :halt , layers }
726
+
727
+ { index , left , right } ->
728
+ hash = hash_nodes ( left , right )
729
+ { :cont , replace_chunk ( layers , index , hash ) }
644
730
end
645
731
end
646
732
@@ -651,7 +737,9 @@ defmodule LambdaEthereumConsensus.SszEx do
651
737
<< left :: binary , new_chunk :: binary , right :: binary >>
652
738
end
653
739
654
- defp get_chunks_len ( chunks ) do
655
- chunks |> byte_size ( ) |> div ( @ bytes_per_chunk )
740
+ defp get_zero_hash ( depth ) do
741
+ offset = ( depth + 1 ) * @ bytes_per_chunk - @ bytes_per_chunk
742
+ << _ :: binary - size ( offset ) , hash :: binary - size ( @ bytes_per_chunk ) , _ :: binary >> = @ zero_hashes
743
+ hash
656
744
end
657
745
end
0 commit comments