From 2634e69aa3172d0a7a1852159771a905c0cb9a9d Mon Sep 17 00:00:00 2001
From: Daiki Ueno <dueno@redhat.com>
Date: Sun, 10 Mar 2024 09:43:04 +0900
Subject: [PATCH] sha3: Extend SHAKE256 API with incremental output

This adds an alternative function sha3_256_shake_output in the
SHAKE256 support, which enables to read output multiple times in an
incremental manner.

Signed-off-by: Daiki Ueno <dueno@redhat.com>
---
 sha3.h                    |  8 ++++++
 shake256.c                | 60 +++++++++++++++++++++++++++++++++++++++
 testsuite/shake256-test.c | 58 +++++++++++++++++++++++++++++++++++++
 3 files changed, 126 insertions(+)

diff --git a/sha3.h b/sha3.h
index 9220829d..4b7e186c 100644
--- a/sha3.h
+++ b/sha3.h
@@ -49,6 +49,7 @@ extern "C" {
 #define sha3_256_update nettle_sha3_256_update
 #define sha3_256_digest nettle_sha3_256_digest
 #define sha3_256_shake nettle_sha3_256_shake
+#define sha3_256_shake_output nettle_sha3_256_shake_output
 #define sha3_384_init nettle_sha3_384_init
 #define sha3_384_update nettle_sha3_384_update
 #define sha3_384_digest nettle_sha3_384_digest
@@ -143,6 +144,13 @@ sha3_256_shake(struct sha3_256_ctx *ctx,
 	       size_t length,
 	       uint8_t *digest);
 
+/* Unlike sha3_256_shake, this function can be called multiple times
+   to retrieve output from shake256 in an incremental manner */
+void
+sha3_256_shake_output(struct sha3_256_ctx *ctx,
+		      size_t length,
+		      uint8_t *digest);
+
 struct sha3_384_ctx
 {
   struct sha3_state state;
diff --git a/shake256.c b/shake256.c
index f5c77a43..9c418395 100644
--- a/shake256.c
+++ b/shake256.c
@@ -36,6 +36,8 @@
 # include "config.h"
 #endif
 
+#include <assert.h>
+#include <limits.h>
 #include <stddef.h>
 #include <string.h>
 
@@ -44,6 +46,8 @@
 
 #include "nettle-write.h"
 
+#define INDEX_HIGH_BIT (~((UINT_MAX) >> 1))
+
 void
 sha3_256_shake (struct sha3_256_ctx *ctx,
 		size_t length,
@@ -61,3 +65,59 @@ sha3_256_shake (struct sha3_256_ctx *ctx,
 
   sha3_256_init (ctx);
 }
+
+void
+sha3_256_shake_output (struct sha3_256_ctx *ctx,
+		       size_t length,
+		       uint8_t *digest)
+{
+  unsigned index, left;
+
+  /* We use the leftmost bit as a flag to indicate SHAKE is initialized.  */
+  if (ctx->index & INDEX_HIGH_BIT)
+    index = ctx->index & ~INDEX_HIGH_BIT;
+  else
+    {
+      /* This is the first call of _shake_output.  */
+      _sha3_pad_shake (&ctx->state, sizeof (ctx->block), ctx->block, ctx->index);
+      /* Point at the end of block to trigger fill in of the buffer.  */
+      index = sizeof (ctx->block);
+    }
+
+  assert (index <= sizeof (ctx->block));
+
+  /* Write remaining data from the buffer.  */
+  left = sizeof (ctx->block) - index;
+  if (length <= left)
+    {
+      memcpy (digest, ctx->block + index, length);
+      ctx->index = (index + length) | INDEX_HIGH_BIT;
+      return;
+    }
+  else
+    {
+      memcpy (digest, ctx->block + index, left);
+      length -= left;
+      digest += left;
+    }
+
+  /* Write full blocks.  */
+  while (length > sizeof (ctx->block))
+    {
+      _nettle_write_le64 (sizeof (ctx->block), digest, ctx->state.a);
+      length -= sizeof (ctx->block);
+      digest += sizeof (ctx->block);
+      sha3_permute (&ctx->state);
+    }
+
+  if (length > 0)
+    {
+      /* Fill in the buffer for next call.  */
+      _nettle_write_le64 (sizeof (ctx->block), ctx->block, ctx->state.a);
+      sha3_permute (&ctx->state);
+      memcpy (digest, ctx->block, length);
+      ctx->index = length | INDEX_HIGH_BIT;
+    }
+  else
+    ctx->index = sizeof (ctx->block) | INDEX_HIGH_BIT;
+}
diff --git a/testsuite/shake256-test.c b/testsuite/shake256-test.c
index b91417cb..016060e8 100644
--- a/testsuite/shake256-test.c
+++ b/testsuite/shake256-test.c
@@ -34,6 +34,8 @@
 
 #include "sha3.h"
 
+#define MIN(a,b) (((a) < (b)) ? (a) : (b))
+
 const struct nettle_hash nettle_shake256 =
   {
    "shake256",
@@ -45,9 +47,36 @@ const struct nettle_hash nettle_shake256 =
    (nettle_hash_digest_func *) sha3_256_shake,
   };
 
+static void
+test_incremental (const struct tstring *msg,
+		  const struct tstring *digest,
+		  size_t interval)
+{
+  struct sha3_256_ctx ctx;
+  size_t offset = 0;
+  uint8_t *buffer = xalloc (digest->length);
+
+  sha3_256_init (&ctx);
+  sha3_256_update (&ctx, msg->length, msg->data);
+
+  while (offset < digest->length)
+    {
+      size_t to_read = MIN(digest->length - offset, interval);
+
+      sha3_256_shake_output (&ctx, to_read, buffer + offset);
+
+      offset += to_read;
+    }
+
+  ASSERT (MEMEQ (digest->length, digest->data, buffer));
+  free (buffer);
+}
+
 void
 test_main(void)
 {
+  const struct tstring *msg, *digest;
+
   /* Extracted from ShortMsgKAT_SHAKE256.txt. */
   test_hash (&nettle_shake256, /* 0 octets */
 	     SHEX(""),
@@ -5937,4 +5966,33 @@ test_main(void)
 		  "805C7B7E2CFD54E0FAD62F0D8CA67A775DC4546AF9096F2EDB2"
 		  "21DB42843D65327861282DC946A0BA01A11863AB2D1DFD16E39"
 		  "73D4"));
+
+  /* Test incremental API */
+  msg = SHEX("3A3A819C48EFDE2AD914FBF00E18AB6BC4F14513AB27D0C178A188B61431E7F5623CB66B23346775D386B50E982C493ADBBFC54B9A3CD383382336A1A0B2150A15358F336D03AE18F666C7573D55C4FD181C29E6CCFDE63EA35F0ADF5885CFC0A3D84A2B2E4DD24496DB789E663170CEF74798AA1BBCD4574EA0BBA40489D764B2F83AADC66B148B4A0CD95246C127D5871C4F11418690A5DDF01246A0C80A43C70088B6183639DCFDA4125BD113A8F49EE23ED306FAAC576C3FB0C1E256671D817FC2534A52F5B439F72E424DE376F4C565CCA82307DD9EF76DA5B7C4EB7E085172E328807C02D011FFBF33785378D79DC266F6A5BE6BB0E4A92ECEEBAEB1");
+  digest = SHEX("8A5199B4A7E133E264A86202720655894D48CFF344A928CF834"
+		"7F48379CEF347DFC5BCFFAB99B27B1F89AA2735E23D30088FFA"
+		"03B9EDB02B9635470AB9F1038985D55F9CA774572DD006470EA"
+		"65145469609F9FA0831BF1FFD842DC24ACADE27BD9816E3B5BF"
+		"2876CB112232A0EB4475F1DFF9F5C713D9FFD4CCB89AE5607FE"
+		"35731DF06317949EEF646E9591CF3BE53ADD6B7DD2B6096E2B3"
+		"FB06E662EC8B2D77422DAAD9463CD155204ACDBD38E319613F3"
+		"9F99B6DFB35CA9365160066DB19835888C2241FF9A731A4ACBB"
+		"5663727AAC34A401247FBAA7499E7D5EE5B69D31025E63D04C3"
+		"5C798BCA1262D5673A9CF0930B5AD89BD485599DC184528DA47"
+		"90F088EBD170B635D9581632D2FF90DB79665CED430089AF13C"
+		"9F21F6D443A818064F17AEC9E9C5457001FA8DC6AFBADBE3138"
+		"F388D89D0E6F22F66671255B210754ED63D81DCE75CE8F189B5"
+		"34E6D6B3539AA51E837C42DF9DF59C71E6171CD4902FE1BDC73"
+		"FB1775B5C754A1ED4EA7F3105FC543EE0418DAD256F3F6118EA"
+		"77114A16C15355B42877A1DB2A7DF0E155AE1D8670ABCEC3450"
+		"F4E2EEC9838F895423EF63D261138BAAF5D9F104CB5A957AEA0"
+		"6C0B9B8C78B0D441796DC0350DDEABB78A33B6F1F9E68EDE3D1"
+		"805C7B7E2CFD54E0FAD62F0D8CA67A775DC4546AF9096F2EDB2"
+		"21DB42843D65327861282DC946A0BA01A11863AB2D1DFD16E39"
+		"73D4");
+
+  test_incremental (msg, digest, 16);
+  test_incremental (msg, digest, SHA3_256_BLOCK_SIZE - 1);
+  test_incremental (msg, digest, SHA3_256_BLOCK_SIZE);
+  test_incremental (msg, digest, SHA3_256_BLOCK_SIZE + 1);
 }
-- 
2.43.2

