duniter_documents/blockchain/v10/documents/
transaction.rs

1//  Copyright (C) 2018  The Duniter Project Developers.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Wrappers around Transaction documents.
17
18use 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/// Wrap a transaction input
59#[derive(Debug, Clone)]
60pub enum TransactionInput {
61    /// Universal Dividend Input
62    D(isize, usize, ed25519::PublicKey, u64),
63    /// Previous Transaction Input
64    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        //Ok(TransactionInput::D(10, 0, PublicKey::from_base58("FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa").unwrap(), 0))
96        } 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/// Wrap a transaction unlock proof
117#[derive(Debug, Clone)]
118pub enum TransactionUnlockProof {
119    /// Indicates that the signature of the corresponding key is at the bottom of the document
120    Sig(usize),
121    /// Provides the code to unlock the corresponding funds
122    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/// Wrap a transaction unlocks input
153#[derive(Debug, Clone)]
154pub struct TransactionInputUnlocks {
155    /// Input index
156    pub index: usize,
157    /// List of proof to unlock funds
158    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/// Wrap a transaction ouput condition
198#[derive(Debug, Clone)]
199pub enum TransactionOuputCondition {
200    /// The consumption of funds will require a valid signature of the specified key
201    Sig(ed25519::PublicKey),
202    /// The consumption of funds will require to provide a code with the hash indicated
203    Xhx(String),
204    /// Funds may not be consumed until the blockchain reaches the timestamp indicated.
205    Cltv(u64),
206    /// Funds may not be consumed before the duration indicated, starting from the timestamp of the block where the transaction is written.
207    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/// Wrap a transaction ouput condition group
251#[derive(Debug, Clone)]
252pub enum TransactionOuputConditionGroup {
253    /// Single
254    Single(TransactionOuputCondition),
255    /// Brackets
256    Brackets(Box<TransactionOuputConditionGroup>),
257    /// And operator
258    And(
259        Box<TransactionOuputConditionGroup>,
260        Box<TransactionOuputConditionGroup>,
261    ),
262    /// Or operator
263    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/// Wrap a transaction ouput
334#[derive(Debug, Clone)]
335pub struct TransactionOuput {
336    /// Amount
337    pub amount: isize,
338    /// Base
339    pub base: usize,
340    /// List of conditions for consum this output
341    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/// Wrap a Transaction document.
375///
376/// Must be created by parsing a text document or using a builder.
377#[derive(Debug, Clone)]
378pub struct TransactionDocument {
379    /// Document as text.
380    ///
381    /// Is used to check signatures, and other values
382    /// must be extracted from it.
383    text: String,
384
385    /// Currency.
386    currency: String,
387    /// Blockstamp
388    blockstamp: Blockstamp,
389    /// Locktime
390    locktime: u64,
391    /// Document issuer (there should be only one).
392    issuers: Vec<ed25519::PublicKey>,
393    /// Transaction inputs.
394    inputs: Vec<TransactionInput>,
395    /// Inputs unlocks.
396    unlocks: Vec<TransactionInputUnlocks>,
397    /// Transaction outputs.
398    outputs: Vec<TransactionOuput>,
399    /// Transaction comment
400    comment: String,
401    /// Document signature (there should be only one).
402    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/// Transaction document builder.
488#[derive(Debug, Copy, Clone)]
489pub struct TransactionDocumentBuilder<'a> {
490    /// Document currency.
491    pub currency: &'a str,
492    /// Reference blockstamp.
493    pub blockstamp: &'a Blockstamp,
494    /// Locktime
495    pub locktime: &'a u64,
496    /// Transaction Document issuers.
497    pub issuers: &'a Vec<ed25519::PublicKey>,
498    /// Transaction inputs.
499    pub inputs: &'a Vec<TransactionInput>,
500    /// Inputs unlocks.
501    pub unlocks: &'a Vec<TransactionInputUnlocks>,
502    /// Transaction ouputs.
503    pub outputs: &'a Vec<TransactionOuput>,
504    /// Transaction comment
505    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/// Transaction document parser
586#[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 : {:?}", doc);
832            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}