]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
OSSL_FN: Add the 'mul' function
authorRichard Levitte <levitte@openssl.org>
Mon, 24 Nov 2025 13:33:33 +0000 (14:33 +0100)
committerRichard Levitte <levitte@openssl.org>
Thu, 11 Dec 2025 09:35:46 +0000 (10:35 +0100)
OSSL_FN_mul() multiplies two operands.  The result OSSL_FN may be of any
size, and if it's smaller than the result of multiplying the two operands,
the result is truncated to that size.

This also adds the function OSSL_FN_copy(), a counterpart for BN_copy(),
as well as an OSSL_FN API test program.

Finally, test/fn_api_test.c is updated with a 'struct test_case_st', used
to instruct test functions what numbers to operate on, and some conditions,
and the 'add' and 'sub' test functions are upgraded to use that structure
for their input.

Related-to: doc/designs/fixed-size-large-numbers.md
Resolves: https://github.com/openssl/openssl/issues/28934

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29203)

crypto/fn/build.info
crypto/fn/fn_lib.c
crypto/fn/fn_mul.c [new file with mode: 0644]
include/crypto/fn.h
test/fn_api_test.c

index ecaf1ac6e792459e5f9b3762e8085999914c3078..599d4f5f684df8ae87645267b0dee548f9c3f3d1 100644 (file)
@@ -2,7 +2,7 @@ $LIBCRYPTO=../../libcrypto
 $LIBFIPS=../../providers/libfips.a
 LIBS=$LIBCRYPTO
 
-$COMMON=fn_err.c fn_lib.c fn_ctx.c fn_intern.c fn_addsub.c
+$COMMON=fn_err.c fn_lib.c fn_ctx.c fn_intern.c fn_addsub.c fn_mul.c
 
 SOURCE[$LIBCRYPTO]=$COMMON
 SOURCE[$LIBFIPS]=$COMMON
index 78638151f883d80eabb991137220c20920c722fb..3853b058ea24824e3a28580f54c8ffc72c895054 100644 (file)
@@ -11,7 +11,9 @@
 #include <limits.h>
 #include <openssl/opensslconf.h>
 #include <openssl/crypto.h>
+#include <openssl/err.h>
 #include "internal/common.h"
+#include "crypto/fnerr.h"
 #include "fn_local.h"
 
 static OSSL_FN *ossl_fn_new_internal(size_t limbs, bool securely)
@@ -102,3 +104,26 @@ void OSSL_FN_clear(OSSL_FN *f)
 
     OPENSSL_cleanse(f->d, limbssize);
 }
+
+OSSL_FN *OSSL_FN_copy(OSSL_FN *a, const OSSL_FN *b)
+{
+    if (ossl_unlikely(a == b))
+        return a;
+
+    size_t al = a->dsize;
+    size_t bl = b->dsize;
+
+    if (al < bl) {
+        ERR_raise_data(ERR_LIB_OSSL_FN, OSSL_FN_R_RESULT_ARG_TOO_SMALL,
+            "Needs to be at least %d, but is only %d",
+            bl, al);
+        return 0;
+    }
+
+    size_t t = ossl_fn_totalsize(bl);
+
+    memcpy(a, b, t);
+    a->dsize = (int)al; /* Restore to its original size */
+    memset(&a->d[a->dsize], 0, sizeof(OSSL_FN_ULONG) * (a->dsize - b->dsize));
+    return a;
+}
diff --git a/crypto/fn/fn_mul.c b/crypto/fn/fn_mul.c
new file mode 100644 (file)
index 0000000..005714a
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <assert.h>
+#include <openssl/err.h>
+#include "crypto/cryptlib.h"
+#include "crypto/fnerr.h"
+#include "../bn/bn_local.h" /* For using the low level bignum functions */
+#include "fn_local.h"
+
+int OSSL_FN_mul(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b, OSSL_FN_CTX *ctx)
+{
+    if (!OSSL_FN_CTX_start(ctx))
+        return 0;
+
+    size_t al = (size_t)a->dsize;
+    size_t bl = (size_t)b->dsize;
+    size_t rl = (size_t)r->dsize;
+    size_t max = (size_t)(al + bl);
+
+    int ret = 0;
+#ifdef BN_MUL_COMBA
+    if (al == bl) {
+        if (rl >= 16 && al == 8) {
+            bn_mul_comba8(r->d, a->d, b->d);
+            goto end;
+        }
+    }
+#endif /* BN_MUL_COMBA */
+
+    OSSL_FN *rr = r;
+    if ((r == a) || (r == b))
+        if ((rr = OSSL_FN_CTX_get_limbs(ctx, rl)) == NULL)
+            goto err;
+
+    bn_mul_truncated(rr->d, (int)rl, a->d, (int)al, b->d, (int)bl);
+
+    if (rr != r)
+        if (OSSL_FN_copy(r, rr) == NULL)
+            goto err;
+
+#if defined(BN_MUL_COMBA) || defined(BN_RECURSION)
+end:
+#endif
+
+{
+    size_t dif = (rl > max) ? rl - max : 0;
+    OSSL_FN_ULONG *rp = &r->d[max];
+    while (dif > 0) {
+        *rp++ = 0;
+        dif--;
+    }
+}
+
+    ret = 1;
+err:
+    OSSL_FN_CTX_end(ctx);
+    return ret;
+}
index 527b34019d3042bbb6a6fad48f26c9c334a88b4a..302cdc96b3fecd8ad5fc0971924c0fa49dbdd88f 100644 (file)
@@ -129,6 +129,15 @@ void OSSL_FN_clear_free(OSSL_FN *f);
  */
 void OSSL_FN_clear(OSSL_FN *f);
 
