From c2bce8b04ad11038cc3c65de196917efaa7a2200 Mon Sep 17 00:00:00 2001 From: danda Date: Thu, 27 Jan 2022 11:22:56 -0800 Subject: [PATCH] feat: add spentbook pubkey to mint's key_manager * add MintNode::trust_spentbook_public_key() to make mint key_manager aware of spentbook pubkey * remove spentbook verifier param from Dbc::confirm_valid() * add to trait KeyManager fn add_known_key() * make MintNode::reissue() accept &self instead of &mut self * make MintNode::issue_genesis_dbc() return self (builder pattern) * update tests to use MintNode builder pattern and trust spentbook pubkey. * add input_key_image to GenesisDbcShare * remove genesis_dbc_input static fn * remove (ugly/temporary) SpentProof::validate_unsafe() --- src/dbc.rs | 43 +++++--------- src/key_manager.rs | 6 ++ src/lib.rs | 5 +- src/mint.rs | 137 ++++++++++++++++++++++----------------------- src/spent_proof.rs | 9 --- 5 files changed, 89 insertions(+), 111 deletions(-) diff --git a/src/dbc.rs b/src/dbc.rs index c76c1d4..dbc063d 100644 --- a/src/dbc.rs +++ b/src/dbc.rs @@ -63,11 +63,12 @@ impl Dbc { // Check there exists a Transaction with the output containing this Dbc // todo: refactor so that common validation logic is shared by MintNode::reissue() and Dbc::confirm_valid() + // + // note: for spent_proofs to validate, the mint_verifier must have/know the spentbook section's public key. pub fn confirm_valid( &self, base_sk: &SecretKey, mint_verifier: &K, - spentbook_verifier: &K, ) -> Result<(), Error> { let tx_hash = Hash::from(self.transaction.hash()); @@ -97,7 +98,7 @@ impl Dbc { { return Err(Error::UnknownInput); } - spent_proof.validate(tx_hash, spentbook_verifier)?; + spent_proof.validate(tx_hash, mint_verifier)?; } let owner = self.owner(base_sk)?; @@ -267,17 +268,10 @@ mod tests { }; let id = crate::bls_dkg_id(&mut rng); - let key_manager = SimpleKeyManager::new(SimpleSigner::from(id)); - - let spentbook_owner = crate::bls_dkg_id(&mut rng); - let spentbook_key_manager = SimpleKeyManager::new(SimpleSigner::from(spentbook_owner)); + let mint_key_manager = SimpleKeyManager::new(SimpleSigner::from(id)); assert!(matches!( - dbc.confirm_valid( - &derived_owner.base_secret_key()?, - &key_manager, - &spentbook_key_manager - ), + dbc.confirm_valid(&derived_owner.base_secret_key()?, &mint_key_manager,), Err(Error::TransactionMustHaveAnInput) )); @@ -298,23 +292,20 @@ mod tests { let mut rng = rand::rngs::StdRng::from_seed([0u8; 32]); let amount = 100; - let mint_owner = crate::bls_dkg_id(&mut rng); - let key_manager = SimpleKeyManager::new(SimpleSigner::from(mint_owner)); - let mut mint_node = MintNode::new(key_manager); + let spentbook_key_manager = + SimpleKeyManager::new(SimpleSigner::from(crate::bls_dkg_id(&mut rng))); - let spentbook_owner = crate::bls_dkg_id(&mut rng); - let spentbook_key_manager = SimpleKeyManager::new(SimpleSigner::from(spentbook_owner)); + let (mint_node, genesis) = MintNode::new(SimpleKeyManager::new(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)?; - let genesis = mint_node.issue_genesis_dbc(amount, &mut rng8)?; - let genesis_input_key_image = genesis.ringct_material.inputs[0] - .true_input - .key_image() - .to_compressed(); - let mut spentbook = SpentBookMock::from((spentbook_key_manager, genesis_input_key_image)); + let mut spentbook = SpentBookMock::from((spentbook_key_manager, genesis.input_key_image)); let _genesis_spent_proof_share = - spentbook.log_spent(genesis_input_key_image, genesis.transaction.clone())?; + spentbook.log_spent(genesis.input_key_image, genesis.transaction.clone())?; let input_owners: Vec = (0..=n_inputs.coerce()) .map(|_| { @@ -491,11 +482,7 @@ mod tests { }; let key_manager = mint_node.key_manager(); - let validation_res = dbc.confirm_valid( - &derived_owner.base_secret_key()?, - key_manager, - &spentbook.key_manager, - ); + let validation_res = dbc.confirm_valid(&derived_owner.base_secret_key()?, key_manager); let dbc_owner = dbc.owner(&derived_owner.base_secret_key()?)?; diff --git a/src/key_manager.rs b/src/key_manager.rs index 0733b46..4d64eaa 100644 --- a/src/key_manager.rs +++ b/src/key_manager.rs @@ -30,6 +30,7 @@ impl NodeSignature { pub trait KeyManager { type Error: std::error::Error; + fn add_known_key(&mut self, key: PublicKey) -> Result<(), Self::Error>; fn sign_with_child_key(&self, idx: &[u8], tx_hash: &Hash) -> Result; fn sign(&self, msg_hash: &Hash) -> Result; @@ -106,6 +107,11 @@ impl SimpleKeyManager { impl KeyManager for SimpleKeyManager { type Error = crate::Error; + fn add_known_key(&mut self, key: PublicKey) -> Result<()> { + self.cache.add_known_key(key); + Ok(()) + } + fn public_key_set(&self) -> Result { Ok(self.signer.public_key_set()) } diff --git a/src/lib.rs b/src/lib.rs index 03db216..578d68b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,10 +31,7 @@ pub use crate::{ KeyManager, NodeSignature, PublicKey, PublicKeySet, Signature, SimpleKeyManager, SimpleSigner, }, - mint::{ - genesis_dbc_input, GenesisDbcShare, MintNode, MintNodeSignatures, ReissueRequest, - ReissueShare, - }, + mint::{GenesisDbcShare, MintNode, MintNodeSignatures, ReissueRequest, ReissueShare}, spent_proof::{SpentProof, SpentProofShare}, }; diff --git a/src/mint.rs b/src/mint.rs index b8a8113..8e64823 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -15,7 +15,7 @@ use crate::{ Amount, AmountSecrets, DbcContent, DerivedOwner, Error, Hash, KeyImage, KeyManager, - NodeSignature, OwnerBase, PublicKeySet, Result, SpentProof, SpentProofShare, + NodeSignature, OwnerBase, PublicKey, PublicKeySet, Result, SpentProof, SpentProofShare, }; use blst_ringct::mlsag::{MlsagMaterial, TrueInput}; use blst_ringct::ringct::{RingCtMaterial, RingCtTransaction}; @@ -36,18 +36,6 @@ use std::iter::FromIterator; pub type MintNodeSignature = (PublicKeySet, NodeSignature); pub type MintNodeSignatures = BTreeMap; -pub fn genesis_dbc_input(share: &GenesisDbcShare) -> Result { - Ok(share - .ringct_material - .inputs - .get(0) - .ok_or(Error::TransactionMustHaveAnInput)? - .true_input - .key_image() - .to_affine() - .to_compressed()) -} - #[derive(Clone)] pub struct GenesisDbcShare { pub ringct_material: RingCtMaterial, @@ -59,6 +47,7 @@ pub struct GenesisDbcShare { pub public_key_set: PublicKeySet, pub transaction_sig: NodeSignature, pub secret_key: Scalar, + pub input_key_image: KeyImage, } // #[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize)] @@ -89,11 +78,18 @@ impl MintNode { Self { key_manager } } + pub fn trust_spentbook_public_key(mut self, public_key: PublicKey) -> Result { + self.key_manager + .add_known_key(public_key) + .map_err(|e| Error::Signing(e.to_string()))?; + Ok(self) + } + pub fn issue_genesis_dbc( - &mut self, + self, amount: Amount, mut rng: impl RngCore + rand_core::CryptoRng, - ) -> Result { + ) -> Result<(Self, GenesisDbcShare)> { // Make a secret key for the input to Genesis Tx. let input_poly = Poly::zero(); let input_secret_key_set = SecretKeySet::from(input_poly); @@ -123,6 +119,8 @@ impl MintNode { }, }; + let input_key_image = true_input.key_image().to_compressed(); + // note: no decoy inputs because no other DBCs exist prior to genesis DBC. let decoy_inputs = vec![]; @@ -149,17 +147,21 @@ impl MintNode { .sign(&Hash::from(transaction.hash())) .map_err(|e| Error::Signing(e.to_string()))?; - Ok(GenesisDbcShare { - ringct_material, - dbc_content, - amount_secrets, - public_key_set, - derived_owner, - transaction, - revealed_commitments, // output commitments - transaction_sig, - secret_key, - }) + Ok(( + self, + GenesisDbcShare { + ringct_material, + dbc_content, + amount_secrets, + public_key_set, + derived_owner, + transaction, + revealed_commitments, // output commitments + transaction_sig, + secret_key, + input_key_image, + }, + )) } pub fn key_manager(&self) -> &K { @@ -195,7 +197,7 @@ impl MintNode { // 6. create SpentProofShare and return it. } - pub fn reissue(&mut self, reissue_req: ReissueRequest) -> Result { + pub fn reissue(&self, reissue_req: ReissueRequest) -> Result { let ReissueRequest { transaction, spent_proofs, @@ -260,11 +262,12 @@ impl MintNode { // Validate that each input has not yet been spent. // iterate over mlsags. each has key_image() + // + // note: for the proofs to validate, our key_manager must have/know + // the pubkey of the spentbook section that signed the proof. + // This is a responsibility of our caller, not this crate. for proof in spent_proofs_sorted.iter() { - // proof.validate(transaction_hash, self.key_manager())?; - - // fixme: this does not validate that signing key belongs to spentbook. - proof.validate_unsafe(transaction_hash)?; + proof.validate(transaction_hash, self.key_manager())?; } let transaction_sigs = self.sign_transaction(&transaction)?; @@ -318,15 +321,16 @@ mod tests { let mut rng8 = rand8::rngs::StdRng::from_seed([0u8; 32]); let mut rng = rand::rngs::StdRng::from_seed([0u8; 32]); - let mint_owner = crate::bls_dkg_id(&mut rng); - let key_manager = SimpleKeyManager::new(SimpleSigner::from(mint_owner)); - let mut mint_node = MintNode::new(key_manager); - let genesis = mint_node.issue_genesis_dbc(1000, &mut rng8).unwrap(); + let spentbook_key_manager = + SimpleKeyManager::new(SimpleSigner::from(crate::bls_dkg_id(&mut rng))); - let spentbook_owner = crate::bls_dkg_id(&mut rng); - let spentbook_key_manager = SimpleKeyManager::new(SimpleSigner::from(spentbook_owner)); - let input_key_image = genesis_dbc_input(&genesis)?; - let mut spentbook = SpentBookMock::from((spentbook_key_manager, input_key_image)); + let (mint_node, genesis) = MintNode::new(SimpleKeyManager::new(SimpleSigner::from( + crate::bls_dkg_id(&mut rng), + ))) + .trust_spentbook_public_key(spentbook_key_manager.public_key_set()?.public_key())? + .issue_genesis_dbc(1000, &mut rng8)?; + + let mut spentbook = SpentBookMock::from((spentbook_key_manager, genesis.input_key_image)); let mint_sig = mint_node .key_manager() @@ -334,7 +338,7 @@ mod tests { .combine_signatures(vec![genesis.transaction_sig.threshold_crypto()])?; let spent_proof_share = - spentbook.log_spent(input_key_image, genesis.transaction.clone())?; + spentbook.log_spent(genesis.input_key_image, genesis.transaction.clone())?; let spentbook_sig = spent_proof_share @@ -360,7 +364,7 @@ mod tests { content: genesis.dbc_content, transaction: genesis.transaction.clone(), transaction_sigs: BTreeMap::from_iter([( - input_key_image, + genesis.input_key_image, ( mint_node.key_manager().public_key_set()?.public_key(), mint_sig, @@ -372,7 +376,6 @@ mod tests { let validation = genesis_dbc.confirm_valid( &genesis.derived_owner.base_secret_key()?, &mint_node.key_manager, - &spentbook.key_manager, ); assert!(validation.is_ok()); @@ -389,20 +392,19 @@ mod tests { let n_outputs = output_amounts.len(); let output_amount = output_amounts.iter().sum(); - let mint_owner = crate::bls_dkg_id(&mut rng); - let key_manager = SimpleKeyManager::new(SimpleSigner::from(mint_owner)); - let mut mint_node = MintNode::new(key_manager); - let genesis = mint_node - .issue_genesis_dbc(output_amount, &mut rng8) - .unwrap(); + let spentbook_key_manager = + SimpleKeyManager::new(SimpleSigner::from(crate::bls_dkg_id(&mut rng))); - let spentbook_owner = crate::bls_dkg_id(&mut rng); - let spentbook_key_manager = SimpleKeyManager::new(SimpleSigner::from(spentbook_owner)); - let input_key_image = genesis_dbc_input(&genesis)?; - let mut spentbook = SpentBookMock::from((spentbook_key_manager, input_key_image)); + let (mint_node, genesis) = MintNode::new(SimpleKeyManager::new(SimpleSigner::from( + crate::bls_dkg_id(&mut rng), + ))) + .trust_spentbook_public_key(spentbook_key_manager.public_key_set()?.public_key())? + .issue_genesis_dbc(output_amount, &mut rng8)?; + + let mut spentbook = SpentBookMock::from((spentbook_key_manager, genesis.input_key_image)); let _genesis_spent_proof_share = - spentbook.log_spent(input_key_image, genesis.transaction.clone())?; + spentbook.log_spent(genesis.input_key_image, genesis.transaction.clone())?; let owners: Vec = (0..output_amounts.len()) .map(|_| { @@ -476,7 +478,6 @@ mod tests { .confirm_valid( &derived_owner.base_secret_key().unwrap(), mint_node.key_manager(), - &spentbook.key_manager ) .is_ok()); } @@ -532,20 +533,19 @@ mod tests { // something like: genesis --> 100 outputs --> x outputs --> y outputs. let num_decoy_inputs: usize = num_decoy_inputs.coerce::() % 2; - let mint_owner = crate::bls_dkg_id(&mut rng); - let key_manager = SimpleKeyManager::new(SimpleSigner::from(mint_owner)); - let mut mint_node = MintNode::new(key_manager); - let genesis = mint_node - .issue_genesis_dbc(genesis_amount, &mut rng8) - .unwrap(); + let spentbook_key_manager = + SimpleKeyManager::new(SimpleSigner::from(crate::bls_dkg_id(&mut rng))); + + let (mint_node, genesis) = MintNode::new(SimpleKeyManager::new(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)?; - let spentbook_owner = crate::bls_dkg_id(&mut rng); - let spentbook_key_manager = SimpleKeyManager::new(SimpleSigner::from(spentbook_owner)); - let input_key_image = genesis_dbc_input(&genesis)?; - let mut spentbook = SpentBookMock::from((spentbook_key_manager, input_key_image)); + let mut spentbook = SpentBookMock::from((spentbook_key_manager, genesis.input_key_image)); let _genesis_spent_proof_share = - spentbook.log_spent(input_key_image, genesis.transaction.clone())?; + spentbook.log_spent(genesis.input_key_image, genesis.transaction.clone())?; let (reissue_tx, revealed_commitments, _material, output_owners) = crate::TransactionBuilder::default() @@ -713,11 +713,8 @@ mod tests { let output_dbcs = dbc_builder.build()?; for (dbc, derived_owner, _amount_secrets) in output_dbcs.iter() { - let dbc_confirm_result = dbc.confirm_valid( - &derived_owner.base_secret_key()?, - &mint_node.key_manager, - &spentbook.key_manager, - ); + let dbc_confirm_result = dbc + .confirm_valid(&derived_owner.base_secret_key()?, &mint_node.key_manager); assert!(dbc_confirm_result.is_ok()); } diff --git a/src/spent_proof.rs b/src/spent_proof.rs index 711c9c8..44d9d39 100644 --- a/src/spent_proof.rs +++ b/src/spent_proof.rs @@ -94,15 +94,6 @@ impl SpentProof { .map_err(|_| Error::InvalidSpentProofSignature(self.key_image))?; Ok(()) } - - // This is unsafe (and temporary) because it does not verify that - // self.spentbook_pub_key actually belongs to the spentbook. - pub(crate) fn validate_unsafe(&self, tx: Hash) -> Result<()> { - match self.spentbook_pub_key.verify(&self.spentbook_sig, &tx) { - true => Ok(()), - false => Err(Error::InvalidSpentProofSignature(self.key_image)), - } - } } impl PartialOrd for SpentProof {