From d1f8e937d9b4681e203303b4b41b5f5938ed1aae Mon Sep 17 00:00:00 2001 From: librelois <elois@ifee.fr> Date: Fri, 30 Mar 2018 22:19:47 +0200 Subject: [PATCH] #21 add certification document --- .../blockchain/v10/documents/certification.rs | 351 ++++++++++++++++++ .../blockchain/v10/documents/membership.rs | 2 +- documents/blockchain/v10/documents/mod.rs | 5 +- 3 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 documents/blockchain/v10/documents/certification.rs diff --git a/documents/blockchain/v10/documents/certification.rs b/documents/blockchain/v10/documents/certification.rs new file mode 100644 index 00000000..68e9cc14 --- /dev/null +++ b/documents/blockchain/v10/documents/certification.rs @@ -0,0 +1,351 @@ +// 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/>. + +//! Wrappers around Certification documents. + +use duniter_crypto::keys::{PublicKey, Signature, ed25519}; +use regex::Regex; + +use Blockstamp; +use blockchain::{BlockchainProtocol, Document, DocumentBuilder, IntoSpecializedDocument}; +use blockchain::v10::documents::{StandardTextDocumentParser, TextDocument, TextDocumentBuilder, + V10Document, V10DocumentParsingError}; + +lazy_static! { + static ref CERTIFICATION_REGEX: Regex = Regex::new( + "^Issuer: (?P<issuer>[1-9A-Za-z][^OIl]{43,44})\n\ + IdtyIssuer: (?P<target>[1-9A-Za-z][^OIl]{43,44})\n\ + IdtyUniqueID: (?P<idty_uid>[[:alnum:]_-]+)\n\ + IdtyTimestamp: (?P<idty_blockstamp>[0-9]+-[0-9A-F]{64})\n\ + IdtySignature: (?P<idty_sig>(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)\n\ + CertTimestamp: (?P<blockstamp>[0-9]+-[0-9A-F]{64})\n$" + ).unwrap(); +} + +/// Wrap an Certification document. +/// +/// Must be created by parsing a text document or using a builder. +#[derive(Debug, Clone)] +pub struct CertificationDocument { + /// Document as text. + /// + /// Is used to check signatures, and other values mut be extracted from it. + text: String, + + /// Name of the currency. + currency: String, + /// Document issuer (there should be only one). + issuers: Vec<ed25519::PublicKey>, + /// issuer of target identity. + target: ed25519::PublicKey, + /// Username of target identity + identity_username: String, + /// Target Identity document blockstamp. + identity_blockstamp: Blockstamp, + /// Target Identity document signature. + identity_sig: ed25519::Signature, + /// Blockstamp + blockstamp: Blockstamp, + /// Document signature (there should be only one). + signatures: Vec<ed25519::Signature>, +} + +impl CertificationDocument { + /// Username of target identity + pub fn identity_username(&self) -> &str { + &self.identity_username + } + + /// Pubkey of source identity + pub fn source(&self) -> &ed25519::PublicKey { + &self.issuers[0] + } + + /// Pubkey of target identity + pub fn target(&self) -> &ed25519::PublicKey { + &self.target + } +} + +impl Document for CertificationDocument { + type PublicKey = ed25519::PublicKey; + type CurrencyType = str; + + fn version(&self) -> u16 { + 10 + } + + fn currency(&self) -> &str { + &self.currency + } + + fn issuers(&self) -> &Vec<ed25519::PublicKey> { + &self.issuers + } + + fn signatures(&self) -> &Vec<ed25519::Signature> { + &self.signatures + } + + fn as_bytes(&self) -> &[u8] { + self.as_text().as_bytes() + } +} + +impl TextDocument for CertificationDocument { + fn as_text(&self) -> &str { + &self.text + } +} + +impl IntoSpecializedDocument<BlockchainProtocol> for CertificationDocument { + fn into_specialized(self) -> BlockchainProtocol { + BlockchainProtocol::V10(V10Document::Certification(Box::new(self))) + } +} + +/// Certification document builder. +#[derive(Debug, Copy, Clone)] +pub struct CertificationDocumentBuilder<'a> { + /// Document currency. + pub currency: &'a str, + /// Certification issuer (=source). + pub issuer: &'a ed25519::PublicKey, + /// Reference blockstamp. + pub blockstamp: &'a Blockstamp, + /// Pubkey of target identity. + pub target: &'a ed25519::PublicKey, + /// Username of target Identity. + pub identity_username: &'a str, + /// Blockstamp of target Identity. + pub identity_blockstamp: &'a Blockstamp, + /// Signature of target Identity. + pub identity_sig: &'a ed25519::Signature, +} + +impl<'a> CertificationDocumentBuilder<'a> { + fn build_with_text_and_sigs( + self, + text: String, + signatures: Vec<ed25519::Signature>, + ) -> CertificationDocument { + CertificationDocument { + text, + currency: self.currency.to_string(), + issuers: vec![*self.issuer], + blockstamp: *self.blockstamp, + target: *self.target, + identity_username: self.identity_username.to_string(), + identity_blockstamp: *self.identity_blockstamp, + identity_sig: *self.identity_sig, + signatures, + } + } +} + +impl<'a> DocumentBuilder for CertificationDocumentBuilder<'a> { + type Document = CertificationDocument; + type PrivateKey = ed25519::PrivateKey; + + fn build_with_signature(&self, signatures: Vec<ed25519::Signature>) -> CertificationDocument { + self.build_with_text_and_sigs(self.generate_text(), signatures) + } + + fn build_and_sign(&self, private_keys: Vec<ed25519::PrivateKey>) -> CertificationDocument { + let (text, signatures) = self.build_signed_text(private_keys); + self.build_with_text_and_sigs(text, signatures) + } +} + +impl<'a> TextDocumentBuilder for CertificationDocumentBuilder<'a> { + fn generate_text(&self) -> String { + format!( + "Version: 10 +Type: Membership +Currency: {currency} +Issuer: {issuer} +IdtyIssuer: {target} +IdtyUniqueID: {idty_uid} +IdtyTimestamp: {idty_blockstamp} +IdtySignature: {idty_sig} +CertTimestamp: {blockstamp} +", + currency = self.currency, + issuer = self.issuer, + target = self.target, + idty_uid = self.identity_username, + idty_blockstamp = self.identity_blockstamp, + idty_sig = self.identity_sig, + blockstamp = self.blockstamp, + ) + } +} + +/// Certification document parser +#[derive(Debug, Clone, Copy)] +pub struct CertificationDocumentParser; + +impl StandardTextDocumentParser for CertificationDocumentParser { + fn parse_standard( + doc: &str, + body: &str, + currency: &str, + signatures: Vec<ed25519::Signature>, + ) -> Result<V10Document, V10DocumentParsingError> { + if let Some(caps) = CERTIFICATION_REGEX.captures(body) { + let issuer = &caps["issuer"]; + let target = &caps["target"]; + let identity_username = &caps["idty_uid"]; + let identity_blockstamp = &caps["idty_blockstamp"]; + let identity_sig = &caps["idty_sig"]; + let blockstamp = &caps["blockstamp"]; + + // Regex match so should not fail. + // TODO : Test it anyway + let issuer = ed25519::PublicKey::from_base58(issuer).unwrap(); + let target = ed25519::PublicKey::from_base58(target).unwrap(); + let identity_username = String::from(identity_username); + let identity_blockstamp = Blockstamp::from_string(identity_blockstamp).unwrap(); + let identity_sig = ed25519::Signature::from_base64(identity_sig).unwrap(); + let blockstamp = Blockstamp::from_string(blockstamp).unwrap(); + + Ok(V10Document::Certification(Box::new( + CertificationDocument { + text: doc.to_owned(), + issuers: vec![issuer], + currency: currency.to_owned(), + target, + identity_username, + identity_blockstamp, + identity_sig, + blockstamp, + signatures, + }, + ))) + } else { + Err(V10DocumentParsingError::InvalidInnerFormat( + "Certification".to_string(), + )) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use duniter_crypto::keys::{PrivateKey, PublicKey, Signature}; + use blockchain::VerificationResult; + + #[test] + fn generate_real_document() { + let pubkey = ed25519::PublicKey::from_base58( + "4tNQ7d9pj2Da5wUVoW9mFn7JjuPoowF977au8DdhEjVR", + ).unwrap(); + + let prikey = ed25519::PrivateKey::from_base58( + "3XGWuuU1dQ7zaYPzE76ATfY71STzRkbT3t4DE1bSjMhYje81XdJFeXVG9uMPi3oDeRTosT2dmBAFH8VydrAUWXRZ", + ).unwrap(); + + let sig = ed25519::Signature::from_base64( + "xNhaCa+HgrQh/2koZ3a7PGku8h+NXIp7YxFcLYzS/YLwNA4lNXOks8hAIX4QyPy7hTUWPlhcQNdq74exXm5pCw==", + ).unwrap(); + + let target = ed25519::PublicKey::from_base58( + "DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV", + ).unwrap(); + + let identity_blockstamp = Blockstamp::from_string( + "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + ).unwrap(); + + let identity_sig = ed25519::Signature::from_base64( + "1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FGMMmQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==", + ).unwrap(); + + let blockstamp = Blockstamp::from_string( + "36-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B865", + ).unwrap(); + + let builder = CertificationDocumentBuilder { + currency: "duniter_unit_test_currency", + issuer: &pubkey, + target: &target, + identity_username: "tic", + identity_blockstamp: &identity_blockstamp, + identity_sig: &identity_sig, + blockstamp: &blockstamp, + }; + + assert_eq!( + builder.build_with_signature(vec![sig]).verify_signatures(), + VerificationResult::Valid() + ); + + assert_eq!( + builder.build_and_sign(vec![prikey]).verify_signatures(), + VerificationResult::Valid() + ); + } + + #[test] + fn certification_standard_regex() { + assert!(CERTIFICATION_REGEX.is_match( + "Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV +IdtyIssuer: 7jzkd8GiFnpys4X7mP78w2Y3y3kwdK6fVSLEaojd3aH9 +IdtyUniqueID: fbarbut +IdtyTimestamp: 98221-000000575AC04F5164F7A307CDB766139EA47DD249E4A2444F292BC8AAB408B3 +IdtySignature: DjeipIeb/RF0tpVCnVnuw6mH1iLJHIsDfPGLR90Twy3PeoaDz6Yzhc/UjLWqHCi5Y6wYajV0dNg4jQRUneVBCQ== +CertTimestamp: 99956-00000472758331FDA8388E30E50CA04736CBFD3B7C21F34E74707107794B56DD +" + )); + } + + #[test] + fn certification_document() { + let doc = "Version: 10 +Type: Certification +Currency: g1 +Issuer: 2sZF6j2PkxBDNAqUde7Dgo5x3crkerZpQ4rBqqJGn8QT +IdtyIssuer: 7jzkd8GiFnpys4X7mP78w2Y3y3kwdK6fVSLEaojd3aH9 +IdtyUniqueID: fbarbut +IdtyTimestamp: 98221-000000575AC04F5164F7A307CDB766139EA47DD249E4A2444F292BC8AAB408B3 +IdtySignature: DjeipIeb/RF0tpVCnVnuw6mH1iLJHIsDfPGLR90Twy3PeoaDz6Yzhc/UjLWqHCi5Y6wYajV0dNg4jQRUneVBCQ== +CertTimestamp: 99956-00000472758331FDA8388E30E50CA04736CBFD3B7C21F34E74707107794B56DD +"; + + let body = "Issuer: 2sZF6j2PkxBDNAqUde7Dgo5x3crkerZpQ4rBqqJGn8QT +IdtyIssuer: 7jzkd8GiFnpys4X7mP78w2Y3y3kwdK6fVSLEaojd3aH9 +IdtyUniqueID: fbarbut +IdtyTimestamp: 98221-000000575AC04F5164F7A307CDB766139EA47DD249E4A2444F292BC8AAB408B3 +IdtySignature: DjeipIeb/RF0tpVCnVnuw6mH1iLJHIsDfPGLR90Twy3PeoaDz6Yzhc/UjLWqHCi5Y6wYajV0dNg4jQRUneVBCQ== +CertTimestamp: 99956-00000472758331FDA8388E30E50CA04736CBFD3B7C21F34E74707107794B56DD +"; + + let currency = "g1"; + + let signatures = vec![Signature::from_base64( +"Hkps1QU4HxIcNXKT8YmprYTVByBhPP1U2tIM7Z8wENzLKIWAvQClkAvBE7pW9dnVa18sJIJhVZUcRrPAZfmjBA==" + ).unwrap(),]; + + let doc = + CertificationDocumentParser::parse_standard(doc, body, currency, signatures).unwrap(); + if let V10Document::Certification(doc) = doc { + println!("Doc : {:?}", doc); + assert_eq!(doc.verify_signatures(), VerificationResult::Valid()) + } else { + panic!("Wrong document type"); + } + } +} diff --git a/documents/blockchain/v10/documents/membership.rs b/documents/blockchain/v10/documents/membership.rs index 88b5e262..fb1d2d12 100644 --- a/documents/blockchain/v10/documents/membership.rs +++ b/documents/blockchain/v10/documents/membership.rs @@ -235,7 +235,7 @@ impl StandardTextDocumentParser for MembershipDocumentParser { })) } else { Err(V10DocumentParsingError::InvalidInnerFormat( - "Identity".to_string(), + "Membership".to_string(), )) } } diff --git a/documents/blockchain/v10/documents/mod.rs b/documents/blockchain/v10/documents/mod.rs index 716ec5f4..4e38bcca 100644 --- a/documents/blockchain/v10/documents/mod.rs +++ b/documents/blockchain/v10/documents/mod.rs @@ -22,9 +22,12 @@ use blockchain::v10::documents::identity::IdentityDocumentParser; pub mod identity; pub mod membership; +pub mod certification; pub use blockchain::v10::documents::identity::{IdentityDocument, IdentityDocumentBuilder}; pub use blockchain::v10::documents::membership::{MembershipDocument, MembershipDocumentParser}; +pub use blockchain::v10::documents::certification::{CertificationDocument, + CertificationDocumentParser}; // Use of lazy_static so the regex is only compiled at first use. lazy_static! { @@ -56,7 +59,7 @@ pub enum V10Document { Membership(MembershipDocument), /// Certification document. - Certification(), + Certification(Box<CertificationDocument>), /// Revocation document. Revocation(), -- GitLab