+/**
+ * Copy the contents of one OSSL_FN instance to another.
+ *
+ * @param[out]  a       The destination OSSL_FN
+ * @param[in]   b       The source OSSL_FN
+ * @returns     1 on success, 0 on error.
+ */
+OSSL_FN *OSSL_FN_copy(OSSL_FN *a, const OSSL_FN *b);
+
 /**
  * Allocate a new OSSL_FN_CTX, given a set of input numbers.
  *
@@ -252,6 +261,23 @@ int OSSL_FN_sub(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b);
  */
 int OSSL_FN_sub_word(OSSL_FN *a, const OSSL_FN_ULONG *w);
 
+/**
+ * Multiply two OSSL_FN numbers.  Truncates the result to fit in r.
+ *
+ * @param[out]          r       The OSSL_FN for the result
+ * @param[in]           a       The first operand
+ * @param[in]           b       The second operand
+ * @param[in]           ctx     A context to get temporary OSSL_FN
+ *                              instances from.
+ * @returns             1 on success, 0 on error
+ *
+ * @note This function currently requires that the OSSL_FN_CTX has free
+ * space for one temporary OSSL_FN with res->dsize limbs, plus one frame
+ * (currently 32 bytes).
+ */
+int OSSL_FN_mul(OSSL_FN *r, const OSSL_FN *a, const OSSL_FN *b,
+    OSSL_FN_CTX *ctx);
+
 #ifdef __cplusplus
 }
 #endif
index 949901d55bbafbf19cd518ad2ae864c22f9b509f..0bf8351ed6ba9d4296512801cebfd52ae633a904 100644 (file)
@@ -8,10 +8,11 @@
  */
 
 /**
- * @file Internal tests of OSSL_FN
+ * @file API tests of OSSL_FN
  *
- * This tests OSSL_FN internals only, i.e. anything that requires including
- * ../crypto/fn/fn_local.h, such as introspection.
+ * This tests the OSSL_FN "public" API, i.e. anything that only requires
+ * including crypto/fn.h.  including crypto/fn_intern.h is included too,
+ * for introspection.
  */
 
 #include <openssl/rand.h>
@@ -40,143 +41,601 @@ static int pollute(OSSL_FN *f, size_t start, size_t end)
     return 1;
 }
 
