diff options
| author | Yawning Angel <yawning@schwanenlied.me> | 2024-01-26 22:04:25 +0900 |
|---|---|---|
| committer | Yawning Angel <yawning@schwanenlied.me> | 2024-02-07 00:37:18 +0900 |
| commit | bc160d2eb75d73f9e46c4aa11a2cb36538ae31f9 (patch) | |
| tree | d3050b2d7939464c29ba839b557878d96dfee2cd /tests | |
| parent | b02b85d242c0d60ae02981df590a079c32557ab4 (diff) | |
tests/core/crypto: Reorganize
All of our crypto is modern now unless exiled to the legacy sub-package,
so move the test cases for the currently un-unified algorithms into the
main test driver file, and rename the benchmark driver to reflect
reality.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/core/Makefile | 2 | ||||
| -rw-r--r-- | tests/core/build.bat | 4 | ||||
| -rw-r--r-- | tests/core/crypto/test_core_crypto.odin | 443 | ||||
| -rw-r--r-- | tests/core/crypto/test_core_crypto_modern.odin | 657 | ||||
| -rw-r--r-- | tests/core/crypto/test_crypto_benchmark.odin | 236 |
5 files changed, 674 insertions, 668 deletions
diff --git a/tests/core/Makefile b/tests/core/Makefile index 3af78b55b..35321696f 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -39,7 +39,7 @@ hash_test: $(ODIN) run hash -o:speed -no-bounds-check -out:test_hash crypto_test: - $(ODIN) run crypto -o:speed -no-bounds-check -out:test_crypto_hash + $(ODIN) run crypto -o:speed -no-bounds-check -out:test_crypto noise_test: $(ODIN) run math/noise -out:test_noise diff --git a/tests/core/build.bat b/tests/core/build.bat index d12f81666..d5f528f0c 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -29,9 +29,9 @@ echo --- %PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe || exit /b echo --- -echo Running core:crypto hash tests +echo Running core:crypto tests echo --- -%PATH_TO_ODIN% run crypto %COMMON% -out:test_crypto_hash.exe || exit /b +%PATH_TO_ODIN% run crypto %COMMON% -out:test_crypto.exe || exit /b echo --- echo Running core:encoding tests diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index bab98ceb8..b11cedd69 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -8,17 +8,23 @@ package test_core_crypto zhibog, dotbmp: Initial implementation. Jeroen van Rijn: Test runner setup. - Tests for the hashing algorithms within the crypto library. + Tests for the various algorithms within the crypto library. Where possible, the official test vectors are used to validate the implementation. */ import "core:encoding/hex" import "core:fmt" +import "core:mem" import "core:os" import "core:testing" +import "core:crypto" +import "core:crypto/chacha20" +import "core:crypto/chacha20poly1305" +import "core:crypto/poly1305" import "core:crypto/siphash" import "core:crypto/shake" +import "core:crypto/x25519" TEST_count := 0 TEST_fail := 0 @@ -43,19 +49,19 @@ when ODIN_TEST { main :: proc() { t := testing.T{} - test_hash(&t) - test_shake(&t) - test_siphash_2_4(&t) + test_rand_bytes(&t) + + test_hash(&t) - // "modern" crypto tests test_chacha20(&t) - test_poly1305(&t) test_chacha20poly1305(&t) + test_poly1305(&t) + test_shake(&t) + test_siphash_2_4(&t) test_x25519(&t) - test_rand_bytes(&t) - bench_modern(&t) + bench_crypto(&t) fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) if TEST_fail > 0 { @@ -63,6 +69,427 @@ main :: proc() { } } +_PLAINTEXT_SUNSCREEN_STR := "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it." + +@(test) +test_chacha20 :: proc(t: ^testing.T) { + log(t, "Testing (X)ChaCha20") + + // Test cases taken from RFC 8439, and draft-irtf-cfrg-xchacha-03 + plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) + + key := [chacha20.KEY_SIZE]byte { + 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, + } + + nonce := [chacha20.NONCE_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, + 0x00, 0x00, 0x00, 0x00, + } + + ciphertext := [114]byte { + 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, + 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81, + 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, + 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, + 0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, + 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57, + 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab, + 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8, + 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, + 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e, + 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, + 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36, + 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, + 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42, + 0x87, 0x4d, + } + ciphertext_str := string(hex.encode(ciphertext[:], context.temp_allocator)) + + derived_ciphertext: [114]byte + ctx: chacha20.Context = --- + chacha20.init(&ctx, key[:], nonce[:]) + chacha20.seek(&ctx, 1) // The test vectors start the counter at 1. + chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) + + derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) + expect( + t, + derived_ciphertext_str == ciphertext_str, + fmt.tprintf( + "Expected %s for xor_bytes(plaintext_str), but got %s instead", + ciphertext_str, + derived_ciphertext_str, + ), + ) + + xkey := [chacha20.KEY_SIZE]byte { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + } + + xnonce := [chacha20.XNONCE_SIZE]byte { + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + } + + xciphertext := [114]byte { + 0xbd, 0x6d, 0x17, 0x9d, 0x3e, 0x83, 0xd4, 0x3b, + 0x95, 0x76, 0x57, 0x94, 0x93, 0xc0, 0xe9, 0x39, + 0x57, 0x2a, 0x17, 0x00, 0x25, 0x2b, 0xfa, 0xcc, + 0xbe, 0xd2, 0x90, 0x2c, 0x21, 0x39, 0x6c, 0xbb, + 0x73, 0x1c, 0x7f, 0x1b, 0x0b, 0x4a, 0xa6, 0x44, + 0x0b, 0xf3, 0xa8, 0x2f, 0x4e, 0xda, 0x7e, 0x39, + 0xae, 0x64, 0xc6, 0x70, 0x8c, 0x54, 0xc2, 0x16, + 0xcb, 0x96, 0xb7, 0x2e, 0x12, 0x13, 0xb4, 0x52, + 0x2f, 0x8c, 0x9b, 0xa4, 0x0d, 0xb5, 0xd9, 0x45, + 0xb1, 0x1b, 0x69, 0xb9, 0x82, 0xc1, 0xbb, 0x9e, + 0x3f, 0x3f, 0xac, 0x2b, 0xc3, 0x69, 0x48, 0x8f, + 0x76, 0xb2, 0x38, 0x35, 0x65, 0xd3, 0xff, 0xf9, + 0x21, 0xf9, 0x66, 0x4c, 0x97, 0x63, 0x7d, 0xa9, + 0x76, 0x88, 0x12, 0xf6, 0x15, 0xc6, 0x8b, 0x13, + 0xb5, 0x2e, + } + xciphertext_str := string(hex.encode(xciphertext[:], context.temp_allocator)) + + chacha20.init(&ctx, xkey[:], xnonce[:]) + chacha20.seek(&ctx, 1) + chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) + + derived_ciphertext_str = string(hex.encode(derived_ciphertext[:], context.temp_allocator)) + expect( + t, + derived_ciphertext_str == xciphertext_str, + fmt.tprintf( + "Expected %s for xor_bytes(plaintext_str), but got %s instead", + xciphertext_str, + derived_ciphertext_str, + ), + ) +} + +@(test) +test_poly1305 :: proc(t: ^testing.T) { + log(t, "Testing poly1305") + + // Test cases taken from poly1305-donna. + key := [poly1305.KEY_SIZE]byte { + 0xee, 0xa6, 0xa7, 0x25, 0x1c, 0x1e, 0x72, 0x91, + 0x6d, 0x11, 0xc2, 0xcb, 0x21, 0x4d, 0x3c, 0x25, + 0x25, 0x39, 0x12, 0x1d, 0x8e, 0x23, 0x4e, 0x65, + 0x2d, 0x65, 0x1f, 0xa4, 0xc8, 0xcf, 0xf8, 0x80, + } + + msg := [131]byte { + 0x8e, 0x99, 0x3b, 0x9f, 0x48, 0x68, 0x12, 0x73, + 0xc2, 0x96, 0x50, 0xba, 0x32, 0xfc, 0x76, 0xce, + 0x48, 0x33, 0x2e, 0xa7, 0x16, 0x4d, 0x96, 0xa4, + 0x47, 0x6f, 0xb8, 0xc5, 0x31, 0xa1, 0x18, 0x6a, + 0xc0, 0xdf, 0xc1, 0x7c, 0x98, 0xdc, 0xe8, 0x7b, + 0x4d, 0xa7, 0xf0, 0x11, 0xec, 0x48, 0xc9, 0x72, + 0x71, 0xd2, 0xc2, 0x0f, 0x9b, 0x92, 0x8f, 0xe2, + 0x27, 0x0d, 0x6f, 0xb8, 0x63, 0xd5, 0x17, 0x38, + 0xb4, 0x8e, 0xee, 0xe3, 0x14, 0xa7, 0xcc, 0x8a, + 0xb9, 0x32, 0x16, 0x45, 0x48, 0xe5, 0x26, 0xae, + 0x90, 0x22, 0x43, 0x68, 0x51, 0x7a, 0xcf, 0xea, + 0xbd, 0x6b, 0xb3, 0x73, 0x2b, 0xc0, 0xe9, 0xda, + 0x99, 0x83, 0x2b, 0x61, 0xca, 0x01, 0xb6, 0xde, + 0x56, 0x24, 0x4a, 0x9e, 0x88, 0xd5, 0xf9, 0xb3, + 0x79, 0x73, 0xf6, 0x22, 0xa4, 0x3d, 0x14, 0xa6, + 0x59, 0x9b, 0x1f, 0x65, 0x4c, 0xb4, 0x5a, 0x74, + 0xe3, 0x55, 0xa5, + } + + tag := [poly1305.TAG_SIZE]byte { + 0xf3, 0xff, 0xc7, 0x70, 0x3f, 0x94, 0x00, 0xe5, + 0x2a, 0x7d, 0xfb, 0x4b, 0x3d, 0x33, 0x05, 0xd9, + } + tag_str := string(hex.encode(tag[:], context.temp_allocator)) + + // Verify - oneshot + compare + ok := poly1305.verify(tag[:], msg[:], key[:]) + expect(t, ok, "oneshot verify call failed") + + // Sum - oneshot + derived_tag: [poly1305.TAG_SIZE]byte + poly1305.sum(derived_tag[:], msg[:], key[:]) + derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator)) + expect( + t, + derived_tag_str == tag_str, + fmt.tprintf("Expected %s for sum(msg, key), but got %s instead", tag_str, derived_tag_str), + ) + + // Incremental + mem.zero(&derived_tag, size_of(derived_tag)) + ctx: poly1305.Context = --- + poly1305.init(&ctx, key[:]) + read_lengths := [11]int{32, 64, 16, 8, 4, 2, 1, 1, 1, 1, 1} + off := 0 + for read_length in read_lengths { + to_read := msg[off:off + read_length] + poly1305.update(&ctx, to_read) + off = off + read_length + } + poly1305.final(&ctx, derived_tag[:]) + derived_tag_str = string(hex.encode(derived_tag[:], context.temp_allocator)) + expect( + t, + derived_tag_str == tag_str, + fmt.tprintf( + "Expected %s for init/update/final - incremental, but got %s instead", + tag_str, + derived_tag_str, + ), + ) +} + +@(test) +test_chacha20poly1305 :: proc(t: ^testing.T) { + log(t, "Testing chacha20poly1205") + + plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) + + aad := [12]byte { + 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, + } + + key := [chacha20poly1305.KEY_SIZE]byte { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + } + + nonce := [chacha20poly1305.NONCE_SIZE]byte { + 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47, + } + + ciphertext := [114]byte { + 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, + 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, + 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, + 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, + 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, + 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, + 0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, + 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36, + 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, + 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58, + 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, + 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc, + 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, + 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b, + 0x61, 0x16, + } + ciphertext_str := string(hex.encode(ciphertext[:], context.temp_allocator)) + + tag := [chacha20poly1305.TAG_SIZE]byte { + 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, + 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91, + } + tag_str := string(hex.encode(tag[:], context.temp_allocator)) + + derived_tag: [chacha20poly1305.TAG_SIZE]byte + derived_ciphertext: [114]byte + + chacha20poly1305.encrypt( + derived_ciphertext[:], + derived_tag[:], + key[:], + nonce[:], + aad[:], + plaintext, + ) + + derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) + expect( + t, + derived_ciphertext_str == ciphertext_str, + fmt.tprintf( + "Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead", + ciphertext_str, + derived_ciphertext_str, + ), + ) + + derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator)) + expect( + t, + derived_tag_str == tag_str, + fmt.tprintf( + "Expected tag %s for encrypt(aad, plaintext), but got %s instead", + tag_str, + derived_tag_str, + ), + ) + + derived_plaintext: [114]byte + ok := chacha20poly1305.decrypt( + derived_plaintext[:], + tag[:], + key[:], + nonce[:], + aad[:], + ciphertext[:], + ) + derived_plaintext_str := string(derived_plaintext[:]) + expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)") + expect( + t, + derived_plaintext_str == _PLAINTEXT_SUNSCREEN_STR, + fmt.tprintf( + "Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead", + _PLAINTEXT_SUNSCREEN_STR, + derived_plaintext_str, + ), + ) + + derived_ciphertext[0] ~= 0xa5 + ok = chacha20poly1305.decrypt( + derived_plaintext[:], + tag[:], + key[:], + nonce[:], + aad[:], + derived_ciphertext[:], + ) + expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)") + + aad[0] ~= 0xa5 + ok = chacha20poly1305.decrypt( + derived_plaintext[:], + tag[:], + key[:], + nonce[:], + aad[:], + ciphertext[:], + ) + expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)") +} + +TestECDH :: struct { + scalar: string, + point: string, + product: string, +} + +@(test) +test_x25519 :: proc(t: ^testing.T) { + log(t, "Testing X25519") + + // Local copy of this so that the base point doesn't need to be exported. + _BASE_POINT: [32]byte = { + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + } + + test_vectors := [?]TestECDH { + // Test vectors from RFC 7748 + { + "a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4", + "e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c", + "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552", + }, + { + "4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d", + "e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493", + "95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957", + }, + } + for v, _ in test_vectors { + scalar, _ := hex.decode(transmute([]byte)(v.scalar), context.temp_allocator) + point, _ := hex.decode(transmute([]byte)(v.point), context.temp_allocator) + + derived_point: [x25519.POINT_SIZE]byte + x25519.scalarmult(derived_point[:], scalar[:], point[:]) + derived_point_str := string(hex.encode(derived_point[:], context.temp_allocator)) + + expect( + t, + derived_point_str == v.product, + fmt.tprintf( + "Expected %s for %s * %s, but got %s instead", + v.product, + v.scalar, + v.point, + derived_point_str, + ), + ) + + // Abuse the test vectors to sanity-check the scalar-basepoint multiply. + p1, p2: [x25519.POINT_SIZE]byte + x25519.scalarmult_basepoint(p1[:], scalar[:]) + x25519.scalarmult(p2[:], scalar[:], _BASE_POINT[:]) + p1_str := string(hex.encode(p1[:], context.temp_allocator)) + p2_str := string(hex.encode(p2[:], context.temp_allocator)) + expect( + t, + p1_str == p2_str, + fmt.tprintf( + "Expected %s for %s * basepoint, but got %s instead", + p2_str, + v.scalar, + p1_str, + ), + ) + } + + // TODO/tests: Run the wycheproof test vectors, once I figure out + // how to work with JSON. +} + +@(test) +test_rand_bytes :: proc(t: ^testing.T) { + log(t, "Testing rand_bytes") + + if ODIN_OS != .Linux { + log(t, "rand_bytes not supported - skipping") + return + } + + allocator := context.allocator + + buf := make([]byte, 1 << 25, allocator) + defer delete(buf) + + // Testing a CSPRNG for correctness is incredibly involved and + // beyond the scope of an implementation that offloads + // responsibility for correctness to the OS. + // + // Just attempt to randomize a sufficiently large buffer, where + // sufficiently large is: + // * Larger than the maximum getentropy request size (256 bytes). + // * Larger than the maximum getrandom request size (2^25 - 1 bytes). + // + // While theoretically non-deterministic, if this fails, chances + // are the CSPRNG is busted. + seems_ok := false + for i := 0; i < 256; i = i + 1 { + mem.zero_explicit(raw_data(buf), len(buf)) + crypto.rand_bytes(buf) + + if buf[0] != 0 && buf[len(buf) - 1] != 0 { + seems_ok = true + break + } + } + + expect( + t, + seems_ok, + "Expected to randomize the head and tail of the buffer within a handful of attempts", + ) +} + TestXOF :: struct { sec_strength: int, output: string, diff --git a/tests/core/crypto/test_core_crypto_modern.odin b/tests/core/crypto/test_core_crypto_modern.odin deleted file mode 100644 index 50da734c1..000000000 --- a/tests/core/crypto/test_core_crypto_modern.odin +++ /dev/null @@ -1,657 +0,0 @@ -package test_core_crypto - -import "core:crypto" -import "core:encoding/hex" -import "core:fmt" -import "core:mem" -import "core:testing" -import "core:time" - -import "core:crypto/chacha20" -import "core:crypto/chacha20poly1305" -import "core:crypto/poly1305" -import "core:crypto/x25519" - -_PLAINTEXT_SUNSCREEN_STR := "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it." - -@(test) -test_chacha20 :: proc(t: ^testing.T) { - log(t, "Testing (X)ChaCha20") - - // Test cases taken from RFC 8439, and draft-irtf-cfrg-xchacha-03 - plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) - - key := [chacha20.KEY_SIZE]byte { - 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, - } - - nonce := [chacha20.NONCE_SIZE]byte { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, - 0x00, 0x00, 0x00, 0x00, - } - - ciphertext := [114]byte { - 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, - 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81, - 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, - 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, - 0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, - 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57, - 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab, - 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8, - 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, - 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e, - 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, - 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36, - 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, - 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42, - 0x87, 0x4d, - } - ciphertext_str := string(hex.encode(ciphertext[:], context.temp_allocator)) - - derived_ciphertext: [114]byte - ctx: chacha20.Context = --- - chacha20.init(&ctx, key[:], nonce[:]) - chacha20.seek(&ctx, 1) // The test vectors start the counter at 1. - chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) - - derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - expect( - t, - derived_ciphertext_str == ciphertext_str, - fmt.tprintf( - "Expected %s for xor_bytes(plaintext_str), but got %s instead", - ciphertext_str, - derived_ciphertext_str, - ), - ) - - xkey := [chacha20.KEY_SIZE]byte { - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, - 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, - 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, - } - - xnonce := [chacha20.XNONCE_SIZE]byte { - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, - } - - xciphertext := [114]byte { - 0xbd, 0x6d, 0x17, 0x9d, 0x3e, 0x83, 0xd4, 0x3b, - 0x95, 0x76, 0x57, 0x94, 0x93, 0xc0, 0xe9, 0x39, - 0x57, 0x2a, 0x17, 0x00, 0x25, 0x2b, 0xfa, 0xcc, - 0xbe, 0xd2, 0x90, 0x2c, 0x21, 0x39, 0x6c, 0xbb, - 0x73, 0x1c, 0x7f, 0x1b, 0x0b, 0x4a, 0xa6, 0x44, - 0x0b, 0xf3, 0xa8, 0x2f, 0x4e, 0xda, 0x7e, 0x39, - 0xae, 0x64, 0xc6, 0x70, 0x8c, 0x54, 0xc2, 0x16, - 0xcb, 0x96, 0xb7, 0x2e, 0x12, 0x13, 0xb4, 0x52, - 0x2f, 0x8c, 0x9b, 0xa4, 0x0d, 0xb5, 0xd9, 0x45, - 0xb1, 0x1b, 0x69, 0xb9, 0x82, 0xc1, 0xbb, 0x9e, - 0x3f, 0x3f, 0xac, 0x2b, 0xc3, 0x69, 0x48, 0x8f, - 0x76, 0xb2, 0x38, 0x35, 0x65, 0xd3, 0xff, 0xf9, - 0x21, 0xf9, 0x66, 0x4c, 0x97, 0x63, 0x7d, 0xa9, - 0x76, 0x88, 0x12, 0xf6, 0x15, 0xc6, 0x8b, 0x13, - 0xb5, 0x2e, - } - xciphertext_str := string(hex.encode(xciphertext[:], context.temp_allocator)) - - chacha20.init(&ctx, xkey[:], xnonce[:]) - chacha20.seek(&ctx, 1) - chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) - - derived_ciphertext_str = string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - expect( - t, - derived_ciphertext_str == xciphertext_str, - fmt.tprintf( - "Expected %s for xor_bytes(plaintext_str), but got %s instead", - xciphertext_str, - derived_ciphertext_str, - ), - ) -} - -@(test) -test_poly1305 :: proc(t: ^testing.T) { - log(t, "Testing poly1305") - - // Test cases taken from poly1305-donna. - key := [poly1305.KEY_SIZE]byte { - 0xee, 0xa6, 0xa7, 0x25, 0x1c, 0x1e, 0x72, 0x91, - 0x6d, 0x11, 0xc2, 0xcb, 0x21, 0x4d, 0x3c, 0x25, - 0x25, 0x39, 0x12, 0x1d, 0x8e, 0x23, 0x4e, 0x65, - 0x2d, 0x65, 0x1f, 0xa4, 0xc8, 0xcf, 0xf8, 0x80, - } - - msg := [131]byte { - 0x8e, 0x99, 0x3b, 0x9f, 0x48, 0x68, 0x12, 0x73, - 0xc2, 0x96, 0x50, 0xba, 0x32, 0xfc, 0x76, 0xce, - 0x48, 0x33, 0x2e, 0xa7, 0x16, 0x4d, 0x96, 0xa4, - 0x47, 0x6f, 0xb8, 0xc5, 0x31, 0xa1, 0x18, 0x6a, - 0xc0, 0xdf, 0xc1, 0x7c, 0x98, 0xdc, 0xe8, 0x7b, - 0x4d, 0xa7, 0xf0, 0x11, 0xec, 0x48, 0xc9, 0x72, - 0x71, 0xd2, 0xc2, 0x0f, 0x9b, 0x92, 0x8f, 0xe2, - 0x27, 0x0d, 0x6f, 0xb8, 0x63, 0xd5, 0x17, 0x38, - 0xb4, 0x8e, 0xee, 0xe3, 0x14, 0xa7, 0xcc, 0x8a, - 0xb9, 0x32, 0x16, 0x45, 0x48, 0xe5, 0x26, 0xae, - 0x90, 0x22, 0x43, 0x68, 0x51, 0x7a, 0xcf, 0xea, - 0xbd, 0x6b, 0xb3, 0x73, 0x2b, 0xc0, 0xe9, 0xda, - 0x99, 0x83, 0x2b, 0x61, 0xca, 0x01, 0xb6, 0xde, - 0x56, 0x24, 0x4a, 0x9e, 0x88, 0xd5, 0xf9, 0xb3, - 0x79, 0x73, 0xf6, 0x22, 0xa4, 0x3d, 0x14, 0xa6, - 0x59, 0x9b, 0x1f, 0x65, 0x4c, 0xb4, 0x5a, 0x74, - 0xe3, 0x55, 0xa5, - } - - tag := [poly1305.TAG_SIZE]byte { - 0xf3, 0xff, 0xc7, 0x70, 0x3f, 0x94, 0x00, 0xe5, - 0x2a, 0x7d, 0xfb, 0x4b, 0x3d, 0x33, 0x05, 0xd9, - } - tag_str := string(hex.encode(tag[:], context.temp_allocator)) - - // Verify - oneshot + compare - ok := poly1305.verify(tag[:], msg[:], key[:]) - expect(t, ok, "oneshot verify call failed") - - // Sum - oneshot - derived_tag: [poly1305.TAG_SIZE]byte - poly1305.sum(derived_tag[:], msg[:], key[:]) - derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator)) - expect( - t, - derived_tag_str == tag_str, - fmt.tprintf("Expected %s for sum(msg, key), but got %s instead", tag_str, derived_tag_str), - ) - - // Incremental - mem.zero(&derived_tag, size_of(derived_tag)) - ctx: poly1305.Context = --- - poly1305.init(&ctx, key[:]) - read_lengths := [11]int{32, 64, 16, 8, 4, 2, 1, 1, 1, 1, 1} - off := 0 - for read_length in read_lengths { - to_read := msg[off:off + read_length] - poly1305.update(&ctx, to_read) - off = off + read_length - } - poly1305.final(&ctx, derived_tag[:]) - derived_tag_str = string(hex.encode(derived_tag[:], context.temp_allocator)) - expect( - t, - derived_tag_str == tag_str, - fmt.tprintf( - "Expected %s for init/update/final - incremental, but got %s instead", - tag_str, - derived_tag_str, - ), - ) -} - -@(test) -test_chacha20poly1305 :: proc(t: ^testing.T) { - log(t, "Testing chacha20poly1205") - - plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) - - aad := [12]byte { - 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, - 0xc4, 0xc5, 0xc6, 0xc7, - } - - key := [chacha20poly1305.KEY_SIZE]byte { - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, - 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, - 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, - } - - nonce := [chacha20poly1305.NONCE_SIZE]byte { - 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, - 0x44, 0x45, 0x46, 0x47, - } - - ciphertext := [114]byte { - 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, - 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, - 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, - 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, - 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, - 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, - 0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, - 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36, - 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, - 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58, - 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, - 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc, - 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, - 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b, - 0x61, 0x16, - } - ciphertext_str := string(hex.encode(ciphertext[:], context.temp_allocator)) - - tag := [chacha20poly1305.TAG_SIZE]byte { - 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, - 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91, - } - tag_str := string(hex.encode(tag[:], context.temp_allocator)) - - derived_tag: [chacha20poly1305.TAG_SIZE]byte - derived_ciphertext: [114]byte - - chacha20poly1305.encrypt( - derived_ciphertext[:], - derived_tag[:], - key[:], - nonce[:], - aad[:], - plaintext, - ) - - derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - expect( - t, - derived_ciphertext_str == ciphertext_str, - fmt.tprintf( - "Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead", - ciphertext_str, - derived_ciphertext_str, - ), - ) - - derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator)) - expect( - t, - derived_tag_str == tag_str, - fmt.tprintf( - "Expected tag %s for encrypt(aad, plaintext), but got %s instead", - tag_str, - derived_tag_str, - ), - ) - - derived_plaintext: [114]byte - ok := chacha20poly1305.decrypt( - derived_plaintext[:], - tag[:], - key[:], - nonce[:], - aad[:], - ciphertext[:], - ) - derived_plaintext_str := string(derived_plaintext[:]) - expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)") - expect( - t, - derived_plaintext_str == _PLAINTEXT_SUNSCREEN_STR, - fmt.tprintf( - "Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead", - _PLAINTEXT_SUNSCREEN_STR, - derived_plaintext_str, - ), - ) - - derived_ciphertext[0] ~= 0xa5 - ok = chacha20poly1305.decrypt( - derived_plaintext[:], - tag[:], - key[:], - nonce[:], - aad[:], - derived_ciphertext[:], - ) - expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)") - - aad[0] ~= 0xa5 - ok = chacha20poly1305.decrypt( - derived_plaintext[:], - tag[:], - key[:], - nonce[:], - aad[:], - ciphertext[:], - ) - expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)") -} - -TestECDH :: struct { - scalar: string, - point: string, - product: string, -} - -@(test) -test_x25519 :: proc(t: ^testing.T) { - log(t, "Testing X25519") - - // Local copy of this so that the base point doesn't need to be exported. - _BASE_POINT: [32]byte = { - 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - } - - test_vectors := [?]TestECDH { - // Test vectors from RFC 7748 - TestECDH { - "a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4", - "e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c", - "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552", - }, - TestECDH { - "4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d", - "e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493", - "95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957", - }, - } - for v, _ in test_vectors { - scalar, _ := hex.decode(transmute([]byte)(v.scalar), context.temp_allocator) - point, _ := hex.decode(transmute([]byte)(v.point), context.temp_allocator) - - derived_point: [x25519.POINT_SIZE]byte - x25519.scalarmult(derived_point[:], scalar[:], point[:]) - derived_point_str := string(hex.encode(derived_point[:], context.temp_allocator)) - - expect( - t, - derived_point_str == v.product, - fmt.tprintf( - "Expected %s for %s * %s, but got %s instead", - v.product, - v.scalar, - v.point, - derived_point_str, - ), - ) - - // Abuse the test vectors to sanity-check the scalar-basepoint multiply. - p1, p2: [x25519.POINT_SIZE]byte - x25519.scalarmult_basepoint(p1[:], scalar[:]) - x25519.scalarmult(p2[:], scalar[:], _BASE_POINT[:]) - p1_str := string(hex.encode(p1[:], context.temp_allocator)) - p2_str := string(hex.encode(p2[:], context.temp_allocator)) - expect( - t, - p1_str == p2_str, - fmt.tprintf( - "Expected %s for %s * basepoint, but got %s instead", - p2_str, - v.scalar, - p1_str, - ), - ) - } - - // TODO/tests: Run the wycheproof test vectors, once I figure out - // how to work with JSON. -} - -@(test) -test_rand_bytes :: proc(t: ^testing.T) { - log(t, "Testing rand_bytes") - - if ODIN_OS != .Linux { - log(t, "rand_bytes not supported - skipping") - return - } - - allocator := context.allocator - - buf := make([]byte, 1 << 25, allocator) - defer delete(buf) - - // Testing a CSPRNG for correctness is incredibly involved and - // beyond the scope of an implementation that offloads - // responsibility for correctness to the OS. - // - // Just attempt to randomize a sufficiently large buffer, where - // sufficiently large is: - // * Larger than the maximum getentropy request size (256 bytes). - // * Larger than the maximum getrandom request size (2^25 - 1 bytes). - // - // While theoretically non-deterministic, if this fails, chances - // are the CSPRNG is busted. - seems_ok := false - for i := 0; i < 256; i = i + 1 { - mem.zero_explicit(raw_data(buf), len(buf)) - crypto.rand_bytes(buf) - - if buf[0] != 0 && buf[len(buf) - 1] != 0 { - seems_ok = true - break - } - } - - expect( - t, - seems_ok, - "Expected to randomize the head and tail of the buffer within a handful of attempts", - ) -} - -@(test) -bench_modern :: proc(t: ^testing.T) { - fmt.println("Starting benchmarks:") - - bench_chacha20(t) - bench_poly1305(t) - bench_chacha20poly1305(t) - bench_x25519(t) -} - -_setup_sized_buf :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - assert(options != nil) - - options.input = make([]u8, options.bytes, allocator) - return nil if len(options.input) == options.bytes else .Allocation_Error -} - -_teardown_sized_buf :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - assert(options != nil) - - delete(options.input) - return nil -} - -_benchmark_chacha20 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [chacha20.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - nonce := [chacha20.NONCE_SIZE]byte { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - } - - ctx: chacha20.Context = --- - chacha20.init(&ctx, key[:], nonce[:]) - - for _ in 0 ..= options.rounds { - chacha20.xor_bytes(&ctx, buf, buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - return nil -} - -_benchmark_poly1305 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [poly1305.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - - tag: [poly1305.TAG_SIZE]byte = --- - for _ in 0 ..= options.rounds { - poly1305.sum(tag[:], buf, key[:]) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - //options.hash = u128(h) - return nil -} - -_benchmark_chacha20poly1305 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [chacha20.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - nonce := [chacha20.NONCE_SIZE]byte { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - } - - tag: [chacha20poly1305.TAG_SIZE]byte = --- - - for _ in 0 ..= options.rounds { - chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - return nil -} - -benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { - fmt.printf( - "\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", - name, - options.rounds, - options.processed, - time.duration_nanoseconds(options.duration), - options.rounds_per_second, - options.megabytes_per_second, - ) -} - -bench_chacha20 :: proc(t: ^testing.T) { - name := "ChaCha20 64 bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_chacha20, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - expect(t, err == nil, name) - benchmark_print(name, options) - - name = "ChaCha20 1024 bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - benchmark_print(name, options) - - name = "ChaCha20 65536 bytes" - options.bytes = 65536 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_poly1305 :: proc(t: ^testing.T) { - name := "Poly1305 64 zero bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_poly1305, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - expect(t, err == nil, name) - benchmark_print(name, options) - - name = "Poly1305 1024 zero bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_chacha20poly1305 :: proc(t: ^testing.T) { - name := "chacha20poly1305 64 bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_chacha20poly1305, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - expect(t, err == nil, name) - benchmark_print(name, options) - - name = "chacha20poly1305 1024 bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - benchmark_print(name, options) - - name = "chacha20poly1305 65536 bytes" - options.bytes = 65536 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_x25519 :: proc(t: ^testing.T) { - point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" - scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" - - point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator) - scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator) - out: [x25519.POINT_SIZE]byte = --- - - iters :: 10000 - start := time.now() - for i := 0; i < iters; i = i + 1 { - x25519.scalarmult(out[:], scalar[:], point[:]) - } - elapsed := time.since(start) - - log( - t, - fmt.tprintf("x25519.scalarmult: ~%f us/op", time.duration_microseconds(elapsed) / iters), - ) -} diff --git a/tests/core/crypto/test_crypto_benchmark.odin b/tests/core/crypto/test_crypto_benchmark.odin new file mode 100644 index 000000000..cadcf8bec --- /dev/null +++ b/tests/core/crypto/test_crypto_benchmark.odin @@ -0,0 +1,236 @@ +package test_core_crypto + +import "core:encoding/hex" +import "core:fmt" +import "core:testing" +import "core:time" + +import "core:crypto/chacha20" +import "core:crypto/chacha20poly1305" +import "core:crypto/poly1305" +import "core:crypto/x25519" + +// Cryptographic primitive benchmarks. + +@(test) +bench_crypto :: proc(t: ^testing.T) { + fmt.println("Starting benchmarks:") + + bench_chacha20(t) + bench_poly1305(t) + bench_chacha20poly1305(t) + bench_x25519(t) +} + +_setup_sized_buf :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + assert(options != nil) + + options.input = make([]u8, options.bytes, allocator) + return nil if len(options.input) == options.bytes else .Allocation_Error +} + +_teardown_sized_buf :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + assert(options != nil) + + delete(options.input) + return nil +} + +_benchmark_chacha20 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [chacha20.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + nonce := [chacha20.NONCE_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + } + + ctx: chacha20.Context = --- + chacha20.init(&ctx, key[:], nonce[:]) + + for _ in 0 ..= options.rounds { + chacha20.xor_bytes(&ctx, buf, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +_benchmark_poly1305 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [poly1305.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + + tag: [poly1305.TAG_SIZE]byte = --- + for _ in 0 ..= options.rounds { + poly1305.sum(tag[:], buf, key[:]) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + //options.hash = u128(h) + return nil +} + +_benchmark_chacha20poly1305 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [chacha20.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + nonce := [chacha20.NONCE_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + } + + tag: [chacha20poly1305.TAG_SIZE]byte = --- + + for _ in 0 ..= options.rounds { + chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { + fmt.printf( + "\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", + name, + options.rounds, + options.processed, + time.duration_nanoseconds(options.duration), + options.rounds_per_second, + options.megabytes_per_second, + ) +} + +bench_chacha20 :: proc(t: ^testing.T) { + name := "ChaCha20 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_chacha20, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + expect(t, err == nil, name) + benchmark_print(name, options) + + name = "ChaCha20 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + expect(t, err == nil, name) + benchmark_print(name, options) + + name = "ChaCha20 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + expect(t, err == nil, name) + benchmark_print(name, options) +} + +bench_poly1305 :: proc(t: ^testing.T) { + name := "Poly1305 64 zero bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_poly1305, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + expect(t, err == nil, name) + benchmark_print(name, options) + + name = "Poly1305 1024 zero bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + expect(t, err == nil, name) + benchmark_print(name, options) +} + +bench_chacha20poly1305 :: proc(t: ^testing.T) { + name := "chacha20poly1305 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_chacha20poly1305, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + expect(t, err == nil, name) + benchmark_print(name, options) + + name = "chacha20poly1305 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + expect(t, err == nil, name) + benchmark_print(name, options) + + name = "chacha20poly1305 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + expect(t, err == nil, name) + benchmark_print(name, options) +} + +bench_x25519 :: proc(t: ^testing.T) { + point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" + + point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator) + scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator) + out: [x25519.POINT_SIZE]byte = --- + + iters :: 10000 + start := time.now() + for i := 0; i < iters; i = i + 1 { + x25519.scalarmult(out[:], scalar[:], point[:]) + } + elapsed := time.since(start) + + log( + t, + fmt.tprintf("x25519.scalarmult: ~%f us/op", time.duration_microseconds(elapsed) / iters), + ) +} |