UltrafastSecp256k1/cpu/tests/test_v4_features.cpp
shrec adba035764
fix: resolve compiler warnings and ASan+UBSan debug assert failures
Compiler warnings fixed:
- point.cpp: change build_table lambda param from int to std::size_t,
  eliminating 'exceeds maximum object size' warning from signed-to-unsigned cast
- test_adversarial_protocol.cpp: remove unused variables sig, addr, addr_len
- test_ecies_regression.cpp: add bounds check before tampered buffer XOR

ASan+UBSan failures fixed (Debug build):
- ecdsa.cpp: move zero-key early return before SECP_ASSERT_SCALAR_VALID so
  ecdsa_sign handles zero private keys gracefully in both Debug and Release
- test_wycheproof_ecdh.cpp: guard off-curve point ECDH test with NDEBUG;
  in Debug builds SECP_ASSERT_ON_CURVE correctly aborts on off-curve input

Verified: 43/43 Release CTest, 34/34 ASan+UBSan CTest (clang-18)
2026-03-14 02:08:48 +00:00

669 lines
24 KiB
C++

// ============================================================================
// Tests: Pedersen, FROST, Adaptor, Address, Silent Payments
// ============================================================================
#include <cstdio>
#include <cstring>
#include <array>
#include <vector>
#include <string>
#include "secp256k1/pedersen.hpp"
#include "secp256k1/frost.hpp"
#include "secp256k1/adaptor.hpp"
#include "secp256k1/address.hpp"
#include "secp256k1/schnorr.hpp"
#include "secp256k1/ecdsa.hpp"
#include "secp256k1/sha256.hpp"
#include "secp256k1/scalar.hpp"
#include "secp256k1/point.hpp"
#include "secp256k1/field.hpp"
using namespace secp256k1;
using fast::Scalar;
using fast::Point;
static int g_pass = 0;
static int g_fail = 0;
#define CHECK(cond, name) do { \
if (cond) { g_pass++; std::printf(" [PASS] %s\n", name); } \
else { g_fail++; std::printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
} while(0)
// ===============================================================================
// Pedersen Commitment Tests
// ===============================================================================
static void test_pedersen_basic() {
std::printf("\n=== Pedersen Commitments ===\n");
// Generator H should be valid non-infinity point
const auto& H = pedersen_generator_H();
CHECK(!H.is_infinity(), "generator_H_not_infinity");
// H should be different from G
auto G_comp = Point::generator().to_compressed();
auto H_comp = H.to_compressed();
CHECK(G_comp != H_comp, "H_differs_from_G");
// Basic commit and verify
Scalar const value = Scalar::from_uint64(42);
Scalar const blinding = Scalar::from_uint64(12345);
auto commitment = pedersen_commit(value, blinding);
CHECK(!commitment.point.is_infinity(), "commitment_not_infinity");
CHECK(commitment.verify(value, blinding), "commit_verify_roundtrip");
// Wrong value should fail
Scalar const wrong_value = Scalar::from_uint64(43);
CHECK(!commitment.verify(wrong_value, blinding), "wrong_value_fails");
// Wrong blinding should fail
Scalar const wrong_blind = Scalar::from_uint64(99999);
CHECK(!commitment.verify(value, wrong_blind), "wrong_blinding_fails");
}
static void test_pedersen_homomorphic() {
std::printf("\n=== Pedersen Homomorphic ===\n");
// C(v1,r1) + C(v2,r2) == C(v1+v2, r1+r2)
Scalar const v1 = Scalar::from_uint64(100);
Scalar const r1 = Scalar::from_uint64(11111);
Scalar const v2 = Scalar::from_uint64(200);
Scalar const r2 = Scalar::from_uint64(22222);
auto c1 = pedersen_commit(v1, r1);
auto c2 = pedersen_commit(v2, r2);
auto c_sum = c1 + c2;
auto c_direct = pedersen_commit(v1 + v2, r1 + r2);
auto sum_comp = c_sum.to_compressed();
auto dir_comp = c_direct.to_compressed();
CHECK(sum_comp == dir_comp, "homomorphic_addition");
}
static void test_pedersen_balance() {
std::printf("\n=== Pedersen Balance ===\n");
// Simulate: 2 inputs (100, 200) -> 2 outputs (150, 150)
Scalar const v_in1 = Scalar::from_uint64(100);
Scalar const r_in1 = Scalar::from_uint64(111);
Scalar const v_in2 = Scalar::from_uint64(200);
Scalar const r_in2 = Scalar::from_uint64(222);
Scalar const v_out1 = Scalar::from_uint64(150);
Scalar const r_out1 = Scalar::from_uint64(333);
Scalar const v_out2 = Scalar::from_uint64(150);
// Compute balancing blinding factor
Scalar blinds_in[] = {r_in1, r_in2};
Scalar blinds_out[] = {r_out1};
Scalar const r_out2 = pedersen_blind_sum(blinds_in, 2, blinds_out, 1);
auto c_in1 = pedersen_commit(v_in1, r_in1);
auto c_in2 = pedersen_commit(v_in2, r_in2);
auto c_out1 = pedersen_commit(v_out1, r_out1);
auto c_out2 = pedersen_commit(v_out2, r_out2);
PedersenCommitment pos[] = {c_in1, c_in2};
PedersenCommitment neg[] = {c_out1, c_out2};
CHECK(pedersen_verify_sum(pos, 2, neg, 2), "balance_proof_valid");
}
static void test_pedersen_switch() {
std::printf("\n=== Pedersen Switch ===\n");
const auto& J = pedersen_generator_J();
CHECK(!J.is_infinity(), "generator_J_not_infinity");
Scalar const value = Scalar::from_uint64(777);
Scalar const blind = Scalar::from_uint64(888);
Scalar const switch_blind = Scalar::from_uint64(999);
auto commitment = pedersen_switch_commit(value, blind, switch_blind);
CHECK(!commitment.point.is_infinity(), "switch_commit_not_infinity");
}
static void test_pedersen_serialization() {
std::printf("\n=== Pedersen Serialization ===\n");
Scalar const v = Scalar::from_uint64(12345);
Scalar const r = Scalar::from_uint64(67890);
auto c = pedersen_commit(v, r);
auto compressed = c.to_compressed();
CHECK(compressed[0] == 0x02 || compressed[0] == 0x03, "compressed_prefix_valid");
CHECK(compressed.size() == 33, "compressed_size_33");
}
// ===============================================================================
// FROST Threshold Signature Tests
// ===============================================================================
static void test_frost_lagrange() {
std::printf("\n=== FROST Lagrange ===\n");
// For 2-of-3, signers {1, 2}: lambda_1 = 2/(2-1) = 2, lambda_2 = 1/(1-2) = -1
std::vector<ParticipantId> const signers = {1, 2};
Scalar const l1 = frost_lagrange_coefficient(1, signers);
Scalar const l2 = frost_lagrange_coefficient(2, signers);
// lambda_1 should be 2
Scalar const expected_l1 = Scalar::from_uint64(2);
CHECK(l1 == expected_l1, "lagrange_l1_equals_2");
// lambda_2 should be -1 (= n-1)
Scalar const expected_l2 = Scalar::from_uint64(1).negate();
CHECK(l2 == expected_l2, "lagrange_l2_equals_neg1");
// lambda_1 * 1 + lambda_2 * 2 should equal the secret (Lagrange property)
// f(0) = lambda_1*f(1) + lambda_2*f(2) where f is degree-1 polynomial
// Let's verify with concrete values: f(x) = 5 + 3x
// f(1) = 8, f(2) = 11
// lambda_1*f(1) + lambda_2*f(2) = 2*8 + (-1)*11 = 16 - 11 = 5 = f(0) [ok]
Scalar const f1 = Scalar::from_uint64(8);
Scalar const f2 = Scalar::from_uint64(11);
Scalar const recovered = l1 * f1 + l2 * f2;
Scalar const expected = Scalar::from_uint64(5);
CHECK(recovered == expected, "lagrange_interpolation");
}
static void test_frost_keygen() {
std::printf("\n=== FROST Key Generation ===\n");
const std::uint32_t T = 2; // threshold
const std::uint32_t N = 3; // participants
// Each participant generates their part
std::array<std::uint8_t, 32> seed1 = {}; seed1[0] = 1;
std::array<std::uint8_t, 32> seed2 = {}; seed2[0] = 2;
std::array<std::uint8_t, 32> seed3 = {}; seed3[0] = 3;
auto [comm1, shares1] = frost_keygen_begin(1, T, N, seed1);
auto [comm2, shares2] = frost_keygen_begin(2, T, N, seed2);
auto [comm3, shares3] = frost_keygen_begin(3, T, N, seed3);
CHECK(comm1.coeffs.size() == T, "participant1_poly_degree");
CHECK(shares1.size() == N, "participant1_share_count");
// Each participant gets shares from others
// Participant 1 gets: shares1[0] (own), shares2[0], shares3[0]
std::vector<FrostCommitment> const all_comms = {comm1, comm2, comm3};
// Build received shares for participant 1: from each participant, get their share for id=1
std::vector<FrostShare> const recv1 = {shares1[0], shares2[0], shares3[0]};
auto [pkg1, ok1] = frost_keygen_finalize(1, all_comms, recv1, T, N);
CHECK(ok1, "participant1_keygen_ok");
std::vector<FrostShare> const recv2 = {shares1[1], shares2[1], shares3[1]};
auto [pkg2, ok2] = frost_keygen_finalize(2, all_comms, recv2, T, N);
CHECK(ok2, "participant2_keygen_ok");
std::vector<FrostShare> const recv3 = {shares1[2], shares2[2], shares3[2]};
auto [pkg3, ok3] = frost_keygen_finalize(3, all_comms, recv3, T, N);
CHECK(ok3, "participant3_keygen_ok");
// All participants should agree on the group public key
auto gpk1 = pkg1.group_public_key.to_compressed();
auto gpk2 = pkg2.group_public_key.to_compressed();
auto gpk3 = pkg3.group_public_key.to_compressed();
CHECK(gpk1 == gpk2, "group_key_1_2_match");
CHECK(gpk2 == gpk3, "group_key_2_3_match");
}
static void test_frost_2of3_signing() {
std::printf("\n=== FROST 2-of-3 Signing ===\n");
const std::uint32_t T = 2, N = 3;
// KeyGen
std::array<std::uint8_t, 32> seed1 = {}; seed1[0] = 0x11;
std::array<std::uint8_t, 32> seed2 = {}; seed2[0] = 0x22;
std::array<std::uint8_t, 32> seed3 = {}; seed3[0] = 0x33;
auto [comm1, shares1] = frost_keygen_begin(1, T, N, seed1);
auto [comm2, shares2] = frost_keygen_begin(2, T, N, seed2);
auto [comm3, shares3] = frost_keygen_begin(3, T, N, seed3);
std::vector<FrostCommitment> const all_comms = {comm1, comm2, comm3};
auto [pkg1, ok1] = frost_keygen_finalize(1, all_comms, {shares1[0], shares2[0], shares3[0]}, T, N);
auto [pkg2, ok2] = frost_keygen_finalize(2, all_comms, {shares1[1], shares2[1], shares3[1]}, T, N);
CHECK(ok1 && ok2, "keygen_2of3_ok");
// Signing (signers 1 and 2)
std::array<std::uint8_t, 32> msg = {};
msg[0] = 0xAB; msg[1] = 0xCD;
std::array<std::uint8_t, 32> nonce_seed1 = {}; nonce_seed1[0] = 0x41;
std::array<std::uint8_t, 32> nonce_seed2 = {}; nonce_seed2[0] = 0x42;
auto [nonce1, ncomm1] = frost_sign_nonce_gen(1, nonce_seed1);
auto [nonce2, ncomm2] = frost_sign_nonce_gen(2, nonce_seed2);
std::vector<FrostNonceCommitment> const nonce_comms = {ncomm1, ncomm2};
auto partial1 = frost_sign(pkg1, nonce1, msg, nonce_comms);
auto partial2 = frost_sign(pkg2, nonce2, msg, nonce_comms);
// Aggregate
std::vector<FrostPartialSig> const partials = {partial1, partial2};
auto sig = frost_aggregate(partials, nonce_comms, pkg1.group_public_key, msg);
// Verify with standard schnorr_verify
auto group_x = pkg1.group_public_key.x().to_bytes();
bool const valid = schnorr_verify(group_x, msg, sig);
CHECK(valid, "frost_2of3_signature_valid");
}
// ===============================================================================
// Adaptor Signature Tests
// ===============================================================================
static void test_schnorr_adaptor_basic() {
std::printf("\n=== Schnorr Adaptor Basic ===\n");
// Secret key
Scalar sk = Scalar::from_uint64(12345678);
Point pk = Point::generator().scalar_mul(sk);
auto pk_x = pk.x().to_bytes();
// Ensure even y for BIP-340
auto pk_y = pk.y().to_bytes();
if (pk_y[31] & 1) {
sk = sk.negate();
pk = pk.negate();
pk_x = pk.x().to_bytes();
}
// Adaptor secret
Scalar const t = Scalar::from_uint64(87654321);
Point const T = Point::generator().scalar_mul(t);
std::array<std::uint8_t, 32> msg = {};
msg[0] = 0xDE; msg[1] = 0xAD;
std::array<std::uint8_t, 32> const aux = {};
// Create adaptor pre-signature
auto pre_sig = schnorr_adaptor_sign(sk, msg, T, aux);
CHECK(!pre_sig.R_hat.is_infinity(), "adaptor_R_hat_not_infinity");
// Verify pre-signature
bool const pre_valid = schnorr_adaptor_verify(pre_sig, pk_x, msg, T);
CHECK(pre_valid, "adaptor_pre_sig_valid");
// Adapt with secret t
auto sig = schnorr_adaptor_adapt(pre_sig, t);
// The adapted signature should be a valid Schnorr signature
bool const sig_valid = schnorr_verify(pk_x, msg, sig);
CHECK(sig_valid, "adapted_sig_valid_schnorr");
// Extract adaptor secret
auto [extracted_t, extract_ok] = schnorr_adaptor_extract(pre_sig, sig);
CHECK(extract_ok, "adaptor_extract_ok");
// t or -t should match
bool const t_matches = (extracted_t == t) || (extracted_t == t.negate());
CHECK(t_matches, "extracted_secret_matches");
}
static void test_ecdsa_adaptor_basic() {
std::printf("\n=== ECDSA Adaptor Basic ===\n");
Scalar const sk = Scalar::from_uint64(999999);
Point const pk = Point::generator().scalar_mul(sk);
Scalar const t = Scalar::from_uint64(777777);
Point const T = Point::generator().scalar_mul(t);
std::array<std::uint8_t, 32> msg = {};
msg[0] = 0xCA; msg[1] = 0xFE;
// Create ECDSA adaptor
auto pre_sig = ecdsa_adaptor_sign(sk, msg, T);
CHECK(!pre_sig.R_hat.is_infinity(), "ecdsa_adaptor_R_hat_valid");
CHECK(!pre_sig.r.is_zero(), "ecdsa_adaptor_r_nonzero");
// Verify adaptor
bool const pre_valid = ecdsa_adaptor_verify(pre_sig, pk, msg, T);
CHECK(pre_valid, "ecdsa_adaptor_verify_ok");
// Adapt
auto sig = ecdsa_adaptor_adapt(pre_sig, t);
CHECK(!sig.r.is_zero() && !sig.s.is_zero(), "adapted_ecdsa_nonzero");
// Extract secret
auto [extracted_t, ok] = ecdsa_adaptor_extract(pre_sig, sig);
CHECK(ok, "ecdsa_extract_ok");
bool const t_match = (extracted_t == t) || (extracted_t == t.negate());
CHECK(t_match, "ecdsa_extracted_secret_matches");
}
// ===============================================================================
// Address Generation Tests
// ===============================================================================
static void test_base58check() {
std::printf("\n=== Base58Check ===\n");
// Known vector: version=0x00, hash160=all zeros -> "1111111111111111111114oLvT2"
std::uint8_t payload[21] = {}; // version 0x00 + 20 zero bytes
auto encoded = base58check_encode(payload, 21);
CHECK(!encoded.empty(), "base58_encode_nonempty");
// cppcheck-suppress containerOutOfBounds
CHECK(encoded[0] == '1', "base58_leading_ones");
// Roundtrip
auto [decoded, valid] = base58check_decode(encoded);
CHECK(valid, "base58_decode_valid");
CHECK(decoded.size() == 21, "base58_decode_size");
bool match = true;
for (std::size_t i = 0; i < 21; ++i) { if (decoded[i] != payload[i]) match = false; }
CHECK(match, "base58_roundtrip");
}
static void test_bech32() {
std::printf("\n=== Bech32/Bech32m ===\n");
// Encode P2WPKH (v0, 20-byte program)
std::uint8_t prog20[20] = {};
prog20[0] = 0x75; prog20[1] = 0x1e;
auto addr = bech32_encode("bc", 0, prog20, 20);
CHECK(!addr.empty(), "bech32_encode_nonempty");
CHECK(addr.substr(0, 3) == "bc1", "bech32_prefix_bc1");
// Decode
auto result = bech32_decode(addr);
CHECK(result.valid, "bech32_decode_valid");
CHECK(result.witness_version == 0, "bech32_witness_v0");
CHECK(result.witness_program.size() == 20, "bech32_prog_20_bytes");
CHECK(result.hrp == "bc", "bech32_hrp_bc");
// Bech32m (P2TR, v1, 32-byte program)
std::uint8_t prog32[32] = {};
prog32[0] = 0xAB;
auto addr_tr = bech32_encode("bc", 1, prog32, 32);
CHECK(addr_tr.substr(0, 4) == "bc1p", "bech32m_prefix_bc1p");
auto result_tr = bech32_decode(addr_tr);
CHECK(result_tr.valid, "bech32m_decode_valid");
CHECK(result_tr.witness_version == 1, "bech32m_witness_v1");
CHECK(result_tr.witness_program.size() == 32, "bech32m_prog_32_bytes");
}
static void test_hash160() {
std::printf("\n=== HASH160 ===\n");
// HASH160 of empty string should be deterministic
auto h1 = hash160(nullptr, 0);
auto h2 = hash160(nullptr, 0);
CHECK(h1 == h2, "hash160_deterministic");
// Non-trivial input
std::uint8_t data[] = {0x01, 0x02, 0x03};
auto h3 = hash160(data, 3);
CHECK(h3 != h1, "hash160_different_for_different_input");
}
static void test_address_p2pkh() {
std::printf("\n=== P2PKH Address ===\n");
Scalar const sk = Scalar::from_uint64(1);
Point const pk = Point::generator().scalar_mul(sk);
auto addr = address_p2pkh(pk, Network::Mainnet);
CHECK(addr[0] == '1', "p2pkh_starts_with_1");
CHECK(addr.size() >= 25 && addr.size() <= 34, "p2pkh_valid_length");
auto addr_test = address_p2pkh(pk, Network::Testnet);
CHECK(addr_test[0] == 'm' || addr_test[0] == 'n', "p2pkh_testnet_prefix");
}
static void test_address_p2wpkh() {
std::printf("\n=== P2WPKH Address ===\n");
Scalar const sk = Scalar::from_uint64(1);
Point const pk = Point::generator().scalar_mul(sk);
auto addr = address_p2wpkh(pk, Network::Mainnet);
CHECK(addr.substr(0, 4) == "bc1q", "p2wpkh_bc1q_prefix");
auto addr_test = address_p2wpkh(pk, Network::Testnet);
CHECK(addr_test.substr(0, 4) == "tb1q", "p2wpkh_testnet_tb1q");
// Roundtrip decode
auto decoded = bech32_decode(addr);
CHECK(decoded.valid, "p2wpkh_decode_valid");
CHECK(decoded.witness_version == 0, "p2wpkh_version_0");
CHECK(decoded.witness_program.size() == 20, "p2wpkh_20_byte_program");
}
static void test_address_p2tr() {
std::printf("\n=== P2TR Address ===\n");
Scalar const sk = Scalar::from_uint64(1);
Point const pk = Point::generator().scalar_mul(sk);
auto addr = address_p2tr(pk, Network::Mainnet);
CHECK(addr.substr(0, 4) == "bc1p", "p2tr_bc1p_prefix");
auto decoded = bech32_decode(addr);
CHECK(decoded.valid, "p2tr_decode_valid");
CHECK(decoded.witness_version == 1, "p2tr_version_1");
CHECK(decoded.witness_program.size() == 32, "p2tr_32_byte_program");
}
static void test_wif() {
std::printf("\n=== WIF Encode/Decode ===\n");
Scalar const sk = Scalar::from_uint64(12345);
// Compressed mainnet
auto wif = wif_encode(sk, true, Network::Mainnet);
CHECK(wif[0] == 'K' || wif[0] == 'L', "wif_compressed_prefix");
auto decoded = wif_decode(wif);
CHECK(decoded.valid, "wif_decode_valid");
CHECK(decoded.compressed, "wif_decode_compressed");
CHECK(decoded.network == Network::Mainnet, "wif_network_mainnet");
CHECK(decoded.key == sk, "wif_key_matches");
// Uncompressed mainnet
auto wif_unc = wif_encode(sk, false, Network::Mainnet);
CHECK(wif_unc[0] == '5', "wif_uncompressed_prefix");
auto dec_unc = wif_decode(wif_unc);
CHECK(dec_unc.valid, "wif_uncompressed_valid");
CHECK(!dec_unc.compressed, "wif_uncompressed_flag");
// Testnet
auto wif_test = wif_encode(sk, true, Network::Testnet);
auto dec_test = wif_decode(wif_test);
CHECK(dec_test.valid && dec_test.network == Network::Testnet, "wif_testnet_roundtrip");
}
// ===============================================================================
// Silent Payments Tests
// ===============================================================================
static void test_silent_payment_basic() {
std::printf("\n=== Silent Payments ===\n");
// Receiver generates address
Scalar const b_scan = Scalar::from_uint64(111);
Scalar const b_spend = Scalar::from_uint64(222);
auto sp_addr = silent_payment_address(b_scan, b_spend);
CHECK(!sp_addr.scan_pubkey.is_infinity(), "sp_scan_key_valid");
CHECK(!sp_addr.spend_pubkey.is_infinity(), "sp_spend_key_valid");
// Encode address
auto encoded = sp_addr.encode(Network::Mainnet);
CHECK(!encoded.empty(), "sp_address_encoded");
CHECK(encoded.substr(0, 2) == "sp", "sp_address_prefix");
}
static void test_silent_payment_flow() {
std::printf("\n=== Silent Payment Flow ===\n");
// Receiver keys
Scalar const b_scan = Scalar::from_uint64(0xBEEF);
Scalar const b_spend = Scalar::from_uint64(0xCAFE);
auto sp_addr = silent_payment_address(b_scan, b_spend);
// Sender creates output
Scalar const a1 = Scalar::from_uint64(0xDEAD); // sender's input key
std::vector<Scalar> const input_keys = {a1};
auto [output_pk, tweak] = silent_payment_create_output(input_keys, sp_addr, 0);
CHECK(!output_pk.is_infinity(), "sp_output_key_valid");
CHECK(!tweak.is_zero(), "sp_tweak_nonzero");
// Receiver scans and detects
auto A1 = Point::generator().scalar_mul(a1);
std::vector<Point> const input_pks = {A1};
auto output_x = output_pk.x().to_bytes();
std::vector<std::array<std::uint8_t, 32>> const outputs = {output_x};
auto detected = silent_payment_scan(b_scan, b_spend, input_pks, outputs);
CHECK(detected.size() == 1, "sp_detected_one_output");
if (!detected.empty()) {
CHECK(detected[0].first == 0, "sp_detected_index_0");
// Verify the spending key works
Scalar const d = detected[0].second;
Point const derived_pk = Point::generator().scalar_mul(d);
auto derived_x = derived_pk.x().to_bytes();
CHECK(derived_x == output_x, "sp_derived_key_matches_output");
}
}
static void test_silent_payment_multiple_outputs() {
std::printf("\n=== Silent Payment Multiple Outputs ===\n");
Scalar const b_scan = Scalar::from_uint64(0x1111);
Scalar const b_spend = Scalar::from_uint64(0x2222);
auto sp_addr = silent_payment_address(b_scan, b_spend);
Scalar const a1 = Scalar::from_uint64(0x3333);
std::vector<Scalar> const input_keys = {a1};
// Create 3 outputs to same recipient
std::vector<std::array<std::uint8_t, 32>> outputs;
for (std::uint32_t k = 0; k < 3; ++k) {
auto [pk, tw] = silent_payment_create_output(input_keys, sp_addr, k);
(void)tw;
outputs.push_back(pk.x().to_bytes());
}
// Scan
auto A1 = Point::generator().scalar_mul(a1);
auto detected = silent_payment_scan(b_scan, b_spend, {A1}, outputs);
CHECK(detected.size() == 3, "sp_detected_three_outputs");
}
// ===============================================================================
// Edge Cases
// ===============================================================================
static void test_pedersen_zero_value() {
std::printf("\n=== Edge: Zero Value Commitment ===\n");
Scalar const zero = Scalar::zero();
Scalar const blind = Scalar::from_uint64(42);
auto c = pedersen_commit(zero, blind);
// Should equal blind*G (since v=0, v*H = O)
Point const expected = Point::generator().scalar_mul(blind);
auto c_comp = c.to_compressed();
auto e_comp = expected.to_compressed();
CHECK(c_comp == e_comp, "zero_value_equals_blind_times_G");
}
static void test_adaptor_zero_adaptor() {
std::printf("\n=== Edge: Identity Adaptor ===\n");
Scalar const sk = Scalar::from_uint64(55555);
Scalar const t = Scalar::from_uint64(1); // minimal adaptor
Point const T = Point::generator().scalar_mul(t);
std::array<std::uint8_t, 32> msg = {}; msg[0] = 0xFF;
std::array<std::uint8_t, 32> const aux = {};
auto pre = schnorr_adaptor_sign(sk, msg, T, aux);
CHECK(!pre.R_hat.is_infinity(), "identity_adaptor_valid");
}
static void test_address_consistency() {
std::printf("\n=== Address Consistency ===\n");
// Same key should always produce same addresses
Scalar const sk = Scalar::from_uint64(7);
Point const pk = Point::generator().scalar_mul(sk);
auto a1 = address_p2pkh(pk);
auto a2 = address_p2pkh(pk);
CHECK(a1 == a2, "p2pkh_deterministic");
auto w1 = address_p2wpkh(pk);
auto w2 = address_p2wpkh(pk);
CHECK(w1 == w2, "p2wpkh_deterministic");
auto t1 = address_p2tr(pk);
auto t2 = address_p2tr(pk);
CHECK(t1 == t2, "p2tr_deterministic");
// Different keys -> different addresses
Scalar const sk2 = Scalar::from_uint64(8);
Point const pk2 = Point::generator().scalar_mul(sk2);
CHECK(address_p2pkh(pk) != address_p2pkh(pk2), "different_keys_different_p2pkh");
CHECK(address_p2wpkh(pk) != address_p2wpkh(pk2), "different_keys_different_p2wpkh");
}
// ===============================================================================
// Main
// ===============================================================================
int test_v4_features_run() {
std::printf("===========================================\n");
std::printf(" v4.0.0 Feature Tests\n");
std::printf(" Pedersen | FROST | Adaptor | Address | SP\n");
std::printf("===========================================\n");
// Pedersen
test_pedersen_basic();
test_pedersen_homomorphic();
test_pedersen_balance();
test_pedersen_switch();
test_pedersen_serialization();
test_pedersen_zero_value();
// FROST
test_frost_lagrange();
test_frost_keygen();
test_frost_2of3_signing();
// Adaptor
test_schnorr_adaptor_basic();
test_ecdsa_adaptor_basic();
test_adaptor_zero_adaptor();
// Address
test_base58check();
test_bech32();
test_hash160();
test_address_p2pkh();
test_address_p2wpkh();
test_address_p2tr();
test_wif();
test_address_consistency();
// Silent Payments
test_silent_payment_basic();
test_silent_payment_flow();
test_silent_payment_multiple_outputs();
std::printf("\n===========================================\n");
std::printf(" Results: %d passed, %d failed\n", g_pass, g_fail);
std::printf("===========================================\n");
return g_fail > 0 ? 1 : 0;
}