Fixes, tests passing.

This commit is contained in:
2023-11-29 21:56:55 +00:00
parent 0b01458a98
commit e063648ecf
5 changed files with 45 additions and 50 deletions
+3 -3
View File
@@ -70,7 +70,7 @@ def _processBuffer(out):
class Cha:
def __init__(self, key: bytes | Any, iv: bytes | Any, *, rounds=8):
def __init__(self, key: bytes | Any, iv: bytes | Any, *, rounds=20):
"""Construct a generator that holds its internal state, moving forward on each call."""
key, iv = _processKeys(key, iv)
self.ctx = ffi.new("cha_ctx*")
@@ -91,7 +91,7 @@ def generate_into(
key: bytes | Any,
iv: bytes | Any = bytes(16),
*,
rounds=8,
rounds=20,
):
"""Fill in random bytes into an existing array (buffer interface)"""
key, iv = _processKeys(key, iv)
@@ -100,7 +100,7 @@ def generate_into(
return out
def generate(outlen: int, key: bytes | Any, iv: bytes | Any = bytes(16), *, rounds=8):
def generate(outlen: int, key: bytes | Any, iv: bytes | Any = bytes(16), *, rounds=20):
"""Return a bytearray of random bytes"""
assert outlen >= 0
return generate_into(bytearray(outlen), key, iv, rounds=rounds)
+9 -7
View File
@@ -4,7 +4,7 @@
#elif defined(__aarch64__)
#include "sse2neon.h"
#endif
#include <stdio.h>
// clang-format off
#define VEC4_ROT(A, IMM) \
@@ -45,12 +45,12 @@
_mm_storeu_si128((__m128i*)(OUT + 192), x[D]); \
}
#define COUNTER_INCREMENT(a, b, c, d) \
#define COUNTER_INCREMENT(addv) \
{ \
__m128i addv = _mm_set_epi32(d, c, b, a); \
__m128i carry = orig[12]; \
orig[12] = _mm_add_epi32(orig[12], addv); \
addv = _mm_srli_epi32(_mm_cmplt_epi32(orig[12], addv), 31); \
orig[13] = _mm_add_epi32(orig[13], addv); \
carry = _mm_srli_epi32(_mm_and_si128(_mm_xor_si128(orig[12], carry), carry), 31); \
orig[13] = _mm_add_epi32(orig[13], carry); \
}
static inline uint64_t
@@ -63,7 +63,9 @@ _cha_4block(uint8_t* buf, size_t bufsize, uint32_t state[16], unsigned rounds) {
// Load state to vectors, duplicate four times, only different counters
__m128i orig[16];
for (unsigned i = 0; i < 16; ++i) orig[i] = _mm_set1_epi32(state[i]);
COUNTER_INCREMENT(0, 1, 2, 3);
__m128i addv = _mm_set_epi32(3, 2, 1, 0);
COUNTER_INCREMENT(addv);
addv = _mm_set1_epi32(4);
const unsigned batches = bufsize / 256;
for (unsigned b = batches; b-->0;) {
__m128i x[16];
@@ -85,7 +87,7 @@ _cha_4block(uint8_t* buf, size_t bufsize, uint32_t state[16], unsigned rounds) {
ONEQUAD(4, 5, 6, 7, buf + 16);
ONEQUAD(8, 9, 10, 11, buf + 32);
ONEQUAD(12, 13, 14, 15, buf + 48);
COUNTER_INCREMENT(4, 4, 4, 4);
COUNTER_INCREMENT(addv);
buf += 256;
}
// Store counter
+3 -1
View File
@@ -66,8 +66,10 @@
#define COUNTER_INCREMENT(addv) \
{ \
__m256i carry = orig[12]; \
orig[12] = _mm256_add_epi32(orig[12], addv); \
orig[13] = _mm256_add_epi32(orig[13], _mm256_srli_epi32(_mm256_cmpgt_epi32(addv, orig[12]), 31)); \
carry = _mm256_srli_epi32(_mm256_and_si256(_mm256_xor_si256(orig[12], carry), carry), 31); \
orig[13] = _mm256_add_epi32(orig[13], carry); \
}
static inline uint64_t
+1 -1
View File
@@ -86,7 +86,7 @@ void cha_seek_blocks(cha_ctx* ctx, int64_t offset) {
/// @param ctx ChaCha context
/// @param out output buffer
/// @param outlen output buffer length
static inline void cha_update(cha_ctx* ctx, uint8_t* out, uint64_t outlen) {
void cha_update(cha_ctx* ctx, uint8_t* out, uint64_t outlen) {
// The included header will mess with these variables
uint8_t* end = out + outlen;
if (ctx->offset) {
+25 -34
View File
@@ -1,44 +1,12 @@
from secrets import randbelow, token_bytes
import numpy as np
import pytest
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import ChaCha20
from scipy.stats import chisquare
from randquik import cha
def test_cha_generate_statistical():
"""Requests in multiple of ChaCha20 block size 64 bytes"""
ROUNDS = 100
min_p = 1 - 0.99 ** (1 / ROUNDS) # 99 % confidence over the entire test
print(min_p)
for round in range(ROUNDS):
# First round both zero, use 8-byte nonce as round counter
key = bytes(32)
nonce = round.to_bytes(8, "little")
iv = bytes(8) + nonce # Cryptography module requires IV (counter, nonce)
# Varying sizes to test internal processing that occurs in 64 bit blocks
# and with SIMD implementations also 256 or 512 bytes at a time.
N = 10000 + randbelow(10000)
ct0, ct1 = bytearray(N), bytearray(N)
Cipher(ChaCha20(key, iv), None, None).encryptor().update_into(bytes(N), ct0)
cha.generate(ct1, key, nonce)
assert len(ct0) == len(ct1)
assert ct0.hex() == ct1.hex()
assert ct1.count(0)
# Zeroing out 50 bytes fail the test in ~30 rounds
# ct1[:50] = bytes(50)
# Test that all byte values are equivalently common (despite zero inputs)
observed = np.bincount(ct1, minlength=256)
expected = np.full(256, N / 256)
chi2, p_value = chisquare(observed, expected)
assert p_value >= min_p, f"{round=} {N=}"
def test_cipherstreams_fullblocks():
"""Requests in multiple of ChaCha20 block size 64 bytes"""
key = token_bytes(32)
@@ -51,6 +19,29 @@ def test_cipherstreams_fullblocks():
ct0 = c0.update(bytes(N))
ct1 = c1(bytearray(N))
assert len(ct0) == len(ct1)
assert ct0.hex() == ct1.hex(), f"{i=} {N=}"
@pytest.mark.parametrize(
"counter",
[
b"\x00\x00\x00\x00\x00\x00\x00\x00",
b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00",
b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
b"\xFF\xFF\xFF\x7F\xFF\xFF\xFF\xFF",
b"\x00\x00\x00\x80\xFF\xFF\xFF\xFF",
],
)
def test_counter_wrap(counter):
"""Tests carry handling of counter increments"""
key = bytes(32)
iv = counter + b"--------"
c0 = Cipher(ChaCha20(key, iv), None, None).encryptor()
c1 = cha.Cha(key, iv)
N = 128
ct0 = c0.update(bytes(N))
ct1 = c1(bytearray(N))
assert len(ct0) == len(ct1)
assert ct0.hex() == ct1.hex()
@@ -66,7 +57,7 @@ def test_cipherstreams_partial_updates():
ct0 = c0.update(bytes(N))
ct1 = c1(bytearray(N))
assert len(ct0) == len(ct1)
assert ct0.hex() == ct1.hex()
assert ct0.hex() == ct1.hex(), f"{i=} {N=}"
def test_cipherstreams_32leftover():