What changed, and why it matters
This commit only adds new test code. It extends the project's constant-time test suite to also exercise the silent payments module, ensuring that secret key material is not accidentally leaked through timing side channels during those operations. There is no change to the actual cryptographic library code that users rely on.
No action required. Treat as routine test improvement. If reviewing, verify the new test cases correctly cover the silent payments API surface and that CI passes with the module enabled.
Security signals we found
Adds constant-time (side-channel) test coverage for the silent payments module
Uses memory-secret marking macros (SECP256K1_CHECKMEM_UNDEFINE/DEFINE) to detect secret-dependent branches
No functional or cryptographic code changes
Evidence from the diff
The diff adds a new test block in src/ctime_tests.c guarded by ENABLE_MODULE_SILENTPAYMENTS. It sets up silent payments sender/recipient objects and calls secp256k1_silentpayments_sender_create_outputs, secp256k1_silentpayments_recipient_label_create, and secp256k1_silentpayments_recipient_scan_outputs while using SECP256K1_CHECKMEM_UNDEFINE/DEFINE macros to verify that branches do not depend on secret bytes. No production source files are modified.
Changed components
src/ctime_tests.cInspect captured patch +78 / −0
diff --git a/src/ctime_tests.c b/src/ctime_tests.c
index 8a885ca..0d201d2 100644
--- a/src/ctime_tests.c
+++ b/src/ctime_tests.c
@@ -40,6 +40,10 @@
#include "../include/secp256k1_ellswift.h"
#endif
+#ifdef ENABLE_MODULE_SILENTPAYMENTS
+#include "../include/secp256k1_silentpayments.h"
+#endif
+
#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic warning "-Wunused-function"
@@ -99,6 +103,27 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
unsigned char ellswift[64];
static const unsigned char prefix[64] = {'t', 'e', 's', 't'};
#endif
+#ifdef ENABLE_MODULE_SILENTPAYMENTS
+ secp256k1_xonly_pubkey generated_output;
+ secp256k1_xonly_pubkey *generated_outputs[1];
+ secp256k1_silentpayments_recipient recipient;
+ secp256k1_silentpayments_label label;
+ const secp256k1_silentpayments_recipient *recipients[1];
+ unsigned char outpoint_smallest[36] = { 0 };
+ secp256k1_keypair sp_keypair;
+ const secp256k1_keypair *sp_keypairs[1];
+ const unsigned char *sp_seckeys[1];
+ secp256k1_silentpayments_found_output found_outputs[1];
+ secp256k1_silentpayments_found_output *found_outputs_ptrs[1];
+ uint32_t n_found_outputs;
+ const secp256k1_xonly_pubkey *tx_outputs[1];
+ secp256k1_silentpayments_prevouts_summary prevouts_summary;
+ unsigned char label_tweak[32] = { 0 };
+ secp256k1_xonly_pubkey sp_xonly_pubkey;
+ const secp256k1_xonly_pubkey *sp_xonly_pubkeys[1];
+ secp256k1_pubkey sp_pubkey;
+ const secp256k1_pubkey *sp_pubkeys[1];
+#endif
for (i = 0; i < 32; i++) {
msg[i] = i + 1;
@@ -268,6 +293,59 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
CHECK(ret == 1);
}
+#endif
+
+#ifdef ENABLE_MODULE_SILENTPAYMENTS
+ SECP256K1_CHECKMEM_DEFINE(key, 32);
+
+ generated_outputs[0] = &generated_output;
+
+ /* Initialize recipient */
+ CHECK(secp256k1_ec_pubkey_create(ctx, &recipient.scan_pubkey, key));
+ key[31] ^= 1;
+ CHECK(secp256k1_ec_pubkey_create(ctx, &recipient.spend_pubkey, key));
+ key[31] ^= (1 << 1);
+ recipient.index = 0;
+ recipients[0] = &recipient;
+
+ /* Set up secret keys */
+ SECP256K1_CHECKMEM_UNDEFINE(key, 32);
+ ret = secp256k1_keypair_create(ctx, &sp_keypair, key);
+ SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
+ CHECK(ret);
+ key[31] ^= (1 << 2);
+ sp_keypairs[0] = &sp_keypair;
+ sp_seckeys[0] = key;
+
+ ret = secp256k1_silentpayments_sender_create_outputs(ctx, generated_outputs, recipients, 1, outpoint_smallest, sp_keypairs, 1, sp_seckeys, 1);
+ CHECK(ret == 1);
+
+ ret = secp256k1_silentpayments_recipient_label_create(ctx, &label, label_tweak, key, 0);
+ key[31] ^= (1 << 3);
+ SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
+ CHECK(ret == 1);
+
+ CHECK(secp256k1_keypair_xonly_pub(ctx, &sp_xonly_pubkey, NULL, &sp_keypair));
+ SECP256K1_CHECKMEM_DEFINE(&sp_xonly_pubkey, sizeof(sp_xonly_pubkey));
+ sp_xonly_pubkeys[0] = &sp_xonly_pubkey;
+ ret = secp256k1_ec_pubkey_create(ctx, &sp_pubkey, sp_seckeys[0]);
+ SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
+ CHECK(ret == 1);
+ SECP256K1_CHECKMEM_DEFINE(&sp_pubkey, sizeof(sp_pubkey));
+ sp_pubkeys[0] = &sp_pubkey;
+
+ ret = secp256k1_silentpayments_recipient_prevouts_summary_create(ctx, &prevouts_summary, outpoint_smallest, sp_xonly_pubkeys, 1, sp_pubkeys, 1);
+ CHECK(ret == 1);
+
+ tx_outputs[0] = generated_outputs[0];
+ found_outputs_ptrs[0] = &found_outputs[0];
+ n_found_outputs = 1;
+ SECP256K1_CHECKMEM_DEFINE(&recipient.spend_pubkey, sizeof(recipient.spend_pubkey));
+ /* It is sufficient to check _recipient_scan_outputs without a label lookup function, since the shared secret is created once (which is where the constant timeness matters)
+ * and then reused for the rest of the scanning logic.
+ */
+ CHECK(secp256k1_silentpayments_recipient_scan_outputs(ctx, found_outputs_ptrs, &n_found_outputs, tx_outputs, 1, key, &prevouts_summary, &recipient.spend_pubkey, NULL, NULL));
+
#endif
}
Why this scored 12/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.