1use std::ops::Deref;
19
20use duniter_crypto::keys::{PublicKey, ed25519};
21use regex::Regex;
22use regex::RegexBuilder;
23
24use Blockstamp;
25use blockchain::{BlockchainProtocol, Document, DocumentBuilder, IntoSpecializedDocument};
26use blockchain::v10::documents::{StandardTextDocumentParser, TextDocument, TextDocumentBuilder,
27 V10Document, V10DocumentParsingError};
28
29lazy_static! {
30 static ref TRANSACTION_REGEX_SIZE: &'static usize = &40_000_000;
31 static ref TRANSACTION_REGEX_BUILDER: &'static str =
32 r"^Blockstamp: (?P<blockstamp>[0-9]+-[0-9A-F]{64})\nLocktime: (?P<locktime>[0-9]+)\nIssuers:(?P<issuers>(?:\n[1-9A-Za-z][^OIl]{43,44})+)Inputs:\n(?P<inputs>([0-9A-Za-z:]+\n)+)Unlocks:\n(?P<unlocks>([0-9]+:(SIG\([0-9]+\) ?|XHX\(\w+\) ?)+\n)+)Outputs:\n(?P<outputs>([0-9A-Za-z()&|: ]+\n)+)Comment: (?P<comment>[\\\w:/;*\[\]()?!^+=@&~#{}|<>%. -]{0,255})\n$";
33 static ref ISSUER_REGEX: Regex = Regex::new("(?P<issuer>[1-9A-Za-z][^OIl]{43,44})\n").unwrap();
34 static ref D_INPUT_REGEX: Regex = Regex::new(
35 "^(?P<amount>[1-9][0-9]*):(?P<base>[0-9]+):D:(?P<pubkey>[1-9A-Za-z][^OIl]{43,44}):(?P<block_number>[0-9]+)$"
36 ).unwrap();
37 static ref T_INPUT_REGEX: Regex = Regex::new(
38 "^(?P<amount>[1-9][0-9]*):(?P<base>[0-9]+):T:(?P<tx_hash>[0-9A-F]{64}):(?P<tx_index>[0-9]+)$"
39 ).unwrap();
40 static ref UNLOCKS_REGEX: Regex = Regex::new(
41 r"^(?P<index>[0-9]+):(?P<unlocks>(SIG\([0-9]+\) ?|XHX\(\w+\) ?)+)$"
42 ).unwrap();
43 static ref UNLOCK_SIG_REGEX: Regex =
44 Regex::new(r"^SIG\((?P<index>[0-9]+)\)$").unwrap();
45 static ref UNLOCK_XHX_REGEX: Regex = Regex::new(r"^XHX\((?P<code>\w+)\)$").unwrap();
46 static ref OUTPUT_COND_SIG_REGEX: Regex = Regex::new(r"^SIG\((?P<pubkey>[1-9A-Za-z][^OIl]{43,44})\)$").unwrap();
47 static ref OUTPUT_COND_XHX_REGEX: Regex = Regex::new(r"^XHX\((?P<hash>[0-9A-F]{64})\)$").unwrap();
48 static ref OUTPUT_COND_CLTV_REGEX: Regex = Regex::new(r"^CLTV\((?P<timestamp>[0-9]+)\)$").unwrap();
49 static ref OUTPUT_COND_CSV_REGEX: Regex = Regex::new(r"^CSV\((?P<timestamp>[0-9]+)\)$").unwrap();
50 static ref OUPUT_CONDS_BRAKETS: Regex = Regex::new(r"^\((?P<conditions>[0-9A-Za-z()&| ]+)\)$").unwrap();
51 static ref OUPUT_CONDS_AND: Regex = Regex::new(r"^(?P<conditions_group_1>[0-9A-Za-z()&| ]+) && (?P<conditions_group_2>[0-9A-Za-z()&| ]+)$").unwrap();
52 static ref OUPUT_CONDS_OR: Regex = Regex::new(r"^(?P<conditions_group_1>[0-9A-Za-z()&| ]+) \|\| (?P<conditions_group_2>[0-9A-Za-z()&| ]+)$").unwrap();
53 static ref OUTPUT_REGEX: Regex = Regex::new(
54 "^(?P<amount>[1-9][0-9]+):(?P<base>[0-9]+):(?P<conditions>[0-9A-Za-z()&| ]+)$"
55 ).unwrap();
56}
57
58#[derive(Debug, Clone)]
60pub enum TransactionInput {
61 D(isize, usize, ed25519::PublicKey, u64),
63 T(isize, usize, String, usize),
65}
66
67impl ToString for TransactionInput {
68 fn to_string(&self) -> String {
69 match *self {
70 TransactionInput::D(amount, base, pubkey, block_number) => {
71 format!("{}:{}:D:{}:{}", amount, base, pubkey, block_number)
72 }
73 TransactionInput::T(amount, base, ref tx_hash, tx_index) => {
74 format!("{}:{}:T:{}:{}", amount, base, tx_hash, tx_index)
75 }
76 }
77 }
78}
79
80impl TransactionInput {
81 fn parse_from_str(source: &str) -> Result<TransactionInput, V10DocumentParsingError> {
82 if let Some(caps) = D_INPUT_REGEX.captures(source) {
83 let amount = &caps["amount"];
84 let base = &caps["base"];
85 let pubkey = &caps["pubkey"];
86 let block_number = &caps["block_number"];
87 Ok(TransactionInput::D(
88 amount.parse().expect("fail to parse input amount !"),
89 base.parse().expect("fail to parse input base !"),
90 ed25519::PublicKey::from_base58(pubkey).expect("fail to parse input pubkey !"),
91 block_number
92 .parse()
93 .expect("fail to parse input block_number !"),
94 ))
95 } else if let Some(caps) = T_INPUT_REGEX.captures(source) {
97 let amount = &caps["amount"];
98 let base = &caps["base"];
99 let tx_hash = &caps["tx_hash"];
100 let tx_index = &caps["tx_index"];
101 Ok(TransactionInput::T(
102 amount.parse().expect("fail to parse input amount"),
103 base.parse().expect("fail to parse base amount"),
104 String::from(tx_hash),
105 tx_index.parse().expect("fail to parse tx_index amount"),
106 ))
107 } else {
108 println!("Fail to parse this input = {:?}", source);
109 Err(V10DocumentParsingError::InvalidInnerFormat(String::from(
110 "Transaction2",
111 )))
112 }
113 }
114}
115
116#[derive(Debug, Clone)]
118pub enum TransactionUnlockProof {
119 Sig(usize),
121 Xhx(String),
123}
124
125impl ToString for TransactionUnlockProof {
126 fn to_string(&self) -> String {
127 match *self {
128 TransactionUnlockProof::Sig(ref index) => format!("SIG({})", index),
129 TransactionUnlockProof::Xhx(ref hash) => format!("XHX({})", hash),
130 }
131 }
132}
133
134impl TransactionUnlockProof {
135 fn parse_from_str(source: &str) -> Result<TransactionUnlockProof, V10DocumentParsingError> {
136 if let Some(caps) = UNLOCK_SIG_REGEX.captures(source) {
137 let index = &caps["index"];
138 Ok(TransactionUnlockProof::Sig(
139 index.parse().expect("fail to parse SIG unlock"),
140 ))
141 } else if let Some(caps) = UNLOCK_XHX_REGEX.captures(source) {
142 let code = &caps["code"];
143 Ok(TransactionUnlockProof::Xhx(String::from(code)))
144 } else {
145 Err(V10DocumentParsingError::InvalidInnerFormat(String::from(
146 "Transaction3",
147 )))
148 }
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct TransactionInputUnlocks {
155 pub index: usize,
157 pub unlocks: Vec<TransactionUnlockProof>,
159}
160
161impl ToString for TransactionInputUnlocks {
162 fn to_string(&self) -> String {
163 let mut result: String = format!("{}:", self.index);
164 for unlock in &self.unlocks {
165 result.push_str(&format!("{} ", unlock.to_string()));
166 }
167 let new_size = result.len() - 1;
168 result.truncate(new_size);
169 result
170 }
171}
172
173impl TransactionInputUnlocks {
174 fn parse_from_str(source: &str) -> Result<TransactionInputUnlocks, V10DocumentParsingError> {
175 if let Some(caps) = UNLOCKS_REGEX.captures(source) {
176 let index = &caps["index"].parse().expect("fail to parse unlock index");
177 let unlocks = &caps["unlocks"];
178
179 let unlocks_array: Vec<&str> = unlocks.split(' ').collect();
180 let mut unlocks = Vec::new();
181 for unlock in unlocks_array {
182 unlocks.push(TransactionUnlockProof::parse_from_str(unlock)?);
183 }
184 Ok(TransactionInputUnlocks {
185 index: *index,
186 unlocks,
187 })
188 } else {
189 println!("Fail to parse this unlock = {:?}", source);
190 Err(V10DocumentParsingError::InvalidInnerFormat(String::from(
191 "Transaction4",
192 )))
193 }
194 }
195}
196
197#[derive(Debug, Clone)]
199pub enum TransactionOuputCondition {
200 Sig(ed25519::PublicKey),
202 Xhx(String),
204 Cltv(u64),
206 Csv(u64),
208}
209
210impl ToString for TransactionOuputCondition {
211 fn to_string(&self) -> String {
212 match *self {
213 TransactionOuputCondition::Sig(ref pubkey) => format!("SIG({})", pubkey),
214 TransactionOuputCondition::Xhx(ref hash) => format!("XHX({})", hash),
215 TransactionOuputCondition::Cltv(timestamp) => format!("CLTV({})", timestamp),
216 TransactionOuputCondition::Csv(duration) => format!("CSV({})", duration),
217 }
218 }
219}
220
221impl TransactionOuputCondition {
222 fn parse_from_str(source: &str) -> Result<TransactionOuputCondition, V10DocumentParsingError> {
223 if let Some(caps) = OUTPUT_COND_SIG_REGEX.captures(source) {
224 Ok(TransactionOuputCondition::Sig(
225 ed25519::PublicKey::from_base58(&caps["pubkey"])
226 .expect("fail to parse SIG TransactionOuputCondition"),
227 ))
228 } else if let Some(caps) = OUTPUT_COND_XHX_REGEX.captures(source) {
229 Ok(TransactionOuputCondition::Xhx(String::from(&caps["hash"])))
230 } else if let Some(caps) = OUTPUT_COND_CLTV_REGEX.captures(source) {
231 Ok(TransactionOuputCondition::Cltv(
232 caps["timestamp"]
233 .parse()
234 .expect("fail to parse CLTV TransactionOuputCondition"),
235 ))
236 } else if let Some(caps) = OUTPUT_COND_CSV_REGEX.captures(source) {
237 Ok(TransactionOuputCondition::Csv(
238 caps["duration"]
239 .parse()
240 .expect("fail to parse CSV TransactionOuputCondition"),
241 ))
242 } else {
243 Err(V10DocumentParsingError::InvalidInnerFormat(
244 "Transaction5".to_string(),
245 ))
246 }
247 }
248}
249
250#[derive(Debug, Clone)]
252pub enum TransactionOuputConditionGroup {
253 Single(TransactionOuputCondition),
255 Brackets(Box<TransactionOuputConditionGroup>),
257 And(
259 Box<TransactionOuputConditionGroup>,
260 Box<TransactionOuputConditionGroup>,
261 ),
262 Or(
264 Box<TransactionOuputConditionGroup>,
265 Box<TransactionOuputConditionGroup>,
266 ),
267}
268
269impl ToString for TransactionOuputConditionGroup {
270 fn to_string(&self) -> String {
271 match *self {
272 TransactionOuputConditionGroup::Single(ref condition) => condition.to_string(),
273 TransactionOuputConditionGroup::Brackets(ref condition_group) => {
274 format!("({})", condition_group.deref().to_string())
275 }
276 TransactionOuputConditionGroup::And(ref condition_group_1, ref condition_group_2) => {
277 format!(
278 "{} && {}",
279 condition_group_1.deref().to_string(),
280 condition_group_2.deref().to_string()
281 )
282 }
283 TransactionOuputConditionGroup::Or(ref condition_group_1, ref condition_group_2) => {
284 format!(
285 "{} || {}",
286 condition_group_1.deref().to_string(),
287 condition_group_2.deref().to_string()
288 )
289 }
290 }
291 }
292}
293
294impl TransactionOuputConditionGroup {
295 fn parse_from_str(
296 conditions: &str,
297 ) -> Result<TransactionOuputConditionGroup, V10DocumentParsingError> {
298 if let Ok(single_condition) = TransactionOuputCondition::parse_from_str(conditions) {
299 Ok(TransactionOuputConditionGroup::Single(single_condition))
300 } else if let Some(caps) = OUPUT_CONDS_BRAKETS.captures(conditions) {
301 let inner_conditions =
302 TransactionOuputConditionGroup::parse_from_str(&caps["conditions"])?;
303 Ok(TransactionOuputConditionGroup::Brackets(Box::new(
304 inner_conditions,
305 )))
306 } else if let Some(caps) = OUPUT_CONDS_AND.captures(conditions) {
307 let conditions_group_1 =
308 TransactionOuputConditionGroup::parse_from_str(&caps["conditions_group_1"])?;
309 let conditions_group_2 =
310 TransactionOuputConditionGroup::parse_from_str(&caps["conditions_group_2"])?;
311 Ok(TransactionOuputConditionGroup::And(
312 Box::new(conditions_group_1),
313 Box::new(conditions_group_2),
314 ))
315 } else if let Some(caps) = OUPUT_CONDS_OR.captures(conditions) {
316 let conditions_group_1 =
317 TransactionOuputConditionGroup::parse_from_str(&caps["conditions_group_1"])?;
318 let conditions_group_2 =
319 TransactionOuputConditionGroup::parse_from_str(&caps["conditions_group_2"])?;
320 Ok(TransactionOuputConditionGroup::Or(
321 Box::new(conditions_group_1),
322 Box::new(conditions_group_2),
323 ))
324 } else {
325 println!("fail to parse this output condition = {:?}", conditions);
326 Err(V10DocumentParsingError::InvalidInnerFormat(String::from(
327 "Transaction6",
328 )))
329 }
330 }
331}
332
333#[derive(Debug, Clone)]
335pub struct TransactionOuput {
336 pub amount: isize,
338 pub base: usize,
340 pub conditions: TransactionOuputConditionGroup,
342}
343
344impl ToString for TransactionOuput {
345 fn to_string(&self) -> String {
346 format!(
347 "{}:{}:{}",
348 self.amount,
349 self.base,
350 self.conditions.to_string()
351 )
352 }
353}
354
355impl TransactionOuput {
356 fn parse_from_str(source: &str) -> Result<TransactionOuput, V10DocumentParsingError> {
357 if let Some(caps) = OUTPUT_REGEX.captures(source) {
358 let amount = caps["amount"].parse().expect("fail to parse output amount");
359 let base = caps["base"].parse().expect("fail to parse base amount");
360 let conditions = TransactionOuputConditionGroup::parse_from_str(&caps["conditions"])?;
361 Ok(TransactionOuput {
362 conditions,
363 amount,
364 base,
365 })
366 } else {
367 Err(V10DocumentParsingError::InvalidInnerFormat(
368 "Transaction7".to_string(),
369 ))
370 }
371 }
372}
373
374#[derive(Debug, Clone)]
378pub struct TransactionDocument {
379 text: String,
384
385 currency: String,
387 blockstamp: Blockstamp,
389 locktime: u64,
391 issuers: Vec<ed25519::PublicKey>,
393 inputs: Vec<TransactionInput>,
395 unlocks: Vec<TransactionInputUnlocks>,
397 outputs: Vec<TransactionOuput>,
399 comment: String,
401 signatures: Vec<ed25519::Signature>,
403}
404
405impl Document for TransactionDocument {
406 type PublicKey = ed25519::PublicKey;
407 type CurrencyType = str;
408
409 fn version(&self) -> u16 {
410 10
411 }
412
413 fn currency(&self) -> &str {
414 &self.currency
415 }
416
417 fn issuers(&self) -> &Vec<ed25519::PublicKey> {
418 &self.issuers
419 }
420
421 fn signatures(&self) -> &Vec<ed25519::Signature> {
422 &self.signatures
423 }
424
425 fn as_bytes(&self) -> &[u8] {
426 self.as_text().as_bytes()
427 }
428}
429
430impl TextDocument for TransactionDocument {
431 fn as_text(&self) -> &str {
432 &self.text
433 }
434
435 fn generate_compact_text(&self) -> String {
436 let mut issuers_str = String::from("");
437 for issuer in self.issuers.clone() {
438 issuers_str.push_str("\n");
439 issuers_str.push_str(&issuer.to_string());
440 }
441 let mut inputs_str = String::from("");
442 for input in self.inputs.clone() {
443 inputs_str.push_str("\n");
444 inputs_str.push_str(&input.to_string());
445 }
446 let mut unlocks_str = String::from("");
447 for unlock in self.unlocks.clone() {
448 unlocks_str.push_str("\n");
449 unlocks_str.push_str(&unlock.to_string());
450 }
451 let mut outputs_str = String::from("");
452 for output in self.outputs.clone() {
453 outputs_str.push_str("\n");
454 outputs_str.push_str(&output.to_string());
455 }
456 let mut signatures_str = String::from("");
457 for sig in self.signatures.clone() {
458 signatures_str.push_str("\n");
459 signatures_str.push_str(&sig.to_string());
460 }
461 format!(
462 "TX:10:{issuers_count}:{inputs_count}:{unlocks_count}:{outputs_count}:{has_comment}:{locktime}
463{blockstamp}{issuers}{inputs}{unlocks}{outputs}\n{comment}{signatures}",
464 issuers_count = self.issuers.len(),
465 inputs_count = self.inputs.len(),
466 unlocks_count = self.unlocks.len(),
467 outputs_count = self.outputs.len(),
468 has_comment = if self.comment.is_empty() { 0 } else { 1 },
469 locktime = self.locktime,
470 blockstamp = self.blockstamp,
471 issuers = issuers_str,
472 inputs = inputs_str,
473 unlocks = unlocks_str,
474 outputs = outputs_str,
475 comment = self.comment,
476 signatures = signatures_str,
477 )
478 }
479}
480
481impl IntoSpecializedDocument<BlockchainProtocol> for TransactionDocument {
482 fn into_specialized(self) -> BlockchainProtocol {
483 BlockchainProtocol::V10(Box::new(V10Document::Transaction(Box::new(self))))
484 }
485}
486
487#[derive(Debug, Copy, Clone)]
489pub struct TransactionDocumentBuilder<'a> {
490 pub currency: &'a str,
492 pub blockstamp: &'a Blockstamp,
494 pub locktime: &'a u64,
496 pub issuers: &'a Vec<ed25519::PublicKey>,
498 pub inputs: &'a Vec<TransactionInput>,
500 pub unlocks: &'a Vec<TransactionInputUnlocks>,
502 pub outputs: &'a Vec<TransactionOuput>,
504 pub comment: &'a str,
506}
507
508impl<'a> TransactionDocumentBuilder<'a> {
509 fn build_with_text_and_sigs(
510 self,
511 text: String,
512 signatures: Vec<ed25519::Signature>,
513 ) -> TransactionDocument {
514 TransactionDocument {
515 text,
516 currency: self.currency.to_string(),
517 blockstamp: *self.blockstamp,
518 locktime: *self.locktime,
519 issuers: self.issuers.clone(),
520 inputs: self.inputs.clone(),
521 unlocks: self.unlocks.clone(),
522 outputs: self.outputs.clone(),
523 comment: String::from(self.comment),
524 signatures,
525 }
526 }
527}
528
529impl<'a> DocumentBuilder for TransactionDocumentBuilder<'a> {
530 type Document = TransactionDocument;
531 type PrivateKey = ed25519::PrivateKey;
532
533 fn build_with_signature(&self, signatures: Vec<ed25519::Signature>) -> TransactionDocument {
534 self.build_with_text_and_sigs(self.generate_text(), signatures)
535 }
536
537 fn build_and_sign(&self, private_keys: Vec<ed25519::PrivateKey>) -> TransactionDocument {
538 let (text, signatures) = self.build_signed_text(private_keys);
539 self.build_with_text_and_sigs(text, signatures)
540 }
541}
542
543impl<'a> TextDocumentBuilder for TransactionDocumentBuilder<'a> {
544 fn generate_text(&self) -> String {
545 let mut issuers_string: String = "".to_owned();
546 let mut inputs_string: String = "".to_owned();
547 let mut unlocks_string: String = "".to_owned();
548 let mut outputs_string: String = "".to_owned();
549 for issuer in self.issuers {
550 issuers_string.push_str(&format!("{}\n", issuer.to_string()))
551 }
552 for input in self.inputs {
553 inputs_string.push_str(&format!("{}\n", input.to_string()))
554 }
555 for unlock in self.unlocks {
556 unlocks_string.push_str(&format!("{}\n", unlock.to_string()))
557 }
558 for output in self.outputs {
559 outputs_string.push_str(&format!("{}\n", output.to_string()))
560 }
561 format!(
562 "Version: 10
563Type: Transaction
564Currency: {currency}
565Blockstamp: {blockstamp}
566Locktime: {locktime}
567Issuers:
568{issuers}Inputs:
569{inputs}Unlocks:
570{unlocks}Outputs:
571{outputs}Comment: {comment}
572",
573 currency = self.currency,
574 blockstamp = self.blockstamp,
575 locktime = self.locktime,
576 issuers = issuers_string,
577 inputs = inputs_string,
578 unlocks = unlocks_string,
579 outputs = outputs_string,
580 comment = self.comment,
581 )
582 }
583}
584
585#[derive(Debug, Clone, Copy)]
587pub struct TransactionDocumentParser;
588
589impl StandardTextDocumentParser for TransactionDocumentParser {
590 fn parse_standard(
591 doc: &str,
592 body: &str,
593 currency: &str,
594 signatures: Vec<ed25519::Signature>,
595 ) -> Result<V10Document, V10DocumentParsingError> {
596 let tx_regex: Regex = RegexBuilder::new(&TRANSACTION_REGEX_BUILDER)
597 .size_limit(**TRANSACTION_REGEX_SIZE)
598 .build()
599 .expect("fail to build TRANSACTION_REGEX !");
600 if let Some(caps) = tx_regex.captures(body) {
601 let blockstamp =
602 Blockstamp::from_string(&caps["blockstamp"]).expect("fail to parse blockstamp");
603 let locktime = caps["locktime"].parse().expect("fail to parse locktime");
604 let issuers_str = &caps["issuers"];
605 let inputs = &caps["inputs"];
606 let unlocks = &caps["unlocks"];
607 let outputs = &caps["outputs"];
608 let comment = String::from(&caps["comment"]);
609
610 let mut issuers = Vec::new();
611 for caps in ISSUER_REGEX.captures_iter(issuers_str) {
612 issuers.push(
613 ed25519::PublicKey::from_base58(&caps["issuer"]).expect("fail to parse issuer"),
614 );
615 }
616 let inputs_array: Vec<&str> = inputs.split('\n').collect();
617 let mut inputs = Vec::new();
618 for input in inputs_array {
619 if !input.is_empty() {
620 inputs.push(TransactionInput::parse_from_str(input)?);
621 }
622 }
623 let unlocks_array: Vec<&str> = unlocks.split('\n').collect();
624 let mut unlocks = Vec::new();
625 for unlock in unlocks_array {
626 if !unlock.is_empty() {
627 unlocks.push(TransactionInputUnlocks::parse_from_str(unlock)?);
628 }
629 }
630 let outputs_array: Vec<&str> = outputs.split('\n').collect();
631 let mut outputs = Vec::new();
632 for output in outputs_array {
633 if !output.is_empty() {
634 outputs.push(TransactionOuput::parse_from_str(output)?);
635 }
636 }
637
638 Ok(V10Document::Transaction(Box::new(TransactionDocument {
639 text: doc.to_owned(),
640 currency: currency.to_owned(),
641 blockstamp,
642 locktime,
643 issuers,
644 inputs,
645 unlocks,
646 outputs,
647 comment,
648 signatures,
649 })))
650 } else {
651 Err(V10DocumentParsingError::InvalidInnerFormat(
652 "Transaction1".to_string(),
653 ))
654 }
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661 use duniter_crypto::keys::{PrivateKey, PublicKey, Signature};
662 use blockchain::{Document, VerificationResult};
663
664 #[test]
665 fn generate_real_document() {
666 let pubkey = ed25519::PublicKey::from_base58(
667 "DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV",
668 ).unwrap();
669
670 let prikey = ed25519::PrivateKey::from_base58(
671 "468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5G\
672 iERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7",
673 ).unwrap();
674
675 let sig = ed25519::Signature::from_base64(
676 "pRQeKlzCsvPNmYAAkEP5jPPQO1RwrtFMRfCajEfkkrG0UQE0DhoTkxG3Zs2JFmvAFLw67pn1V5NQ08zsSfJkBg==",
677 ).unwrap();
678
679 let block = Blockstamp::from_string(
680 "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
681 ).unwrap();
682
683 let builder = TransactionDocumentBuilder {
684 currency: "duniter_unit_test_currency",
685 blockstamp: &block,
686 locktime: &0,
687 issuers: &vec![pubkey],
688 inputs: &vec![
689 TransactionInput::parse_from_str(
690 "10:0:D:DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV:0",
691 ).expect("fail to parse input !"),
692 ],
693 unlocks: &vec![
694 TransactionInputUnlocks::parse_from_str("0:SIG(0)")
695 .expect("fail to parse unlock !"),
696 ],
697 outputs: &vec![
698 TransactionOuput::parse_from_str(
699 "10:0:SIG(FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa)",
700 ).expect("fail to parse output !"),
701 ],
702 comment: "test",
703 };
704 println!(
705 "Signature = {:?}",
706 builder.build_and_sign(vec![prikey]).signatures()
707 );
708 assert_eq!(
709 builder.build_with_signature(vec![sig]).verify_signatures(),
710 VerificationResult::Valid()
711 );
712 assert_eq!(
713 builder.build_and_sign(vec![prikey]).verify_signatures(),
714 VerificationResult::Valid()
715 );
716 }
717
718 #[test]
719 fn transaction_standard_regex() {
720 let tx_regex: Regex = RegexBuilder::new(&TRANSACTION_REGEX_BUILDER)
721 .size_limit(**TRANSACTION_REGEX_SIZE)
722 .build()
723 .expect("fail to build TRANSACTION_REGEX !");
724 assert!(tx_regex.is_match(
725 "Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
726Locktime: 0
727Issuers:
728HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY
729CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp
730FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa
731Inputs:
73240:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2
73370:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8
73420:2:D:HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY:46
73570:2:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3
73620:2:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5
73715:2:D:FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa:46
738Unlocks:
7390:SIG(0)
7401:XHX(7665798292)
7412:SIG(0)
7423:SIG(0) SIG(2)
7434:SIG(0) SIG(1) SIG(2)
7445:SIG(2)
745Outputs:
746120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
747146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)
74849:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85))
749Comment: -----@@@----- (why not this comment?)
750"
751 ));
752 }
753
754 #[test]
755 fn parse_transaction_document() {
756 let doc = "Version: 10
757Type: Transaction
758Currency: duniter_unit_test_currency
759Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
760Locktime: 0
761Issuers:
762DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
7634tNQ7d9pj2Da5wUVoW9mFn7JjuPoowF977au8DdhEjVR
764FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa
765Inputs:
76640:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2
76770:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8
76820:2:D:DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV:46
76970:2:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3
77020:2:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5
77115:2:D:FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa:46
772Unlocks:
7730:SIG(0)
7741:XHX(7665798292)
7752:SIG(0)
7763:SIG(0) SIG(2)
7774:SIG(0) SIG(1) SIG(2)
7785:SIG(2)
779Outputs:
780120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
781146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)
78249:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85F2AC2FD3FA4FDC46A4FC01CA))
783Comment: -----@@@----- (why not this comment?)
784";
785
786 let body =
787 "Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
788Locktime: 0
789Issuers:
790DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
7914tNQ7d9pj2Da5wUVoW9mFn7JjuPoowF977au8DdhEjVR
792FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa
793Inputs:
79440:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2
79570:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8
79620:2:D:DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV:46
79770:2:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3
79820:2:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5
79915:2:D:FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa:46
800Unlocks:
8010:SIG(0)
8021:XHX(7665798292)
8032:SIG(0)
8043:SIG(0) SIG(2)
8054:SIG(0) SIG(1) SIG(2)
8065:SIG(2)
807Outputs:
808120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
809146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)
81049:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85F2AC2FD3FA4FDC46A4FC01CA))
811Comment: -----@@@----- (why not this comment?)
812";
813
814 let currency = "duniter_unit_test_currency";
815
816 let signatures = vec![
817 Signature::from_base64(
818"kL59C1izKjcRN429AlKdshwhWbasvyL7sthI757zm1DfZTdTIctDWlKbYeG/tS7QyAgI3gcfrTHPhu1E1lKCBw=="
819 ).expect("fail to parse test signature"),
820 Signature::from_base64(
821"e3LpgB2RZ/E/BCxPJsn+TDDyxGYzrIsMyDt//KhJCjIQD6pNUxr5M5jrq2OwQZgwmz91YcmoQ2XRQAUDpe4BAw=="
822 ).expect("fail to parse test signature"),
823 Signature::from_base64(
824"w69bYgiQxDmCReB0Dugt9BstXlAKnwJkKCdWvCeZ9KnUCv0FJys6klzYk/O/b9t74tYhWZSX0bhETWHiwfpWBw=="
825 ).expect("fail to parse test signature"),
826 ];
827
828 let doc = TransactionDocumentParser::parse_standard(doc, body, currency, signatures)
829 .expect("fail to parse test transaction document !");
830 if let V10Document::Transaction(doc) = doc {
831 println!("{}", doc.generate_compact_text());
833 assert_eq!(doc.verify_signatures(), VerificationResult::Valid());
834 assert_eq!(
835 doc.generate_compact_text(),
836 "TX:10:3:6:6:3:1:0
837204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
838DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
8394tNQ7d9pj2Da5wUVoW9mFn7JjuPoowF977au8DdhEjVR
840FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa
84140:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2
84270:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8
84320:2:D:DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV:46
84470:2:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3
84520:2:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5
84615:2:D:FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa:46
8470:SIG(0)
8481:XHX(7665798292)
8492:SIG(0)
8503:SIG(0) SIG(2)
8514:SIG(0) SIG(1) SIG(2)
8525:SIG(2)
853120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
854146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)
85549:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85F2AC2FD3FA4FDC46A4FC01CA))
856-----@@@----- (why not this comment?)
857kL59C1izKjcRN429AlKdshwhWbasvyL7sthI757zm1DfZTdTIctDWlKbYeG/tS7QyAgI3gcfrTHPhu1E1lKCBw==
858e3LpgB2RZ/E/BCxPJsn+TDDyxGYzrIsMyDt//KhJCjIQD6pNUxr5M5jrq2OwQZgwmz91YcmoQ2XRQAUDpe4BAw==
859w69bYgiQxDmCReB0Dugt9BstXlAKnwJkKCdWvCeZ9KnUCv0FJys6klzYk/O/b9t74tYhWZSX0bhETWHiwfpWBw=="
860 );
861 } else {
862 panic!("Wrong document type");
863 }
864 }
865}