//! GB/T 32918.5-2017 / GM/T 0003.5 SM2 key-exchange worked example on the
//! RECOMMENDED SM2 curve (p = FFFFFFFE…) — the curve this library hard-codes.
//! NOT the GB/T 32918.3 `8542D69E…` test-curve Annex (those vectors cannot
//! reproduce here; see docs/v1.1-sm2kx-kat-sourcing.md).
//!
//! Human-gated values transcribed from the recommended-curve worked example
//! (maintainer-verified against /private/tmp/sm2pdf/params-hi-01.png + -02.png,
//! 2026-06-10). cfg-gated on `sm2-key-exchange`.
#![cfg(feature = "sm2-key-exchange")]

use gmcrypto_core::sm2::key_exchange::{
    Sm2KxConfirm, Sm2KxEphemeralPoint, Sm2KxInitiator, Sm2KxResponder,
};
use gmcrypto_core::sm2::{Sm2PrivateKey, compute_z};
use hex_literal::hex;

// Both parties use the DEFAULT ID "1234567812345678" in the GM/T
// 0003.5-2012 recommended-curve worked example. The ALICE123@YAHOO.COM /
// BILL456@YAHOO.COM identities belong to the GB/T 32918.3 *test-curve*
// Annex — using them here reproduces every point but derives the wrong
// Z_A/Z_B (and hence K); see docs/v1.1-sm2kx-kat-sourcing.md.
const ID_A: &[u8] = b"1234567812345678";
const ID_B: &[u8] = b"1234567812345678";
const KLEN: usize = 16; // 128-bit example

// Static private keys (big-endian, 32 bytes) — recommended-curve example:
const D_A: [u8; 32] = hex!("81EB26E941BB5AF16DF116495F90695272AE2CD63D6C4AE1678418BE48230029");
const D_B: [u8; 32] = hex!("785129917D45A9EA5437A59356B82338EAADDA6CEB199088F14AE10DEFA229B5");

// Ephemeral private keys (fed via the fixed-scalar TryCryptoRng below) —
// human-gated, maintainer-confirmed from the recommended-curve example:
const R_A_EPH: [u8; 32] = hex!("D4DE15474DB74D06491C440D305E012400990F3E390C7E87153C12DB2EA60BB3");
const R_B_EPH: [u8; 32] = hex!("7E07124814B309489125EAED101113164EBF0F3458C5BD88335C1F9D596243D6");

// Intermediate values from the worked example (staged assertions localize
// any divergence to the exact step that introduced it):
// Static public points P_A / P_B (SEC1 uncompressed 04‖X‖Y).
const P_A_SEC1: [u8; 65] = hex!(
    "04160E12897DF4EDB61DD812FEB96748FBD3CCF4FFE26AA6F6DB9540AF49C942324A7DAD08BB9A459531694BEB20AA489D6649975E1BFCF8C4741B78B4B223007F"
);
const P_B_SEC1: [u8; 65] = hex!(
    "046AE848C57C53C7B1B5FA99EB2286AF078BA64C64591B8B566F7357D576F16DFBEE489D771621A27B36C5C7992062E9CD09A9264386F3FBEA54DFF69305621C4D"
);
// Ephemeral public points R_A / R_B.
const R_A_SEC1: [u8; 65] = hex!(
    "0464CED1BDBC99D590049B434D0FD73428CF608A5DB8FE5CE07F15026940BAE40E376629C7AB21E7DB260922499DDB118F07CE8EAAE3E7720AFEF6A5CC062070C0"
);
const R_B_SEC1: [u8; 65] = hex!(
    "04ACC27688A6F7B706098BC91FF3AD1BFF7DC2802CDB14CCCCDB0A90471F9BD7072FEDAC0494B2FFC4D6853876C79B8F301C6573AD0AA50F39FC87181E1A1B46FE"
);
// Party hashes Z_A / Z_B as printed in the worked example's KDF input.
const Z_A_STD: [u8; 32] = hex!("3B85A57179E11E7E513AA622991F2CA74D1807A0BD4D4B38F90987A17AC245B1");
const Z_B_STD: [u8; 32] = hex!("79C988D63229D97EF19FE02CA1056E01E6A7411ED24694AA8F834F4A4AB022F7");
// Shared point U = V (not API-observable; recorded for documentation —
// the K assertion covers it transitively):
//   x_U = C558B44BEE5301D9F52B44D939BB59584D75B9034DD6A9FC826872109A65739F
//   y_U = 3252B35B191D8AE01CD122C025204334C5EACF68A0CB4854C6A7D367ECAD4DE7

