Finish the refactor.

This commit is contained in:
Milan Špinka
2025-02-01 23:23:51 +01:00
parent 0ae582b733
commit 3afe9895c5
14 changed files with 521 additions and 572 deletions

View File

@ -21,6 +21,7 @@ Most (**theoretical!**) users should directly use one of the cryptographic *prot
### Primitives
- Advanced Encryption Standard (FIPS 197): AES-128, AES-192, AES-256
- Block Cipher Operation Modes: CBC-PKCS7
- ChaCha20 (RFC 7539): ChaCha20 with 64-bit nonce and 64-bit counter, ChaCha20 with 96-bit nonce and 32-bit counter
- Salsa20 (Bernstein, not standardized): Salsa20/20 with 256-key, Salsa20/20 with 128-bit key
- Secure Hash Standard (FIPS 180-4): SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256
@ -38,7 +39,7 @@ Most (**theoretical!**) users should directly use one of the cryptographic *prot
### Primitives
- DES, 3DES
- Block cipher modes: CBC-PKCS7, CFB, OFB, CTR, GCM
- Block cipher modes: CFB, OFB, CTR, GCM
- Poly1305
- SHA-512/t, SHA-3, HMAC
- BigIntegers & modular arithmetic

View File

@ -1,24 +1,16 @@
const std = @import("std");
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});
// Define a private module for importing source files in tests.
const src_module = b.createModule(.{
.root_source_file = .{ .cwd_relative = "src/root.zig" },
const utility_module = b.createModule(.{
.root_source_file = .{ .cwd_relative = "src/utility//index.zig" },
});
const primitive_module = b.createModule(.{
.root_source_file = .{ .cwd_relative = "src/primitive/index.zig" },
});
primitive_module.addImport("utility", utility_module);
const lib = b.addStaticLibrary(.{
.name = "crypto",
@ -26,26 +18,21 @@ pub fn build(b: *std.Build) void {
.target = target,
.optimize = optimize,
});
lib.root_module.addImport("primitive", primitive_module);
lib.root_module.addImport("utility", utility_module);
// This declares intent for the library to be installed into the standard
// location when the user invokes the "install" step (the default step when
// running `zig build`).
b.installArtifact(lib);
// Creates a step for unit testing. This only builds the test executable
// but does not run it.
const lib_unit_tests = b.addTest(.{
.root_source_file = b.path("test/index.zig"),
.target = target,
.optimize = optimize,
});
lib_unit_tests.root_module.addImport("ziggy", src_module);
lib_unit_tests.root_module.addImport("primitive", primitive_module);
lib_unit_tests.root_module.addImport("utility", utility_module);
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
}

View File

@ -1,7 +0,0 @@
pub const CryptoError = error{
MessageLengthLimitExceeded,
BufferSizeMismatch,
InvalidIVLength,
InvalidTagLength,
IncorrectAuthenticationTag,
};

View File

@ -1,7 +1,7 @@
const std = @import("std");
const testing = std.testing;
const byte_operations = @import("../../utility/byte_operations.zig");
const byte_operations = @import("utility").byte_operations;
const word_to_bytes = byte_operations.word_to_bytes_be;
const bytes_to_word = byte_operations.bytes_to_word_be;

View File

@ -9,22 +9,22 @@ const CryptoError = error{
IncorrectAuthenticationTag,
};
pub const GCM_128_BLOCK_SIZE = 128 / 8;
pub const GCM128_BLOCK_SIZE = 128 / 8;
pub fn gcm_128_cipher_fn_t(comptime key_size: usize) type {
pub fn gcm128_cipher_fn_t(comptime key_size: usize) type {
return fn (
key: *const [key_size]u8,
block_in: *const [GCM_128_BLOCK_SIZE]u8,
block_out: *[GCM_128_BLOCK_SIZE]u8,
block_in: *const [GCM128_BLOCK_SIZE]u8,
block_out: *[GCM128_BLOCK_SIZE]u8,
) void;
}
fn Gcm128Ctx(
comptime cipher_key_size: usize,
cipher_encrypt_fn: gcm_128_cipher_fn_t(cipher_key_size),
cipher_encrypt_fn: gcm128_cipher_fn_t(cipher_key_size),
) type {
return struct {
const BLOCK_SIZE = GCM_128_BLOCK_SIZE;
const BLOCK_SIZE = GCM128_BLOCK_SIZE;
const AAD_MAX_BYTES = (1 << 64) / 8;
const TEXT_MAX_BYTES = ((1 << 39) - 256) / 8;
const TAG_MAX_BYTES = 128 / 8;
@ -37,21 +37,21 @@ fn Gcm128Ctx(
counter: [BLOCK_SIZE]u8,
ghash_x: [BLOCK_SIZE]u8,
h: [BLOCK_SIZE]u8,
y0: [BLOCK_SIZE]u8,
ciphertext_buffer: [BLOCK_SIZE]u8,
ciphertext_length: u64,
aad_length: u64,
};
}
pub fn gcm_128_new(
pub fn gcm128_new(
comptime cipher_key_size: usize,
cipher_encrypt_fn: gcm_128_cipher_fn_t(cipher_key_size),
cipher_encrypt_fn: gcm128_cipher_fn_t(cipher_key_size),
key: *const [cipher_key_size]u8,
iv: []const u8,
) !Gcm128Ctx(cipher_key_size, cipher_encrypt_fn) {
if (iv.len == 0) {
if (iv.len == 0)
return CryptoError.InvalidIVLength;
}
var ctx = Gcm128Ctx(cipher_key_size, cipher_encrypt_fn){
.key = undefined,
@ -59,6 +59,7 @@ pub fn gcm_128_new(
.counter = undefined,
.ghash_x = undefined,
.h = undefined,
.y0 = undefined,
.ciphertext_buffer = undefined,
.ciphertext_length = 0,
.aad_length = 0,
@ -68,7 +69,7 @@ pub fn gcm_128_new(
@memcpy(&ctx.key, key);
// Compute the `H` value.
cipher_encrypt_fn(&ctx.key, &std.mem.zeroes([GCM_128_BLOCK_SIZE]u8), &ctx.h);
cipher_encrypt_fn(&ctx.key, &std.mem.zeroes([GCM128_BLOCK_SIZE]u8), &ctx.h);
// Set the counter initial value (`Y_0`).
if (iv.len == 96 / 8) {
@ -76,15 +77,16 @@ pub fn gcm_128_new(
@memset(ctx.counter[(96 / 8)..(ctx.counter.len - 1)], 0x00);
ctx.counter[ctx.counter.len - 1] = 0x01;
} else {
gcm_128_ghash(&ctx.h, &.{}, iv, &ctx.counter);
gcm128_ghash(&ctx.h, &.{}, iv, &ctx.counter);
}
@memcpy(&ctx.y0, &ctx.counter);
return ctx;
}
pub fn gcm_128_destroy(
pub fn gcm128_destroy(
comptime cipher_key_size: usize,
cipher_encrypt_fn: gcm_128_cipher_fn_t(cipher_key_size),
cipher_encrypt_fn: gcm128_cipher_fn_t(cipher_key_size),
ctx: *Gcm128Ctx(cipher_key_size, cipher_encrypt_fn),
) void {
@memset(&ctx, 0);
@ -93,9 +95,9 @@ pub fn gcm_128_destroy(
// Important: this function must only be called at most once
// AFTER the context is initialized but BEFORE any data is en/decrypted.
// TODO: Enforce at API level.
pub fn gcm_128_authenticate_data_once(
pub fn gcm128_authenticate_data_once(
comptime cipher_key_size: usize,
cipher_encrypt_fn: gcm_128_cipher_fn_t(cipher_key_size),
cipher_encrypt_fn: gcm128_cipher_fn_t(cipher_key_size),
ctx: *Gcm128Ctx(cipher_key_size, cipher_encrypt_fn),
data: []const u8,
) !void {
@ -104,15 +106,15 @@ pub fn gcm_128_authenticate_data_once(
return CryptoError.MessageLengthLimitExceeded;
// Compute and store `X_m` where m is the length in blocks of the AAD.
gcm_128_ghash_padded_chunk(&std.mem.zeroes([]u8), &ctx.h, data, &ctx.ghash_x);
gcm128_ghash_pad_chunk(&std.mem.zeroes([]u8), &ctx.h, data, &ctx.ghash_x);
// Store the AAD length for the tag computation.
ctx.aad_length = data.len;
}
pub fn gcm_128_encrypt(
pub fn gcm128_encrypt(
comptime cipher_key_size: usize,
cipher_encrypt_fn: gcm_128_cipher_fn_t(cipher_key_size),
cipher_encrypt_fn: gcm128_cipher_fn_t(cipher_key_size),
ctx: *Gcm128Ctx(cipher_key_size, cipher_encrypt_fn),
plaintext: []const u8,
ciphertext: []u8,
@ -123,41 +125,25 @@ pub fn gcm_128_encrypt(
if (ctx.ciphertext_length + plaintext.len >= @TypeOf(ctx.*).TEXT_MAX_BYTES)
return CryptoError.MessageLengthLimitExceeded;
const BS = GCM_128_BLOCK_SIZE;
var bytes_processed: usize = 0;
const BS = GCM128_BLOCK_SIZE;
const KS = @TypeOf(ctx.*).KEY_SIZE;
const ENC = @TypeOf(ctx.*).CIPHER_FN;
const bytes_buffered_ct = ctx.ciphertext_length % BS;
var bytes_processed: usize = 0;
// If we're not at the beginning of a new block, use the existing keystream to encrypt the plaintext.
if (bytes_buffered_ct != 0) {
// Simplest case - no block border is reached - just XOR-encrypt and we're done.
if (bytes_buffered_ct + plaintext.len < BS) {
gcm_128_crypt_xor(
@TypeOf(ctx.*).KEY_SIZE,
@TypeOf(ctx.*).CIPHER_FN,
ctx,
plaintext,
ciphertext,
false,
);
return;
return gcm128_crypt_xor(KS, ENC, ctx, plaintext, ciphertext, false);
}
// Otherwise, encrypt the remainder of the block with the existing
// keystream and then authenticate the completed ciphertext block.
else {
gcm_128_crypt_xor(
@TypeOf(ctx.*).KEY_SIZE,
@TypeOf(ctx.*).CIPHER_FN,
ctx,
plaintext[0 .. BS - bytes_buffered_ct],
ciphertext[0 .. BS - bytes_buffered_ct],
false,
);
gcm_128_authenticate_ciphertext_block(
@TypeOf(ctx.*).KEY_SIZE,
@TypeOf(ctx.*).CIPHER_FN,
ctx,
);
const len = BS - bytes_buffered_ct;
gcm128_crypt_xor(KS, ENC, ctx, plaintext[0..len], ciphertext[0..len], false);
gcm128_authenticate_ciphertext_block(KS, ENC, ctx);
bytes_processed += BS - bytes_buffered_ct;
}
}
@ -165,58 +151,34 @@ pub fn gcm_128_encrypt(
// As long as we have another whole block worth of plaintext, encrypt it
// and authenticate the CT.
while ((plaintext.len - bytes_processed) / BS > 0) : (bytes_processed += BS) {
gcm_128_update_keystream(
@TypeOf(ctx.*).KEY_SIZE,
@TypeOf(ctx.*).CIPHER_FN,
ctx,
);
gcm_128_crypt_xor(
@TypeOf(ctx.*).KEY_SIZE,
@TypeOf(ctx.*).CIPHER_FN,
ctx,
plaintext[bytes_processed .. bytes_processed + BS],
ciphertext[bytes_processed .. bytes_processed + BS],
false,
);
gcm_128_authenticate_ciphertext_block(
@TypeOf(ctx.*).KEY_SIZE,
@TypeOf(ctx.*).CIPHER_FN,
ctx,
);
const start = bytes_processed;
const end = bytes_processed + BS;
gcm128_update_keystream(KS, ENC, ctx);
gcm128_crypt_xor(KS, ENC, ctx, plaintext[start..end], ciphertext[start..end], false);
gcm128_authenticate_ciphertext_block(KS, ENC, ctx);
}
// Finally, if there's still plaintext left, update the keystream and encrypt the rest of it.
if (bytes_processed < plaintext.len) {
gcm_128_update_keystream(
@TypeOf(ctx.*).KEY_SIZE,
@TypeOf(ctx.*).CIPHER_FN,
ctx,
);
gcm_128_crypt_xor(
@TypeOf(ctx.*).KEY_SIZE,
@TypeOf(ctx.*).CIPHER_FN,
ctx,
plaintext[bytes_processed..],
ciphertext[bytes_processed..],
false,
);
gcm128_update_keystream(KS, ENC, ctx);
gcm128_crypt_xor(KS, ENC, ctx, plaintext[bytes_processed..], ciphertext[bytes_processed..], false);
bytes_processed += plaintext.len - bytes_processed;
}
}
fn gcm_128_crypt_xor(
fn gcm128_crypt_xor(
comptime cipher_key_size: usize,
cipher_encrypt_fn: gcm_128_cipher_fn_t(cipher_key_size),
cipher_encrypt_fn: gcm128_cipher_fn_t(cipher_key_size),
ctx: *Gcm128Ctx(cipher_key_size, cipher_encrypt_fn),
input_slice: []const u8,
output_slice: []u8,
decrypt: bool,
) void {
const block_offset = ctx.ciphertext_length % GCM_128_BLOCK_SIZE;
const block_offset = ctx.ciphertext_length % GCM128_BLOCK_SIZE;
if (input_slice.len != output_slice.len or
block_offset + input_slice.len > GCM_128_BLOCK_SIZE)
@panic("gcm_128_crypt_xor contract violated!");
block_offset + input_slice.len > GCM128_BLOCK_SIZE)
@panic("gcm128_crypt_xor contract violated!");
const len = input_slice.len;
for (0..len, block_offset..block_offset + len) |ct_i, ks_i| {
@ -230,111 +192,108 @@ fn gcm_128_crypt_xor(
ctx.ciphertext_length += len;
}
fn gcm_128_update_keystream(
fn gcm128_update_keystream(
comptime cipher_key_size: usize,
cipher_encrypt_fn: gcm_128_cipher_fn_t(cipher_key_size),
cipher_encrypt_fn: gcm128_cipher_fn_t(cipher_key_size),
ctx: *Gcm128Ctx(cipher_key_size, cipher_encrypt_fn),
) void {
gcm_128_incr(&ctx.counter);
gcm128_incr(&ctx.counter);
cipher_encrypt_fn(&ctx.key, &ctx.counter, &ctx.keystream);
}
fn gcm_128_authenticate_ciphertext_block(
fn gcm128_authenticate_ciphertext_block(
comptime cipher_key_size: usize,
cipher_encrypt_fn: gcm_128_cipher_fn_t(cipher_key_size),
cipher_encrypt_fn: gcm128_cipher_fn_t(cipher_key_size),
ctx: *Gcm128Ctx(cipher_key_size, cipher_encrypt_fn),
) void {
xor(GCM_128_BLOCK_SIZE, &ctx.ghash_x, &ctx.ciphertext_buffer, &ctx.ghash_x);
gcm_128_mult(&ctx.ghash_x, &ctx.h, &ctx.ghash_x);
xor(GCM128_BLOCK_SIZE, &ctx.ghash_x, &ctx.ciphertext_buffer, &ctx.ghash_x);
gcm128_mult(&ctx.ghash_x, &ctx.h, &ctx.ghash_x);
}
pub fn gcm_128_encrypt_final(
pub fn gcm128_encrypt_final(
comptime cipher_key_size: usize,
cipher_encrypt_fn: gcm_128_cipher_fn_t(cipher_key_size),
cipher_encrypt_fn: gcm128_cipher_fn_t(cipher_key_size),
ctx: *Gcm128Ctx(cipher_key_size, cipher_encrypt_fn),
tag: []u8,
) !void {
if (tag.len > @TypeOf(ctx.*).TAG_MAX_BYTES)
return CryptoError.InvalidTagLength;
const KS = @TypeOf(ctx.*).KEY_SIZE;
const ENC = @TypeOf(ctx.*).CIPHER_FN;
// The last ciphertext block is 0-padded and authenticated.
const ct_residue_bytes = ctx.ciphertext_length % GCM_128_BLOCK_SIZE;
const ct_residue_bytes = ctx.ciphertext_length % GCM128_BLOCK_SIZE;
@memset(ctx.ciphertext_buffer[ct_residue_bytes..], 0);
gcm_128_authenticate_ciphertext_block(
@TypeOf(ctx.*).KEY_SIZE,
@TypeOf(ctx.*).CIPHER_FN,
ctx,
);
gcm128_authenticate_ciphertext_block(KS, ENC, ctx);
// Lastly, authenticate the lengths of the AAD and the ciphertext.
std.mem.writeInt(u64, ctx.ciphertext_buffer[0..8], ctx.aad_length, .big);
std.mem.writeInt(u64, ctx.ciphertext_buffer[8..], ctx.ciphertext_length, .big);
gcm_128_authenticate_ciphertext_block(
@TypeOf(ctx.*).KEY_SIZE,
@TypeOf(ctx.*).CIPHER_FN,
ctx,
);
gcm128_authenticate_ciphertext_block(KS, ENC, ctx);
// The authentication tag is the [tag.len] leftmost bytes of the final `X` XORed with `E(K,Y0)`.
cipher_encrypt_fn(&ctx.key, &ctx.y0, &ctx.keystream);
xor(GCM128_BLOCK_SIZE, &ctx.ghash_x, &ctx.keystream, &ctx.ghash_x);
// The authentication tag is the [tag.len] leftmost bytes of the final `X` XORed with `H`.
xor(GCM_128_BLOCK_SIZE, &ctx.ghash_x, &ctx.h, &ctx.ghash_x);
@memcpy(tag[0..], ctx.ghash_x[0..tag.len]);
}
pub fn gcm_128_incr(ctr: *[GCM_128_BLOCK_SIZE]u8) void {
pub fn gcm128_incr(ctr: *[GCM128_BLOCK_SIZE]u8) void {
const val32 = std.mem.readInt(u32, ctr[ctr.len - 4 .. ctr.len], .big);
std.mem.writeInt(u32, ctr[ctr.len - 4 .. ctr.len], val32 +% 1, .big);
}
pub fn gcm_128_ghash_padded_chunk(
iv: *const [GCM_128_BLOCK_SIZE]u8,
h: *const [GCM_128_BLOCK_SIZE]u8,
pub fn gcm128_ghash_pad_chunk(
iv: *const [GCM128_BLOCK_SIZE]u8,
h: *const [GCM128_BLOCK_SIZE]u8,
chunk: []const u8,
out: *[GCM_128_BLOCK_SIZE]u8,
out: *[GCM128_BLOCK_SIZE]u8,
) void {
const BS = GCM_128_BLOCK_SIZE;
const BS = GCM128_BLOCK_SIZE;
const m = chunk.len / BS;
// Note: This definition of `v` is different from the one in the standard,
// it is the number of residue *bytes* in the last block, not bits.
const v = chunk.len % BS;
var x_i: [GCM_128_BLOCK_SIZE]u8 = iv.*;
var x_i: [GCM128_BLOCK_SIZE]u8 = iv.*;
var i: usize = 0;
while (i < m) : (i += 1) {
xor(BS, x_i[0..], @ptrCast(chunk[i * BS .. (i + 1) * BS]), &x_i);
gcm_128_mult(&x_i, h, &x_i);
gcm128_mult(&x_i, h, &x_i);
}
var padded: [GCM_128_BLOCK_SIZE]u8 = undefined;
var padded: [GCM128_BLOCK_SIZE]u8 = undefined;
@memcpy(padded[0..v], chunk[i * BS ..]);
@memset(padded[v..], 0x00);
xor(BS, x_i[0..], padded[0..], &x_i);
gcm_128_mult(&x_i, h, out);
gcm128_mult(&x_i, h, out);
}
pub fn gcm_128_ghash(
h: *const [GCM_128_BLOCK_SIZE]u8,
pub fn gcm128_ghash(
h: *const [GCM128_BLOCK_SIZE]u8,
aad_chunk: []const u8,
ciphertext_chunk: []const u8,
out: *[GCM_128_BLOCK_SIZE]u8,
out: *[GCM128_BLOCK_SIZE]u8,
) void {
gcm_128_ghash_padded_chunk(&std.mem.zeroes([GCM_128_BLOCK_SIZE]u8), h, aad_chunk, out);
gcm_128_ghash_padded_chunk(out, h, ciphertext_chunk, out);
gcm128_ghash_pad_chunk(&std.mem.zeroes([GCM128_BLOCK_SIZE]u8), h, aad_chunk, out);
gcm128_ghash_pad_chunk(out, h, ciphertext_chunk, out);
const BS = GCM_128_BLOCK_SIZE;
const BS = GCM128_BLOCK_SIZE;
var lengths: [BS]u8 = undefined;
std.mem.writeInt(u64, lengths[0 .. BS / 2], aad_chunk.len * 8, .big);
std.mem.writeInt(u64, lengths[BS / 2 ..], ciphertext_chunk.len * 8, .big);
xor(BS, lengths[0..], out[0..], out);
gcm_128_mult(out, h, out);
gcm128_mult(out, h, out);
}
// TODO: Naive algorithm: implement optimized versions with table lookups.
pub fn gcm_128_mult(
pub fn gcm128_mult(
a: *const [128 / 8]u8,
b: *const [128 / 8]u8,
out: *[128 / 8]u8,
@ -363,54 +322,3 @@ fn xor(L: comptime_int, a: *const [L]u8, b: *const [L]u8, out: *[L]u8) void {
for (0..L) |i|
out[i] = a[i] ^ b[i];
}
inline fn bytes_from_be_int(Int: type, value: Int) [@sizeOf(Int)]u8 {
var res: [@sizeOf(Int)]u8 = undefined;
std.mem.writeInt(Int, &res, value, .big);
return res;
}
const aes = @import("aes.zig");
// https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
test "AES-GCM Test Case 1" {
const K = bytes_from_be_int(u128, 0x00000000000000000000000000000000);
const P = [0]u8{};
const IV = std.mem.zeroes([96 / 8]u8);
const H = bytes_from_be_int(u128, 0x66e94bd4ef8a2c3b884cfa59ca342b2e);
const Y0 = bytes_from_be_int(u128, 0x00000000000000000000000000000001);
// const E_K_Y0 = bytes_from_be_int(u128, 0x58e2fccefa7e3061367f1d57a4e7455a);
// const len_A_len_C = bytes_from_be_int(u128, 0x00000000000000000000000000000000);
// const GHASH_H_A_C = bytes_from_be_int(u128, 0x00000000000000000000000000000000);
const C = [0]u8{};
const T = bytes_from_be_int(u128, 0x58e2fccefa7e3061367f1d57a4e7455a);
var ctx = try gcm_128_new(
aes.Aes128Parameters.KEY_SIZE,
aes.aes_128_encrypt_block,
&K,
&IV,
);
try testing.expectEqualSlices(u8, &H, &ctx.h);
try testing.expectEqualSlices(u8, &Y0, &ctx.counter);
var ciphertext_buffer = [0]u8{};
try gcm_128_encrypt(
aes.Aes128Parameters.KEY_SIZE,
aes.aes_128_encrypt_block,
&ctx,
&P,
&ciphertext_buffer,
);
try testing.expectEqualSlices(u8, &C, &ciphertext_buffer);
var tag_buffer: @TypeOf(T) = undefined;
try gcm_128_encrypt_final(
aes.Aes128Parameters.KEY_SIZE,
aes.aes_128_encrypt_block,
&ctx,
&tag_buffer,
);
try testing.expectEqualSlices(u8, &T, &tag_buffer);
}

View File

@ -1,7 +1,7 @@
const std = @import("std");
const testing = std.testing;
const byte_operations = @import("TODO").utility.byte_operations;
const byte_operations = @import("utility").byte_operations;
const word_to_bytes = byte_operations.word_to_bytes_le;
const bytes_to_word = byte_operations.bytes_to_word_le;

View File

@ -1,58 +1,62 @@
const std = @import("std");
const testing = std.testing;
const byte_operations = @import("utility").byte_operations;
const bytes_to_word = byte_operations.bytes_to_word_le;
const word_to_bytes = byte_operations.word_to_bytes_le;
// ----------------------------------- ERROR DEFINITIONS ----------------------------------- //
const KeyStreamDepleted = error.KeyStreamDepleted;
pub const KeyStreamDepleted = error.KeyStreamDepleted;
// ----------------------------------- ChaCha20 CONSTANTS ----------------------------------- //
const SALSA20_BLOCK_SIZE = 512 / 8;
const SALSA20_BLOCK_WORDS = SALSA20_BLOCK_SIZE / 4;
pub const BLOCK_SIZE = 512 / 8;
pub const BLOCK_WORDS = BLOCK_SIZE / 4;
const SALSA20_KEY_SIZE = 256 / 8;
const SALSA20_KEY_WORDS = SALSA20_KEY_SIZE / 4;
const SALSA20_128_KEY_SIZE = 128 / 8;
const SALSA20_128_KEY_WORDS = SALSA20_128_KEY_SIZE / 4;
pub const KEY_SIZE = 256 / 8;
pub const KEY_WORDS = KEY_SIZE / 4;
pub const KEY_SIZE_128 = 128 / 8;
pub const KEY_WORDS_128 = KEY_SIZE_128 / 4;
const SALSA20_NONCE_SIZE = 64 / 8;
const SALSA20_NONCE_WORDS = SALSA20_NONCE_SIZE / 4;
pub const NONCE_SIZE = 64 / 8;
pub const NONCE_WORDS = NONCE_SIZE / 4;
const SALSA20_COUNTER_SIZE = 64 / 8;
const SALSA20_COUNTER_WORDS = SALSA20_COUNTER_SIZE / 4;
pub const COUNTER_SIZE = 64 / 8;
pub const COUNTER_WORDS = COUNTER_SIZE / 4;
const SALSA20_CONSTANT_WORDS = 128 / 8 / 4;
pub const CONSTANT_WORDS = 128 / 8 / 4;
const SALSA20_256_CONSTANTS = [SALSA20_CONSTANT_WORDS]u32{
bytes_to_word_le("expa"),
bytes_to_word_le("nd 3"),
bytes_to_word_le("2-by"),
bytes_to_word_le("te k"),
pub const CONSTANTS_256 = [CONSTANT_WORDS]u32{
bytes_to_word("expa"),
bytes_to_word("nd 3"),
bytes_to_word("2-by"),
bytes_to_word("te k"),
};
const SALSA20_128_CONSTANTS = [SALSA20_CONSTANT_WORDS]u32{
bytes_to_word_le("expa"),
bytes_to_word_le("nd 1"),
bytes_to_word_le("6-by"),
bytes_to_word_le("te k"),
pub const CONSTANTS_128 = [CONSTANT_WORDS]u32{
bytes_to_word("expa"),
bytes_to_word("nd 1"),
bytes_to_word("6-by"),
bytes_to_word("te k"),
};
// ----------------------------------- CONTEXT MANAGEMENT ----------------------------------- //
pub const Salsa20Ctx = struct {
key: [SALSA20_KEY_WORDS]u32,
nonce: [SALSA20_NONCE_WORDS]u32,
counter: [SALSA20_COUNTER_WORDS]u32,
constants: [SALSA20_CONSTANT_WORDS]u32,
state: [SALSA20_BLOCK_WORDS]u32,
working_state: [SALSA20_BLOCK_WORDS]u32,
key: [KEY_WORDS]u32,
nonce: [NONCE_WORDS]u32,
counter: [COUNTER_WORDS]u32,
constants: [CONSTANT_WORDS]u32,
state: [BLOCK_WORDS]u32,
working_state: [BLOCK_WORDS]u32,
keystream_idx: u8,
};
pub fn salsa20_new(
key: *const [SALSA20_KEY_SIZE]u8,
nonce: *const [SALSA20_NONCE_SIZE]u8,
counter: *const [SALSA20_COUNTER_SIZE]u8,
constants: *const [SALSA20_CONSTANT_WORDS]u32,
key: *const [KEY_SIZE]u8,
nonce: *const [NONCE_SIZE]u8,
counter: *const [COUNTER_SIZE]u8,
constants: *const [CONSTANT_WORDS]u32,
) Salsa20Ctx {
var ctx = Salsa20Ctx{
.key = undefined,
@ -64,12 +68,12 @@ pub fn salsa20_new(
.keystream_idx = undefined,
};
salsa20_deserialize(SALSA20_KEY_WORDS, key, &ctx.key);
salsa20_deserialize(SALSA20_COUNTER_WORDS, counter, &ctx.counter);
salsa20_deserialize(SALSA20_NONCE_WORDS, nonce, &ctx.nonce);
deserialize(KEY_WORDS, key, &ctx.key);
deserialize(COUNTER_WORDS, counter, &ctx.counter);
deserialize(NONCE_WORDS, nonce, &ctx.nonce);
@memcpy(&ctx.constants, constants);
salsa20_block_function(&ctx);
block_function(&ctx);
ctx.keystream_idx = 0;
return ctx;
@ -85,9 +89,9 @@ pub fn salsa20_destroy(ctx: *Salsa20Ctx) void {
// ----------------------------------- ENCRYPTION/DECRYPTION ----------------------------------- //
pub fn salsa20_encrypt_inplace(ctx: *Salsa20Ctx, plaintext: []u8) !void {
pub fn encrypt_inplace(ctx: *Salsa20Ctx, plaintext: []u8) !void {
for (0..plaintext.len) |i| {
try salsa20_ensure_keystream_expanded(ctx);
try ensure_keystream_expanded(ctx);
const keystream: [*]const u8 = @ptrCast(&ctx.state);
plaintext[i] ^= keystream[ctx.keystream_idx];
@ -95,20 +99,20 @@ pub fn salsa20_encrypt_inplace(ctx: *Salsa20Ctx, plaintext: []u8) !void {
}
}
pub fn salsa20_decrypt_inplace(ctx: *Salsa20Ctx, ciphertext: []u8) !void {
return salsa20_encrypt_inplace(ctx, ciphertext);
pub fn decrypt_inplace(ctx: *Salsa20Ctx, ciphertext: []u8) !void {
return encrypt_inplace(ctx, ciphertext);
}
fn salsa20_ensure_keystream_expanded(ctx: *Salsa20Ctx) !void {
if (ctx.keystream_idx == SALSA20_BLOCK_SIZE) {
try salsa20_increment_counter(ctx);
salsa20_block_function(ctx);
pub fn ensure_keystream_expanded(ctx: *Salsa20Ctx) !void {
if (ctx.keystream_idx == BLOCK_SIZE) {
try increment_counter(ctx);
block_function(ctx);
ctx.keystream_idx = 0;
}
}
fn salsa20_increment_counter(ctx: *Salsa20Ctx) !void {
for (0..SALSA20_COUNTER_WORDS) |idx| {
pub fn increment_counter(ctx: *Salsa20Ctx) !void {
for (0..COUNTER_WORDS) |idx| {
const ov = @addWithOverflow(ctx.counter[idx], 1);
ctx.counter[idx] = ov[0];
@ -120,7 +124,7 @@ fn salsa20_increment_counter(ctx: *Salsa20Ctx) !void {
// ----------------------------------- KEYSTREAM EXPANSION ----------------------------------- //
pub fn salsa20_quarter_round(state: *[SALSA20_BLOCK_WORDS]u32, ia: u8, ib: u8, ic: u8, id: u8) void {
pub fn quarter_round(state: *[BLOCK_WORDS]u32, ia: u8, ib: u8, ic: u8, id: u8) void {
var a = state[ia];
var b = state[ib];
var c = state[ic];
@ -137,41 +141,41 @@ pub fn salsa20_quarter_round(state: *[SALSA20_BLOCK_WORDS]u32, ia: u8, ib: u8, i
state[id] = d;
}
pub fn salsa20_column_round(state: *[SALSA20_BLOCK_WORDS]u32) void {
salsa20_quarter_round(state, 0, 4, 8, 12);
salsa20_quarter_round(state, 5, 9, 13, 1);
salsa20_quarter_round(state, 10, 14, 2, 6);
salsa20_quarter_round(state, 15, 3, 7, 11);
pub fn column_round(state: *[BLOCK_WORDS]u32) void {
quarter_round(state, 0, 4, 8, 12);
quarter_round(state, 5, 9, 13, 1);
quarter_round(state, 10, 14, 2, 6);
quarter_round(state, 15, 3, 7, 11);
}
pub fn salsa20_row_round(state: *[SALSA20_BLOCK_WORDS]u32) void {
salsa20_quarter_round(state, 0, 1, 2, 3);
salsa20_quarter_round(state, 5, 6, 7, 4);
salsa20_quarter_round(state, 10, 11, 8, 9);
salsa20_quarter_round(state, 15, 12, 13, 14);
pub fn row_round(state: *[BLOCK_WORDS]u32) void {
quarter_round(state, 0, 1, 2, 3);
quarter_round(state, 5, 6, 7, 4);
quarter_round(state, 10, 11, 8, 9);
quarter_round(state, 15, 12, 13, 14);
}
pub fn salsa20_double_round(state: *[SALSA20_BLOCK_WORDS]u32) void {
salsa20_column_round(state);
salsa20_row_round(state);
pub fn double_round(state: *[BLOCK_WORDS]u32) void {
column_round(state);
row_round(state);
}
pub fn salsa20_hash_function(ctx: *Salsa20Ctx) void {
pub fn hash_function(ctx: *Salsa20Ctx) void {
// Copy state to working_state.
@memcpy(&ctx.working_state, &ctx.state);
// Perform all 20 rounds (10 row and 10 column rounds).
for (0..10) |_| {
salsa20_double_round(&ctx.working_state);
double_round(&ctx.working_state);
}
// Add the working_state to the state.
for (0..SALSA20_BLOCK_WORDS) |i| {
for (0..BLOCK_WORDS) |i| {
ctx.state[i] +%= ctx.working_state[i];
}
}
pub fn salsa20_block_function(ctx: *Salsa20Ctx) void {
pub fn block_function(ctx: *Salsa20Ctx) void {
// Reset state.
{
ctx.state[0] = ctx.constants[0];
@ -195,286 +199,21 @@ pub fn salsa20_block_function(ctx: *Salsa20Ctx) void {
}
// Perform the hashing.
salsa20_hash_function(ctx);
hash_function(ctx);
}
// ----------------------------------- LITTLE ENDIAN HELPERS ----------------------------------- //
// ----------------------------------- HELPERS ----------------------------------- //
fn salsa20_serialize(L: comptime_int, words: *const [L]u32, bytes: *[L * 4]u8) void {
pub fn serialize(L: comptime_int, words: *const [L]u32, bytes: *[L * 4]u8) void {
for (0..L) |i|
std.mem.writeInt(u32, @ptrCast(bytes[(i * 4)..(i * 4 + 4)]), words[i], .little);
}
fn salsa20_deserialize(L: comptime_int, bytes: *const [L * 4]u8, words: *[L]u32) void {
pub fn deserialize(L: comptime_int, bytes: *const [L * 4]u8, words: *[L]u32) void {
for (0..L) |i|
words[i] = std.mem.readInt(u32, @ptrCast(bytes[(i * 4)..(i * 4 + 4)]), .little);
}
fn bytes_to_word_le(bytes: *const [4]u8) u32 {
return std.mem.readInt(u32, bytes, .little);
}
fn word_to_bytes_le(word: u32) [4]u8 {
var bytes: [4]u8 = undefined;
std.mem.writeInt(u32, &bytes, word, .little);
return bytes;
}
// ----------------------------------- GENERIC HELPERS ----------------------------------- //
fn rol(word: u32, bits: comptime_int) u32 {
return word << bits | word >> (32 - bits);
}
// ----------------------------------- TEST VECTORS ----------------------------------- //
fn test_quarter_round_function(y0: u32, y1: u32, y2: u32, y3: u32, z0: u32, z1: u32, z2: u32, z3: u32) !void {
var dummy_state: [SALSA20_BLOCK_WORDS]u32 = undefined;
dummy_state[0] = y0;
dummy_state[1] = y1;
dummy_state[2] = y2;
dummy_state[3] = y3;
salsa20_quarter_round(&dummy_state, 0, 1, 2, 3);
try testing.expectEqualSlices(u32, &.{ z0, z1, z2, z3 }, dummy_state[0..4]);
}
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 quarterround function" {
try test_quarter_round_function(0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000);
try test_quarter_round_function(0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x08008145, 0x00000080, 0x00010200, 0x20500000);
try test_quarter_round_function(0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x88000100, 0x00000001, 0x00000200, 0x00402000);
try test_quarter_round_function(0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x80040000, 0x00000000, 0x00000001, 0x00002000);
try test_quarter_round_function(0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00048044, 0x00000080, 0x00010000, 0x20100001);
try test_quarter_round_function(0xe7e8c006, 0xc4f9417d, 0x6479b4b2, 0x68c67137, 0xe876d72b, 0x9361dfd5, 0xf1460244, 0x948541a3);
try test_quarter_round_function(0xd3917c5b, 0x55f1c407, 0x52a58a7a, 0x8f887a3b, 0x3e2f308c, 0xd90a8f36, 0x6ab2a923, 0x2883524c);
}
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 rowround function" {
var y1 = [_]u32{
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
};
const z1 = [_]u32{
0x08008145, 0x00000080, 0x00010200, 0x20500000,
0x20100001, 0x00048044, 0x00000080, 0x00010000,
0x00000001, 0x00002000, 0x80040000, 0x00000000,
0x00000001, 0x00000200, 0x00402000, 0x88000100,
};
salsa20_row_round(&y1);
try testing.expectEqualSlices(u32, z1[0..], y1[0..]);
var y2 = [_]u32{
0x08521bd6, 0x1fe88837, 0xbb2aa576, 0x3aa26365,
0xc54c6a5b, 0x2fc74c2f, 0x6dd39cc3, 0xda0a64f6,
0x90a2f23d, 0x067f95a6, 0x06b35f61, 0x41e4732e,
0xe859c100, 0xea4d84b7, 0x0f619bff, 0xbc6e965a,
};
const z2 = [_]u32{
0xa890d39d, 0x65d71596, 0xe9487daa, 0xc8ca6a86,
0x949d2192, 0x764b7754, 0xe408d9b9, 0x7a41b4d1,
0x3402e183, 0x3c3af432, 0x50669f96, 0xd89ef0a8,
0x0040ede5, 0xb545fbce, 0xd257ed4f, 0x1818882d,
};
salsa20_row_round(&y2);
try testing.expectEqualSlices(u32, z2[0..], y2[0..]);
}
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 columnround function" {
var x1 = [_]u32{
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
};
const y1 = [_]u32{
0x10090288, 0x00000000, 0x00000000, 0x00000000,
0x00000101, 0x00000000, 0x00000000, 0x00000000,
0x00020401, 0x00000000, 0x00000000, 0x00000000,
0x40a04001, 0x00000000, 0x00000000, 0x00000000,
};
salsa20_column_round(&x1);
try testing.expectEqualSlices(u32, y1[0..], x1[0..]);
var x2 = [_]u32{
0x08521bd6, 0x1fe88837, 0xbb2aa576, 0x3aa26365,
0xc54c6a5b, 0x2fc74c2f, 0x6dd39cc3, 0xda0a64f6,
0x90a2f23d, 0x067f95a6, 0x06b35f61, 0x41e4732e,
0xe859c100, 0xea4d84b7, 0x0f619bff, 0xbc6e965a,
};
const y2 = [_]u32{
0x8c9d190a, 0xce8e4c90, 0x1ef8e9d3, 0x1326a71a,
0x90a20123, 0xead3c4f3, 0x63a091a0, 0xf0708d69,
0x789b010c, 0xd195a681, 0xeb7d5504, 0xa774135c,
0x481c2027, 0x53a8e4b5, 0x4c1f89c5, 0x3f78c9c8,
};
salsa20_column_round(&x2);
try testing.expectEqualSlices(u32, y2[0..], x2[0..]);
}
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 doubleround function" {
var x1 = [_]u32{
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000,
};
const z1 = [_]u32{
0x8186a22d, 0x0040a284, 0x82479210, 0x06929051,
0x08000090, 0x02402200, 0x00004000, 0x00800000,
0x00010200, 0x20400000, 0x08008104, 0x00000000,
0x20500000, 0xa0000040, 0x0008180a, 0x612a8020,
};
salsa20_double_round(&x1);
try testing.expectEqualSlices(u32, z1[0..], x1[0..]);
var x2 = [_]u32{
0xde501066, 0x6f9eb8f7, 0xe4fbbd9b, 0x454e3f57,
0xb75540d3, 0x43e93a4c, 0x3a6f2aa0, 0x726d6b36,
0x9243f484, 0x9145d1e8, 0x4fa9d247, 0xdc8dee11,
0x054bf545, 0x254dd653, 0xd9421b6d, 0x67b276c1,
};
const z2 = [_]u32{
0xccaaf672, 0x23d960f7, 0x9153e63a, 0xcd9a60d0,
0x50440492, 0xf07cad19, 0xae344aa0, 0xdf4cfdfc,
0xca531c29, 0x8e7943db, 0xac1680cd, 0xd503ca00,
0xa74b2ad6, 0xbc331c5c, 0x1dda24c7, 0xee928277,
};
salsa20_double_round(&x2);
try testing.expectEqualSlices(u32, z2[0..], x2[0..]);
}
fn test_hash_function(count: comptime_int, input: *const [SALSA20_BLOCK_SIZE]u8, reference: *const [SALSA20_BLOCK_SIZE]u8) !void {
var dummy_ctx = Salsa20Ctx{
.key = undefined,
.nonce = undefined,
.counter = undefined,
.constants = undefined,
.state = undefined,
.working_state = undefined,
.keystream_idx = undefined,
};
salsa20_deserialize(SALSA20_BLOCK_WORDS, input, dummy_ctx.state[0..]);
for (0..count) |_|
salsa20_hash_function(&dummy_ctx);
var serialized_result: [SALSA20_BLOCK_SIZE]u8 = undefined;
salsa20_serialize(SALSA20_BLOCK_WORDS, dummy_ctx.state[0..], serialized_result[0..]);
try testing.expectEqualSlices(u8, reference, serialized_result[0..]);
}
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 hash function" {
try test_hash_function(1, &std.mem.zeroes([SALSA20_BLOCK_SIZE]u8), &std.mem.zeroes([SALSA20_BLOCK_SIZE]u8));
try test_hash_function(1, &.{
211, 159, 13, 115, 76, 55, 82, 183, 3, 117, 222, 37, 191, 187, 234, 136,
49, 237, 179, 48, 1, 106, 178, 219, 175, 199, 166, 48, 86, 16, 179, 207,
31, 240, 32, 63, 15, 83, 93, 161, 116, 147, 48, 113, 238, 55, 204, 36,
79, 201, 235, 79, 3, 81, 156, 47, 203, 26, 244, 243, 88, 118, 104, 54,
}, &.{
109, 42, 178, 168, 156, 240, 248, 238, 168, 196, 190, 203, 26, 110, 170, 154,
29, 29, 150, 26, 150, 30, 235, 249, 190, 163, 251, 48, 69, 144, 51, 57,
118, 40, 152, 157, 180, 57, 27, 94, 107, 42, 236, 35, 27, 111, 114, 114,
219, 236, 232, 135, 111, 155, 110, 18, 24, 232, 95, 158, 179, 19, 48, 202,
});
try test_hash_function(1, &.{
88, 118, 104, 54, 79, 201, 235, 79, 3, 81, 156, 47, 203, 26, 244, 243,
191, 187, 234, 136, 211, 159, 13, 115, 76, 55, 82, 183, 3, 117, 222, 37,
86, 16, 179, 207, 49, 237, 179, 48, 1, 106, 178, 219, 175, 199, 166, 48,
238, 55, 204, 36, 31, 240, 32, 63, 15, 83, 93, 161, 116, 147, 48, 113,
}, &.{
179, 19, 48, 202, 219, 236, 232, 135, 111, 155, 110, 18, 24, 232, 95, 158,
26, 110, 170, 154, 109, 42, 178, 168, 156, 240, 248, 238, 168, 196, 190, 203,
69, 144, 51, 57, 29, 29, 150, 26, 150, 30, 235, 249, 190, 163, 251, 48,
27, 111, 114, 114, 118, 40, 152, 157, 180, 57, 27, 94, 107, 42, 236, 35,
});
try test_hash_function(1000000, &.{
6, 124, 83, 146, 38, 191, 9, 50, 4, 161, 47, 222, 122, 182, 223, 185,
75, 27, 0, 216, 16, 122, 7, 89, 162, 104, 101, 147, 213, 21, 54, 95,
225, 253, 139, 176, 105, 132, 23, 116, 76, 41, 176, 207, 221, 34, 157, 108,
94, 94, 99, 52, 90, 117, 91, 220, 146, 190, 239, 143, 196, 176, 130, 186,
}, &.{
8, 18, 38, 199, 119, 76, 215, 67, 173, 127, 144, 162, 103, 212, 176, 217,
192, 19, 233, 33, 159, 197, 154, 160, 128, 243, 219, 65, 171, 136, 135, 225,
123, 11, 68, 86, 237, 82, 20, 155, 133, 189, 9, 83, 167, 116, 194, 78,
122, 127, 195, 185, 185, 204, 188, 90, 245, 9, 183, 248, 226, 85, 245, 104,
});
}
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 expansion function" {
const key1 = [SALSA20_KEY_SIZE]u8{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216,
};
const nonce1 = [SALSA20_NONCE_SIZE]u8{ 101, 102, 103, 104, 105, 106, 107, 108 };
const pos1 = [SALSA20_NONCE_SIZE]u8{ 109, 110, 111, 112, 113, 114, 115, 116 };
const reference1 = [SALSA20_BLOCK_SIZE]u8{
69, 37, 68, 39, 41, 15, 107, 193, 255, 139, 122, 6, 170, 233, 217, 98,
89, 144, 182, 106, 21, 51, 200, 65, 239, 49, 222, 34, 215, 114, 40, 126,
104, 197, 7, 225, 197, 153, 31, 2, 102, 78, 76, 176, 84, 245, 246, 184,
177, 160, 133, 130, 6, 72, 149, 119, 192, 195, 132, 236, 234, 103, 246, 74,
};
var ctx1 = salsa20_new(&key1, &nonce1, &pos1, &SALSA20_256_CONSTANTS);
defer salsa20_destroy(&ctx1);
var keystream_buffer: [SALSA20_BLOCK_SIZE]u8 = undefined;
salsa20_serialize(SALSA20_BLOCK_WORDS, ctx1.state[0..], keystream_buffer[0..]);
try testing.expectEqualSlices(u8, reference1[0..], keystream_buffer[0..]);
const key2 = [SALSA20_KEY_SIZE]u8{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
};
const reference2 = [SALSA20_BLOCK_SIZE]u8{
39, 173, 46, 248, 30, 200, 82, 17, 48, 67, 254, 239, 37, 18, 13, 247,
241, 200, 61, 144, 10, 55, 50, 185, 6, 47, 246, 253, 143, 86, 187, 225,
134, 85, 110, 246, 161, 163, 43, 235, 231, 94, 171, 51, 145, 214, 112, 29,
14, 232, 5, 16, 151, 140, 183, 141, 171, 9, 122, 181, 104, 182, 177, 193,
};
var ctx2 = salsa20_new(&key2, &nonce1, &pos1, &SALSA20_128_CONSTANTS);
defer salsa20_destroy(&ctx2);
salsa20_serialize(SALSA20_BLOCK_WORDS, ctx2.state[0..], keystream_buffer[0..]);
try testing.expectEqualSlices(u8, reference2[0..], keystream_buffer[0..]);
}
test "Salsa20 encryption" {
const key = [SALSA20_KEY_SIZE]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
};
const nonce = [SALSA20_NONCE_SIZE]u8{
0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
};
const counter = word_to_bytes_le(0) ++ word_to_bytes_le(0);
const plaintext = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
const reference = [_]u8{
0x13, 0xfc, 0x5e, 0x28, 0x51, 0xf0, 0x79, 0x9d, 0x97, 0x52, 0x1b, 0xa6, 0xa9, 0x37, 0x11, 0x87,
0x00, 0x95, 0x7c, 0x2e, 0xf9, 0x96, 0xe4, 0x2c, 0x59, 0x84, 0x9d, 0x45, 0x0c, 0x6e, 0xa2, 0x2e,
0x91, 0xcd, 0xcc, 0xea, 0x39, 0x1e, 0xe0, 0xa3, 0x57, 0xbb, 0x56, 0xcd, 0xf5, 0x2c, 0x56, 0xce,
0x01, 0x38, 0x07, 0xa9, 0x45, 0xb9, 0x17, 0xee, 0x6c, 0xcb, 0x18, 0xce, 0xca, 0xbe, 0x4b, 0xf4,
0x09, 0xba, 0x72, 0xfb, 0xdf, 0x90, 0xdb, 0x02, 0x8d, 0x22, 0x61, 0x7f, 0x1c, 0xa9, 0x84, 0x15,
0x6c, 0xa2, 0x72, 0x47, 0x3a, 0xf4, 0xf0, 0xe4, 0xcb, 0x3d, 0x85, 0x8b, 0x7a, 0xb4, 0x67, 0xae,
0x14, 0x71, 0x87, 0xab, 0xac, 0xb7, 0xc6, 0xe9, 0xaf, 0x6f, 0x2f, 0x47, 0x28, 0x7e, 0x2e, 0x0c,
0xb3, 0x18,
};
var ctx = salsa20_new(&key, &nonce, &counter, &SALSA20_256_CONSTANTS);
defer salsa20_destroy(&ctx);
var buffer: [plaintext.len]u8 = undefined;
@memcpy(&buffer, plaintext);
try salsa20_encrypt_inplace(&ctx, &buffer);
try testing.expectEqualSlices(u8, reference[0..], buffer[0..]);
}

View File

@ -1,2 +1,2 @@
pub const primitive = @import("./primitive/index.zig");
pub const utility = @import("./utility/index.zig");
pub const primitive = @import("primitive");
pub const utility = @import("utility");

View File

@ -1,5 +1,26 @@
const std = @import("std");
fn hex_nibble_to_int(ascii_hex: u8) u4 {
const x = ascii_hex;
return @intCast(if (x >= '0' and x <= '9')
x - '0'
else if (x >= 'a' and x <= 'f')
10 + (x - 'a')
else if (x >= 'A' and x <= 'F')
10 + (x - 'A')
else
@panic("Argument is not a valid hex digit!"));
}
pub fn hex_to_bytes(L: comptime_int, hex_string: *const [2 * L]u8) [L]u8 {
var res: [L]u8 = undefined;
for (0..L) |i| {
res[i] = @as(u8, hex_nibble_to_int(hex_string[2 * i])) << 4;
res[i] |= hex_nibble_to_int(hex_string[2 * i + 1]);
}
return res;
}
pub fn word_to_bytes_le(word: u32) [4]u8 {
var bytes: [4]u8 = undefined;
std.mem.writeInt(u32, &bytes, word, .little);

View File

@ -1,7 +1,7 @@
const std = @import("std");
const testing = std.testing;
const aes = @import("ziggy").primitive.blockcipher.aes;
const aes = @import("primitive").blockcipher.aes;
test "AES-128 key expansion (FIPS 197)" {
const key = [aes.KEY_SIZE_128]u8{

View File

@ -1,12 +1,15 @@
const std = @import("std");
const testing = std.testing;
const blockcipher = @import("primitive").blockcipher;
const hex_to_bytes = @import("utility").byte_operations.hex_to_bytes;
// Use AES to test all of the operation modes.
const aes = @import("ziggy").primitive.blockcipher.aes;
const aes = blockcipher.aes;
// ----------------------------------- CBC MODE ----------------------------------- //
const cbc = @import("ziggy").primitive.blockcipher.operation_mode.cbc;
const cbc = blockcipher.operation_mode.cbc;
test "AES-128-CBC basic test" {
const BS = aes.BLOCK_SIZE;
@ -76,3 +79,62 @@ test "AES-128-CBC basic test" {
try testing.expectEqualSlices(u8, plaintext[0..], pt_buffer[0..plaintext.len]);
}
}
// ----------------------------------- GCM MODE ----------------------------------- //
const gcm = @import("primitive").blockcipher.operation_mode.gcm;
// https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
test "AES-GCM Test Case 1" {
const K = hex_to_bytes(128 / 8, "00000000000000000000000000000000");
const P = hex_to_bytes(0, "");
const IV = hex_to_bytes(96 / 8, "000000000000000000000000");
const H = hex_to_bytes(128 / 8, "66e94bd4ef8a2c3b884cfa59ca342b2e");
const Y0 = hex_to_bytes(128 / 8, "00000000000000000000000000000001");
// E_K_Y0 = 58e2fccefa7e3061367f1d57a4e7455a
// len_A_len_C = 00000000000000000000000000000000
// GHASH_H_A_C = 00000000000000000000000000000000
const C = hex_to_bytes(0, "");
const T = hex_to_bytes(128 / 8, "58e2fccefa7e3061367f1d57a4e7455a");
var ctx = try gcm.gcm128_new(aes.KEY_SIZE_128, aes.aes128_encrypt_block, &K, &IV);
try testing.expectEqualSlices(u8, &H, &ctx.h);
try testing.expectEqualSlices(u8, &Y0, &ctx.counter);
var ciphertext_buffer: @TypeOf(P) = undefined;
try gcm.gcm128_encrypt(aes.KEY_SIZE_128, aes.aes128_encrypt_block, &ctx, &P, &ciphertext_buffer);
try testing.expectEqualSlices(u8, &C, &ciphertext_buffer);
var tag_buffer: @TypeOf(T) = undefined;
try gcm.gcm128_encrypt_final(aes.KEY_SIZE_128, aes.aes128_encrypt_block, &ctx, &tag_buffer);
try testing.expectEqualSlices(u8, &T, &tag_buffer);
}
test "AES-GCM Test Case 2" {
const K = hex_to_bytes(128 / 8, "00000000000000000000000000000000");
const P = hex_to_bytes(128 / 8, "00000000000000000000000000000000");
const IV = hex_to_bytes(96 / 8, "000000000000000000000000");
const H = hex_to_bytes(128 / 8, "66e94bd4ef8a2c3b884cfa59ca342b2e");
const Y0 = hex_to_bytes(128 / 8, "00000000000000000000000000000001");
// E_K_Y0 = 58e2fccefa7e3061367f1d57a4e7455a
// Y1 = 00000000000000000000000000000002
// E_K_Y1 = 0388dace60b6a392f328c2b971b2fe78
// X1 = 5e2ec746917062882c85b0685353deb7
// len_A_len_C = 00000000000000000000000000000080
// GHASH_H_A_C = f38cbb1ad69223dcc3457ae5b6b0f885
const C = hex_to_bytes(128 / 8, "0388dace60b6a392f328c2b971b2fe78");
const T = hex_to_bytes(128 / 8, "ab6e47d42cec13bdf53a67b21257bddf");
var ctx = try gcm.gcm128_new(aes.KEY_SIZE_128, aes.aes128_encrypt_block, &K, &IV);
try testing.expectEqualSlices(u8, &H, &ctx.h);
try testing.expectEqualSlices(u8, &Y0, &ctx.counter);
var ciphertext_buffer: @TypeOf(P) = undefined;
try gcm.gcm128_encrypt(aes.KEY_SIZE_128, aes.aes128_encrypt_block, &ctx, &P, &ciphertext_buffer);
try testing.expectEqualSlices(u8, &C, &ciphertext_buffer);
var tag_buffer: @TypeOf(T) = undefined;
try gcm.gcm128_encrypt_final(aes.KEY_SIZE_128, aes.aes128_encrypt_block, &ctx, &tag_buffer);
try testing.expectEqualSlices(u8, &T, &tag_buffer);
}

View File

@ -1,8 +1,10 @@
const std = @import("std");
const testing = @import("std").testing;
const testing = std.testing;
const sha1 = @import("ziggy").primitive.digest.sha1;
const sha2 = @import("ziggy").primitive.digest.sha2;
const sha1 = @import("primitive").digest.sha1;
const sha2 = @import("primitive").digest.sha2;
const hex_to_bytes = @import("utility").byte_operations.hex_to_bytes;
// ----------------------------------- SHA-1 ----------------------------------- //
@ -285,27 +287,6 @@ test "SHA-384 basic test" {
// ----------------------------------- TEST HELPERS ----------------------------------- //
fn hex_nibble_to_int(ascii_hex: u8) u4 {
const x = ascii_hex;
return @intCast(if (x >= '0' and x <= '9')
x - '0'
else if (x >= 'a' and x <= 'f')
10 + (x - 'a')
else if (x >= 'A' and x <= 'F')
10 + (x - 'A')
else
@panic("Argument is not a valid hex digit!"));
}
fn hex_to_bytes(L: comptime_int, hex_string: *const [2 * L]u8) [L]u8 {
var res: [L]u8 = undefined;
for (0..L) |i| {
res[i] = @as(u8, hex_nibble_to_int(hex_string[2 * i])) << 4;
res[i] |= hex_nibble_to_int(hex_string[2 * i + 1]);
}
return res;
}
fn run_hash_precomputed_tests(
Ctx: type,
L: comptime_int,

View File

@ -1,11 +1,11 @@
const std = @import("std");
const testing = std.testing;
const byte_operations = @import("ziggy").utility.byte_operations;
const byte_operations = @import("utility").byte_operations;
const word_to_bytes = byte_operations.word_to_bytes_le;
const bytes_to_word = byte_operations.bytes_to_word_le;
const chacha20 = @import("ziggy").primitive.streamcipher.chacha20;
const chacha20 = @import("primitive").streamcipher.chacha20;
// ----------------------------------- TEST VECTORS ----------------------------------- //

View File

@ -0,0 +1,257 @@
const std = @import("std");
const testing = std.testing;
const salsa20 = @import("primitive").streamcipher.salsa20;
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 quarterround function" {
try test_quarter_round_function(0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000);
try test_quarter_round_function(0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x08008145, 0x00000080, 0x00010200, 0x20500000);
try test_quarter_round_function(0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x88000100, 0x00000001, 0x00000200, 0x00402000);
try test_quarter_round_function(0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x80040000, 0x00000000, 0x00000001, 0x00002000);
try test_quarter_round_function(0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00048044, 0x00000080, 0x00010000, 0x20100001);
try test_quarter_round_function(0xe7e8c006, 0xc4f9417d, 0x6479b4b2, 0x68c67137, 0xe876d72b, 0x9361dfd5, 0xf1460244, 0x948541a3);
try test_quarter_round_function(0xd3917c5b, 0x55f1c407, 0x52a58a7a, 0x8f887a3b, 0x3e2f308c, 0xd90a8f36, 0x6ab2a923, 0x2883524c);
}
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 rowround function" {
var y1 = [_]u32{
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
};
const z1 = [_]u32{
0x08008145, 0x00000080, 0x00010200, 0x20500000,
0x20100001, 0x00048044, 0x00000080, 0x00010000,
0x00000001, 0x00002000, 0x80040000, 0x00000000,
0x00000001, 0x00000200, 0x00402000, 0x88000100,
};
salsa20.row_round(&y1);
try testing.expectEqualSlices(u32, z1[0..], y1[0..]);
var y2 = [_]u32{
0x08521bd6, 0x1fe88837, 0xbb2aa576, 0x3aa26365,
0xc54c6a5b, 0x2fc74c2f, 0x6dd39cc3, 0xda0a64f6,
0x90a2f23d, 0x067f95a6, 0x06b35f61, 0x41e4732e,
0xe859c100, 0xea4d84b7, 0x0f619bff, 0xbc6e965a,
};
const z2 = [_]u32{
0xa890d39d, 0x65d71596, 0xe9487daa, 0xc8ca6a86,
0x949d2192, 0x764b7754, 0xe408d9b9, 0x7a41b4d1,
0x3402e183, 0x3c3af432, 0x50669f96, 0xd89ef0a8,
0x0040ede5, 0xb545fbce, 0xd257ed4f, 0x1818882d,
};
salsa20.row_round(&y2);
try testing.expectEqualSlices(u32, z2[0..], y2[0..]);
}
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 columnround function" {
var x1 = [_]u32{
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x00000000, 0x00000000,
};
const y1 = [_]u32{
0x10090288, 0x00000000, 0x00000000, 0x00000000,
0x00000101, 0x00000000, 0x00000000, 0x00000000,
0x00020401, 0x00000000, 0x00000000, 0x00000000,
0x40a04001, 0x00000000, 0x00000000, 0x00000000,
};
salsa20.column_round(&x1);
try testing.expectEqualSlices(u32, y1[0..], x1[0..]);
var x2 = [_]u32{
0x08521bd6, 0x1fe88837, 0xbb2aa576, 0x3aa26365,
0xc54c6a5b, 0x2fc74c2f, 0x6dd39cc3, 0xda0a64f6,
0x90a2f23d, 0x067f95a6, 0x06b35f61, 0x41e4732e,
0xe859c100, 0xea4d84b7, 0x0f619bff, 0xbc6e965a,
};
const y2 = [_]u32{
0x8c9d190a, 0xce8e4c90, 0x1ef8e9d3, 0x1326a71a,
0x90a20123, 0xead3c4f3, 0x63a091a0, 0xf0708d69,
0x789b010c, 0xd195a681, 0xeb7d5504, 0xa774135c,
0x481c2027, 0x53a8e4b5, 0x4c1f89c5, 0x3f78c9c8,
};
salsa20.column_round(&x2);
try testing.expectEqualSlices(u32, y2[0..], x2[0..]);
}
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 doubleround function" {
var x1 = [_]u32{
0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000,
};
const z1 = [_]u32{
0x8186a22d, 0x0040a284, 0x82479210, 0x06929051,
0x08000090, 0x02402200, 0x00004000, 0x00800000,
0x00010200, 0x20400000, 0x08008104, 0x00000000,
0x20500000, 0xa0000040, 0x0008180a, 0x612a8020,
};
salsa20.double_round(&x1);
try testing.expectEqualSlices(u32, z1[0..], x1[0..]);
var x2 = [_]u32{
0xde501066, 0x6f9eb8f7, 0xe4fbbd9b, 0x454e3f57,
0xb75540d3, 0x43e93a4c, 0x3a6f2aa0, 0x726d6b36,
0x9243f484, 0x9145d1e8, 0x4fa9d247, 0xdc8dee11,
0x054bf545, 0x254dd653, 0xd9421b6d, 0x67b276c1,
};
const z2 = [_]u32{
0xccaaf672, 0x23d960f7, 0x9153e63a, 0xcd9a60d0,
0x50440492, 0xf07cad19, 0xae344aa0, 0xdf4cfdfc,
0xca531c29, 0x8e7943db, 0xac1680cd, 0xd503ca00,
0xa74b2ad6, 0xbc331c5c, 0x1dda24c7, 0xee928277,
};
salsa20.double_round(&x2);
try testing.expectEqualSlices(u32, z2[0..], x2[0..]);
}
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 hash function" {
try test_hash_function(1, &std.mem.zeroes([salsa20.BLOCK_SIZE]u8), &std.mem.zeroes([salsa20.BLOCK_SIZE]u8));
try test_hash_function(1, &.{
211, 159, 13, 115, 76, 55, 82, 183, 3, 117, 222, 37, 191, 187, 234, 136,
49, 237, 179, 48, 1, 106, 178, 219, 175, 199, 166, 48, 86, 16, 179, 207,
31, 240, 32, 63, 15, 83, 93, 161, 116, 147, 48, 113, 238, 55, 204, 36,
79, 201, 235, 79, 3, 81, 156, 47, 203, 26, 244, 243, 88, 118, 104, 54,
}, &.{
109, 42, 178, 168, 156, 240, 248, 238, 168, 196, 190, 203, 26, 110, 170, 154,
29, 29, 150, 26, 150, 30, 235, 249, 190, 163, 251, 48, 69, 144, 51, 57,
118, 40, 152, 157, 180, 57, 27, 94, 107, 42, 236, 35, 27, 111, 114, 114,
219, 236, 232, 135, 111, 155, 110, 18, 24, 232, 95, 158, 179, 19, 48, 202,
});
try test_hash_function(1, &.{
88, 118, 104, 54, 79, 201, 235, 79, 3, 81, 156, 47, 203, 26, 244, 243,
191, 187, 234, 136, 211, 159, 13, 115, 76, 55, 82, 183, 3, 117, 222, 37,
86, 16, 179, 207, 49, 237, 179, 48, 1, 106, 178, 219, 175, 199, 166, 48,
238, 55, 204, 36, 31, 240, 32, 63, 15, 83, 93, 161, 116, 147, 48, 113,
}, &.{
179, 19, 48, 202, 219, 236, 232, 135, 111, 155, 110, 18, 24, 232, 95, 158,
26, 110, 170, 154, 109, 42, 178, 168, 156, 240, 248, 238, 168, 196, 190, 203,
69, 144, 51, 57, 29, 29, 150, 26, 150, 30, 235, 249, 190, 163, 251, 48,
27, 111, 114, 114, 118, 40, 152, 157, 180, 57, 27, 94, 107, 42, 236, 35,
});
try test_hash_function(1000000, &.{
6, 124, 83, 146, 38, 191, 9, 50, 4, 161, 47, 222, 122, 182, 223, 185,
75, 27, 0, 216, 16, 122, 7, 89, 162, 104, 101, 147, 213, 21, 54, 95,
225, 253, 139, 176, 105, 132, 23, 116, 76, 41, 176, 207, 221, 34, 157, 108,
94, 94, 99, 52, 90, 117, 91, 220, 146, 190, 239, 143, 196, 176, 130, 186,
}, &.{
8, 18, 38, 199, 119, 76, 215, 67, 173, 127, 144, 162, 103, 212, 176, 217,
192, 19, 233, 33, 159, 197, 154, 160, 128, 243, 219, 65, 171, 136, 135, 225,
123, 11, 68, 86, 237, 82, 20, 155, 133, 189, 9, 83, 167, 116, 194, 78,
122, 127, 195, 185, 185, 204, 188, 90, 245, 9, 183, 248, 226, 85, 245, 104,
});
}
// https://cr.yp.to/snuffle/spec.pdf
test "Salsa20 expansion function" {
const key1 = [salsa20.KEY_SIZE]u8{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216,
};
const nonce1 = [salsa20.NONCE_SIZE]u8{ 101, 102, 103, 104, 105, 106, 107, 108 };
const pos1 = [salsa20.NONCE_SIZE]u8{ 109, 110, 111, 112, 113, 114, 115, 116 };
const reference1 = [salsa20.BLOCK_SIZE]u8{
69, 37, 68, 39, 41, 15, 107, 193, 255, 139, 122, 6, 170, 233, 217, 98,
89, 144, 182, 106, 21, 51, 200, 65, 239, 49, 222, 34, 215, 114, 40, 126,
104, 197, 7, 225, 197, 153, 31, 2, 102, 78, 76, 176, 84, 245, 246, 184,
177, 160, 133, 130, 6, 72, 149, 119, 192, 195, 132, 236, 234, 103, 246, 74,
};
var ctx1 = salsa20.salsa20_new(&key1, &nonce1, &pos1, &salsa20.CONSTANTS_256);
defer salsa20.salsa20_destroy(&ctx1);
var keystream_buffer: [salsa20.BLOCK_SIZE]u8 = undefined;
salsa20.serialize(salsa20.BLOCK_WORDS, ctx1.state[0..], keystream_buffer[0..]);
try testing.expectEqualSlices(u8, reference1[0..], keystream_buffer[0..]);
const key2 = [salsa20.KEY_SIZE]u8{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
};
const reference2 = [salsa20.BLOCK_SIZE]u8{
39, 173, 46, 248, 30, 200, 82, 17, 48, 67, 254, 239, 37, 18, 13, 247,
241, 200, 61, 144, 10, 55, 50, 185, 6, 47, 246, 253, 143, 86, 187, 225,
134, 85, 110, 246, 161, 163, 43, 235, 231, 94, 171, 51, 145, 214, 112, 29,
14, 232, 5, 16, 151, 140, 183, 141, 171, 9, 122, 181, 104, 182, 177, 193,
};
var ctx2 = salsa20.salsa20_new(&key2, &nonce1, &pos1, &salsa20.CONSTANTS_128);
defer salsa20.salsa20_destroy(&ctx2);
salsa20.serialize(salsa20.BLOCK_WORDS, ctx2.state[0..], keystream_buffer[0..]);
try testing.expectEqualSlices(u8, reference2[0..], keystream_buffer[0..]);
}
test "Salsa20 encryption" {
const key = [salsa20.KEY_SIZE]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
};
const nonce = [salsa20.NONCE_SIZE]u8{
0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
};
const counter = std.mem.zeroes([salsa20.COUNTER_SIZE]u8);
const plaintext = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
const reference = [_]u8{
0x13, 0xfc, 0x5e, 0x28, 0x51, 0xf0, 0x79, 0x9d, 0x97, 0x52, 0x1b, 0xa6, 0xa9, 0x37, 0x11, 0x87,
0x00, 0x95, 0x7c, 0x2e, 0xf9, 0x96, 0xe4, 0x2c, 0x59, 0x84, 0x9d, 0x45, 0x0c, 0x6e, 0xa2, 0x2e,
0x91, 0xcd, 0xcc, 0xea, 0x39, 0x1e, 0xe0, 0xa3, 0x57, 0xbb, 0x56, 0xcd, 0xf5, 0x2c, 0x56, 0xce,
0x01, 0x38, 0x07, 0xa9, 0x45, 0xb9, 0x17, 0xee, 0x6c, 0xcb, 0x18, 0xce, 0xca, 0xbe, 0x4b, 0xf4,
0x09, 0xba, 0x72, 0xfb, 0xdf, 0x90, 0xdb, 0x02, 0x8d, 0x22, 0x61, 0x7f, 0x1c, 0xa9, 0x84, 0x15,
0x6c, 0xa2, 0x72, 0x47, 0x3a, 0xf4, 0xf0, 0xe4, 0xcb, 0x3d, 0x85, 0x8b, 0x7a, 0xb4, 0x67, 0xae,
0x14, 0x71, 0x87, 0xab, 0xac, 0xb7, 0xc6, 0xe9, 0xaf, 0x6f, 0x2f, 0x47, 0x28, 0x7e, 0x2e, 0x0c,
0xb3, 0x18,
};
var ctx = salsa20.salsa20_new(&key, &nonce, &counter, &salsa20.CONSTANTS_256);
defer salsa20.salsa20_destroy(&ctx);
var buffer: [plaintext.len]u8 = undefined;
@memcpy(&buffer, plaintext);
try salsa20.encrypt_inplace(&ctx, &buffer);
try testing.expectEqualSlices(u8, reference[0..], buffer[0..]);
}
// ----------------------------------- TEST HELPERS ----------------------------------- //
fn test_quarter_round_function(y0: u32, y1: u32, y2: u32, y3: u32, z0: u32, z1: u32, z2: u32, z3: u32) !void {
var dummy_state: [salsa20.BLOCK_WORDS]u32 = undefined;
dummy_state[0] = y0;
dummy_state[1] = y1;
dummy_state[2] = y2;
dummy_state[3] = y3;
salsa20.quarter_round(&dummy_state, 0, 1, 2, 3);
try testing.expectEqualSlices(u32, &.{ z0, z1, z2, z3 }, dummy_state[0..4]);
}
fn test_hash_function(count: comptime_int, input: *const [salsa20.BLOCK_SIZE]u8, reference: *const [salsa20.BLOCK_SIZE]u8) !void {
var dummy_ctx = salsa20.Salsa20Ctx{
.key = undefined,
.nonce = undefined,
.counter = undefined,
.constants = undefined,
.state = undefined,
.working_state = undefined,
.keystream_idx = undefined,
};
salsa20.deserialize(salsa20.BLOCK_WORDS, input, dummy_ctx.state[0..]);
for (0..count) |_|
salsa20.hash_function(&dummy_ctx);
var serialized_result: [salsa20.BLOCK_SIZE]u8 = undefined;
salsa20.serialize(salsa20.BLOCK_WORDS, dummy_ctx.state[0..], serialized_result[0..]);
try testing.expectEqualSlices(u8, reference, serialized_result[0..]);
}