// ============================================================================
// test/test_ota_signed/test_ota_signed.cpp
//
// Unity tests for src/ota_signed.{h,cpp}. This file had zero native test
// coverage before (only reachable indirectly via ota_adapter). Added
// 2026-07-30 alongside ed25519_keypair_from_seed()/ed25519_sign()/
// x25519_scalarmult()/x25519_keypair_from_seed() -- new primitives written
// for reticulum_identity.{h,cpp}'s real RNS node identity.
//
// All Ed25519/X25519 test vectors below were generated with the REAL
// `cryptography` Python library (pip install cryptography), NOT hand
// computed or guessed -- e.g.:
//
//   from cryptography.hazmat.primitives.asymmetric import ed25519
//   sk = ed25519.Ed25519PrivateKey.from_private_bytes(seed)
//   sig = sk.sign(msg)
//
// This is the same rigor as this session's other from-scratch crypto (the
// Mythos gateway fix, CRC/HMAC ports) -- verify against a real independent
// implementation, don't trust self-consistency alone. If any of these
// fail, the bug is in ota_signed.cpp, not the test.
//
// Run with:
//   pio test -e native -f test_ota_signed
// ============================================================================

#include <unity.h>
#include <cstring>
#include "../../src/ota_signed.cpp"

void setUp(void) {}
void tearDown(void) {}

static void hex2bin(const char* hex, uint8_t* out, size_t out_len) {
    for (size_t i = 0; i < out_len; i++) {
        auto nib = [](char c) -> uint8_t {
            if (c >= '0' && c <= '9') return (uint8_t)(c - '0');
            return (uint8_t)(c - 'a' + 10);
        };
        out[i] = (uint8_t)((nib(hex[2*i]) << 4) | nib(hex[2*i + 1]));
    }
}

// ---------------------------------------------------------------------------
// Ed25519 keypair + sign -- vectors generated via cryptography.hazmat's
// Ed25519PrivateKey.from_private_bytes(seed) / .sign(msg)
// ---------------------------------------------------------------------------

void test_ed25519_keypair_vector1(void) {
    uint8_t seed[32], expected_pk[32], pk[32], sk[64];
    hex2bin("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", seed, 32);
    hex2bin("03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8", expected_pk, 32);

    ed25519_keypair_from_seed(seed, pk, sk);

    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_pk, pk, 32);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(seed, sk, 32);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(pk, sk + 32, 32);
}

void test_ed25519_sign_vector1(void) {
    uint8_t seed[32], pk[32], sk[64];
    hex2bin("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", seed, 32);
    ed25519_keypair_from_seed(seed, pk, sk);

    const char* msg = "Reticulum announce test vector";
    uint8_t sig[64];
    ed25519_sign(sk, (const uint8_t*)msg, strlen(msg), sig);

    uint8_t expected_sig[64];
    hex2bin("ddecf35ced62d54921b7dd31ee3ef744cdf8a5dc54004b091968d30212d3ab0c75ff13e27386b86ddb5ae5f1f3341be9c36afd9efce9d5013d5f1be5d4aeb909", expected_sig, 64);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_sig, sig, 64);
}

void test_ed25519_sign_vector2_empty_message(void) {
    uint8_t seed[32], pk[32], sk[64];
    for (int i = 0; i < 32; i++) seed[i] = 0xAB;
    ed25519_keypair_from_seed(seed, pk, sk);

    uint8_t expected_pk[32];
    hex2bin("248acbdbaf9e050196de704bea2d68770e519150d103b587dae2d9cad53dd930", expected_pk, 32);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_pk, pk, 32);

    uint8_t sig[64];
    ed25519_sign(sk, (const uint8_t*)"", 0, sig);

    uint8_t expected_sig[64];
    hex2bin("292f860f7732ee7613ca2a84a57a6fa1e2e0301eaa00f7baff1a866dc7fe3eb73a1b2203322f6b134e58a60fd44542a61f283181399f08f4b582eca4719f1104", expected_sig, 64);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_sig, sig, 64);
}

void test_ed25519_sign_vector3_100_byte_message(void) {
    uint8_t seed[32], pk[32], sk[64];
    for (int i = 0; i < 16; i++) seed[i] = 0x11;
    for (int i = 16; i < 32; i++) seed[i] = 0x22;
    ed25519_keypair_from_seed(seed, pk, sk);

    uint8_t msg[100];
    for (int i = 0; i < 100; i++) msg[i] = (uint8_t)i;

    uint8_t sig[64];
    ed25519_sign(sk, msg, 100, sig);

    uint8_t expected_sig[64];
    hex2bin("285071641f33fd099dad8f00c716d0a4ffebfef9e3c15f6cb03602e69688dbdee39dff5d357a71a47d1fdea554381119e6e1bc3f4e53ab54e2d6eaeda61ae704", expected_sig, 64);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_sig, sig, 64);
}

