blob: 96222922b692d6069d7564a92694e153caa0fd7e [file] [edit]
/*
* Copyright 2026 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 <limits.h>
#include <stdio.h>
#include <string.h>
#include <openssl/bn.h>
#include <openssl/crypto.h>
#include "crypto/fn.h"
#include "crypto/bn.h"
#include "crypto/fn_intern.h"
#include "internal/nelem.h"
#include "crypto/fnerr.h"
#include "testutil.h"
/*
* This is a stanza-driven arithmetic test for OSSL_FN. Non-stanza API tests
* belong in fn_api_test.
*/
typedef struct filetest_st {
const char *name;
int (*func)(STANZA *s);
int skipped;
} FILETEST;
static const char *findattr(STANZA *s, const char *key)
{
int i = s->numpairs;
PAIR *pp = s->pairs;
for (; --i >= 0; pp++)
if (OPENSSL_strcasecmp(pp->key, key) == 0)
return pp->value;
return NULL;
}
static int parseBN(BIGNUM **out, const char *in)
{
*out = NULL;
return BN_hex2bn(out, in);
}
static BIGNUM *getBN(STANZA *s, const char *attribute)
{
const char *hex;
BIGNUM *ret = NULL;
if ((hex = findattr(s, attribute)) == NULL) {
TEST_error("%s:%d: Can't find %s", s->test_file, s->start, attribute);
return NULL;
}
if (parseBN(&ret, hex) != (int)strlen(hex)) {
TEST_error("Could not decode '%s'", hex);
BN_free(ret);
return NULL;
}
return ret;
}
static int getint(STANZA *s, int *out, const char *attribute)
{
BIGNUM *ret;
BN_ULONG word;
int st = 0;
if (!TEST_ptr(ret = getBN(s, attribute))
|| !TEST_uint64_t_le(word = BN_get_word(ret), INT_MAX))
goto err;
*out = (int)word;
st = 1;
err:
BN_free(ret);
return st;
}
static int equalBN(const char *op, const BIGNUM *expected, const BIGNUM *actual)
{
if (!TEST_BN_eq(expected, actual)) {
TEST_error("unexpected %s value", op);
return 0;
}
return 1;
}
/*
* Number of limbs needed to hold bn's value, equivalent to the (properly
* adjusted) BIGNUM 'top' field, which is not directly accessible here.
*/
static int limbs(const BIGNUM *bn)
{
int ret = (BN_num_bits(bn) + BN_BITS2 - 1) / BN_BITS2;
return ret > 0 ? ret : 1;
}
static int set_result_addsub(OSSL_FN *r, OSSL_FN *a, int a_neg,
OSSL_FN *b, int b_neg, int *neg)
{
int cmp;
*neg = 0;
if (a_neg == b_neg) {
*neg = a_neg;
return OSSL_FN_add(r, a, b);
}
cmp = OSSL_FN_cmp(a, b);
if (cmp >= 0) {
*neg = a_neg;
return OSSL_FN_sub(r, a, b);
}
*neg = b_neg;
return OSSL_FN_sub(r, b, a);
}
static int file_sum(STANZA *s)
{
BIGNUM *a = NULL, *b = NULL, *sum = NULL, *ret = NULL;
OSSL_FN *af = NULL, *bf = NULL, *rf = NULL;
int a_neg = 0, b_neg = 0, r_neg = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(b = getBN(s, "B"))
|| !TEST_ptr(sum = getBN(s, "Sum"))
|| !TEST_ptr(ret = BN_new()))
goto err;
a_neg = BN_is_negative(a);
b_neg = BN_is_negative(b);
nlimbs = limbs(sum);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(bf = bn_get_ossl_fn(b))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(set_result_addsub(rf, af, a_neg, bf, b_neg, &r_neg)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, r_neg);
r_acq = 0;
if (!equalBN("A + B", sum, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
BN_free(a);
BN_free(b);
BN_free(sum);
BN_free(ret);
return st;
}
static int file_product(STANZA *s)
{
BIGNUM *a = NULL, *b = NULL, *product = NULL, *ret = NULL;
OSSL_FN *af = NULL, *bf = NULL, *rf = NULL;
OSSL_FN_CTX *ctx = NULL;
int a_neg = 0, b_neg = 0, r_neg = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(b = getBN(s, "B"))
|| !TEST_ptr(product = getBN(s, "Product"))
|| !TEST_ptr(ret = BN_new()))
goto err;
a_neg = BN_is_negative(a);
b_neg = BN_is_negative(b);
r_neg = a_neg ^ b_neg;
nlimbs = limbs(product);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(bf = bn_get_ossl_fn(b))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mul_ctx_size(rf, af, bf))))
goto err;
if (!TEST_true(OSSL_FN_mul(rf, af, bf, ctx)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, r_neg);
r_acq = 0;
if (!equalBN("A * B", product, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(b);
BN_free(product);
BN_free(ret);
return st;
}
static int file_square(STANZA *s)
{
BIGNUM *a = NULL, *square = NULL, *ret = NULL;
OSSL_FN *af = NULL, *rf = NULL;
OSSL_FN_CTX *ctx = NULL;
int st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(square = getBN(s, "Square"))
|| !TEST_ptr(ret = BN_new()))
goto err;
nlimbs = limbs(square);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_sqr_ctx_size(rf, af))))
goto err;
if (!TEST_true(OSSL_FN_sqr(rf, af, ctx)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, 0);
r_acq = 0;
if (!equalBN("A^2", square, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(square);
BN_free(ret);
return st;
}
static int file_quotient(STANZA *s)
{
BIGNUM *a = NULL, *b = NULL, *quotient = NULL, *remainder = NULL;
BIGNUM *qret = NULL, *rret = NULL, *mret = NULL;
OSSL_FN *af = NULL, *bf = NULL, *qf = NULL, *rf = NULL, *mf = NULL;
OSSL_FN_CTX *ctx = NULL, *mod_ctx = NULL;
int a_neg = 0, b_neg = 0, q_neg = 0, r_neg = 0, st = 0;
int q_acq = 0, r_acq = 0, m_acq = 0;
int q_limbs = 0, r_limbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(b = getBN(s, "B"))
|| !TEST_ptr(quotient = getBN(s, "Quotient"))
|| !TEST_ptr(remainder = getBN(s, "Remainder"))
|| !TEST_ptr(qret = BN_new())
|| !TEST_ptr(rret = BN_new())
|| !TEST_ptr(mret = BN_new()))
goto err;
a_neg = BN_is_negative(a);
b_neg = BN_is_negative(b);
q_neg = a_neg ^ b_neg;
r_neg = a_neg;
q_limbs = limbs(quotient);
r_limbs = limbs(remainder);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(bf = bn_get_ossl_fn(b))
|| !TEST_ptr(qf = bn_acquire_ossl_fn(qret, q_limbs)))
goto err;
q_acq = 1;
if (!TEST_ptr(rf = bn_acquire_ossl_fn(rret, r_limbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(mf = bn_acquire_ossl_fn(mret, r_limbs)))
goto err;
m_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_div_ctx_size(qf, rf, af, bf)))
|| !TEST_ptr(mod_ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_ctx_size(mf, af, bf))))
goto err;
if (!TEST_true(OSSL_FN_div(qf, rf, af, bf, ctx)))
goto err;
bn_release(qret, q_limbs);
if (!BN_is_zero(qret))
BN_set_negative(qret, q_neg);
q_acq = 0;
bn_release(rret, r_limbs);
if (!BN_is_zero(rret))
BN_set_negative(rret, r_neg);
r_acq = 0;
if (!equalBN("A / B", quotient, qret)
|| !equalBN("A % B", remainder, rret))
goto err;
if (!TEST_true(OSSL_FN_mod(mf, af, bf, mod_ctx)))
goto err;
bn_release(mret, r_limbs);
if (!BN_is_zero(mret))
BN_set_negative(mret, r_neg);
m_acq = 0;
if (!equalBN("A % B (mod)", remainder, mret))
goto err;
st = 1;
err:
if (m_acq)
bn_release(mret, r_limbs);
if (r_acq)
bn_release(rret, r_limbs);
if (q_acq)
bn_release(qret, q_limbs);
OSSL_FN_CTX_free(mod_ctx);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(b);
BN_free(quotient);
BN_free(remainder);
BN_free(qret);
BN_free(rret);
BN_free(mret);
return st;
}
static int file_lshift1(STANZA *s)
{
BIGNUM *a = NULL, *lshift1 = NULL, *two = NULL, *ret = NULL;
OSSL_FN *af = NULL, *lf = NULL, *tf = NULL, *rf = NULL;
OSSL_FN_CTX *ctx = NULL;
int a_neg = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(lshift1 = getBN(s, "LShift1"))
|| !TEST_ptr(two = BN_new())
|| !TEST_ptr(ret = BN_new()))
goto err;
a_neg = BN_is_negative(a);
nlimbs = limbs(lshift1);
BN_set_word(two, 2);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(lf = bn_get_ossl_fn(lshift1))
|| !TEST_ptr(tf = bn_get_ossl_fn(two))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mul_ctx_size(rf, af, tf))))
goto err;
/* A + A == LShift1 */
if (!TEST_true(OSSL_FN_add(rf, af, af)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("A + A", lshift1, ret))
goto err;
/* A * 2 == LShift1 */
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_mul(rf, af, tf, ctx)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("A * 2", lshift1, ret))
goto err;
/*
* TODO(FIXNUM): bntest.c also checks the division/modulus identities
* LShift1 / 2 == A ("LShift1 / 2")
* LShift1 % 2 == 0 ("LShift1 % 2")
* using BN_div(). These cannot be ported yet: there is no OSSL_FN_div().
*/
/* A << 1 == LShift1 */
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_lshift1(rf, af)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg);
r_acq = 0;
if (!equalBN("A << 1", lshift1, ret))
goto err;
/*
* Round-trip: LShift1 >> 1 == A, done twice to match bntest.c's
* double-check structure.
*
* TODO(FIXNUM): bntest.c's second iteration forces the LSB of LShift1
* to 1 (BN_set_bit(lshift1, 0)) and then checks
* (LShift1 | 1) / 2 == A ("(LShift1 | 1) / 2")
* (LShift | 1) >> 1 == A ("(LShift | 1) >> 1")
* to exercise rshift1's flooring of an odd operand. This cannot be
* ported yet: there is no OSSL_FN_set_bit(), and the division variant
* also needs OSSL_FN_div(). For now we just repeat the even case to
* preserve the two-iteration shape.
*/
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_rshift1(rf, lf)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("LShift >> 1", a, ret))
goto err;
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_rshift1(rf, lf)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("LShift >> 1", a, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(lshift1);
BN_free(two);
BN_free(ret);
return st;
}
static int file_lshift(STANZA *s)
{
BIGNUM *a = NULL, *lshift = NULL, *ret = NULL;
OSSL_FN *af = NULL, *lf = NULL, *rf = NULL;
int a_neg = 0, n = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(lshift = getBN(s, "LShift"))
|| !TEST_ptr(ret = BN_new())
|| !getint(s, &n, "N"))
goto err;
a_neg = BN_is_negative(a);
nlimbs = limbs(lshift);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(lf = bn_get_ossl_fn(lshift))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_lshift(rf, af, n)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg);
r_acq = 0;
if (!equalBN("A << N", lshift, ret))
goto err;
/* Round-trip: shift the result back and recover A */
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_rshift(rf, lf, n)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("A >> N", a, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
BN_free(a);
BN_free(lshift);
BN_free(ret);
return st;
}
static int file_rshift(STANZA *s)
{
BIGNUM *a = NULL, *rshift = NULL, *ret = NULL;
OSSL_FN *af = NULL, *rf = NULL;
int a_neg = 0, n = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(rshift = getBN(s, "RShift"))
|| !TEST_ptr(ret = BN_new())
|| !getint(s, &n, "N"))
goto err;
a_neg = BN_is_negative(a);
nlimbs = limbs(rshift);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_rshift(rf, af, n)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("A >> N", rshift, ret))
goto err;
/* If N == 1, try with rshift1 as well */
if (n == 1) {
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_true(OSSL_FN_rshift1(rf, af)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, a_neg && !BN_is_zero(ret));
r_acq = 0;
if (!equalBN("A >> 1 (rshift1)", rshift, ret))
goto err;
}
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
BN_free(a);
BN_free(rshift);
BN_free(ret);
return st;
}
static int file_modmul(STANZA *s)
{
BIGNUM *a = NULL, *b = NULL, *m = NULL, *mod_mul = NULL, *ret = NULL;
OSSL_FN *af = NULL, *bf = NULL, *rf = NULL, *mf = NULL;
OSSL_FN_CTX *ctx = NULL;
OSSL_FN_MONT_CTX *mont = NULL;
const void *token = NULL;
int a_neg = 0, b_neg = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(b = getBN(s, "B"))
|| !TEST_ptr(m = getBN(s, "M"))
|| !TEST_ptr(mod_mul = getBN(s, "ModMul"))
|| !TEST_ptr(ret = BN_new()))
goto err;
a_neg = BN_is_negative(a);
b_neg = BN_is_negative(b);
nlimbs = limbs(m);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(bf = bn_get_ossl_fn(b))
|| !TEST_ptr(mf = bn_get_ossl_fn(m))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_mul_ctx_size(rf, af, bf, mf))))
goto err;
/*
* OSSL_FN is unsigned, so the multiplication is on absolute values.
* If the operands have different signs, the non-negative modular
* residue of A * B is M - ((|A| * |B|) mod M), unless that is zero.
*/
if (!TEST_true(OSSL_FN_mod_mul(rf, af, bf, mf, ctx)))
goto err;
bn_release(ret, nlimbs);
r_acq = 0;
if ((a_neg ^ b_neg) && !BN_is_zero(ret)) {
if (!TEST_true(BN_sub(ret, m, ret)))
goto err;
}
if (!equalBN("A * B (mod M)", mod_mul, ret))
goto err;
if (BN_is_odd(m)) {
/* Test the Montgomery version. */
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(mont = OSSL_FN_MONT_CTX_new(mf)))
goto err;
size_t max = 0, tmp;
tmp = OSSL_FN_to_mont_ctx_size(NULL, af, mont);
if (tmp > max)
max = tmp;
tmp = OSSL_FN_to_mont_ctx_size(NULL, bf, mont);
if (tmp > max)
max = tmp;
tmp = OSSL_FN_mul_mont_quick_ctx_size(NULL, NULL, NULL, mont);
if (tmp > max)
max = tmp;
tmp = OSSL_FN_from_mont_ctx_size(NULL, NULL, mont);
if (tmp > max)
max = tmp;
OSSL_FN_CTX_free(ctx);
ctx = NULL;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
max + OSSL_FN_CTX_size(1, 3, 3 * (size_t)nlimbs)))
|| !TEST_ptr(token = OSSL_FN_CTX_start(ctx)))
goto err;
OSSL_FN *am, *bm, *rm;
if (!TEST_ptr(am = OSSL_FN_CTX_get_limbs(ctx, nlimbs))
|| !TEST_ptr(bm = OSSL_FN_CTX_get_limbs(ctx, nlimbs))
|| !TEST_ptr(rm = OSSL_FN_CTX_get_limbs(ctx, nlimbs)))
goto err;
/*
* OSSL_FN is unsigned, so the multiplication is on absolute values.
* If the operands have different signs, the non-negative modular
* residue of A * B is M - ((|A| * |B|) mod M), unless that is zero.
*/
if (!TEST_true(OSSL_FN_to_mont(am, af, mont, ctx))
|| !TEST_true(OSSL_FN_to_mont(bm, bf, mont, ctx))
|| !TEST_true(OSSL_FN_mul_mont_quick(rm, am, bm, mont, ctx))
|| !TEST_true(OSSL_FN_from_mont(rf, rm, mont, ctx)))
goto err;
bn_release(ret, nlimbs);
r_acq = 0;
if ((a_neg ^ b_neg) && !BN_is_zero(ret)) {
if (!TEST_true(BN_sub(ret, m, ret)))
goto err;
}
if (!equalBN("A * B (mod M)", mod_mul, ret))
goto err;
}
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
if (token != NULL)
OSSL_FN_CTX_end(ctx, token);
OSSL_FN_CTX_free(ctx);
OSSL_FN_MONT_CTX_free(mont);
BN_free(a);
BN_free(b);
BN_free(m);
BN_free(mod_mul);
BN_free(ret);
return st;
}
static int file_modsqr(STANZA *s)
{
BIGNUM *a = NULL, *m = NULL, *mod_sqr = NULL, *ret = NULL;
OSSL_FN *af = NULL, *rf = NULL, *mf = NULL;
OSSL_FN_CTX *ctx = NULL;
OSSL_FN_MONT_CTX *mont = NULL;
const void *token = NULL;
int st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(m = getBN(s, "M"))
|| !TEST_ptr(mod_sqr = getBN(s, "ModSqr"))
|| !TEST_ptr(ret = BN_new()))
goto err;
nlimbs = limbs(m);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(mf = bn_get_ossl_fn(m))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_sqr_ctx_size(rf, af, mf))))
goto err;
/*
* Squaring is always non-negative, so no sign fixup is needed.
*/
if (!TEST_true(OSSL_FN_mod_sqr(rf, af, mf, ctx)))
goto err;
bn_release(ret, nlimbs);
r_acq = 0;
if (!equalBN("A^2 (mod M)", mod_sqr, ret))
goto err;
if (BN_is_odd(m)) {
/* Test the Montgomery version. */
if (!TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(mont = OSSL_FN_MONT_CTX_new(mf)))
goto err;
size_t max = 0, tmp;
tmp = OSSL_FN_to_mont_ctx_size(NULL, af, mont);
if (tmp > max)
max = tmp;
tmp = OSSL_FN_mul_mont_quick_ctx_size(NULL, NULL, NULL, mont);
if (tmp > max)
max = tmp;
tmp = OSSL_FN_from_mont_ctx_size(NULL, NULL, mont);
if (tmp > max)
max = tmp;
OSSL_FN_CTX_free(ctx);
ctx = NULL;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
max + OSSL_FN_CTX_size(1, 3, 3 * (size_t)nlimbs)))
|| !TEST_ptr(token = OSSL_FN_CTX_start(ctx)))
goto err;
OSSL_FN *am, *rm;
if (!TEST_ptr(am = OSSL_FN_CTX_get_limbs(ctx, nlimbs))
|| !TEST_ptr(rm = OSSL_FN_CTX_get_limbs(ctx, nlimbs)))
goto err;
if (!TEST_true(OSSL_FN_to_mont(am, af, mont, ctx))
|| !TEST_true(OSSL_FN_mul_mont_quick(rm, am, am, mont, ctx))
|| !TEST_true(OSSL_FN_from_mont(rf, rm, mont, ctx)))
goto err;
bn_release(ret, nlimbs);
r_acq = 0;
if (!equalBN("A ^ 2 (mod M)", mod_sqr, ret))
goto err;
}
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
if (token != NULL)
OSSL_FN_CTX_end(ctx, token);
OSSL_FN_CTX_free(ctx);
OSSL_FN_MONT_CTX_free(mont);
BN_free(a);
BN_free(m);
BN_free(mod_sqr);
BN_free(ret);
return st;
}
static int file_gcd(STANZA *s)
{
BIGNUM *a = NULL, *b = NULL, *gcd = NULL, *ret = NULL;
OSSL_FN *af = NULL, *bf = NULL, *rf = NULL;
OSSL_FN_CTX *ctx = NULL;
int st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(b = getBN(s, "B"))
|| !TEST_ptr(gcd = getBN(s, "GCD"))
|| !TEST_ptr(ret = BN_new()))
goto err;
nlimbs = limbs(gcd);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(bf = bn_get_ossl_fn(b))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_gcd_ctx_size(af, bf))))
goto err;
if (!TEST_true(OSSL_FN_gcd(rf, af, bf, ctx)))
goto err;
bn_release(ret, nlimbs);
BN_set_negative(ret, 0);
r_acq = 0;
if (!equalBN("gcd(A,B)", gcd, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(b);
BN_free(gcd);
BN_free(ret);
return st;
}
static int file_modinv(STANZA *s)
{
BIGNUM *a = NULL, *m = NULL, *ainv_check = NULL, *ret = NULL, *prod = NULL;
OSSL_FN *af = NULL, *rf = NULL, *mf = NULL, *pf = NULL;
OSSL_FN_CTX *ctx = NULL, *mul_ctx = NULL;
int a_neg = 0, st = 0;
int r_acq = 0, p_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(m = getBN(s, "M"))
|| !TEST_ptr(ainv_check = getBN(s, "ModInv"))
|| !TEST_ptr(ret = BN_new())
|| !TEST_ptr(prod = BN_new()))
goto err;
a_neg = BN_is_negative(a);
nlimbs = limbs(m);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(mf = bn_get_ossl_fn(m))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(pf = bn_acquire_ossl_fn(prod, nlimbs)))
goto err;
p_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_inverse_ctx_size(rf, af, mf))))
goto err;
if (BN_is_negative(ainv_check)) {
/*
* Negative test: A has no inverse mod M (this covers the
* degenerate moduli M = 0 and M = 1, a == 0, and non-coprime pairs).
*/
ERR_set_mark();
if (!TEST_false(OSSL_FN_mod_inverse(rf, af, mf, ctx))
|| !TEST_int_eq(ERR_GET_LIB(ERR_peek_last_error()), ERR_LIB_OSSL_FN)
|| !TEST_int_eq(ERR_GET_REASON(ERR_peek_last_error()),
OSSL_FN_R_NO_INVERSE))
goto err;
ERR_pop_to_mark();
st = 1;
goto err;
}
/* The inverse is unique in [1, M). */
if (!TEST_true(OSSL_FN_mod_inverse(rf, af, mf, ctx)))
goto err;
/*
* Verify the unsigned identity |A| * r == 1 (mod M) natively, before the
* sign fixup below consumes r. OSSL_FN_mod_mul() needs its own sized
* arena, so a second context is allocated (the file_quotient idiom); pf
* is a distinct destination so r survives for the sign fixup and the
* corpus comparison below.
*/
if (!TEST_ptr(mul_ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_mul_ctx_size(pf, af, rf, mf)))
|| !TEST_true(OSSL_FN_mod_mul(pf, af, rf, mf, mul_ctx))
|| !TEST_true(OSSL_FN_is_one(pf)))
goto err;
bn_release(prod, nlimbs);
p_acq = 0;
bn_release(ret, nlimbs);
r_acq = 0;
/*
* OSSL_FN is unsigned, so OSSL_FN_mod_inverse() computed the inverse of
* |A|. For negative A the inverse of A is M - r (r is never zero for a
* valid inverse in [1, M)).
*/
if (a_neg) {
if (!TEST_true(BN_sub(ret, m, ret)))
goto err;
}
if (!equalBN("inv(A) (mod M)", ainv_check, ret))
goto err;
st = 1;
err:
if (p_acq)
bn_release(prod, nlimbs);
if (r_acq)
bn_release(ret, nlimbs);
OSSL_FN_CTX_free(mul_ctx);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(m);
BN_free(ainv_check);
BN_free(prod);
BN_free(ret);
return st;
}
static int file_modexp(STANZA *s)
{
BIGNUM *a = NULL, *e = NULL, *m = NULL, *mod_exp = NULL, *ret = NULL;
OSSL_FN *af = NULL, *ef = NULL, *rf = NULL, *mf = NULL;
OSSL_FN_CTX *ctx = NULL;
int a_neg = 0, e_odd = 0, st = 0;
int r_acq = 0;
int nlimbs = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(e = getBN(s, "E"))
|| !TEST_ptr(m = getBN(s, "M"))
|| !TEST_ptr(mod_exp = getBN(s, "ModExp"))
|| !TEST_ptr(ret = BN_new()))
goto err;
/*
* OSSL_FN_mod_exp() is unsigned: the base is exponentiated as its
* absolute value, so a negative base with an odd exponent yields
* M - (|A|^E mod M) (unless that is zero), as for modular
* multiplication; an even exponent leaves the residue non-negative.
* The bnmod.txt vectors currently use no negative base or exponent,
* so the fixup is a no-op there but is kept for forward safety.
*/
a_neg = BN_is_negative(a);
e_odd = BN_is_odd(e);
nlimbs = limbs(m);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(ef = bn_get_ossl_fn(e))
|| !TEST_ptr(mf = bn_get_ossl_fn(m))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_exp_ctx_size(rf, af, ef, mf))))
goto err;
if (!TEST_true(OSSL_FN_mod_exp(rf, af, ef, mf, ctx)))
goto err;
bn_release(ret, nlimbs);
r_acq = 0;
BN_set_negative(ret, 0);
if (a_neg && e_odd && !BN_is_zero(ret)) {
if (!TEST_true(BN_sub(ret, m, ret)))
goto err;
}
if (!equalBN("A ^ E (mod M)", mod_exp, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(e);
BN_free(m);
BN_free(mod_exp);
BN_free(ret);
return st;
}
static int file_modsqrt(STANZA *s)
{
BIGNUM *a = NULL, *p = NULL, *mod_sqrt = NULL, *ret = NULL, *ret2 = NULL;
OSSL_FN *af = NULL, *pf = NULL, *rf = NULL, *r2f = NULL;
OSSL_FN_CTX *ctx = NULL;
BN_CTX *bnctx = NULL;
int r_acq = 0, r2_acq = 0;
int nlimbs = 0;
int st = 0;
if (!TEST_ptr(a = getBN(s, "A"))
|| !TEST_ptr(p = getBN(s, "P"))
|| !TEST_ptr(mod_sqrt = getBN(s, "ModSqrt"))
|| !TEST_ptr(ret = BN_new())
|| !TEST_ptr(ret2 = BN_new())
|| !TEST_ptr(bnctx = BN_CTX_new()))
goto err;
/*
* BN_mod_sqrt() reduces a into [0, p) up front via BN_nnmod(); OSSL_FN is
* unsigned, so that reduction belongs here at the BIGNUM boundary rather
* than inside OSSL_FN_mod_sqrt() (which only sees the magnitude). Passing
* the raw magnitude for a negative a would feed sqrt() the wrong residue
* (e.g. a = -5, p = 7 reduces to 2, not 5).
*/
if (!TEST_true(BN_nnmod(a, a, p, bnctx)))
goto err;
nlimbs = limbs(p);
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(pf = bn_get_ossl_fn(p))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(ret, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(r2f = bn_acquire_ossl_fn(ret2, nlimbs)))
goto err;
r2_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_sqrt_ctx_size(rf, af, pf))))
goto err;
/*
* A negative ModSqrt value marks a negative testcase (mirroring bntest's
* file_modsqrt, which keys on BN_is_negative(mod_sqrt)): the input is not
* a square mod p (or p is not prime), and OSSL_FN_mod_sqrt() must fail.
* OSSL_FN is unsigned and the operands are taken as magnitudes; the
* negative marker only selects the failure expectation, it carries no
* sign into the computation.
*/
if (BN_is_negative(mod_sqrt)) {
if (!TEST_false(OSSL_FN_mod_sqrt(rf, af, pf, ctx)))
goto err;
st = 1;
goto err;
}
if (!TEST_true(OSSL_FN_mod_sqrt(rf, af, pf, ctx)))
goto err;
/* The other root is p - ret; both are valid answers. */
if (!TEST_true(OSSL_FN_sub(r2f, pf, rf)))
goto err;
bn_release(ret, nlimbs);
r_acq = 0;
BN_set_negative(ret, 0);
bn_release(ret2, nlimbs);
r2_acq = 0;
BN_set_negative(ret2, 0);
/*
* Accept either root, as in bntest. Use BN_cmp() for the first check so
* a mismatch on the wrong root does not emit a spurious equalBN diagnostic.
*/
if (BN_cmp(ret2, mod_sqrt) != 0
&& !equalBN("sqrt(A) (mod P)", mod_sqrt, ret))
goto err;
st = 1;
err:
if (r_acq)
bn_release(ret, nlimbs);
if (r2_acq)
bn_release(ret2, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_CTX_free(bnctx);
BN_free(a);
BN_free(p);
BN_free(mod_sqrt);
BN_free(ret);
BN_free(ret2);
return st;
}
static FILETEST filetests[] = {
{ "Sum", file_sum, 0 },
{ "LShift1", file_lshift1, 0 },
{ "LShift", file_lshift, 0 },
{ "RShift", file_rshift, 0 },
{ "Square", file_square, 0 },
{ "Product", file_product, 0 },
{ "Quotient", file_quotient, 0 },
{ "ModMul", file_modmul, 0 },
{ "ModSqr", file_modsqr, 0 },
{ "ModInv", file_modinv, 0 },
{ "ModExp", file_modexp, 0 },
{ "Exp", NULL, 0 },
{ "ModSqrt", file_modsqrt, 0 },
{ "GCD", file_gcd, 0 },
};
static int file_test_run(STANZA *s)
{
const FILETEST *tp = filetests;
size_t i;
for (i = 0; i < OSSL_NELEM(filetests); i++, tp++) {
if (findattr(s, tp->name) != NULL) {
if (tp->func == NULL) {
filetests[i].skipped++;
return 1;
}
if (!tp->func(s)) {
TEST_info("%s:%d: Failed %s test",
s->test_file, s->start, tp->name);
return 0;
}
return 1;
}
}
TEST_info("%s:%d: Unknown test, skipped", s->test_file, s->start);
return 1;
}
static int run_file_tests(int i)
{
STANZA *s = NULL;
char *testfile = test_get_argument(i);
int c;
size_t j;
for (j = 0; j < OSSL_NELEM(filetests); j++)
filetests[j].skipped = 0;
if (!TEST_ptr(s = OPENSSL_zalloc(sizeof(*s))))
return 0;
if (!test_start_file(s, testfile)) {
OPENSSL_free(s);
return 0;
}
while (!BIO_eof(s->fp) && test_readstanza(s)) {
if (s->numpairs == 0)
continue;
if (!file_test_run(s))
s->errors++;
s->numtests++;
test_clearstanza(s);
}
test_end_file(s);
for (j = 0; j < OSSL_NELEM(filetests); j++)
if (filetests[j].skipped > 0)
TEST_info("%s: skipped %d unsupported %s stanzas",
testfile, filetests[j].skipped, filetests[j].name);
c = s->errors;
OPENSSL_free(s);
return c == 0;
}
OPT_TEST_DECLARE_USAGE("file...\n")
/*
* Standalone modular-inverse test. Exercises OSSL_FN_mod_inverse() through
* the BIGNUM/OSSL_FN bridge (bn_acquire_ossl_fn() / bn_release()) with a
* known coprime pair: 5193817943 ^-1 mod 3259122431 == 2609653924.
*
* The r == n and r == a aliasing cases are expected to succeed:
* OSSL_FN_mod_inverse() captures the modulus and the operand into scratch
* temporaries before writing the result,
* and OSSL_FN_div() captures the denominator before writing the remainder,
* so overwriting either input operand with the result is safe.
*/
static int test_mod_inverse(void)
{
BIGNUM *a = NULL, *n = NULL, *r = NULL, *expected = NULL;
OSSL_FN *af = NULL, *nf = NULL, *rf = NULL;
OSSL_FN_CTX *ctx = NULL;
int r_acq = 0, n_acq = 0, a_acq = 0;
int nlimbs = 0;
int st = 0;
if (!TEST_true(BN_dec2bn(&a, "5193817943"))
|| !TEST_true(BN_dec2bn(&n, "3259122431"))
|| !TEST_true(BN_dec2bn(&expected, "2609653924"))
|| !TEST_ptr(r = BN_new()))
goto err;
nlimbs = limbs(n);
/* Distinct result: r = a^-1 mod n. */
if (!TEST_ptr(af = bn_get_ossl_fn(a))
|| !TEST_ptr(nf = bn_get_ossl_fn(n))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(r, nlimbs)))
goto err;
r_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_inverse_ctx_size(rf, af, nf))))
goto err;
if (!TEST_true(OSSL_FN_mod_inverse(rf, af, nf, ctx)))
goto err;
bn_release(r, nlimbs);
r_acq = 0;
BN_set_negative(r, 0);
if (!equalBN("a^-1 mod n", expected, r))
goto err;
/*
* Alias the result with the modulus: write the inverse back into n.
* Re-acquire n as the writable result; rf now aliases the modulus, so it
* is passed as both the result and the modulus.
*/
OSSL_FN_CTX_free(ctx);
ctx = NULL;
BN_free(r);
r = NULL;
if (!TEST_ptr(rf = bn_acquire_ossl_fn(n, nlimbs)))
goto err;
n_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_inverse_ctx_size(rf, af, rf))))
goto err;
if (!TEST_true(OSSL_FN_mod_inverse(rf, af, rf, ctx)))
goto err;
bn_release(n, nlimbs);
n_acq = 0;
BN_set_negative(n, 0);
if (!equalBN("a^-1 mod n (r == n)", expected, n))
goto err;
/*
* Alias the result with the operand: write the inverse back into a.
* Restore n first (it was overwritten above), then re-acquire a as the
* writable result; rf now aliases the operand, so it is passed as both
* the result and the operand.
*/
OSSL_FN_CTX_free(ctx);
ctx = NULL;
if (!TEST_true(BN_dec2bn(&n, "3259122431"))
|| !TEST_ptr(nf = bn_get_ossl_fn(n))
|| !TEST_ptr(rf = bn_acquire_ossl_fn(a, nlimbs)))
goto err;
a_acq = 1;
if (!TEST_ptr(ctx = OSSL_FN_CTX_new_size(NULL,
OSSL_FN_mod_inverse_ctx_size(rf, rf, nf))))
goto err;
if (!TEST_true(OSSL_FN_mod_inverse(rf, rf, nf, ctx)))
goto err;
bn_release(a, nlimbs);
a_acq = 0;
BN_set_negative(a, 0);
if (!equalBN("a^-1 mod n (r == a)", expected, a))
goto err;
st = 1;
err:
if (a_acq)
bn_release(a, nlimbs);
if (r_acq)
bn_release(r, nlimbs);
if (n_acq)
bn_release(n, nlimbs);
OSSL_FN_CTX_free(ctx);
BN_free(a);
BN_free(n);
BN_free(r);
BN_free(expected);
return st;
}
int setup_tests(void)
{
size_t n = test_get_argument_count();
if (!TEST_size_t_gt(n, 0))
return 0;
ADD_TEST(test_mod_inverse);
ADD_ALL_TESTS(run_file_tests, (int)n);
return 1;
}