diff --git a/core/crypto/_sha3/sha3.odin b/core/crypto/_sha3/sha3.odin index b2d032b31..cfcafdc98 100644 --- a/core/crypto/_sha3/sha3.odin +++ b/core/crypto/_sha3/sha3.odin @@ -31,21 +31,24 @@ DS_SHA3 :: 0x06 DS_SHAKE :: 0x1f DS_CSHAKE :: 0x04 +DS_TURBOSHAKE_DEFAULT :: 0x1f + Context :: struct { - st: struct #raw_union { + st: struct #raw_union { b: [200]u8, q: [25]u64, }, - pt: int, - rsiz: int, - mdlen: int, - dsbyte: byte, - is_initialized: bool, - is_finalized: bool, // For SHAKE (unlimited squeeze is allowed) + pt: int, + rsiz: int, + mdlen: int, + keccak_round_start: i32, // Round at which keccak_permute starts + dsbyte: byte, + is_initialized: bool, + is_finalized: bool, // For SHAKE (unlimited squeeze is allowed) } @(private, rodata) -keccakf_rndc := [?]u64 { +keccak_rndc := [?]u64 { 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, @@ -57,20 +60,23 @@ keccakf_rndc := [?]u64 { } @(private, rodata) -keccakf_rotc := [?]int { +keccak_rotc := [?]int { 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44, } @(private, rodata) -keccakf_piln := [?]i32 { +keccak_piln := [?]i32 { 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1, } +// Covers the Keccak permutation rounds, allows for a starting round parameter to support for example Keccak-p[1600,12] @(private) -keccakf :: proc "contextless" (st: ^[25]u64) { - i, j, r: i32 = ---, ---, --- +keccak_permute :: proc "contextless" (st: ^[25]u64, starting_round: i32 = 0) { + ensure_contextless((starting_round >= 0 && starting_round <= 23), "crypto/sha3: invalid Keccak permutation starting round") + + i, j, r: i32 = ---, ---, starting_round t: u64 = --- bc: [5]u64 = --- @@ -80,7 +86,7 @@ keccakf :: proc "contextless" (st: ^[25]u64) { } } - for r = 0; r < ROUNDS; r += 1 { + for ; r < ROUNDS; r += 1 { // theta for i = 0; i < 5; i += 1 { bc[i] = st[i] ~ st[i + 5] ~ st[i + 10] ~ st[i + 15] ~ st[i + 20] @@ -96,9 +102,9 @@ keccakf :: proc "contextless" (st: ^[25]u64) { // rho pi t = st[1] for i = 0; i < 24; i += 1 { - j = keccakf_piln[i] + j = keccak_piln[i] bc[0] = st[j] - st[j] = bits.rotate_left64(t, keccakf_rotc[i]) + st[j] = bits.rotate_left64(t, keccak_rotc[i]) t = bc[0] } @@ -112,7 +118,7 @@ keccakf :: proc "contextless" (st: ^[25]u64) { } } - st[0] ~= keccakf_rndc[r] + st[0] ~= keccak_rndc[r] } when ODIN_ENDIAN != .Little { @@ -142,7 +148,7 @@ update :: proc "contextless" (ctx: ^Context, data: []byte) { ctx.st.b[j] ~= data[i] j += 1 if j >= ctx.rsiz { - keccakf(&ctx.st.q) + keccak_permute(&ctx.st.q, ctx.keccak_round_start) j = 0 } } @@ -164,7 +170,7 @@ final :: proc "contextless" (ctx: ^Context, hash: []byte, finalize_clone: bool = ctx.st.b[ctx.pt] ~= ctx.dsbyte ctx.st.b[ctx.rsiz - 1] ~= 0x80 - keccakf(&ctx.st.q) + keccak_permute(&ctx.st.q, ctx.keccak_round_start) for i := 0; i < ctx.mdlen; i += 1 { hash[i] = ctx.st.b[i] } @@ -188,7 +194,7 @@ shake_xof :: proc "contextless" (ctx: ^Context) { ctx.st.b[ctx.pt] ~= ctx.dsbyte ctx.st.b[ctx.rsiz - 1] ~= 0x80 - keccakf(&ctx.st.q) + keccak_permute(&ctx.st.q, ctx.keccak_round_start) ctx.pt = 0 ctx.is_finalized = true // No more absorb, unlimited squeeze. @@ -201,11 +207,11 @@ shake_out :: proc "contextless" (ctx: ^Context, hash: []byte) { j := ctx.pt for i := 0; i < len(hash); i += 1 { if j >= ctx.rsiz { - keccakf(&ctx.st.q) + keccak_permute(&ctx.st.q, ctx.keccak_round_start) j = 0 } hash[i] = ctx.st.b[j] j += 1 } ctx.pt = j -} +} \ No newline at end of file diff --git a/core/crypto/_sha3/turboshake.odin b/core/crypto/_sha3/turboshake.odin new file mode 100644 index 000000000..e78498cbb --- /dev/null +++ b/core/crypto/_sha3/turboshake.odin @@ -0,0 +1,12 @@ +package _sha3 + +init_turboshake :: proc "contextless" (ctx: ^Context, d: byte, sec_strength: int) { + ensure_contextless((d >= 0x01 && d <= 0x7f), "crypto/sha3: invalid TurboSHAKE domain separation byte, allowed: >= 0x01 && <= 0x7f") + + ctx.mdlen = sec_strength / 8 + ctx.dsbyte = d + + init(ctx) + + ctx.keccak_round_start = 12 +} diff --git a/core/crypto/turboshake/turboshake.odin b/core/crypto/turboshake/turboshake.odin new file mode 100644 index 000000000..9d2b88962 --- /dev/null +++ b/core/crypto/turboshake/turboshake.odin @@ -0,0 +1,57 @@ +/* +`TurboSHAKE` XOF algorithm family. + +The SHA3 hash algorithm can be found in the crypto/sha3. + +See: +- [[ https://www.rfc-editor.org/rfc/rfc9861 ]] +*/ +package turboshake + +import "../_sha3" + +// Context is a TurboSHAKE128 or TurboSHAKE256 instance. +Context :: distinct _sha3.Context + +// init_128 initializes a Context for TurboSHAKE128. +// Uses the default domain separation byte of 0x1f, unless one is supplied. +init_128 :: proc(ctx: ^Context, d: byte = _sha3.DS_TURBOSHAKE_DEFAULT) { + _sha3.init_turboshake((^_sha3.Context)(ctx), d, 128) +} + +// init_256 initializes a Context for TurboSHAKE256. +// Uses the default domain separation byte of 0x1f, unless one is supplied. +init_256 :: proc(ctx: ^Context, d: byte = _sha3.DS_TURBOSHAKE_DEFAULT) { + _sha3.init_turboshake((^_sha3.Context)(ctx), d, 256) +} + +// write writes more data into the TurboSHAKE instance. This MUST not be +// called after any reads have been done, and attempts to do so will panic. +write :: proc(ctx: ^Context, data: []byte) { + _sha3.update((^_sha3.Context)(ctx), data) +} + +// read reads output from the TurboSHAKE instance. There is no practical +// upper limit to the amount of data that can be read from TurboSHAKE. +// After read has been called one or more times, further calls to write +// will panic. +read :: proc(ctx: ^Context, dst: []byte) { + ctx_ := (^_sha3.Context)(ctx) + + if !ctx.is_finalized { + _sha3.shake_xof(ctx_) + } + + _sha3.shake_out(ctx_, dst) +} + +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^Context) { + _sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other)) +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + _sha3.reset((^_sha3.Context)(ctx)) +} \ No newline at end of file diff --git a/tests/core/crypto/test_core_crypto_sha3_variants.odin b/tests/core/crypto/test_core_crypto_sha3_variants.odin index ca254c383..c5e04d5df 100644 --- a/tests/core/crypto/test_core_crypto_sha3_variants.odin +++ b/tests/core/crypto/test_core_crypto_sha3_variants.odin @@ -6,6 +6,7 @@ import "core:testing" import "core:crypto/kmac" import "core:crypto/shake" import "core:crypto/tuplehash" +import "core:crypto/turboshake" import "core:strings" @(test) @@ -434,4 +435,378 @@ test_kmac :: proc(t:^testing.T) { dst_str, ) } -} \ No newline at end of file +} + +@(test) +test_turboshake :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + // Test vectors are based on the repetition of the pattern `00 01 02 .. + // F9 FA` with a specific length. ptn(n) defines a string by repeating + // the pattern `00 01 02 .. F9 FA` as many times as necessary and + // truncated to n bytes, + ptn :: proc(n: int, allocator := context.temp_allocator) -> []byte { + p := make([]byte, n, allocator) + for i in 0..