File Coverage

blib/lib/Ordeal/Model/ChaCha20.pm
Criterion Covered Total %
statement 236 279 84.5
branch 8 18 44.4
condition 2 3 66.6
subroutine 22 26 84.6
pod 6 6 100.0
total 274 332 82.5


line stmt bran cond sub pod time code
1             package Ordeal::Model::ChaCha20;
2              
3             # vim: ts=3 sts=3 sw=3 et ai :
4              
5             # Adapted from Math::Prime::Util::ChaCha 0.70
6             # https://metacpan.org/pod/Math::Prime::Util::ChaCha
7             # which is copyright 2017 by Dana Jacobsen E<lt>dana@acm.orgE<gt>
8              
9 5     5   90 use 5.020;
  5         18  
10 5     5   28 use strict;
  5         9  
  5         110  
11 5     5   24 use warnings;
  5         10  
  5         245  
12             { our $VERSION = '0.003'; }
13 5     5   30 use Ouch;
  5         9  
  5         29  
14 5     5   366 use Mo qw< build default >;
  5         9  
  5         26  
15 5     5   5590 use experimental qw< signatures postderef >;
  5         7124  
  5         38  
16 5     5   881 no warnings qw< experimental::signatures experimental::postderef >;
  5         12  
  5         214  
17              
18 5     5   28 use constant BITS => (~0 == 4294967295) ? 32 : 64;
  5         10  
  5         354  
19 5     5   28 use constant CACHE_SIZE => 1000;
  5         9  
  5         234  
20 5     5   28 use constant ROUNDS => 20;
  5         45  
  5         368  
21              
22 5     5   32 use constant RELEASE => 0x01; # 0x00 marks extension
  5         9  
  5         8867  
23              
24             has _state => ();
25             has _buffer => ();
26             has seed => ();
27              
28 15     15   19 sub _bits_rand ($self, $n) {
  15         20  
  15         34  
  15         20  
29 15         32 while (length($self->{_buffer}) < $n) {
30 3         11 my $add_on = $self->_core((int($n / 8) + 64) >> 6);
31 3         22 $self->{_buffer} .= unpack 'b*', $add_on;
32             }
33 15         42 return substr $self->{_buffer}, 0, $n, '';
34             } ## end sub _bits_rand
35              
36 3     3 1 126 sub BUILD ($self) {
  3         5  
  3         5  
37 3   66     8 my $seed = $self->seed // do {
38 1         49 my $s = CORE::rand 1_000_000;
39 1 50       13 $s < 4294967295
40             ? pack 'V', $s
41             : pack 'V2', $s, $s >> 32;
42             };
43 3         23 $self->seed($seed);
44 3         18 $self->reset;
45             } ## end sub BUILD ($self)
46              
47 0     0 1 0 sub clone ($self) { return ref($self)->new->restore($self->freeze) }
  0         0  
  0         0  
  0         0  
