// Copyright (C) 2018 The Duniter Project Developers. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . //! Provide wrappers around Duniter documents and events. use std::fmt::Debug; use duniter_crypto::keys::{PrivateKey, PublicKey}; pub mod v10; /// List of blockchain protocol versions. #[derive(Debug, Clone)] pub enum BlockchainProtocol { /// Version 10. V10(Box), /// Version 11. (not done yet, but defined for tests) V11(), } /// trait providing commun methods for any documents of any protocol version. /// /// # Design choice /// /// Allow only ed25519 for protocol 10 and many differents /// schemes for protocol 11 through a proxy type. pub trait Document: Debug + Clone { /// Type of the `PublicKey` used by the document. type PublicKey: PublicKey; /// Data type of the currency code used by the document. type CurrencyType: ?Sized; /// Get document version. fn version(&self) -> u16; /// Get document currency. fn currency(&self) -> &Self::CurrencyType; /// Iterate over document issuers. fn issuers(&self) -> &Vec; /// Iterate over document signatures. fn signatures(&self) -> &Vec<::Signature>; /// Get document as bytes for signature verification. fn as_bytes(&self) -> &[u8]; /// Verify signatures of document content (as text format) fn verify_signatures(&self) -> VerificationResult { let issuers_count = self.issuers().len(); let signatures_count = self.signatures().len(); if issuers_count != signatures_count { VerificationResult::IncompletePairs(issuers_count, signatures_count) } else { let issuers = self.issuers(); let signatures = self.signatures(); let mismatches: Vec<_> = issuers .iter() .zip(signatures) .enumerate() .filter(|&(_, (key, signature))| !key.verify(self.as_bytes(), signature)) .map(|(i, _)| i) .collect(); if mismatches.is_empty() { VerificationResult::Valid() } else { VerificationResult::Invalid(mismatches) } } } } /// List of possible results for signature verification. #[derive(Debug, Clone, PartialEq, Eq)] pub enum VerificationResult { /// All signatures are valid. Valid(), /// Not same amount of issuers and signatures. /// (issuers count, signatures count) IncompletePairs(usize, usize), /// Signatures don't match. /// List of mismatching pairs indexes. Invalid(Vec), } /// Trait allowing access to the document through it's proper protocol version. /// /// This trait is generic over `P` providing all supported protocol version variants. /// /// A lifetime is specified to allow enum variants to hold references to the document. pub trait IntoSpecializedDocument

{ /// Get a protocol-specific document wrapped in an enum variant. fn into_specialized(self) -> P; } /// Trait helper for building new documents. pub trait DocumentBuilder { /// Type of the builded document. type Document: Document; /// Type of the private keys signing the documents. type PrivateKey: PrivateKey< Signature = <::PublicKey as PublicKey>::Signature, >; /// Build a document with provided signatures. fn build_with_signature( &self, signatures: Vec<<::PublicKey as PublicKey>::Signature>, ) -> Self::Document; /// Build a document and sign it with the private key. fn build_and_sign(&self, private_keys: Vec) -> Self::Document; } /// Trait for a document parser from a `S` source /// format to a `D` document. Will return the /// parsed document or an `E` error. pub trait DocumentParser { /// Parse a source and return a document or an error. fn parse(source: S) -> Result; } #[cfg(test)] mod tests { use super::*; use duniter_crypto::keys::{Signature, ed25519}; // simple text document for signature testing #[derive(Debug, Clone)] struct PlainTextDocument { pub text: &'static str, pub issuers: Vec, pub signatures: Vec, } impl Document for PlainTextDocument { type PublicKey = ed25519::PublicKey; type CurrencyType = str; fn version(&self) -> u16 { unimplemented!() } fn currency(&self) -> &str { unimplemented!() } fn issuers(&self) -> &Vec { &self.issuers } fn signatures(&self) -> &Vec { &self.signatures } fn as_bytes(&self) -> &[u8] { self.text.as_bytes() } } #[test] fn verify_signatures() { let text = "Version: 10 Type: Identity Currency: duniter_unit_test_currency Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV UniqueID: tic Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 "; // good pair let issuer1 = ed25519::PublicKey::from_base58( "DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV", ).unwrap(); let sig1 = ed25519::Signature::from_base64( "1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FGMM\ mQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==", ).unwrap(); // incorrect pair let issuer2 = ed25519::PublicKey::from_base58( "DNann1Lh55eZMEDXeYt32bzHbA3NJR46DeQYCS2qQdLV", ).unwrap(); let sig2 = ed25519::Signature::from_base64( "1eubHHbuNfilHHH0G2bI30iZzebQ2cQ1PC7uPAw08FGMM\ mQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==", ).unwrap(); { let doc = PlainTextDocument { text, issuers: vec![issuer1], signatures: vec![sig1], }; assert_eq!(doc.verify_signatures(), VerificationResult::Valid()); } { let doc = PlainTextDocument { text, issuers: vec![issuer1], signatures: vec![sig2], }; assert_eq!( doc.verify_signatures(), VerificationResult::Invalid(vec![0]) ); } { let doc = PlainTextDocument { text, issuers: vec![issuer1, issuer2], signatures: vec![sig1], }; assert_eq!( doc.verify_signatures(), VerificationResult::IncompletePairs(2, 1) ); } { let doc = PlainTextDocument { text, issuers: vec![issuer1], signatures: vec![sig1, sig2], }; assert_eq!( doc.verify_signatures(), VerificationResult::IncompletePairs(1, 2) ); } } }