File Coverage

blib/lib/Math/Prime/Util/ChaCha.pm
Criterion Covered Total %
statement 176 211 83.4
branch 15 34 44.1
condition n/a
subroutine 17 23 73.9
pod 5 5 100.0
total 213 273 78.0


line stmt bran cond sub pod time code
1             package Math::Prime::Util::ChaCha;
2 1     1   6 use strict;
  1         2  
  1         37  
3 1     1   4 use warnings;
  1         1  
  1         67  
4 1     1   5 use Carp qw/carp croak confess/;
  1         2  
  1         92  
5              
6             BEGIN {
7 1     1   3 $Math::Prime::Util::ChaCha::AUTHORITY = 'cpan:DANAJ';
8 1         34 $Math::Prime::Util::ChaCha::VERSION = '0.74';
9             }
10              
11             ###############################################################################
12             # Begin ChaCha core, reference RFC 7539
13             # with change to make blockcount/nonce be 64/64 from 32/96
14             # Dana Jacobsen, 9 Apr 2017
15              
16 0         0 BEGIN {
17 1     1   5 use constant ROUNDS => 20;
  1         2  
  1         81  
18 1     1   5 use constant BUFSZ => 1024;
  1         2  
  1         66  
19 1     1   5 use constant BITS => (~0 == 4294967295) ? 32 : 64;
  1     0   1  
  1         165  
20             }
21              
22             # State is:
23             # cccccccc cccccccc cccccccc cccccccc
24             # kkkkkkkk kkkkkkkk kkkkkkkk kkkkkkkk
25             # kkkkkkkk kkkkkkkk kkkkkkkk kkkkkkkk
26             # bbbbbbbb nnnnnnnn nnnnnnnn nnnnnnnn
27             #
28             # c=constant k=key b=blockcount n=nonce
29              
30             # We have to take care with 32-bit Perl so it sticks with integers.
31             # Unfortunately the pragma "use integer" means signed integer so
32             # it ruins right shifts. We also must ensure we save as unsigned.
33              
34             sub _core {
35 3     3   8 my($j, $blocks) = @_;
36 3         8 my $ks = '';
37 3 100       9 $blocks = 1 unless defined $blocks;
38              
39 3         9 while ($blocks-- > 0) {
40 19         71 my($x0,$x1,$x2,$x3,$x4,$x5,$x6,$x7,$x8,$x9,$x10,$x11,$x12,$x13,$x14,$x15) = @$j;
41 19         54 for (1 .. ROUNDS/2) {
42 1     1   7 use integer;
  1         2  
  1         9  
43 190         313 if (BITS == 64) {
44 190         289 $x0 =($x0 +$x4 )&0xFFFFFFFF; $x12^=$x0 ; $x12=(($x12<<16)|($x12>>16))&0xFFFFFFFF;
  190         288  
  190         319  
45 190         290 $x8 =($x8 +$x12)&0xFFFFFFFF; $x4 ^=$x8 ; $x4 =(($x4 <<12)|($x4 >>20))&0xFFFFFFFF;
  190         305  
  190         314  
46 190         345 $x0 =($x0 +$x4 )&0xFFFFFFFF; $x12^=$x0 ; $x12=(($x12<< 8)|($x12>>24))&0xFFFFFFFF;
  190         278  
  190         324  
47 190         309 $x8 =($x8 +$x12)&0xFFFFFFFF; $x4 ^=$x8 ; $x4 =(($x4 << 7)|($x4 >>25))&0xFFFFFFFF;
  190         260  
  190         333  
48 190         305 $x1 =($x1 +$x5 )&0xFFFFFFFF; $x13^=$x1 ; $x13=(($x13<<16)|($x13>>16))&0xFFFFFFFF;
  190         270  
  190         290  
49 190         336 $x9 =($x9 +$x13)&0xFFFFFFFF; $x5 ^=$x9 ; $x5 =(($x5 <<12)|($x5 >>20))&0xFFFFFFFF;
  190         304  
  190         334  
50 190         272 $x1 =($x1 +$x5 )&0xFFFFFFFF; $x13^=$x1 ; $x13=(($x13<< 8)|($x13>>24))&0xFFFFFFFF;
  190         277  
  190         338  
51 190         287 $x9 =($x9 +$x13)&0xFFFFFFFF; $x5 ^=$x9 ; $x5 =(($x5 << 7)|($x5 >>25))&0xFFFFFFFF;
  190         274  
  190         313  
52 190         308 $x2 =($x2 +$x6 )&0xFFFFFFFF; $x14^=$x2 ; $x14=(($x14<<16)|($x14>>16))&0xFFFFFFFF;
  190         298  
  190         300  
53 190         312 $x10=($x10+$x14)&0xFFFFFFFF; $x6 ^=$x10; $x6 =(($x6 <<12)|($x6 >>20))&0xFFFFFFFF;
  190         274  
  190         321  
54 190         306 $x2 =($x2 +$x6 )&0xFFFFFFFF; $x14^=$x2 ; $x14=(($x14<< 8)|($x14>>24))&0xFFFFFFFF;
  190         286  
  190         322  
55 190         304 $x10=($x10+$x14)&0xFFFFFFFF; $x6 ^=$x10; $x6 =(($x6 << 7)|($x6 >>25))&0xFFFFFFFF;
  190         321  
  190         392  
56 190         271 $x3 =($x3 +$x7 )&0xFFFFFFFF; $x15^=$x3 ; $x15=(($x15<<16)|($x15>>16))&0xFFFFFFFF;
  190         270  
  190         296  
57 190         276 $x11=($x11+$x15)&0xFFFFFFFF; $x7 ^=$x11; $x7 =(($x7 <<12)|($x7 >>20))&0xFFFFFFFF;
  190         272  
  190         344  
58 190         285 $x3 =($x3 +$x7 )&0xFFFFFFFF; $x15^=$x3 ; $x15=(($x15<< 8)|($x15>>24))&0xFFFFFFFF;
  190         261  
  190         330  
59 190         315 $x11=($x11+$x15)&0xFFFFFFFF; $x7 ^=$x11; $x7 =(($x7 << 7)|($x7 >>25))&0xFFFFFFFF;
  190         309  
  190         310  
60 190         270 $x0 =($x0 +$x5 )&0xFFFFFFFF; $x15^=$x0 ; $x15=(($x15<<16)|($x15>>16))&0xFFFFFFFF;
  190         292  
  190         297  
61 190         346 $x10=($x10+$x15)&0xFFFFFFFF; $x5 ^=$x10; $x5 =(($x5 <<12)|($x5 >>20))&0xFFFFFFFF;
  190         285  
  190         341  
62 190         287 $x0 =($x0 +$x5 )&0xFFFFFFFF; $x15^=$x0 ; $x15=(($x15<< 8)|($x15>>24))&0xFFFFFFFF;
  190         301  
  190         1510  
63 190         310 $x10=($x10+$x15)&0xFFFFFFFF; $x5 ^=$x10; $x5 =(($x5 << 7)|($x5 >>25))&0xFFFFFFFF;
  190         260  
  190         304  
64 190         281 $x1 =($x1 +$x6 )&0xFFFFFFFF; $x12^=$x1 ; $x12=(($x12<<16)|($x12>>16))&0xFFFFFFFF;
  190         314  
  190         309  
65 190         279 $x11=($x11+$x12)&0xFFFFFFFF; $x6 ^=$x11; $x6 =(($x6 <<12)|($x6 >>20))&0xFFFFFFFF;
  190         295  
  190         299  
66 190         282 $x1 =($x1 +$x6 )&0xFFFFFFFF; $x12^=$x1 ; $x12=(($x12<< 8)|($x12>>24))&0xFFFFFFFF;
  190         295  
  190         304  
67 190         300 $x11=($x11+$x12)&0xFFFFFFFF; $x6 ^=$x11; $x6 =(($x6 << 7)|($x6 >>25))&0xFFFFFFFF;
  190         272  
  190         342  
68 190         316 $x2 =($x2 +$x7 )&0xFFFFFFFF; $x13^=$x2 ; $x13=(($x13<<16)|($x13>>16))&0xFFFFFFFF;
  190         285  
  190         347  
69 190         329 $x8 =($x8 +$x13)&0xFFFFFFFF; $x7 ^=$x8 ; $x7 =(($x7 <<12)|($x7 >>20))&0xFFFFFFFF;
  190         295  
  190         315  
70 190         295 $x2 =($x2 +$x7 )&0xFFFFFFFF; $x13^=$x2 ; $x13=(($x13<< 8)|($x13>>24))&0xFFFFFFFF;
  190         282  
  190         314  
71 190         313 $x8 =($x8 +$x13)&0xFFFFFFFF; $x7 ^=$x8 ; $x7 =(($x7 << 7)|($x7 >>25))&0xFFFFFFFF;
  190         279  
  190         322  
72 190         288 $x3 =($x3 +$x4 )&0xFFFFFFFF; $x14^=$x3 ; $x14=(($x14<<16)|($x14>>16))&0xFFFFFFFF;
  190         306  
  190         310  
73 190         290 $x9 =($x9 +$x14)&0xFFFFFFFF; $x4 ^=$x9 ; $x4 =(($x4 <<12)|($x4 >>20))&0xFFFFFFFF;
  190         272  
  190         368  
74 190         289 $x3 =($x3 +$x4 )&0xFFFFFFFF; $x14^=$x3 ; $x14=(($x14<< 8)|($x14>>24))&0xFFFFFFFF;
  190         327  
  190         301  
75 190         293 $x9 =($x9 +$x14)&0xFFFFFFFF; $x4 ^=$x9 ; $x4 =(($x4 << 7)|($x4 >>25))&0xFFFFFFFF;
  190         1638  
  190         448  
76             } else { # 32-bit
77             $x0 +=$x4 ; $x12^=$x0 ; $x12=($x12<<16)|(($x12>>16)& 0xFFFF);
78             $x8 +=$x12; $x4 ^=$x8 ; $x4 =($x4 <<12)|(($x4 >>20)& 0xFFF);
79             $x0 +=$x4 ; $x12^=$x0 ; $x12=($x12<< 8)|(($x12>>24)& 0xFF);
80             $x8 +=$x12; $x4 ^=$x8 ; $x4 =($x4 << 7)|(($x4 >>25)& 0x7F);
81             $x1 +=$x5 ; $x13^=$x1 ; $x13=($x13<<16)|(($x13>>16)& 0xFFFF);
82             $x9 +=$x13; $x5 ^=$x9 ; $x5 =($x5 <<12)|(($x5 >>20)& 0xFFF);
83             $x1 +=$x5 ; $x13^=$x1 ; $x13=($x13<< 8)|(($x13>>24)& 0xFF);
84             $x9 +=$x13; $x5 ^=$x9 ; $x5 =($x5 << 7)|(($x5 >>25)& 0x7F);
85             $x2 +=$x6 ; $x14^=$x2 ; $x14=($x14<<16)|(($x14>>16)& 0xFFFF);
86             $x10+=$x14; $x6 ^=$x10; $x6 =($x6 <<12)|(($x6 >>20)& 0xFFF);
87             $x2 +=$x6 ; $x14^=$x2 ; $x14=($x14<< 8)|(($x14>>24)& 0xFF);
88             $x10+=$x14; $x6 ^=$x10; $x6 =($x6 << 7)|(($x6 >>25)& 0x7F);
89             $x3 +=$x7 ; $x15^=$x3 ; $x15=($x15<<16)|(($x15>>16)& 0xFFFF);
90             $x11+=$x15; $x7 ^=$x11; $x7 =($x7 <<12)|(($x7 >>20)& 0xFFF);
91             $x3 +=$x7 ; $x15^=$x3 ; $x15=($x15<< 8)|(($x15>>24)& 0xFF);
92             $x11+=$x15; $x7 ^=$x11; $x7 =($x7 << 7)|(($x7 >>25)& 0x7F);
93             $x0 +=$x5 ; $x15^=$x0 ; $x15=($x15<<16)|(($x15>>16)& 0xFFFF);
94             $x10+=$x15; $x5 ^=$x10; $x5 =($x5 <<12)|(($x5 >>20)& 0xFFF);
95             $x0 +=$x5 ; $x15^=$x0 ; $x15=($x15<< 8)|(($x15>>24)& 0xFF);
96             $x10+=$x15; $x5 ^=$x10; $x5 =($x5 << 7)|(($x5 >>25)& 0x7F);
97             $x1 +=$x6 ; $x12^=$x1 ; $x12=($x12<<16)|(($x12>>16)& 0xFFFF);
98             $x11+=$x12; $x6 ^=$x11; $x6 =($x6 <<12)|(($x6 >>20)& 0xFFF);
99             $x1 +=$x6 ; $x12^=$x1 ; $x12=($x12<< 8)|(($x12>>24)& 0xFF);
100             $x11+=$x12; $x6 ^=$x11; $x6 =($x6 << 7)|(($x6 >>25)& 0x7F);
101             $x2 +=$x7 ; $x13^=$x2 ; $x13=($x13<<16)|(($x13>>16)& 0xFFFF);
102             $x8 +=$x13; $x7 ^=$x8 ; $x7 =($x7 <<12)|(($x7 >>20)& 0xFFF);
103             $x2 +=$x7 ; $x13^=$x2 ; $x13=($x13<< 8)|(($x13>>24)& 0xFF);
104             $x8 +=$x13; $x7 ^=$x8 ; $x7 =($x7 << 7)|(($x7 >>25)& 0x7F);
105             $x3 +=$x4 ; $x14^=$x3 ; $x14=($x14<<16)|(($x14>>16)& 0xFFFF);
106             $x9 +=$x14; $x4 ^=$x9 ; $x4 =($x4 <<12)|(($x4 >>20)& 0xFFF);
107             $x3 +=$x4 ; $x14^=$x3 ; $x14=($x14<< 8)|(($x14>>24)& 0xFF);
108             $x9 +=$x14; $x4 ^=$x9 ; $x4 =($x4 << 7)|(($x4 >>25)& 0x7F);
109             }
110             }
111 19         172 $ks .= pack("V16",$x0 +$j->[ 0],$x1 +$j->[ 1],$x2 +$j->[ 2],$x3 +$j->[ 3],
112             $x4 +$j->[ 4],$x5 +$j->[ 5],$x6 +$j->[ 6],$x7 +$j->[ 7],
113             $x8 +$j->[ 8],$x9 +$j->[ 9],$x10+$j->[10],$x11+$j->[11],
114             $x12+$j->[12],$x13+$j->[13],$x14+$j->[14],$x15+$j->[15]);
115 19 50       103 if (++$j->[12] > 4294967295) {
116 0         0 $j->[12] = 0;
117 0         0 $j->[13]++;
118             }
119             }
120 3         21 $ks;
121             }
122             sub _test_core {
123 1     1   2 return unless ROUNDS == 20;
124 1         3 my $init_state = '617078653320646e79622d326b20657403020100070605040b0a09080f0e0d0c13121110171615141b1a19181f1e1d1c00000001090000004a00000000000000';
125 1         7 my @state = map { hex("0x$_") } unpack "a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8", $init_state;
  16         30  
126 1         4 my $instr = join("",map { sprintf("%08x",$_) } @state);
  16         34  
127 1 50       5 die "Block function fail test 2.3.2 input" unless $instr eq '617078653320646e79622d326b20657403020100070605040b0a09080f0e0d0c13121110171615141b1a19181f1e1d1c00000001090000004a00000000000000';
128 1         3 my @out = unpack("V16", _core(\@state));
129 1         3 my $outstr = join("",map { sprintf("%08x",$_) } @out);
  16         34  
130             #printf " %08x %08x %08x %08x\n %08x %08x %08x %08x\n %08x %08x %08x %08x\n %08x %08x %08x %08x\n", @state;
131 1 50       6 die "Block function fail test 2.3.2 output" unless $outstr eq 'e4e7f11015593bd11fdd0f50c47120a3c7f4d1c70368c0339aaa22044e6cd4c3466482d209aa9f0705d7c214a2028bd9d19c12b5b94e16dee883d0cb4e3c50a2';
132             }
133             _test_core();
134              
135             # Returns integral number of 64-byte blocks.
136             sub _keystream {
137 2     2   8 my($nbytes, $rstate) = @_;
138 2 50       7 croak "Keystream invalid state" unless scalar(@$rstate) == 16;
139 2         25 _core($rstate, ($nbytes+63) >> 6);
140             }
141             sub _test_keystream {
142 1     1   2 return unless ROUNDS == 20;
143 1         2 my $init_state = '617078653320646e79622d326b20657403020100070605040b0a09080f0e0d0c13121110171615141b1a19181f1e1d1c00000001000000004a00000000000000';
144 1         4 my @state = map { hex("0x$_") } unpack "a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8", $init_state;
  16         26  
145 1         14 my $instr = join("",map { sprintf("%08x",$_) } @state);
  16         32  
146 1 50       4 die "Block function fail test 2.4.2 input" unless $instr eq '617078653320646e79622d326b20657403020100070605040b0a09080f0e0d0c13121110171615141b1a19181f1e1d1c00000001000000004a00000000000000';
147 1         3 my $keystream = _keystream(114, \@state);
148             # Verify new state
149 1         3 my $outstr = join("",map { sprintf("%08x",$_) } @state);
  16         45  
150 1 50       9 die "Block function fail test 2.4.2 output" unless $outstr eq '617078653320646e79622d326b20657403020100070605040b0a09080f0e0d0c13121110171615141b1a19181f1e1d1c00000003000000004a00000000000000';
151 1         6 my $ksstr = unpack("H*",$keystream);
152 1 50       6 die "Block function fail test 2.4.2 keystream" unless substr($ksstr,0,2*114) eq '224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf788a3b0aa372600a92b57974cded2b9334794cba40c63e34cdea212c4cf07d41b769a6749f3f630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53ac40c5945398b6eda1a832c89c167eacd901d7e2bf363';
153             }
154             _test_keystream();
155              
156             # End ChaCha core
157             ###############################################################################
158              
159             # Simple PRNG used to fill small seeds
160             sub _prng_next {
161 0     0   0 my($s) = @_;
162 0         0 my $word;
163 0         0 my $oldstate = $s->[0];
164 0         0 if (BITS == 64) {
165 0         0 $s->[0] = ($s->[0] * 747796405 + $s->[1]) & 0xFFFFFFFF;
166 0         0 $word = ((($oldstate >> (($oldstate >> 28) + 4)) ^ $oldstate) * 277803737) & 0xFFFFFFFF;
167             } else {
168 1     1   1649 { use integer; $s->[0] = unpack("L",pack("L", $s->[0] * 747796405 + $s->[1] )); }
  1         3  
  1         5  
169             $word = (($oldstate >> (($oldstate >> 28) + 4)) ^ $oldstate) & 0xFFFFFFFF;
170 1     1   55 { use integer; $word = unpack("L",pack("L", $word * 277803737)); }
  1         2  
  1         3  
171             }
172 0         0 ($word >> 22) ^ $word;
173             }
174             sub _prng_new {
175 0     0   0 my($a,$b,$c,$d) = @_;
176 0         0 my @s = (0, (($b << 1) | 1) & 0xFFFFFFFF);
177 0         0 _prng_next(\@s);
178 0         0 $s[0] = ($s[0] + $a) & 0xFFFFFFFF;
179 0         0 _prng_next(\@s);
180 0         0 $s[0] = ($s[0] ^ $c) & 0xFFFFFFFF;
181 0         0 _prng_next(\@s);
182 0         0 $s[0] = ($s[0] ^ $d) & 0xFFFFFFFF;
183 0         0 _prng_next(\@s);
184 0         0 \@s;
185             }
186             ###############################################################################
187              
188             # These variables are not accessible outside this file by standard means.
189             {
190             my $_goodseed; # Did we get a long seed
191             my $_state; # the cipher state. 40 bytes user data, 64 total.
192             my $_str; # buffered to-be-sent output.
193              
194 0     0   0 sub _is_csprng_well_seeded { $_goodseed }
195              
196             sub csrand {
197 1     1 1 3 my($seed) = @_;
198 1         3 $_goodseed = length($seed) >= 16;
199 1         3 while (length($seed) % 4) { $seed .= pack("C",0); } # zero pad end word
  0         0  
200 1         7 my @seed = unpack("V*",substr($seed,0,40));
201             # If not enough data, fill rest using simple RNG
202 1 50       4 if ($#seed < 9) {
203 0 0       0 my $rng = _prng_new(map { $_ <= $#seed ? $seed[$_] : 0 } 0..3);
  0         0  
204 0         0 push @seed, _prng_next($rng) while $#seed < 9;
205             }
206 1 50       31 croak "Seed count failure" unless $#seed == 9;
207 1         5 $_state = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
208             @seed[0..7],
209             0, 0, @seed[8..9]];
210 1         3 $_str = '';
211             }
212             sub srand {
213 0     0 1 0 my $seed = shift;
214 0 0       0 $seed = CORE::rand unless defined $seed;
215 0 0       0 if ($seed <= 4294967295) { csrand(pack("V",$seed)); }
  0         0  
216 0         0 else { csrand(pack("V2",$seed,$seed>>32)); }
217 0         0 $seed;
218             }
219             sub irand {
220 152 100   152 1 267 $_str .= _keystream(BUFSZ,$_state) if length($_str) < 4;
221 152         378 return unpack("V",substr($_str, 0, 4, ''));
222             }
223             sub irand64 {
224 0     0 1 0 return irand() if ~0 == 4294967295;
225 0 0       0 $_str .= _keystream(BUFSZ,$_state) if length($_str) < 8;
226 0         0 ($a,$b) = unpack("V2",substr($_str, 0, 8, ''));
227 0         0 return ($a << 32) | $b;
228             }
229             sub random_bytes {
230 3     3 1 10 my($bytes) = @_;
231 3 50       41 $bytes = (defined $bytes) ? int abs $bytes : 0;
232 3 50       10 $_str .= _keystream($bytes-length($_str),$_state) if length($_str) < $bytes;
233 3         14 return substr($_str, 0, $bytes, '');
234             }
235             }
236              
237             1;
238              
239             __END__