48              
49 0     0 1 0 sub freeze ($self) {
  0         0  
  0         0  
50 0         0 my $release = unpack 'H*', pack 'C*', RELEASE;
51 0         0 my $state = unpack 'H*', join '', pack 'N*', $self->_state->@*;
52 0         0 my $buffer = $self->_buffer;
53 0         0 my $buflen = unpack 'H*', pack 'N', length $buffer;
54 0         0 $buffer = unpack 'H*', join '', pack 'B*', $buffer;
55 0         0 my $seed = unpack 'H*', substr $self->seed, 0, 40;
56 0         0 return join '', $release, $state, $buflen, $buffer, $seed;
57             }
58              
59 13     13   18 sub _int_rand_parameters ($self, $N) {
  13         15  
  13         18  
  13         18  
60 13         16 state $cache = {};
61 13 50       28 return $cache->{$N}->@* if exists $cache->{$N};
62              
63             # basic parameters, find the minimum number of bits to cover $N
64 13         39 my $nbits = int(log($N) / log(2));
65 13         20 my $M = 2 ** $nbits;
66 13         29 while ($M < $N) {
67 6         11 $nbits++;
68 6         11 $M *= 2;
69             }
70 13         19 my $reject_threshold = $M - $M % $N; # same as $N here
71              
72             # if there is still space in the cache, this pair will be used many
73             # times, so we want to reduce the rejection rate
74 13 50       31 if (keys($cache->%*) <= CACHE_SIZE) {
75 13         30 while (($nbits * $M / $reject_threshold) > ($nbits + 1)) {
76 3         6 $nbits++;
77 3         4 $M *= 2;
78 3         9 $reject_threshold = $M - $M % $N;
79             }
80             }
81 13         29 return ($nbits, $reject_threshold);
82             }
83              
84 13     13 1 17 sub int_rand ($self, $low, $high) {
  13         18  
  13         18  
  13         16  
  13         15  
85 13         29 my $N = $high - $low + 1;
86 13         22 my ($nbits, $reject_threshold) = $self->_int_rand_parameters($N);
87 13         23 my $retval = $reject_threshold;
88 13         24 while ($retval >= $reject_threshold) {
89 15         28 my $bitsequence = $self->_bits_rand($nbits);
90 15         24 $retval = 0;
91 15         49 for my $v (reverse split //, pack 'b*', $bitsequence) {
92 13         24 $retval <<= 8;
93 13         31 $retval += ord $v;
94             }
95             } ## end while ($retval >= $reject_threshold)
96 13         32 return $low + $retval % $N;
97             } ## end sub int_rand
98              
99 3     3 1 5 sub reset ($self) {
  3         5  
  3         4  
100 3         7 my $seed = $self->seed;
101 3         23 $seed .= pack 'C', 0 while length($seed) % 4;
102 3         16 my @seed = unpack 'V*', substr $seed, 0, 40;
103 3 50       17 if (@seed < 10) {
104 3 100       9 my $rng = __prng_new(map { $_ <= $#seed ? $seed[$_] : 0 } 0 .. 3);
  12         48  
105 3         10 push @seed, __prng_next($rng) while @seed < 10;
106             }
107 3 50       20 ouch 500, 'seed count failure', @seed if @seed != 10;
108 3         24 $self->_state(
109             [
110             0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, # 1^ row
111             @seed[0 .. 3], # 2^ row
112             @seed[4 .. 7], # 3^ row
113             0, 0, @seed[8 .. 9], # 4^ row
114             ]
115             );
116 3         16 $self->_buffer('');
117             }
118              
119 0     0   0 sub _restore_01 ($self, $opaque) {
  0         0  
  0         0  
  0         0  
120 0         0 for ($opaque) {
121 0         0 my @state = unpack 'N*', join '', pack 'H*', substr $_, 0, 128, '';
122 0         0 $self->_state(\@state);
123 0         0 s{^-}{}mxs;
124 0         0 my $buflen = unpack 'N', pack 'H*', substr $_, 0, 8, '';
125 0         0 s{^-}{}mxs;
126 0         0 my $buffer = '';
127 0 0       0 if ($buflen) {
128 0         0 my $sl = ($buflen + (8 - $buflen % 8) % 8) / 4; # 2 * ... / 8
129 0         0 $buffer = unpack 'B*', join '', pack 'H*', substr $_, 0, $sl, '';
130 0         0 $buffer = substr $buffer, 0, $buflen;
131             }
132 0         0 $self->_buffer($buffer);
133 0         0 s{^-}{}mxs;
134 0         0 $self->seed(join '', pack 'H*', $_);
135             }
136 0         0 return $self;
137             }
138              
139 0     0 1 0 sub restore ($self, $opaque) {
  0         0  
  0         0  
  0         0  
140 0         0 my $release = substr $opaque, 0, 2, '';
141 0 0       0 my $method = $self->can("_restore_$release")
142             or ouch 400, 'cannot restore release', $release;
143 0         0 $self->$method($opaque);
144 0         0 return $self;
145             }
146              
147             # Simple PRNG used to fill small seeds
148 37     37   42 sub __prng_next ($s) {
  37         72  
  37         43  
149 37         44 my $word;
150 37         45 my $oldstate = $s->[0];
151 37         44 if (BITS == 64) {
152 37         59 $s->[0] = ($s->[0] * 747796405 + $s->[1]) & 0xFFFFFFFF;
153 37         61 $word =
154             ((($oldstate >> (($oldstate >> 28) + 4)) ^ $oldstate) * 277803737)
155             & 0xFFFFFFFF;
156             } ## end if (BITS == 64)
157             else {
158             {
159 5     5   2825 use integer;
  5         73  
  5         35  
160             $s->[0] = unpack("L", pack("L", $s->[0] * 747796405 + $s->[1]));
161             }
162             $word =
163             (($oldstate >> (($oldstate >> 28) + 4)) ^ $oldstate) & 0xFFFFFFFF;
164 5     5   372 { use integer; $word = unpack("L", pack("L", $word * 277803737)); }
  5         47  
  5         24  
165             } ## end else [ if (BITS == 64) ]
166 37         88 ($word >> 22) ^ $word;
167             } ## end sub __prng_next ($s)
168              
169 3     3   5 sub __prng_new ($A, $B, $C, $D) {
  3         5  
  3         5  
  3         3  
  3         4  
  3         6  
170 3         10 my @s = (0, (($B << 1) | 1) & 0xFFFFFFFF);
171 3         9 __prng_next(\@s);
172 3         8 $s[0] = ($s[0] + $A) & 0xFFFFFFFF;
173 3         8 __prng_next(\@s);
174 3         8 $s[0] = ($s[0] ^ $C) & 0xFFFFFFFF;
175 3         7 __prng_next(\@s);
176 3         6 $s[0] = ($s[0] ^ $D) & 0xFFFFFFFF;
177 3         6 __prng_next(\@s);
178 3         11 return \@s;
179             } ## end sub __prng_new
180              
181             ###############################################################################
182             # Begin ChaCha core, reference RFC 7539
183             # with change to make blockcount/nonce be 64/64 from 32/96
184             # Dana Jacobsen, 9 Apr 2017
185             # Adapted Flavio Poletti, 3 Feb 2018
186              
187             # State is:
188             # cccccccc cccccccc cccccccc cccccccc
189             # kkkkkkkk kkkkkkkk kkkkkkkk kkkkkkkk
190             # kkkkkkkk kkkkkkkk kkkkkkkk kkkkkkkk
191             # bbbbbbbb nnnnnnnn nnnnnnnn nnnnnnnn
192             #
193             # c=constant k=key b=blockcount n=nonce
194              
195             # We have to take care with 32-bit Perl so it sticks with integers.
196             # Unfortunately the pragma "use integer" means signed integer so
197             # it ruins right shifts. We also must ensure we save as unsigned.
198              
199 3     3   5 sub _core ($self, $blocks) {
  3         4  
  3         4  
  3         6  
200 3         10 my $j = $self->_state;
201 3         14 my $ks = '';
202              
203 3         9 while ($blocks-- > 0) {
204             my (
205 3         9 $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7,
206             $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15
207             ) = @$j;
208 3         8 for (1 .. ROUNDS / 2) {
209 5     5   1649 use integer;
  5         10  
  5         26  
210 30         40 if (BITS == 64) {
211 30         43 $x0 = ($x0 + $x4) & 0xFFFFFFFF;
212 30         35 $x12 ^= $x0;
213 30         49 $x12 = (($x12 << 16) | ($x12 >> 16)) & 0xFFFFFFFF;
214 30         34 $x8 = ($x8 + $x12) & 0xFFFFFFFF;
215 30         42 $x4 ^= $x8;
216 30         47 $x4 = (($x4 << 12) | ($x4 >> 20)) & 0xFFFFFFFF;
217 30         35 $x0 = ($x0 + $x4) & 0xFFFFFFFF;
218 30         42 $x12 ^= $x0;
219 30         41 $x12 = (($x12 << 8) | ($x12 >> 24)) & 0xFFFFFFFF;
220 30         40 $x8 = ($x8 + $x12) & 0xFFFFFFFF;
221 30         39 $x4 ^= $x8;
222 30         47 $x4 = (($x4 << 7) | ($x4 >> 25)) & 0xFFFFFFFF;
223 30         48 $x1 = ($x1 + $x5) & 0xFFFFFFFF;
224 30         31 $x13 ^= $x1;
225 30         45 $x13 = (($x13 << 16) | ($x13 >> 16)) & 0xFFFFFFFF;
226 30         37 $x9 = ($x9 + $x13) & 0xFFFFFFFF;
227 30         41 $x5 ^= $x9;
228 30         43 $x5 = (($x5 << 12) | ($x5 >> 20)) & 0xFFFFFFFF;
229 30         39 $x1 = ($x1 + $x5) & 0xFFFFFFFF;
230 30         42 $x13 ^= $x1;
231 30         41 $x13 = (($x13 << 8) | ($x13 >> 24)) & 0xFFFFFFFF;
232 30         43 $x9 = ($x9 + $x13) & 0xFFFFFFFF;
233 30         41 $x5 ^= $x9;
234 30         39 $x5 = (($x5 << 7) | ($x5 >> 25)) & 0xFFFFFFFF;
235 30         43 $x2 = ($x2 + $x6) & 0xFFFFFFFF;
236 30         37 $x14 ^= $x2;
237 30         45 $x14 = (($x14 << 16) | ($x14 >> 16)) & 0xFFFFFFFF;
238 30         36 $x10 = ($x10 + $x14) & 0xFFFFFFFF;
239 30         40 $x6 ^= $x10;
240 30         46 $x6 = (($x6 << 12) | ($x6 >> 20)) & 0xFFFFFFFF;
241 30         37 $x2 = ($x2 + $x6) & 0xFFFFFFFF;
242 30         42 $x14 ^= $x2;
243 30         39 $x14 = (($x14 << 8) | ($x14 >> 24)) & 0xFFFFFFFF;
244 30         37 $x10 = ($x10 + $x14) & 0xFFFFFFFF;
245 30         41 $x6 ^= $x10;
246 30         39 $x6 = (($x6 << 7) | ($x6 >> 25)) & 0xFFFFFFFF;
247 30         43 $x3 = ($x3 + $x7) & 0xFFFFFFFF;
248 30         35 $x15 ^= $x3;
249 30         43 $x15 = (($x15 << 16) | ($x15 >> 16)) & 0xFFFFFFFF;
250 30         39 $x11 = ($x11 + $x15) & 0xFFFFFFFF;
251 30         38 $x7 ^= $x11;
252 30         42 $x7 = (($x7 << 12) | ($x7 >> 20)) & 0xFFFFFFFF;
253 30         38 $x3 = ($x3 + $x7) & 0xFFFFFFFF;
254 30         41 $x15 ^= $x3;
255 30         37 $x15 = (($x15 << 8) | ($x15 >> 24)) & 0xFFFFFFFF;
256 30         40 $x11 = ($x11 + $x15) & 0xFFFFFFFF;
257 30         38 $x7 ^= $x11;
258 30         41 $x7 = (($x7 << 7) | ($x7 >> 25)) & 0xFFFFFFFF;
259 30         71 $x0 = ($x0 + $x5) & 0xFFFFFFFF;
260 30         33 $x15 ^= $x0;
261 30         49 $x15 = (($x15 << 16) | ($x15 >> 16)) & 0xFFFFFFFF;
262 30         35 $x10 = ($x10 + $x15) & 0xFFFFFFFF;
263 30         40 $x5 ^= $x10;
264 30         45 $x5 = (($x5 << 12) | ($x5 >> 20)) & 0xFFFFFFFF;
265 30         45 $x0 = ($x0 + $x5) & 0xFFFFFFFF;
266 30         40 $x15 ^= $x0;
267 30         38 $x15 = (($x15 << 8) | ($x15 >> 24)) & 0xFFFFFFFF;
268 30         44 $x10 = ($x10 + $x15) & 0xFFFFFFFF;
269 30         34 $x5 ^= $x10;
270 30         43 $x5 = (($x5 << 7) | ($x5 >> 25)) & 0xFFFFFFFF;
271 30         39 $x1 = ($x1 + $x6) & 0xFFFFFFFF;
272 30         39 $x12 ^= $x1;
273 30         40 $x12 = (($x12 << 16) | ($x12 >> 16)) & 0xFFFFFFFF;
274 30         38 $x11 = ($x11 + $x12) & 0xFFFFFFFF;
275 30         41 $x6 ^= $x11;
276 30         40 $x6 = (($x6 << 12) | ($x6 >> 20)) & 0xFFFFFFFF;
277 30         41 $x1 = ($x1 + $x6) & 0xFFFFFFFF;
278 30         37 $x12 ^= $x1;
279 30         44 $x12 = (($x12 << 8) | ($x12 >> 24)) & 0xFFFFFFFF;
280 30         46 $x11 = ($x11 + $x12) & 0xFFFFFFFF;
281 30         37 $x6 ^= $x11;
282 30         76 $x6 = (($x6 << 7) | ($x6 >> 25)) & 0xFFFFFFFF;
283 30         36 $x2 = ($x2 + $x7) & 0xFFFFFFFF;
284 30         41 $x13 ^= $x2;
285 30         38 $x13 = (($x13 << 16) | ($x13 >> 16)) & 0xFFFFFFFF;
286 30         42 $x8 = ($x8 + $x13) & 0xFFFFFFFF;
287 30         37 $x7 ^= $x8;
288 30         42 $x7 = (($x7 << 12) | ($x7 >> 20)) & 0xFFFFFFFF;
289 30         45 $x2 = ($x2 + $x7) & 0xFFFFFFFF;
290 30         37 $x13 ^= $x2;
291 30         41 $x13 = (($x13 << 8) | ($x13 >> 24)) & 0xFFFFFFFF;
292 30         43 $x8 = ($x8 + $x13) & 0xFFFFFFFF;
293 30         38 $x7 ^= $x8;
294 30         41 $x7 = (($x7 << 7) | ($x7 >> 25)) & 0xFFFFFFFF;
295 30         38 $x3 = ($x3 + $x4) & 0xFFFFFFFF;
296 30         43 $x14 ^= $x3;
297 30         42 $x14 = (($x14 << 16) | ($x14 >> 16)) & 0xFFFFFFFF;
298 30         35 $x9 = ($x9 + $x14) & 0xFFFFFFFF;
299 30         42 $x4 ^= $x9;
300 30         46 $x4 = (($x4 << 12) | ($x4 >> 20)) & 0xFFFFFFFF;
301 30         40 $x3 = ($x3 + $x4) & 0xFFFFFFFF;
302 30         42 $x14 ^= $x3;
303 30         40 $x14 = (($x14 << 8) | ($x14 >> 24)) & 0xFFFFFFFF;
304 30         40 $x9 = ($x9 + $x14) & 0xFFFFFFFF;
305 30         38 $x4 ^= $x9;
306 30         51 $x4 = (($x4 << 7) | ($x4 >> 25)) & 0xFFFFFFFF;
307             } ## end if (BITS == 64)
308             else { # 32-bit
309             $x0 += $x4;
310             $x12 ^= $x0;
311             $x12 = ($x12 << 16) | (($x12 >> 16) & 0xFFFF);
312             $x8 += $x12;
313             $x4 ^= $x8;
314             $x4 = ($x4 << 12) | (($x4 >> 20) & 0xFFF);
315             $x0 += $x4;
316             $x12 ^= $x0;
317             $x12 = ($x12 << 8) | (($x12 >> 24) & 0xFF);
318             $x8 += $x12;
319             $x4 ^= $x8;
320             $x4 = ($x4 << 7) | (($x4 >> 25) & 0x7F);
321             $x1 += $x5;
322             $x13 ^= $x1;
323             $x13 = ($x13 << 16) | (($x13 >> 16) & 0xFFFF);
324             $x9 += $x13;
325             $x5 ^= $x9;
326             $x5 = ($x5 << 12) | (($x5 >> 20) & 0xFFF);
327             $x1 += $x5;
328             $x13 ^= $x1;
329             $x13 = ($x13 << 8) | (($x13 >> 24) & 0xFF);
330             $x9 += $x13;
331             $x5 ^= $x9;
332             $x5 = ($x5 << 7) | (($x5 >> 25) & 0x7F);
333             $x2 += $x6;
334             $x14 ^= $x2;
335             $x14 = ($x14 << 16) | (($x14 >> 16) & 0xFFFF);
336             $x10 += $x14;
337             $x6 ^= $x10;
338             $x6 = ($x6 << 12) | (($x6 >> 20) & 0xFFF);
339             $x2 += $x6;
340             $x14 ^= $x2;
341             $x14 = ($x14 << 8) | (($x14 >> 24) & 0xFF);
342             $x10 += $x14;
343             $x6 ^= $x10;
344             $x6 = ($x6 << 7) | (($x6 >> 25) & 0x7F);
345             $x3 += $x7;
346             $x15 ^= $x3;
347             $x15 = ($x15 << 16) | (($x15 >> 16) & 0xFFFF);
348             $x11 += $x15;
349             $x7 ^= $x11;
350             $x7 = ($x7 << 12) | (($x7 >> 20) & 0xFFF);
351             $x3 += $x7;
352             $x15 ^= $x3;
353             $x15 = ($x15 << 8) | (($x15 >> 24) & 0xFF);
354             $x11 += $x15;
355             $x7 ^= $x11;
356             $x7 = ($x7 << 7) | (($x7 >> 25) & 0x7F);
357             $x0 += $x5;
358             $x15 ^= $x0;
359             $x15 = ($x15 << 16) | (($x15 >> 16) & 0xFFFF);
360             $x10 += $x15;
361             $x5 ^= $x10;
362             $x5 = ($x5 << 12) | (($x5 >> 20) & 0xFFF);
363             $x0 += $x5;
364             $x15 ^= $x0;
365             $x15 = ($x15 << 8) | (($x15 >> 24) & 0xFF);
366             $x10 += $x15;
367             $x5 ^= $x10;
368             $x5 = ($x5 << 7) | (($x5 >> 25) & 0x7F);
369             $x1 += $x6;
370             $x12 ^= $x1;
371             $x12 = ($x12 << 16) | (($x12 >> 16) & 0xFFFF);
372             $x11 += $x12;
373             $x6 ^= $x11;
374             $x6 = ($x6 << 12) | (($x6 >> 20) & 0xFFF);
375             $x1 += $x6;
376             $x12 ^= $x1;
377             $x12 = ($x12 << 8) | (($x12 >> 24) & 0xFF);
378             $x11 += $x12;
379             $x6 ^= $x11;
380             $x6 = ($x6 << 7) | (($x6 >> 25) & 0x7F);
381             $x2 += $x7;
382             $x13 ^= $x2;
383             $x13 = ($x13 << 16) | (($x13 >> 16) & 0xFFFF);
384             $x8 += $x13;
385             $x7 ^= $x8;
386             $x7 = ($x7 << 12) | (($x7 >> 20) & 0xFFF);
387             $x2 += $x7;
388             $x13 ^= $x2;
389             $x13 = ($x13 << 8) | (($x13 >> 24) & 0xFF);
390             $x8 += $x13;
391             $x7 ^= $x8;
392             $x7 = ($x7 << 7) | (($x7 >> 25) & 0x7F);
393             $x3 += $x4;
394             $x14 ^= $x3;
395             $x14 = ($x14 << 16) | (($x14 >> 16) & 0xFFFF);
396             $x9 += $x14;
397             $x4 ^= $x9;
398             $x4 = ($x4 << 12) | (($x4 >> 20) & 0xFFF);
399             $x3 += $x4;
400             $x14 ^= $x3;
401             $x14 = ($x14 << 8) | (($x14 >> 24) & 0xFF);
402             $x9 += $x14;
403             $x4 ^= $x9;
404             $x4 = ($x4 << 7) | (($x4 >> 25) & 0x7F);
405             } ## end else [ if (BITS == 64) ]
406             } ## end for (1 .. ROUNDS / 2)
407 3         44 $ks .= pack("V16",
408             $x0 + $j->[0],
409             $x1 + $j->[1],
410             $x2 + $j->[2],
411             $x3 + $j->[3],
412             $x4 + $j->[4],
413             $x5 + $j->[5],
414             $x6 + $j->[6],
415             $x7 + $j->[7],
416             $x8 + $j->[8],
417             $x9 + $j->[9],
418             $x10 + $j->[10],
419             $x11 + $j->[11],
420             $x12 + $j->[12],
421             $x13 + $j->[13],
422             $x14 + $j->[14],
423             $x15 + $j->[15]);
424 3 50       19 if (++$j->[12] > 4294967295) {
425 0         0 $j->[12] = 0;
426 0         0 $j->[13]++;
427             }
428             } ## end while ($blocks-- > 0)
429 3         9 return $ks;
430             } ## end sub _core
431              
432             # End ChaCha core
433             ###############################################################################
434              
435             1;
436             __END__