UltrafastSecp256k1/cpu/tests/test_ecies.cpp
shrec d64cd2b8e2
Horizontal audit: benchmarks + tests for Adaptor/FROST/MuSig2/ECIES/SHA/FFI
bench_unified: add sections 8.6-8.10
  - Adaptor Signatures (Schnorr + ECDSA)
  - FROST 2-of-3 threshold (keygen/sign/verify/agg)
  - MuSig2 2-of-2 (key_agg/nonce/partial_sign/sig_agg)
  - ECIES encrypt/decrypt (256B payload)
  - Message Signing, SHA-256/512, Multi-scalar mul (4pt/64pt)
  - Throughput summary entries for all new sections

test_ecies.cpp: 7 tests (roundtrip, variable lengths, wrong key, tamper, nondeterminism)
test_sha.cpp: 9 tests with NIST FIPS 180-4 vectors (SHA-256/512)
test_ffi_coverage.cpp: 34 C ABI tests (ctx_size, Pedersen, ZK range, AEAD, ElligatorSwift, BIP-324)

Bug fixes:
  - ufsecp.h: UFSECP_ZK_RANGE_PROOF_MAX_LEN 675 -> 688 (was too small for actual proof)
  - ufsecp_impl.cpp: fix undefined UFSECP_ERROR -> UFSECP_ERR_INTERNAL,
    UFSECP_ERR_BUFFER_TOO_SMALL -> UFSECP_ERR_BUF_TOO_SMALL in BIP-324 functions

All 28/28 selftest modules pass. All benchmarks produce valid output.
2026-03-22 11:53:04 +00:00

218 lines
7.9 KiB
C++

