#![allow(clippy::expect_used)]
use exo_proofs::envelope::{
BackendId, ProofEnvelope, ProofStatementKind, UNAUDITED_BLAKE3_STANDIN_BACKEND_ID,
};
fn sample_envelope() -> ProofEnvelope {
ProofEnvelope {
statement_kind: ProofStatementKind::GovernanceCompliance,
backend_id: UNAUDITED_BLAKE3_STANDIN_BACKEND_ID,
version: 1,
public_inputs: vec![b"public-input-a".to_vec(), b"public-input-b".to_vec()],
commitment_roots: vec![exo_core::types::Hash256::digest(b"commitment-root-1")],
verifier_key_or_image_id: b"verifier-key-or-image-id-bytes".to_vec(),
domain_separator: b"exo-proofs:envelope:v1:governance-compliance".to_vec(),
}
}
fn cbor_bytes<T: serde::Serialize>(value: &T) -> Vec<u8> {
let mut encoded = Vec::new();
ciborium::into_writer(value, &mut encoded).expect("canonical CBOR encode");
encoded
}
#[test]
fn proof_envelope_round_trips_through_canonical_cbor() {
let envelope = sample_envelope();
let encoded = cbor_bytes(&envelope);
let decoded: ProofEnvelope =
ciborium::from_reader(encoded.as_slice()).expect("canonical CBOR decode");
assert_eq!(decoded.statement_kind, envelope.statement_kind);
assert_eq!(decoded.backend_id, envelope.backend_id);
assert_eq!(decoded.version, envelope.version);
assert_eq!(decoded.public_inputs, envelope.public_inputs);
assert_eq!(decoded.commitment_roots, envelope.commitment_roots);
assert_eq!(
decoded.verifier_key_or_image_id,
envelope.verifier_key_or_image_id
);
assert_eq!(decoded.domain_separator, envelope.domain_separator);
}
#[test]
fn proof_envelope_round_trip_covers_every_statement_kind() {
let kinds = [
ProofStatementKind::GovernanceCompliance,
ProofStatementKind::DagInclusion,
ProofStatementKind::ExecutionReceipt,
ProofStatementKind::ModelInference,
ProofStatementKind::PedagogicalCompatibility,
];
for kind in kinds {
let mut envelope = sample_envelope();
envelope.statement_kind = kind;
let encoded = cbor_bytes(&envelope);
let decoded: ProofEnvelope =
ciborium::from_reader(encoded.as_slice()).expect("canonical CBOR decode");
assert_eq!(
decoded.statement_kind, kind,
"statement kind {kind:?} must round-trip through canonical CBOR"
);
}
}
#[test]
fn proof_envelope_rejects_json_bytes() {
let envelope = sample_envelope();
let json_bytes = serde_json::to_vec(&envelope).expect("json encode for negative fixture");
let result: Result<ProofEnvelope, _> = ciborium::from_reader(json_bytes.as_slice());
assert!(
result.is_err(),
"JSON-encoded envelope bytes must not decode as canonical CBOR"
);
}
#[test]
fn envelope_with_unknown_backend_id_fails_closed() {
let mut envelope = sample_envelope();
envelope.backend_id = BackendId::Unknown(0xFFFF_FFFF);
let result = envelope.validate_backend();
assert!(
result.is_err(),
"an envelope naming an unknown/future backend id must fail closed, not validate"
);
}
#[test]
fn envelope_backend_registry_rejects_unregistered_numeric_id() {
let mut envelope = sample_envelope();
envelope.backend_id = BackendId::Unknown(1234);
let encoded = cbor_bytes(&envelope);
let decoded: ProofEnvelope =
ciborium::from_reader(encoded.as_slice()).expect("canonical CBOR decode");
assert!(
decoded.validate_backend().is_err(),
"unregistered numeric backend ids must fail closed after a CBOR round-trip"
);
}
#[cfg(not(feature = "unaudited-pedagogical-proofs"))]
#[test]
fn envelope_wrapping_unaudited_backend_refuses_without_feature() {
let envelope = sample_envelope();
let result = envelope.verify(&[]);
assert!(
matches!(
result,
Err(exo_proofs::error::ProofError::UnauditedImplementation { .. })
),
"an envelope wrapping the still-unaudited blake3 stand-in backend must refuse \
verification unless 'unaudited-pedagogical-proofs' is enabled, got {result:?}"
);
}
#[cfg(feature = "unaudited-pedagogical-proofs")]
#[test]
fn envelope_wrapping_unaudited_backend_construction_allowed_but_verify_fails_closed() {
let envelope = sample_envelope();
let result = envelope.verify(&[]);
assert!(
!matches!(
result,
Err(exo_proofs::error::ProofError::UnauditedImplementation { .. })
),
"with 'unaudited-pedagogical-proofs' enabled, the unaudited backend must not \
hard-refuse at the guard_unaudited gate with UnauditedImplementation, got {result:?}"
);
assert!(
matches!(
result,
Err(exo_proofs::error::ProofError::VerificationFailed(_))
),
"verify() must still fail closed with VerificationFailed (no verifier wired yet, \
arrives with VCG-001b) rather than reporting success, got {result:?}"
);
}
#[test]
fn truncated_cbor_bytes_fail_to_deserialize() {
let envelope = sample_envelope();
let full = cbor_bytes(&envelope);
assert!(
full.len() > 16,
"sanity: sample envelope encoding should be long enough to exercise several cut points"
);
let cut_points = [1usize, 4, full.len() / 4, full.len() / 2, full.len() - 1];
for cut in cut_points {
let truncated = &full[..cut];
let result: Result<ProofEnvelope, _> = ciborium::from_reader(truncated);
assert!(
result.is_err(),
"truncated CBOR bytes (cut at {cut} of {} total) must fail to deserialize, got Ok",
full.len()
);
}
}
#[test]
fn unknown_statement_kind_code_fails_closed() {
let envelope = sample_envelope();
let valid = cbor_bytes(&envelope);
let needle = {
let mut bytes = Vec::new();
bytes.push(0x74u8); bytes.extend_from_slice(b"GovernanceCompliance");
bytes
};
let replacement = {
let mut bytes = Vec::new();
bytes.push(0x74u8); bytes.extend_from_slice(b"NotARealStatementKnd"); bytes
};
assert_eq!(
needle.len(),
replacement.len(),
"sanity: splice must preserve overall byte layout so only the discriminant changes"
);
let position = valid
.windows(needle.len())
.position(|window| window == needle.as_slice())
.expect("sample envelope encoding must contain the statement_kind value bytes");
let mut corrupted = valid.clone();
corrupted[position..position + replacement.len()].copy_from_slice(&replacement);
let result: Result<ProofEnvelope, _> = ciborium::from_reader(corrupted.as_slice());
assert!(
result.is_err(),
"an out-of-registry statement-kind discriminant must fail closed at deserialization, \
got Ok({result:?})"
);
}
#[test]
fn garbage_bytes_fail_to_deserialize() {
let garbage_fixtures: [&[u8]; 4] = [
&[],
&[0xFF, 0xFF, 0xFF, 0xFF],
b"not cbor at all, just ascii text padding to be non-trivially long",
&[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09],
];
for garbage in garbage_fixtures {
let result: Result<ProofEnvelope, _> = ciborium::from_reader(garbage);
assert!(
result.is_err(),
"garbage bytes {garbage:?} must fail to deserialize as ProofEnvelope, got Ok"
);
}
}