// Round-trip: a signature this module produces must also verify under this
// module's OWN (previously existing, separately-authored) verify path --
// catches the case where sign and verify silently disagree about
// conventions (e.g. cofactor handling) even if each matches its own
// external reference in isolation.
void test_ed25519_sign_then_verify_round_trip(void) {
    uint8_t seed[32], pk[32], sk[64];
    for (int i = 0; i < 32; i++) seed[i] = (uint8_t)(i * 7 + 3);
    ed25519_keypair_from_seed(seed, pk, sk);

    const char* msg = "round trip check";
    uint8_t sig[64];
    ed25519_sign(sk, (const uint8_t*)msg, strlen(msg), sig);

    TEST_ASSERT_EQUAL_INT(1, ed25519_verify_impl(sig, (const uint8_t*)msg, strlen(msg), pk));

    // Tampered message must fail.
    TEST_ASSERT_EQUAL_INT(0, ed25519_verify_impl(sig, (const uint8_t*)"round trip CHECK", 17, pk));
}

// ---------------------------------------------------------------------------
// X25519 -- vectors generated via cryptography.hazmat's
// X25519PrivateKey.from_private_bytes(priv).public_key()
// ---------------------------------------------------------------------------

void test_x25519_keypair_vector1(void) {
    uint8_t priv[32], pk[32], expected[32];
    for (int i = 0; i < 32; i++) priv[i] = 0x01;
    hex2bin("a4e09292b651c278b9772c569f5fa9bb13d906b46ab68c9df9dc2b4409f8a209", expected, 32);

    x25519_keypair_from_seed(priv, pk);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, pk, 32);
}

void test_x25519_keypair_vector2(void) {
    uint8_t priv[32], pk[32], expected[32];
    for (int i = 0; i < 32; i++) priv[i] = 0x42;
    hex2bin("132c442be010fbd57e72603328aa76e71fccc1503aae219327d14d9c9993f472", expected, 32);

    x25519_keypair_from_seed(priv, pk);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, pk, 32);
}

void test_x25519_keypair_vector3(void) {
    uint8_t priv[32], pk[32], expected[32];
    for (int i = 0; i < 32; i++) priv[i] = (uint8_t)i;
    hex2bin("8f40c5adb68f25624ae5b214ea767a6ec94d829d3d7b5e1ad1ba6f3e2138285f", expected, 32);

    x25519_keypair_from_seed(priv, pk);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, pk, 32);
}

// ECDH cross-check: two independently-derived shared secrets (A's priv x
// B's pub, and B's priv x A's pub) must be identical, matching real X25519
// semantics -- and matching the real library's own cross-check.
void test_x25519_ecdh_shared_secret_matches_both_directions(void) {
    uint8_t priv_a[32], priv_b[32], pub_a[32], pub_b[32];
    for (int i = 0; i < 32; i++) priv_a[i] = 0x01;
    for (int i = 0; i < 32; i++) priv_b[i] = 0x42;
    x25519_keypair_from_seed(priv_a, pub_a);
    x25519_keypair_from_seed(priv_b, pub_b);

    uint8_t shared_ab[32], shared_ba[32];
    x25519_scalarmult(priv_a, pub_b, shared_ab);
    x25519_scalarmult(priv_b, pub_a, shared_ba);

    TEST_ASSERT_EQUAL_UINT8_ARRAY(shared_ab, shared_ba, 32);

    uint8_t expected[32];
    hex2bin("b03f79fe8c437f34eb8544eb2fa5b3f905f91cf385fcf739d2b29a1e79247032", expected, 32);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, shared_ab, 32);
}

// ---------------------------------------------------------------------------
// sc_reduce -- scalar mod-L reduction. Vectors generated via plain Python
// arbitrary-precision `% L` arithmetic (no crypto library needed, just
// int.to_bytes/from_bytes) -- pure ground truth for what "reduce mod L"
// means, independent of any Ed25519-specific implementation. This function
// used to be a ref10-style radix-2^21 limb-packed reduction that mismatched
// this exact ground truth on 10 of 20 random 512-bit inputs (50% failure
// rate) -- a real, previously-undetected bug in production OTA chunk
// signature verification, since ed25519_verify_impl() depends on this
// function. Replaced with a directly-verifiable shift-compare-subtract
// binary long division; these vectors are permanent regression coverage
// for that fix.
// ---------------------------------------------------------------------------

