//! Property-based tests for Signed and Verified. #![cfg(feature = "ed25519")] use bolero::check; use evidence::{ codec::Identity, signature::ed25519::Ed25519, signed::{Signed, SignedUnchecked}, verified::{Verified, VerifiedUnchecked}, }; /// Helper to create a signing key from seed bytes. fn signing_key_from_seed(seed: [u8; 32]) -> ed25519_dalek::SigningKey { ed25519_dalek::SigningKey::from_bytes(&seed) } #[test] #[allow(clippy::expect_used)] // test should panic if verification fails fn signed_verify_roundtrip() { check!() .with_type::<([u8; 32], Vec)>() .for_each(|(seed, payload)| { let signing_key = signing_key_from_seed(*seed); let signed: Signed, Ed25519, Identity> = Signed::seal(&signing_key, payload); let verified = signed .try_verify() .expect("verification should succeed for just-signed payload"); assert_eq!( verified.payload(), payload, "verified payload must match original" ); assert_eq!( verified.issuer(), &signing_key.verifying_key(), "issuer must match signer's public key" ); }); } #[test] fn signed_deterministic() { check!() .with_type::<([u8; 32], Vec)>() .for_each(|(seed, payload)| { let signing_key = signing_key_from_seed(*seed); let signed1: Signed, Ed25519, Identity> = Signed::seal(&signing_key, payload); let signed2: Signed, Ed25519, Identity> = Signed::seal(&signing_key, payload); // Ed25519 with dalek is deterministic (RFC 8032) assert_eq!(signed1, signed2, "same input must produce same signature"); }); } #[test] fn different_payloads_different_signatures() { check!() .with_type::<([u8; 32], Vec, Vec)>() .filter(|(_, a, b)| a != b) .for_each(|(seed, payload_a, payload_b)| { let signing_key = signing_key_from_seed(*seed); let signed_a: Signed, Ed25519, Identity> = Signed::seal(&signing_key, payload_a); let signed_b: Signed, Ed25519, Identity> = Signed::seal(&signing_key, payload_b); assert_ne!( signed_a.signature(), signed_b.signature(), "different payloads should produce different signatures" ); }); } #[test] fn wrong_key_fails_verification() { check!() .with_type::<([u8; 32], [u8; 32], Vec)>() .filter(|(a, b, _)| a != b) .for_each(|(seed_signer, seed_wrong, payload)| { let signing_key = signing_key_from_seed(*seed_signer); let wrong_key = signing_key_from_seed(*seed_wrong); let signed: Signed, Ed25519, Identity> = Signed::seal(&signing_key, payload); let tampered: Signed, Ed25519, Identity> = Signed::from_unchecked_parts( wrong_key.verifying_key(), *signed.signature(), signed.encoded_payload().to_vec(), ); assert!( tampered.try_verify().is_err(), "verification must fail with wrong key" ); }); } #[test] #[allow(clippy::indexing_slicing)] // payload is non-empty (filter guarantees) fn tampered_payload_fails_verification() { check!() .with_type::<([u8; 32], Vec)>() .cloned() .filter(|(_, payload)| !payload.is_empty()) .for_each(|(seed, payload)| { let signing_key = signing_key_from_seed(seed); let signed: Signed, Ed25519, Identity> = Signed::seal(&signing_key, &payload); let mut tampered_bytes = signed.encoded_payload().to_vec(); tampered_bytes[0] = tampered_bytes[0].wrapping_add(1); let tampered: Signed, Ed25519, Identity> = Signed::from_unchecked_parts(*signed.issuer(), *signed.signature(), tampered_bytes); assert!( tampered.try_verify().is_err(), "verification must fail with tampered payload" ); }); } #[test] #[allow(clippy::expect_used)] // test should panic if verification fails fn verified_into_parts() { check!() .with_type::<([u8; 32], Vec)>() .for_each(|(seed, payload)| { let signing_key = signing_key_from_seed(*seed); let signed: Signed, Ed25519, Identity> = Signed::seal(&signing_key, payload); let verified = signed.try_verify().expect("verification should succeed"); let expected_issuer = signing_key.verifying_key(); let (recovered_signed, recovered_payload) = verified.into_parts(); assert_eq!( recovered_signed.issuer(), &expected_issuer, "issuer must match" ); assert_eq!(&recovered_signed, &signed, "signed envelope must match"); assert_eq!(&recovered_payload, payload, "payload must match"); }); } #[test] fn verified_unchecked_roundtrip() { check!() .with_type::<([u8; 32], Vec)>() .for_each(|(seed, payload)| { let signing_key = signing_key_from_seed(*seed); let signed: Signed, Ed25519, Identity> = Signed::seal(&signing_key, payload); let verified: Verified, Ed25519, Identity> = Verified::from_unchecked_parts(signed.clone(), payload.clone()); assert_eq!(verified.issuer(), &signing_key.verifying_key()); assert_eq!(verified.payload(), payload); assert_eq!(verified.signed(), &signed); }); } #[test] #[allow(clippy::expect_used)] // test should panic if verification fails fn verified_signed_back_ref() { check!() .with_type::<([u8; 32], Vec)>() .for_each(|(seed, payload)| { let signing_key = signing_key_from_seed(*seed); let signed: Signed, Ed25519, Identity> = Signed::seal(&signing_key, payload); let verified = signed.try_verify().expect("verification should succeed"); assert_eq!( verified.signed(), &signed, "verified must retain the original signed envelope" ); assert_eq!( verified.signed().encoded_payload(), signed.encoded_payload(), "encoded payload bytes must be preserved" ); assert_eq!( verified.signed().signature(), signed.signature(), "signature must be preserved" ); }); } #[test] #[allow(clippy::expect_used)] // test should panic if verification fails fn into_verified_roundtrip() { check!() .with_type::<([u8; 32], Vec)>() .for_each(|(seed, payload)| { let signing_key = signing_key_from_seed(*seed); let signed: Signed, Ed25519, Identity> = Signed::seal(&signing_key, payload); let signed_clone = signed.clone(); let verified = signed.into_verified().expect("verification should succeed"); assert_eq!( verified.payload(), payload, "verified payload must match original" ); assert_eq!( verified.signed(), &signed_clone, "signed envelope must be preserved" ); }); } #[test] fn seal_verified_roundtrip() { check!() .with_type::<([u8; 32], Vec)>() .cloned() .for_each(|(seed, payload)| { let signing_key = signing_key_from_seed(seed); let verified: Verified, Ed25519, Identity> = Signed::seal_verified(&signing_key, payload.clone()); assert_eq!( verified.payload(), &payload, "verified payload must match original" ); assert_eq!( verified.issuer(), &signing_key.verifying_key(), "issuer must match signer's public key" ); }); } #[test] #[allow(clippy::expect_used)] // test should panic if verification fails fn seal_verified_matches_seal_then_verify() { check!() .with_type::<([u8; 32], Vec)>() .cloned() .for_each(|(seed, payload)| { let signing_key = signing_key_from_seed(seed); // The two-step path let signed: Signed, Ed25519, Identity> = Signed::seal(&signing_key, &payload); let two_step = signed.try_verify().expect("verification should succeed"); // The one-step path let one_step: Verified, Ed25519, Identity> = Signed::seal_verified(&signing_key, payload.clone()); assert_eq!( two_step.payload(), one_step.payload(), "payloads must match" ); assert_eq!( two_step.signed(), one_step.signed(), "signed envelopes must be identical" ); }); } #[test] #[allow(clippy::expect_used)] // test should panic if verification fails fn seal_verified_envelope_verifies_independently() { check!() .with_type::<([u8; 32], Vec)>() .cloned() .for_each(|(seed, payload)| { let signing_key = signing_key_from_seed(seed); let verified: Verified, Ed25519, Identity> = Signed::seal_verified(&signing_key, payload); let re_verified = verified .signed() .try_verify() .expect("re-verification should succeed"); assert_eq!( re_verified.payload(), verified.payload(), "re-verified payload must match" ); }); }