zig/lib/std/crypto/benchmark.zig
Frank Denis 28364166e8
crypto.hash.sha3: make permutation generic and public, add SHAKE (#14756)
Make the Keccak permutation public, as it's useful for more than
SHA-3 (kMAC, SHAKE, TurboSHAKE, TupleHash, etc).

Our Keccak implementation was accepting f as a comptime parameter,
but always used 64-bit words and 200 byte states, so it actually
didn't work with anything besides f=1600.

That has been fixed. The ability to use reduced-round versions
was also added in order to support M14 and K12.

The state was constantly converted back and forth between bytes
and words, even though only a part of the state is actually used
for absorbing and squeezing bytes. It was changed to something
similar to the other permutations we have, so we can avoid extra
copies, and eventually add vectorized implementations.

In addition, the SHAKE extendable output function (XOF) was
added (SHAKE128, SHAKE256). It is required by newer schemes,
such as the Kyber post-quantum key exchange mechanism, whose
implementation is currently blocked by SHAKE missing from our
standard library.

Breaking change: `Keccak_256` and `Keccak_512` were renamed to
`Keccak256` and `Keccak512` for consistency with all other
hash functions.
2023-03-02 06:13:40 +00:00

487 lines
16 KiB
Zig

// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
const std = @import("std");
const builtin = @import("builtin");
const mem = std.mem;
const time = std.time;
const Timer = time.Timer;
const crypto = std.crypto;
const KiB = 1024;
const MiB = 1024 * KiB;
var prng = std.rand.DefaultPrng.init(0);
const random = prng.random();
const Crypto = struct {
ty: type,
name: []const u8,
};
const hashes = [_]Crypto{
Crypto{ .ty = crypto.hash.Md5, .name = "md5" },
Crypto{ .ty = crypto.hash.Sha1, .name = "sha1" },
Crypto{ .ty = crypto.hash.sha2.Sha256, .name = "sha256" },
Crypto{ .ty = crypto.hash.sha2.Sha512, .name = "sha512" },
Crypto{ .ty = crypto.hash.sha3.Sha3_256, .name = "sha3-256" },
Crypto{ .ty = crypto.hash.sha3.Sha3_512, .name = "sha3-512" },
Crypto{ .ty = crypto.hash.sha3.Shake128, .name = "shake-128" },
Crypto{ .ty = crypto.hash.sha3.Shake256, .name = "shake-256" },
Crypto{ .ty = crypto.hash.Gimli, .name = "gimli-hash" },
Crypto{ .ty = crypto.hash.blake2.Blake2s256, .name = "blake2s" },
Crypto{ .ty = crypto.hash.blake2.Blake2b512, .name = "blake2b" },
Crypto{ .ty = crypto.hash.Blake3, .name = "blake3" },
};
pub fn benchmarkHash(comptime Hash: anytype, comptime bytes: comptime_int) !u64 {
var h = Hash.init(.{});
var block: [Hash.digest_length]u8 = undefined;
random.bytes(block[0..]);
var offset: usize = 0;
var timer = try Timer.start();
const start = timer.lap();
while (offset < bytes) : (offset += block.len) {
h.update(block[0..]);
}
mem.doNotOptimizeAway(&h);
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, bytes / elapsed_s);
return throughput;
}
const macs = [_]Crypto{
Crypto{ .ty = crypto.onetimeauth.Ghash, .name = "ghash" },
Crypto{ .ty = crypto.onetimeauth.Polyval, .name = "polyval" },
Crypto{ .ty = crypto.onetimeauth.Poly1305, .name = "poly1305" },
Crypto{ .ty = crypto.auth.hmac.HmacMd5, .name = "hmac-md5" },
Crypto{ .ty = crypto.auth.hmac.HmacSha1, .name = "hmac-sha1" },
Crypto{ .ty = crypto.auth.hmac.sha2.HmacSha256, .name = "hmac-sha256" },
Crypto{ .ty = crypto.auth.hmac.sha2.HmacSha512, .name = "hmac-sha512" },
Crypto{ .ty = crypto.auth.siphash.SipHash64(2, 4), .name = "siphash-2-4" },
Crypto{ .ty = crypto.auth.siphash.SipHash64(1, 3), .name = "siphash-1-3" },
Crypto{ .ty = crypto.auth.siphash.SipHash128(2, 4), .name = "siphash128-2-4" },
Crypto{ .ty = crypto.auth.siphash.SipHash128(1, 3), .name = "siphash128-1-3" },
Crypto{ .ty = crypto.auth.aegis.Aegis128LMac, .name = "aegis-128l mac" },
Crypto{ .ty = crypto.auth.aegis.Aegis256Mac, .name = "aegis-256 mac" },
Crypto{ .ty = crypto.auth.cmac.CmacAes128, .name = "aes-cmac" },
};
pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 {
var in: [512 * KiB]u8 = undefined;
random.bytes(in[0..]);
const key_length = if (Mac.key_length == 0) 32 else Mac.key_length;
var key: [key_length]u8 = undefined;
random.bytes(key[0..]);
var mac: [Mac.mac_length]u8 = undefined;
var offset: usize = 0;
var timer = try Timer.start();
const start = timer.lap();
while (offset < bytes) : (offset += in.len) {
Mac.create(mac[0..], in[0..], key[0..]);
mem.doNotOptimizeAway(&mac);
}
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, bytes / elapsed_s);
return throughput;
}
const exchanges = [_]Crypto{Crypto{ .ty = crypto.dh.X25519, .name = "x25519" }};
pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 {
std.debug.assert(DhKeyExchange.shared_length >= DhKeyExchange.secret_length);
var secret: [DhKeyExchange.shared_length]u8 = undefined;
random.bytes(secret[0..]);
var public: [DhKeyExchange.shared_length]u8 = undefined;
random.bytes(public[0..]);
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < exchange_count) : (i += 1) {
const out = try DhKeyExchange.scalarmult(secret, public);
mem.copy(u8, secret[0..16], out[0..16]);
mem.copy(u8, public[0..16], out[16..32]);
mem.doNotOptimizeAway(&out);
}
}
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, exchange_count / elapsed_s);
return throughput;
}
const signatures = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }};
pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
const msg = [_]u8{0} ** 64;
const key_pair = try Signature.KeyPair.create(null);
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < signatures_count) : (i += 1) {
const sig = try key_pair.sign(&msg, null);
mem.doNotOptimizeAway(&sig);
}
}
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, signatures_count / elapsed_s);
return throughput;
}
const signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }};
pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
const msg = [_]u8{0} ** 64;
const key_pair = try Signature.KeyPair.create(null);
const sig = try key_pair.sign(&msg, null);
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < signatures_count) : (i += 1) {
try sig.verify(&msg, key_pair.public_key);
mem.doNotOptimizeAway(&sig);
}
}
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, signatures_count / elapsed_s);
return throughput;
}
const batch_signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }};
pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
const msg = [_]u8{0} ** 64;
const key_pair = try Signature.KeyPair.create(null);
const sig = try key_pair.sign(&msg, null);
var batch: [64]Signature.BatchElement = undefined;
for (&batch) |*element| {
element.* = Signature.BatchElement{ .sig = sig, .msg = &msg, .public_key = key_pair.public_key };
}
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < signatures_count) : (i += 1) {
try Signature.verifyBatch(batch.len, batch);
mem.doNotOptimizeAway(&sig);
}
}
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = batch.len * @floatToInt(u64, signatures_count / elapsed_s);
return throughput;
}
const aeads = [_]Crypto{
Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" },
Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
Crypto{ .ty = crypto.aead.chacha_poly.XChaCha8Poly1305, .name = "xchacha8Poly1305" },
Crypto{ .ty = crypto.aead.salsa_poly.XSalsa20Poly1305, .name = "xsalsa20Poly1305" },
Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" },
Crypto{ .ty = crypto.aead.aegis.Aegis128L, .name = "aegis-128l" },
Crypto{ .ty = crypto.aead.aegis.Aegis256, .name = "aegis-256" },
Crypto{ .ty = crypto.aead.aes_gcm.Aes128Gcm, .name = "aes128-gcm" },
Crypto{ .ty = crypto.aead.aes_gcm.Aes256Gcm, .name = "aes256-gcm" },
Crypto{ .ty = crypto.aead.aes_ocb.Aes128Ocb, .name = "aes128-ocb" },
Crypto{ .ty = crypto.aead.aes_ocb.Aes256Ocb, .name = "aes256-ocb" },
Crypto{ .ty = crypto.aead.isap.IsapA128A, .name = "isapa128a" },
};
pub fn benchmarkAead(comptime Aead: anytype, comptime bytes: comptime_int) !u64 {
var in: [512 * KiB]u8 = undefined;
random.bytes(in[0..]);
var tag: [Aead.tag_length]u8 = undefined;
var key: [Aead.key_length]u8 = undefined;
random.bytes(key[0..]);
var nonce: [Aead.nonce_length]u8 = undefined;
random.bytes(nonce[0..]);
var offset: usize = 0;
var timer = try Timer.start();
const start = timer.lap();
while (offset < bytes) : (offset += in.len) {
Aead.encrypt(in[0..], tag[0..], in[0..], &[_]u8{}, nonce, key);
try Aead.decrypt(in[0..], in[0..], tag, &[_]u8{}, nonce, key);
}
mem.doNotOptimizeAway(&in);
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, 2 * bytes / elapsed_s);
return throughput;
}
const aes = [_]Crypto{
Crypto{ .ty = crypto.core.aes.Aes128, .name = "aes128-single" },
Crypto{ .ty = crypto.core.aes.Aes256, .name = "aes256-single" },
};
pub fn benchmarkAes(comptime Aes: anytype, comptime count: comptime_int) !u64 {
var key: [Aes.key_bits / 8]u8 = undefined;
random.bytes(key[0..]);
const ctx = Aes.initEnc(key);
var in = [_]u8{0} ** 16;
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < count) : (i += 1) {
ctx.encrypt(&in, &in);
}
}
mem.doNotOptimizeAway(&in);
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, count / elapsed_s);
return throughput;
}
const aes8 = [_]Crypto{
Crypto{ .ty = crypto.core.aes.Aes128, .name = "aes128-8" },
Crypto{ .ty = crypto.core.aes.Aes256, .name = "aes256-8" },
};
pub fn benchmarkAes8(comptime Aes: anytype, comptime count: comptime_int) !u64 {
var key: [Aes.key_bits / 8]u8 = undefined;
random.bytes(key[0..]);
const ctx = Aes.initEnc(key);
var in = [_]u8{0} ** (8 * 16);
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < count) : (i += 1) {
ctx.encryptWide(8, &in, &in);
}
}
mem.doNotOptimizeAway(&in);
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, 8 * count / elapsed_s);
return throughput;
}
const CryptoPwhash = struct {
ty: type,
params: *const anyopaque,
name: []const u8,
};
const bcrypt_params = crypto.pwhash.bcrypt.Params{ .rounds_log = 8 };
const pwhashes = [_]CryptoPwhash{
.{
.ty = crypto.pwhash.bcrypt,
.params = &bcrypt_params,
.name = "bcrypt",
},
.{
.ty = crypto.pwhash.scrypt,
.params = &crypto.pwhash.scrypt.Params.interactive,
.name = "scrypt",
},
.{
.ty = crypto.pwhash.argon2,
.params = &crypto.pwhash.argon2.Params.interactive_2id,
.name = "argon2",
},
};
fn benchmarkPwhash(
allocator: mem.Allocator,
comptime ty: anytype,
comptime params: *const anyopaque,
comptime count: comptime_int,
) !f64 {
const password = "testpass" ** 2;
const opts = .{
.allocator = allocator,
.params = @ptrCast(*const ty.Params, @alignCast(std.meta.alignment(ty.Params), params)).*,
.encoding = .phc,
};
var buf: [256]u8 = undefined;
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < count) : (i += 1) {
_ = try ty.strHash(password, opts, &buf);
mem.doNotOptimizeAway(&buf);
}
}
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = elapsed_s / count;
return throughput;
}
fn usage() void {
std.debug.print(
\\throughput_test [options]
\\
\\Options:
\\ --filter [test-name]
\\ --seed [int]
\\ --help
\\
, .{});
}
fn mode(comptime x: comptime_int) comptime_int {
return if (builtin.mode == .Debug) x / 64 else x;
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const arena_allocator = arena.allocator();
const args = try std.process.argsAlloc(arena_allocator);
var filter: ?[]u8 = "";
var i: usize = 1;
while (i < args.len) : (i += 1) {
if (std.mem.eql(u8, args[i], "--mode")) {
try stdout.print("{}\n", .{builtin.mode});
return;
} else if (std.mem.eql(u8, args[i], "--seed")) {
i += 1;
if (i == args.len) {
usage();
std.os.exit(1);
}
const seed = try std.fmt.parseUnsigned(u32, args[i], 10);
prng.seed(seed);
} else if (std.mem.eql(u8, args[i], "--filter")) {
i += 1;
if (i == args.len) {
usage();
std.os.exit(1);
}
filter = args[i];
} else if (std.mem.eql(u8, args[i], "--help")) {
usage();
return;
} else {
usage();
std.os.exit(1);
}
}
inline for (hashes) |H| {
if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
const throughput = try benchmarkHash(H.ty, mode(128 * MiB));
try stdout.print("{s:>17}: {:10} MiB/s\n", .{ H.name, throughput / (1 * MiB) });
}
}
inline for (macs) |M| {
if (filter == null or std.mem.indexOf(u8, M.name, filter.?) != null) {
const throughput = try benchmarkMac(M.ty, mode(128 * MiB));
try stdout.print("{s:>17}: {:10} MiB/s\n", .{ M.name, throughput / (1 * MiB) });
}
}
inline for (exchanges) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkKeyExchange(E.ty, mode(1000));
try stdout.print("{s:>17}: {:10} exchanges/s\n", .{ E.name, throughput });
}
}
inline for (signatures) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkSignature(E.ty, mode(1000));
try stdout.print("{s:>17}: {:10} signatures/s\n", .{ E.name, throughput });
}
}
inline for (signature_verifications) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkSignatureVerification(E.ty, mode(1000));
try stdout.print("{s:>17}: {:10} verifications/s\n", .{ E.name, throughput });
}
}
inline for (batch_signature_verifications) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkBatchSignatureVerification(E.ty, mode(1000));
try stdout.print("{s:>17}: {:10} verifications/s (batch)\n", .{ E.name, throughput });
}
}
inline for (aeads) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkAead(E.ty, mode(128 * MiB));
try stdout.print("{s:>17}: {:10} MiB/s\n", .{ E.name, throughput / (1 * MiB) });
}
}
inline for (aes) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkAes(E.ty, mode(100000000));
try stdout.print("{s:>17}: {:10} ops/s\n", .{ E.name, throughput });
}
}
inline for (aes8) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkAes8(E.ty, mode(10000000));
try stdout.print("{s:>17}: {:10} ops/s\n", .{ E.name, throughput });
}
}
inline for (pwhashes) |H| {
if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
const throughput = try benchmarkPwhash(arena_allocator, H.ty, H.params, mode(64));
try stdout.print("{s:>17}: {d:10.3} s/ops\n", .{ H.name, throughput });
}
}
}