open-standards/smart-byte/smart-byte-rs/crates/smart-byte-pq/tests/kat.rs
Transaction Science 01d4ad9c7b Ingest batch 2: 9 new crates across openpay / smart-byte / eoc
openpay/ (+3 crates, ~8400 LOC, 162 tests)
  op-iso8583/         ISO 8583 messages for direct acquirer / card-network
                      connectivity. MTIs, primary+secondary bitmaps, full
                      DE catalogue, BCD/ASCII/EBCDIC/LLVAR/LLLVAR codecs,
                      five dialects (Visa Base I, Mastercard MDS, Amex GNS,
                      Discover, JCB), network-management messages
                      (sign-on/echo/cutover), DE 55 EMV TLV.
  op-mobile-wallets/  Apple Pay (ECDH-P256 + AES-256-GCM), Google Pay ECv2
                      (ECDH + HKDF + AES-256-CTR + HMAC), Samsung Pay. PAN
                      never leaves the decryption boundary as a String;
                      lands in VaultRef. No-PAN-leakage assertions per
                      wallet.
  op-routing/         Least-cost routing + MCC-aware routing + intelligent
                      retry / soft-decline recovery. 231-MCC catalogue.
                      Real ISO 8583 DE-39 soft/hard decline taxonomies.

smart-byte/smart-byte-rs/ (+3 crates, ~7100 LOC, 96 tests)
  smart-byte-vc/      W3C Verifiable Credentials 2.0: VCDM, presentations,
                      Data Integrity proofs (eddsa-jcs-2022), VC-JWT,
                      SD-JWT (selective disclosure), Bitstring Status
                      List 2021. Cargo-bridge maps VCs to Smart Byte
                      envelopes with SAID stability.
  smart-byte-did/     W3C DID resolution: did:key (Ed25519/P-256/secp256k1),
                      did:web (HTTPS), did:peer (numalgo 2+4), did:jwk,
                      did:ion (feature-gated stub). DID URL dereferencer.
  smart-byte-pq/      Post-quantum signatures: ML-DSA-44/65/87 (FIPS 204),
                      SLH-DSA across 12 parameter sets (FIPS 205), FN-DSA
                      (draft FIPS 206, feature-gated). Hybrid Ed25519 +
                      ML-DSA pattern for the transition. Secret-key
                      zeroize-on-drop.

eoc/eoc-rs/ (+3 crates, ~6450 LOC, 76 tests)
  eoc-vendor-api/     NeuralBackend implementations: Anthropic, OpenAI,
                      Google, Mistral, Cohere, Groq, Together, Fireworks.
                      SSE streaming. Prompt-caching where supported.
                      Joule estimates per published energy coefficient
                      (12-model curated table).
  eoc-embeddings/     21 embedding models behind one Embedder trait:
                      OpenAI v3 (3), Cohere v3 (3), Voyage (5), Mistral,
                      Jina, plus local ONNX (BGE x3, nomic, mxbai, GTE,
                      E5) behind 'local' feature. Matryoshka-style
                      dimension projection. Per-content-hash cache.
  eoc-route-learned/  RouteLLM-style matrix-factorisation router +
                      multinomial-logreg classifier + LinUCB / Thompson
                      bandits for online learning. LearnedCascade wraps
                      the rule-based cascade with a confidence-threshold
                      skip policy. Synthetic-eval accuracy: MF 0.935,
                      LogReg 0.980.

Quality:
- cargo check across all three workspaces: clean
- cargo clippy --all-targets -- -D warnings on every new crate: clean
- 334 new tests total; all passing
- #![forbid(unsafe_code)] across every new crate
- No unwrap/expect outside #[cfg(test)]

Rust 1.95.0 edition 2024 (inherited via root rust-toolchain.toml).
2026-05-23 18:12:24 -04:00

180 lines
6.9 KiB
Rust

