1use 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 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 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 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 input_amounts: TinyVec<TinyInt>,
304 output_amounts: TinyVec<(TinyInt, TinyInt)>,
306 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 assert_eq!(
395 outputs.iter().map(|o| o.amount).collect::<BTreeSet<_>>(),
396 output_amounts.into_iter().map(|(_, a)| a).collect()
397 );
398
399 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}