1use crypto::digest::Digest;
19use crypto::sha2::Sha256;
20use duniter_crypto::keys::{PrivateKey, ed25519};
21
22use Hash;
23use blockchain::{BlockchainProtocol, Document, IntoSpecializedDocument};
24use blockchain::v10::documents::{TextDocument, V10Document};
25use blockchain::v10::documents::identity::IdentityDocument;
26use blockchain::v10::documents::membership::MembershipDocument;
27use blockchain::v10::documents::certification::CertificationDocument;
28use blockchain::v10::documents::revocation::RevocationDocument;
29use blockchain::v10::documents::transaction::TransactionDocument;
30
31#[derive(Debug, Copy, Clone, PartialEq)]
33pub struct BlockParameters {
34 pub c: f64,
36 pub dt: i64,
38 pub ud0: i64,
40 pub sig_period: u64,
42 pub sig_stock: i64,
44 pub sig_window: i64,
46 pub sig_validity: i64,
48 pub sig_qty: i64,
50 pub idty_window: i64,
52 pub ms_window: i64,
54 pub x_percent: f64,
56 pub ms_validity: u64,
58 pub step_max: u32,
62 pub median_time_blocks: i64,
64 pub avg_gen_time: i64,
66 pub dt_diff_eval: i64,
68 pub percent_rot: f64,
70 pub ud_time0: i64,
72 pub ud_reeval_time0: i64,
74 pub dt_reeval: i64,
76}
77
78#[derive(Debug, Clone)]
82pub struct BlockDocument {
83 nonce: u64,
85 number: u64,
87 pow_min: usize,
89 time: u64,
91 median_time: u64,
93 members_count: usize,
95 monetary_mass: usize,
97 unit_base: usize,
99 issuers_count: usize,
101 issuers_frame: isize,
103 issuers_frame_var: isize,
105 currency: String,
107 issuers: Vec<ed25519::PublicKey>,
109 signatures: Vec<ed25519::Signature>,
112 hash: Option<Hash>,
114 parameters: Option<BlockParameters>,
116 previous_hash: Option<Hash>,
118 previous_issuer: Option<ed25519::PublicKey>,
120 inner_hash: Option<Hash>,
122 dividend: Option<usize>,
124 identities: Vec<IdentityDocument>,
126 joiners: Vec<MembershipDocument>,
128 actives: Vec<MembershipDocument>,
130 leavers: Vec<MembershipDocument>,
132 revoked: Vec<RevocationDocument>,
134 excluded: Vec<ed25519::PublicKey>,
136 certifications: Vec<CertificationDocument>,
138 transactions: Vec<TransactionDocument>,
140 inner_hash_and_nonce_str: String,
142}
143
144impl BlockDocument {
145 fn _compute_inner_hash(&mut self) {
146 let mut sha256 = Sha256::new();
147 sha256.input_str(&self.generate_compact_inner_text());
148 self.inner_hash = Some(Hash::from_hex(&sha256.result_str()).unwrap());
149 }
150 fn _change_nonce(&mut self, new_nonce: u64) {
151 self.nonce = new_nonce;
152 self.inner_hash_and_nonce_str = format!(
153 "InnerHash: {}\nNonce: {}\n",
154 self.inner_hash.unwrap().to_hex(),
155 self.nonce
156 );
157 }
158 fn _sign(&mut self, privkey: ed25519::PrivateKey) {
159 self.signatures = vec![privkey.sign(self.inner_hash_and_nonce_str.as_bytes())];
160 }
161 fn _compute_hash(&mut self) {
162 let mut sha256 = Sha256::new();
163 sha256.input_str(&format!(
164 "InnerHash: {}\nNonce: {}\n{}\n",
165 self.inner_hash.unwrap().to_hex(),
166 self.nonce,
167 self.signatures[0]
168 ));
169 self.hash = Some(Hash::from_hex(&sha256.result_str()).unwrap());
170 }
171 fn generate_compact_inner_text(&self) -> String {
172 let mut identities_str = String::from("");
173 for identity in self.identities.clone() {
174 identities_str.push_str("\n");
175 identities_str.push_str(&identity.generate_compact_text());
176 }
177 let mut joiners_str = String::from("");
178 for joiner in self.joiners.clone() {
179 joiners_str.push_str("\n");
180 joiners_str.push_str(&joiner.generate_compact_text());
181 }
182 let mut actives_str = String::from("");
183 for active in self.actives.clone() {
184 actives_str.push_str("\n");
185 actives_str.push_str(&active.generate_compact_text());
186 }
187 let mut leavers_str = String::from("");
188 for leaver in self.leavers.clone() {
189 leavers_str.push_str("\n");
190 leavers_str.push_str(&leaver.generate_compact_text());
191 }
192 let mut identities_str = String::from("");
193 for identity in self.identities.clone() {
194 identities_str.push_str("\n");
195 identities_str.push_str(&identity.generate_compact_text());
196 }
197 let mut revokeds_str = String::from("");
198 for revocation in self.revoked.clone() {
199 revokeds_str.push_str("\n");
200 revokeds_str.push_str(&revocation.generate_compact_text());
201 }
202 let mut excludeds_str = String::from("");
203 for exclusion in self.excluded.clone() {
204 excludeds_str.push_str("\n");
205 excludeds_str.push_str(&exclusion.to_string());
206 }
207 let mut certifications_str = String::from("");
208 for certification in self.certifications.clone() {
209 certifications_str.push_str("\n");
210 certifications_str.push_str(&certification.generate_compact_text());
211 }
212 let mut transactions_str = String::from("");
213 for transaction in self.transactions.clone() {
214 transactions_str.push_str("\n");
215 transactions_str.push_str(&transaction.generate_compact_text());
216 }
217 format!(
218 "Version: 10
219Type: Block
220Currency: {currency}
221Number: {block_number}
222PoWMin: {pow_min}
223Time: {time}
224MedianTime: {median_time}
225UnitBase: {unit_base}
226Issuer: {issuer}
227IssuersFrame: {issuers_frame}
228IssuersFrameVar: {issuers_frame_var}
229DifferentIssuersCount: {issuers_count}
230PreviousHash: {previous_hash}
231PreviousIssuer: {previous_issuer}
232MembersCount: {members_count}
233Identities:{identities}
234Joiners:{joiners}
235Actives:{actives}
236Leavers:{leavers}
237Revoked:{revoked}
238Excluded:{excluded}
239Certifications:{certifications}
240Transactions:{transactions}
241",
242 currency = self.currency,
243 block_number = self.number,
244 pow_min = self.pow_min,
245 time = self.time,
246 median_time = self.median_time,
247 unit_base = self.unit_base,
248 issuer = self.issuers[0],
249 issuers_frame = self.issuers_frame,
250 issuers_frame_var = self.issuers_frame_var,
251 issuers_count = self.issuers_count,
252 previous_hash = self.previous_hash.unwrap(),
253 previous_issuer = self.previous_issuer.unwrap(),
254 members_count = self.members_count,
255 identities = identities_str,
256 joiners = joiners_str,
257 actives = actives_str,
258 leavers = leavers_str,
259 revoked = revokeds_str,
260 excluded = excludeds_str,
261 certifications = certifications_str,
262 transactions = transactions_str,
263 )
264 }
265}
266
267impl Document for BlockDocument {
268 type PublicKey = ed25519::PublicKey;
269 type CurrencyType = str;
270
271 fn version(&self) -> u16 {
272 10
273 }
274
275 fn currency(&self) -> &str {
276 &self.currency
277 }
278
279 fn issuers(&self) -> &Vec<ed25519::PublicKey> {
280 &self.issuers
281 }
282
283 fn signatures(&self) -> &Vec<ed25519::Signature> {
284 &self.signatures
285 }
286
287 fn as_bytes(&self) -> &[u8] {
288 self.inner_hash_and_nonce_str.as_bytes()
289 }
290}
291
292impl TextDocument for BlockDocument {
293 fn as_text(&self) -> &str {
294 panic!();
295 }
296
297 fn generate_compact_text(&self) -> String {
298 let compact_inner_text = self.generate_compact_inner_text();
299 format!(
300 "{}InnerHash: {}\nNonce: ",
301 compact_inner_text,
302 self.inner_hash.unwrap().to_hex()
303 )
304 }
305}
306
307impl IntoSpecializedDocument<BlockchainProtocol> for BlockDocument {
308 fn into_specialized(self) -> BlockchainProtocol {
309 BlockchainProtocol::V10(Box::new(V10Document::Block(Box::new(self))))
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use std::ops::Deref;
317 use duniter_crypto::keys::{PublicKey, Signature};
318 use blockchain::{Document, DocumentParser, VerificationResult};
319 use blockchain::v10::documents::V10DocumentParser;
320
321 #[test]
322 fn generate_and_verify_empty_block() {
323 let mut block = BlockDocument {
324 nonce: 10_500_000_089_933,
325 number: 107_777,
326 pow_min: 89,
327 time: 1_522_624_657,
328 median_time: 1_522_616_790,
329 members_count: 894,
330 monetary_mass: 139_571_973,
331 unit_base: 0,
332 issuers_count: 41,
333 issuers_frame: 201,
334 issuers_frame_var: 5,
335 currency: String::from("g1"),
336 issuers: vec![ed25519::PublicKey::from_base58("2sZF6j2PkxBDNAqUde7Dgo5x3crkerZpQ4rBqqJGn8QT").unwrap()],
337 signatures: vec![ed25519::Signature::from_base64("FsRxB+NOiL+8zTr2d3B2j2KBItDuCa0KjFMF6hXmdQzfqXAs9g3m7DlGgYLcqzqe6JXjx/Lyzqze1HBR4cS0Aw==").unwrap()],
338 hash: None,
339 parameters: None,
340 previous_hash: Some(Hash::from_hex("0000001F8AACF6764135F3E5D0D4E8358A3CBE537A4BF71152A00CC442EFD136").expect("fail to parse previous_hash")),
341 previous_issuer: Some(ed25519::PublicKey::from_base58("38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE").unwrap()),
342 inner_hash: None,
343 dividend: None,
344 identities: Vec::new(),
345 joiners: Vec::new(),
346 actives: Vec::new(),
347 leavers: Vec::new(),
348 revoked: Vec::new(),
349 excluded: Vec::new(),
350 certifications: Vec::new(),
351 transactions: Vec::new(),
352 inner_hash_and_nonce_str: String::new(),
353 };
354 block._compute_inner_hash();
356 println!("{}", block.generate_compact_text());
357 assert_eq!(
358 block.inner_hash.unwrap().to_hex(),
359 "95948AC4D45E46DA07CE0713EDE1CE0295C227EE4CA5557F73F56B7DD46FE89C"
360 );
361 assert_eq!(
363 block.generate_compact_text(),
364 "Version: 10
365Type: Block
366Currency: g1
367Number: 107777
368PoWMin: 89
369Time: 1522624657
370MedianTime: 1522616790
371UnitBase: 0
372Issuer: 2sZF6j2PkxBDNAqUde7Dgo5x3crkerZpQ4rBqqJGn8QT
373IssuersFrame: 201
374IssuersFrameVar: 5
375DifferentIssuersCount: 41
376PreviousHash: 0000001F8AACF6764135F3E5D0D4E8358A3CBE537A4BF71152A00CC442EFD136
377PreviousIssuer: 38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE
378MembersCount: 894
379Identities:
380Joiners:
381Actives:
382Leavers:
383Revoked:
384Excluded:
385Certifications:
386Transactions:
387InnerHash: 95948AC4D45E46DA07CE0713EDE1CE0295C227EE4CA5557F73F56B7DD46FE89C
388Nonce: "
389 );
390 block._change_nonce(10_500_000_089_933);
392 assert_eq!(block.verify_signatures(), VerificationResult::Valid());
393 block._compute_hash();
395 assert_eq!(
396 block.hash.unwrap().to_hex(),
397 "000002D3296A2D257D01F6FEE8AEC5C3E5779D04EA43F08901F41998FA97D9A1"
398 );
399 }
400
401 #[test]
402 fn generate_and_verify_block() {
403 let cert1_ = V10DocumentParser::parse("Version: 10
404Type: Certification
405Currency: g1
406Issuer: 6TAzLWuNcSqgNDNpAutrKpPXcGJwy1ZEMeVvZSZNs2e3
407IdtyIssuer: CYPsYTdt87Tx6cCiZs9KD4jqPgYxbcVEqVZpRgJ9jjoV
408IdtyUniqueID: PascaleM
409IdtyTimestamp: 97401-0000003821911909F98519CC773D2D3E5CFE3D5DBB39F4F4FF33B96B4D41800E
410IdtySignature: QncUVXxZ2NfARjdJOn6luILvDuG1NuK9qSoaU4CST2Ij8z7oeVtEgryHl+EXOjSe6XniALsCT0gU8wtadcA/Cw==
411CertTimestamp: 106669-000003682E6FE38C44433DCE92E8B2A26C69B6D7867A2BAED231E788DDEF4251
412UmseG2XKNwKcY8RFi6gUCT91udGnnNmSh7se10J1jeRVlwf+O2Tyb2Cccot9Dt7BO4+Kx2P6vFJB3oVGGHMxBA==").unwrap();
413 let cert1 = match cert1_ {
414 V10Document::Certification(doc) => (*doc.deref()).clone(),
415 _ => panic!("Wrong document type"),
416 };
417
418 let tx1_ = V10DocumentParser::parse("Version: 10
419Type: Transaction
420Currency: g1
421Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
422Locktime: 0
423Issuers:
4248dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
425Inputs:
4261002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106345
427Unlocks:
4280:SIG(0)
429Outputs:
4301002:0:SIG(CitdnuQgZ45tNFCagay7Wh12gwwHM8VLej1sWmfHWnQX)
431Comment: 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
432T0LlCcbIn7xDFws48H8LboN6NxxwNXXTovG4PROLf7tkUAueHFWjfwZFKQXeZEHxfaL1eYs3QspGtLWUHPRVCQ==").unwrap();
433 let tx1 = match tx1_ {
434 V10Document::Transaction(doc) => (*doc.deref()).clone(),
435 _ => panic!("Wrong document type"),
436 };
437
438 let tx2_ = V10DocumentParser::parse("Version: 10
439Type: Transaction
440Currency: g1
441Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
442Locktime: 0
443Issuers:
4448dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
445Inputs:
4461002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106614
447Unlocks:
4480:SIG(0)
449Outputs:
4501002:0:SIG(78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8)
451Comment: 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
452a9PHPuSfw7jW8FRQHXFsGi/bnLjbtDnTYvEVgUC9u0WlR7GVofa+Xb+l5iy6NwuEXiwvueAkf08wPVY8xrNcCg==").unwrap();
453 let tx2 = match tx2_ {
454 V10Document::Transaction(doc) => (*doc.deref()).clone(),
455 _ => panic!("Wrong document type"),
456 };
457
458 let mut block = BlockDocument {
459 nonce: 0,
460 number: 107_984,
461 pow_min: 88,
462 time: 1_522_685_861,
463 median_time: 1522683184,
464 members_count: 896,
465 monetary_mass: 140_469_765,
466 unit_base: 0,
467 issuers_count: 42,
468 issuers_frame: 211,
469 issuers_frame_var: 0,
470 currency: String::from("g1"),
471 issuers: vec![ed25519::PublicKey::from_base58("DA4PYtXdvQqk1nCaprXH52iMsK5Ahxs1nRWbWKLhpVkQ").unwrap()],
472 signatures: vec![ed25519::Signature::from_base64("92id58VmkhgVNee4LDqBGSm8u/ooHzAD67JM6fhAE/CV8LCz7XrMF1DvRl+eRpmlaVkp6I+Iy8gmZ1WUM5C8BA==").unwrap()],
473 hash: None,
474 parameters: None,
475 previous_hash: Some(Hash::from_hex("000001144968D0C3516BE6225E4662F182E28956AF46DD7FB228E3D0F9413FEB").expect("fail to parse previous_hash")),
476 previous_issuer: Some(ed25519::PublicKey::from_base58("D3krfq6J9AmfpKnS3gQVYoy7NzGCc61vokteTS8LJ4YH").unwrap()),
477 inner_hash: None,
478 dividend: None,
479 identities: Vec::new(),
480 joiners: Vec::new(),
481 actives: Vec::new(),
482 leavers: Vec::new(),
483 revoked: Vec::new(),
484 excluded: Vec::new(),
485 certifications: vec![cert1],
486 transactions: vec![tx1, tx2],
487 inner_hash_and_nonce_str: String::new(),
488 };
489 block._compute_inner_hash();
491 println!("{}", block.generate_compact_text());
492 assert_eq!(
493 block.inner_hash.unwrap().to_hex(),
494 "C8AB69E33ECE2612EADC7AB30D069B1F1A3D8C95EBBFD50DE583AC8E3666CCA1"
495 );
496 assert_eq!(
498 block.generate_compact_text(),
499 "Version: 10
500Type: Block
501Currency: g1
502Number: 107984
503PoWMin: 88
504Time: 1522685861
505MedianTime: 1522683184
506UnitBase: 0
507Issuer: DA4PYtXdvQqk1nCaprXH52iMsK5Ahxs1nRWbWKLhpVkQ
508IssuersFrame: 211
509IssuersFrameVar: 0
510DifferentIssuersCount: 42
511PreviousHash: 000001144968D0C3516BE6225E4662F182E28956AF46DD7FB228E3D0F9413FEB
512PreviousIssuer: D3krfq6J9AmfpKnS3gQVYoy7NzGCc61vokteTS8LJ4YH
513MembersCount: 896
514Identities:
515Joiners:
516Actives:
517Leavers:
518Revoked:
519Excluded:
520Certifications:
5216TAzLWuNcSqgNDNpAutrKpPXcGJwy1ZEMeVvZSZNs2e3:CYPsYTdt87Tx6cCiZs9KD4jqPgYxbcVEqVZpRgJ9jjoV:106669:UmseG2XKNwKcY8RFi6gUCT91udGnnNmSh7se10J1jeRVlwf+O2Tyb2Cccot9Dt7BO4+Kx2P6vFJB3oVGGHMxBA==
522Transactions:
523TX:10:1:1:1:1:1:0
524107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
5258dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
5261002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106345
5270:SIG(0)
5281002:0:SIG(CitdnuQgZ45tNFCagay7Wh12gwwHM8VLej1sWmfHWnQX)
529DU 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
530T0LlCcbIn7xDFws48H8LboN6NxxwNXXTovG4PROLf7tkUAueHFWjfwZFKQXeZEHxfaL1eYs3QspGtLWUHPRVCQ==
531TX:10:1:1:1:1:1:0
532107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
5338dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
5341002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106614
5350:SIG(0)
5361002:0:SIG(78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8)
537DU 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
538a9PHPuSfw7jW8FRQHXFsGi/bnLjbtDnTYvEVgUC9u0WlR7GVofa+Xb+l5iy6NwuEXiwvueAkf08wPVY8xrNcCg==
539InnerHash: C8AB69E33ECE2612EADC7AB30D069B1F1A3D8C95EBBFD50DE583AC8E3666CCA1
540Nonce: "
541 );
542 block._change_nonce(10_300_000_018_323);
544 assert_eq!(block.verify_signatures(), VerificationResult::Valid());
545 block._compute_hash();
547 assert_eq!(
548 block.hash.unwrap().to_hex(),
549 "000004F8B84A3590243BA562E5F2BA379F55A0B387C5D6FAC1022DFF7FFE6014"
550 );
551 }
552}