dubp_documents/
lib.rs

1//  Copyright (C) 2020  Éloïs SANCHEZ.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Define DUBP Documents.
17
18#![allow(clippy::upper_case_acronyms)]
19#![deny(
20    clippy::expect_used,
21    clippy::unwrap_used,
22    missing_debug_implementations,
23    missing_copy_implementations,
24    trivial_casts,
25    trivial_numeric_casts,
26    unsafe_code,
27    unstable_features,
28    unused_import_braces
29)]
30
31pub mod certification;
32pub mod identity;
33pub mod membership;
34pub mod revocation;
35mod traits;
36pub mod transaction;
37
38// Re-export crates
39pub use dubp_wallet;
40pub use dubp_wallet::dubp_common;
41pub use dubp_wallet::smallvec;
42
43// prelude
44pub mod prelude {
45    pub use crate::traits::{
46        text::{CompactTextDocument, TextDocument, TextDocumentBuilder, TextDocumentFormat},
47        Document, DocumentBuilder, ToJsonObject, ToStringObject,
48    };
49    pub use crate::{DubpDocument, DubpDocumentStr};
50}
51
52// Crate imports
53pub(crate) use crate::prelude::*;
54pub(crate) use crate::transaction::{
55    TransactionDocumentTrait, TransactionSignErr, UTXOConditions, UnsignedTransactionDocumentTrait,
56};
57pub(crate) use beef::lean::Cow as BeefCow;
58pub(crate) use dubp_common::crypto::bases::b58::ToBase58;
59pub(crate) use dubp_common::crypto::hashs::Hash;
60pub(crate) use dubp_common::crypto::keys::*;
61pub(crate) use dubp_common::prelude::*;
62pub(crate) use dubp_wallet::prelude::*;
63pub(crate) use serde::{Deserialize, Serialize};
64pub(crate) use smallvec::{smallvec as svec, SmallVec, ToSmallVec};
65pub(crate) use std::{
66    borrow::Cow,
67    collections::{BTreeMap, BTreeSet, HashMap},
68    fmt::Debug,
69};
70
71/// Signed or unsigned document
72#[derive(Debug)]
73pub enum SignedOrUnsignedDocument<S, U> {
74    Signed(S),
75    Unsigned(U),
76}
77
78/// User document of DUBP (DUniter Blockhain Protocol)
79#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
80pub enum DubpDocument {
81    /// Transaction document.
82    Transaction(transaction::TransactionDocument),
83
84    /// Identity document.
85    Identity(identity::IdentityDocument),
86
87    /// Membership document.
88    Membership(membership::MembershipDocument),
89
90    /// Certification document.
91    Certification(certification::CertificationDocument),
92
93    /// Revocation document.
94    Revocation(revocation::RevocationDocument),
95}
96
97/// List of stringified user document types.
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub enum DubpDocumentStr {
100    /// Transaction document.
101    Transaction(Box<transaction::TransactionDocumentStringified>),
102
103    /// Identity document.
104    Identity(identity::IdentityDocumentStringified),
105
106    /// Membership document.
107    Membership(membership::MembershipDocumentStringified),
108
109    /// Certification document.
110    Certification(Box<certification::CertificationDocumentStringified>),
111
112    /// Revocation document.
113    Revocation(Box<revocation::RevocationDocumentStringified>),
114}
115
116impl ToStringObject for DubpDocument {
117    type StringObject = DubpDocumentStr;
118
119    fn to_string_object(&self) -> Self::StringObject {
120        match *self {
121            DubpDocument::Identity(ref doc) => DubpDocumentStr::Identity(doc.to_string_object()),
122            DubpDocument::Membership(ref doc) => {
123                DubpDocumentStr::Membership(doc.to_string_object())
124            }
125            DubpDocument::Certification(ref doc) => {
126                DubpDocumentStr::Certification(Box::new(doc.to_string_object()))
127            }
128            DubpDocument::Revocation(ref doc) => {
129                DubpDocumentStr::Revocation(Box::new(doc.to_string_object()))
130            }
131            DubpDocument::Transaction(ref doc) => {
132                DubpDocumentStr::Transaction(Box::new(doc.to_string_object()))
133            }
134        }
135    }
136}
137
138macro_rules! dubp_document_fn {
139    ($fn_name:ident, $return_type:ty) => {
140        fn $fn_name(&self) -> $return_type {
141            match self {
142                Self::Certification(doc) => doc.$fn_name(),
143                Self::Identity(doc) => doc.$fn_name(),
144                Self::Membership(doc) => doc.$fn_name(),
145                Self::Revocation(doc) => doc.$fn_name(),
146                Self::Transaction(doc) => doc.$fn_name(),
147            }
148        }
149    };
150}
151
152impl Document for DubpDocument {
153    type PublicKey = PubKeyEnum;
154
155    dubp_document_fn!(as_bytes, BeefCow<[u8]>);
156    dubp_document_fn!(blockstamp, Blockstamp);
157    dubp_document_fn!(currency, &str);
158    dubp_document_fn!(issuers, SmallVec<[Self::PublicKey; 1]>);
159    dubp_document_fn!(
160        signatures,
161        SmallVec<[<Self::PublicKey as PublicKey>::Signature; 1]>
162    );
163    dubp_document_fn!(version, usize);
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    //use pretty_assertions::assert_eq;
170    use unwrap::unwrap;
171
172    // simple text document for signature testing
173    #[derive(Debug, Clone, PartialEq, Eq)]
174    struct PlainTextDocument {
175        pub text: &'static str,
176        pub issuers: SmallVec<[PubKeyEnum; 1]>,
177        pub signatures: SmallVec<[Sig; 1]>,
178    }
179
180    impl Document for PlainTextDocument {
181        type PublicKey = PubKeyEnum;
182
183        fn version(&self) -> usize {
184            unimplemented!()
185        }
186
187        fn currency(&self) -> &str {
188            unimplemented!()
189        }
190
191        fn blockstamp(&self) -> Blockstamp {
192            unimplemented!()
193        }
194
195        fn issuers(&self) -> SmallVec<[Self::PublicKey; 1]> {
196            self.issuers.iter().copied().collect()
197        }
198
199        fn signatures(&self) -> SmallVec<[<Self::PublicKey as PublicKey>::Signature; 1]> {
200            self.signatures.iter().copied().collect()
201        }
202
203        fn as_bytes(&self) -> BeefCow<[u8]> {
204            BeefCow::borrowed(self.text.as_bytes())
205        }
206    }
207
208    #[test]
209    fn verify_signatures() {
210        let text = "Version: 10
211Type: Identity
212Currency: duniter_unit_test_currency
213Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
214UniqueID: tic
215Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
216";
217
218        // good pair
219        let issuer1 = PubKeyEnum::Ed25519(unwrap!(
220            ed25519::PublicKey::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV"),
221            "Fail to parse PublicKey from base58"
222        ));
223
224        let sig1 = Sig::Ed25519(unwrap!(
225            ed25519::Signature::from_base64(
226                "1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FGMM\
227                 mQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==",
228            ),
229            "Fail to parse Signature from base64"
230        ));
231
232        // incorrect pair
233        let issuer2 = PubKeyEnum::Ed25519(unwrap!(
234            ed25519::PublicKey::from_base58("DNann1Lh55eZMEDXeYt32bzHbA3NJR46DeQYCS2qQdLV"),
235            "Fail to parse PublicKey from base58"
236        ));
237
238        let sig2 = Sig::Ed25519(unwrap!(
239            ed25519::Signature::from_base64(
240                "1eubHHbuNfilHHH0G2bI30iZzebQ2cQ1PC7uPAw08FGMM\
241                 mQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==",
242            ),
243            "Fail to parse Signature from base64"
244        ));
245
246        {
247            let doc = PlainTextDocument {
248                text,
249                issuers: svec![issuer1],
250                signatures: svec![sig1],
251            };
252
253            if let Err(e) = doc.verify_signatures() {
254                panic!("DocumentSigsErr: {:?}", e)
255            }
256        }
257
258        {
259            let doc = PlainTextDocument {
260                text,
261                issuers: svec![issuer1],
262                signatures: svec![sig2],
263            };
264            assert_eq!(
265                doc.verify_signatures(),
266                Err(DocumentSigsErr::Invalid(
267                    maplit::hashmap![0 => SigError::InvalidSig]
268                ))
269            );
270        }
271
272        {
273            let doc = PlainTextDocument {
274                text,
275                issuers: svec![issuer1, issuer2],
276                signatures: svec![sig1],
277            };
278
279            assert_eq!(
280                doc.verify_signatures(),
281                Err(DocumentSigsErr::IncompletePairs(2, 1))
282            );
283        }
284
285        {
286            let doc = PlainTextDocument {
287                text,
288                issuers: svec![issuer1],
289                signatures: svec![sig1, sig2],
290            };
291
292            assert_eq!(
293                doc.verify_signatures(),
294                Err(DocumentSigsErr::IncompletePairs(1, 2))
295            );
296        }
297    }
298}