// ============================================================================
// Test: ECIES Encryption / Decryption
// ============================================================================
// Validates ECIES encrypt → decrypt roundtrip, tamper detection, wrong-key
// rejection, empty plaintext, and variable-length payloads.
// ============================================================================
#include "secp256k1/ecies.hpp"
#include "secp256k1/scalar.hpp"
#include "secp256k1/point.hpp"
#include <cstdio>
#include <cstring>
#include <vector>
#include <array>
using namespace secp256k1;
using fast::Scalar;
using fast::Point;
static int tests_run = 0;
static int tests_passed = 0;
#define CHECK(cond, msg) do { \
++tests_run; \
if (cond) { ++tests_passed; } \
else { std::printf(" [FAIL] %s\n", msg); } \
} while(0)
// ============================================================================
// Test helpers
// ============================================================================
static Scalar make_key(std::uint64_t seed) {
std::array<std::uint8_t, 32> buf{};
for (int i = 0; i < 8; ++i)
buf[static_cast<std::size_t>(24 + i)] = static_cast<std::uint8_t>((seed >> (8 * (7 - i))) & 0xFF);
buf[0] = 0x01; // ensure non-zero
return Scalar::from_bytes(buf);
}
// ============================================================================
// Roundtrip: encrypt then decrypt recovers plaintext
// ============================================================================
static void test_roundtrip_basic() {
std::printf("\n=== ECIES: Basic roundtrip ===\n");
auto sk = make_key(0xEC1E5001ULL);
auto pk = Point::generator().scalar_mul(sk);
const std::uint8_t pt[] = "Hello, ECIES!";
auto ct = ecies_encrypt(pk, pt, sizeof(pt) - 1);
CHECK(!ct.empty(), "encrypt produces output");
auto recovered = ecies_decrypt(sk, ct.data(), ct.size());
CHECK(!recovered.empty(), "decrypt succeeds");
CHECK(recovered.size() == sizeof(pt) - 1, "plaintext length matches");
CHECK(std::memcmp(recovered.data(), pt, sizeof(pt) - 1) == 0,
"plaintext content matches");
}
// ============================================================================
// Variable-length payloads: 1B, 256B, 4KB
// ============================================================================
static void test_variable_lengths() {
std::printf("\n=== ECIES: Variable payload lengths ===\n");
auto sk = make_key(0xEC1E5002ULL);
auto pk = Point::generator().scalar_mul(sk);
for (std::size_t len : {std::size_t{1}, std::size_t{256}, std::size_t{4096}}) {
std::vector<std::uint8_t> pt(len, static_cast<std::uint8_t>(len & 0xFF));
auto ct = ecies_encrypt(pk, pt.data(), pt.size());
CHECK(!ct.empty(), "encrypt non-empty");
CHECK(ct.size() > pt.size(), "ciphertext larger than plaintext");
auto recovered = ecies_decrypt(sk, ct.data(), ct.size());
char label[64];
std::snprintf(label, sizeof(label), "roundtrip %zu bytes", len);
CHECK(recovered.size() == len && std::memcmp(recovered.data(), pt.data(), len) == 0,
label);
}
}
// ============================================================================
// Empty plaintext
// ============================================================================
static void test_empty_plaintext() {
std::printf("\n=== ECIES: Empty plaintext ===\n");
auto sk = make_key(0xEC1E5003ULL);
auto pk = Point::generator().scalar_mul(sk);
auto ct = ecies_encrypt(pk, nullptr, 0);
// Implementations may either produce an empty envelope or a valid one
if (!ct.empty()) {
auto recovered = ecies_decrypt(sk, ct.data(), ct.size());
CHECK(recovered.empty(), "empty plaintext → empty recovery");
} else {
CHECK(true, "encrypt(empty) returns empty (implementation choice)");
}
}
// ============================================================================
// Wrong key: decryption must fail
// ============================================================================
static void test_wrong_key() {
std::printf("\n=== ECIES: Wrong key rejection ===\n");
auto sk1 = make_key(0xEC1E5010ULL);
auto pk1 = Point::generator().scalar_mul(sk1);
auto sk2 = make_key(0xEC1E5020ULL); // different key
const std::uint8_t pt[] = "secret data";
auto ct = ecies_encrypt(pk1, pt, sizeof(pt) - 1);
CHECK(!ct.empty(), "encrypt ok");
auto bad = ecies_decrypt(sk2, ct.data(), ct.size());
CHECK(bad.empty(), "wrong key → empty result (decryption fails)");
}
// ============================================================================
// Tamper detection: flipping a ciphertext byte must fail decryption
// ============================================================================
static void test_tamper_detection() {
std::printf("\n=== ECIES: Tamper detection ===\n");
auto sk = make_key(0xEC1E5030ULL);
auto pk = Point::generator().scalar_mul(sk);
const std::uint8_t pt[] = "integrity check payload";
auto ct = ecies_encrypt(pk, pt, sizeof(pt) - 1);
CHECK(!ct.empty(), "encrypt ok");
// Flip a byte in the middle of the ciphertext
auto tampered = ct;
tampered[tampered.size() / 2] ^= 0xFF;
auto bad = ecies_decrypt(sk, tampered.data(), tampered.size());
CHECK(bad.empty(), "tampered ciphertext → decryption fails");
}
// ============================================================================
// Truncated envelope: short data must fail
// ============================================================================
static void test_truncated() {
std::printf("\n=== ECIES: Truncated envelope ===\n");
auto sk = make_key(0xEC1E5040ULL);
auto pk = Point::generator().scalar_mul(sk);
const std::uint8_t pt[] = "truncation test";
auto ct = ecies_encrypt(pk, pt, sizeof(pt) - 1);
CHECK(!ct.empty(), "encrypt ok");
// Try decrypting with only half the envelope
auto half = ecies_decrypt(sk, ct.data(), ct.size() / 2);
CHECK(half.empty(), "truncated envelope → decryption fails");
// Minimum overhead is 33 (pubkey) + 16 (IV) + 32 (HMAC) = 81 for CBC mode
auto tiny = ecies_decrypt(sk, ct.data(), 10);
CHECK(tiny.empty(), "tiny envelope → decryption fails");
}
// ============================================================================
// Determinism: two encryptions of same plaintext produce different ciphertext
// (due to ephemeral key)
// ============================================================================
static void test_nondeterminism() {
std::printf("\n=== ECIES: Non-deterministic encryption ===\n");
auto sk = make_key(0xEC1E5050ULL);
auto pk = Point::generator().scalar_mul(sk);
const std::uint8_t pt[] = "same message twice";
auto ct1 = ecies_encrypt(pk, pt, sizeof(pt) - 1);
auto ct2 = ecies_encrypt(pk, pt, sizeof(pt) - 1);
CHECK(!ct1.empty() && !ct2.empty(), "both encryptions succeed");
// Ciphertext must differ (ephemeral key randomness)
CHECK(ct1 != ct2, "two encryptions of same plaintext differ");
// But both decrypt to the same plaintext
auto pt1 = ecies_decrypt(sk, ct1.data(), ct1.size());
auto pt2 = ecies_decrypt(sk, ct2.data(), ct2.size());
CHECK(pt1 == pt2, "both decrypt to same plaintext");
}
// ============================================================================
// Entry point
// ============================================================================
int test_ecies_run() {
std::printf("\n========== ECIES Encryption Tests ==========\n");
test_roundtrip_basic();
test_variable_lengths();
test_empty_plaintext();
test_wrong_key();
test_tamper_detection();
test_truncated();
test_nondeterminism();
std::printf("\n ECIES: %d/%d passed\n", tests_passed, tests_run);
return tests_run - tests_passed;
}
#ifdef STANDALONE_TEST
int main() {
return test_ecies_run();
}
#endif