-static const OSSL_FN_ULONG num[][8 / OSSL_FN_BYTES] = {
-    /* num[0] */ { OSSL_FN_ULONG64_C(0x80000000, 0x00000001) },
-    /* num[1] */ { OSSL_FN_ULONG64_C(0x00000001, 0x80000000) },
-    /* num[2] */ { OSSL_FN_ULONG64_C(0x01234567, 0x89abcdef) },
-    /* num[3] */ { OSSL_FN_ULONG64_C(0xfedcba98, 0x76543210) },
-};
-
-static const OSSL_FN_ULONG expected_add_num_num[4][4][3] = {
-    {
-        /* num[0] + num[0] */ { OSSL_FN_ULONG64_C(0x00000000, 0x00000002), OSSL_FN_ULONG_C(0x1) },
-        /* num[0] + num[1] */ { OSSL_FN_ULONG64_C(0x80000001, 0x80000001) },
-        /* num[0] + num[2] */ { OSSL_FN_ULONG64_C(0x81234567, 0x89abcdf0) },
-        /* num[0] + num[3] */ { OSSL_FN_ULONG64_C(0x7edcba98, 0x76543211), OSSL_FN_ULONG_C(0x1) },
-    },
-    {
-        /* num[1] + num[0] */ { OSSL_FN_ULONG64_C(0x80000001, 0x80000001) },
-        /* num[1] + num[1] */ { OSSL_FN_ULONG64_C(0x00000003, 0x00000000) },
-        /* num[1] + num[2] */ { OSSL_FN_ULONG64_C(0x01234569, 0x09abcdef) },
-        /* num[1] + num[3] */ { OSSL_FN_ULONG64_C(0xfedcba99, 0xf6543210) },
-    },
-    {
-        /* num[2] + num[0] */ { OSSL_FN_ULONG64_C(0x81234567, 0x89abcdf0) },
-        /* num[2] + num[1] */ { OSSL_FN_ULONG64_C(0x01234569, 0x09abcdef) },
-        /* num[2] + num[2] */ { OSSL_FN_ULONG64_C(0x02468acf, 0x13579bde) },
-        /* num[2] + num[3] */ { OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) },
-    },
-    {
-        /* num[3] + num[0] */ { OSSL_FN_ULONG64_C(0x7edcba98, 0x76543211), OSSL_FN_ULONG_C(0x1) },
-        /* num[3] + num[1] */ { OSSL_FN_ULONG64_C(0xfedcba99, 0xf6543210) },
-        /* num[3] + num[2] */ { OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) },
-        /* num[3] + num[3] */ { OSSL_FN_ULONG64_C(0xfdb97530, 0xeca86420), OSSL_FN_ULONG_C(0x1) },
-    },
+static int check_zero(const OSSL_FN *f, size_t start, size_t end)
+{
+    const OSSL_FN_ULONG *u = ossl_fn_get_words(f);
+    size_t l = ossl_fn_get_dsize(f);
+
+    if (end > l)
+        end = l;
+    if (start > end)
+        start = end;
+
+    for (size_t i = start; i < end; i++)
+        if (u[i] != 0)
+            return 0;
+    return 1;
+}
+
+/* A set of numbers on OSSL_FN_ULONG array form */
+static const OSSL_FN_ULONG num0[] = { OSSL_FN_ULONG64_C(0x80000000, 0x00000001) };
+static const OSSL_FN_ULONG num1[] = { OSSL_FN_ULONG64_C(0x00000001, 0x80000000) };
+static const OSSL_FN_ULONG num2[] = { OSSL_FN_ULONG64_C(0x01234567, 0x89abcdef) };
+static const OSSL_FN_ULONG num3[] = { OSSL_FN_ULONG64_C(0xfedcba98, 0x76543210) };
+
+/* Numbers for edge cases */
+static const OSSL_FN_ULONG num4[] = { OSSL_FN_ULONG64_C(0x00000000, 0x00000000) };
+static const OSSL_FN_ULONG num5[] = { OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) };
+static const OSSL_FN_ULONG num6[] = {
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000000),
+    OSSL_FN_ULONG64_C(0x10000000, 0x00000000),
+};
+
+/*
+ * For each test function using predefined numbers, set up an
+ * arrays of test cases to simply run through, and call common
+ * test function for the operation being tested.
+ *
+ * All sizes are in number of limbs, the LIMBSOF() macro is there to help
+ */
+#define LIMBSOF(num) ((sizeof(num) + OSSL_FN_BYTES - 1) / OSSL_FN_BYTES)
+struct test_case_st {
+    /* Two operands and expected full result */
+    const OSSL_FN_ULONG *op1;
+    size_t op1_size;
+    const OSSL_FN_ULONG *op2;
+    size_t op2_size;
+    const OSSL_FN_ULONG *ex;
+    size_t ex_size;
+
+    /* Setup sizes for creating OSSL_FNs */
+    size_t op1_live_size;
+    size_t op2_live_size;
+    size_t res_live_size;
+
+    /* Number of limbs to compare the result's OSSL_FN_ULONG array against ex */
+    size_t check_size;
+};
+
+static const OSSL_FN_ULONG ex_add_num0_num0[] = {
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000002),
+    OSSL_FN_ULONG_C(0x1),
+};
+static const OSSL_FN_ULONG ex_add_num0_num1[] = {
+    OSSL_FN_ULONG64_C(0x80000001, 0x80000001),
+};
+static const OSSL_FN_ULONG ex_add_num0_num2[] = {
+    OSSL_FN_ULONG64_C(0x81234567, 0x89abcdf0),
+};
+static const OSSL_FN_ULONG ex_add_num0_num3[] = {
+    OSSL_FN_ULONG64_C(0x7edcba98, 0x76543211),
+    OSSL_FN_ULONG_C(0x1),
+};
+static const OSSL_FN_ULONG ex_add_num1_num1[] = {
+    OSSL_FN_ULONG64_C(0x00000003, 0x00000000),
+};
+static const OSSL_FN_ULONG ex_add_num1_num2[] = {
+    OSSL_FN_ULONG64_C(0x01234569, 0x09abcdef),
+};
+static const OSSL_FN_ULONG ex_add_num1_num3[] = {
+    OSSL_FN_ULONG64_C(0xfedcba99, 0xf6543210),
+};
+static const OSSL_FN_ULONG ex_add_num2_num2[] = {
+    OSSL_FN_ULONG64_C(0x02468acf, 0x13579bde),
+};
+static const OSSL_FN_ULONG ex_add_num2_num3[] = {
+    OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff),
+};
+static const OSSL_FN_ULONG ex_add_num3_num3[] = {
+    OSSL_FN_ULONG64_C(0xfdb97530, 0xeca86420),
+    OSSL_FN_ULONG_C(0x1),
+};
+
+#define ADD_CASE(i, op1, op2, ex)         \
+    {                                     \
+        /* op1, with size */ op1,         \
+        LIMBSOF(op1),                     \
+        /* op2, with size */ op2,         \
+        LIMBSOF(op2),                     \
+        /* ex, with size */ ex,           \
+        LIMBSOF(ex),                      \
+        /* op1_live_size */ LIMBSOF(op1), \
+        /* op2_live_size */ LIMBSOF(op2), \
+        /* res_live_size */ LIMBSOF(ex),  \
+        /* check_size */ LIMBSOF(ex),     \
+    }
+
+static struct test_case_st test_add_cases[] = {
+    ADD_CASE(1, num0, num0, ex_add_num0_num0),
+    ADD_CASE(2, num0, num1, ex_add_num0_num1),
+    ADD_CASE(3, num0, num2, ex_add_num0_num2),
+    ADD_CASE(4, num0, num3, ex_add_num0_num3),
+    ADD_CASE(5, num1, num0, ex_add_num0_num1), /* Commutativity check */
+    ADD_CASE(6, num1, num1, ex_add_num1_num1),
+    ADD_CASE(7, num1, num2, ex_add_num1_num2),
+    ADD_CASE(8, num1, num3, ex_add_num1_num3),
+    ADD_CASE(9, num2, num0, ex_add_num0_num2), /* Commutativity check */
+    ADD_CASE(10, num2, num1, ex_add_num1_num2), /* Commutativity check */
+    ADD_CASE(11, num2, num2, ex_add_num2_num2),
+    ADD_CASE(12, num2, num3, ex_add_num2_num3),
+    ADD_CASE(13, num3, num0, ex_add_num0_num3), /* Commutativity check */
+    ADD_CASE(14, num3, num1, ex_add_num1_num3), /* Commutativity check */
+    ADD_CASE(15, num3, num2, ex_add_num2_num3), /* Commutativity check */
+    ADD_CASE(16, num3, num3, ex_add_num3_num3),
 };
 
 static int test_add(int i)
 {
-    size_t i1 = i / 4;
-    size_t i2 = i % 4;
-    const OSSL_FN_ULONG *n1 = num[i1];
-    size_t n1_limbs = sizeof(num[i1]) / OSSL_FN_BYTES;
-    const OSSL_FN_ULONG *n2 = num[i2];
-    size_t n2_limbs = sizeof(num[i2]) / OSSL_FN_BYTES;
-    const OSSL_FN_ULONG *ex = expected_add_num_num[i1][i2];
-    size_t ex_limbs = sizeof(expected_add_num_num[i1][i2]) / OSSL_FN_BYTES;
+    const OSSL_FN_ULONG *n1 = test_add_cases[i].op1;
+    size_t n1_limbs = test_add_cases[i].op1_size;
+    const OSSL_FN_ULONG *n2 = test_add_cases[i].op2;
+    size_t n2_limbs = test_add_cases[i].op2_size;
+    const OSSL_FN_ULONG *ex = test_add_cases[i].ex;
+    size_t ex_limbs = test_add_cases[i].ex_size;
+    size_t check_limbs = test_add_cases[i].check_size;
     int ret = 1;
-    OSSL_FN *num1 = NULL, *num2 = NULL, *res = NULL;
+    OSSL_FN *fn1 = NULL, *fn2 = NULL, *res = NULL;
     const OSSL_FN_ULONG *u = NULL;
 
     /* To test that OSSL_FN_add() does a complete job, 'res' is pre-polluted */
 
-    if (!TEST_ptr(num1 = OSSL_FN_new_limbs(n1_limbs))
-        || !TEST_ptr(num2 = OSSL_FN_new_limbs(n2_limbs))
+    if (!TEST_ptr(fn1 = OSSL_FN_new_limbs(n1_limbs))
+        || !TEST_ptr(fn2 = OSSL_FN_new_limbs(n2_limbs))
         || !TEST_ptr(res = OSSL_FN_new_limbs(ex_limbs))
         || !TEST_true(pollute(res, 0, ex_limbs))
-        || !TEST_true(ossl_fn_set_words(num1, n1, n1_limbs))
-        || !TEST_true(ossl_fn_set_words(num2, n2, n2_limbs))
-        || !TEST_true(OSSL_FN_add(res, num1, num2))
+        || !TEST_true(ossl_fn_set_words(fn1, n1, n1_limbs))
+        || !TEST_true(ossl_fn_set_words(fn2, n2, n2_limbs))
+        || !TEST_true(OSSL_FN_add(res, fn1, fn2))
         || !TEST_ptr(u = ossl_fn_get_words(res))
         || !TEST_mem_eq(u, ossl_fn_get_dsize(res) * OSSL_FN_BYTES,
-            ex, ex_limbs * OSSL_FN_BYTES))
+            ex, check_limbs * OSSL_FN_BYTES))
         ret = 0;
