[PATCH 04/10] tests: add sntrup761 internal arithmetic regression test
Jussi Kivilinna
jussi.kivilinna at iki.fi
Sun Aug 2 11:55:09 CEST 2026
* tests/Makefile.am (tests_bin): Add 't-sntrup761'.
(t_sntrup761_CPPFLAGS): New.
* tests/t-sntrup761.c: New.
--
Polynomial multiplication and modular reduction helpers of SNTRUP761 are
file-static, so test includes implementation directly, same way as
bench-slope reaches cipher internals. Outputs are compared against
reference reduction written with plain C operators.
Input patterns maximize intermediate accumulators instead of sampling
randomly, as that is what deferred reduction depends on.
Signed-off-by: Jussi Kivilinna <jussi.kivilinna at iki.fi>
---
tests/Makefile.am | 3 +-
tests/t-sntrup761.c | 407 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 409 insertions(+), 1 deletion(-)
create mode 100644 tests/t-sntrup761.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 8fcde4fc..8c468b4d 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -26,7 +26,7 @@ tests_bin = \
t-mpi-bit t-mpi-point t-lock \
prime basic keygen pubkey hmac hashtest t-kdf keygrip \
aeswrap random t-kem t-thread-local t-fips-service-ind \
- t-cipher-internal
+ t-cipher-internal t-sntrup761
if USE_RSA
tests_bin += pkcs1v2 t-rsa-pss t-rsa-15 t-rsa-testparm
@@ -106,6 +106,7 @@ t_thread_local_LDADD = $(standard_ldadd) $(GPG_ERROR_MT_LIBS) @LDADD_FOR_TESTS_K
t_thread_local_CFLAGS = $(GPG_ERROR_MT_CFLAGS)
testdrv_LDADD = $(LDADD_FOR_TESTS_KLUDGE)
t_cipher_internal_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/cipher
+t_sntrup761_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/cipher
# Build a version of the test driver for the build platform.
testdrv-build: testdrv.c
diff --git a/tests/t-sntrup761.c b/tests/t-sntrup761.c
new file mode 100644
index 00000000..7d8b2c80
--- /dev/null
+++ b/tests/t-sntrup761.c
@@ -0,0 +1,407 @@
+/* t-sntrup761.c - SNTRUP761 internal arithmetic regression tests
+ * Copyright (C) 2026 Jussi Kivilinna <jussi.kivilinna at iki.fi>
+ *
+ * This file is part of Libgcrypt.
+ *
+ * Libgcrypt is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Libgcrypt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Reference functions derived from public domain source, written
+ * by (in alphabetical order):
+ * - Daniel J. Bernstein
+ * - Chitchanok Chuengsatiansup
+ * - Tanja Lange
+ * - Christine van Vredendaal
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Stub out external dependencies so that including implementation does
+ not require linking them. */
+#define _gcry_md_hash_buffer t_sntrup761_md_hash_buffer
+#define _gcry_ct_not_memequal t_sntrup761_ct_not_memequal
+#define _gcry_ct_memmov_cond t_sntrup761_ct_memmov_cond
+
+static void
+t_sntrup761_md_hash_buffer (int algo, void *digest, const void *buf,
+ size_t len)
+{
+ (void)algo;
+ (void)digest;
+ (void)buf;
+ (void)len;
+}
+
+static unsigned int
+t_sntrup761_ct_not_memequal (const void *b1, const void *b2, size_t len)
+{
+ (void)b1;
+ (void)b2;
+ (void)len;
+ return 0;
+}
+
+static void
+t_sntrup761_ct_memmov_cond (void *dst, const void *src, size_t len,
+ unsigned long op_enable)
+{
+ (void)dst;
+ (void)src;
+ (void)len;
+ (void)op_enable;
+}
+
+/* Include after implementation, as 't-common.h' pulls in public gcrypt.h
+ which 'sntrup761.h' refuses to see. */
+#include "../cipher/sntrup761.c"
+
+#define PGM "t-sntrup761"
+#include "t-common.h"
+
+static uint32_t rng_counter;
+
+static uint32_t
+rng (void)
+{
+ unsigned char ctr[4], dig[20];
+
+ ctr[0] = rng_counter;
+ ctr[1] = rng_counter >> 8;
+ ctr[2] = rng_counter >> 16;
+ ctr[3] = rng_counter >> 24;
+ rng_counter++;
+
+ gcry_md_hash_buffer (GCRY_MD_SHA1, dig, ctr, sizeof (ctr));
+
+ return ((uint32_t)dig[0] | ((uint32_t)dig[1] << 8)
+ | ((uint32_t)dig[2] << 16) | ((uint32_t)dig[3] << 24));
+}
+
+/* Canonical representative of X modulo M, in [0,M). */
+static long
+ref_mod (long long x, long m)
+{
+ long long r = x % m;
+
+ if (r < 0)
+ r += m;
+ return (long)r;
+}
+
+static int
+ref_F3_freeze (long long x)
+{
+ return ref_mod (x + 1, 3) - 1;
+}
+
+static int
+ref_Fq_freeze (long long x)
+{
+ return ref_mod (x + q12, q) - q12;
+}
+
+/* Multiplication in Z[x]/(q, x^p - x - 1). */
+static void
+ref_Rq_mult_small (Fq *h, const Fq *f, const small *g)
+{
+ static long long fg[p + p - 1];
+ int i, j;
+
+ for (i = 0; i < p + p - 1; i++)
+ fg[i] = 0;
+ for (i = 0; i < p; i++)
+ for (j = 0; j < p; j++)
+ fg[i + j] += (long long)f[i] * g[j];
+ for (i = p + p - 2; i >= p; i--)
+ {
+ fg[i - p] += fg[i];
+ fg[i - p + 1] += fg[i];
+ }
+ for (i = 0; i < p; i++)
+ h[i] = ref_Fq_freeze (fg[i]);
+}
+
+static void
+ref_R3_mult (small *h, const small *f, const small *g)
+{
+ static long long fg[p + p - 1];
+ int i, j;
+
+ for (i = 0; i < p + p - 1; i++)
+ fg[i] = 0;
+ for (i = 0; i < p; i++)
+ for (j = 0; j < p; j++)
+ fg[i + j] += (long long)f[i] * g[j];
+ for (i = p + p - 2; i >= p; i--)
+ {
+ fg[i - p] += fg[i];
+ fg[i - p + 1] += fg[i];
+ }
+ for (i = 0; i < p; i++)
+ h[i] = ref_F3_freeze (fg[i]);
+}
+
+static void
+test_freeze_helpers (void)
+{
+ /* Coprime to q, so that sweep hits different residue every step. */
+ static const long sweep_step = 9973;
+ static const long fq_extremes[] =
+ {
+ -2 * (long)q12 * q12, 2 * (long)q12 * q12,
+ -2 * (long)q12 * q12 + 1, 2 * (long)q12 * q12 - 1,
+ -(long)q12 * q12, (long)q12 * q12,
+ -(long)p * q12, (long)p * q12
+ };
+ long x;
+ unsigned int i;
+
+ if (verbose)
+ fprintf (stderr, PGM ": checking F3_freeze over all int16_t\n");
+ for (x = -32768; x <= 32767; x++)
+ {
+ int got = F3_freeze ((int16_t)x);
+ int want = ref_F3_freeze (x);
+
+ if (got != want)
+ fail ("F3_freeze(%ld): got %d, want %d", x, got, want);
+ }
+
+ if (verbose)
+ fprintf (stderr, PGM ": checking Fq_freeze over reachable range\n");
+ for (x = -(2 * (long)q12 + 8); x <= 2 * (long)q12 + 8; x++)
+ {
+ int got = Fq_freeze ((int32_t)x);
+ int want = ref_Fq_freeze (x);
+
+ if (got != want)
+ fail ("Fq_freeze(%ld): got %d, want %d", x, got, want);
+ }
+
+ /* Widest values callers produce, from Rq_recip3. */
+ for (x = -2 * (long)q12 * q12; x <= 2 * (long)q12 * q12; x += sweep_step)
+ {
+ int got = Fq_freeze ((int32_t)x);
+ int want = ref_Fq_freeze (x);
+
+ if (got != want)
+ fail ("Fq_freeze(%ld): got %d, want %d", x, got, want);
+ }
+
+ for (i = 0; i < DIM (fq_extremes); i++)
+ {
+ int got = Fq_freeze ((int32_t)fq_extremes[i]);
+ int want = ref_Fq_freeze (fq_extremes[i]);
+
+ if (got != want)
+ fail ("Fq_freeze(%ld): got %d, want %d", fq_extremes[i], got, want);
+ }
+}
+
+/* Input patterns maximize intermediate accumulators. */
+static void
+make_inputs (int pattern, Fq *f, small *g)
+{
+ int i;
+
+ switch (pattern)
+ {
+ case 0:
+ for (i = 0; i < p; i++)
+ {
+ f[i] = q12;
+ g[i] = 1;
+ }
+ break;
+ case 1:
+ for (i = 0; i < p; i++)
+ {
+ f[i] = -q12;
+ g[i] = -1;
+ }
+ break;
+ case 2:
+ for (i = 0; i < p; i++)
+ {
+ f[i] = q12;
+ g[i] = -1;
+ }
+ break;
+ case 3:
+ for (i = 0; i < p; i++)
+ {
+ f[i] = q12;
+ g[i] = (i & 1) ? -1 : 1;
+ }
+ break;
+ case 4:
+ for (i = 0; i < p; i++)
+ {
+ f[i] = (i & 1) ? -q12 : q12;
+ g[i] = 1;
+ }
+ break;
+ case 5:
+ for (i = 0; i < p; i++)
+ {
+ f[i] = (rng () & 1) ? q12 : -q12;
+ g[i] = 1;
+ }
+ break;
+ case 6:
+ for (i = 0; i < p; i++)
+ {
+ f[i] = 0;
+ g[i] = 0;
+ }
+ break;
+ case 7:
+ for (i = 0; i < p; i++)
+ {
+ f[i] = 0;
+ g[i] = 0;
+ }
+ f[p - 1] = q12;
+ g[p - 1] = -1;
+ break;
+ case 8:
+ for (i = 0; i < p; i++)
+ {
+ f[i] = q12;
+ g[i] = 0;
+ }
+ g[0] = 1;
+ g[p - 1] = 1;
+ break;
+ case 9:
+ for (i = 0; i < p; i++)
+ {
+ f[i] = (int)(rng () % (2 * q12 + 1)) - q12;
+ g[i] = i < w ? ((rng () & 1) ? 1 : -1) : 0;
+ }
+ break;
+ default:
+ for (i = 0; i < p; i++)
+ {
+ f[i] = (int)(rng () % (2 * q12 + 1)) - q12;
+ g[i] = (int)(rng () % 3) - 1;
+ }
+ break;
+ }
+}
+
+static void
+test_mult (void)
+{
+ static Fq f[p], h[p], h_ref[p];
+ static small g[p], fs[p], h3[p], h3_ref[p];
+ int pattern, i;
+
+ for (pattern = 0; pattern < 32; pattern++)
+ {
+ rng_counter = pattern << 24;
+
+ make_inputs (pattern, f, g);
+ for (i = 0; i < p; i++)
+ fs[i] = g[i];
+
+ Rq_mult_small (h, f, g);
+ ref_Rq_mult_small (h_ref, f, g);
+ if (memcmp (h, h_ref, sizeof (h)))
+ {
+ for (i = 0; i < p; i++)
+ if (h[i] != h_ref[i])
+ {
+ fail ("Rq_mult_small pattern %d coeff %d: got %d, want %d",
+ pattern, i, (int)h[i], (int)h_ref[i]);
+ break;
+ }
+ }
+
+ R3_mult (h3, fs, g);
+ ref_R3_mult (h3_ref, fs, g);
+ if (memcmp (h3, h3_ref, sizeof (h3)))
+ {
+ for (i = 0; i < p; i++)
+ if (h3[i] != h3_ref[i])
+ {
+ fail ("R3_mult pattern %d coeff %d: got %d, want %d",
+ pattern, i, (int)h3[i], (int)h3_ref[i]);
+ break;
+ }
+ }
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ int last_argc = -1;
+
+ if (argc)
+ {
+ argc--;
+ argv++;
+ }
+
+ while (argc && last_argc != argc)
+ {
+ last_argc = argc;
+ if (!strcmp (*argv, "--"))
+ {
+ argc--;
+ argv++;
+ break;
+ }
+ else if (!strcmp (*argv, "--help"))
+ {
+ fputs ("usage: " PGM " [--verbose]\n", stdout);
+ exit (0);
+ }
+ else if (!strcmp (*argv, "--verbose"))
+ {
+ verbose++;
+ argc--;
+ argv++;
+ }
+ else if (!strncmp (*argv, "--", 2))
+ {
+ fprintf (stderr, PGM ": unknown option '%s'\n", *argv);
+ exit (1);
+ }
+ }
+
+ xgcry_control ((GCRYCTL_SET_VERBOSITY, (int) verbose));
+
+ if (!gcry_check_version (GCRYPT_VERSION))
+ die ("version mismatch\n");
+
+ xgcry_control ((GCRYCTL_DISABLE_SECMEM, 0));
+ xgcry_control ((GCRYCTL_INITIALIZATION_FINISHED, 0));
+ xgcry_control ((GCRYCTL_ENABLE_QUICK_RANDOM, 0));
+
+ test_freeze_helpers ();
+ test_mult ();
+
+ if (verbose)
+ fprintf (stderr, PGM ": %d error(s)\n", error_count);
+ return !!error_count;
+}
--
2.53.0
More information about the Gcrypt-devel
mailing list