single_use_seals/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
// Client-side-validation foundation libraries.
//
// SPDX-License-Identifier: Apache-2.0
//
// Written in 2019-2024 by
//     Dr. Maxim Orlovsky <orlovsky@lnp-bp.org>
//
// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(not(feature = "strict_encoding"), no_std)]

//! # Single-use-seals
//!
//! Set of traits that allow to implement Peter's Todd **single-use seal**
//! paradigm. Information in this file partially contains extracts from Peter's
//! works listed in "Further reading" section.
//!
//! ## Single-use-seal definition
//!
//! Analogous to the real-world, physical, single-use-seals used to secure
//! shipping containers, a single-use-seal primitive is a unique object that can
//! be closed over a message exactly once. In short, a single-use-seal is an
//! abstract mechanism to prevent double-spends.
//!
//! A single-use-seal implementation supports two fundamental operations:
//! * `Close(l,m) → w` — Close seal l over message m, producing a witness `w`.
//! * `Verify(l,w,m) → bool` — Verify that the seal l was closed over message
//!   `m`.
//!
//! A single-use-seal implementation is secure if it is impossible for an
//! attacker to cause the Verify function to return true for two distinct
//! messages m1, m2, when applied to the same seal (it is acceptable, although
//! non-ideal, for there to exist multiple witnesses for the same seal/message
//! pair).
//!
//! Practical single-use-seal implementations will also obviously require some
//! way of generating new single-use-seals:
//! * `Gen(p)→l` — Generate a new seal basing on some seal definition data `p`.
//!
//! ## Terminology
//!
//! **Single-use-seal**: a commitment to commit to some (potentially unknown)
//!   message. The first commitment (i.e. single-use-seal) must be a
//!   well-defined (i.e. fully specified and unequally identifiable
//!   in some space, like in time/place or within a given formal informational
//!   system).
//! **Closing of a single-use-seal over message**: a fulfilment of the first
//!   commitment: creation of the actual commitment to some message in a form
//!   unequally defined by the seal.
//! **Witness**: data produced with closing of a single use seal which are
//!   required and sufficient for an independent party to verify that the seal
//!   was indeed closed over a given message (i.e. the commitment to the message
//!   had being created according to the seal definition).
//!
//! NB: It's important to note, that while its possible to deterministically
//!   define was a given seal closed it yet may be not possible to find out
//!   if the seal is open; i.e. seal status may be either "closed over message"
//!   or "unknown". Some specific implementations of single-use-seals may define
//!   procedure to deterministically prove that a given seal is not closed (i.e.
//!   opened), however this is not a part of the specification, and we should
//!   not rely on the existence of such possibility in all cases.
//!
//! ## Trait structure
//!
//! The module defines trait [`SealProtocol`] that can be used for
//! implementation of single-use-seals with methods for seal close and
//! verification. A type implementing this trait operates only with messages
//! (which is represented by any type that implements `AsRef<[u8]>`,i.e. can be
//! represented as a sequence of bytes) and witnesses (which is represented by
//! an associated type [`SealProtocol::Witness`]). At the same time,
//! [`SealProtocol`] can't define seals by itself.
//!
//! Seal protocol operates with a *seal medium *: a proof of publication medium
//! on which the seals are defined.
//!
//! The module provides two options of implementing such medium: synchronous
//! [`SealProtocol`] and asynchronous `SealProtocolAsync`.
//!
//! ## Sample implementation
//!
//! Examples of implementations can be found in `bp::seals` module of `bp-core`
//! crate.
//!
//! ## Further reading
//!
//! * Peter Todd. Preventing Consensus Fraud with Commitments and
//!   Single-Use-Seals.
//!   <https://petertodd.org/2016/commitments-and-single-use-seals>.
//! * Peter Todd. Scalable Semi-Trustless Asset Transfer via Single-Use-Seals
//!   and Proof-of-Publication. 1. Single-Use-Seal Definition.
//!   <https://petertodd.org/2017/scalable-single-use-seal-asset-transfer>

#[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 {}

/// Trait for proof-of-publication medium on which the seals are defined,
/// closed, verified and which can be used for convenience operations related to
/// seals:
/// * finding out the seal status
/// * publishing witness information
/// * get some identifier on the exact place of the witness publication
/// * check validity of the witness publication identifier
///
/// All these operations are medium-specific; for the same single-use-seal type
/// they may differ when are applied to different proof of publication mediums.
///
/// To read more on proof-of-publication please check
/// <https://petertodd.org/2014/setting-the-record-proof-of-publication>
pub trait SingleUseSeal:
    Clone + Debug + Display + StrictDumb + StrictEncode + StrictDecode
{
    /// Message type that is supported by the current single-use-seal.
    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 {
    /// Client-side witness is specific to just one type of single-use seals,
    /// provided as an associated type.
    type Seal: SingleUseSeal;
    /// Proof which is passed from the client-side witness to the public-side
    /// witness during single-use seal validation.
    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, Default)]
pub struct NoWitness<Seal: SingleUseSeal>(PhantomData<Seal>);

impl<Seal: SingleUseSeal> PartialEq for NoWitness<Seal> {
    fn eq(&self, _: &Self) -> bool { true }
}
impl<Seal: SingleUseSeal> Eq for NoWitness<Seal> {}

impl<Seal: SingleUseSeal> ClientSideWitness for NoWitness<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>(())
    }
}

/// Public witness can be used by multiple types of single-use seals, hence it
/// has the seal type as a generic parameter.
pub trait PublishedWitness<Seal: SingleUseSeal> {
    /// Publication id that may be used for referencing publication of
    /// witness data in the medium. By default, set `()`, so [`SingleUseSeal`]
    /// may not implement publication id and related functions.
    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>;
}

/// Seal closing witness.
#[derive(Clone, Copy)]
#[cfg_attr(
    feature = "strict_encoding",
    derive(StrictType, StrictDumb, StrictEncode, StrictDecode),
    strict_type(lib = "SingleUseSeals")
)]
#[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>> {
        // ensure that witness includes all seals
        for seal in seals {
            seal.borrow()
                .is_included(message, self)
                .then_some(())
                .ok_or(SealError::NotIncluded(seal.borrow().clone(), self.published.pub_id()))?;
        }
        // ensure that published witness contains the commitment to the
        // f(message), where `f` is defined in the client-side witness
        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 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),
        }
    }
}