Squashed 'src/secp256k1/' changes from 7262adb4b4..bd0287d650
What changed, and why it matters
This commit is a routine subtree update of the secp256k1 cryptographic library inside Bitcoin Core. It pulls in a batch of upstream secp256k1 changes: build-system cleanups, new tests, documentation fixes, a minor MuSig nonce-generation cleanup, and a refactor that adds a helper for converting secret-key multiplications into plain (non-Jacobian) curve points. None of the changes appear to fix an exploitable vulnerability in Bitcoin Core itself, and the commit message does not describe any security issue.
Treat as a normal dependency subtree update. Reviewers should verify the subtree hash matches the upstream secp256k1 commit bd0287d650c24dc41e0362675a9f6a49ee952def and that downstream Bitcoin Core code does not rely on any removed or renamed internal secp256k1 symbols. No urgent security action is indicated by the supplied materials.
Security signals we found
MuSig secret-key wipe now happens on both success and failure paths in `secp256k1_musig_nonce_gen_counter`
New `_ecmult_gen_ge` helper clears Jacobian intermediate state to reduce potential side-channel leakage
Force-inlining of performance-critical field operations (mul/sqr) is a hardening/performance change, not a vulnerability fix
No mention of CVE, security advisory, or vulnerability disclosure in commit message or diff
Evidence from the diff
The squashed subtree bump brings secp256k1 from 7262adb4b4 to bd0287d650. Notable code changes include: (1) a new _ecmult_gen_ge helper that computes generator multiplication directly in affine coordinates and clears intermediate Jacobian state, with call sites updated in ECDSA, Schnorr signing, pubkey creation, and blinding; (2) force-inlining of the 5x52 field multiply/square routines plus a new SECP256K1_FORCE_INLINE macro; (3) a fix in secp256k1_musig_nonce_gen_counter so the secret key is cleared even when the internal nonce-generation function returns failure; (4) dead-test fixes and new exhaustive ECDH tests; (5) CMake shared-library versioning emulation for NetBSD/OpenBSD and CI tweaks. The changes are defensive/refactoring in nature and do not alter consensus rules or wire protocol behavior.
Changed components
src/secp256k1 (subtree)secp256k1 ECDSA signingsecp256k1 Schnorr signingsecp256k1 public-key generationsecp256k1 MuSig nonce generationsecp256k1 build system (CMake/autotools)Inspect captured patch +257 / −90
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 152f9a1f..9b836cd6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -604,11 +604,10 @@ jobs:
steps:
- *CHECKOUT
- - name: Add cl.exe to PATH
- uses: ilammy/msvc-dev-cmd@v1
-
- name: C++ (public headers)
+ shell: cmd
run: |
+ call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cl.exe -c -WX -TP include/*.h
cxx_fpermissive_debian:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4ef69c08..a84305c3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -147,6 +147,8 @@ if(MSVC)
string(REGEX REPLACE "/DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
string(REGEX REPLACE "/DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
string(REGEX REPLACE "/DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}")
+ # Match GCC/Clang's size-optimization macro for the inline guard
+ add_compile_definitions($<$<CONFIG:MinSizeRel>:__OPTIMIZE_SIZE__=1>)
else()
string(REGEX REPLACE "-DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
string(REGEX REPLACE "-DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
diff --git a/ci/linux-debian.Dockerfile b/ci/linux-debian.Dockerfile
index a575d9b1..e743cda8 100644
--- a/ci/linux-debian.Dockerfile
+++ b/ci/linux-debian.Dockerfile
@@ -40,7 +40,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-instal
apt-get clean && rm -rf /var/lib/apt/lists/*
# Build and install gcc snapshot
-ARG GCC_SNAPSHOT_MAJOR=16
+ARG GCC_SNAPSHOT_MAJOR=17
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
wget libgmp-dev libmpfr-dev libmpc-dev flex && \
mkdir gcc && cd gcc && \
diff --git a/cmake/SetLibtoolAbiVersion.cmake b/cmake/SetLibtoolAbiVersion.cmake
new file mode 100644
index 00000000..0e981053
--- /dev/null
+++ b/cmake/SetLibtoolAbiVersion.cmake
@@ -0,0 +1,61 @@
+#[=[
+This emulates Libtool to make sure Libtool and CMake agree on
+the ABI version and file naming for shared libraries.
+
+The `version_type` variable is set in `libtool.m4` (installed
+by autoreconf into autotools-aux/m4/).
+For the `major` and `versuffix` variables, see below "Calculate
+the version variables" in `ltmain.sh` (installed by autoreconf
+into autotools-aux/).
+]=]
+function(set_libtool_abi_version target current revision age)
+ if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|FreeBSD)$")
+ # version_type = linux | freebsd-elf
+ # major = $current - $age
+ # versuffix = $major.$age.$revision
+ math(EXPR _major "${current} - ${age}")
+ set_target_properties(${target} PROPERTIES
+ SOVERSION ${_major}
+ VERSION ${_major}.${age}.${revision}
+ )
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
+ # version_type = sunos
+ # major = $current
+ # versuffix = $current.$revision
+ set_target_properties(${target} PROPERTIES
+ SOVERSION ${current}
+ VERSION ${current}.${revision}
+ )
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
+ # version_type = sunos
+ # major = $current
+ # versuffix = $current.$revision
+ set_target_properties(${target} PROPERTIES
+ # OpenBSD has no `soname_spec` defined in `libtool.m4`.
+ VERSION ${current}.${revision}
+ )
+ elseif(APPLE)
+ # version_type = darwin
+ # major = $current - $age
+ math(EXPR _major "${current} - ${age}")
+ math(EXPR _compatibility "${current} + 1")
+ set_target_properties(${target} PROPERTIES
+ SOVERSION ${_major}
+ MACHO_COMPATIBILITY_VERSION ${_compatibility}
+ MACHO_CURRENT_VERSION ${_compatibility}.${revision}
+ )
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ # version_type = windows
+ # major = $current - $age
+ # versuffix = $major
+ math(EXPR _major "${current} - ${age}")
+ set(_windows_name "secp256k1")
+ if(MSVC)
+ set(_windows_name "${PROJECT_NAME}")
+ endif()
+ set_target_properties(${target} PROPERTIES
+ ARCHIVE_OUTPUT_NAME "${_windows_name}"
+ RUNTIME_OUTPUT_NAME "${_windows_name}-${_major}"
+ )
+ endif()
+endfunction()
diff --git a/include/secp256k1.h b/include/secp256k1.h
index b7ec6a22..2f3383e1 100644
--- a/include/secp256k1.h
+++ b/include/secp256k1.h
@@ -687,7 +687,7 @@ SECP256K1_API const secp256k1_nonce_function secp256k1_nonce_function_default;
* Returns: 1: signature created
* 0: the nonce generation function failed, or the secret key was invalid.
* Args: ctx: pointer to a context object (not secp256k1_context_static).
- * Out: sig: pointer to an array where the signature will be placed.
+ * Out: sig: pointer to a signature object.
* In: msghash32: the 32-byte message hash being signed.
* seckey: pointer to a 32-byte secret key.
* noncefp: pointer to a nonce generation function. If NULL,
diff --git a/include/secp256k1_recovery.h b/include/secp256k1_recovery.h
index 2430f993..45f20aba 100644
--- a/include/secp256k1_recovery.h
+++ b/include/secp256k1_recovery.h
@@ -73,7 +73,7 @@ SECP256K1_API int secp256k1_ecdsa_recoverable_signature_serialize_compact(
* Returns: 1: signature created
* 0: the nonce generation function failed, or the secret key was invalid.
* Args: ctx: pointer to a context object (not secp256k1_context_static).
- * Out: sig: pointer to an array where the signature will be placed.
+ * Out: sig: pointer to a signature object.
* In: msghash32: the 32-byte message hash being signed.
* seckey: pointer to a 32-byte secret key.
* noncefp: pointer to a nonce generation function. If NULL,
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 322f1987..a45eeb93 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -94,35 +94,12 @@ set_target_properties(secp256k1_objs PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "$<TARGET_PROPERTY:secp256k1,INTERFACE_INCLUDE_DIRECTORIES>"
)
-# This emulates Libtool to make sure Libtool and CMake agree on the ABI version,
-# see below "Calculate the version variables" in autotools-aux/ltmain.sh.
-math(EXPR ${PROJECT_NAME}_soversion "${${PROJECT_NAME}_LIB_VERSION_CURRENT} - ${${PROJECT_NAME}_LIB_VERSION_AGE}")
-set_target_properties(secp256k1 PROPERTIES
- SOVERSION ${${PROJECT_NAME}_soversion}
+include(SetLibtoolAbiVersion)
+set_libtool_abi_version(secp256k1
+ ${${PROJECT_NAME}_LIB_VERSION_CURRENT}
+ ${${PROJECT_NAME}_LIB_VERSION_REVISION}
+ ${${PROJECT_NAME}_LIB_VERSION_AGE}
)
-if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|FreeBSD)$")
- set_target_properties(secp256k1 PROPERTIES
- VERSION ${${PROJECT_NAME}_soversion}.${${PROJECT_NAME}_LIB_VERSION_AGE}.${${PROJECT_NAME}_LIB_VERSION_REVISION}
- )
-elseif(APPLE)
- math(EXPR ${PROJECT_NAME}_compatibility_version "${${PROJECT_NAME}_LIB_VERSION_CURRENT} + 1")
- set_target_properties(secp256k1 PROPERTIES
- MACHO_COMPATIBILITY_VERSION ${${PROJECT_NAME}_compatibility_version}
- MACHO_CURRENT_VERSION ${${PROJECT_NAME}_compatibility_version}.${${PROJECT_NAME}_LIB_VERSION_REVISION}
- )
- unset(${PROJECT_NAME}_compatibility_version)
-elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
- set(${PROJECT_NAME}_windows "secp256k1")
- if(MSVC)
- set(${PROJECT_NAME}_windows "${PROJECT_NAME}")
- endif()
- set_target_properties(secp256k1 PROPERTIES
- ARCHIVE_OUTPUT_NAME "${${PROJECT_NAME}_windows}"
- RUNTIME_OUTPUT_NAME "${${PROJECT_NAME}_windows}-${${PROJECT_NAME}_soversion}"
- )
- unset(${PROJECT_NAME}_windows)
-endif()
-unset(${PROJECT_NAME}_soversion)
if(SECP256K1_BUILD_BENCHMARK)
add_executable(bench bench.c)
diff --git a/src/bench_ecmult.c b/src/bench_ecmult.c
index eb546db4..12d550a6 100644
--- a/src/bench_ecmult.c
+++ b/src/bench_ecmult.c
@@ -88,7 +88,7 @@ static void bench_ecmult_teardown_helper(bench_data* data, size_t* seckey_offset
secp256k1_scalar_add(&sum_scalars, &sum_scalars, &s);
}
}
- secp256k1_ecmult_gen(&data->ctx->ecmult_gen_ctx, &tmp, &sum_scalars);
+ secp256k1_ecmult_gen_gej(&data->ctx->ecmult_gen_ctx, &tmp, &sum_scalars);
CHECK(secp256k1_gej_eq_var(&tmp, &sum_output));
}
@@ -104,7 +104,7 @@ static void bench_ecmult_gen(void* arg, int iters) {
int i;
for (i = 0; i < iters; ++i) {
- secp256k1_ecmult_gen(&data->ctx->ecmult_gen_ctx, &data->output[i], &data->scalars[(data->offset1+i) % POINTS]);
+ secp256k1_ecmult_gen_gej(&data->ctx->ecmult_gen_ctx, &data->output[i], &data->scalars[(data->offset1+i) % POINTS]);
}
}
diff --git a/src/bench_internal.c b/src/bench_internal.c
index f3c1be98..453f3bb3 100644
--- a/src/bench_internal.c
+++ b/src/bench_internal.c
@@ -194,6 +194,17 @@ static void bench_field_normalize(void* arg, int iters) {
}
}
+static void bench_field_normalize_var(void* arg, int iters) {
+ int i;
+ bench_inv *data = (bench_inv*)arg;
+
+ /* Note that this benchmark measures the optimistic path. The worst-case path with the final
+ reduction is very unlikely to be needed, so this is representative of the common case. */
+ for (i = 0; i < iters; i++) {
+ secp256k1_fe_normalize_var(&data->fe[0]);
+ }
+}
+
static void bench_field_normalize_weak(void* arg, int iters) {
int i;
bench_inv *data = (bench_inv*)arg;
@@ -421,6 +432,7 @@ int main(int argc, char **argv) {
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "half")) run_benchmark("field_half", bench_field_half, bench_setup, NULL, &data, 10, iters*100);
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "normalize")) run_benchmark("field_normalize", bench_field_normalize, bench_setup, NULL, &data, 10, iters*100);
+ if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "normalize")) run_benchmark("field_normalize_var", bench_field_normalize_var, bench_setup, NULL, &data, 10, iters*100);
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "normalize")) run_benchmark("field_normalize_weak", bench_field_normalize_weak, bench_setup, NULL, &data, 10, iters*100);
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "sqr")) run_benchmark("field_sqr", bench_field_sqr, bench_setup, NULL, &data, 10, iters*10);
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "mul")) run_benchmark("field_mul", bench_field_mul, bench_setup, NULL, &data, 10, iters*10);
diff --git a/src/ctime_tests.c b/src/ctime_tests.c
index f80042a8..8a885ca2 100644
--- a/src/ctime_tests.c
+++ b/src/ctime_tests.c
@@ -40,6 +40,11 @@
#include "../include/secp256k1_ellswift.h"
#endif
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic warning "-Wunused-function"
+#endif
+
static void run_tests(secp256k1_context *ctx, unsigned char *key);
int main(void) {
@@ -265,3 +270,7 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
#endif
}
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
diff --git a/src/ecdsa_impl.h b/src/ecdsa_impl.h
index 163539eb..32f1e585 100644
--- a/src/ecdsa_impl.h
+++ b/src/ecdsa_impl.h
@@ -273,14 +273,12 @@ static int secp256k1_ecdsa_sig_verify(const secp256k1_scalar *sigr, const secp25
static int secp256k1_ecdsa_sig_sign(const secp256k1_ecmult_gen_context *ctx, secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *seckey, const secp256k1_scalar *message, const secp256k1_scalar *nonce, int *recid) {
unsigned char b[32];
- secp256k1_gej rp;
secp256k1_ge r;
secp256k1_scalar n;
int overflow = 0;
int high;
- secp256k1_ecmult_gen(ctx, &rp, nonce);
- secp256k1_ge_set_gej(&r, &rp);
+ secp256k1_ecmult_gen_ge(ctx, &r, nonce);
secp256k1_fe_normalize(&r.x);
secp256k1_fe_normalize(&r.y);
secp256k1_fe_get_b32(b, &r.x);
@@ -296,7 +294,6 @@ static int secp256k1_ecdsa_sig_sign(const secp256k1_ecmult_gen_context *ctx, sec
secp256k1_scalar_inverse(sigs, nonce);
secp256k1_scalar_mul(sigs, sigs, &n);
secp256k1_scalar_clear(&n);
- secp256k1_gej_clear(&rp);
secp256k1_ge_clear(&r);
high = secp256k1_scalar_is_high(sigs);
secp256k1_scalar_cond_negate(sigs, high);
diff --git a/src/ecmult_gen.h b/src/ecmult_gen.h
index 8bc4f14c..770b2cb2 100644
--- a/src/ecmult_gen.h
+++ b/src/ecmult_gen.h
@@ -137,7 +137,8 @@ static void secp256k1_ecmult_gen_context_build(secp256k1_ecmult_gen_context* ctx
static void secp256k1_ecmult_gen_context_clear(secp256k1_ecmult_gen_context* ctx);
/** Multiply with the generator: R = a*G */
-static void secp256k1_ecmult_gen(const secp256k1_ecmult_gen_context* ctx, secp256k1_gej *r, const secp256k1_scalar *a);
+static void secp256k1_ecmult_gen_gej(const secp256k1_ecmult_gen_context* ctx, secp256k1_gej *r, const secp256k1_scalar *a);
+static void secp256k1_ecmult_gen_ge(const secp256k1_ecmult_gen_context* ctx, secp256k1_ge *r, const secp256k1_scalar *a);
static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const secp256k1_hash_ctx *hash_ctx, const unsigned char *seed32);
diff --git a/src/ecmult_gen_impl.h b/src/ecmult_gen_impl.h
index 5a954977..a7a6d34d 100644
--- a/src/ecmult_gen_impl.h
+++ b/src/ecmult_gen_impl.h
@@ -51,7 +51,7 @@ static void secp256k1_ecmult_gen_scalar_diff(secp256k1_scalar* diff) {
secp256k1_scalar_add(diff, diff, &neghalf);
}
-static void secp256k1_ecmult_gen(const secp256k1_ecmult_gen_context *ctx, secp256k1_gej *r, const secp256k1_scalar *gn) {
+static void secp256k1_ecmult_gen_gej(const secp256k1_ecmult_gen_context *ctx, secp256k1_gej *r, const secp256k1_scalar *gn) {
uint32_t comb_off;
secp256k1_ge add;
secp256k1_fe neg;
@@ -281,11 +281,19 @@ static void secp256k1_ecmult_gen(const secp256k1_ecmult_gen_context *ctx, secp25
secp256k1_memclear_explicit(&recoded, sizeof(recoded));
}
+SECP256K1_INLINE static void secp256k1_ecmult_gen_ge(const secp256k1_ecmult_gen_context *ctx, secp256k1_ge *r, const secp256k1_scalar *a) {
+ secp256k1_gej rj;
+ secp256k1_ecmult_gen_gej(ctx, &rj, a);
+ secp256k1_ge_set_gej(r, &rj);
+ /* Jacobian coordinates resulting from our multiplication algorithm could potentially leak
+ * information about the secret input scalar, so clear the memory out to be on the safe side. */
+ secp256k1_gej_clear(&rj);
+}
+
/* Setup blinding values for secp256k1_ecmult_gen. */
static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const secp256k1_hash_ctx *hash_ctx, const unsigned char *seed32) {
secp256k1_scalar b;
secp256k1_scalar diff;
- secp256k1_gej gb;
secp256k1_fe f;
unsigned char nonce32[32];
secp256k1_rfc6979_hmac_sha256 rng;
@@ -325,15 +333,13 @@ static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const
* which secp256k1_gej_add_ge cannot handle. */
secp256k1_scalar_cmov(&b, &secp256k1_scalar_one, secp256k1_scalar_is_zero(&b));
secp256k1_rfc6979_hmac_sha256_finalize(&rng);
- secp256k1_ecmult_gen(ctx, &gb, &b);
+ secp256k1_ecmult_gen_ge(ctx, &ctx->ge_offset, &b);
secp256k1_scalar_negate(&b, &b);
secp256k1_scalar_add(&ctx->scalar_offset, &b, &diff);
- secp256k1_ge_set_gej(&ctx->ge_offset, &gb);
/* Clean up. */
secp256k1_memclear_explicit(nonce32, sizeof(nonce32));
secp256k1_scalar_clear(&b);
- secp256k1_gej_clear(&gb);
secp256k1_fe_clear(&f);
secp256k1_rfc6979_hmac_sha256_clear(&rng);
}
diff --git a/src/field_5x52_impl.h b/src/field_5x52_impl.h
index 3a976135..0e0e2d65 100644
--- a/src/field_5x52_impl.h
+++ b/src/field_5x52_impl.h
@@ -338,11 +338,11 @@ SECP256K1_INLINE static void secp256k1_fe_impl_add(secp256k1_fe *r, const secp25
r->n[4] += a->n[4];
}
-SECP256K1_INLINE static void secp256k1_fe_impl_mul(secp256k1_fe *r, const secp256k1_fe *a, const secp256k1_fe * SECP256K1_RESTRICT b) {
+SECP256K1_FORCE_INLINE static void secp256k1_fe_impl_mul(secp256k1_fe *r, const secp256k1_fe *a, const secp256k1_fe * SECP256K1_RESTRICT b) {
secp256k1_fe_mul_inner(r->n, a->n, b->n);
}
-SECP256K1_INLINE static void secp256k1_fe_impl_sqr(secp256k1_fe *r, const secp256k1_fe *a) {
+SECP256K1_FORCE_INLINE static void secp256k1_fe_impl_sqr(secp256k1_fe *r, const secp256k1_fe *a) {
secp256k1_fe_sqr_inner(r->n, a->n);
}
diff --git a/src/field_5x52_int128_impl.h b/src/field_5x52_int128_impl.h
index f23f8ee1..8d1977b2 100644
--- a/src/field_5x52_int128_impl.h
+++ b/src/field_5x52_int128_impl.h
@@ -15,7 +15,7 @@
#define VERIFY_BITS(x, n) VERIFY_CHECK(((x) >> (n)) == 0)
#define VERIFY_BITS_128(x, n) VERIFY_CHECK(secp256k1_u128_check_bits((x), (n)))
-SECP256K1_INLINE static void secp256k1_fe_mul_inner(uint64_t *r, const uint64_t *a, const uint64_t * SECP256K1_RESTRICT b) {
+SECP256K1_FORCE_INLINE static void secp256k1_fe_mul_inner(uint64_t *r, const uint64_t *a, const uint64_t * SECP256K1_RESTRICT b) {
secp256k1_uint128 c, d;
uint64_t t3, t4, tx, u0;
uint64_t a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4];
@@ -151,7 +151,7 @@ SECP256K1_INLINE static void secp256k1_fe_mul_inner(uint64_t *r, const uint64_t
/* [r4 r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */
}
-SECP256K1_INLINE static void secp256k1_fe_sqr_inner(uint64_t *r, const uint64_t *a) {
+SECP256K1_FORCE_INLINE static void secp256k1_fe_sqr_inner(uint64_t *r, const uint64_t *a) {
secp256k1_uint128 c, d;
uint64_t a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4];
uint64_t t3, t4, tx, u0;
diff --git a/src/modules/ecdh/Makefile.am.include b/src/modules/ecdh/Makefile.am.include
index 18660535..81bc6279 100644
--- a/src/modules/ecdh/Makefile.am.include
+++ b/src/modules/ecdh/Makefile.am.include
@@ -1,5 +1,6 @@
include_HEADERS += include/secp256k1_ecdh.h
noinst_HEADERS += src/modules/ecdh/main_impl.h
noinst_HEADERS += src/modules/ecdh/tests_impl.h
+noinst_HEADERS += src/modules/ecdh/tests_exhaustive_impl.h
noinst_HEADERS += src/modules/ecdh/bench_impl.h
noinst_HEADERS += src/wycheproof/ecdh_secp256k1_test.h
diff --git a/src/modules/ecdh/tests_exhaustive_impl.h b/src/modules/ecdh/tests_exhaustive_impl.h
new file mode 100644
index 00000000..2bcd2434
--- /dev/null
+++ b/src/modules/ecdh/tests_exhaustive_impl.h
@@ -0,0 +1,56 @@
+/***********************************************************************
+ * Distributed under the MIT software license, see the accompanying *
+ * file COPYING or https://www.opensource.org/licenses/mit-license.php.*
+ ***********************************************************************/
+
+#ifndef SECP256K1_MODULE_ECDH_TESTS_EXHAUSTIVE_H
+#define SECP256K1_MODULE_ECDH_TESTS_EXHAUSTIVE_H
+
+#include "../../../include/secp256k1_ecdh.h"
+#include "main_impl.h"
+
+static void test_exhaustive_ecdh(const secp256k1_context *ctx, const secp256k1_ge *group) {
+ int i, j;
+ unsigned char seckeys[EXHAUSTIVE_TEST_ORDER - 1][32];
+ secp256k1_pubkey pubkeys[EXHAUSTIVE_TEST_ORDER - 1];
+
+ /* Construct key pairs (32-byte secret key, public key object) for the entire group. */
+ for (i = 1; i < EXHAUSTIVE_TEST_ORDER; i++) {
+ secp256k1_scalar scalar;
+ secp256k1_scalar_set_int(&scalar, i);
+ secp256k1_scalar_get_b32(seckeys[i - 1], &scalar);
+ CHECK(secp256k1_ec_pubkey_create(ctx, &pubkeys[i - 1], seckeys[i - 1]));
+ }
+
+ /* Loop over key combinations. */
+ for (i = 1; i < EXHAUSTIVE_TEST_ORDER; i++) {
+ for (j = 1; j < EXHAUSTIVE_TEST_ORDER; j++) {
+ unsigned char ecdh_result_ij[32];
+ unsigned char ecdh_result_ji[32];
+
+ /* Calculate ECDH(i*G, j) and ECDH(j*G, i) using API function and verify that the results match. */
+ CHECK(secp256k1_ecdh(ctx, ecdh_result_ij, &pubkeys[i - 1], seckeys[j - 1], NULL, NULL));
+ CHECK(secp256k1_ecdh(ctx, ecdh_result_ji, &pubkeys[j - 1], seckeys[i - 1], NULL, NULL));
+ CHECK(secp256k1_memcmp_var(ecdh_result_ij, ecdh_result_ji, 32) == 0);
+
+ /* Recalculate the expected ECDH result manually by invoking the default ECDH hash
+ * function on the precomputed group element (group[i * j]) coordinates, and verify
+ * that it matches the previously calculated public API results. */
+ {
+ secp256k1_ge ecdh_ge_expected = group[(i * j) % EXHAUSTIVE_TEST_ORDER];
+ unsigned char ecdh_result_expected[32];
+ unsigned char x[32];
+ unsigned char y[32];
+
+ secp256k1_fe_normalize_var(&ecdh_ge_expected.x);
+ secp256k1_fe_normalize_var(&ecdh_ge_expected.y);
+ secp256k1_fe_get_b32(x, &ecdh_ge_expected.x);
+ secp256k1_fe_get_b32(y, &ecdh_ge_expected.y);
+ CHECK(secp256k1_ecdh_hash_function_default(ecdh_result_expected, x, y, NULL));
+ CHECK(secp256k1_memcmp_var(ecdh_result_ij, ecdh_result_expected, 32) == 0);
+ }
+ }
+ }
+}
+
+#endif
diff --git a/src/modules/musig/session_impl.h b/src/modules/musig/session_impl.h
index 6a37bfdf..c05801ee 100644
--- a/src/modules/musig/session_impl.h
+++ b/src/modules/musig/session_impl.h
@@ -415,7 +415,7 @@ static int secp256k1_musig_nonce_gen_internal(const secp256k1_context* ctx, secp
/* Compute pubnonce as two gejs */
for (i = 0; i < 2; i++) {
- secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_ptj[i], &k[i]);
+ secp256k1_ecmult_gen_gej(&ctx->ecmult_gen_ctx, &nonce_ptj[i], &k[i]);
secp256k1_scalar_clear(&k[i]);
}
@@ -483,11 +483,9 @@ int secp256k1_musig_nonce_gen_counter(const secp256k1_context* ctx, secp256k1_mu
(void) ret;
#endif
- if (!secp256k1_musig_nonce_gen_internal(ctx, secnonce, pubnonce, buf, seckey, &pubkey, msg32, keyagg_cache, extra_input32)) {
- return 0;
- }
+ ret = secp256k1_musig_nonce_gen_internal(ctx, secnonce, pubnonce, buf, seckey, &pubkey, msg32, keyagg_cache, extra_input32);
secp256k1_memclear_explicit(seckey, sizeof(seckey));
- return 1;
+ return ret;
}
static int secp256k1_musig_sum_pubnonces(const secp256k1_context* ctx, secp256k1_gej *summed_pubnonces, const secp256k1_musig_pubnonce * const* pubnonces, size_t n_pubnonces) {
diff --git a/src/modules/musig/tests_impl.h b/src/modules/musig/tests_impl.h
index cc644916..3a30c233 100644
--- a/src/modules/musig/tests_impl.h
+++ b/src/modules/musig/tests_impl.h
@@ -374,7 +374,7 @@ static void musig_api_tests(void) {
secp256k1_ge aggnonce_pt[2];
secp256k1_musig_aggnonce_load(CTX, aggnonce_pt, &aggnonce);
for (i = 0; i < 2; i++) {
- secp256k1_ge_is_infinity(&aggnonce_pt[i]);
+ CHECK(secp256k1_ge_is_infinity(&aggnonce_pt[i]) == 1);
}
}
CHECK(secp256k1_musig_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 2) == 1);
@@ -862,7 +862,7 @@ static void musig_test_vectors_nonceagg(void) {
}
CHECK(secp256k1_musig_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 2));
CHECK(secp256k1_musig_aggnonce_serialize(CTX, aggnonce66, &aggnonce));
- CHECK(secp256k1_memcmp_var(aggnonce66, c->expected, 33) == 0);
+ CHECK(secp256k1_memcmp_var(aggnonce66, c->expected, sizeof(aggnonce66)) == 0);
}
for (i = 0; i < ARRAY_SIZE(vector->error_case); i++) {
const struct musig_nonce_agg_test_case *c = &vector->error_case[i];
diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h
index 5100557f..efc72165 100644
--- a/src/modules/schnorrsig/main_impl.h
+++ b/src/modules/schnorrsig/main_impl.h
@@ -123,7 +123,6 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
secp256k1_scalar sk;
secp256k1_scalar e;
secp256k1_scalar k;
- secp256k1_gej rj;
secp256k1_ge pk;
secp256k1_ge r;
unsigned char nonce32[32] = { 0 };
@@ -160,8 +159,7 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
ret &= !secp256k1_scalar_is_zero(&k);
secp256k1_scalar_cmov(&k, &secp256k1_scalar_one, !ret);
- secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k);
- secp256k1_ge_set_gej(&r, &rj);
+ secp256k1_ecmult_gen_ge(&ctx->ecmult_gen_ctx, &r, &k);
/* We declassify r to allow using it as a branch point. This is fine
* because r is not a secret. */
@@ -183,7 +181,6 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
secp256k1_scalar_clear(&sk);
secp256k1_memclear_explicit(seckey, sizeof(seckey));
secp256k1_memclear_explicit(nonce32, sizeof(nonce32));
- secp256k1_gej_clear(&rj);
return ret;
}
diff --git a/src/secp256k1.c b/src/secp256k1.c
index e4b80fff..b216872e 100644
--- a/src/secp256k1.c
+++ b/src/secp256k1.c
@@ -624,15 +624,12 @@ int secp256k1_ec_seckey_verify(const secp256k1_context* ctx, const unsigned char
}
static int secp256k1_ec_pubkey_create_helper(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, secp256k1_scalar *seckey_scalar, secp256k1_ge *p, const unsigned char *seckey) {
- secp256k1_gej pj;
int ret;
ret = secp256k1_scalar_set_b32_seckey(seckey_scalar, seckey);
secp256k1_scalar_cmov(seckey_scalar, &secp256k1_scalar_one, !ret);
- secp256k1_ecmult_gen(ecmult_gen_ctx, &pj, seckey_scalar);
- secp256k1_ge_set_gej(p, &pj);
- secp256k1_gej_clear(&pj);
+ secp256k1_ecmult_gen_ge(ecmult_gen_ctx, p, seckey_scalar);
return ret;
}
diff --git a/src/tests.c b/src/tests.c
index 862bef61..6c3cd39f 100644
--- a/src/tests.c
+++ b/src/tests.c
@@ -37,6 +37,11 @@
#include "int128_impl.h"
#endif
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic warning "-Wunused-function"
+#endif
+
#define CONDITIONAL_TEST(cnt, nam) if (COUNT < (cnt)) { printf("Skipping %s (iteration count too low)\n", nam); } else
static secp256k1_context *CTX = NULL;
@@ -241,7 +246,6 @@ static void run_proper_context_tests(int use_prealloc) {
void *my_ctx_prealloc = NULL;
unsigned char seed[32] = {0x17};
- secp256k1_gej pubj;
secp256k1_ge pub;
secp256k1_scalar msg, key, nonce;
secp256k1_scalar sigr, sigs;
@@ -329,8 +333,7 @@ static void run_proper_context_tests(int use_prealloc) {
/*** attempt to use them ***/
testutil_random_scalar_order_test(&msg);
testutil_random_scalar_order_test(&key);
- secp256k1_ecmult_gen(&my_ctx->ecmult_gen_ctx, &pubj, &key);
- secp256k1_ge_set_gej(&pub, &pubj);
+ secp256k1_ecmult_gen_ge(&my_ctx->ecmult_gen_ctx, &pub, &key);
/* obtain a working nonce */
do {
@@ -4304,19 +4307,16 @@ static void test_ec_combine(void) {
const secp256k1_pubkey* d[6];
secp256k1_pubkey sd;
secp256k1_pubkey sd2;
- secp256k1_gej Qj;
secp256k1_ge Q;
int i;
for (i = 1; i <= 6; i++) {
secp256k1_scalar s;
testutil_random_scalar_order_test(&s);
secp256k1_scalar_add(&sum, &sum, &s);
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &Qj, &s);
- secp256k1_ge_set_gej(&Q, &Qj);
+ secp256k1_ecmult_gen_ge(&CTX->ecmult_gen_ctx, &Q, &s);
secp256k1_pubkey_save(&data[i - 1], &Q);
d[i - 1] = &data[i - 1];
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &Qj, &sum);
- secp256k1_ge_set_gej(&Q, &Qj);
+ secp256k1_ecmult_gen_ge(&CTX->ecmult_gen_ctx, &Q, &sum);
secp256k1_pubkey_save(&sd, &Q);
CHECK(secp256k1_ec_pubkey_combine(CTX, &sd2, d, i) == 1);
CHECK(secp256k1_memcmp_var(&sd, &sd2, sizeof(sd)) == 0);
@@ -4593,9 +4593,9 @@ static void test_ecmult_target(const secp256k1_scalar* target, int mode) {
/* EC multiplications */
if (mode == 0) {
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &p1j, &n1);
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &p2j, &n2);
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &ptj, target);
+ secp256k1_ecmult_gen_gej(&CTX->ecmult_gen_ctx, &p1j, &n1);
+ secp256k1_ecmult_gen_gej(&CTX->ecmult_gen_ctx, &p2j, &n2);
+ secp256k1_ecmult_gen_gej(&CTX->ecmult_gen_ctx, &ptj, target);
} else if (mode == 1) {
secp256k1_ecmult(&p1j, &pj, &n1, &secp256k1_scalar_zero);
secp256k1_ecmult(&p2j, &pj, &n2, &secp256k1_scalar_zero);
@@ -5162,7 +5162,7 @@ static int test_ecmult_multi_random(secp256k1_scratch *scratch) {
secp256k1_scalar_mul(&scalars[filled], &sc_tmp, &g_scalar);
secp256k1_scalar_inverse_var(&sc_tmp, &sc_tmp);
secp256k1_scalar_negate(&sc_tmp, &sc_tmp);
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &gejs[filled], &sc_tmp);
+ secp256k1_ecmult_gen_gej(&CTX->ecmult_gen_ctx, &gejs[filled], &sc_tmp);
++filled;
++mults;
}
@@ -5642,7 +5642,7 @@ static void test_ecmult_accumulate(secp256k1_sha256* acc, const secp256k1_scalar
size_t i;
secp256k1_gej_set_ge(&gj, &secp256k1_ge_const_g);
secp256k1_gej_set_infinity(&infj);
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &rj[0], x);
+ secp256k1_ecmult_gen_gej(&CTX->ecmult_gen_ctx, &rj[0], x);
secp256k1_ecmult(&rj[1], &gj, x, NULL);
secp256k1_ecmult(&rj[2], &gj, x, &secp256k1_scalar_zero);
secp256k1_ecmult(&rj[3], &infj, &secp256k1_scalar_zero, x);
@@ -5786,6 +5786,25 @@ static void run_ecmult_constants(void) {
}
}
+static void run_ecmult_gen_ge(void) {
+ /* Test that secp256k1_ecmult_gen_ge result matches secp256k1_ecmult_gen_gej with
+ * manual Jacobian-to-affine conversion (secp256k1_ge_set_gej) over random scalars */
+ int i;
+
+ for (i = 0; i < COUNT; i++) {
+ secp256k1_scalar scalar;
+ secp256k1_gej result_gej;
+ secp256k1_ge result_ge, expected_ge;
+
+ testutil_random_scalar_order_test(&scalar);
+ secp256k1_ecmult_gen_gej(&CTX->ecmult_gen_ctx, &result_gej, &scalar);
+ secp256k1_ge_set_gej(&expected_ge, &result_gej);
+ secp256k1_ecmult_gen_ge(&CTX->ecmult_gen_ctx, &result_ge, &scalar);
+
+ CHECK(secp256k1_ge_eq_var(&result_ge, &expected_ge));
+ }
+}
+
static void test_ecmult_gen_blind(void) {
/* Test ecmult_gen() blinding and confirm that the blinding changes, the affine points match, and the z's don't match. */
secp256k1_scalar key;
@@ -5796,13 +5815,13 @@ static void test_ecmult_gen_blind(void) {
secp256k1_ge p;
secp256k1_ge pge;
testutil_random_scalar_order_test(&key);
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &pgej, &key);
+ secp256k1_ecmult_gen_gej(&CTX->ecmult_gen_ctx, &pgej, &key);
testrand256(seed32);
b = CTX->ecmult_gen_ctx.scalar_offset;
p = CTX->ecmult_gen_ctx.ge_offset;
secp256k1_ecmult_gen_blind(&CTX->ecmult_gen_ctx, secp256k1_get_hash_context(CTX), seed32);
CHECK(!secp256k1_scalar_eq(&b, &CTX->ecmult_gen_ctx.scalar_offset));
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &pgej2, &key);
+ secp256k1_ecmult_gen_gej(&CTX->ecmult_gen_ctx, &pgej2, &key);
CHECK(!gej_xyz_equals_gej(&pgej, &pgej2));
CHECK(!secp256k1_ge_eq_var(&p, &CTX->ecmult_gen_ctx.ge_offset));
secp256k1_ge_set_gej(&pge, &pgej);
@@ -5832,7 +5851,7 @@ static void test_ecmult_gen_edge_cases(void) {
for (i = -1; i < 2; ++i) {
/* Run test with gn = i - scalar_offset (so that the ecmult_gen recoded value represents i). */
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &res1, &gn);
+ secp256k1_ecmult_gen_gej(&CTX->ecmult_gen_ctx, &res1, &gn);
secp256k1_ecmult(&res2, NULL, &secp256k1_scalar_zero, &gn);
secp256k1_ecmult_const(&res3, &secp256k1_ge_const_g, &gn);
CHECK(secp256k1_gej_eq_var(&res1, &res2));
@@ -6515,7 +6534,6 @@ static void random_sign(secp256k1_scalar *sigr, secp256k1_scalar *sigs, const se
}
static void test_ecdsa_sign_verify(void) {
- secp256k1_gej pubj;
secp256k1_ge pub;
secp256k1_scalar one;
secp256k1_scalar msg, key;
@@ -6524,8 +6542,7 @@ static void test_ecdsa_sign_verify(void) {
int recid;
testutil_random_scalar_order_test(&msg);
testutil_random_scalar_order_test(&key);
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &pubj, &key);
- secp256k1_ge_set_gej(&pub, &pubj);
+ secp256k1_ecmult_gen_ge(&CTX->ecmult_gen_ctx, &pub, &key);
getrec = testrand_bits(1);
/* The specific way in which this conditional is written sidesteps a potential bug in clang.
See the commit messages of the commit that introduced this comment for details. */
@@ -7284,7 +7301,6 @@ static void run_ecdsa_edge_cases(void) {
/* Test the case where ECDSA recomputes a point that is infinity. */
{
- secp256k1_gej keyj;
secp256k1_ge key;
secp256k1_scalar msg;
secp256k1_scalar sr, ss;
@@ -7292,8 +7308,7 @@ static void run_ecdsa_edge_cases(void) {
secp256k1_scalar_negate(&ss, &ss);
secp256k1_scalar_inverse(&ss, &ss);
secp256k1_scalar_set_int(&sr, 1);
- secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &keyj, &sr);
- secp256k1_ge_set_gej(&key, &keyj);
+ secp256k1_ecmult_gen_ge(&CTX->ecmult_gen_ctx, &key, &sr);
msg = ss;
CHECK(secp256k1_ecdsa_sig_verify(&sr, &ss, &key, &msg) == 0);
}
@@ -7969,6 +7984,7 @@ static const struct tf_test_entry tests_ecmult[] = {
CASE(ecmult_near_split_bound),
CASE(ecmult_chain),
CASE(ecmult_constants),
+ CASE(ecmult_gen_ge),
CASE(ecmult_gen_blind),
CASE(ecmult_const_tests),
CASE(ecmult_multi_tests),
@@ -8080,3 +8096,7 @@ int main(int argc, char **argv) {
if (tf_init(&tf, argc, argv) != 0) return EXIT_FAILURE;
return tf_run(&tf);
}
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
diff --git a/src/tests_exhaustive.c b/src/tests_exhaustive.c
index 68d4bec3..99d7b244 100644
--- a/src/tests_exhaustive.c
+++ b/src/tests_exhaustive.c
@@ -31,6 +31,11 @@
#include "testutil.h"
#include "util.h"
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic warning "-Wunused-function"
+#endif
+
static int count = 2;
static uint32_t num_cores = 1;
@@ -337,6 +342,10 @@ static void test_exhaustive_sign(const secp256k1_context *ctx, const secp256k1_g
*/
}
+#ifdef ENABLE_MODULE_ECDH
+#include "modules/ecdh/tests_exhaustive_impl.h"
+#endif
+
#ifdef ENABLE_MODULE_RECOVERY
#include "modules/recovery/tests_exhaustive_impl.h"
#endif
@@ -417,12 +426,10 @@ int main(int argc, char** argv) {
/* Verify against ecmult_gen */
{
secp256k1_scalar scalar_i;
- secp256k1_gej generatedj;
secp256k1_ge generated;
secp256k1_scalar_set_int(&scalar_i, i);
- secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &generatedj, &scalar_i);
- secp256k1_ge_set_gej(&generated, &generatedj);
+ secp256k1_ecmult_gen_ge(&ctx->ecmult_gen_ctx, &generated, &scalar_i);
CHECK(!secp256k1_ge_is_infinity(&group[i]));
CHECK(secp256k1_ge_eq_var(&group[i], &generated));
@@ -437,6 +444,9 @@ int main(int argc, char** argv) {
test_exhaustive_sign(ctx, group);
test_exhaustive_verify(ctx, group);
+#ifdef ENABLE_MODULE_ECDH
+ test_exhaustive_ecdh(ctx, group);
+#endif
#ifdef ENABLE_MODULE_RECOVERY
test_exhaustive_recovery(ctx, group);
#endif
@@ -462,3 +472,7 @@ int main(int argc, char** argv) {
printf("no problems found\n");
return EXIT_SUCCESS;
}
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
diff --git a/src/unit_test.c b/src/unit_test.c
index a1858a11..2ac709cc 100644
--- a/src/unit_test.c
+++ b/src/unit_test.c
@@ -17,6 +17,11 @@
#include "testrand.h"
#include "tests_common.h"
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic warning "-Wunused-function"
+#endif
+
#define UNUSED(x) (void)(x)
/* Number of times certain tests will run */
@@ -477,3 +482,7 @@ static int tf_run(struct tf_framework* tf) {
return status;
}
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
diff --git a/src/util.h b/src/util.h
index 5d03e4c7..492a5929 100644
--- a/src/util.h
+++ b/src/util.h
@@ -57,6 +57,17 @@ static void print_buf_plain(const unsigned char *buf, size_t len) {
# define SECP256K1_INLINE inline
# endif
+# if !defined(_DEBUG) && !defined(__NO_INLINE__) && !defined(__OPTIMIZE_SIZE__)
+# if defined(__OPTIMIZE__) && (SECP256K1_GNUC_PREREQ(3, 0) || defined(__clang__))
+# define SECP256K1_FORCE_INLINE SECP256K1_INLINE __attribute__((always_inline))
+# elif defined(_MSC_VER)
+# define SECP256K1_FORCE_INLINE __forceinline
+# endif
+# endif
+# ifndef SECP256K1_FORCE_INLINE
+# define SECP256K1_FORCE_INLINE SECP256K1_INLINE
+# endif
+
/** Assert statically that expr is true.
*
* This is a statement-like macro and can only be used inside functions.
Why this scored 21/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.