-    OSSL_FN_free(num1);
-    OSSL_FN_free(num2);
+    OSSL_FN_free(fn1);
+    OSSL_FN_free(fn2);
     OSSL_FN_free(res);
 
     return ret;
 }
 
-/* Dimension the expected arrays to fit 4 32-bit limbs */
-#define EXPECTED_LIMBS ((16 + OSSL_FN_BYTES - 1) / OSSL_FN_BYTES)
-static OSSL_FN_ULONG expected_sub_num_num[4][4][EXPECTED_LIMBS] = {
-    {
-        /* num[0] - num[0] */ { OSSL_FN_ULONG64_C(0x00000000, 0x00000000) },
-        /* num[0] - num[1] */ { OSSL_FN_ULONG64_C(0x7ffffffe, 0x80000001) },
-        /* num[0] - num[2] */ { OSSL_FN_ULONG64_C(0x7edcba98, 0x76543212) },
-        /* num[0] - num[3] */ { OSSL_FN_ULONG64_C(0x81234567, 0x89abcdf1), OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) },
-    },
-    {
-        /* num[1] - num[0] */ { OSSL_FN_ULONG64_C(0x80000001, 0x7fffffff),
-            OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) },
-        /* num[1] - num[1] */ { OSSL_FN_ULONG64_C(0x00000000, 0x00000000) },
-        /* num[1] - num[2] */ { OSSL_FN_ULONG64_C(0xfedcba99, 0xf6543211), OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) },
-        /* num[1] - num[3] */ { OSSL_FN_ULONG64_C(0x01234569, 0x09abcdf0), OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) },
-    },
-    {
-        /* num[2] - num[0] */ { OSSL_FN_ULONG64_C(0x81234567, 0x89abcdee),
-            OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) },
-        /* num[2] - num[1] */ { OSSL_FN_ULONG64_C(0x01234566, 0x09abcdef) },
-        /* num[2] - num[2] */ { OSSL_FN_ULONG64_C(0x00000000, 0x00000000) },
-        /* num[2] - num[3] */ { OSSL_FN_ULONG64_C(0x02468acf, 0x13579bdf), OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff) },
-    },
-    {
-        /* num[3] - num[0] */ { OSSL_FN_ULONG64_C(0x7edcba98, 0x7654320f) },
-        /* num[3] - num[1] */ { OSSL_FN_ULONG64_C(0xfedcba96, 0xf6543210) },
-        /* num[3] - num[2] */ { OSSL_FN_ULONG64_C(0xfdb97530, 0xeca86421) },
-        /* num[3] - num[3] */ { OSSL_FN_ULONG64_C(0x00000000, 0x00000000) },
-    },
+static const OSSL_FN_ULONG ex_sub_num0_num0[] = {
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000000),
+};
+static const OSSL_FN_ULONG ex_sub_num0_num1[] = {
+    OSSL_FN_ULONG64_C(0x7ffffffe, 0x80000001),
+};
+static const OSSL_FN_ULONG ex_sub_num0_num2[] = {
+    OSSL_FN_ULONG64_C(0x7edcba98, 0x76543212),
+};
+static const OSSL_FN_ULONG ex_sub_num0_num3[] = {
+    OSSL_FN_ULONG64_C(0x81234567, 0x89abcdf1),
+    OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff)
+};
+static const OSSL_FN_ULONG ex_sub_num1_num0[] = {
+    OSSL_FN_ULONG64_C(0x80000001, 0x7fffffff),
+    OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff),
+};
+static const OSSL_FN_ULONG ex_sub_num1_num1[] = {
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000000),
+};
+static const OSSL_FN_ULONG ex_sub_num1_num2[] = {
+    OSSL_FN_ULONG64_C(0xfedcba99, 0xf6543211),
+    OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff),
+};
+static const OSSL_FN_ULONG ex_sub_num1_num3[] = {
+    OSSL_FN_ULONG64_C(0x01234569, 0x09abcdf0),
+    OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff),
+};
+static const OSSL_FN_ULONG ex_sub_num2_num0[] = {
+    OSSL_FN_ULONG64_C(0x81234567, 0x89abcdee),
+    OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff),
+};
+static const OSSL_FN_ULONG ex_sub_num2_num1[] = {
+    OSSL_FN_ULONG64_C(0x01234566, 0x09abcdef),
+};
+static const OSSL_FN_ULONG ex_sub_num2_num2[] = {
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000000),
+};
+static const OSSL_FN_ULONG ex_sub_num2_num3[] = {
+    OSSL_FN_ULONG64_C(0x02468acf, 0x13579bdf),
+    OSSL_FN_ULONG64_C(0xffffffff, 0xffffffff),
+};
+static const OSSL_FN_ULONG ex_sub_num3_num0[] = {
+    OSSL_FN_ULONG64_C(0x7edcba98, 0x7654320f),
+};
+static const OSSL_FN_ULONG ex_sub_num3_num1[] = {
+    OSSL_FN_ULONG64_C(0xfedcba96, 0xf6543210),
+};
+static const OSSL_FN_ULONG ex_sub_num3_num2[] = {
+    OSSL_FN_ULONG64_C(0xfdb97530, 0xeca86421),
+};
+static const OSSL_FN_ULONG ex_sub_num3_num3[] = {
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000000),
+};
+
+#define SUB_CASE(i, op1, op2, ex)         \
+    {                                     \
+        /* op1, with size */ op1,         \
+        LIMBSOF(op1),                     \
+        /* op2, with size */ op2,         \
+        LIMBSOF(op2),                     \
+        /* ex, with size */ ex,           \
+        LIMBSOF(ex),                      \
+        /* op1_live_size */ LIMBSOF(op1), \
+        /* op2_live_size */ LIMBSOF(op2), \
+        /* res_live_size */ LIMBSOF(ex),  \
+        /* check_size */ LIMBSOF(ex),     \
+    }
+
+static struct test_case_st test_sub_cases[] = {
+    SUB_CASE(1, num0, num0, ex_sub_num0_num0),
+    SUB_CASE(2, num0, num1, ex_sub_num0_num1),
+    SUB_CASE(3, num0, num2, ex_sub_num0_num2),
+    SUB_CASE(4, num0, num3, ex_sub_num0_num3),
+    SUB_CASE(5, num1, num0, ex_sub_num1_num0),
+    SUB_CASE(6, num1, num1, ex_sub_num1_num1),
+    SUB_CASE(7, num1, num2, ex_sub_num1_num2),
+    SUB_CASE(8, num1, num3, ex_sub_num1_num3),
+    SUB_CASE(9, num2, num0, ex_sub_num2_num0),
+    SUB_CASE(10, num2, num1, ex_sub_num2_num1),
+    SUB_CASE(11, num2, num2, ex_sub_num2_num2),
+    SUB_CASE(12, num2, num3, ex_sub_num2_num3),
+    SUB_CASE(13, num3, num0, ex_sub_num3_num0),
+    SUB_CASE(14, num3, num1, ex_sub_num3_num1),
+    SUB_CASE(15, num3, num2, ex_sub_num3_num2),
+    SUB_CASE(16, num3, num3, ex_sub_num3_num3),
 };
 
 static int test_sub(int i)
 {
-    size_t i1 = i / 4;
-    size_t i2 = i % 4;
-    const OSSL_FN_ULONG *n1 = num[i1];
-    size_t n1_limbs = sizeof(num[i1]) / OSSL_FN_BYTES;
-    const OSSL_FN_ULONG *n2 = num[i2];
-    size_t n2_limbs = sizeof(num[i2]) / OSSL_FN_BYTES;
-    const OSSL_FN_ULONG *ex = expected_sub_num_num[i1][i2];
-    size_t ex_limbs = sizeof(expected_sub_num_num[i1][i2]) / OSSL_FN_BYTES;
+    const OSSL_FN_ULONG *n1 = test_sub_cases[i].op1;
+    size_t n1_limbs = test_sub_cases[i].op1_size;
+    const OSSL_FN_ULONG *n2 = test_sub_cases[i].op2;
+    size_t n2_limbs = test_sub_cases[i].op2_size;
+    const OSSL_FN_ULONG *ex = test_sub_cases[i].ex;
+    size_t ex_limbs = test_sub_cases[i].ex_size;
+    size_t check_limbs = test_sub_cases[i].check_size;
     int ret = 1;
-    OSSL_FN *num1 = NULL, *num2 = NULL, *res = NULL;
+    OSSL_FN *fn1 = NULL, *fn2 = NULL, *res = NULL;
     const OSSL_FN_ULONG *u = NULL;
 
     /* To test that OSSL_FN_sub() does a complete job, 'res' is pre-polluted */
 
-    if (!TEST_ptr(num1 = OSSL_FN_new_limbs(n1_limbs))
-        || !TEST_ptr(num2 = OSSL_FN_new_limbs(n2_limbs))
+    if (!TEST_ptr(fn1 = OSSL_FN_new_limbs(n1_limbs))
+        || !TEST_ptr(fn2 = OSSL_FN_new_limbs(n2_limbs))
         || !TEST_ptr(res = OSSL_FN_new_limbs(ex_limbs))
         || !TEST_true(pollute(res, 0, ex_limbs))
-        || !TEST_true(ossl_fn_set_words(num1, n1, n1_limbs))
-        || !TEST_true(ossl_fn_set_words(num2, n2, n2_limbs))
-        || !TEST_true(OSSL_FN_sub(res, num1, num2))
+        || !TEST_true(ossl_fn_set_words(fn1, n1, n1_limbs))
+        || !TEST_true(ossl_fn_set_words(fn2, n2, n2_limbs))
+        || !TEST_true(OSSL_FN_sub(res, fn1, fn2))
         || !TEST_ptr(u = ossl_fn_get_words(res))
         || !TEST_mem_eq(u, ossl_fn_get_dsize(res) * OSSL_FN_BYTES,
-            ex, ex_limbs * OSSL_FN_BYTES))
+            ex, check_limbs * OSSL_FN_BYTES))
+        ret = 0;
+    OSSL_FN_free(fn1);
+    OSSL_FN_free(fn2);
+    OSSL_FN_free(res);
+
+    return ret;
+}
+
+/* A set of expected results, also in OSSL_FN_ULONG array form */
+static const OSSL_FN_ULONG ex_mul_num0_num0[] = {
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000001),
+    OSSL_FN_ULONG64_C(0x40000000, 0x00000001),
+};
+static const OSSL_FN_ULONG ex_mul_num0_num1[] = {
+    OSSL_FN_ULONG64_C(0x00000001, 0x80000000),
+    OSSL_FN_ULONG64_C(0x00000000, 0xC0000000),
+};
+static const OSSL_FN_ULONG ex_mul_num0_num2[] = {
+    OSSL_FN_ULONG64_C(0x81234567, 0x89ABCDEF),
+    OSSL_FN_ULONG64_C(0x0091A2B3, 0xC4D5E6F7),
+};
+static const OSSL_FN_ULONG ex_mul_num0_num3[] = {
+    OSSL_FN_ULONG64_C(0xFEDCBA98, 0x76543210),
+    OSSL_FN_ULONG64_C(0x7F6E5D4C, 0x3B2A1908),
+};
+static const OSSL_FN_ULONG ex_mul_num1_num1[] = {
+    OSSL_FN_ULONG64_C(0x40000000, 0x00000000),
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000002),
+};
+static const OSSL_FN_ULONG ex_mul_num1_num2[] = {
+    OSSL_FN_ULONG64_C(0x4E81B4E6, 0x80000000),
+    OSSL_FN_ULONG64_C(0x00000000, 0x01B4E81B),
+};
+static const OSSL_FN_ULONG ex_mul_num1_num3[] = {
+    OSSL_FN_ULONG64_C(0xB17E4B18, 0x00000000),
+    OSSL_FN_ULONG64_C(0x00000001, 0x7E4B17E4),
+};
+static const OSSL_FN_ULONG ex_mul_num2_num2[] = {
+    OSSL_FN_ULONG64_C(0xDCA5E208, 0x90F2A521),
+    OSSL_FN_ULONG64_C(0x00014B66, 0xDC33F6AC),
+};
+static const OSSL_FN_ULONG ex_mul_num2_num3[] = {
+    OSSL_FN_ULONG64_C(0x2236D88F, 0xE5618CF0),
+    OSSL_FN_ULONG64_C(0x0121FA00, 0xAD77D742),
+};
+static const OSSL_FN_ULONG ex_mul_num3_num3[] = {
+    OSSL_FN_ULONG64_C(0xDEEC6CD7, 0xA44A4100),
+    OSSL_FN_ULONG64_C(0xFDBAC097, 0xC8DC5ACC),
+};
+/* Expected results for edge cases */
+static const OSSL_FN_ULONG ex_mul_num4_num4[] = {
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000000),
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000000),
+};
+static const OSSL_FN_ULONG ex_mul_num5_num5[] = {
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000001),
+    OSSL_FN_ULONG64_C(0xFFFFFFFF, 0xFFFFFFFE),
+};
+static const OSSL_FN_ULONG ex_mul_num6_num6[] = {
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000000),
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000000),
+    OSSL_FN_ULONG64_C(0x00000000, 0x00000000),
+    OSSL_FN_ULONG64_C(0x01000000, 0x00000000),
+};
+
+static int test_mul_feature_r_is_operand(int i)
+{
+    int ret = 1;
+    const OSSL_FN_ULONG *a_data = num0;
+    size_t a_limbs = sizeof(num0) / OSSL_FN_BYTES;
+    const OSSL_FN_ULONG *b_data = num1;
+    size_t b_limbs = sizeof(num1) / OSSL_FN_BYTES;
+    OSSL_FN *a = NULL, *b = NULL, *res = NULL;
+
+    OSSL_FN_CTX *ctx = NULL;
+    if (!TEST_ptr(ctx = OSSL_FN_CTX_new(NULL, 1, 1, a_limbs + b_limbs))
+        || !TEST_ptr(a = OSSL_FN_new_limbs(a_limbs))
+        || !TEST_ptr(b = OSSL_FN_new_limbs(b_limbs))
+        || !TEST_true(ossl_fn_set_words(a, a_data, a_limbs))
+        || !TEST_true(ossl_fn_set_words(b, b_data, b_limbs))) {
+        ret = 0;
+        /* There's no way to continue tests in this case */
+        goto end;
+    }
+
+    const OSSL_FN *op1 = NULL, *op2 = NULL;
+    const OSSL_FN_ULONG *u = NULL;
+    const OSSL_FN_ULONG *ex_data = NULL;
+    size_t ex_limbs = 0;
+
+    switch (i) {
+    case 0:
+        /* a * b, result in separate OSSL_FN */
+        if (!TEST_ptr(res = OSSL_FN_new_limbs(a_limbs + b_limbs))) {
+            ret = 0;
+            goto end;
+        }
+        op1 = a;
+        op2 = b;
+        ex_data = ex_mul_num0_num1;
+        ex_limbs = ossl_fn_get_dsize(res);
+        break;
+    case 1:
+        /* a * b, result in a */
+        res = a;
+        op1 = a;
+        op2 = b;
+        ex_data = ex_mul_num0_num1;
+        ex_limbs = ossl_fn_get_dsize(res);
+        break;
+    case 2:
+        /* a * b, result in b */
+        res = b;
+        op1 = a;
+        op2 = b;
+        ex_data = ex_mul_num0_num1;
+        ex_limbs = ossl_fn_get_dsize(res);
+        break;
+    case 3:
+        /* a * a, result in a */
+        res = a;
+        op1 = a;
+        op2 = a;
+        ex_data = ex_mul_num0_num0;
+        ex_limbs = ossl_fn_get_dsize(res);
+        break;
+    default:
+        /* Invalid call */
         ret = 0;
-    OSSL_FN_free(num1);
-    OSSL_FN_free(num2);
+        goto end;
+    }
+
+    if (!TEST_true(OSSL_FN_mul(res, op1, op2, ctx))
+        || !TEST_ptr(u = ossl_fn_get_words(res))
+        || !TEST_mem_eq(u, ex_limbs * OSSL_FN_BYTES,
+            ex_data, ex_limbs * OSSL_FN_BYTES))
+        ret = 0;
+
+    if (TEST_ptr(u = ossl_fn_get_words(a))) {
+        if (res == a) {
+            if (!TEST_mem_eq(u, ex_limbs * OSSL_FN_BYTES,
+                    ex_data, ex_limbs * OSSL_FN_BYTES))
+                res = 0;
+        } else {
+            if (!TEST_mem_eq(u, a_limbs * OSSL_FN_BYTES,
+                    a_data, a_limbs * OSSL_FN_BYTES))
+                res = 0;
+        }
+    }
+
+    if (TEST_ptr(u = ossl_fn_get_words(b))) {
+        if (res == b) {
+            if (!TEST_mem_eq(u, ex_limbs * OSSL_FN_BYTES,
+                    ex_data, ex_limbs * OSSL_FN_BYTES))
+                res = 0;
+        } else {
+            if (!TEST_mem_eq(u, b_limbs * OSSL_FN_BYTES,
+                    b_data, b_limbs * OSSL_FN_BYTES))
+                res = 0;
+        }
+    }
+
+end:
+    OSSL_FN_CTX_free(ctx);
+    if (res != a && res != b)
+        OSSL_FN_free(res);
+    OSSL_FN_free(a);
+    OSSL_FN_free(b);
+    return ret;
+}
+
+static int test_mul_common(struct test_case_st test_case)
+{
+    int ret = 1;
+    const OSSL_FN_ULONG *n1 = test_case.op1;
+    size_t n1_limbs = test_case.op1_size;
+    const OSSL_FN_ULONG *n2 = test_case.op2;
+    size_t n2_limbs = test_case.op2_size;
+    const OSSL_FN_ULONG *ex = test_case.ex;
+    size_t n1_new_limbs = test_case.op1_live_size;
+    size_t n2_new_limbs = test_case.op2_live_size;
+    size_t res_limbs = test_case.res_live_size;
+    size_t check_limbs = test_case.check_size;
+    OSSL_FN *fn1 = NULL, *fn2 = NULL, *res = NULL;
+    const OSSL_FN_ULONG *u = NULL;
+
+    OSSL_FN_CTX *ctx = NULL;
+    if (!TEST_ptr(ctx = OSSL_FN_CTX_new(NULL, 1, 1, res_limbs))
+        || !TEST_ptr(fn1 = OSSL_FN_new_limbs(n1_new_limbs))
+        || !TEST_ptr(fn2 = OSSL_FN_new_limbs(n2_new_limbs))
+        || !TEST_true(ossl_fn_set_words(fn1, n1, n1_limbs))
+        || !TEST_true(ossl_fn_set_words(fn2, n2, n2_limbs))
+        || !TEST_ptr(res = OSSL_FN_new_limbs(res_limbs))) {
+        res = 0;
+        /* There's no way to continue tests in this case */
+        goto end;
+    }
+
+    /* To test that OSSL_FN_mul() does a complete job, 'res' is pre-polluted */
+
+    if (!TEST_true(pollute(res, 0, res_limbs))
+        || !TEST_true(OSSL_FN_mul(res, fn1, fn2, ctx))
+        || !TEST_ptr(u = ossl_fn_get_words(res))
+        || !TEST_mem_eq(u, check_limbs * OSSL_FN_BYTES,
+            ex, check_limbs * OSSL_FN_BYTES)
+        || !TEST_true(check_zero(res, check_limbs, res_limbs)))
+        ret = 0;
+
+end:
+    OSSL_FN_CTX_free(ctx);
+    OSSL_FN_free(fn1);
+    OSSL_FN_free(fn2);
     OSSL_FN_free(res);
 
     return ret;
 }
 
