openpay/ (+3 crates)
op-bnpl/ Affirm + Klarna + Afterpay/Clearpay rails. Full lifecycle
typestate (Initiated→Approved→Captured→Settled→Refunded).
Per-provider sandbox+production base URLs, regions (NA/EU/OC
for Klarna; US/AuNz/Gb/Ca/Eu for Afterpay). HMAC-SHA256
webhook verification with constant-time compare. 61 tests.
op-batch/ NACHA ACH (94-char records, PPD/CCD/WEB/TEL/CTX, R01-R99
return codes, Same-Day ACH cutoffs); SEPA pain.001.001.09
(CT) + pain.008.001.08 (DD) with SCT-Inst flagging; Fedwire
MT103/MT202/CHIPS + pacs.008.001.12 / pacs.009.001.10 +
UETR; Bacs Standard 18. Per-rail cutoff scheduler. Exception
taxonomy unified across rails. 51 tests.
op-screening/ OFAC SDN + EU consolidated + UN consolidated + HMT UK
consolidated + Australia DFAT + Canada SEMA + Japan MOF.
Levenshtein + Jaro-Winkler + Soundex + Metaphone + token
Jaccard scoring. Unicode NFKD normalisation + Cyrillic /
Arabic transliteration. Ed25519-signed BLAKE3-chained
audit log. BSA helpers: CTR + SAR pattern detection
(structuring, round-dollar, rapid-in-out). 43 tests.
smart-byte/smart-byte-rs/ (+3 crates)
smart-byte-bbs/ BBS+ signatures (IETF draft-irtf-cfrg-bbs-signatures-08).
BLS12-381 keys, signatures, disclosure proofs with
cryptographic unlinkability. bbs-2023 Data Integrity
cryptosuite. AnonCreds-shaped link secrets +
predicate proofs. SecretKey zeroize-on-drop. 49 tests.
smart-byte-didcomm/ DIDComm v2 messaging per DIF spec. Pack-encrypted
(ECDH-1PU + ECDH-ES via X25519), pack-signed (JWS),
pack-plaintext. Aries protocols: issue-credential v3,
present-proof v3, basic-message, trust-ping, discover-
features, out-of-band, coordinate-mediation, message-
pickup. Mediator role with pluggable storage. 23 tests.
smart-byte-mdl/ ISO/IEC 18013-5 mobile driver's licence + 18013-7
online presentment. mDoc + MSO with salted-hash
selective disclosure (per item; SHA-256 digest tree).
org.iso.18013.5.1 namespace + AAMVA add-on. ES256
device-auth + issuer-auth. QR-code device-engagement
scaffolding. 28 tests.
eoc/eoc-rs/ (+3 crates)
eoc-local/ Local-inference backends behind one NeuralBackend:
llama.cpp (GGUF, Q4/Q5/Q6/Q8/F16/F32 quantisation),
MLX (Apple Silicon, feature-gated), MLC-LLM (TVM, feature-
gated marker), ONNX Runtime. GGUF file-format reader.
HuggingFace tokenizers. Greedy / top-k / top-p / temperature /
Mirostat samplers. Model registry scanning ~/.eoc/models/.
Joule attribution via eoc-meter. 39 tests.
eoc-rerank/ BM25 sparse + HNSW dense + RRF + min-max hybrid fusion.
Cohere v3.5 + Voyage v2 + bge-reranker v2-m3 + ms-marco
MiniLM + ColBERT v2 rerankers. Two-stage pipeline:
retrieve top-K hybrid → cross-encoder rerank → top-N.
KV-stage recovery shim into the cascade. 31 tests.
eoc-eval/ 15 canonical eval harnesses: MMLU, MMLU-Pro, GPQA,
HumanEval (pass@1 with python3 sandbox), BBH, IFEval (25
verifiable constraints), AlpacaEval, AGIEval, HellaSwag,
ARC (Easy + Challenge), TruthfulQA (MC1+MC2), BoolQ,
GSM8K, MATH (boxed-answer equivalence), Winogrande. Real
questions in BuiltinSamples. EvalRunner emits joules-per-
correct alongside accuracy. 49 tests.
Quality:
- cargo check across all three workspaces: clean
- cargo clippy --all-targets -- -D warnings on every new crate: clean
- 374 new tests, 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).
246 lines
7.5 KiB
Rust
246 lines
7.5 KiB
Rust
//! End-to-end issue / present / verify integration tests.
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::sync::Arc;
|
|
|
|
use chrono::{TimeZone, Utc};
|
|
use ciborium::value::Value as CborValue;
|
|
use smart_byte_mdl::{
|
|
FixedTime, Issuer, IssuerKey, ItemsRequest, MobileDoc, NS_MDL,
|
|
P256DeviceSigner, SessionTranscript, TrustAnchor, ValidityInfo, Verifier,
|
|
present,
|
|
};
|
|
|
|
fn build_validity() -> ValidityInfo {
|
|
ValidityInfo {
|
|
signed: Utc.with_ymd_and_hms(2026, 1, 1, 0, 0, 0).unwrap(),
|
|
valid_from: Utc.with_ymd_and_hms(2026, 1, 1, 0, 0, 0).unwrap(),
|
|
valid_until: Utc.with_ymd_and_hms(2030, 1, 1, 0, 0, 0).unwrap(),
|
|
expected_update: None,
|
|
}
|
|
}
|
|
|
|
fn build_claims() -> BTreeMap<String, BTreeMap<String, CborValue>> {
|
|
let mut ns: BTreeMap<String, BTreeMap<String, CborValue>> = BTreeMap::new();
|
|
let mut inner: BTreeMap<String, CborValue> = BTreeMap::new();
|
|
inner.insert("family_name".into(), CborValue::Text("Doe".into()));
|
|
inner.insert("given_name".into(), CborValue::Text("Jane".into()));
|
|
inner.insert("birth_date".into(), CborValue::Text("1990-04-12".into()));
|
|
inner.insert("age_over_21".into(), CborValue::Bool(true));
|
|
inner.insert("document_number".into(), CborValue::Text("Z-1234".into()));
|
|
ns.insert(NS_MDL.into(), inner);
|
|
ns
|
|
}
|
|
|
|
struct Setup {
|
|
issuer_key: IssuerKey,
|
|
device_key: IssuerKey,
|
|
doc: MobileDoc,
|
|
}
|
|
|
|
fn issue() -> Setup {
|
|
let issuer_key = IssuerKey::generate_es256();
|
|
let device_key = IssuerKey::generate_es256();
|
|
let issuer = Issuer::new(
|
|
IssuerKey {
|
|
alg: issuer_key.alg,
|
|
p256: issuer_key.p256.clone(),
|
|
},
|
|
Vec::new(),
|
|
build_validity(),
|
|
);
|
|
let doc = issuer
|
|
.issue(
|
|
"org.iso.18013.5.1.mDL",
|
|
build_claims(),
|
|
device_key.cose_public_key(),
|
|
b"integration-seed",
|
|
)
|
|
.unwrap();
|
|
Setup {
|
|
issuer_key,
|
|
device_key,
|
|
doc,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn issue_verify_full_disclosure() {
|
|
let setup = issue();
|
|
let anchor = TrustAnchor::es256(setup.issuer_key.p256.verifying_key(), None);
|
|
let verifier = Verifier::new(
|
|
vec![anchor],
|
|
Arc::new(FixedTime(
|
|
Utc.with_ymd_and_hms(2027, 1, 1, 0, 0, 0).unwrap(),
|
|
)),
|
|
);
|
|
let claims = verifier.verify(&setup.doc).unwrap();
|
|
assert_eq!(claims.doc_type, "org.iso.18013.5.1.mDL");
|
|
let ns = &claims.claims[NS_MDL];
|
|
assert_eq!(ns["family_name"], CborValue::Text("Doe".into()));
|
|
assert_eq!(ns["given_name"], CborValue::Text("Jane".into()));
|
|
assert_eq!(ns["age_over_21"], CborValue::Bool(true));
|
|
assert_eq!(ns.len(), 5);
|
|
}
|
|
|
|
#[test]
|
|
fn selective_disclosure_reveals_only_requested_items() {
|
|
let setup = issue();
|
|
let signer = P256DeviceSigner {
|
|
key: setup.device_key.p256.clone(),
|
|
};
|
|
let transcript =
|
|
SessionTranscript::for_oid4vp("client-id", "https://verifier", "n", "m");
|
|
let mut req: BTreeMap<String, Vec<String>> = BTreeMap::new();
|
|
req.insert(
|
|
NS_MDL.into(),
|
|
vec!["family_name".into(), "age_over_21".into()],
|
|
);
|
|
let presented = present(
|
|
&setup.doc,
|
|
&req,
|
|
&signer,
|
|
&transcript,
|
|
BTreeMap::new(),
|
|
)
|
|
.unwrap();
|
|
|
|
let anchor = TrustAnchor::es256(setup.issuer_key.p256.verifying_key(), None);
|
|
let verifier = Verifier::new(
|
|
vec![anchor],
|
|
Arc::new(FixedTime(
|
|
Utc.with_ymd_and_hms(2027, 1, 1, 0, 0, 0).unwrap(),
|
|
)),
|
|
)
|
|
.with_transcript(transcript);
|
|
let claims = verifier.verify(&presented).unwrap();
|
|
let ns = &claims.claims[NS_MDL];
|
|
assert_eq!(ns.len(), 2);
|
|
assert!(ns.contains_key("family_name"));
|
|
assert!(ns.contains_key("age_over_21"));
|
|
assert!(!ns.contains_key("birth_date"));
|
|
assert!(!ns.contains_key("document_number"));
|
|
assert!(!ns.contains_key("given_name"));
|
|
}
|
|
|
|
#[test]
|
|
fn tampered_issuer_signed_item_is_rejected() {
|
|
let mut setup = issue();
|
|
let items = setup
|
|
.doc
|
|
.issuer_signed
|
|
.name_spaces
|
|
.get_mut(NS_MDL)
|
|
.unwrap();
|
|
for it in items.iter_mut() {
|
|
if it.element_identifier == "family_name" {
|
|
it.element_value = CborValue::Text("Mallory".into());
|
|
break;
|
|
}
|
|
}
|
|
let anchor = TrustAnchor::es256(setup.issuer_key.p256.verifying_key(), None);
|
|
let verifier = Verifier::new(
|
|
vec![anchor],
|
|
Arc::new(FixedTime(
|
|
Utc.with_ymd_and_hms(2027, 1, 1, 0, 0, 0).unwrap(),
|
|
)),
|
|
);
|
|
let err = verifier.verify(&setup.doc).unwrap_err();
|
|
assert!(
|
|
matches!(err, smart_byte_mdl::MdlError::DigestMismatch { .. }),
|
|
"got {err:?}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn expired_validity_info_is_rejected() {
|
|
let setup = issue();
|
|
let anchor = TrustAnchor::es256(setup.issuer_key.p256.verifying_key(), None);
|
|
// 2031 is after validUntil 2030.
|
|
let verifier = Verifier::new(
|
|
vec![anchor],
|
|
Arc::new(FixedTime(
|
|
Utc.with_ymd_and_hms(2031, 1, 1, 0, 0, 0).unwrap(),
|
|
)),
|
|
);
|
|
let err = verifier.verify(&setup.doc).unwrap_err();
|
|
assert!(
|
|
matches!(err, smart_byte_mdl::MdlError::ValidityOutOfRange { .. }),
|
|
"got {err:?}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn device_signed_with_wrong_key_is_rejected() {
|
|
let setup = issue();
|
|
// Sign with a wrong device key — the MSO commits to the original.
|
|
let wrong_device = IssuerKey::generate_es256();
|
|
let signer = P256DeviceSigner {
|
|
key: wrong_device.p256,
|
|
};
|
|
let transcript =
|
|
SessionTranscript::for_oid4vp("client-id", "https://verifier", "n", "m");
|
|
let mut req: BTreeMap<String, Vec<String>> = BTreeMap::new();
|
|
req.insert(NS_MDL.into(), vec!["family_name".into()]);
|
|
let presented = present(
|
|
&setup.doc,
|
|
&req,
|
|
&signer,
|
|
&transcript,
|
|
BTreeMap::new(),
|
|
)
|
|
.unwrap();
|
|
let anchor = TrustAnchor::es256(setup.issuer_key.p256.verifying_key(), None);
|
|
let verifier = Verifier::new(
|
|
vec![anchor],
|
|
Arc::new(FixedTime(
|
|
Utc.with_ymd_and_hms(2027, 1, 1, 0, 0, 0).unwrap(),
|
|
)),
|
|
)
|
|
.with_transcript(transcript);
|
|
let err = verifier.verify(&presented).unwrap_err();
|
|
assert!(
|
|
matches!(err, smart_byte_mdl::MdlError::Signature(_)),
|
|
"got {err:?}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn items_request_drives_disclosure() {
|
|
let setup = issue();
|
|
let mut ns: BTreeMap<String, BTreeMap<String, bool>> = BTreeMap::new();
|
|
let mut inner: BTreeMap<String, bool> = BTreeMap::new();
|
|
inner.insert("family_name".into(), false);
|
|
inner.insert("age_over_21".into(), false);
|
|
ns.insert(NS_MDL.into(), inner);
|
|
let req = ItemsRequest {
|
|
doc_type: "org.iso.18013.5.1.mDL".into(),
|
|
name_spaces: ns,
|
|
};
|
|
let signer = P256DeviceSigner {
|
|
key: setup.device_key.p256,
|
|
};
|
|
let transcript =
|
|
SessionTranscript::for_oid4vp("client-id", "https://verifier", "n", "m");
|
|
let presented = present(
|
|
&setup.doc,
|
|
&req.requested(),
|
|
&signer,
|
|
&transcript,
|
|
BTreeMap::new(),
|
|
)
|
|
.unwrap();
|
|
let anchor = TrustAnchor::es256(setup.issuer_key.p256.verifying_key(), None);
|
|
let verifier = Verifier::new(
|
|
vec![anchor],
|
|
Arc::new(FixedTime(
|
|
Utc.with_ymd_and_hms(2027, 1, 1, 0, 0, 0).unwrap(),
|
|
)),
|
|
)
|
|
.with_transcript(transcript);
|
|
let claims = verifier.verify(&presented).unwrap();
|
|
let ns = &claims.claims[NS_MDL];
|
|
assert_eq!(ns.len(), 2);
|
|
assert!(ns.contains_key("family_name"));
|
|
assert!(ns.contains_key("age_over_21"));
|
|
}
|