Skip to content

Commit

Permalink
feat: validate tx in spentbook
Browse files Browse the repository at this point in the history
* impl From<(Amount, Scalar)> for AmountSecrets
* impl Into<Amount> for AmountSecrets
* move genesis creation boilerplate into tests::init_genesis()
* add Tx verification to SpentBookMock::log_spent()
* add SpentBookMock::log_spent_and_skip_tx_verification()
* modify tests to check errors from SpentBookMock::log_spent()
* uncomment test_inputs_are_validated() and gets it working
* uncomment and refactor test_mismatched_amount_and_commitment
  • Loading branch information
dan-da authored and dirvine committed Feb 17, 2022
1 parent 5a5dd18 commit 42fb29e
Show file tree
Hide file tree
Showing 4 changed files with 615 additions and 487 deletions.
16 changes: 16 additions & 0 deletions src/amount_secrets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,29 @@ impl From<RevealedCommitment> for AmountSecrets {
}
}

impl From<(Amount, Scalar)> for AmountSecrets {
/// create AmountSecrets from an amount and a randomly generated blinding factor
fn from(params: (Amount, Scalar)) -> Self {
let (value, blinding) = params;

Self(RevealedCommitment { value, blinding })
}
}

#[allow(clippy::from_over_into)]
impl Into<RevealedCommitment> for AmountSecrets {
fn into(self) -> RevealedCommitment {
self.0
}
}

#[allow(clippy::from_over_into)]
impl Into<Amount> for AmountSecrets {
fn into(self) -> Amount {
self.0.value()
}
}