// EXPECTED OUTPUTS:
const EXPECT_K: [u8; KLEN] = hex!("6C89347354DE2484C60B4AB1FDE4C6E5");
// Confirmation tags (asserted from Task 2.3; inner hash for reference:
// SM3(x_U‖Z_A‖Z_B‖x1‖y1‖x2‖y2) =
//   90E2A628E4F57ABD78339EA33F967D11A154117BEA442F7B627D4F4DD047B7F6).
const EXPECT_S_B: [u8; 32] =
    hex!("D3A0FE15DEE185CEAE907A6B595CC32A266ED7B3367E9983A896DC32FA20F8EB");
const EXPECT_S_A: [u8; 32] =
    hex!("18C7894B3816DF16CF07B05C5EC0BEF5D655D58F779CC1B400A4F3884644DB88");

/// Fixed-bytes `TryCryptoRng` — integration tests cannot reach the
/// module-private `#[cfg(test)]` helper, so the 3-line impl is inlined
/// here (plan amendment S10).
struct FixedScalarRng([u8; 32]);

impl rand_core::TryRng for FixedScalarRng {
    type Error = core::convert::Infallible;
    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
        Ok(0)
    }
    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
        Ok(0)
    }
    fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
        assert_eq!(dst.len(), 32);
        dst.copy_from_slice(&self.0);
        Ok(())
    }
}
impl rand_core::TryCryptoRng for FixedScalarRng {}

/// Stage 1: the static key pairs and the party hashes. A failure here
/// means the d→P derivation or `compute_z` diverges from the standard
/// before any key-exchange math runs.
#[test]
fn statics_and_z_match_standard() {
    let da = Sm2PrivateKey::from_bytes_be(&D_A).unwrap();
    let db = Sm2PrivateKey::from_bytes_be(&D_B).unwrap();
    let (pa, pb) = (da.public_key(), db.public_key());

    assert_eq!(pa.to_sec1_uncompressed(), P_A_SEC1, "P_A != standard");
    assert_eq!(pb.to_sec1_uncompressed(), P_B_SEC1, "P_B != standard");

    assert_eq!(compute_z(&pa, ID_A), Z_A_STD, "Z_A != standard");
    assert_eq!(compute_z(&pb, ID_B), Z_B_STD, "Z_B != standard");
}

/// Stage 2: the ephemeral points from the fixed standard ephemerals.
#[test]
fn ephemerals_match_standard() {
    let da = Sm2PrivateKey::from_bytes_be(&D_A).unwrap();
    let db = Sm2PrivateKey::from_bytes_be(&D_B).unwrap();
    let (pa, pb) = (da.public_key(), db.public_key());

    let init = Sm2KxInitiator::new(&da, &pb, ID_A, ID_B, KLEN).unwrap();
    let (ra, _iw) = init
        .produce_ephemeral(&mut FixedScalarRng(R_A_EPH))
        .unwrap();
    assert_eq!(ra.to_bytes(), R_A_SEC1, "R_A != standard");

    let resp = Sm2KxResponder::new(&db, &pa, ID_A, ID_B, KLEN).unwrap();
    let (rb, _sb, _rw) = resp.respond(&ra, &mut FixedScalarRng(R_B_EPH)).unwrap();
    assert_eq!(rb.to_bytes(), R_B_SEC1, "R_B != standard");
}

/// Stage 3: the full agreement — K must be byte-identical to the
/// worked example on both sides.
#[test]
fn annex_shared_key_matches_standard() {
    let da = Sm2PrivateKey::from_bytes_be(&D_A).unwrap();
    let db = Sm2PrivateKey::from_bytes_be(&D_B).unwrap();
    let (pa, pb) = (da.public_key(), db.public_key());
    let mut rng_a = FixedScalarRng(R_A_EPH);
    let mut rng_b = FixedScalarRng(R_B_EPH);

    let init = Sm2KxInitiator::new(&da, &pb, ID_A, ID_B, KLEN).unwrap();
    let (ra, iw) = init.produce_ephemeral(&mut rng_a).unwrap();
    let resp = Sm2KxResponder::new(&db, &pa, ID_A, ID_B, KLEN).unwrap();
    let (rb, sb, rw) = resp.respond(&ra, &mut rng_b).unwrap();
    // Confirmation tags byte-identical to the worked example (Task 2.3):
    // S_B is the page's 选项 S_1 (0x02 prefix), S_A the 选项 S_A (0x03).
    assert_eq!(sb.to_bytes(), EXPECT_S_B, "S_B != standard value");
    let (k_a, sa) = iw.confirm(&rb, &sb).unwrap();
    assert_eq!(sa.to_bytes(), EXPECT_S_A, "S_A != standard value");
    let k_b = rw.finish(&sa).unwrap();

    assert_eq!(k_a.as_bytes(), EXPECT_K, "K != standard value");
    assert_eq!(k_a.as_bytes(), k_b.as_bytes(), "K_A != K_B");
}

