Skip to content

Commit

Permalink
fix(mint): validate output numbering
Browse files Browse the repository at this point in the history
  • Loading branch information
davidrusu committed May 11, 2021
1 parent b8fdc93 commit 64f67df
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 19 deletions.
9 changes: 4 additions & 5 deletions src/dbc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ mod tests {

use quickcheck_macros::quickcheck;

use crate::tests::TinyInt;
use crate::tests::{NonZeroTinyInt, TinyInt};
use crate::{Mint, MintRequest};

fn divide(amount: u64, n_ways: u8) -> impl Iterator<Item = u64> {
Expand Down Expand Up @@ -110,9 +110,8 @@ mod tests {
}

#[quickcheck]
#[ignore]
fn prop_dbc_validation(
n_inputs: TinyInt, // # of input DBC's
n_inputs: NonZeroTinyInt, // # of input DBC's
n_valid_sigs: TinyInt, // # of valid sigs
n_wrong_signer_sigs: TinyInt, // # of valid sigs from unrecognized authority
n_wrong_msg_sigs: TinyInt, // # of sigs from recognized authority signing wrong message
Expand Down Expand Up @@ -229,7 +228,7 @@ mod tests {
Ok(()) => {
assert!(dbc.transaction.outputs.contains(&dbc.content.hash()));
assert!(n_inputs.coerce::<u8>() > 0);
assert!(n_valid_sigs >= n_inputs);
assert!(n_valid_sigs.coerce::<u8>() >= n_inputs.coerce::<u8>());
assert_eq!(dbc.amount(), amount);
assert_eq!(n_extra_input_sigs.coerce::<u8>(), 0);
assert_eq!(n_wrong_signer_sigs.coerce::<u8>(), 0);
Expand All @@ -239,7 +238,7 @@ mod tests {
assert_eq!(n_drop_parents.coerce::<u8>(), 0);
}
Err(Error::MissingSignatureForInput) => {
assert!(n_valid_sigs < n_inputs);
assert!(n_valid_sigs.coerce::<u8>() < n_inputs.coerce::<u8>());
}
Err(Error::Ed25519(_)) => {
assert!(n_wrong_msg_sigs.coerce::<u8>() > 0);
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub enum Error {
MissingSignatureForInput,
#[error("Mint request doesn't balance out sum(input) == sum(output)")]
DbcMintRequestDoesNotBalance,
#[error("Outputs must be numbered 0..N where N = # of outputs")]
OutputsAreNotNumberedCorrectly,
#[error("DBC already spent in transaction: {transaction:?}")]
DbcAlreadySpent {
transaction: crate::DbcTransaction,
Expand Down
25 changes: 25 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,31 @@ mod tests {
}
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct NonZeroTinyInt(u8);

impl NonZeroTinyInt {
pub fn coerce<T: From<u8>>(self) -> T {
self.0.into()
}
}

impl std::fmt::Debug for NonZeroTinyInt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl Arbitrary for NonZeroTinyInt {
fn arbitrary(g: &mut Gen) -> Self {
Self(u8::arbitrary(g) % 4 + 1)
}

fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new((0..(self.0)).into_iter().rev().map(Self))
}
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct TinyVec<T>(Vec<T>);

Expand Down
62 changes: 48 additions & 14 deletions src/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// input is vaid
// Outputs <= input value

use std::collections::{BTreeMap, HashSet};
use std::collections::{BTreeMap, BTreeSet, HashSet};

use crate::{
Dbc, DbcContent, DbcContentHash, DbcTransaction, Error, KeyCache, KeyManager, PublicKey,
Expand Down Expand Up @@ -112,6 +112,7 @@ impl Mint {
)> {
mint_request.verify_transaction_balances()?;
self.validate_transaction_input_dbcs(&mint_request.inputs)?;
self.validate_transaction_outputs(&mint_request.outputs)?;
let transaction = mint_request.to_transaction();
let transaction_sigs = self.sign_transaction(&transaction);

Expand Down Expand Up @@ -143,6 +144,21 @@ impl Mint {
Ok(())
}

fn validate_transaction_outputs(&self, outputs: &HashSet<DbcContent>) -> Result<()> {
let number_set = outputs
.iter()
.map(|dbc_content| dbc_content.output_number.into())
.collect::<BTreeSet<_>>();

let expected_number_set = (0..outputs.len()).into_iter().collect::<BTreeSet<_>>();

if number_set != expected_number_set {
return Err(Error::OutputsAreNotNumberedCorrectly);
}

Ok(())
}

fn sign_transaction(
&self,
transaction: &DbcTransaction,
Expand Down Expand Up @@ -272,7 +288,7 @@ mod tests {
#[quickcheck]
fn prop_dbc_transaction_many_to_many(
input_amounts: TinyVec<TinyInt>,
output_amounts: TinyVec<TinyInt>,
output_amounts: TinyVec<(TinyInt, TinyInt)>,
) {
let genesis_amount = input_amounts.vec().iter().map(|i| i.coerce::<u64>()).sum();

Expand Down Expand Up @@ -308,8 +324,13 @@ mod tests {
let outputs: HashSet<_> = output_amounts
.vec()
.iter()
.enumerate()
.map(|(i, amount)| DbcContent::new(input_hashes.clone(), amount.coerce(), i as u8))
.map(|(output_number, amount)| {
DbcContent::new(
input_hashes.clone(),
amount.coerce(),
output_number.coerce(),
)
})
.collect();

let mint_request = MintRequest {
Expand All @@ -319,10 +340,13 @@ mod tests {

let many_to_many_result = genesis.reissue(mint_request.clone());

let output_amounts_set: BTreeSet<_> = output_amounts.vec().into_iter().collect();
let output_amount: u64 = output_amounts_set
.iter()
.map(|(_, amount)| amount.coerce::<u64>())
.sum();
match many_to_many_result {
Ok((transaction, transaction_sigs)) => {
let output_amount: u64 =
output_amounts.vec().iter().map(|i| i.coerce::<u64>()).sum();
assert_eq!(genesis_amount, output_amount);

let output_dbcs: Vec<_> = outputs
Expand All @@ -345,25 +369,35 @@ mod tests {
output_dbcs.iter().map(|dbc| dbc.amount()).sum::<u64>(),
output_amount
);

// The outputs should have been uniquely number from 0 to N (N = # of outputs)
assert_eq!(
output_amounts_set
.iter()
.map(|(output_number, _)| output_number.coerce())
.collect::<BTreeSet<_>>(),
(0..output_amounts_set.len()).into_iter().collect()
);
}
Err(Error::DbcMintRequestDoesNotBalance) => {
let output_amount: u64 =
output_amounts.vec().iter().map(|i| i.coerce::<u64>()).sum();
assert_ne!(genesis_amount, output_amount);
}
Err(Error::TransactionMustHaveAnInput) => {
assert_eq!(input_amounts.vec().len(), 0);
}
Err(Error::OutputsAreNotNumberedCorrectly) => {
assert_ne!(
output_amounts_set
.iter()
.map(|(output_number, _)| output_number.coerce())
.collect::<BTreeSet<_>>(),
(0..output_amounts_set.len()).into_iter().collect()
);
}
err => panic!("Unexpected reissue err {:#?}", err),
}
}

#[quickcheck]
#[ignore]
fn prop_dbc_ensure_outputs_are_numbered_uniquely() {
todo!()
}

#[quickcheck]
#[ignore]
fn prop_in_progress_transaction_can_be_continued_across_churn() {
Expand Down

0 comments on commit 64f67df

Please sign in to comment.