Skip to content
Snippets Groups Projects
Select Git revision
  • master
1 result

index.rst

Blame
  • mod.rs 7.58 KiB
    //  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 <https://www.gnu.org/licenses/>.
    
    //! 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)]
    pub enum BlockchainProtocol {
        /// Version 10.
        V10(Box<v10::documents::V10Document>),
        /// 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 {
        /// 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<Self::PublicKey>;
    
        /// Iterate over document signatures.
        fn signatures(&self) -> &Vec<<Self::PublicKey as PublicKey>::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<usize>),
    }
    
    /// 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<P> {
        /// 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 = <<Self::Document as Document>::PublicKey as PublicKey>::Signature,
        >;
    
        /// Build a document with provided signatures.
        fn build_with_signature(
            &self,
            signatures: Vec<<<Self::Document as Document>::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::PrivateKey>) -> 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<S, D, E> {
        /// Parse a source and return a document or an error.
        fn parse(source: S) -> Result<D, E>;
    }
    
    #[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<ed25519::PublicKey>,
            pub signatures: Vec<ed25519::Signature>,
        }
    
        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<ed25519::PublicKey> {
                &self.issuers
            }
    
            fn signatures(&self) -> &Vec<ed25519::Signature> {
                &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)
                );
            }
        }
    }