impl TryFrom<(&SecretKey, &Ciphertext)> for AmountSecrets {
type Error = Error;

Expand Down
23 changes: 6 additions & 17 deletions src/dbc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ mod tests {

use quickcheck_macros::quickcheck;

use crate::tests::{NonZeroTinyInt, SpentBookMock, TinyInt};
use crate::tests::{init_genesis, NonZeroTinyInt, SpentBookMock, TinyInt};
use crate::{
Amount, AmountSecrets, BlsHelper, DbcBuilder, DerivedOwner, Hash, KeyManager, MintNode,
OwnerBase, ReissueRequest, ReissueRequestBuilder, SimpleKeyManager, SimpleSigner,
Amount, AmountSecrets, BlsHelper, DbcBuilder, DerivedOwner, Hash, KeyManager, OwnerBase,
ReissueRequest, ReissueRequestBuilder, SimpleKeyManager, SimpleSigner,
};
use blst_ringct::ringct::RingCtMaterial;
use blst_ringct::{Output, RevealedCommitment};
Expand Down Expand Up @@ -284,20 +284,8 @@ mod tests {

let amount = 100;

let mut spentbook = SpentBookMock::from(SimpleKeyManager::from(SimpleSigner::from(
crate::bls_dkg_id(&mut rng),
)));

let (mint_node, genesis) = MintNode::new(SimpleKeyManager::from(SimpleSigner::from(
crate::bls_dkg_id(&mut rng),
)))
.trust_spentbook_public_key(spentbook.key_manager.public_key_set()?.public_key())?
.issue_genesis_dbc(amount, &mut rng8)?;

spentbook.set_genesis(&genesis.ringct_material);

let _genesis_spent_proof_share =
spentbook.log_spent(genesis.input_key_image, genesis.transaction.clone())?;
let (mint_node, mut spentbook, genesis, _genesis_dbc) =
init_genesis(&mut rng, &mut rng8, amount)?;

let input_owners: Vec<DerivedOwner> = (0..=n_inputs.coerce())
.map(|_| {
Expand Down Expand Up @@ -350,6 +338,7 @@ mod tests {

let derived_owner =
DerivedOwner::from_owner_base(OwnerBase::from_random_secret_key(&mut rng), &mut rng8);

let (reissue_tx, _revealed_commitments, material, _output_owners) =
crate::TransactionBuilder::default()
.add_inputs_by_secrets(inputs, &mut rng8)
Expand Down
144 changes: 122 additions & 22 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,26 @@ mod tests {
&mut self,
key_image: KeyImage,
tx: RingCtTransaction,
) -> Result<SpentProofShare> {
self.log_spent_worker(key_image, tx, true)
}

// This is invalid behavior, however we provide this method for test cases
// that need to write an invalid Tx to spentbook in order to test reissue
// behavior.
pub fn log_spent_and_skip_tx_verification(
&mut self,
key_image: KeyImage,
tx: RingCtTransaction,
) -> Result<SpentProofShare> {
self.log_spent_worker(key_image, tx, false)
}

fn log_spent_worker(
&mut self,
key_image: KeyImage,
tx: RingCtTransaction,
verify_tx: bool,
) -> Result<SpentProofShare> {
let tx_hash = Hash::from(tx.hash());

Expand All @@ -345,22 +365,20 @@ mod tests {
};

// public_commitments are not available in spentbook for genesis transaction.
let public_commitments: Vec<G1Affine> = if key_image == genesis_key_image {
vec![genesis_public_commitment]
} else {
// Todo: make this cleaner and more efficient.
// spentbook needs to also be indexed by OutputProof PublicKey.
// perhaps map PublicKey --> KeyImage.
tx.mlsags
.iter()
.flat_map(|mlsag| {
if mlsag.key_image.to_compressed() != key_image {
vec![]
} else {
let public_commitments_info: Vec<(KeyImage, Vec<G1Affine>)> =
if key_image == genesis_key_image {
vec![(key_image, vec![genesis_public_commitment])]
} else {
// Todo: make this cleaner and more efficient.
// spentbook needs to also be indexed by OutputProof PublicKey.
// perhaps map PublicKey --> KeyImage.
tx.mlsags
.iter()
.map(|mlsag| {
let commitments: Vec<G1Affine> = mlsag
.public_keys()
.iter()
.filter_map(|pk| {
.map(|pk| {
let output_proofs: Vec<&OutputProof> = self
.transactions
.values()
Expand All @@ -372,19 +390,32 @@ mod tests {
})
.collect();
assert_eq!(output_proofs.len(), 1);
match output_proofs.is_empty() {
true => None,
false => Some(output_proofs[0].commitment()),
}
output_proofs[0].commitment()
})
.collect();
assert_eq!(commitments.len(), mlsag.public_keys().len());
assert!(commitments.len() == mlsag.ring.len());
commitments
}
})
.collect()
};
(mlsag.key_image.to_compressed(), commitments)
})
.collect()
};

// Grab the commitments specific to the spent KeyImage
let tx_public_commitments: Vec<Vec<G1Affine>> = public_commitments_info
.clone()
.into_iter()
.map(|(_, v)| v)
.collect();

let public_commitments: Vec<G1Affine> = public_commitments_info
.into_iter()
.flat_map(|(k, v)| if k == key_image { v } else { vec![] })
.collect();

if verify_tx {
// do not permit invalid tx to be logged.
tx.verify(&tx_public_commitments)?;
}

let existing_tx = self
.transactions
Expand Down Expand Up @@ -413,4 +444,73 @@ mod tests {
self.genesis = Some((key_image, public_commitment));
}
}

pub(crate) fn init_genesis(
mut rng: impl rand::RngCore,
mut rng8: impl rand8::RngCore + rand_core::CryptoRng,
genesis_amount: Amount,
) -> Result<(
MintNode<SimpleKeyManager>,
SpentBookMock,
GenesisDbcShare,
Dbc,
)> {
use std::collections::BTreeSet;
use std::iter::FromIterator;

let mut spentbook = SpentBookMock::from(SimpleKeyManager::from(SimpleSigner::from(
crate::bls_dkg_id(&mut rng),
)));

let (mint_node, genesis) = MintNode::new(SimpleKeyManager::from(SimpleSigner::from(
crate::bls_dkg_id(&mut rng),
)))
.trust_spentbook_public_key(spentbook.key_manager.public_key_set()?.public_key())?
.issue_genesis_dbc(genesis_amount, &mut rng8)?;

spentbook.set_genesis(&genesis.ringct_material);

let mint_sig = mint_node
.key_manager()
.public_key_set()?
.combine_signatures(vec![genesis.transaction_sig.threshold_crypto()])?;

let spent_proof_share =
spentbook.log_spent(genesis.input_key_image, genesis.transaction.clone())?;

let spentbook_sig =
spent_proof_share
.spentbook_pks
.combine_signatures(vec![spent_proof_share
.spentbook_sig_share
.threshold_crypto()])?;

let tx_hash = Hash::from(genesis.transaction.hash());
assert!(spent_proof_share
.spentbook_pks
.public_key()
.verify(&spentbook_sig, &tx_hash));

let spent_proofs = BTreeSet::from_iter(vec![SpentProof {
key_image: spent_proof_share.key_image,
spentbook_pub_key: spent_proof_share.spentbook_pks.public_key(),
spentbook_sig,
public_commitments: spent_proof_share.public_commitments,
}]);

let genesis_dbc = Dbc {
content: genesis.dbc_content.clone(),
transaction: genesis.transaction.clone(),
transaction_sigs: BTreeMap::from_iter([(
genesis.input_key_image,
(
mint_node.key_manager().public_key_set()?.public_key(),
mint_sig,
),
)]),
spent_proofs,
};

Ok((mint_node, spentbook, genesis, genesis_dbc))
}
}

0 comments on commit 42fb29e

Please sign in to comment.