Hunting a bug that causes test failure
This commit is contained in:
+4
-13
@@ -30,22 +30,10 @@ keywords = [
|
|||||||
[project.urls]
|
[project.urls]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = ["pytest", "ruff"]
|
dev = ["pytest", "ruff", "cryptography"]
|
||||||
|
|
||||||
[tool.hatchling]
|
[tool.hatchling]
|
||||||
|
|
||||||
[tool.hatch.version]
|
|
||||||
source = "hatchling.version:Version"
|
|
||||||
|
|
||||||
[tool.hatch.build]
|
|
||||||
hooks.vcs.version-file = "randquik/_version.py"
|
|
||||||
hooks.vcs.template = """
|
|
||||||
# This file is automatically generated by hatch build.
|
|
||||||
__version__ = {version!r}
|
|
||||||
"""
|
|
||||||
|
|
||||||
targets.sdist.include = ["/randquik"]
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
extend-select = ["I", "W", "UP", "C4", "ISC", "S"]
|
extend-select = ["I", "W", "UP", "C4", "ISC", "S"]
|
||||||
# Worth selecting but still too broken: ASYNC, B, DTZ, FA
|
# Worth selecting but still too broken: ASYNC, B, DTZ, FA
|
||||||
@@ -70,3 +58,6 @@ ignore = [
|
|||||||
]
|
]
|
||||||
show-source = true
|
show-source = true
|
||||||
show-fixes = true
|
show-fixes = true
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
pythonpath = ["."]
|
||||||
|
|||||||
+13
-7
@@ -11,7 +11,11 @@ if not src.is_dir():
|
|||||||
ffi = cffi.FFI()
|
ffi = cffi.FFI()
|
||||||
ffi.cdef(
|
ffi.cdef(
|
||||||
"""
|
"""
|
||||||
typedef struct cha_ctx { uint32_t input[16]; } cha_ctx;
|
typedef struct cha_ctx {
|
||||||
|
uint32_t input[16];
|
||||||
|
uint8_t unconsumed[64];
|
||||||
|
uint8_t uncount;
|
||||||
|
} cha_ctx;
|
||||||
|
|
||||||
int cha_generate(uint8_t* out, uint64_t outlen, const uint8_t key[32], const uint8_t iv[16]);
|
int cha_generate(uint8_t* out, uint64_t outlen, const uint8_t key[32], const uint8_t iv[16]);
|
||||||
|
|
||||||
@@ -20,7 +24,9 @@ ffi.cdef(
|
|||||||
int cha_update(cha_ctx* ctx, uint8_t* out, uint64_t outlen);
|
int cha_update(cha_ctx* ctx, uint8_t* out, uint64_t outlen);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
lib = ffi.dlopen("../build/librandquik-chacha20.so")
|
lib = ffi.dlopen(
|
||||||
|
(Path(__file__).parent.parent / "build/librandquik-chacha20.so").as_posix()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _processKeys(key, iv):
|
def _processKeys(key, iv):
|
||||||
@@ -58,14 +64,14 @@ class Cha:
|
|||||||
|
|
||||||
def __call__(self, out: bytearray | Any):
|
def __call__(self, out: bytearray | Any):
|
||||||
"""Fill the parameter with random bytes"""
|
"""Fill the parameter with random bytes"""
|
||||||
out, outlen = _processBuffer(out)
|
outbuf, outlen = _processBuffer(out)
|
||||||
lib.cha_update(self.ctx, out, outlen)
|
lib.cha_update(self.ctx, outbuf, outlen)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def generate(out: bytearray | Any, key: bytes | Any, iv: bytes | Any):
|
def generate(out: bytearray | Any, key: bytes | Any, iv: bytes | Any):
|
||||||
"""Setup a generator, fill the out buffer and dispose the generator"""
|
"""Setup a generator, fill the out buffer and dispose the generator"""
|
||||||
key, iv =_processKeys(key, iv)
|
key, iv = _processKeys(key, iv)
|
||||||
out, outlen = _processBuffer(out)
|
outbuf, outlen = _processBuffer(out)
|
||||||
lib.cha_generate(out, outlen, key, iv)
|
lib.cha_generate(outbuf, outlen, key, iv)
|
||||||
return out
|
return out
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ static inline uint64_t _cha_block(cha_ctx* ctx, uint8_t* begin, uint8_t* end) {
|
|||||||
if (bytes < 64) {
|
if (bytes < 64) {
|
||||||
memcpy(c, x, bytes);
|
memcpy(c, x, bytes);
|
||||||
c = end;
|
c = end;
|
||||||
|
// Leftover bytes are stored in ctx for next call
|
||||||
|
ctx->uncount = 64 - bytes;
|
||||||
|
memcpy(ctx->unconsumed, x + bytes, ctx->uncount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
memcpy(c, x, 64);
|
memcpy(c, x, 64);
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ void cha_init(cha_ctx* ctx, const uint8_t* key, const uint8_t* iv) {
|
|||||||
ctx->state[3] = 0x6b206574;
|
ctx->state[3] = 0x6b206574;
|
||||||
memcpy(ctx->state + 4, key, 32);
|
memcpy(ctx->state + 4, key, 32);
|
||||||
memcpy(ctx->state + 12, iv, 16);
|
memcpy(ctx->state + 12, iv, 16);
|
||||||
|
memset(ctx->unconsumed, 0, sizeof ctx->unconsumed);
|
||||||
|
ctx->uncount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cha_wipe(cha_ctx* ctx) { memset(ctx, 0, sizeof(cha_ctx)); }
|
void cha_wipe(cha_ctx* ctx) { memset(ctx, 0, sizeof(cha_ctx)); }
|
||||||
@@ -38,6 +40,19 @@ int cha_update(cha_ctx* ctx, uint8_t* out, uint64_t outlen) {
|
|||||||
// The included header will mess with these variables
|
// The included header will mess with these variables
|
||||||
uint8_t* c = out;
|
uint8_t* c = out;
|
||||||
uint8_t* end = out + outlen;
|
uint8_t* end = out + outlen;
|
||||||
|
if (ctx->uncount) {
|
||||||
|
// Deliver stored bytes first
|
||||||
|
uint64_t N = ctx->uncount >= outlen ? outlen : ctx->uncount;
|
||||||
|
fprintf(stderr, "%lu, %i, %lu\n", N, ctx->uncount, outlen);
|
||||||
|
memcpy(c, ctx->unconsumed, N);
|
||||||
|
ctx->uncount -= N;
|
||||||
|
c += N;
|
||||||
|
if (ctx->uncount) {
|
||||||
|
memmove(ctx->unconsumed, ctx->unconsumed + N, ctx->uncount);
|
||||||
|
}
|
||||||
|
if (c == out + outlen)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
// TODO: Handle resume if we are not at block boundary
|
// TODO: Handle resume if we are not at block boundary
|
||||||
if (__builtin_cpu_supports("ssse3")) {
|
if (__builtin_cpu_supports("ssse3")) {
|
||||||
if (__builtin_cpu_supports("avx2")) {
|
if (__builtin_cpu_supports("avx2")) {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ static const uint64_t CHA_BLOCK_SIZE = 64;
|
|||||||
|
|
||||||
typedef struct cha_ctx {
|
typedef struct cha_ctx {
|
||||||
uint32_t state[16];
|
uint32_t state[16];
|
||||||
|
uint8_t unconsumed[64];
|
||||||
|
uint8_t uncount;
|
||||||
} cha_ctx;
|
} cha_ctx;
|
||||||
|
|
||||||
/// @brief Initialize cha_ctx
|
/// @brief Initialize cha_ctx
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from secrets import randbelow, token_bytes
|
||||||
|
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||||
|
from cryptography.hazmat.primitives.ciphers.algorithms import ChaCha20
|
||||||
|
|
||||||
|
from randquik import cha
|
||||||
|
|
||||||
|
|
||||||
|
def test_cipherstreams_fullblocks():
|
||||||
|
key = token_bytes(32)
|
||||||
|
nonce = token_bytes(16)
|
||||||
|
c0 = Cipher(ChaCha20(key, nonce), None, None).encryptor()
|
||||||
|
c1 = cha.Cha(key, nonce)
|
||||||
|
|
||||||
|
for i in range(2048):
|
||||||
|
N = 64 * (1 + randbelow(2048))
|
||||||
|
ct0 = c0.update(bytes(N))
|
||||||
|
ct1 = c1(bytearray(N))
|
||||||
|
assert len(ct0) == len(ct1)
|
||||||
|
assert ct0.hex() == ct1.hex()
|
||||||
|
|
||||||
|
|
||||||
|
def test_cipherstreams_partial_updates():
|
||||||
|
key = token_bytes(32)
|
||||||
|
nonce = token_bytes(16)
|
||||||
|
c0 = Cipher(ChaCha20(key, nonce), None, None).encryptor()
|
||||||
|
c1 = cha.Cha(key, nonce)
|
||||||
|
|
||||||
|
for i in range(2048):
|
||||||
|
N = 1 + randbelow(2048)
|
||||||
|
ct0 = c0.update(bytes(N))
|
||||||
|
ct1 = c1(bytearray(N))
|
||||||
|
assert len(ct0) == len(ct1)
|
||||||
|
assert ct0.hex() == ct1.hex()
|
||||||
Reference in New Issue
Block a user