[core:crypto] Add TurboSHAKE

This commit is contained in:
zhibog
2026-07-14 16:25:05 +02:00
committed by unknown
parent a6af2551c6
commit 2efcd11ab4
4 changed files with 472 additions and 22 deletions

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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,
)
}
}
}
@(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..<n {
p[i] = byte(i % 251) // repeats 00..FA
}
return p
}
test_vectors := []struct {
sec_strength: int,
domainsep: byte,
output: string,
msg: []byte,
out_len: int,
last_only: bool,
} {
// TurboSHAKE128
// - https://datatracker.ietf.org/doc/html/rfc9861#section-5
// TurboSHAKE128(M=`00`^0, D=`1F`, 32)
{
128,
0x1f,
"1e415f1c5983aff2169217277d17bb538cd945a397ddec541f1ce41af2c1b74c",
[]byte{},
32,
false,
},
// TurboSHAKE128(M=`00`^0, D=`1F`, 64)
{
128,
0x1f,
"1e415f1c5983aff2169217277d17bb538cd945a397ddec541f1ce41af2c1b74c3e8ccae2a4dae56c84a04c2385c03c15e8193bdf58737363321691c05462c8df",
[]byte{},
64,
false,
},
// TurboSHAKE128(M=`00`^0, D=`1F`, 10032), last 32 bytes
{
128,
0x1f,
"a3b9b0385900ce761f22aed548e754da10a5242d62e8c658e3f3a923a7555607",
[]byte{},
10032,
true,
},
// TurboSHAKE128(M=ptn(17**0), D=`1F`, 32)
{
128,
0x1f,
"55cedd6f60af7bb29a4042ae832ef3f58db7299f893ebb9247247d856958daa9",
ptn(1),
32,
false,
},
// TurboSHAKE128(M=ptn(17**1), D=`1F`, 32)
{
128,
0x1f,
"9c97d036a3bac819db70ede0ca554ec6e4c2a1a4ffbfd9ec269ca6a111161233",
ptn(17),
32,
false,
},
// TurboSHAKE128(M=ptn(17**2), D=`1F`, 32)
{
128,
0x1f,
"96c77c279e0126f7fc07c9b07f5cdae1e0be60bdbe10620040e75d7223a624d2",
ptn(17 * 17),
32,
false,
},
// TurboSHAKE128(M=ptn(17**3), D=`1F`, 32)
{
128,
0x1f,
"d4976eb56bcf118520582b709f73e1d6853e001fdaf80e1b13e0d0599d5fb372",
ptn(17 * 17 * 17),
32,
false,
},
// TurboSHAKE128(M=ptn(17**4), D=`1F`, 32)
{
128,
0x1f,
"da67c7039e98bf530cf7a37830c6664e14cbab7f540f58403b1b82951318ee5c",
ptn(17 * 17 * 17 * 17),
32,
false,
},
// TurboSHAKE128(M=ptn(17**5), D=`1F`, 32)
{
128,
0x1f,
"b97a906fbf83ef7c812517abf3b2d0aea0c4f60318ce11cf103925127f59eecd",
ptn(17 * 17 * 17 * 17 * 17),
32,
false,
},
// TurboSHAKE128(M=ptn(17**6), D=`1F`, 32)
{
128,
0x1f,
"35cd494adeded2f25239af09a7b8ef0c4d1ca4fe2d1ac370fa63216fe7b4c2b1",
ptn(17 * 17 * 17 * 17 * 17 * 17),
32,
false,
},
// TurboSHAKE128(M=`FF FF FF`, D=`01`, 32)
{
128,
0x01,
"bf323f940494e88ee1c540fe660be8a0c93f43d15ec006998462fa994eed5dab",
[]byte{0xff, 0xff, 0xff},
32,
false,
},
// TurboSHAKE128(M=`FF`, D=`06`, 32)
{
128,
0x06,
"8ec9c66465ed0d4a6c35d13506718d687a25cb05c74cca1e42501abd83874a67",
[]byte{0xff},
32,
false,
},
// TurboSHAKE128(M=`FF FF FF`, D=`07`, 32)
{
128,
0x07,
"b658576001cad9b1e5f399a9f77723bba05458042d68206f7252682dba3663ed",
[]byte{0xff, 0xff, 0xff},
32,
false,
},
// TurboSHAKE128(M=`FF FF FF FF FF FF FF`, D=`0B`, 32)
{
128,
0x0b,
"8deeaa1aec47ccee569f659c21dfa8e112db3cee37b18178b2acd805b799cc37",
[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
32,
false,
},
// TurboSHAKE128(M=`FF`, D=`30`, 32)
{
128,
0x30,
"553122e2135e363c3292bed2c6421fa232bab03daa07c7d6636603286506325b",
[]byte{0xff},
32,
false,
},
// TurboSHAKE128(M=`FF FF FF`, D=`7F`, 32)
{
128,
0x7f,
"16274cc656d44cefd422395d0f9053bda6d28e122aba15c765e5ad0e6eaf26f9",
[]byte{0xff, 0xff, 0xff},
32,
false,
},
// TurboSHAKE256
// - https://datatracker.ietf.org/doc/html/rfc9861#section-5
// TurboSHAKE256(M=`00`^0, D=`1F`, 64)
{
256,
0x1f,
"367a329dafea871c7802ec67f905ae13c57695dc2c6663c61035f59a18f8e7db11edc0e12e91ea60eb6b32df06dd7f002fbafabb6e13ec1cc20d995547600db0",
[]byte{},
64,
false,
},
// TurboSHAKE256(M=`00`^0, D=`1F`, 10032), last 32 bytes
{
256,
0x1f,
"abefa11630c661269249742685ec082f207265dccf2f43534e9c61ba0c9d1d75",
[]byte{},
10032,
true,
},
// TurboSHAKE256(M=ptn(17**0), D=`1F`, 64)
{
256,
0x1f,
"3e1712f928f8eaf1054632b2aa0a246ed8b0c378728f60bc970410155c28820e90cc90d8a3006aa2372c5c5ea176b0682bf22bae7467ac94f74d43d39b0482e2",
ptn(1),
64,
false,
},
// TurboSHAKE256(M=ptn(17**1), D=`1F`, 64)
{
256,
0x1f,
"b3bab0300e6a191fbe6137939835923578794ea54843f5011090fa2f3780a9e5cb22c59d78b40a0fbff9e672c0fbe0970bd2c845091c6044d687054da5d8e9c7",
ptn(17),
64,
false,
},
// TurboSHAKE256(M=ptn(17**2), D=`1F`, 64)
{
256,
0x1f,
"66b810db8e90780424c0847372fdc95710882fde31c6df75beb9d4cd9305cfcae35e7b83e8b7e6eb4b78605880116316fe2c078a09b94ad7b8213c0a738b65c0",
ptn(17 * 17),
64,
false,
},
// TurboSHAKE256(M=ptn(17**3), D=`1F`, 64)
{
256,
0x1f,
"c74ebc919a5b3b0dd1228185ba02d29ef442d69d3d4276a93efe0bf9a16a7dc0cd4eabadab8cd7a5edd96695f5d360abe09e2c6511a3ec397da3b76b9e1674fb",
ptn(17 * 17 * 17),
64,
false,
},
// TurboSHAKE256(M=ptn(17**4), D=`1F`, 64)
{
256,
0x1f,
"02cc3a8897e6f4f6ccb6fd46631b1f5207b66c6de9c7b55b2d1a23134a170afdac234eaba9a77cff88c1f020b73724618c5687b362c430b248cd38647f848a1d",
ptn(17 * 17 * 17 * 17),
64,
false,
},
// TurboSHAKE256(M=ptn(17**5), D=`1F`, 64)
{
256,
0x1f,
"add53b06543e584b5823f626996aee50fe45ed15f20243a7165485acb4aa76b4ffda75cedf6d8cdc95c332bd56f4b986b58bb17d1778bfc1b1a97545cdf4ec9f",
ptn(17 * 17 * 17 * 17 * 17),
64,
false,
},
// TurboSHAKE256(M=ptn(17**6), D=`1F`, 64)
{
256,
0x1f,
"9e11bc59c24e73993c1484ec66358ef71db74aefd84e123f7800ba9c4853e02cfe701d9e6bb765a304f0dc34a4ee3ba82c410f0da70e86bfbd90ea877c2d6104",
ptn(17 * 17 * 17 * 17 * 17 * 17),
64,
false,
},
// TurboSHAKE256(M=`FF FF FF`, D=`01`, 64)
{
256,
0x01,
"d21c6fbbf587fa2282f29aea620175fb0257413af78a0b1b2a87419ce031d933ae7a4d383327a8a17641a34f8a1d1003ad7da6b72dba84bb62fef28f62f12424",
[]byte{0xff, 0xff, 0xff},
64,
false,
},
// TurboSHAKE256(M=`FF`, D=`06`, 64)
{
256,
0x06,
"738d7b4e37d18b7f22ad1b5313e357e3dd7d07056a26a303c433fa3533455280f4f5a7d4f700efb437fe6d281405e07be32a0a972e22e63adc1b090daefe004b",
[]byte{0xff},
64,
false,
},
// TurboSHAKE256(M=`FF FF FF`, D=`07`, 64)
{
256,
0x07,
"18b3b5b7061c2e67c1753a00e6ad7ed7ba1c906cf93efb7092eaf27fbeebb755ae6e292493c110e48d260028492b8e09b5500612b8f2578985ded5357d00ec67",
[]byte{0xff, 0xff, 0xff},
64,
false,
},
// TurboSHAKE256(M=`FF FF FF FF FF FF FF`, D=`0B`, 64)
{
256,
0x0b,
"bb36764951ec97e9d85f7ee9a67a7718fc005cf42556be79ce12c0bde50e5736d6632b0d0dfb202d1bbb8ffe3dd74cb00834fa756cb03471bab13a1e2c16b3c0",
[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
64,
false,
},
// TurboSHAKE256(M=`FF`, D=`30`, 64)
{
256,
0x30,
"f3fe12873d34bcbb2e608779d6b70e7f86bec7e90bf113cbd4fdd0c4e2f4625e148dd7ee1a52776cf77f240514d9ccfc3b5ddab8ee255e39ee389072962c111a",
[]byte{0xff},
64,
false,
},
// TurboSHAKE256(M=`FF FF FF`, D=`7F`, 64)
{
256,
0x7f,
"abe569c1f77ec340f02705e7d37c9ab7e155516e4a6a150021d70b6fac0bb40c069f9a9828a0d575cd99f9bae435ab1acf7ed9110ba97ce0388d074bac768776",
[]byte{0xff, 0xff, 0xff},
64,
false,
},
}
for v in test_vectors {
dst := make([]byte, v.out_len, context.temp_allocator)
ctx: turboshake.Context
switch v.sec_strength {
case 128:
turboshake.init_128(&ctx, v.domainsep)
case 256:
turboshake.init_256(&ctx, v.domainsep)
}
turboshake.write(&ctx, v.msg)
turboshake.read(&ctx, dst)
got := dst
if v.last_only {
got = got[len(got)-32:]
}
got_str := string(hex.encode(got, context.temp_allocator))
testing.expectf(
t,
got_str == v.output,
"TurboSHAKE%d: expected %s, got %s",
v.sec_strength,
v.output,
got_str,
)
}
}