evidence 0.1.0

Type-level tags for cryptographic primitives
Documentation
//! 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<u8>)>()
        .for_each(|(seed, payload)| {
            let signing_key = signing_key_from_seed(*seed);

            let signed: Signed<Vec<u8>, 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<u8>)>()
        .for_each(|(seed, payload)| {
            let signing_key = signing_key_from_seed(*seed);

            let signed1: Signed<Vec<u8>, Ed25519, Identity> = Signed::seal(&signing_key, payload);
            let signed2: Signed<Vec<u8>, 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<u8>, Vec<u8>)>()
        .filter(|(_, a, b)| a != b)
        .for_each(|(seed, payload_a, payload_b)| {
            let signing_key = signing_key_from_seed(*seed);

            let signed_a: Signed<Vec<u8>, Ed25519, Identity> =
                Signed::seal(&signing_key, payload_a);
            let signed_b: Signed<Vec<u8>, 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<u8>)>()
        .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<Vec<u8>, Ed25519, Identity> = Signed::seal(&signing_key, payload);

            let tampered: Signed<Vec<u8>, 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<u8>)>()
        .cloned()
        .filter(|(_, payload)| !payload.is_empty())
        .for_each(|(seed, payload)| {
            let signing_key = signing_key_from_seed(seed);

            let signed: Signed<Vec<u8>, 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<Vec<u8>, 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<u8>)>()
        .for_each(|(seed, payload)| {
            let signing_key = signing_key_from_seed(*seed);

            let signed: Signed<Vec<u8>, 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<u8>)>()
        .for_each(|(seed, payload)| {
            let signing_key = signing_key_from_seed(*seed);

            let signed: Signed<Vec<u8>, Ed25519, Identity> = Signed::seal(&signing_key, payload);

            let verified: Verified<Vec<u8>, 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<u8>)>()
        .for_each(|(seed, payload)| {
            let signing_key = signing_key_from_seed(*seed);

            let signed: Signed<Vec<u8>, 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<u8>)>()
        .for_each(|(seed, payload)| {
            let signing_key = signing_key_from_seed(*seed);

            let signed: Signed<Vec<u8>, 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<u8>)>()
        .cloned()
        .for_each(|(seed, payload)| {
            let signing_key = signing_key_from_seed(seed);

            let verified: Verified<Vec<u8>, 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<u8>)>()
        .cloned()
        .for_each(|(seed, payload)| {
            let signing_key = signing_key_from_seed(seed);

            // The two-step path
            let signed: Signed<Vec<u8>, 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<Vec<u8>, 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<u8>)>()
        .cloned()
        .for_each(|(seed, payload)| {
            let signing_key = signing_key_from_seed(seed);

            let verified: Verified<Vec<u8>, 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"
            );
        });
}