//! Standards-conformance tests.
//!
//! These tests stand in for the NIST Known-Answer Test (KAT) vectors
//! published in the FIPS 204 and FIPS 205 submission packages. The
//! full KAT files (per-parameter-set, multi-megabyte blobs of seeded
//! deterministic vectors) are not vendored into this crate; instead
//! the tests below assert the two properties any KAT-conformant
//! implementation must satisfy and which break under any incorrect
//! wiring of the underlying primitive:
//!
//! 1. **Length conformance**: keys and signatures the implementation
//! produces have exactly the byte lengths FIPS 204 / 205 specify
//! for the parameter set in question.
//! 2. **Sign / verify consistency**: a freshly generated key pair
//! verifies its own signatures over random messages and rejects
//! tampered ones.
//!
//! The vendored PQClean reference implementations underneath
//! `pqcrypto-mldsa` and `pqcrypto-sphincsplus` are themselves KAT-
//! validated in their respective repositories; this test file
//! verifies that the smart-byte-pq wrappers around them preserve
//! those properties at the public-API surface.
//!
//! To replace the consistency tests with real KAT vector files, drop
//! the relevant `.rsp` files into a `tests/kat_vectors/` directory and
//! parse them in additional `#[test]` functions; the surrounding
//! sign/verify API is already KAT-shaped.
use rand::rngs::OsRng;
use smart_byte_pq::{
PqError,
algorithm::SignatureAlgorithm,
hybrid::{HybridKeyPair, sign_hybrid, verify_hybrid},
mldsa::{self, MlDsaLevel, Signature as MlDsaSignature},
slhdsa::{self, SlhDsaParam},
};
// ---------------------------------------------------------------------------
// ML-DSA (FIPS 204)
// ---------------------------------------------------------------------------
#[test]
fn mldsa44_lengths_match_fips204() {
let mut rng = OsRng;
let kp = mldsa::keygen(MlDsaLevel::Level2, &mut rng);
assert_eq!(kp.public.as_bytes().len(), 1312);
assert_eq!(kp.secret.as_bytes().len(), 2560);
let sig = mldsa::sign(b"smart byte SAID", &kp.secret).expect("sign");
assert_eq!(sig.as_bytes().len(), 2420);
mldsa::verify(b"smart byte SAID", &sig, &kp.public).expect("verify");
}
#[test]
fn mldsa65_lengths_match_fips204() {
let mut rng = OsRng;
let kp = mldsa::keygen(MlDsaLevel::Level3, &mut rng);
assert_eq!(kp.public.as_bytes().len(), 1952);
assert_eq!(kp.secret.as_bytes().len(), 4032);
let sig = mldsa::sign(b"smart byte SAID", &kp.secret).expect("sign");
assert_eq!(sig.as_bytes().len(), 3309);
mldsa::verify(b"smart byte SAID", &sig, &kp.public).expect("verify");
}
#[test]
fn mldsa87_lengths_match_fips204() {
let mut rng = OsRng;
let kp = mldsa::keygen(MlDsaLevel::Level5, &mut rng);
assert_eq!(kp.public.as_bytes().len(), 2592);
assert_eq!(kp.secret.as_bytes().len(), 4896);
let sig = mldsa::sign(b"smart byte SAID", &kp.secret).expect("sign");
assert_eq!(sig.as_bytes().len(), 4627);
mldsa::verify(b"smart byte SAID", &sig, &kp.public).expect("verify");
}
#[test]
fn mldsa_tamper_rejection() {
// One-byte tamper of an ML-DSA signature must verify-fail.
let mut rng = OsRng;
let kp = mldsa::keygen(MlDsaLevel::Level3, &mut rng);
let msg = b"smart byte envelope SAID";
let sig = mldsa::sign(msg, &kp.secret).expect("sign");
let mut bytes = sig.as_bytes().to_vec();
bytes[0] ^= 0x01;
let bad =
MlDsaSignature::from_bytes(MlDsaLevel::Level3, &bytes).expect("decode length-OK sig");
let err = mldsa::verify(msg, &bad, &kp.public).expect_err("must fail");
assert!(matches!(err, PqError::BadSignature));
}
// ---------------------------------------------------------------------------
// SLH-DSA (FIPS 205) — three parameter sets per the test plan.
// ---------------------------------------------------------------------------
#[test]
fn slhdsa_sha2_128s_round_trip() {
let mut rng = OsRng;
let kp = slhdsa::keygen(SlhDsaParam::Sha2_128s, &mut rng);
assert_eq!(
kp.public.as_bytes().len(),
SignatureAlgorithm::SlhDsaSha2_128s.public_key_bytes_len()
);
let msg = b"smart byte SAID";
let sig = slhdsa::sign(msg, &kp.secret).expect("sign");
assert_eq!(
sig.as_bytes().len(),
SignatureAlgorithm::SlhDsaSha2_128s.signature_bytes_len()
);
slhdsa::verify(msg, &sig, &kp.public).expect("verify");
}
#[test]
fn slhdsa_sha2_192s_round_trip() {
let mut rng = OsRng;
let kp = slhdsa::keygen(SlhDsaParam::Sha2_192s, &mut rng);
let msg = b"smart byte SAID";
let sig = slhdsa::sign(msg, &kp.secret).expect("sign");
assert_eq!(
sig.as_bytes().len(),
SignatureAlgorithm::SlhDsaSha2_192s.signature_bytes_len()
);
slhdsa::verify(msg, &sig, &kp.public).expect("verify");
}
#[test]
fn slhdsa_shake_256s_round_trip() {
let mut rng = OsRng;
// SHAKE-256s is the largest "small" variant; we keep this one
// test for KAT-style coverage of the SHAKE family at the top
// security level. (The much-larger 256f case is exercised
// implicitly by the signature_bytes_len constant tests.)
let kp = slhdsa::keygen(SlhDsaParam::Shake_256s, &mut rng);
let msg = b"smart byte SAID";
let sig = slhdsa::sign(msg, &kp.secret).expect("sign");
assert_eq!(
sig.as_bytes().len(),
SignatureAlgorithm::SlhDsaShake_256s.signature_bytes_len()
);
slhdsa::verify(msg, &sig, &kp.public).expect("verify");
}
// ---------------------------------------------------------------------------
// Hybrid (Ed25519 + ML-DSA-65)
// ---------------------------------------------------------------------------
#[test]
fn hybrid_ed25519_mldsa65_round_trip() {
let mut rng = OsRng;
let kp = HybridKeyPair::generate(MlDsaLevel::Level3, &mut rng);
let pub_key = kp.public_key();
let msg = b"smart byte SAID";
let sig = sign_hybrid(msg, &kp).expect("sign");
verify_hybrid(msg, &sig, &pub_key).expect("verify");
}
#[test]
fn hybrid_rejects_when_classical_only_valid() {
// Tamper the PQ half. Classical alone must not authenticate.
let mut rng = OsRng;
let kp = HybridKeyPair::generate(MlDsaLevel::Level3, &mut rng);
let pub_key = kp.public_key();
let msg = b"smart byte SAID";
let mut sig = sign_hybrid(msg, &kp).expect("sign");
let idx = sig.pq.len() / 2;
sig.pq[idx] ^= 0x01;
let err = verify_hybrid(msg, &sig, &pub_key).expect_err("must fail");
assert!(matches!(err, PqError::BadSignature));
}
#[test]
fn hybrid_rejects_when_pq_only_valid() {
// Tamper the classical half. PQ alone must not authenticate.
let mut rng = OsRng;
let kp = HybridKeyPair::generate(MlDsaLevel::Level3, &mut rng);
let pub_key = kp.public_key();
let msg = b"smart byte SAID";
let mut sig = sign_hybrid(msg, &kp).expect("sign");
sig.classical[0] ^= 0xFF;
let err = verify_hybrid(msg, &sig, &pub_key).expect_err("must fail");
assert!(matches!(err, PqError::BadSignature));
}