// ---- Negative paths (Task 2.4) — every failure collapses to the single
// Error::Failed, no panic. klen == 0 / over-long id / identity-peer rejects
// are covered at module level (key_exchange unit tests); these exercise
// the peer-facing wire inputs through the public API.

fn kat_parties() -> (Sm2PrivateKey, Sm2PrivateKey) {
    (
        Sm2PrivateKey::from_bytes_be(&D_A).unwrap(),
        Sm2PrivateKey::from_bytes_be(&D_B).unwrap(),
    )
}

/// Off-curve peer R (flipped coordinate byte) → Failed on both the
/// responder (`respond`) and initiator (`confirm`) sides — the
/// invalid-curve defense.
#[test]
fn negative_off_curve_peer_r_rejected() {
    let (da, db) = kat_parties();
    let (pa, pb) = (da.public_key(), db.public_key());

    let init = Sm2KxInitiator::new(&da, &pb, ID_A, ID_B, KLEN).unwrap();
    let (ra, iw) = init
        .produce_ephemeral(&mut FixedScalarRng(R_A_EPH))
        .unwrap();

    // Responder side: corrupt R_A's x-coordinate.
    let mut bad = ra.to_bytes();
    bad[10] ^= 0x01;
    let corrupt_initiator_r = Sm2KxEphemeralPoint::from_bytes(&bad);
    let resp = Sm2KxResponder::new(&db, &pa, ID_A, ID_B, KLEN).unwrap();
    assert!(
        resp.respond(&corrupt_initiator_r, &mut FixedScalarRng(R_B_EPH))
            .is_err(),
        "off-curve R_A accepted by respond"
    );

    // Initiator side: corrupt R_B's y-coordinate.
    let resp = Sm2KxResponder::new(&db, &pa, ID_A, ID_B, KLEN).unwrap();
    let (rb, sb, _rw) = resp.respond(&ra, &mut FixedScalarRng(R_B_EPH)).unwrap();
    let mut bad = rb.to_bytes();
    bad[40] ^= 0x01;
    let corrupt_responder_r = Sm2KxEphemeralPoint::from_bytes(&bad);
    assert!(
        iw.confirm(&corrupt_responder_r, &sb).is_err(),
        "off-curve R_B accepted by confirm"
    );
}

/// Non-`04` SEC1 tag byte on the peer R → Failed (S8). A SEC1-encoded
/// identity is not constructible in the uncompressed form; the U = O
/// case is covered by the internal `to_affine → None` path.
#[test]
fn negative_bad_tag_peer_r_rejected() {
    let (da, db) = kat_parties();
    let (pa, pb) = (da.public_key(), db.public_key());

    let init = Sm2KxInitiator::new(&da, &pb, ID_A, ID_B, KLEN).unwrap();
    let (ra, _iw) = init
        .produce_ephemeral(&mut FixedScalarRng(R_A_EPH))
        .unwrap();
    for tag in [0x00u8, 0x02, 0x03, 0x05] {
        let mut bad = ra.to_bytes();
        bad[0] = tag;
        let corrupt_initiator_r = Sm2KxEphemeralPoint::from_bytes(&bad);
        let resp = Sm2KxResponder::new(&db, &pa, ID_A, ID_B, KLEN).unwrap();
        assert!(
            resp.respond(&corrupt_initiator_r, &mut FixedScalarRng(R_B_EPH))
                .is_err(),
            "peer R with SEC1 tag {tag:#04x} accepted"
        );
    }
}

/// Tampered `S_A` at `finish` → Failed (the responder never releases `K`).
#[test]
fn negative_tampered_s_a_rejected() {
    let (da, db) = kat_parties();
    let (pa, pb) = (da.public_key(), db.public_key());

    let init = Sm2KxInitiator::new(&da, &pb, ID_A, ID_B, KLEN).unwrap();
    let (ra, iw) = init
        .produce_ephemeral(&mut FixedScalarRng(R_A_EPH))
        .unwrap();
    let resp = Sm2KxResponder::new(&db, &pa, ID_A, ID_B, KLEN).unwrap();
    let (rb, sb, rw) = resp.respond(&ra, &mut FixedScalarRng(R_B_EPH)).unwrap();
    let (_k_a, sa) = iw.confirm(&rb, &sb).unwrap();
    let mut bad = sa.to_bytes();
    bad[15] ^= 0x10;
    assert!(
        rw.finish(&Sm2KxConfirm::from_bytes(&bad)).is_err(),
        "tampered S_A accepted by finish"
    );
}