+/* i should be set to match the iteration number that's displayed when testing */
+#define MUL_CASE(i, op1, op2, ex)                                        \
+    {                                                                    \
+        /* op1, with size */ op1,                                        \
+        LIMBSOF(op1),                                                    \
+        /* op2, with size */ op2,                                        \
+        LIMBSOF(op2),                                                    \
+        /* ex, with size */ ex,                                          \
+        LIMBSOF(ex),                                                     \
+        /* op1_live_size */ LIMBSOF(op1) + 1,                            \
+        /* op2_live_size */ LIMBSOF(op2) + 2,                            \
+        /* res_live_size */ LIMBSOF(op1) + LIMBSOF(op2) + ((i - 1) % 4), \
+        /* check_size */ LIMBSOF(ex),                                    \
+    }
+
+static struct test_case_st test_mul_cases[] = {
+    MUL_CASE(1, num0, num0, ex_mul_num0_num0),
+    MUL_CASE(2, num0, num1, ex_mul_num0_num1),
+    MUL_CASE(3, num0, num2, ex_mul_num0_num2),
+    MUL_CASE(4, num0, num3, ex_mul_num0_num3),
+    MUL_CASE(5, num1, num0, ex_mul_num0_num1), /* Commutativity check */
+    MUL_CASE(6, num1, num1, ex_mul_num1_num1),
+    MUL_CASE(7, num1, num2, ex_mul_num1_num2),
+    MUL_CASE(8, num1, num3, ex_mul_num1_num3),
+    MUL_CASE(9, num2, num0, ex_mul_num0_num2), /* Commutativity check */
+    MUL_CASE(10, num2, num1, ex_mul_num1_num2), /* Commutativity check */
+    MUL_CASE(11, num2, num2, ex_mul_num2_num2),
+    MUL_CASE(12, num2, num3, ex_mul_num2_num3),
+    MUL_CASE(13, num3, num0, ex_mul_num0_num3), /* Commutativity check */
+    MUL_CASE(14, num3, num1, ex_mul_num1_num3), /* Commutativity check */
+    MUL_CASE(15, num3, num2, ex_mul_num2_num3), /* Commutativity check */
+    MUL_CASE(16, num3, num3, ex_mul_num3_num3),
+
+    /* Edge cases */
+    MUL_CASE(17, num4, num4, ex_mul_num4_num4),
+    MUL_CASE(18, num5, num5, ex_mul_num5_num5),
+    MUL_CASE(19, num6, num6, ex_mul_num6_num6),
+};
+
+static int test_mul(int i)
+{
+    return test_mul_common(test_mul_cases[i]);
+}
+
+/* i should be set to match the iteration number that's displayed when testing */
+#define MUL_TRUNCATED_CASE(i, op1, op2, ex)   \
+    {                                         \
+        /* op1, with size */ op1,             \
+        LIMBSOF(op1),                         \
+        /* op2, with size */ op2,             \
+        LIMBSOF(op2),                         \
+        /* ex, with size */ ex,               \
+        LIMBSOF(ex),                          \
+        /* op1_live_size */ LIMBSOF(op1) + 1, \
+        /* op2_live_size */ LIMBSOF(op2) + 2, \
+        /* res_live_size */ LIMBSOF(ex) / 2,  \
+        /* check_size */ LIMBSOF(ex) / 2,     \
+    }
+/* A special case, where the truncation is set to the size of ex minus 64 bits */
+#define MUL_TRUNCATED_SPECIAL_CASE1(i, op1, op2, ex)         \
+    {                                                        \
+        /* op1, with size */ op1,                            \
+        LIMBSOF(op1),                                        \
+        /* op2, with size */ op2,                            \
+        LIMBSOF(op2),                                        \
+        /* ex, with size */ ex,                              \
+        LIMBSOF(ex),                                         \
+        /* op1_live_size */ LIMBSOF(op1) + 1,                \
+        /* op2_live_size */ LIMBSOF(op2) + 2,                \
+        /* res_live_size */ LIMBSOF(ex) - 8 / OSSL_FN_BYTES, \
+        /* check_size */ LIMBSOF(ex) - 8 / OSSL_FN_BYTES,    \
+    }
+
+static struct test_case_st test_mul_truncate_cases[] = {
+    MUL_TRUNCATED_CASE(1, num0, num0, ex_mul_num0_num0),
+    MUL_TRUNCATED_CASE(2, num0, num1, ex_mul_num0_num1),
+    MUL_TRUNCATED_CASE(3, num0, num2, ex_mul_num0_num2),
+    MUL_TRUNCATED_CASE(4, num0, num3, ex_mul_num0_num3),
+    MUL_TRUNCATED_CASE(5, num1, num0, ex_mul_num0_num1), /* Commutativity check */
+    MUL_TRUNCATED_CASE(6, num1, num1, ex_mul_num1_num1),
+    MUL_TRUNCATED_CASE(7, num1, num2, ex_mul_num1_num2),
+    MUL_TRUNCATED_CASE(8, num1, num3, ex_mul_num1_num3),
+    MUL_TRUNCATED_CASE(9, num2, num0, ex_mul_num0_num2), /* Commutativity check */
+    MUL_TRUNCATED_CASE(10, num2, num1, ex_mul_num1_num2), /* Commutativity check */
+    MUL_TRUNCATED_CASE(11, num2, num2, ex_mul_num2_num2),
+    MUL_TRUNCATED_CASE(12, num2, num3, ex_mul_num2_num3),
+    MUL_TRUNCATED_CASE(13, num3, num0, ex_mul_num0_num3), /* Commutativity check */
+    MUL_TRUNCATED_CASE(14, num3, num1, ex_mul_num1_num3), /* Commutativity check */
+    MUL_TRUNCATED_CASE(15, num3, num2, ex_mul_num2_num3), /* Commutativity check */
+    MUL_TRUNCATED_CASE(16, num3, num3, ex_mul_num3_num3),
+
+    /* Edge cases */
+    MUL_TRUNCATED_CASE(17, num4, num4, ex_mul_num4_num4),
+    MUL_TRUNCATED_CASE(18, num5, num5, ex_mul_num5_num5),
+    MUL_TRUNCATED_SPECIAL_CASE1(19, num6, num6, ex_mul_num6_num6),
+};
+
+static int test_mul_truncated(int i)
+{
+    return test_mul_common(test_mul_truncate_cases[i]);
+}
+
 int setup_tests(void)
 {
     ADD_ALL_TESTS(test_add, 16);
     ADD_ALL_TESTS(test_sub, 16);
+    ADD_ALL_TESTS(test_mul_feature_r_is_operand, 4);
+    ADD_ALL_TESTS(test_mul, OSSL_NELEM(test_mul_cases));
+    ADD_ALL_TESTS(test_mul_truncated, OSSL_NELEM(test_mul_truncate_cases));
 
     return 1;
 }