// 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 . //! Wrappers around Block document. use crypto::digest::Digest; use crypto::sha2::Sha256; use duniter_crypto::keys::{PrivateKey, ed25519}; use Hash; use blockchain::{BlockchainProtocol, Document, IntoSpecializedDocument}; use blockchain::v10::documents::{TextDocument, V10Document}; use blockchain::v10::documents::identity::IdentityDocument; use blockchain::v10::documents::membership::MembershipDocument; use blockchain::v10::documents::certification::CertificationDocument; use blockchain::v10::documents::revocation::RevocationDocument; use blockchain::v10::documents::transaction::TransactionDocument; /// Currency parameters #[derive(Debug, Copy, Clone, PartialEq)] pub struct BlockParameters { /// UD target growth rate (see Relative Theorie of Money) pub c: f64, /// Duration between the creation of two DU (in seconds) pub dt: i64, /// Amount of the initial UD pub ud0: i64, /// Minimum duration between the writing of 2 certifications from the same issuer (in seconds) pub sig_period: u64, /// Maximum number of active certifications at the same time (for the same issuer) pub sig_stock: i64, /// Maximum retention period of a pending certification pub sig_window: i64, /// Time to expiry of written certification pub sig_validity: i64, /// Minimum number of certifications required to become a member pub sig_qty: i64, /// Maximum retention period of a pending identity pub idty_window: i64, /// Maximum retention period of a pending membership pub ms_window: i64, /// Percentage of referring members who must be within step_max steps of each member pub x_percent: f64, /// Time to expiry of written membership pub ms_validity: u64, /// For a member to respect the distance rule, /// there must exist for more than x_percent % of the referring members /// a path of less than step_max steps from the referring member to the evaluated member. pub step_max: u32, /// Number of blocks used for calculating median time. pub median_time_blocks: i64, /// The average time for writing 1 block (wished time) pub avg_gen_time: i64, /// The number of blocks required to evaluate again PoWMin value pub dt_diff_eval: i64, /// The percent of previous issuers to reach for personalized difficulty pub percent_rot: f64, /// Time of first UD. pub ud_time0: i64, /// Time of first reevaluation of the UD. pub ud_reeval_time0: i64, /// Time period between two re-evaluation of the UD. pub dt_reeval: i64, } /// Wrap a Block document. /// /// Must be created by parsing a text document or using a builder. #[derive(Debug, Clone)] pub struct BlockDocument { /// Nonce nonce: u64, /// number number: u64, /// Minimal proof of work difficulty pow_min: usize, /// Local time of the block issuer time: u64, /// Average time median_time: u64, /// Members count members_count: usize, /// Monetary mass monetary_mass: usize, /// Unit base (power of ten) unit_base: usize, /// Number of compute members in the current frame issuers_count: usize, /// Current frame size (in blocks) issuers_frame: isize, /// Current frame variation buffer issuers_frame_var: isize, /// Currency. currency: String, /// Document issuer (there should be only one). issuers: Vec, /// Document signature (there should be only one). /// This vector is empty, when the block is generated but the proof of work has not yet started signatures: Vec, /// The hash is None, when the block is generated but the proof of work has not yet started hash: Option, /// Currency parameters (only in genesis block) parameters: Option, /// Hash of the previous block previous_hash: Option, /// Issuer of the previous block previous_issuer: Option, /// Hash of the deterministic content of the block inner_hash: Option, /// Amount of new dividend created at this block, None if no dividend is created at this block dividend: Option, /// Identities identities: Vec, /// joiners joiners: Vec, /// Actives (=renewals) actives: Vec, /// Leavers leavers: Vec, /// Revokeds revoked: Vec, /// Excludeds excluded: Vec, /// Certifications certifications: Vec, /// Transactions transactions: Vec, /// Part to sign inner_hash_and_nonce_str: String, } impl BlockDocument { fn _compute_inner_hash(&mut self) { let mut sha256 = Sha256::new(); sha256.input_str(&self.generate_compact_inner_text()); self.inner_hash = Some(Hash::from_hex(&sha256.result_str()).unwrap()); } fn _change_nonce(&mut self, new_nonce: u64) { self.nonce = new_nonce; self.inner_hash_and_nonce_str = format!( "InnerHash: {}\nNonce: {}\n", self.inner_hash.unwrap().to_hex(), self.nonce ); } fn _sign(&mut self, privkey: ed25519::PrivateKey) { self.signatures = vec![privkey.sign(self.inner_hash_and_nonce_str.as_bytes())]; } fn _compute_hash(&mut self) { let mut sha256 = Sha256::new(); sha256.input_str(&format!( "InnerHash: {}\nNonce: {}\n{}\n", self.inner_hash.unwrap().to_hex(), self.nonce, self.signatures[0] )); self.hash = Some(Hash::from_hex(&sha256.result_str()).unwrap()); } fn generate_compact_inner_text(&self) -> String { let mut identities_str = String::from(""); for identity in self.identities.clone() { identities_str.push_str("\n"); identities_str.push_str(&identity.generate_compact_text()); } let mut joiners_str = String::from(""); for joiner in self.joiners.clone() { joiners_str.push_str("\n"); joiners_str.push_str(&joiner.generate_compact_text()); } let mut actives_str = String::from(""); for active in self.actives.clone() { actives_str.push_str("\n"); actives_str.push_str(&active.generate_compact_text()); } let mut leavers_str = String::from(""); for leaver in self.leavers.clone() { leavers_str.push_str("\n"); leavers_str.push_str(&leaver.generate_compact_text()); } let mut identities_str = String::from(""); for identity in self.identities.clone() { identities_str.push_str("\n"); identities_str.push_str(&identity.generate_compact_text()); } let mut revokeds_str = String::from(""); for revocation in self.revoked.clone() { revokeds_str.push_str("\n"); revokeds_str.push_str(&revocation.generate_compact_text()); } let mut excludeds_str = String::from(""); for exclusion in self.excluded.clone() { excludeds_str.push_str("\n"); excludeds_str.push_str(&exclusion.to_string()); } let mut certifications_str = String::from(""); for certification in self.certifications.clone() { certifications_str.push_str("\n"); certifications_str.push_str(&certification.generate_compact_text()); } let mut transactions_str = String::from(""); for transaction in self.transactions.clone() { transactions_str.push_str("\n"); transactions_str.push_str(&transaction.generate_compact_text()); } format!( "Version: 10 Type: Block Currency: {currency} Number: {block_number} PoWMin: {pow_min} Time: {time} MedianTime: {median_time} UnitBase: {unit_base} Issuer: {issuer} IssuersFrame: {issuers_frame} IssuersFrameVar: {issuers_frame_var} DifferentIssuersCount: {issuers_count} PreviousHash: {previous_hash} PreviousIssuer: {previous_issuer} MembersCount: {members_count} Identities:{identities} Joiners:{joiners} Actives:{actives} Leavers:{leavers} Revoked:{revoked} Excluded:{excluded} Certifications:{certifications} Transactions:{transactions} ", currency = self.currency, block_number = self.number, pow_min = self.pow_min, time = self.time, median_time = self.median_time, unit_base = self.unit_base, issuer = self.issuers[0], issuers_frame = self.issuers_frame, issuers_frame_var = self.issuers_frame_var, issuers_count = self.issuers_count, previous_hash = self.previous_hash.unwrap(), previous_issuer = self.previous_issuer.unwrap(), members_count = self.members_count, identities = identities_str, joiners = joiners_str, actives = actives_str, leavers = leavers_str, revoked = revokeds_str, excluded = excludeds_str, certifications = certifications_str, transactions = transactions_str, ) } } impl Document for BlockDocument { type PublicKey = ed25519::PublicKey; type CurrencyType = str; fn version(&self) -> u16 { 10 } fn currency(&self) -> &str { &self.currency } fn issuers(&self) -> &Vec { &self.issuers } fn signatures(&self) -> &Vec { &self.signatures } fn as_bytes(&self) -> &[u8] { self.inner_hash_and_nonce_str.as_bytes() } } impl TextDocument for BlockDocument { fn as_text(&self) -> &str { panic!(); } fn generate_compact_text(&self) -> String { let compact_inner_text = self.generate_compact_inner_text(); format!( "{}InnerHash: {}\nNonce: ", compact_inner_text, self.inner_hash.unwrap().to_hex() ) } } impl IntoSpecializedDocument for BlockDocument { fn into_specialized(self) -> BlockchainProtocol { BlockchainProtocol::V10(Box::new(V10Document::Block(Box::new(self)))) } } #[cfg(test)] mod tests { use super::*; use std::ops::Deref; use duniter_crypto::keys::{PublicKey, Signature}; use blockchain::{Document, DocumentParser, VerificationResult}; use blockchain::v10::documents::V10DocumentParser; #[test] fn generate_and_verify_empty_block() { let mut block = BlockDocument { nonce: 10_500_000_089_933, number: 107_777, pow_min: 89, time: 1_522_624_657, median_time: 1_522_616_790, members_count: 894, monetary_mass: 139_571_973, unit_base: 0, issuers_count: 41, issuers_frame: 201, issuers_frame_var: 5, currency: String::from("g1"), issuers: vec![ed25519::PublicKey::from_base58("2sZF6j2PkxBDNAqUde7Dgo5x3crkerZpQ4rBqqJGn8QT").unwrap()], signatures: vec![ed25519::Signature::from_base64("FsRxB+NOiL+8zTr2d3B2j2KBItDuCa0KjFMF6hXmdQzfqXAs9g3m7DlGgYLcqzqe6JXjx/Lyzqze1HBR4cS0Aw==").unwrap()], hash: None, parameters: None, previous_hash: Some(Hash::from_hex("0000001F8AACF6764135F3E5D0D4E8358A3CBE537A4BF71152A00CC442EFD136").expect("fail to parse previous_hash")), previous_issuer: Some(ed25519::PublicKey::from_base58("38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE").unwrap()), inner_hash: None, dividend: None, identities: Vec::new(), joiners: Vec::new(), actives: Vec::new(), leavers: Vec::new(), revoked: Vec::new(), excluded: Vec::new(), certifications: Vec::new(), transactions: Vec::new(), inner_hash_and_nonce_str: String::new(), }; // test inner_hash computation block._compute_inner_hash(); println!("{}", block.generate_compact_text()); assert_eq!( block.inner_hash.unwrap().to_hex(), "95948AC4D45E46DA07CE0713EDE1CE0295C227EE4CA5557F73F56B7DD46FE89C" ); // test generate_compact_text() assert_eq!( block.generate_compact_text(), "Version: 10 Type: Block Currency: g1 Number: 107777 PoWMin: 89 Time: 1522624657 MedianTime: 1522616790 UnitBase: 0 Issuer: 2sZF6j2PkxBDNAqUde7Dgo5x3crkerZpQ4rBqqJGn8QT IssuersFrame: 201 IssuersFrameVar: 5 DifferentIssuersCount: 41 PreviousHash: 0000001F8AACF6764135F3E5D0D4E8358A3CBE537A4BF71152A00CC442EFD136 PreviousIssuer: 38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE MembersCount: 894 Identities: Joiners: Actives: Leavers: Revoked: Excluded: Certifications: Transactions: InnerHash: 95948AC4D45E46DA07CE0713EDE1CE0295C227EE4CA5557F73F56B7DD46FE89C Nonce: " ); // Test signature validity block._change_nonce(10_500_000_089_933); assert_eq!(block.verify_signatures(), VerificationResult::Valid()); // Test hash computation block._compute_hash(); assert_eq!( block.hash.unwrap().to_hex(), "000002D3296A2D257D01F6FEE8AEC5C3E5779D04EA43F08901F41998FA97D9A1" ); } #[test] fn generate_and_verify_block() { let cert1_ = V10DocumentParser::parse("Version: 10 Type: Certification Currency: g1 Issuer: 6TAzLWuNcSqgNDNpAutrKpPXcGJwy1ZEMeVvZSZNs2e3 IdtyIssuer: CYPsYTdt87Tx6cCiZs9KD4jqPgYxbcVEqVZpRgJ9jjoV IdtyUniqueID: PascaleM IdtyTimestamp: 97401-0000003821911909F98519CC773D2D3E5CFE3D5DBB39F4F4FF33B96B4D41800E IdtySignature: QncUVXxZ2NfARjdJOn6luILvDuG1NuK9qSoaU4CST2Ij8z7oeVtEgryHl+EXOjSe6XniALsCT0gU8wtadcA/Cw== CertTimestamp: 106669-000003682E6FE38C44433DCE92E8B2A26C69B6D7867A2BAED231E788DDEF4251 UmseG2XKNwKcY8RFi6gUCT91udGnnNmSh7se10J1jeRVlwf+O2Tyb2Cccot9Dt7BO4+Kx2P6vFJB3oVGGHMxBA==").unwrap(); let cert1 = match cert1_ { V10Document::Certification(doc) => (*doc.deref()).clone(), _ => panic!("Wrong document type"), }; let tx1_ = V10DocumentParser::parse("Version: 10 Type: Transaction Currency: g1 Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B Locktime: 0 Issuers: 8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3 Inputs: 1002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106345 Unlocks: 0:SIG(0) Outputs: 1002:0:SIG(CitdnuQgZ45tNFCagay7Wh12gwwHM8VLej1sWmfHWnQX) Comment: DU symbolique pour demander le codage de nouvelles fonctionnalites cf. https://forum.monnaie-libre.fr/t/creer-de-nouvelles-fonctionnalites-dans-cesium-les-autres-applications/2025 Merci T0LlCcbIn7xDFws48H8LboN6NxxwNXXTovG4PROLf7tkUAueHFWjfwZFKQXeZEHxfaL1eYs3QspGtLWUHPRVCQ==").unwrap(); let tx1 = match tx1_ { V10Document::Transaction(doc) => (*doc.deref()).clone(), _ => panic!("Wrong document type"), }; let tx2_ = V10DocumentParser::parse("Version: 10 Type: Transaction Currency: g1 Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B Locktime: 0 Issuers: 8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3 Inputs: 1002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106614 Unlocks: 0:SIG(0) Outputs: 1002:0:SIG(78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8) Comment: DU symbolique pour demander le codage de nouvelles fonctionnalites cf. https://forum.monnaie-libre.fr/t/creer-de-nouvelles-fonctionnalites-dans-cesium-les-autres-applications/2025 Merci a9PHPuSfw7jW8FRQHXFsGi/bnLjbtDnTYvEVgUC9u0WlR7GVofa+Xb+l5iy6NwuEXiwvueAkf08wPVY8xrNcCg==").unwrap(); let tx2 = match tx2_ { V10Document::Transaction(doc) => (*doc.deref()).clone(), _ => panic!("Wrong document type"), }; let mut block = BlockDocument { nonce: 0, number: 107_984, pow_min: 88, time: 1_522_685_861, median_time: 1522683184, members_count: 896, monetary_mass: 140_469_765, unit_base: 0, issuers_count: 42, issuers_frame: 211, issuers_frame_var: 0, currency: String::from("g1"), issuers: vec![ed25519::PublicKey::from_base58("DA4PYtXdvQqk1nCaprXH52iMsK5Ahxs1nRWbWKLhpVkQ").unwrap()], signatures: vec![ed25519::Signature::from_base64("92id58VmkhgVNee4LDqBGSm8u/ooHzAD67JM6fhAE/CV8LCz7XrMF1DvRl+eRpmlaVkp6I+Iy8gmZ1WUM5C8BA==").unwrap()], hash: None, parameters: None, previous_hash: Some(Hash::from_hex("000001144968D0C3516BE6225E4662F182E28956AF46DD7FB228E3D0F9413FEB").expect("fail to parse previous_hash")), previous_issuer: Some(ed25519::PublicKey::from_base58("D3krfq6J9AmfpKnS3gQVYoy7NzGCc61vokteTS8LJ4YH").unwrap()), inner_hash: None, dividend: None, identities: Vec::new(), joiners: Vec::new(), actives: Vec::new(), leavers: Vec::new(), revoked: Vec::new(), excluded: Vec::new(), certifications: vec![cert1], transactions: vec![tx1, tx2], inner_hash_and_nonce_str: String::new(), }; // test inner_hash computation block._compute_inner_hash(); println!("{}", block.generate_compact_text()); assert_eq!( block.inner_hash.unwrap().to_hex(), "C8AB69E33ECE2612EADC7AB30D069B1F1A3D8C95EBBFD50DE583AC8E3666CCA1" ); // test generate_compact_text() assert_eq!( block.generate_compact_text(), "Version: 10 Type: Block Currency: g1 Number: 107984 PoWMin: 88 Time: 1522685861 MedianTime: 1522683184 UnitBase: 0 Issuer: DA4PYtXdvQqk1nCaprXH52iMsK5Ahxs1nRWbWKLhpVkQ IssuersFrame: 211 IssuersFrameVar: 0 DifferentIssuersCount: 42 PreviousHash: 000001144968D0C3516BE6225E4662F182E28956AF46DD7FB228E3D0F9413FEB PreviousIssuer: D3krfq6J9AmfpKnS3gQVYoy7NzGCc61vokteTS8LJ4YH MembersCount: 896 Identities: Joiners: Actives: Leavers: Revoked: Excluded: Certifications: 6TAzLWuNcSqgNDNpAutrKpPXcGJwy1ZEMeVvZSZNs2e3:CYPsYTdt87Tx6cCiZs9KD4jqPgYxbcVEqVZpRgJ9jjoV:106669:UmseG2XKNwKcY8RFi6gUCT91udGnnNmSh7se10J1jeRVlwf+O2Tyb2Cccot9Dt7BO4+Kx2P6vFJB3oVGGHMxBA== Transactions: TX:10:1:1:1:1:1:0 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B 8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3 1002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106345 0:SIG(0) 1002:0:SIG(CitdnuQgZ45tNFCagay7Wh12gwwHM8VLej1sWmfHWnQX) DU symbolique pour demander le codage de nouvelles fonctionnalites cf. https://forum.monnaie-libre.fr/t/creer-de-nouvelles-fonctionnalites-dans-cesium-les-autres-applications/2025 Merci T0LlCcbIn7xDFws48H8LboN6NxxwNXXTovG4PROLf7tkUAueHFWjfwZFKQXeZEHxfaL1eYs3QspGtLWUHPRVCQ== TX:10:1:1:1:1:1:0 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B 8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3 1002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106614 0:SIG(0) 1002:0:SIG(78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8) DU symbolique pour demander le codage de nouvelles fonctionnalites cf. https://forum.monnaie-libre.fr/t/creer-de-nouvelles-fonctionnalites-dans-cesium-les-autres-applications/2025 Merci a9PHPuSfw7jW8FRQHXFsGi/bnLjbtDnTYvEVgUC9u0WlR7GVofa+Xb+l5iy6NwuEXiwvueAkf08wPVY8xrNcCg== InnerHash: C8AB69E33ECE2612EADC7AB30D069B1F1A3D8C95EBBFD50DE583AC8E3666CCA1 Nonce: " ); // Test signature validity block._change_nonce(10_300_000_018_323); assert_eq!(block.verify_signatures(), VerificationResult::Valid()); // Test hash computation block._compute_hash(); assert_eq!( block.hash.unwrap().to_hex(), "000004F8B84A3590243BA562E5F2BA379F55A0B387C5D6FAC1022DFF7FFE6014" ); } }