safe_dbc/
mint.rs

1// Copyright 2021 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9// Code required to mint Dbcs
10// The in the most basic terms means
11// a valid input DBC can be split into
12// 1 or more DBCs as long as
13// input is vaid
14// Outputs <= input value
15
16use std::collections::{BTreeMap, BTreeSet, HashSet};
17
18use crate::{
19    Dbc, DbcContent, DbcContentHash, DbcTransaction, Error, KeyCache, KeyManager, PublicKey,
20    Result, Signature,
21};
22
23pub type InputSignatures = BTreeMap<DbcContentHash, (PublicKey, Signature)>;
24
25#[derive(Default)]
26struct SpendBook {
27    transactions: BTreeMap<DbcContentHash, DbcTransaction>,
28}
29
30impl SpendBook {
31    fn lookup(&self, dbc_hash: &DbcContentHash) -> Option<&DbcTransaction> {
32        self.transactions.get(dbc_hash)
33    }
34
35    fn log(&mut self, dbc_hash: DbcContentHash, transaction: DbcTransaction) {
36        self.transactions.insert(dbc_hash, transaction);
37    }
38}
39
40#[derive(Debug, Clone)]
41pub struct MintRequest {
42    pub inputs: HashSet<Dbc>,
43    pub outputs: HashSet<DbcContent>,
44}
45
46impl MintRequest {
47    pub fn to_transaction(&self) -> DbcTransaction {
48        DbcTransaction {
49            inputs: self.inputs.iter().map(|i| i.name()).collect(),
50            outputs: self.outputs.iter().map(|i| i.hash()).collect(),
51        }
52    }
53
54    pub fn verify_transaction_balances(&self) -> Result<()> {
55        let input: u64 = self.inputs.iter().map(|input| input.amount()).sum();
56        let output: u64 = self.outputs.iter().map(|output| output.amount).sum();
57        if input != output {
58            Err(Error::DbcMintRequestDoesNotBalance { input, output })
59        } else {
60            Ok(())
61        }
62    }
63}
64
65pub struct Mint {
66    pub(crate) key_mgr: KeyManager,
67    spendbook: SpendBook,
68}
69
70impl Mint {
71    pub fn genesis(amount: u64) -> (Self, Dbc) {
72        let key_mgr = KeyManager::new_genesis();
73
74        let genesis_input = [0u8; 32];
75
76        let parents = vec![genesis_input].into_iter().collect();
77        let content = DbcContent::new(parents, amount, 0);
78
79        let transaction = DbcTransaction {
80            inputs: vec![genesis_input].into_iter().collect(),
81            outputs: vec![content.hash()].into_iter().collect(),
82        };
83
84        let mut spendbook = SpendBook::default();
85        spendbook.log(genesis_input, transaction.clone());
86
87        let transaction_sig = key_mgr.sign(&transaction.hash());
88
89        let dbc = Dbc {
90            content,
91            transaction,
92            transaction_sigs: vec![(genesis_input, (key_mgr.public_key(), transaction_sig))]
93                .into_iter()
94                .collect(),
95        };
96
97        (Self { key_mgr, spendbook }, dbc)
98    }
99
100    pub fn key_cache(&self) -> &KeyCache {
101        self.key_mgr.key_cache()
102    }
103
104    pub fn public_key(&self) -> PublicKey {
105        self.key_mgr.public_key()
106    }
107
108    pub fn reissue(
109        &mut self,
110        mint_request: MintRequest,
111    ) -> Result<(DbcTransaction, InputSignatures)> {
112        mint_request.verify_transaction_balances()?;
113        let transaction = mint_request.to_transaction();
114
115        self.validate_transaction_input_dbcs(&mint_request.inputs)?;
116        self.validate_transaction_outputs(&transaction.inputs, &mint_request.outputs)?;
117
118        let transaction_sigs = self.sign_transaction(&transaction);
119
120        for input in mint_request.inputs.iter() {
121            self.spendbook.log(input.name(), transaction.clone());
122        }
123
124        Ok((transaction, transaction_sigs))
125    }
126
127    fn validate_transaction_input_dbcs(&self, inputs: &HashSet<Dbc>) -> Result<()> {
128        if inputs.is_empty() {
129            return Err(Error::TransactionMustHaveAnInput);
130        }
131
132        for input in inputs.iter() {
133            input.confirm_valid(self.key_cache())?;
134
135            if let Some(transaction) = self.spendbook.lookup(&input.name()).cloned() {
136                // This input has already been spent, return the transaction to the user
137                let transaction_sigs = self.sign_transaction(&transaction);
138                return Err(Error::DbcAlreadySpent {
139                    transaction,
140                    transaction_sigs,
141                });
142            }
143        }
144
145        Ok(())
146    }
147
148    fn validate_transaction_outputs(
149        &self,
150        inputs: &BTreeSet<DbcContentHash>,
151        outputs: &HashSet<DbcContent>,
152    ) -> Result<()> {
153        let number_set = outputs
154            .iter()
155            .map(|dbc_content| dbc_content.output_number.into())
156            .collect::<BTreeSet<_>>();
157
158        let expected_number_set = (0..outputs.len()).into_iter().collect::<BTreeSet<_>>();
159
160        if number_set != expected_number_set {
161            return Err(Error::OutputsAreNotNumberedCorrectly);
162        }
163
164        if outputs.iter().any(|o| &o.parents != inputs) {
165            return Err(Error::DbcContentParentsDifferentFromTransactionInputs);
166        }
167
168        Ok(())
169    }
170
171    fn sign_transaction(
172        &self,
173        transaction: &DbcTransaction,
174    ) -> BTreeMap<DbcContentHash, (PublicKey, Signature)> {
175        let sig = self.key_mgr.sign(&transaction.hash());
176
177        transaction
178            .inputs
179            .iter()
180            .copied()
181            .zip(std::iter::repeat((self.public_key(), sig)))
182            .collect()
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use std::collections::BTreeSet;
190
191    use quickcheck_macros::quickcheck;
192
193    use crate::tests::{TinyInt, TinyVec};
194
195    #[quickcheck]
196    fn prop_genesis() {
197        let (mint, genesis_dbc) = Mint::genesis(1000);
198        assert_eq!(genesis_dbc.content.amount, 1000);
199        let validation = genesis_dbc.confirm_valid(&mint.key_cache());
200        println!("Genesis DBC Validation {:?}", validation);
201        assert!(validation.is_ok());
202    }
203
204    #[quickcheck]
205    fn prop_splitting_the_genesis_dbc(output_amounts: TinyVec<TinyInt>) {
206        let output_amounts: Vec<u64> = output_amounts
207            .vec()
208            .into_iter()
209            .map(TinyInt::coerce)
210            .collect();
211        let output_amount = output_amounts.iter().sum();
212
213        let (mut genesis, genesis_dbc) = Mint::genesis(output_amount);
214
215        let inputs: HashSet<_> = vec![genesis_dbc].into_iter().collect();
216        let input_hashes: BTreeSet<_> = inputs.iter().map(|in_dbc| in_dbc.name()).collect();
217
218        let outputs = output_amounts
219            .iter()
220            .enumerate()
221            .map(|(i, amount)| DbcContent::new(input_hashes.clone(), *amount, i as u8))
222            .collect();
223
224        let mint_request = MintRequest { inputs, outputs };
225
226        let (transaction, transaction_sigs) = genesis.reissue(mint_request.clone()).unwrap();
227
228        // Verify transaction returned to us by the Mint matches our request
229        assert_eq!(mint_request.to_transaction(), transaction);
230        assert_eq!(
231            transaction.inputs,
232            mint_request.inputs.iter().map(|i| i.name()).collect()
233        );
234        assert_eq!(
235            transaction.outputs,
236            mint_request.outputs.iter().map(|o| o.hash()).collect()
237        );
238
239        // Verify signatures corespond to each input
240        let (pubkey, sig) = transaction_sigs.values().cloned().next().unwrap();
241        for input in mint_request.inputs.iter() {
242            assert_eq!(transaction_sigs.get(&input.name()), Some(&(pubkey, sig)));
243        }
244        assert_eq!(transaction_sigs.len(), transaction.inputs.len());
245
246        let output_dbcs: Vec<_> = mint_request
247            .outputs
248            .into_iter()
249            .map(|content| Dbc {
250                content,
251                transaction: transaction.clone(),
252                transaction_sigs: transaction_sigs.clone(),
253            })
254            .collect();
255
256        let key_cache = KeyCache::from(vec![genesis.public_key()]);
257        for dbc in output_dbcs.iter() {
258            let expected_amount: u64 = output_amounts[dbc.content.output_number as usize];
259            assert_eq!(dbc.amount(), expected_amount);
260            assert!(dbc.confirm_valid(&key_cache).is_ok());
261        }
262
263        assert_eq!(
264            output_dbcs.iter().map(|dbc| dbc.amount()).sum::<u64>(),
265            output_amount
266        );
267    }
268
269    #[test]
270    fn test_double_spend_protection() {
271        let (mut genesis, genesis_dbc) = Mint::genesis(1000);
272
273        let inputs: HashSet<_> = vec![genesis_dbc.clone()].into_iter().collect();
274        let input_hashes: BTreeSet<_> = vec![genesis_dbc.name()].into_iter().collect();
275
276        let mint_request = MintRequest {
277            inputs: inputs.clone(),
278            outputs: vec![DbcContent::new(input_hashes.clone(), 1000, 0)]
279                .into_iter()
280                .collect(),
281        };
282
283        let (t, s) = genesis.reissue(mint_request).unwrap();
284
285        let double_spend_mint_request = MintRequest {
286            inputs,
287            outputs: vec![DbcContent::new(input_hashes, 1000, 1)]
288                .into_iter()
289                .collect(),
290        };
291
292        let res = genesis.reissue(double_spend_mint_request);
293
294        assert!(matches!(
295            res,
296            Err(Error::DbcAlreadySpent { transaction, transaction_sigs }) if transaction == t && transaction_sigs == s
297        ));
298    }
299
300    #[quickcheck]
301    fn prop_dbc_transaction_many_to_many(
302        // the amount of each input transaction
303        input_amounts: TinyVec<TinyInt>,
304        // The output_number and amount for each transaction output
305        output_amounts: TinyVec<(TinyInt, TinyInt)>,
306        // Outputs with output_numbers that appear in this vec will
307        // have extra parents inserted into the transaction
308        extra_output_parents: TinyVec<TinyInt>,
309    ) {
310        let input_amounts: Vec<u64> = input_amounts
311            .vec()
312            .into_iter()
313            .map(TinyInt::coerce)
314            .collect();
315
316        let output_amounts: Vec<(u8, u64)> = output_amounts
317            .vec()
318            .into_iter()
319            .map(|(number, amount)| (number.coerce(), amount.coerce()))
320            .collect();
321
322        let extra_output_parents: Vec<u8> = extra_output_parents
323            .vec()
324            .into_iter()
325            .map(TinyInt::coerce)
326            .collect();
327
328        let genesis_amount: u64 = input_amounts.iter().sum();
329
330        let (mut genesis, genesis_dbc) = Mint::genesis(genesis_amount);
331
332        let gen_inputs: HashSet<_> = vec![genesis_dbc].into_iter().collect();
333        let gen_input_hashes: BTreeSet<_> = gen_inputs.iter().map(Dbc::name).collect();
334        let input_content: HashSet<_> = input_amounts
335            .iter()
336            .enumerate()
337            .map(|(i, amount)| DbcContent::new(gen_input_hashes.clone(), *amount, i as u8))
338            .collect();
339
340        let mint_request = MintRequest {
341            inputs: gen_inputs,
342            outputs: input_content.clone(),
343        };
344
345        let (transaction, transaction_sigs) = genesis.reissue(mint_request).unwrap();
346
347        let input_dbcs: HashSet<_> = input_content
348            .into_iter()
349            .map(|content| Dbc {
350                content,
351                transaction: transaction.clone(),
352                transaction_sigs: transaction_sigs.clone(),
353            })
354            .collect();
355
356        let input_hashes: BTreeSet<_> = input_dbcs.iter().map(Dbc::name).collect();
357
358        let outputs: HashSet<_> = output_amounts
359            .iter()
360            .map(|(output_number, amount)| {
361                let mut fuzzed_parents = input_hashes.clone();
362
363                for _ in extra_output_parents
364                    .iter()
365                    .filter(|idx| idx == &output_number)
366                {
367                    fuzzed_parents.insert(rand::random());
368                }
369
370                DbcContent::new(fuzzed_parents, *amount, *output_number)
371            })
372            .collect();
373
374        let mint_request = MintRequest {
375            inputs: input_dbcs,
376            outputs: outputs.clone(),
377        };
378
379        let many_to_many_result = genesis.reissue(mint_request);
380
381        let output_amount: u64 = outputs.iter().map(|output| output.amount).sum();
382        let number_of_fuzzed_output_parents = extra_output_parents
383            .into_iter()
384            .collect::<BTreeSet<_>>()
385            .intersection(&output_amounts.iter().map(|(n, _)| *n).collect())
386            .count();
387
388        match many_to_many_result {
389            Ok((transaction, transaction_sigs)) => {
390                assert_eq!(genesis_amount, output_amount);
391                assert_eq!(number_of_fuzzed_output_parents, 0);
392
393                // The output amounts should correspond to the output_amounts
394                assert_eq!(
395                    outputs.iter().map(|o| o.amount).collect::<BTreeSet<_>>(),
396                    output_amounts.into_iter().map(|(_, a)| a).collect()
397                );
398
399                // The outputs should have been uniquely number from 0 to N (N = # of outputs)
400                assert_eq!(
401                    outputs
402                        .iter()
403                        .map(|content| content.output_number as usize)
404                        .collect::<BTreeSet<_>>(),
405                    (0..outputs.len()).into_iter().collect()
406                );
407
408                let output_dbcs: Vec<_> = outputs
409                    .into_iter()
410                    .map(|content| Dbc {
411                        content,
412                        transaction: transaction.clone(),
413                        transaction_sigs: transaction_sigs.clone(),
414                    })
415                    .collect();
416
417                for dbc in output_dbcs.iter() {
418                    let dbc_confirm_result =
419                        dbc.confirm_valid(&KeyCache::from(vec![genesis.public_key()]));
420                    println!("DBC confirm result {:?}", dbc_confirm_result);
421                    assert!(dbc_confirm_result.is_ok());
422                }
423
424                assert_eq!(
425                    output_dbcs.iter().map(|dbc| dbc.amount()).sum::<u64>(),
426                    output_amount
427                );
428            }
429            Err(Error::DbcMintRequestDoesNotBalance { .. }) => {
430                assert_ne!(genesis_amount, output_amount);
431            }
432            Err(Error::TransactionMustHaveAnInput) => {
433                assert_eq!(input_amounts.len(), 0);
434            }
435            Err(Error::OutputsAreNotNumberedCorrectly) => {
436                assert_ne!(
437                    outputs
438                        .iter()
439                        .map(|content| content.output_number as usize)
440                        .collect::<BTreeSet<_>>(),
441                    (0..outputs.len()).into_iter().collect()
442                );
443            }
444            Err(Error::DbcContentParentsDifferentFromTransactionInputs) => {
445                assert_ne!(number_of_fuzzed_output_parents, 0)
446            }
447            err => panic!("Unexpected reissue err {:#?}", err),
448        }
449    }
450
451    #[quickcheck]
452    #[ignore]
453    fn prop_in_progress_transaction_can_be_continued_across_churn() {
454        todo!()
455    }
456
457    #[quickcheck]
458    #[ignore]
459    fn prop_reject_invalid_prefix() {
460        todo!();
461    }
462
463    #[quickcheck]
464    #[ignore]
465    fn prop_input_ownership_proofs_are_checked() {
466        todo!();
467    }
468
469    #[test]
470    fn test_inputs_are_validated() {
471        let (mut genesis, _) = Mint::genesis(1000);
472
473        let input_content = DbcContent {
474            parents: Default::default(),
475            amount: 100,
476            output_number: 0,
477        };
478        let input_content_hashes: BTreeSet<_> = vec![input_content.hash()].into_iter().collect();
479
480        let fraudulant_reissue_result = genesis.reissue(MintRequest {
481            inputs: vec![Dbc {
482                content: input_content,
483                transaction: DbcTransaction {
484                    inputs: Default::default(),
485                    outputs: input_content_hashes.clone(),
486                },
487                transaction_sigs: Default::default(),
488            }]
489            .into_iter()
490            .collect(),
491            outputs: vec![DbcContent {
492                parents: input_content_hashes,
493                amount: 100,
494                output_number: 0,
495            }]
496            .into_iter()
497            .collect(),
498        });
499        assert!(fraudulant_reissue_result.is_err());
500    }
501}