duniter_documents/blockchain/
mod.rs

1//  Copyright (C) 2018  The Duniter Project Developers.
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//! Provide wrappers around Duniter documents and events.
17
18use std::fmt::Debug;
19
20use duniter_crypto::keys::{PrivateKey, PublicKey};
21
22pub mod v10;
23
24/// List of blockchain protocol versions.
25#[derive(Debug, Clone)]
26pub enum BlockchainProtocol {
27    /// Version 10.
28    V10(Box<v10::documents::V10Document>),
29    /// Version 11. (not done yet, but defined for tests)
30    V11(),
31}
32
33/// trait providing commun methods for any documents of any protocol version.
34///
35/// # Design choice
36///
37/// Allow only ed25519 for protocol 10 and many differents
38/// schemes for protocol 11 through a proxy type.
39pub trait Document: Debug + Clone {
40    /// Type of the `PublicKey` used by the document.
41    type PublicKey: PublicKey;
42    /// Data type of the currency code used by the document.
43    type CurrencyType: ?Sized;
44
45    /// Get document version.
46    fn version(&self) -> u16;
47
48    /// Get document currency.
49    fn currency(&self) -> &Self::CurrencyType;
50
51    /// Iterate over document issuers.
52    fn issuers(&self) -> &Vec<Self::PublicKey>;
53
54    /// Iterate over document signatures.
55    fn signatures(&self) -> &Vec<<Self::PublicKey as PublicKey>::Signature>;
56
57    /// Get document as bytes for signature verification.
58    fn as_bytes(&self) -> &[u8];
59
60    /// Verify signatures of document content (as text format)
61    fn verify_signatures(&self) -> VerificationResult {
62        let issuers_count = self.issuers().len();
63        let signatures_count = self.signatures().len();
64
65        if issuers_count != signatures_count {
66            VerificationResult::IncompletePairs(issuers_count, signatures_count)
67        } else {
68            let issuers = self.issuers();
69            let signatures = self.signatures();
70            let mismatches: Vec<_> = issuers
71                .iter()
72                .zip(signatures)
73                .enumerate()
74                .filter(|&(_, (key, signature))| !key.verify(self.as_bytes(), signature))
75                .map(|(i, _)| i)
76                .collect();
77
78            if mismatches.is_empty() {
79                VerificationResult::Valid()
80            } else {
81                VerificationResult::Invalid(mismatches)
82            }
83        }
84    }
85}
86
87/// List of possible results for signature verification.
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub enum VerificationResult {
90    /// All signatures are valid.
91    Valid(),
92    /// Not same amount of issuers and signatures.
93    /// (issuers count, signatures count)
94    IncompletePairs(usize, usize),
95    /// Signatures don't match.
96    /// List of mismatching pairs indexes.
97    Invalid(Vec<usize>),
98}
99
100/// Trait allowing access to the document through it's proper protocol version.
101///
102/// This trait is generic over `P` providing all supported protocol version variants.
103///
104/// A lifetime is specified to allow enum variants to hold references to the document.
105pub trait IntoSpecializedDocument<P> {
106    /// Get a protocol-specific document wrapped in an enum variant.
107    fn into_specialized(self) -> P;
108}
109
110/// Trait helper for building new documents.
111pub trait DocumentBuilder {
112    /// Type of the builded document.
113    type Document: Document;
114
115    /// Type of the private keys signing the documents.
116    type PrivateKey: PrivateKey<
117        Signature = <<Self::Document as Document>::PublicKey as PublicKey>::Signature,
118    >;
119
120    /// Build a document with provided signatures.
121    fn build_with_signature(
122        &self,
123        signatures: Vec<<<Self::Document as Document>::PublicKey as PublicKey>::Signature>,
124    ) -> Self::Document;
125
126    /// Build a document and sign it with the private key.
127    fn build_and_sign(&self, private_keys: Vec<Self::PrivateKey>) -> Self::Document;
128}
129
130/// Trait for a document parser from a `S` source
131/// format to a `D` document. Will return the
132/// parsed document or an `E` error.
133pub trait DocumentParser<S, D, E> {
134    /// Parse a source and return a document or an error.
135    fn parse(source: S) -> Result<D, E>;
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use duniter_crypto::keys::{Signature, ed25519};
142
143    // simple text document for signature testing
144    #[derive(Debug, Clone)]
145    struct PlainTextDocument {
146        pub text: &'static str,
147        pub issuers: Vec<ed25519::PublicKey>,
148        pub signatures: Vec<ed25519::Signature>,
149    }
150
151    impl Document for PlainTextDocument {
152        type PublicKey = ed25519::PublicKey;
153        type CurrencyType = str;
154
155        fn version(&self) -> u16 {
156            unimplemented!()
157        }
158
159        fn currency(&self) -> &str {
160            unimplemented!()
161        }
162
163        fn issuers(&self) -> &Vec<ed25519::PublicKey> {
164            &self.issuers
165        }
166
167        fn signatures(&self) -> &Vec<ed25519::Signature> {
168            &self.signatures
169        }
170
171        fn as_bytes(&self) -> &[u8] {
172            self.text.as_bytes()
173        }
174    }
175
176    #[test]
177    fn verify_signatures() {
178        let text = "Version: 10
179Type: Identity
180Currency: duniter_unit_test_currency
181Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
182UniqueID: tic
183Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
184";
185
186        // good pair
187        let issuer1 = ed25519::PublicKey::from_base58(
188            "DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV",
189        ).unwrap();
190
191        let sig1 = ed25519::Signature::from_base64(
192            "1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FGMM\
193             mQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==",
194        ).unwrap();
195
196        // incorrect pair
197        let issuer2 = ed25519::PublicKey::from_base58(
198            "DNann1Lh55eZMEDXeYt32bzHbA3NJR46DeQYCS2qQdLV",
199        ).unwrap();
200
201        let sig2 = ed25519::Signature::from_base64(
202            "1eubHHbuNfilHHH0G2bI30iZzebQ2cQ1PC7uPAw08FGMM\
203             mQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==",
204        ).unwrap();
205
206        {
207            let doc = PlainTextDocument {
208                text,
209                issuers: vec![issuer1],
210                signatures: vec![sig1],
211            };
212
213            assert_eq!(doc.verify_signatures(), VerificationResult::Valid());
214        }
215
216        {
217            let doc = PlainTextDocument {
218                text,
219                issuers: vec![issuer1],
220                signatures: vec![sig2],
221            };
222
223            assert_eq!(
224                doc.verify_signatures(),
225                VerificationResult::Invalid(vec![0])
226            );
227        }
228
229        {
230            let doc = PlainTextDocument {
231                text,
232                issuers: vec![issuer1, issuer2],
233                signatures: vec![sig1],
234            };
235
236            assert_eq!(
237                doc.verify_signatures(),
238                VerificationResult::IncompletePairs(2, 1)
239            );
240        }
241
242        {
243            let doc = PlainTextDocument {
244                text,
245                issuers: vec![issuer1],
246                signatures: vec![sig1, sig2],
247            };
248
249            assert_eq!(
250                doc.verify_signatures(),
251                VerificationResult::IncompletePairs(1, 2)
252            );
253        }
254    }
255}