@@ -3,8 +3,8 @@ defmodule LambdaEthereumConsensus.SszEx do
3
3
SSZ library in Elixir
4
4
"""
5
5
alias LambdaEthereumConsensus.Utils.BitList
6
- alias LambdaEthereumConsensus.Utils.BitVector
7
6
import alias LambdaEthereumConsensus.Utils.BitVector
7
+ import Bitwise
8
8
alias LambdaEthereumConsensus.Utils.ZeroHashes
9
9
10
10
#################
@@ -34,8 +34,11 @@ defmodule LambdaEthereumConsensus.SszEx do
34
34
else: encode_fixed_size_list ( list , basic_type , size )
35
35
end
36
36
37
- def encode ( vector , { :vector , basic_type , size } ) ,
38
- do: encode_fixed_size_list ( vector , basic_type , size )
37
+ def encode ( vector , { :vector , basic_type , size } ) do
38
+ if variable_size? ( basic_type ) ,
39
+ do: encode_variable_size_list ( vector , basic_type , size ) ,
40
+ else: encode_fixed_size_list ( vector , basic_type , size )
41
+ end
39
42
40
43
def encode ( value , { :bitlist , max_size } ) when is_bitstring ( value ) ,
41
44
do: encode_bitlist ( value , max_size )
@@ -56,18 +59,26 @@ defmodule LambdaEthereumConsensus.SszEx do
56
59
def decode ( binary , { :list , basic_type , size } ) do
57
60
if variable_size? ( basic_type ) ,
58
61
do: decode_variable_list ( binary , basic_type , size ) ,
59
- else: decode_list ( binary , basic_type , size )
62
+ else: decode_fixed_list ( binary , basic_type , size )
60
63
end
61
64
62
- def decode ( binary , { :vector , basic_type , size } ) , do: decode_list ( binary , basic_type , size )
65
+ def decode ( binary , { :vector , basic_type , size } ) do
66
+ if variable_size? ( basic_type ) ,
67
+ do: decode_variable_list ( binary , basic_type , size ) ,
68
+ else: decode_fixed_vector ( binary , basic_type , size )
69
+ end
63
70
64
71
def decode ( value , { :bitlist , max_size } ) when is_bitstring ( value ) ,
65
72
do: decode_bitlist ( value , max_size )
66
73
67
74
def decode ( value , { :bitvector , size } ) when is_bitstring ( value ) ,
68
75
do: decode_bitvector ( value , size )
69
76
70
- def decode ( binary , module ) when is_atom ( module ) , do: decode_container ( binary , module )
77
+ def decode ( binary , module ) when is_atom ( module ) do
78
+ if variable_size? ( module ) ,
79
+ do: decode_variable_container ( binary , module ) ,
80
+ else: decode_fixed_container ( binary , module )
81
+ end
71
82
72
83
@ spec hash_tree_root! ( boolean , atom ) :: Types . root ( )
73
84
def hash_tree_root! ( value , :bool ) , do: pack ( value , :bool )
@@ -274,13 +285,16 @@ defmodule LambdaEthereumConsensus.SszEx do
274
285
defp encode_bool ( true ) , do: { :ok , "\x01 " }
275
286
defp encode_bool ( false ) , do: { :ok , "\x00 " }
276
287
277
- defp decode_uint ( binary , size ) do
288
+ defp decode_uint ( binary , size ) when bit_size ( binary ) == size do
278
289
<< element :: integer - size ( size ) - little , _rest :: bitstring >> = binary
279
290
{ :ok , element }
280
291
end
281
292
293
+ defp decode_uint ( _binary , size ) , do: { :error , "invalid byte size #{ inspect ( size ) } " }
294
+
282
295
defp decode_bool ( "\x01 " ) , do: { :ok , true }
283
296
defp decode_bool ( "\x00 " ) , do: { :ok , false }
297
+ defp decode_bool ( _ ) , do: { :error , "invalid bool value" }
284
298
285
299
defp encode_fixed_size_list ( list , _basic_type , max_size ) when length ( list ) > max_size ,
286
300
do: { :error , "invalid max_size of list" }
@@ -348,7 +362,7 @@ defmodule LambdaEthereumConsensus.SszEx do
348
362
end
349
363
end
350
364
351
- defp decode_bitlist ( bit_list , max_size ) do
365
+ defp decode_bitlist ( bit_list , max_size ) when bit_size ( bit_list ) > 0 do
352
366
num_bytes = byte_size ( bit_list )
353
367
{ decoded , len } = BitList . new ( bit_list )
354
368
@@ -367,26 +381,70 @@ defmodule LambdaEthereumConsensus.SszEx do
367
381
end
368
382
end
369
383
370
- defp decode_bitvector ( bit_vector , size ) when bit_size ( bit_vector ) == size ,
371
- do: { :ok , BitVector . new ( bit_vector , size ) }
384
+ defp decode_bitlist ( _bit_list , _max_size ) , do: { :error , "invalid bitlist" }
372
385
373
- defp decode_bitvector ( _bit_vector , _size ) , do: { :error , "invalid bit_vector length" }
386
+ defp decode_bitvector ( bit_vector , size ) do
387
+ num_bytes = byte_size ( bit_vector )
374
388
375
- defp decode_list ( binary , basic_type , size ) do
389
+ cond do
390
+ bit_size ( bit_vector ) == 0 ->
391
+ { :error , "ExcessBits" }
392
+
393
+ num_bytes != max ( 1 , div ( size + 7 , 8 ) ) ->
394
+ { :error , "InvalidByteCount" }
395
+
396
+ true ->
397
+ case bit_vector do
398
+ # Padding bits are clear
399
+ << _first :: binary - size ( num_bytes - 1 ) , 0 :: size ( 8 - rem ( size , 8 ) &&& 7 ) ,
400
+ _rest :: bitstring >> ->
401
+ { :ok , BitVector . new ( bit_vector , size ) }
402
+
403
+ _else ->
404
+ { :error , "ExcessBits" }
405
+ end
406
+ end
407
+ end
408
+
409
+ defp decode_fixed_list ( binary , basic_type , size ) do
376
410
fixed_size = get_fixed_size ( basic_type )
377
411
378
- with { :ok , decoded_list } = result <-
379
- binary
380
- |> decode_chunk ( fixed_size , basic_type )
381
- |> flatten_results ( ) do
382
- if length ( decoded_list ) > size do
383
- { :error , "invalid max_size of list" }
384
- else
385
- result
386
- end
412
+ with { :ok , decoded_list } = result <- decode_fixed_collection ( binary , fixed_size , basic_type ) ,
413
+ :ok <- check_valid_list_size_after_decode ( size , length ( decoded_list ) ) do
414
+ result
415
+ end
416
+ end
417
+
418
+ defp decode_fixed_vector ( binary , basic_type , size ) do
419
+ fixed_size = get_fixed_size ( basic_type )
420
+
421
+ with :ok <- check_valid_vector_size_prev_decode ( fixed_size , size , binary ) ,
422
+ { :ok , decoded_vector } = result <-
423
+ decode_fixed_collection ( binary , fixed_size , basic_type ) ,
424
+ :ok <- check_valid_vector_size_after_decode ( size , length ( decoded_vector ) ) do
425
+ result
387
426
end
388
427
end
389
428
429
+ def check_valid_vector_size_prev_decode ( fixed_size , size , binary )
430
+ when fixed_size * size == byte_size ( binary ) ,
431
+ do: :ok
432
+
433
+ def check_valid_vector_size_prev_decode ( _fixed_size , _size , _binary ) ,
434
+ do: { :error , "Invalid vector size" }
435
+
436
+ def check_valid_vector_size_after_decode ( size , decoded_size )
437
+ when decoded_size == size and decoded_size > 0 ,
438
+ do: :ok
439
+
440
+ def check_valid_vector_size_after_decode ( _size , _decoded_size ) ,
441
+ do: { :error , "invalid vector decoded size" }
442
+
443
+ def check_valid_list_size_after_decode ( size , decoded_size ) when decoded_size <= size , do: :ok
444
+
445
+ def check_valid_list_size_after_decode ( _size , _decoded_size ) ,
446
+ do: { :error , "invalid max_size of list" }
447
+
390
448
defp decode_variable_list ( binary , _ , _ ) when byte_size ( binary ) == 0 do
391
449
{ :ok , [ ] }
392
450
end
@@ -407,7 +465,7 @@ defmodule LambdaEthereumConsensus.SszEx do
407
465
first_offset < @ bytes_per_length_offset do
408
466
{ :error , "InvalidListFixedBytesLen" }
409
467
else
410
- with { :ok , first_offset } <-
468
+ with :ok <-
411
469
sanitize_offset ( first_offset , nil , byte_size ( binary ) , first_offset ) do
412
470
decode_variable_list_elements (
413
471
num_elements ,
@@ -448,7 +506,7 @@ defmodule LambdaEthereumConsensus.SszEx do
448
506
) do
449
507
<< next_offset :: integer - 32 - little , rest_bytes :: bitstring >> = acc_rest_bytes
450
508
451
- with { :ok , next_offset } <-
509
+ with :ok <-
452
510
sanitize_offset ( next_offset , offset , byte_size ( binary ) , first_offset ) do
453
511
part = :binary . part ( binary , offset , next_offset - offset )
454
512
@@ -467,7 +525,7 @@ defmodule LambdaEthereumConsensus.SszEx do
467
525
defp encode_container ( container , schemas ) do
468
526
{ fixed_size_values , fixed_length , variable_values } = analyze_schemas ( container , schemas )
469
527
470
- with { :ok , variable_parts } <- encode_schemas ( variable_values ) ,
528
+ with { :ok , variable_parts } <- encode_schemas ( Enum . reverse ( variable_values ) ) ,
471
529
offsets = calculate_offsets ( variable_parts , fixed_length ) ,
472
530
variable_length =
473
531
Enum . reduce ( variable_parts , 0 , fn part , acc -> byte_size ( part ) + acc end ) ,
@@ -524,48 +582,87 @@ defmodule LambdaEthereumConsensus.SszEx do
524
582
defp replace_offset ( element , { acc_fixed_list , acc_offsets_list } ) ,
525
583
do: { [ element | acc_fixed_list ] , acc_offsets_list }
526
584
527
- defp decode_container ( binary , module ) do
585
+ defp decode_variable_container ( binary , module ) do
528
586
schemas = module . schema ( )
529
587
fixed_length = get_fixed_length ( schemas )
530
- << fixed_binary :: binary - size ( fixed_length ) , variable_binary :: bitstring >> = binary
531
588
532
- with { :ok , fixed_parts , offsets } <- decode_fixed_section ( fixed_binary , schemas , fixed_length ) ,
533
- { :ok , variable_parts } <- decode_variable_section ( variable_binary , offsets ) do
589
+ with :ok <- sanitize_offset ( fixed_length , nil , byte_size ( binary ) , nil ) ,
590
+ << fixed_binary :: binary - size ( fixed_length ) , variable_binary :: bitstring >> = binary ,
591
+ { :ok , fixed_parts , offsets , items_index } <-
592
+ decode_fixed_section ( fixed_binary , schemas , fixed_length ) ,
593
+ :ok <- check_first_offset ( offsets , items_index , byte_size ( binary ) ) ,
594
+ { :ok , variable_parts } <- decode_variable_section ( binary , variable_binary , offsets ) do
534
595
{ :ok , struct! ( module , fixed_parts ++ variable_parts ) }
535
596
end
536
597
end
537
598
538
- defp decode_variable_section ( binary , offsets ) do
599
+ defp decode_fixed_container ( binary , module ) do
600
+ schemas = module . schema ( )
601
+ fixed_length = get_fixed_length ( schemas )
602
+
603
+ with { :ok , fixed_parts , _offsets , items_index } <-
604
+ decode_fixed_section ( binary , schemas , fixed_length ) ,
605
+ :ok <- check_byte_len ( items_index , byte_size ( binary ) ) do
606
+ { :ok , struct! ( module , fixed_parts ) }
607
+ end
608
+ end
609
+
610
+ defp check_first_offset ( [ { offset , _ } | _rest ] , items_index , _binary_size ) do
611
+ cond do
612
+ offset < items_index -> { :error , "OffsetIntoFixedPortion" }
613
+ offset > items_index -> { :error , "OffsetSkipsVariableBytes" }
614
+ true -> :ok
615
+ end
616
+ end
617
+
618
+ defp check_byte_len ( items_index , binary_size )
619
+ when items_index == binary_size ,
620
+ do: :ok
621
+
622
+ defp check_byte_len ( _items_index , _binary_size ) ,
623
+ do: { :error , "InvalidByteLength" }
624
+
625
+ defp decode_variable_section ( full_binary , binary , offsets ) do
539
626
offsets
540
627
|> Enum . chunk_every ( 2 , 1 )
541
- |> Enum . reduce ( { binary , [ ] } , fn
628
+ |> Enum . reduce_while ( { binary , [ ] } , fn
542
629
[ { offset , { key , schema } } , { next_offset , _ } ] , { rest_bytes , acc_variable_parts } ->
543
- size = next_offset - offset
544
- << chunk :: binary - size ( size ) , rest :: bitstring >> = rest_bytes
545
- { rest , [ { key , decode ( chunk , schema ) } | acc_variable_parts ] }
630
+ case sanitize_offset ( next_offset , offset , byte_size ( full_binary ) , nil ) do
631
+ :ok ->
632
+ size = next_offset - offset
633
+ << chunk :: binary - size ( size ) , rest :: bitstring >> = rest_bytes
634
+ { :cont , { rest , [ { key , decode ( chunk , schema ) } | acc_variable_parts ] } }
635
+
636
+ error ->
637
+ { :halt , { << >> , [ { key , error } | acc_variable_parts ] } }
638
+ end
546
639
547
640
[ { _offset , { key , schema } } ] , { rest_bytes , acc_variable_parts } ->
548
- { << >> , [ { key , decode ( rest_bytes , schema ) } | acc_variable_parts ] }
641
+ { :cont , { << >> , [ { key , decode ( rest_bytes , schema ) } | acc_variable_parts ] } }
549
642
end )
550
643
|> then ( fn { << >> , variable_parts } ->
551
644
flatten_container_results ( variable_parts )
552
645
end )
553
646
end
554
647
555
- defp decode_fixed_section ( binary , schemas , fixed_length ) do
648
+ defp decode_fixed_section ( binary , schemas , _fixed_length ) do
556
649
schemas
557
- |> Enum . reduce ( { binary , [ ] , [ ] } , fn { key , schema } , { binary , fixed_parts , offsets } ->
650
+ |> Enum . reduce ( { binary , [ ] , [ ] , 0 } , fn { key , schema } ,
651
+ { binary , fixed_parts , offsets , items_index } ->
558
652
if variable_size? ( schema ) do
559
653
<< offset :: integer - size ( @ offset_bits ) - little , rest :: bitstring >> = binary
560
- { rest , fixed_parts , [ { offset - fixed_length , { key , schema } } | offsets ] }
654
+
655
+ { rest , fixed_parts , [ { offset , { key , schema } } | offsets ] ,
656
+ items_index + @ bytes_per_length_offset }
561
657
else
562
658
ssz_fixed_len = get_fixed_size ( schema )
563
659
<< chunk :: binary - size ( ssz_fixed_len ) , rest :: bitstring >> = binary
564
- { rest , [ { key , decode ( chunk , schema ) } | fixed_parts ] , offsets }
660
+ { rest , [ { key , decode ( chunk , schema ) } | fixed_parts ] , offsets , items_index + ssz_fixed_len }
565
661
end
566
662
end )
567
- |> then ( fn { _rest_bytes , fixed_parts , offsets } ->
663
+ |> then ( fn { _rest_bytes , fixed_parts , offsets , items_index } ->
568
664
Tuple . append ( flatten_container_results ( fixed_parts ) , Enum . reverse ( offsets ) )
665
+ |> Tuple . append ( items_index )
569
666
end )
570
667
end
571
668
@@ -582,35 +679,47 @@ defmodule LambdaEthereumConsensus.SszEx do
582
679
end
583
680
584
681
# https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view
585
- defp sanitize_offset ( offset , previous_offset , num_bytes , num_fixed_bytes ) do
682
+ defp sanitize_offset ( offset , previous_offset , num_bytes , nil ) do
586
683
cond do
587
- offset < num_fixed_bytes ->
588
- { :error , "OffsetIntoFixedPortion" }
589
-
590
- previous_offset == nil && offset != num_fixed_bytes ->
591
- { :error , "OffsetSkipsVariableBytes" }
592
-
593
684
offset > num_bytes ->
594
685
{ :error , "OffsetOutOfBounds" }
595
686
596
687
previous_offset != nil && previous_offset > offset ->
597
688
{ :error , "OffsetsAreDecreasing" }
598
689
599
690
true ->
600
- { :ok , offset }
691
+ :ok
601
692
end
602
693
end
603
694
604
- defp decode_chunk ( binary , chunk_size , basic_type ) do
605
- decode_chunk ( binary , chunk_size , basic_type , [ ] )
695
+ defp sanitize_offset ( offset , previous_offset , _num_bytes , num_fixed_bytes ) do
696
+ cond do
697
+ offset < num_fixed_bytes ->
698
+ { :error , "OffsetIntoFixedPortion" }
699
+
700
+ previous_offset == nil && offset != num_fixed_bytes ->
701
+ { :error , "OffsetSkipsVariableBytes" }
702
+
703
+ true ->
704
+ :ok
705
+ end
706
+ end
707
+
708
+ defp decode_fixed_collection ( binary , chunk_size , basic_type ) do
709
+ decode_fixed_collection ( binary , chunk_size , basic_type , [ ] )
606
710
|> Enum . reverse ( )
711
+ |> flatten_results ( )
607
712
end
608
713
609
- defp decode_chunk ( << >> , _chunk_size , _basic_type , results ) , do: results
714
+ defp decode_fixed_collection ( << >> , _chunk_size , _basic_type , results ) , do: results
715
+
716
+ defp decode_fixed_collection ( binary , chunk_size , _basic_type , results )
717
+ when byte_size ( binary ) < chunk_size ,
718
+ do: [ { :error , "InvalidByteLength" } | results ]
610
719
611
- defp decode_chunk ( binary , chunk_size , basic_type , results ) do
720
+ defp decode_fixed_collection ( binary , chunk_size , basic_type , results ) do
612
721
<< element :: binary - size ( chunk_size ) , rest :: bitstring >> = binary
613
- decode_chunk ( rest , chunk_size , basic_type , [ decode ( element , basic_type ) | results ] )
722
+ decode_fixed_collection ( rest , chunk_size , basic_type , [ decode ( element , basic_type ) | results ] )
614
723
end
615
724
616
725
defp flatten_results ( results ) do
@@ -645,7 +754,8 @@ defmodule LambdaEthereumConsensus.SszEx do
645
754
defp get_fixed_size ( :bool ) , do: 1
646
755
defp get_fixed_size ( { :int , size } ) , do: div ( size , @ bits_per_byte )
647
756
defp get_fixed_size ( { :bytes , size } ) , do: size
648
- defp get_fixed_size ( { :vector , _ , size } ) , do: size
757
+ defp get_fixed_size ( { :vector , basic_type , size } ) , do: size * get_fixed_size ( basic_type )
758
+ defp get_fixed_size ( { :bitvector , _ } ) , do: 1
649
759
650
760
defp get_fixed_size ( module ) when is_atom ( module ) do
651
761
schemas = module . schema ( )
@@ -656,10 +766,12 @@ defmodule LambdaEthereumConsensus.SszEx do
656
766
end
657
767
658
768
defp variable_size? ( { :list , _ , _ } ) , do: true
659
- defp variable_size? ( { :vector , _ , _ } ) , do: false
660
769
defp variable_size? ( :bool ) , do: false
661
770
defp variable_size? ( { :int , _ } ) , do: false
662
771
defp variable_size? ( { :bytes , _ } ) , do: false
772
+ defp variable_size? ( { :bitlist , _ } ) , do: true
773
+ defp variable_size? ( { :bitvector , _ } ) , do: false
774
+ defp variable_size? ( { :vector , basic_type , _ } ) , do: variable_size? ( basic_type )
663
775
664
776
defp variable_size? ( module ) when is_atom ( module ) do
665
777
module . schema ( )
0 commit comments