//! Signed payloads with type-level tracking. //! //! [`Signed`] represents a payload that has been signed but not yet verified. //! The payload is _intentionally inaccessible_ — you must call [`Signed::try_verify`] //! to obtain a [`Verified`](crate::verified::Verified) witness that proves //! verification succeeded. //! //! # Type Parameters //! //! - `T`: The payload type that was signed //! - `S`: The signature primitive (e.g., [`Ed25519`](crate::signature::ed25519::Ed25519)) //! - `C`: The codec used to serialize the payload before signing //! //! # Example //! //! ``` //! # #[cfg(feature = "ed25519")] //! # { //! use evidence::{codec::Identity, signature::{ed25519::Ed25519, Signer}, signed::Signed, verified::Verified}; //! //! // Create a signing key //! let signing_key = ed25519_dalek::SigningKey::from_bytes(&[1u8; 32]); //! //! // Sign some data //! let data = b"hello world"; //! let signed: Signed<[u8; 11], Ed25519, Identity> = Signed::seal(&signing_key, data); //! //! // Payload is NOT accessible here — must verify first //! // signed.payload() // <- This method doesn't exist! //! //! // Verify to get access //! let verified: Verified<[u8; 11], Ed25519, Identity> = signed.try_verify().unwrap(); //! assert_eq!(verified.payload(), data); //! # } //! ``` use alloc::vec::Vec; use core::marker::PhantomData; use future_form::FutureForm; use crate::{ codec::{Decode, Encode}, signature::{AsyncSigner, SignaturePrimitive, Signer}, verified::Verified, }; /// A signed payload that has not yet been verified. /// /// This type deliberately does _not_ provide access to the payload. /// You must call [`try_verify`](Self::try_verify) to obtain a /// [`Verified`] witness that proves verification succeeded. /// /// # Construction /// /// Use [`seal`](Self::seal) or [`seal_async`](Self::seal_async) to create /// a signed payload from a signer. Use [`seal_verified`](Self::seal_verified) /// or [`seal_verified_async`](Self::seal_verified_async) to sign and obtain /// a [`Verified`] witness directly without redundant re-verification. /// /// For deserialization from untrusted sources, import the /// [`SignedUnchecked`] extension trait. pub struct Signed { issuer: S::VerifyingKey, signature: S::Signature, encoded_payload: Vec, _marker: PhantomData (T, C)>, } impl Clone for Signed where S::VerifyingKey: Clone, S::Signature: Clone, { fn clone(&self) -> Self { Self { issuer: self.issuer.clone(), signature: self.signature.clone(), encoded_payload: self.encoded_payload.clone(), _marker: PhantomData, } } } impl PartialEq for Signed where S::VerifyingKey: PartialEq, S::Signature: PartialEq, { fn eq(&self, other: &Self) -> bool { self.issuer == other.issuer && self.signature == other.signature && self.encoded_payload == other.encoded_payload } } impl Eq for Signed where S::VerifyingKey: Eq, S::Signature: Eq, { } impl core::hash::Hash for Signed where S::VerifyingKey: core::hash::Hash, S::Signature: core::hash::Hash, { fn hash(&self, state: &mut H) { self.issuer.hash(state); self.signature.hash(state); self.encoded_payload.hash(state); } } impl core::fmt::Debug for Signed where S::VerifyingKey: core::fmt::Debug, S::Signature: core::fmt::Debug, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Signed") .field("issuer", &self.issuer) .field("signature", &self.signature) .field("encoded_payload", &self.encoded_payload) .finish() } } impl Signed { /// Create a signed payload from its components. /// /// This is `pub(crate)` — external users should use [`seal`](Self::seal) /// or the [`SignedUnchecked`] extension trait. #[must_use] pub(crate) fn new( issuer: S::VerifyingKey, signature: S::Signature, encoded_payload: Vec, ) -> Self { Self { issuer, signature, encoded_payload, _marker: PhantomData, } } /// Sign a payload using a synchronous signer. /// /// The payload is encoded using codec `C`, then signed with signer `K`. /// /// # Panics /// /// Panics if the codec fails to encode the payload. #[must_use] #[allow(clippy::expect_used)] // documented panic on encode failure pub fn seal>(signer: &K, payload: &T) -> Self where C: Encode, { let encoded = C::encode(payload).expect("encoding failed"); let signature = signer.sign(&encoded); Self::new(signer.verifying_key(), signature, encoded) } /// Sign a payload using an asynchronous signer. /// /// The payload is encoded using codec `C`, then signed with signer `K`. /// Use this for hardware security modules or remote signing services. /// /// The `F` parameter selects the [`FutureForm`] — use /// [`Sendable`](future_form::Sendable) for multi-threaded runtimes or /// [`Local`](future_form::Local) for Wasm / single-threaded executors. /// /// # Panics /// /// Panics if the codec fails to encode the payload. #[allow(clippy::expect_used)] // documented panic on encode failure pub async fn seal_async>(signer: &K, payload: &T) -> Self where C: Encode, { let encoded = C::encode(payload).expect("encoding failed"); let signature = signer.sign(&encoded).await; Self::new(signer.verifying_key(), signature, encoded) } /// Sign a payload and return a [`Verified`] witness directly. /// /// Unlike [`seal`](Self::seal) followed by [`try_verify`](Self::try_verify), /// this method skips the redundant verification and decode steps — the /// caller just signed the data, so verification is tautological, and the /// original `T` is kept without round-tripping through the codec. /// /// This is the primary constructor for locally-authored data that will /// be used immediately in verified form. /// /// # Panics /// /// Panics if the codec fails to encode the payload. #[allow(clippy::expect_used)] // documented panic on encode failure pub fn seal_verified>(signer: &K, payload: T) -> Verified where C: Encode, { let encoded = C::encode(&payload).expect("encoding failed"); let signature = signer.sign(&encoded); let envelope = Self::new(signer.verifying_key(), signature, encoded); Verified::new(envelope, payload) } /// Sign a payload asynchronously and return a [`Verified`] witness directly. /// /// Async variant of [`seal_verified`](Self::seal_verified). Use this for /// hardware security modules or remote signing services. /// /// The `F` parameter selects the [`FutureForm`] — use /// [`Sendable`](future_form::Sendable) for multi-threaded runtimes or /// [`Local`](future_form::Local) for Wasm / single-threaded executors. /// /// # Panics /// /// Panics if the codec fails to encode the payload. #[allow(clippy::expect_used)] // documented panic on encode failure pub async fn seal_verified_async>( signer: &K, payload: T, ) -> Verified where C: Encode, { let encoded = C::encode(&payload).expect("encoding failed"); let signature = signer.sign(&encoded).await; let envelope = Self::new(signer.verifying_key(), signature, encoded); Verified::new(envelope, payload) } /// Verify the signature and decode the payload. /// /// On success, returns a [`Verified`] witness that proves /// verification succeeded. The verified payload is only accessible /// through this witness type. /// /// The original `Signed` envelope is cloned into the `Verified` witness. /// Use [`into_verified`](Self::into_verified) to avoid the clone. /// /// # Errors /// /// Returns [`VerificationError::InvalidSignature`] if the signature /// does not verify against the issuer's public key. /// /// Returns [`VerificationError::DecodeError`] if the payload cannot /// be decoded using codec `C`. pub fn try_verify(&self) -> Result, VerificationError> where C: Decode, S::VerifyingKey: Clone, S::Signature: Clone, { S::verify(&self.issuer, &self.encoded_payload, &self.signature) .map_err(|_| VerificationError::InvalidSignature)?; let payload = C::decode(&self.encoded_payload).map_err(|_| VerificationError::DecodeError)?; Ok(Verified::new(self.clone(), payload)) } /// Verify the signature and decode the payload, consuming the `Signed` envelope. /// /// Like [`try_verify`](Self::try_verify), but moves `self` into the /// [`Verified`] witness instead of cloning. /// /// # Errors /// /// Returns [`VerificationError::InvalidSignature`] if the signature /// does not verify against the issuer's public key. /// /// Returns [`VerificationError::DecodeError`] if the payload cannot /// be decoded using codec `C`. pub fn into_verified(self) -> Result, VerificationError> where C: Decode, { S::verify(&self.issuer, &self.encoded_payload, &self.signature) .map_err(|_| VerificationError::InvalidSignature)?; let payload = C::decode(&self.encoded_payload).map_err(|_| VerificationError::DecodeError)?; Ok(Verified::new(self, payload)) } /// Get the issuer's public key. #[must_use] pub const fn issuer(&self) -> &S::VerifyingKey { &self.issuer } /// Get the signature. #[must_use] pub const fn signature(&self) -> &S::Signature { &self.signature } /// Get the encoded payload bytes. /// /// Note: This returns the raw encoded bytes, _not_ the decoded payload. /// The decoded payload is only accessible after verification via /// [`Verified::payload`](crate::verified::Verified::payload). #[must_use] pub fn encoded_payload(&self) -> &[u8] { &self.encoded_payload } } /// Error returned when signature verification fails. /// /// Details of _why_ verification failed are intentionally hidden /// to avoid leaking information that could aid timing attacks. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VerificationError { /// Signature did not verify against the issuer's public key. InvalidSignature, /// Payload could not be decoded. DecodeError, } impl core::fmt::Display for VerificationError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::InvalidSignature => write!(f, "invalid signature"), Self::DecodeError => write!(f, "payload decode error"), } } } /// Extension trait for constructing [`Signed`] from raw components. /// /// This trait is _intentionally_ not in the prelude. Importing it is an explicit /// acknowledgment that you are bypassing the normal signing flow. /// /// # When to use /// /// - Deserializing a signed payload from storage or network /// - Interoperating with external systems /// - Testing /// /// # Example /// /// ``` /// # #[cfg(feature = "ed25519")] /// # { /// use evidence::{codec::Identity, signature::ed25519::Ed25519, signed::{Signed, SignedUnchecked}}; /// /// // Reconstruct from deserialized components /// let issuer = ed25519_dalek::VerifyingKey::from_bytes(&[0u8; 32]).unwrap(); /// let signature = ed25519_dalek::Signature::from_bytes(&[0u8; 64]); /// let encoded = vec![1, 2, 3, 4]; /// /// let signed: Signed, Ed25519, Identity> = /// Signed::from_unchecked_parts(issuer, signature, encoded); /// # } /// ``` pub trait SignedUnchecked { /// Create a signed payload from raw components. /// /// # Safety (logical) /// /// This does not perform any verification. The caller must ensure /// the components represent a valid signed payload. fn from_unchecked_parts( issuer: S::VerifyingKey, signature: S::Signature, encoded_payload: Vec, ) -> Self; } impl SignedUnchecked for Signed { fn from_unchecked_parts( issuer: S::VerifyingKey, signature: S::Signature, encoded_payload: Vec, ) -> Self { Self::new(issuer, signature, encoded_payload) } } #[cfg(feature = "serde")] impl serde::Serialize for Signed where S::VerifyingKey: serde::Serialize, S::Signature: serde::Serialize, { fn serialize(&self, serializer: Ser) -> Result { use serde::ser::SerializeStruct; let mut state = serializer.serialize_struct("Signed", 3)?; state.serialize_field("issuer", &self.issuer)?; state.serialize_field("signature", &self.signature)?; state.serialize_field("encoded_payload", &self.encoded_payload)?; state.end() } } #[cfg(feature = "serde")] impl<'de, T, S: SignaturePrimitive, C> serde::Deserialize<'de> for Signed where S::VerifyingKey: serde::Deserialize<'de>, S::Signature: serde::Deserialize<'de>, { fn deserialize>(deserializer: D) -> Result { use serde::de::{MapAccess, Visitor}; struct SignedVisitor(PhantomData<(T, S, C)>); impl<'de, T, S: SignaturePrimitive, C> Visitor<'de> for SignedVisitor where S::VerifyingKey: serde::Deserialize<'de>, S::Signature: serde::Deserialize<'de>, { type Value = Signed; fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { formatter.write_str("struct Signed") } fn visit_map>(self, mut map: V) -> Result, V::Error> { let mut issuer = None; let mut signature = None; let mut encoded_payload = None; while let Some(key) = map.next_key::<&str>()? { match key { "issuer" => issuer = Some(map.next_value()?), "signature" => signature = Some(map.next_value()?), "encoded_payload" => encoded_payload = Some(map.next_value()?), _ => { let _: serde::de::IgnoredAny = map.next_value()?; } } } let issuer = issuer.ok_or_else(|| serde::de::Error::missing_field("issuer"))?; let signature = signature.ok_or_else(|| serde::de::Error::missing_field("signature"))?; let encoded_payload = encoded_payload .ok_or_else(|| serde::de::Error::missing_field("encoded_payload"))?; Ok(Signed::new(issuer, signature, encoded_payload)) } } const FIELDS: &[&str] = &["issuer", "signature", "encoded_payload"]; deserializer.deserialize_struct("Signed", FIELDS, SignedVisitor(PhantomData)) } } #[cfg(feature = "arbitrary")] impl<'a, T, S: SignaturePrimitive, C> arbitrary::Arbitrary<'a> for Signed where S::VerifyingKey: arbitrary::Arbitrary<'a>, S::Signature: arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let issuer = S::VerifyingKey::arbitrary(u)?; let signature = S::Signature::arbitrary(u)?; let encoded_payload = Vec::arbitrary(u)?; Ok(Self::new(issuer, signature, encoded_payload)) } } #[cfg(feature = "bolero")] impl bolero_generator::TypeGenerator for Signed where S::VerifyingKey: bolero_generator::TypeGenerator, S::Signature: bolero_generator::TypeGenerator, { fn generate(driver: &mut D) -> Option { let issuer = S::VerifyingKey::generate(driver)?; let signature = S::Signature::generate(driver)?; let encoded_payload = Vec::generate(driver)?; Some(Self::new(issuer, signature, encoded_payload)) } } #[cfg(feature = "proptest")] impl proptest::arbitrary::Arbitrary for Signed where S::VerifyingKey: proptest::arbitrary::Arbitrary + 'static, S::Signature: proptest::arbitrary::Arbitrary + 'static, { type Parameters = (); type Strategy = proptest::strategy::BoxedStrategy; fn arbitrary_with((): Self::Parameters) -> Self::Strategy { use proptest::prelude::*; ( any::(), any::(), proptest::collection::vec(any::(), 0..256), ) .prop_map(|(issuer, signature, encoded_payload)| { Self::new(issuer, signature, encoded_payload) }) .boxed() } } #[cfg(feature = "rkyv")] /// Zero-copy [`rkyv`] serialization support for [`Signed`]. pub mod archive { use super::{SignaturePrimitive, Signed}; use alloc::vec::Vec; use rkyv::{Archive, Archived, Deserialize, Serialize, rancor::Fallible}; impl Archive for Signed where S::VerifyingKey: Archive, S::Signature: Archive, { type Archived = ArchivedSigned; type Resolver = SignedResolver; fn resolve(&self, resolver: Self::Resolver, out: rkyv::Place) { let helper = SignedHelper { issuer: self.issuer.clone(), signature: self.signature.clone(), encoded_payload: self.encoded_payload.clone(), }; helper.resolve(resolver, out); } } impl Serialize for Signed where S::VerifyingKey: Serialize, S::Signature: Serialize, Ser: Fallible + rkyv::ser::Allocator + rkyv::ser::Writer + ?Sized, { fn serialize(&self, serializer: &mut Ser) -> Result { let helper = SignedHelper { issuer: self.issuer.clone(), signature: self.signature.clone(), encoded_payload: self.encoded_payload.clone(), }; helper.serialize(serializer) } } impl Deserialize, D> for ArchivedSigned where S::VerifyingKey: Archive, S::Signature: Archive, Archived: Deserialize, Archived: Deserialize, D: Fallible + ?Sized, D::Error: rkyv::rancor::Source, { fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { let helper: SignedHelper = as Deserialize< SignedHelper, D, >>::deserialize(self, deserializer)?; Ok(Signed::new( helper.issuer, helper.signature, helper.encoded_payload, )) } } /// Helper struct for rkyv serialization. /// /// The phantom type parameters from [`Signed`] are erased in the archived /// form since they only matter at compile time. #[derive(Debug, Archive, Serialize, Deserialize)] pub struct SignedHelper { issuer: VerifyingKey, signature: Signature, encoded_payload: Vec, } /// Type alias for the archived form of [`Signed`]. pub type ArchivedSigned = ArchivedSignedHelper; /// Type alias for the resolver of [`Signed`]. pub type SignedResolver = SignedHelperResolver; }