File Coverage

lib/Digest/Merkle/SHA256.pm
Criterion Covered Total %
statement 34 34 100.0
branch 4 4 100.0
condition n/a
subroutine 7 7 100.0
pod 1 1 100.0
total 46 46 100.0


line stmt bran cond sub pod time code
1 1     1   257248 use v5.40;
  1         4  
2 1     1   8 use feature 'class';
  1         3  
  1         208  
3 1     1   8 no warnings 'experimental::class';
  1         8  
  1         160  
4             #
5             class Digest::Merkle::SHA256 v1.0.0 {
6 1     1   883 use Digest::SHA qw[sha256];
  1         4612  
  1         104  
7 1     1   8 use Carp qw[croak];
  1         2  
  1         2441  
8             field $file_size : param : reader;
9             field $block_size : param : reader //= 16384; # 16KiB
10             field $height : reader;
11             field $node_count : reader;
12             field $nodes : reader(dump_state) : writer(load_state); # {level}{index} => hash
13              
14             #
15             my @zero_hashes; # Shared cache for zero hashes across all instances
16              
17 29     29   28 sub _zero_hash ($h) {
  29         24  
  29         26  
18 29 100       40 $zero_hashes[0] = pack 'H*', '0' x 40 unless @zero_hashes;
19 29         69 for ( my $i = scalar @zero_hashes; $i <= $h; $i++ ) {
20 2         9 $zero_hashes[$i] = sha256( $zero_hashes[ $i - 1 ] . $zero_hashes[ $i - 1 ] );
21             }
22 29         43 $zero_hashes[$h];
23             }
24             ADJUST {
25             if ( $file_size > 0 ) {
26             my $num_blocks = int( ( $file_size + $block_size - 1 ) / $block_size );
27             $height = 0;
28             my $p = 1;
29             while ( $p < $num_blocks ) {
30             $p <<= 1;
31             $height++;
32             }
33             $node_count = $p;
34             }
35             else {
36             $height = 0;
37             $node_count = 0;
38             }
39             }
40             method root () { $self->get_node( 0, 0 ) }
41              
42             method set_block ( $index, $hash ) {
43             croak "Index $index out of bounds (max " . ( $node_count - 1 ) . ")" if $index >= $node_count;
44             $self->_set_node( $height, $index, $hash );
45             }
46              
47             method get_node ( $level, $index ) {
48             return $nodes->{$level}{$index} if exists $nodes->{$level}{$index};
49             _zero_hash( $height - $level );
50             }
51              
52             method _set_node ( $level, $index, $hash ) {
53             $nodes->{$level}{$index} = $hash;
54             if ( $level > 0 ) {
55             my $parent_index = $index >> 1;
56             my $sibling_index = $index ^ 1;
57             my $left = $index % 2 == 0 ? $hash : $self->get_node( $level, $sibling_index );
58             my $right = $index % 2 == 0 ? $self->get_node( $level, $sibling_index ) : $hash;
59             $self->_set_node( $level - 1, $parent_index, sha256( $left . $right ) );
60             }
61             }
62              
63             method get_hashes ( $level, $index, $count ) {
64             croak "Level $level out of bounds" if $level > $height;
65             my $res = '';
66             for my $i ( 0 .. $count ) {
67             $res .= $self->get_node( $level, $index + $i );
68             }
69             $res;
70             }
71              
72             method get_audit_path ($index) {
73             croak "Index $index out of bounds" if $index >= $node_count;
74             my @path;
75             my $current_index = $index;
76             for ( my $level = $height; $level > 0; $level-- ) {
77             my $sibling_index = $current_index ^ 1;
78             push @path, $self->get_node( $level, $sibling_index );
79             $current_index >>= 1;
80             }
81             \@path;
82             }
83              
84             method get_layer ($layer_height) {
85             croak "Layer height $layer_height out of bounds" if $layer_height > $height;
86             my $num_nodes = 1 << $layer_height;
87             my $layer = "";
88             for ( my $i = 0; $i < $num_nodes; $i++ ) {
89             $layer .= $self->get_node( $layer_height, $i );
90             }
91             $layer;
92             }
93              
94             method get_piece_layer ($piece_size) {
95             my $k = 0;
96             my $tmp = $piece_size / $block_size;
97             croak "piece_size must be a power of two and >= block_size" if $tmp < 1 || ( $tmp & ( $tmp - 1 ) ) != 0;
98             while ( $tmp > 1 ) {
99             $tmp >>= 1;
100             $k++;
101             }
102             $height >= $k ? $self->get_layer( $height - $k ) : $self->root;
103             }
104              
105 2     2 1 494 sub verify_hash ( $s, $index, $hash, $audit_path, $expected_root ) {
  2         3  
  2         4  
  2         3  
  2         3  
  2         2  
  2         3  
106 2         3 my $current_hash = $hash;
107 2         9 my $current_index = $index;
108 2         4 for my $sibling_hash (@$audit_path) {
109 6 100       58 $current_hash = $current_index % 2 == 0 ? sha256( $current_hash . $sibling_hash ) : sha256( $sibling_hash . $current_hash );
110 6         11 $current_index >>= 1;
111             }
112 2         12 $current_hash eq $expected_root;
113             }
114             };
115             1;