void test_sc_reduce_zero(void) {
    u8 in[64]; hex2bin("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", in, 64);
    u8 expected[32]; hex2bin("0000000000000000000000000000000000000000000000000000000000000000", expected, 32);
    sc_reduce(in);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, in, 32);
}

void test_sc_reduce_just_under_L(void) {
    u8 in[64]; hex2bin("ecd3f55c1a631258d69cf7a2def9de14000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000", in, 64);
    u8 expected[32]; hex2bin("ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", expected, 32);
    sc_reduce(in);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, in, 32);
}

void test_sc_reduce_exactly_L(void) {
    u8 in[64]; hex2bin("edd3f55c1a631258d69cf7a2def9de14000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000", in, 64);
    u8 expected[32]; hex2bin("0000000000000000000000000000000000000000000000000000000000000000", expected, 32);
    sc_reduce(in);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, in, 32);
}

void test_sc_reduce_just_over_L(void) {
    u8 in[64]; hex2bin("f2d3f55c1a631258d69cf7a2def9de14000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000", in, 64);
    u8 expected[32]; hex2bin("0500000000000000000000000000000000000000000000000000000000000000", expected, 32);
    sc_reduce(in);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, in, 32);
}

void test_sc_reduce_max_512bit(void) {
    u8 in[64]; hex2bin("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", in, 64);
    u8 expected[32]; hex2bin("000f9c44e31106a447938568a71b0ed065bef517d273ecce3d9a307c1b419903", expected, 32);
    sc_reduce(in);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, in, 32);
}

void test_sc_reduce_random0(void) {
    u8 in[64]; hex2bin("38b4e652e44da7f2370d9e260e27136550a4a3a6d07f5c0c332f8b1224083fd22b902f8911e81818f8c99d5d5d9831957504d90e945de2e8f54ee781cc75f636", in, 64);
    u8 expected[32]; hex2bin("69e635e2b59edaf289828e009b47ac5dd30f507e94a31614a8be389e1655b504", expected, 32);
    sc_reduce(in);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, in, 32);
}

void test_sc_reduce_random1(void) {
    u8 in[64]; hex2bin("d85099095aa300165a67036f9b540d6b8f0be21124179c3dd9f73817ce6e118d264aad6cb6dd210faf94acd3cf92c190237cb11f5d108cf25930263938b370a1", in, 64);
    u8 expected[32]; hex2bin("841ac4e571c9aab54df078817d95682262aed88f044783d0d94ebef20ceea708", expected, 32);
    sc_reduce(in);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, in, 32);
}

void test_sc_reduce_random2(void) {
    u8 in[64]; hex2bin("b5769fa0f1483f95a90d9df2f130d60fcf04bd93f50ae69514da8c659ce2b10cccdaebf990d19838b0d7ec0b3e97818ecb96c4dbadbe172296d5234a42b24c6b", in, 64);
    u8 expected[32]; hex2bin("fbfa1ec8eb3a28a0e6867e40d52d53090b65e07e85158eb020b4e9cfd6832400", expected, 32);
    sc_reduce(in);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, in, 32);
}

int main(int, char**) {
    UNITY_BEGIN();

    RUN_TEST(test_sc_reduce_zero);
    RUN_TEST(test_sc_reduce_just_under_L);
    RUN_TEST(test_sc_reduce_exactly_L);
    RUN_TEST(test_sc_reduce_just_over_L);
    RUN_TEST(test_sc_reduce_max_512bit);
    RUN_TEST(test_sc_reduce_random0);
    RUN_TEST(test_sc_reduce_random1);
    RUN_TEST(test_sc_reduce_random2);

    RUN_TEST(test_ed25519_keypair_vector1);
    RUN_TEST(test_ed25519_sign_vector1);
    RUN_TEST(test_ed25519_sign_vector2_empty_message);
    RUN_TEST(test_ed25519_sign_vector3_100_byte_message);
    RUN_TEST(test_ed25519_sign_then_verify_round_trip);

    RUN_TEST(test_x25519_keypair_vector1);
    RUN_TEST(test_x25519_keypair_vector2);
    RUN_TEST(test_x25519_keypair_vector3);
    RUN_TEST(test_x25519_ecdh_shared_secret_matches_both_directions);

    return UNITY_END();
}
