ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sha3.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file sha3.h
6 * @brief Keccak-f[1600] sponge: SHA3-256, SHA3-512, SHAKE128, SHAKE256 (FIPS 202).
7 *
8 * The symmetric primitives ML-KEM (FIPS 203) is built on: G = SHA3-512, H = SHA3-256, the matrix
9 * XOF = SHAKE128, and the noise PRF = SHAKE256. Zero-heap, endian-independent (the sponge state is
10 * addressed as a little-endian byte string regardless of host byte order), no external dependency.
11 *
12 * One-shot entries cover fixed-length digests and arbitrary SHAKE output. For an incremental XOF
13 * (ML-KEM samples the public matrix by squeezing three bytes at a time) absorb once with
14 * @ref Sha3Ns::shake128_absorb then pull with @ref Sha3Ns::squeeze as many times as needed.
15 *
16 * @author Douglas Quigg (dstroy0)
17 * @date 2026
18 */
19
20#ifndef PROTOCORE_SHA3_H
21#define PROTOCORE_SHA3_H
22
23#include "protocore_config.h"
24
25#if PROTOCORE_ENABLE_SHA3
26
28
29/// Sponge rates (block size in octets = 1600/8 - 2*capacity/8) for the modes we use.
30#define KECCAK_RATE_SHA3_256 136
31#define KECCAK_RATE_SHA3_512 72
32#define KECCAK_RATE_SHAKE128 168
33#define KECCAK_RATE_SHAKE256 136
34
35/** @brief The message a raw sponge absorbs, at a stated rate and domain. */
36typedef struct
37{
38 uint32_t rate; ///< sponge rate in octets
39 const uint8_t *in; ///< the message
40 size_t inlen; ///< its length
41 uint8_t domain; ///< domain-separation byte (0x06 SHA3, 0x1F SHAKE)
42} Sha3AbsorbArgs;
43
44/** @brief Where squeezed octets land. */
45typedef struct
46{
47 uint8_t *out; ///< the output buffer
48 size_t outlen; ///< how many octets to pull
49} Sha3SqueezeArgs;
50
51/** @brief The message a fixed-length digest is taken over. */
52typedef struct
53{
54 uint8_t *out; ///< 32 octets for SHA3-256, 64 for SHA3-512
55 const uint8_t *in; ///< the message
56 size_t inlen; ///< its length
57} Sha3DigestArgs;
58
59/** @brief The message a one-shot XOF is taken over, and how much output it yields. */
60typedef struct
61{
62 uint8_t *out; ///< the output buffer
63 size_t outlen; ///< how many octets to produce
64 const uint8_t *in; ///< the message
65 size_t inlen; ///< its length
66} Sha3XofArgs;
67
68/** @brief The message an incremental SHAKE128 XOF absorbs. */
69typedef struct
70{
71 const uint8_t *in; ///< the message
72 size_t inlen; ///< its length
73} Sha3Shake128AbsorbArgs;
74
75// PROTOCORE_SHA3_BORROW - the bytes a sponge runs out of - is stated in protocore_config.h, which sums
76// it into the secure arena. A caller takes them once and passes the pointer to every call.
77
78/**
79 * @brief SHA3-256 / SHA3-512 / SHAKE128 / SHAKE256 (FIPS 202).
80 *
81 * A caller sets the members a call takes, invokes it through ::Sha3 with the bytes it runs out of, and
82 * reads the outcome off the same handle. How those bytes are carved is this module's and is never
83 * named here.
84 *
85 * The incremental XOF ML-KEM samples its matrix with:
86 *
87 * Sha3.shake128_absorb_args.in = seed;
88 * Sha3.shake128_absorb_args.inlen = sizeof(seed);
89 * Sha3.shake128_absorb(work);
90 * Sha3.squeeze_args.out = buf;
91 * Sha3.squeeze_args.outlen = sizeof(buf);
92 * Sha3.squeeze(work);
93 *
94 * @var Sha3Ns::absorb_args the message a raw sponge absorbs, at a stated rate and domain
95 * @var Sha3Ns::squeeze_args where squeezed octets land
96 * @var Sha3Ns::digest_args the message a fixed-length digest is taken over
97 * @var Sha3Ns::xof_args the message a one-shot XOF is taken over
98 * @var Sha3Ns::shake128_absorb_args the message an incremental SHAKE128 XOF absorbs
99 * @var Sha3Ns::ok a call's true/false outcome
100 * @var Sha3Ns::absorb absorb the whole message, pad, leave the sponge ready to squeeze
101 * @var Sha3Ns::squeeze pull octets, permuting between blocks; repeatable for XOF use
102 * @var Sha3Ns::sha3_256 SHA3-256 one-shot, 32 octets out
103 * @var Sha3Ns::sha3_512 SHA3-512 one-shot, 64 octets out
104 * @var Sha3Ns::shake128 SHAKE128 one-shot
105 * @var Sha3Ns::shake256 SHAKE256 one-shot
106 * @var Sha3Ns::shake128_absorb begin an incremental SHAKE128 XOF
107 *
108 * @c work is PROTOCORE_SHA3_BORROW secure bytes the CALLER took, at an address it knows. It is not held past the call,
109 * so nothing here aliases it. The caller releases it, and the pool wipes on release; this module neither takes it,
110 * holds it, releases it, nor wipes it.
111 *
112 * The borrow IS the sponge, and everything carried call to call lives in it. A digest taken in its own
113 * borrow therefore leaves an incremental XOF running in another exactly where it was.
114 *
115 * No storage member and no context: a caller sets operands and reads @ref Sha3Ns::ok, and that is
116 * all the surface there is.
117 */
118typedef struct
119{
120 Sha3AbsorbArgs absorb_args;
121 Sha3SqueezeArgs squeeze_args;
122 Sha3DigestArgs digest_args;
123 Sha3XofArgs xof_args;
124 Sha3Shake128AbsorbArgs shake128_absorb_args;
125 proto_bool ok;
126} Sha3Vars;
127
128/** @brief The operands and the outcome. */
129extern Sha3Vars Sha3V;
130
131/** @brief The entries. */
132typedef struct
133{
134 void (*const absorb)(uint8_t *work);
135 void (*const squeeze)(uint8_t *work);
136 void (*const sha3_256)(uint8_t *work);
137 void (*const sha3_512)(uint8_t *work);
138 void (*const shake128)(uint8_t *work);
139 void (*const shake256)(uint8_t *work);
140 void (*const shake128_absorb)(uint8_t *work);
141} Sha3Ns;
142
143// What the table binds, defined once in the .c and taking one parameter each: everything
144// else an entry needs is an operand in Sha3V or a region of the borrow at a fixed offset.
145void protocore_sha3_absorb(uint8_t *work);
146void protocore_sha3_squeeze(uint8_t *work);
147void protocore_sha3_sha3_256(uint8_t *work);
148void protocore_sha3_sha3_512(uint8_t *work);
149void protocore_sha3_shake128(uint8_t *work);
150void protocore_sha3_shake256(uint8_t *work);
151void protocore_sha3_shake128_absorb(uint8_t *work);
152
153// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
154// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
155// `Sha3.absorb(work)` resolves to a named function and becomes a DIRECT call. An extern table
156// leaves the call indirect and the symbol live at every level, -O2 -flto included.
157static const Sha3Ns Sha3 __attribute__((unused)) = {
158 .absorb = protocore_sha3_absorb,
159 .squeeze = protocore_sha3_squeeze,
160 .sha3_256 = protocore_sha3_sha3_256,
161 .sha3_512 = protocore_sha3_sha3_512,
162 .shake128 = protocore_sha3_shake128,
163 .shake256 = protocore_sha3_shake256,
164 .shake128_absorb = protocore_sha3_shake128_absorb,
165};
166
168
169#endif // PROTOCORE_ENABLE_SHA3
170
171#endif // PROTOCORE_SHA3_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97