#![deny(
unsafe_code,
dead_code,
missing_docs,
unused_variables,
unused_mut,
unused_imports,
non_upper_case_globals,
non_camel_case_types,
non_snake_case
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
#![cfg_attr(not(feature = "strict_encoding"), no_std)]
#[cfg(feature = "strict_encoding")]
#[macro_use]
extern crate strict_encoding;
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
use core::borrow::Borrow;
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Debug, Display, Formatter};
use core::marker::PhantomData;
#[cfg(feature = "strict_encoding")]
use strict_encoding::{StrictDecode, StrictDumb, StrictEncode};
#[cfg(not(feature = "strict_encoding"))]
trait StrictDumb {}
#[cfg(not(feature = "strict_encoding"))]
impl<T> StrictDumb for T {}
#[cfg(not(feature = "strict_encoding"))]
trait StrictEncode {}
#[cfg(not(feature = "strict_encoding"))]
impl<T> StrictEncode for T {}
#[cfg(not(feature = "strict_encoding"))]
trait StrictDecode {}
#[cfg(not(feature = "strict_encoding"))]
impl<T> StrictDecode for T {}
pub const LIB_NAME_SEALS: &str = "SingleUseSeals";
pub trait SingleUseSeal:
Clone + Debug + Display + StrictDumb + StrictEncode + StrictDecode
{
type Message: Copy + Eq;
type PubWitness: PublishedWitness<Self> + StrictDumb + StrictEncode + StrictDecode;
type CliWitness: ClientSideWitness<Seal = Self> + StrictDumb + StrictEncode + StrictDecode;
fn is_included(&self, message: Self::Message, witness: &SealWitness<Self>) -> bool;
}
pub trait ClientSideWitness: Eq {
type Seal: SingleUseSeal;
type Proof;
type Error: Clone + Error;
fn convolve_commit(
&self,
msg: <Self::Seal as SingleUseSeal>::Message,
) -> Result<Self::Proof, Self::Error>;
fn merge(&mut self, other: Self) -> Result<(), impl Error>
where Self: Sized;
}
#[derive(Copy, Clone, Debug)]
pub struct NoClientWitness<Seal: SingleUseSeal>(PhantomData<Seal>);
impl<Seal: SingleUseSeal> PartialEq for NoClientWitness<Seal> {
fn eq(&self, _: &Self) -> bool { true }
}
impl<Seal: SingleUseSeal> Eq for NoClientWitness<Seal> {}
impl<Seal: SingleUseSeal> ClientSideWitness for NoClientWitness<Seal> {
type Seal = Seal;
type Proof = Seal::Message;
type Error = Infallible;
fn convolve_commit(&self, msg: Seal::Message) -> Result<Self::Proof, Self::Error> { Ok(msg) }
fn merge(&mut self, _: Self) -> Result<(), impl Error>
where Self: Sized {
Ok::<_, Infallible>(())
}
}
impl<Seal: SingleUseSeal> NoClientWitness<Seal> {
pub fn new() -> Self { Self(PhantomData) }
}
impl<Seal: SingleUseSeal> Default for NoClientWitness<Seal> {
fn default() -> Self { Self::new() }
}
#[cfg(feature = "strict_encoding")]
mod _strict_encoding_impls {
use strict_encoding::{
DecodeError, StrictProduct, StrictTuple, StrictType, TypedRead, TypedWrite,
};
use super::*;
impl<Seal: SingleUseSeal> StrictType for NoClientWitness<Seal> {
const STRICT_LIB_NAME: &'static str = LIB_NAME_SEALS;
}
impl<Seal: SingleUseSeal> StrictProduct for NoClientWitness<Seal> {}
impl<Seal: SingleUseSeal> StrictTuple for NoClientWitness<Seal> {
const FIELD_COUNT: u8 = 0;
}
impl<Seal: SingleUseSeal> StrictEncode for NoClientWitness<Seal> {
fn strict_encode<W: TypedWrite>(&self, writer: W) -> std::io::Result<W> { Ok(writer) }
}
impl<Seal: SingleUseSeal> StrictDecode for NoClientWitness<Seal> {
fn strict_decode(_reader: &mut impl TypedRead) -> Result<Self, DecodeError> {
Ok(NoClientWitness::new())
}
}
}
pub trait PublishedWitness<Seal: SingleUseSeal> {
type PubId: Copy + Ord + Debug + Display;
type Error: Clone + Error;
fn pub_id(&self) -> Self::PubId;
fn verify_commitment(
&self,
proof: <Seal::CliWitness as ClientSideWitness>::Proof,
) -> Result<(), Self::Error>;
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(
feature = "strict_encoding",
derive(StrictType, StrictDumb, StrictEncode, StrictDecode),
strict_type(lib = LIB_NAME_SEALS)
)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(bound = "Seal::PubWitness: serde::Serialize + for<'d> serde::Deserialize<'d>, \
Seal::CliWitness: serde::Serialize + for<'d> serde::Deserialize<'d>")
)]
pub struct SealWitness<Seal>
where Seal: SingleUseSeal
{
pub published: Seal::PubWitness,
pub client: Seal::CliWitness,
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "strict_encoding", strict_type(skip))]
_phantom: PhantomData<Seal>,
}
impl<Seal> SealWitness<Seal>
where Seal: SingleUseSeal
{
pub fn new(published: Seal::PubWitness, client: Seal::CliWitness) -> Self {
Self {
published,
client,
_phantom: PhantomData,
}
}
pub fn verify_seal_closing(
&self,
seal: impl Borrow<Seal>,
message: Seal::Message,
) -> Result<(), SealError<Seal>> {
self.verify_seals_closing([seal], message)
}
pub fn verify_seals_closing(
&self,
seals: impl IntoIterator<Item = impl Borrow<Seal>>,
message: Seal::Message,
) -> Result<(), SealError<Seal>> {
for seal in seals {
seal.borrow()
.is_included(message, self)
.then_some(())
.ok_or(SealError::NotIncluded(seal.borrow().clone(), self.published.pub_id()))?;
}
let f_msg = self
.client
.convolve_commit(message)
.map_err(SealError::Client)?;
self.published
.verify_commitment(f_msg)
.map_err(SealError::Published)
}
}
#[derive(Clone)]
pub enum SealError<Seal: SingleUseSeal> {
NotIncluded(Seal, <Seal::PubWitness as PublishedWitness<Seal>>::PubId),
Published(<Seal::PubWitness as PublishedWitness<Seal>>::Error),
Client(<Seal::CliWitness as ClientSideWitness>::Error),
}
impl<Seal: SingleUseSeal> Debug for SealError<Seal> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
SealError::NotIncluded(seal, pub_id) => f
.debug_tuple("SealError::NotIncluded")
.field(seal)
.field(pub_id)
.finish(),
SealError::Published(err) => f.debug_tuple("SealError::Published").field(err).finish(),
SealError::Client(err) => f.debug_tuple("SealError::Client(err").field(err).finish(),
}
}
}
impl<Seal: SingleUseSeal> Display for SealError<Seal> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
SealError::NotIncluded(seal, pub_id) => {
write!(f, "seal {seal} is not included in the public witness {pub_id}")
}
SealError::Published(err) => Display::fmt(err, f),
SealError::Client(err) => Display::fmt(err, f),
}
}
}
impl<Seal: SingleUseSeal> Error for SealError<Seal>
where
<<Seal as SingleUseSeal>::PubWitness as PublishedWitness<Seal>>::Error: 'static,
<<Seal as SingleUseSeal>::CliWitness as ClientSideWitness>::Error: 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
SealError::NotIncluded(..) => None,
SealError::Published(e) => Some(e),
SealError::Client(e) => Some(e),
}
}
}
#[cfg(test)]
mod tests {
#![cfg_attr(coverage_nightly, coverage(off))]
use super::*;
const LIB_NAME: &str = "SingleUseSealTests";
type DumbMessage = [u8; 16];
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME)]
struct DumbSeal(u8);
impl Display for DumbSeal {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("DumbSeal") }
}
impl SingleUseSeal for DumbSeal {
type Message = DumbMessage;
type PubWitness = DumbPubWitness;
type CliWitness = NoClientWitness<DumbSeal>;
fn is_included(&self, _message: Self::Message, witness: &SealWitness<Self>) -> bool {
witness.published.1 == *self
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME)]
struct DumbPubWitness<Seal: SingleUseSeal + Default = DumbSeal>(DumbMessage, Seal);
impl PublishedWitness<DumbSeal> for DumbPubWitness {
type PubId = u8;
type Error = Invalid;
fn pub_id(&self) -> Self::PubId { self.1 .0 }
fn verify_commitment(&self, proof: DumbMessage) -> Result<(), Self::Error> {
(self.0 == proof).then_some(()).ok_or(Invalid)
}
}
impl PublishedWitness<FailingSeal> for DumbPubWitness {
type PubId = u8;
type Error = Invalid;
fn pub_id(&self) -> Self::PubId { self.1 .0 }
fn verify_commitment(&self, proof: DumbMessage) -> Result<(), Self::Error> {
(self.0 == proof).then_some(()).ok_or(Invalid)
}
}
impl PublishedWitness<FailingSeal> for DumbPubWitness<FailingSeal> {
type PubId = u8;
type Error = Invalid;
fn pub_id(&self) -> Self::PubId { self.1 .0 }
fn verify_commitment(&self, proof: DumbMessage) -> Result<(), Self::Error> {
(self.0 == proof).then_some(()).ok_or(Invalid)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME)]
struct FailingSeal(u8);
impl Display for FailingSeal {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("DumbSeal") }
}
impl SingleUseSeal for FailingSeal {
type Message = DumbMessage;
type PubWitness = DumbPubWitness<FailingSeal>;
type CliWitness = FailingCliWitness;
fn is_included(&self, _message: Self::Message, _witness: &SealWitness<Self>) -> bool {
true
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME)]
struct FailingCliWitness();
impl ClientSideWitness for FailingCliWitness {
type Seal = FailingSeal;
type Proof = DumbMessage;
type Error = Invalid;
fn convolve_commit(
&self,
_msg: <Self::Seal as SingleUseSeal>::Message,
) -> Result<Self::Proof, Self::Error> {
Err(Invalid)
}
fn merge(&mut self, _other: Self) -> Result<(), impl Error>
where Self: Sized {
Result::<_, Infallible>::Ok(())
}
}
#[derive(Copy, Clone, Debug)]
struct Invalid;
impl Display for Invalid {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("invalid seal") }
}
impl Error for Invalid {}
#[test]
fn verify_no_client_witness() {
let mut a = NoClientWitness::default();
let b = NoClientWitness::<DumbSeal>::new();
assert_eq!(a, b);
a.merge(b).unwrap();
}
#[test]
fn verify_success() {
let seal = DumbSeal(0xCA);
let message = [
0xDEu8, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xFE, 0xED, 0xBE, 0xD0, 0xBA, 0xD1,
0xDA, 0xFE,
];
let witness = SealWitness::new(DumbPubWitness(message, seal), NoClientWitness::default());
witness
.verify_seal_closing(seal, message)
.inspect_err(|e| eprintln!("{e}"))
.unwrap()
}
#[test]
fn verify_not_included() {
let seal = DumbSeal(0xCA);
let fake_seal = DumbSeal(0x00);
let message = [
0xDEu8, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xFE, 0xED, 0xBE, 0xD0, 0xBA, 0xD1,
0xDA, 0xFE,
];
let witness =
SealWitness::new(DumbPubWitness(message, fake_seal), NoClientWitness::default());
let res = witness.verify_seal_closing(seal, message).unwrap_err();
println!("{res}");
println!("{res:?}");
assert!(matches!(
res,
SealError::NotIncluded(s, 0x00) if s == seal
));
}
#[test]
fn verify_fake_message() {
let seal = DumbSeal(0xCA);
let message = [
0xDEu8, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xFE, 0xED, 0xBE, 0xD0, 0xBA, 0xD1,
0xDA, 0xFE,
];
let mut fake_message = message;
fake_message[0] = 0x00;
let witness = SealWitness::new(DumbPubWitness(message, seal), NoClientWitness::default());
let res = witness.verify_seal_closing(seal, fake_message).unwrap_err();
println!("{res}");
println!("{res:?}");
assert!(matches!(res, SealError::Published(Invalid)));
}
#[test]
fn verify_failing_client_witness() {
let seal = FailingSeal(0xCA);
let message = [
0xDEu8, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xFE, 0xED, 0xBE, 0xD0, 0xBA, 0xD1,
0xDA, 0xFE,
];
let witness = SealWitness::new(DumbPubWitness(message, seal), FailingCliWitness::default());
let res = witness.verify_seal_closing(seal, message).unwrap_err();
println!("{res}");
println!("{res:?}");
assert!(matches!(res, SealError::Client(Invalid)));
}
}