diff --git a/src/ext/keccak-tiny/keccak-tiny-unrolled.c b/src/ext/keccak-tiny/keccak-tiny-unrolled.c index c238af41ee..9ad818603c 100644 --- a/src/ext/keccak-tiny/keccak-tiny-unrolled.c +++ b/src/ext/keccak-tiny/keccak-tiny-unrolled.c @@ -102,46 +102,253 @@ mkapply_ds(xorin, dst[i] ^= src[i]) // xorin mkapply_sd(setout, dst[i] = src[i]) // setout #define P keccakf -#define Plen 200 +#define Plen KECCAK_MAX_RATE + +#define KECCAK_DELIM_DIGEST 0x06 +#define KECCAK_DELIM_XOF 0x1f // Fold P*F over the full blocks of an input. #define foldP(I, L, F) \ - while (L >= rate) { \ - F(a, I, rate); \ - P(a); \ - I += rate; \ - L -= rate; \ + while (L >= s->rate) { \ + F(s->a, I, s->rate); \ + P(s->a); \ + I += s->rate; \ + L -= s->rate; \ } +static inline void +keccak_absorb_blocks(keccak_state *s, const uint8_t *buf, size_t nr_blocks) +{ + size_t blen = nr_blocks * s->rate; + foldP(buf, blen, xorin); +} + +static int +keccak_update(keccak_state *s, const uint8_t *buf, size_t len) +{ + if (s->finalized) + return -1; + if ((buf == NULL) && len != 0) + return -1; + + size_t remaining = len; + while (remaining > 0) { + if (s->offset == 0) { + const size_t blocks = remaining / s->rate; + size_t direct_bytes = blocks * s->rate; + if (direct_bytes > 0) { + keccak_absorb_blocks(s, buf, blocks); + remaining -= direct_bytes; + buf += direct_bytes; + } + } + + const size_t buf_avail = s->rate - s->offset; + const size_t buf_bytes = (buf_avail > remaining) ? remaining : buf_avail; + if (buf_bytes > 0) { + memcpy(&s->block[s->offset], buf, buf_bytes); + s->offset += buf_bytes; + remaining -= buf_bytes; + buf += buf_bytes; + } + if (s->offset == s->rate) { + keccak_absorb_blocks(s, s->block, 1); + s->offset = 0; + } + } + return 0; +} + +static void +keccak_finalize(keccak_state *s) +{ + // Xor in the DS and pad frame. + s->a[s->offset] ^= s->delim; + s->a[s->rate - 1] ^= 0x80; + // Xor in the last block. + xorin(s->a, s->block, s->offset); + + memset_s(s->block, sizeof(s->block), 0, sizeof(s->block)); + s->finalized = 1; + s->offset = s->rate; +} + +static inline void +keccak_squeeze_blocks(keccak_state *s, uint8_t *out, size_t nr_blocks) +{ + for (size_t n = 0; n < nr_blocks; n++) { + keccakf(s->a); + setout(s->a, out, s->rate); + out += s->rate; + } +} + +static int +keccak_squeeze(keccak_state *s, uint8_t *out, size_t outlen) +{ + if (!s->finalized) + return -1; + + size_t remaining = outlen; + while (remaining > 0) { + if (s->offset == s->rate) { + const size_t blocks = remaining / s->rate; + const size_t direct_bytes = blocks * s->rate; + if (blocks > 0) { + keccak_squeeze_blocks(s, out, blocks); + out += direct_bytes; + remaining -= direct_bytes; + } + + if (remaining > 0) { + keccak_squeeze_blocks(s, s->block, 1); + s->offset = 0; + } + } + + const size_t buf_bytes = s->rate - s->offset; + const size_t indirect_bytes = (buf_bytes > remaining) ? remaining : buf_bytes; + if (indirect_bytes > 0) { + memcpy(out, &s->block[s->offset], indirect_bytes); + out += indirect_bytes; + s->offset += indirect_bytes; + remaining -= indirect_bytes; + } + } + return 0; +} + +int +keccak_digest_init(keccak_state *s, size_t bits) +{ + if (s == NULL) + return -1; + if (bits != 224 && bits != 256 && bits != 384 && bits != 512) + return -1; + + keccak_cleanse(s); + s->rate = KECCAK_RATE(bits); + s->delim = KECCAK_DELIM_DIGEST; + return 0; +} + +int +keccak_digest_update(keccak_state *s, const uint8_t *buf, size_t len) +{ + if (s == NULL) + return -1; + if (s->delim != KECCAK_DELIM_DIGEST) + return -1; + + return keccak_update(s, buf, len); +} + +int +keccak_digest_sum(const keccak_state *s, uint8_t *out, size_t outlen) +{ + if (s == NULL) + return -1; + if (s->delim != KECCAK_DELIM_DIGEST) + return -1; + if (out == NULL || outlen > 4 * (KECCAK_MAX_RATE - s->rate) / 8) + return -1; + + // Work in a copy so that incremental/rolling hashes are easy. + keccak_state s_tmp; + keccak_clone(&s_tmp, s); + keccak_finalize(&s_tmp); + int ret = keccak_squeeze(&s_tmp, out, outlen); + keccak_cleanse(&s_tmp); + return ret; +} + +int +keccak_xof_init(keccak_state *s, size_t bits) +{ + if (s == NULL) + return -1; + if (bits != 128 && bits != 256) + return -1; + + keccak_cleanse(s); + s->rate = KECCAK_RATE(bits); + s->delim = KECCAK_DELIM_XOF; + return 0; +} + +int +keccak_xof_absorb(keccak_state *s, const uint8_t *buf, size_t len) +{ + if (s == NULL) + return -1; + if (s->delim != KECCAK_DELIM_XOF) + return -1; + + return keccak_update(s, buf, len); +} + +int +keccak_xof_squeeze(keccak_state *s, uint8_t *out, size_t outlen) +{ + if (s == NULL) + return -1; + if (s->delim != KECCAK_DELIM_XOF) + return -1; + + if (!s->finalized) + keccak_finalize(s); + + return keccak_squeeze(s, out, outlen); +} + +void +keccak_clone(keccak_state *out, const keccak_state *in) +{ + memcpy(out, in, sizeof(keccak_state)); +} + +void +keccak_cleanse(keccak_state *s) +{ + memset_s(s, sizeof(keccak_state), 0, sizeof(keccak_state)); +} + /** The sponge-based hash construction. **/ static inline int hash(uint8_t* out, size_t outlen, const uint8_t* in, size_t inlen, - size_t rate, uint8_t delim) { - if ((out == NULL) || ((in == NULL) && inlen != 0) || (rate >= Plen)) { + size_t bits, uint8_t delim) { + if ((out == NULL) || ((in == NULL) && inlen != 0)) { return -1; } - uint8_t a[Plen] = {0}; - // Absorb input. - foldP(in, inlen, xorin); - // Xor in the DS and pad frame. - a[inlen] ^= delim; - a[rate - 1] ^= 0x80; - // Xor in the last block. - xorin(a, in, inlen); - // Apply P - P(a); - // Squeeze output. - foldP(out, outlen, setout); - setout(a, out, outlen); - memset_s(a, 200, 0, 200); - return 0; + + int ret = 0; + keccak_state s; + + switch (delim) { + case KECCAK_DELIM_DIGEST: + ret |= keccak_digest_init(&s, bits); + ret |= keccak_digest_update(&s, in, inlen); + // Use the internal API instead of sum to avoid the memcpy. + keccak_finalize(&s); + ret |= keccak_squeeze(&s, out, outlen); + break; + case KECCAK_DELIM_XOF: + ret |= keccak_xof_init(&s, bits); + ret |= keccak_xof_absorb(&s, in, inlen); + ret |= keccak_xof_squeeze(&s, out, outlen); + break; + default: + return -1; + } + keccak_cleanse(&s); + return ret; } /*** Helper macros to define SHA3 and SHAKE instances. ***/ #define defshake(bits) \ int shake##bits(uint8_t* out, size_t outlen, \ const uint8_t* in, size_t inlen) { \ - return hash(out, outlen, in, inlen, 200 - (bits / 4), 0x1f); \ + return hash(out, outlen, in, inlen, bits, KECCAK_DELIM_XOF); \ } #define defsha3(bits) \ int sha3_##bits(uint8_t* out, size_t outlen, \ @@ -149,7 +356,7 @@ static inline int hash(uint8_t* out, size_t outlen, if (outlen > (bits/8)) { \ return -1; \ } \ - return hash(out, outlen, in, inlen, 200 - (bits / 4), 0x06); \ + return hash(out, outlen, in, inlen, bits, KECCAK_DELIM_DIGEST); \ } /*** FIPS202 SHAKE VOFs ***/ diff --git a/src/ext/keccak-tiny/keccak-tiny.h b/src/ext/keccak-tiny/keccak-tiny.h index c4c678e874..04044cdc64 100644 --- a/src/ext/keccak-tiny/keccak-tiny.h +++ b/src/ext/keccak-tiny/keccak-tiny.h @@ -4,6 +4,53 @@ #include #include +#define KECCAK_MAX_RATE 200 + +/* Calculate the rate (block size) from the security target. */ +#define KECCAK_RATE(bits) (KECCAK_MAX_RATE - (bits / 4)) + +/* The internal structure of a FIPS202 hash/xof instance. Most callers + * should treat this as an opaque structure. + */ +typedef struct keccak_state { + uint8_t a[KECCAK_MAX_RATE]; + size_t rate; + uint8_t delim; + + uint8_t block[KECCAK_MAX_RATE]; + size_t offset; + + uint8_t finalized : 1; +} keccak_state; + +/* Initialize a Keccak instance suitable for SHA-3 hash functions. */ +int keccak_digest_init(keccak_state *s, size_t bits); + +/* Feed more data into the SHA-3 hash instance. */ +int keccak_digest_update(keccak_state *s, const uint8_t *buf, size_t len); + +/* Calculate the SHA-3 hash digest. The state is unmodified to support + * calculating multiple/rolling digests. + */ +int keccak_digest_sum(const keccak_state *s, uint8_t *out, size_t outlen); + +/* Initialize a Keccak instance suitable for XOFs (SHAKE-128/256). */ +int keccak_xof_init(keccak_state *s, size_t bits); + +/* Absorb more data into the XOF. Must not be called after a squeeze call. */ +int keccak_xof_absorb(keccak_state *s, const uint8_t *buf, size_t len); + +/* Squeeze data out of the XOF. Must not attempt to absorb additional data, + * after a squeeze has been called. + */ +int keccak_xof_squeeze(keccak_state *s, uint8_t *out, size_t outlen); + +/* Clone an existing hash/XOF instance. */ +void keccak_clone(keccak_state *out, const keccak_state *in); + +/* Cleanse sensitive data from a given hash instance. */ +void keccak_cleanse(keccak_state *s); + #define decshake(bits) \ int shake##bits(uint8_t*, size_t, const uint8_t*, size_t);