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).
180 lines
6.9 KiB
Rust
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));
|
|
}
|