/// The GM/T 0003.5 worked example's K is derived BEFORE the
/// confirmation tags, so the same standard vector pins the v1.6
/// no-confirmation completers byte-for-byte.
#[test]
fn unconfirmed_paths_reproduce_standard_k() {
    let da = Sm2PrivateKey::from_bytes_be(&D_A).unwrap();
    let db = Sm2PrivateKey::from_bytes_be(&D_B).unwrap();
    let (pa, pb) = (da.public_key(), db.public_key());

    let init = Sm2KxInitiator::new(&da, &pb, ID_A, ID_B, KLEN).unwrap();
    let (ra, iw) = init
        .produce_ephemeral(&mut FixedScalarRng(R_A_EPH))
        .unwrap();
    let resp = Sm2KxResponder::new(&db, &pa, ID_A, ID_B, KLEN).unwrap();
    let (rb, k_resp) = resp
        .respond_without_key_confirmation(&ra, &mut FixedScalarRng(R_B_EPH))
        .unwrap();
    assert_eq!(rb.to_bytes(), R_B_SEC1, "R_B != standard");
    assert_eq!(k_resp.as_bytes(), EXPECT_K, "responder K != standard");

    let k_init = iw
        .derive_without_key_confirmation(&Sm2KxEphemeralPoint::from_bytes(&R_B_SEC1))
        .unwrap();
    assert_eq!(k_init.as_bytes(), EXPECT_K, "initiator K != standard");
}

/// Confirmed and unconfirmed flows agree at both the standard klen
/// (16) and the TLCP pre-master klen (48) — scope Q6.6 / Codex W1
/// Medium-5.
#[test]
fn unconfirmed_matches_confirmed_at_klen_16_and_48() {
    for klen in [16usize, 48] {
        let da = Sm2PrivateKey::from_bytes_be(&D_A).unwrap();
        let db = Sm2PrivateKey::from_bytes_be(&D_B).unwrap();
        let (pa, pb) = (da.public_key(), db.public_key());

        // Confirmed reference run.
        let init = Sm2KxInitiator::new(&da, &pb, ID_A, ID_B, klen).unwrap();
        let (ra, iw) = init
            .produce_ephemeral(&mut FixedScalarRng(R_A_EPH))
            .unwrap();
        let resp = Sm2KxResponder::new(&db, &pa, ID_A, ID_B, klen).unwrap();
        let (rb, sb, rw) = resp.respond(&ra, &mut FixedScalarRng(R_B_EPH)).unwrap();
        let (k_conf, sa) = iw.confirm(&rb, &sb).unwrap();
        let k_conf_resp = rw.finish(&sa).unwrap();

        // Unconfirmed run, same ephemerals.
        let init = Sm2KxInitiator::new(&da, &pb, ID_A, ID_B, klen).unwrap();
        let (ra2, iw2) = init
            .produce_ephemeral(&mut FixedScalarRng(R_A_EPH))
            .unwrap();
        assert_eq!(ra2.to_bytes(), ra.to_bytes());
        let resp = Sm2KxResponder::new(&db, &pa, ID_A, ID_B, klen).unwrap();
        let (rb2, k_unc_resp) = resp
            .respond_without_key_confirmation(&ra2, &mut FixedScalarRng(R_B_EPH))
            .unwrap();
        assert_eq!(rb2.to_bytes(), rb.to_bytes());
        let k_unc_init = iw2.derive_without_key_confirmation(&rb2).unwrap();

        assert_eq!(k_unc_init.as_bytes(), k_conf.as_bytes());
        assert_eq!(k_unc_resp.as_bytes(), k_conf_resp.as_bytes());
    }
}

/// The unconfirmed paths keep the v1.1 rejects: off-curve / garbage
/// peer points fail with the single error on both roles.
#[test]
fn unconfirmed_paths_reject_invalid_peer_points() {
    let da = Sm2PrivateKey::from_bytes_be(&D_A).unwrap();
    let db = Sm2PrivateKey::from_bytes_be(&D_B).unwrap();
    let (pa, pb) = (da.public_key(), db.public_key());

    let mut bad = R_B_SEC1;
    bad[64] ^= 0x01; // off-curve y
    let garbage = [0x5Au8; 65]; // wrong tag byte

    for evil in [bad, garbage] {
        let init = Sm2KxInitiator::new(&da, &pb, ID_A, ID_B, KLEN).unwrap();
        let (_ra, iw) = init
            .produce_ephemeral(&mut FixedScalarRng(R_A_EPH))
            .unwrap();
        assert!(
            iw.derive_without_key_confirmation(&Sm2KxEphemeralPoint::from_bytes(&evil))
                .is_err()
        );

        let resp = Sm2KxResponder::new(&db, &pa, ID_A, ID_B, KLEN).unwrap();
        assert!(
            resp.respond_without_key_confirmation(
                &Sm2KxEphemeralPoint::from_bytes(&evil),
                &mut FixedScalarRng(R_B_EPH),
            )
            .is_err()
        );
    }
}
