From 3b8d2d2664a1c5d1b7c8a61ff4272e82274fd6e9 Mon Sep 17 00:00:00 2001 From: danda Date: Thu, 17 Feb 2022 16:24:51 -0800 Subject: [PATCH] feat: make GenesisMaterial usable by any node This commit makes GenesisMaterial fully fixed. It no longer accepts an amount parameter or any parameter. This means that every node of every type (mint, spentbook, wallet, audit) can know the GenesisMaterial without need to consult any other node, so it greatly simplifies coordination. For now, the GENESIS_AMOUNT is set to u64::MAX. This takes full advantage of the u64 range. It also caused some tests to break because they use the genesis amount as a starting point and then increment. Such tests were fixed by first reissuing to an intermediate "starting_dbc" of the desired value. * use GenesisMaterial in SpentBookNodeMock * remove SpentBookNodeMock::set_genesis() * remove MintNode::GenesisDbcShare * remove MintNode::issue_genesis_dbc() * modify mismatched test to avoid int overflow * GenesisMaterial::new --> default() * define GENESIS_AMOUNT to u64::MAX * derive Default for GenesisBuilderMock * modify GenesisBuilderMock to build using GenesisMaterial * remove ability to choose genesis amount in mint-repl * modify reissue bench to first generate a dbc of value 100. * modify prop_dbc_validation() to first generate a dbc of value 100 to avoid integer overflow. --- benches/reissue.rs | 67 ++++++++++++-- examples/mint-repl/mint-repl.rs | 31 ++----- src/builder.rs | 157 +++++++++++--------------------- src/dbc.rs | 82 +++++++++++++++-- src/genesis.rs | 24 +++-- src/lib.rs | 2 +- src/mint.rs | 126 +++++++++---------------- src/spentbook.rs | 45 +++------ 8 files changed, 263 insertions(+), 271 deletions(-) diff --git a/benches/reissue.rs b/benches/reissue.rs index 032f6b6..342a84a 100644 --- a/benches/reissue.rs +++ b/benches/reissue.rs @@ -9,8 +9,8 @@ #![allow(clippy::from_iter_instead_of_collect)] use sn_dbc::{ - Amount, DbcBuilder, GenesisBuilderMock, KeyImage, Owner, OwnerOnce, ReissueRequestBuilder, - SpentProofShare, + Amount, Dbc, DbcBuilder, GenesisBuilderMock, KeyImage, MintNode, Owner, OwnerOnce, + ReissueRequestBuilder, Result, SimpleKeyManager, SpentBookNodeMock, SpentProofShare, }; use blst_ringct::Output; @@ -25,18 +25,18 @@ fn bench_reissue_1_to_100(c: &mut Criterion) { let mut rng8 = rand8::rngs::StdRng::from_seed([0u8; 32]); let mut rng = rand::rngs::StdRng::from_seed([0u8; 32]); - let (mintnode, mut spentbook, _genesis_dbc_share, genesis_dbc) = - GenesisBuilderMock::init_genesis_single(N_OUTPUTS as u64, &mut rng, &mut rng8).unwrap(); + let (mintnode, mut spentbook, starting_dbc) = + generate_dbc_of_value(N_OUTPUTS as Amount, &mut rng, &mut rng8).unwrap(); let (reissue_tx, _revealed_commitments, _material, _output_owners) = sn_dbc::TransactionBuilder::default() .add_input_by_secrets( - genesis_dbc + starting_dbc .owner_once_bearer() .unwrap() .secret_key_blst() .unwrap(), - genesis_dbc.amount_secrets_bearer().unwrap(), + starting_dbc.amount_secrets_bearer().unwrap(), vec![], // never any decoys for genesis &mut rng8, ) @@ -76,18 +76,18 @@ fn bench_reissue_100_to_1(c: &mut Criterion) { let mut rng = rand::rngs::StdRng::from_seed([0u8; 32]); let num_decoys = 0; - let (mintnode, mut spentbook, _genesis_dbc_share, genesis_dbc) = - GenesisBuilderMock::init_genesis_single(N_OUTPUTS as u64, &mut rng, &mut rng8).unwrap(); + let (mintnode, mut spentbook, starting_dbc) = + generate_dbc_of_value(N_OUTPUTS as Amount, &mut rng, &mut rng8).unwrap(); let (reissue_tx, revealed_commitments, _material, output_owners) = sn_dbc::TransactionBuilder::default() .add_input_by_secrets( - genesis_dbc + starting_dbc .owner_once_bearer() .unwrap() .secret_key_blst() .unwrap(), - genesis_dbc.amount_secrets_bearer().unwrap(), + starting_dbc.amount_secrets_bearer().unwrap(), vec![], // never any decoy inputs for genesis &mut rng8, ) @@ -170,6 +170,53 @@ fn bench_reissue_100_to_1(c: &mut Criterion) { }); } +fn generate_dbc_of_value( + amount: Amount, + rng: &mut impl rand::RngCore, + rng8: &mut (impl rand8::RngCore + rand_core::CryptoRng), +) -> Result<(MintNode, SpentBookNodeMock, Dbc)> { + let (mint_node, mut spentbook_node, genesis_dbc, _genesis_material, _amount_secrets) = + GenesisBuilderMock::init_genesis_single(rng, rng8)?; + + let output_amounts = vec![amount, sn_dbc::GenesisMaterial::GENESIS_AMOUNT - amount]; + + let (tx, revealed_commitments, _material, output_owners) = + sn_dbc::TransactionBuilder::default() + .add_input_by_secrets( + genesis_dbc.owner_once_bearer()?.secret_key_blst()?, + genesis_dbc.amount_secrets_bearer()?, + vec![], // never any decoys for genesis + rng8, + ) + .add_outputs(output_amounts.into_iter().map(|amount| { + let owner_once = + OwnerOnce::from_owner_base(Owner::from_random_secret_key(rng), rng8); + ( + Output { + amount, + public_key: owner_once.as_owner().public_key_blst(), + }, + owner_once, + ) + })) + .build(rng8)?; + + // Build ReissuRequest + let mut rr_builder = ReissueRequestBuilder::new(tx.clone()); + for mlsag in tx.mlsags.iter() { + let spent_proof_share = spentbook_node.log_spent(mlsag.key_image.into(), tx.clone())?; + rr_builder = rr_builder.add_spent_proof_share(spent_proof_share); + } + let rr = rr_builder.build()?; + + let reissue_share = mint_node.reissue(rr)?; + let mut dbc_builder = DbcBuilder::new(revealed_commitments, output_owners); + dbc_builder = dbc_builder.add_reissue_share(reissue_share); + let (starting_dbc, ..) = dbc_builder.build()?.into_iter().next().unwrap(); + + Ok((mint_node, spentbook_node, starting_dbc)) +} + criterion_group! { name = reissue; config = Criterion::default().sample_size(10); diff --git a/examples/mint-repl/mint-repl.rs b/examples/mint-repl/mint-repl.rs index c875a7c..9a76d80 100644 --- a/examples/mint-repl/mint-repl.rs +++ b/examples/mint-repl/mint-repl.rs @@ -94,7 +94,7 @@ fn main() -> Result<()> { println!("Type 'help' to get started.\n"); // Create a default mint with money supply = 1000. - let mut mintinfo: MintInfo = mk_new_random_mint(0, 1000)?; + let mut mintinfo: MintInfo = mk_new_random_mint(0)?; let mut rl = Editor::<()>::new(); rl.set_auto_add_history(true); @@ -161,21 +161,6 @@ fn newmint() -> Result { return Err(anyhow!("newmint operation cancelled")); } - let amount = loop { - let amount: Amount = readline_prompt("\nTotal Money Supply Amount: ")?.parse()?; - - if amount == 0 { - let answer = readline_prompt( - "\nA mint with supply == 0 can only reissue Dbc worth 0. Change? [y/n]: ", - )?; - if answer.to_ascii_lowercase() != "n" { - continue; - } - // note: we allow amount to be 0. Let sn_dbc validation deal with it (or not). - } - break amount; - }; - // polynomial, from which SecretKeySet is built. let poly_input = readline_prompt_nl("\nSecretKeySet Poly Hex, or [r]andom: ")?; @@ -190,12 +175,12 @@ fn newmint() -> Result { println!("\nThere must be at least 1 signer\n"); } }; - mk_new_random_mint(threshold, amount)? + mk_new_random_mint(threshold)? } _ => { let poly: Poly = from_be_hex(&poly_input)?; let secret_key_set = SecretKeySet::from(poly.clone()); - mk_new_mint(secret_key_set, poly, amount)? + mk_new_mint(secret_key_set, poly)? } }; @@ -205,21 +190,21 @@ fn newmint() -> Result { } /// creates a new mint using a random seed. -fn mk_new_random_mint(threshold: usize, amount: Amount) -> Result { +fn mk_new_random_mint(threshold: usize) -> Result { let (poly, secret_key_set) = mk_secret_key_set(threshold)?; - mk_new_mint(secret_key_set, poly, amount) + mk_new_mint(secret_key_set, poly) } /// creates a new mint from an existing SecretKeySet that was seeded by poly. -fn mk_new_mint(sks: SecretKeySet, poly: Poly, genesis_amount: Amount) -> Result { +fn mk_new_mint(sks: SecretKeySet, poly: Poly) -> Result { let mut rng8 = rand8::rngs::StdRng::from_seed([0u8; 32]); // make as many spentbook nodes as mintnodes. (for now) let num_mint_nodes = sks.threshold() + 1; let num_spentbook_nodes = num_mint_nodes; - let (mint_nodes, spentbook_nodes, _genesis_dbc_shares, genesis_dbc) = - GenesisBuilderMock::from(genesis_amount) + let (mint_nodes, spentbook_nodes, genesis_dbc, _genesis, _amount_secrets) = + GenesisBuilderMock::default() .gen_mint_nodes_with_sks(num_mint_nodes, &sks) .gen_spentbook_nodes_with_sks(num_spentbook_nodes, &sks) .build(&mut rng8)?; diff --git a/src/builder.rs b/src/builder.rs index cea2871..2a07b3a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -481,12 +481,11 @@ impl DbcBuilder { // SpentBookNodeMock, SimpleKeyManager, SimpleSigner, GenesisBuilderMock pub mod mock { use crate::{ - Amount, Dbc, GenesisDbcShare, Hash, KeyManager, MintNode, Result, SimpleKeyManager, - SimpleSigner, SpentBookNodeMock, SpentProof, SpentProofContent, SpentProofShare, + Amount, AmountSecrets, Dbc, DbcBuilder, GenesisMaterial, KeyManager, MintNode, + ReissueRequestBuilder, Result, SimpleKeyManager, SimpleSigner, SpentBookNodeMock, + TransactionBuilder, }; - use blsttc::{SecretKeySet, SignatureShare}; - use std::collections::{BTreeMap, BTreeSet}; - use std::iter::FromIterator; + use blsttc::SecretKeySet; /// A builder for initializing a set of N mintnodes, a set /// of Y spentbooks, and generating a genesis dbc with amount Z. @@ -494,22 +493,13 @@ pub mod mock { /// In SafeNetwork terms, the set of MintNodes represents a single /// Mint section (a) and the set of SpentBooksNodes represents a /// single Spentbook section (b). + #[derive(Default)] pub struct GenesisBuilderMock { pub genesis_amount: Amount, pub mint_nodes: Vec>, pub spentbook_nodes: Vec, } - impl From for GenesisBuilderMock { - fn from(genesis_amount: Amount) -> Self { - Self { - genesis_amount, - mint_nodes: Default::default(), - spentbook_nodes: Default::default(), - } - } - } - impl GenesisBuilderMock { /// generates a list of mint nodes sharing a random SecretKeySet and adds to the builder. pub fn gen_mint_nodes( @@ -606,104 +596,66 @@ pub mod mock { /// builds and returns mintnodes, spentbooks, genesis_dbc_shares, and genesis dbc #[allow(clippy::type_complexity)] pub fn build( - self, - rng8_initial: &mut (impl rand8::RngCore + rand_core::CryptoRng + Clone), + mut self, + rng8: &mut (impl rand8::RngCore + rand_core::CryptoRng), ) -> Result<( Vec>, Vec, - Vec, Dbc, + GenesisMaterial, + AmountSecrets, )> { - let mut spent_proof_shares: Vec = Default::default(); - let mut genesis_set: Vec = Default::default(); let mut mint_nodes: Vec> = Default::default(); - let mut spentbook_nodes: Vec = Default::default(); - for mint_node in self.mint_nodes.into_iter() { - // Each MintNode must start from same random seed when issuing Genesis. - let mut rng8 = rng8_initial.clone(); + // note: rng is necessary for RingCtMaterial::sign(). + + let genesis_material = GenesisMaterial::default(); + let (genesis_tx, revealed_commitments, _ringct_material, output_owner_map) = + TransactionBuilder::default() + .add_input(genesis_material.ringct_material.inputs[0].clone()) + .add_output( + genesis_material.ringct_material.outputs[0].clone(), + genesis_material.owner_once.clone(), + ) + .build(rng8)?; + + let mut rr_builder = ReissueRequestBuilder::new(genesis_tx.clone()); + + for spentbook_node in self.spentbook_nodes.iter_mut() { + rr_builder = rr_builder.add_spent_proof_share( + spentbook_node + .log_spent(genesis_material.input_key_image.clone(), genesis_tx.clone())?, + ); + } - let (mut mint_node, genesis_dbc_share) = - mint_node.issue_genesis_dbc(self.genesis_amount, &mut rng8)?; + let reissue_request = rr_builder.build()?; + let mut dbc_builder = DbcBuilder::new(revealed_commitments, output_owner_map); + for mint_node in self.mint_nodes.into_iter() { // note: for our (mock) purposes, all spentbook nodes are validated to // have the same public key. (in the same section) let spentbook_node_arbitrary = &self.spentbook_nodes[0]; - mint_node = mint_node.trust_spentbook_public_key( + let mint_node = mint_node.trust_spentbook_public_key( spentbook_node_arbitrary .key_manager .public_key_set()? .public_key(), )?; + dbc_builder = + dbc_builder.add_reissue_share(mint_node.reissue(reissue_request.clone())?); mint_nodes.push(mint_node); - genesis_set.push(genesis_dbc_share); - } - let genesis_dbc_share_arbitrary = &genesis_set[0]; - - for mut spentbook_node in self.spentbook_nodes.into_iter() { - spentbook_node.set_genesis(&genesis_dbc_share_arbitrary.ringct_material); - spent_proof_shares.push(spentbook_node.log_spent( - genesis_dbc_share_arbitrary.input_key_image.clone(), - genesis_dbc_share_arbitrary.transaction.clone(), - )?); - spentbook_nodes.push(spentbook_node); } - // Make a list of (Index, SignatureShare) for combining sigs. - let node_sigs: Vec<(u64, &SignatureShare)> = genesis_set - .iter() - .map(|g| g.mintnode_sig_share.threshold_crypto()) - .collect(); - - let mint_node_arbitrary = &mint_nodes[0]; - - let mint_sig = mint_node_arbitrary - .key_manager - .public_key_set()? - .combine_signatures(node_sigs)?; - - let spent_sigs: Vec<(u64, &SignatureShare)> = spent_proof_shares - .iter() - .map(|s| s.spentbook_sig_share.threshold_crypto()) - .collect(); - - let spent_proof_share_arbitrary = &spent_proof_shares[0]; - - let spentbook_sig = spent_proof_share_arbitrary - .spentbook_pks - .combine_signatures(spent_sigs)?; - - let transaction_hash = Hash::from(genesis_dbc_share_arbitrary.transaction.hash()); - - let spent_proofs = BTreeSet::from_iter([SpentProof { - content: SpentProofContent { - key_image: spent_proof_share_arbitrary.key_image().clone(), - transaction_hash, - public_commitments: spent_proof_share_arbitrary.public_commitments().clone(), - }, - spentbook_pub_key: spent_proof_share_arbitrary.spentbook_pks.public_key(), - spentbook_sig, - }]); - - // Create the Genesis Dbc - let genesis_dbc = Dbc { - content: genesis_dbc_share_arbitrary.dbc_content.clone(), - transaction: genesis_dbc_share_arbitrary.transaction.clone(), - mint_sigs: BTreeMap::from_iter([( - genesis_dbc_share_arbitrary.input_key_image.clone(), - ( - mint_node_arbitrary - .key_manager - .public_key_set()? - .public_key(), - mint_sig, - ), - )]), - spent_proofs, - }; - - Ok((mint_nodes, spentbook_nodes, genesis_set, genesis_dbc)) + let (genesis_dbc, _owner_once, amount_secrets) = + dbc_builder.build()?.into_iter().next().unwrap(); + Ok(( + mint_nodes, + self.spentbook_nodes, + genesis_dbc, + genesis_material, + amount_secrets, + )) } /// builds and returns mintnodes, spentbooks, genesis_dbc_shares, and genesis dbc @@ -711,18 +663,18 @@ pub mod mock { /// the spentbook nodes use a different randomly generated SecretKeySet #[allow(clippy::type_complexity)] pub fn init_genesis( - genesis_amount: Amount, num_mint_nodes: usize, num_spentbook_nodes: usize, rng: &mut impl rand::RngCore, - rng8: &mut (impl rand8::RngCore + rand_core::CryptoRng + Clone), + rng8: &mut (impl rand8::RngCore + rand_core::CryptoRng), ) -> Result<( Vec>, Vec, - Vec, Dbc, + GenesisMaterial, + AmountSecrets, )> { - Self::from(genesis_amount) + Self::default() .gen_mint_nodes(num_mint_nodes, rng)? .gen_spentbook_nodes(num_spentbook_nodes, rng)? .build(rng8) @@ -734,17 +686,17 @@ pub mod mock { /// the spentbook node uses a different randomly generated SecretKeySet #[allow(clippy::type_complexity)] pub fn init_genesis_single( - genesis_amount: Amount, rng: &mut impl rand::RngCore, - rng8: &mut (impl rand8::RngCore + rand_core::CryptoRng + Clone), + rng8: &mut (impl rand8::RngCore + rand_core::CryptoRng), ) -> Result<( MintNode, SpentBookNodeMock, - GenesisDbcShare, Dbc, + GenesisMaterial, + AmountSecrets, )> { - let (mint_nodes, spentbook_nodes, genesis_dbc_shares, genesis_dbc) = - GenesisBuilderMock::from(genesis_amount) + let (mint_nodes, spentbook_nodes, genesis_dbc, genesis_material, amount_secrets) = + Self::default() .gen_mint_nodes(1, rng)? .gen_spentbook_nodes(1, rng)? .build(rng8)?; @@ -757,8 +709,9 @@ pub mod mock { Ok(( mint_nodes.into_iter().next().unwrap(), spentbook_nodes.into_iter().next().unwrap(), - genesis_dbc_shares.into_iter().next().unwrap(), genesis_dbc, + genesis_material, + amount_secrets, )) } } diff --git a/src/dbc.rs b/src/dbc.rs index a729660..c00878a 100644 --- a/src/dbc.rs +++ b/src/dbc.rs @@ -280,7 +280,7 @@ impl Dbc { } #[cfg(test)] -mod tests { +pub(crate) mod tests { use super::*; use std::iter::FromIterator; @@ -289,9 +289,9 @@ mod tests { use crate::tests::{NonZeroTinyInt, TinyInt}; use crate::{ - Amount, AmountSecrets, DbcBuilder, GenesisBuilderMock, Hash, KeyManager, OutputOwnerMap, - Owner, OwnerOnce, ReissueRequest, ReissueRequestBuilder, SecretKeyBlst, SimpleKeyManager, - SimpleSigner, SpentBookNodeMock, + Amount, AmountSecrets, DbcBuilder, GenesisBuilderMock, Hash, KeyManager, MintNode, + OutputOwnerMap, Owner, OwnerOnce, ReissueRequest, ReissueRequestBuilder, SecretKeyBlst, + SimpleKeyManager, SimpleSigner, SpentBookNodeMock, }; use blst_ringct::ringct::RingCtMaterial; use blst_ringct::{DecoyInput, Output, RevealedCommitment}; @@ -411,16 +411,23 @@ mod tests { let amount = 100; - let (mint_node, mut spentbook, genesis, _genesis_dbc) = - GenesisBuilderMock::init_genesis_single(amount, &mut rng, &mut rng8)?; + // first we will reissue genesis into outputs (100, GENESIS-100). + // The 100 output will be our starting_dbc. + // + // we do this instead of just using GENESIS_AMOUNT as our starting amount + // because GENESIS_AMOUNT is u64::MAX (or could be) and later in the test + // we add extra_output_amount to amount, which would otherwise + // cause an integer overflow. + let (mint_node, mut spentbook, _genesis_dbc, starting_dbc, _change_dbc) = + generate_dbc_of_value(amount, &mut rng, &mut rng8)?; let input_owners: Vec = (0..n_inputs.coerce()) .map(|_| OwnerOnce::from_owner_base(Owner::from_random_secret_key(&mut rng), &mut rng8)) .collect(); let (reissue_request, revealed_commitments, output_owners) = prepare_even_split( - genesis.owner_once.as_owner().secret_key_blst()?, - genesis.amount_secrets, + starting_dbc.owner_once_bearer()?.secret_key_blst()?, + starting_dbc.amount_secrets_bearer()?, n_inputs.coerce(), input_owners, &mut spentbook, @@ -431,7 +438,6 @@ mod tests { assert!(sp .spentbook_pub_key .verify(&sp.spentbook_sig, sp.content.hash())); - let split_reissue_share = mint_node.reissue(reissue_request)?; let mut dbc_builder = DbcBuilder::new(revealed_commitments, output_owners); @@ -473,7 +479,9 @@ mod tests { } let rr = rr_builder.build()?; + let reissue_share = mint_node.reissue(rr)?; + assert_eq!(reissue_tx.hash(), reissue_share.transaction.hash()); let (mint_key_set, mint_sig_share) = @@ -654,4 +662,60 @@ mod tests { Ok(()) } + + pub(crate) fn generate_dbc_of_value( + amount: Amount, + rng: &mut impl rand::RngCore, + rng8: &mut (impl rand8::RngCore + rand_core::CryptoRng), + ) -> Result<(MintNode, SpentBookNodeMock, Dbc, Dbc, Dbc)> { + let (mint_node, mut spentbook_node, genesis_dbc, _genesis_material, _amount_secrets) = + GenesisBuilderMock::init_genesis_single(rng, rng8)?; + + let output_amounts = vec![amount, sn_dbc::GenesisMaterial::GENESIS_AMOUNT - amount]; + + let (tx, revealed_commitments, _material, output_owners) = + crate::TransactionBuilder::default() + .add_input_by_secrets( + genesis_dbc.owner_once_bearer()?.secret_key_blst()?, + genesis_dbc.amount_secrets_bearer()?, + vec![], // never any decoys for genesis + rng8, + ) + .add_outputs(output_amounts.into_iter().map(|amount| { + let owner_once = + OwnerOnce::from_owner_base(Owner::from_random_secret_key(rng), rng8); + ( + Output { + amount, + public_key: owner_once.as_owner().public_key_blst(), + }, + owner_once, + ) + })) + .build(rng8)?; + + // Build ReissuRequest + let mut rr_builder = ReissueRequestBuilder::new(tx.clone()); + for mlsag in tx.mlsags.iter() { + let spent_proof_share = spentbook_node.log_spent(mlsag.key_image.into(), tx.clone())?; + rr_builder = rr_builder.add_spent_proof_share(spent_proof_share); + } + let rr = rr_builder.build()?; + + let reissue_share = mint_node.reissue(rr)?; + let mut dbc_builder = DbcBuilder::new(revealed_commitments, output_owners); + dbc_builder = dbc_builder.add_reissue_share(reissue_share); + + let mut iter = dbc_builder.build()?.into_iter(); + let (starting_dbc, ..) = iter.next().unwrap(); + let (change_dbc, ..) = iter.next().unwrap(); + + Ok(( + mint_node, + spentbook_node, + genesis_dbc, + starting_dbc, + change_dbc, + )) + } } diff --git a/src/genesis.rs b/src/genesis.rs index bd3b800..268c4d1 100644 --- a/src/genesis.rs +++ b/src/genesis.rs @@ -1,4 +1,4 @@ -use crate::{Amount, KeyImage, Owner, OwnerOnce, PublicKeyBlst, Result, SecretKeyBlst}; +use crate::{Amount, KeyImage, Owner, OwnerOnce, PublicKeyBlst, SecretKeyBlst}; use blst_ringct::mlsag::{MlsagMaterial, TrueInput}; use blst_ringct::ringct::RingCtMaterial; use blst_ringct::{Output, RevealedCommitment}; @@ -15,9 +15,11 @@ pub struct GenesisMaterial { } impl GenesisMaterial { - /// This is the "real" amount that all network participants should use. - pub const STD_GENESIS_AMOUNT: Amount = 30000000; + /// The Genesis DBC will mint all possible tokens. + pub const GENESIS_AMOUNT: Amount = 18446744073709551615; // aka 2^64 aka Amount::MAX +} +impl Default for GenesisMaterial { /// generate the GenesisMaterial. /// /// It is allowed to pass in an amount for local testing purposes. @@ -25,12 +27,15 @@ impl GenesisMaterial { /// one must use GenesisMaterial::STD_GENESIS_AMOUNT /// /// todo: implement Network enum {Mainnet, Testnet, ...} - pub fn new(amount: Amount) -> Result { + fn default() -> Self { // Make a secret key for the input to Genesis Tx. let input_poly = Poly::zero(); let input_secret_key_set = SecretKeySet::from(input_poly); // fixme, unwrap. + // note: this is only temporary until blsttc is impld with blstrs. At which + // point the byte conversion and unwrap should not be necessary. + // Thus, we are not returning a Result from this fn. let input_secret_key = SecretKeyBlst::from_bytes_be(&input_secret_key_set.secret_key().to_bytes()).unwrap(); @@ -53,7 +58,7 @@ impl GenesisMaterial { let true_input = TrueInput { secret_key: input_secret_key, revealed_commitment: RevealedCommitment { - value: amount, + value: Self::GENESIS_AMOUNT, blinding: 5.into(), // todo: choose Genesis blinding factor. }, }; @@ -78,13 +83,16 @@ impl GenesisMaterial { let ringct_material = RingCtMaterial { inputs: vec![mlsag_material], - outputs: vec![Output { public_key, amount }], + outputs: vec![Output { + public_key, + amount: Self::GENESIS_AMOUNT, + }], }; - Ok(Self { + Self { ringct_material, owner_once, input_key_image, - }) + } } } diff --git a/src/lib.rs b/src/lib.rs index 865511f..b8d913c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ pub use crate::{ KeyManager, NodeSignature, PublicKey, PublicKeySet, Signature, SimpleKeyManager, SimpleSigner, }, - mint::{GenesisDbcShare, MintNode, MintNodeSignatures, ReissueRequest, ReissueShare}, + mint::{MintNode, MintNodeSignatures, ReissueRequest, ReissueShare}, owner::{DerivationIndex, Owner, OwnerOnce}, spent_proof::{SpentProof, SpentProofContent, SpentProofShare}, spentbook::SpentBookNodeMock, diff --git a/src/mint.rs b/src/mint.rs index e0aa2ff..3f39bd8 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -14,12 +14,10 @@ // Outputs <= input value use crate::{ - Amount, AmountSecrets, DbcContent, Error, GenesisMaterial, Hash, KeyImage, KeyManager, - NodeSignature, OwnerOnce, PublicKey, PublicKeySet, Result, SpentProof, SpentProofShare, - TransactionValidator, + Error, Hash, KeyImage, KeyManager, NodeSignature, PublicKey, PublicKeySet, Result, SpentProof, + SpentProofShare, TransactionValidator, }; -use blst_ringct::ringct::{RingCtMaterial, RingCtTransaction}; -use rand_core::RngCore; +use blst_ringct::ringct::RingCtTransaction; use std::collections::{BTreeMap, BTreeSet}; #[cfg(feature = "serde")] @@ -28,18 +26,6 @@ use serde::{Deserialize, Serialize}; pub type MintNodeSignature = (PublicKeySet, NodeSignature); pub type MintNodeSignatures = BTreeMap; -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone)] -pub struct GenesisDbcShare { - pub ringct_material: RingCtMaterial, - pub dbc_content: DbcContent, - pub amount_secrets: AmountSecrets, - pub owner_once: OwnerOnce, - pub transaction: RingCtTransaction, - pub mintnode_sig_share: NodeSignature, - pub input_key_image: KeyImage, -} - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone)] pub struct ReissueRequest { @@ -81,47 +67,6 @@ impl MintNode { Ok(self) } - pub fn issue_genesis_dbc( - self, - amount: Amount, - mut rng: impl RngCore + rand_core::CryptoRng, - ) -> Result<(Self, GenesisDbcShare)> { - let GenesisMaterial { - ringct_material, - owner_once, - input_key_image, - } = GenesisMaterial::new(amount)?; - - // Here we sign as the input DBC owner. - let (transaction, revealed_commitments) = ringct_material.sign(&mut rng)?; - - let amount_secrets = AmountSecrets::from(revealed_commitments[0]); - let dbc_content = DbcContent::from(( - owner_once.owner_base.clone(), - owner_once.derivation_index, - amount_secrets.clone(), - )); - - // Here we sign as the mint. - let mintnode_sig_share = self - .key_manager - .sign(&Hash::from(transaction.hash())) - .map_err(|e| Error::Signing(e.to_string()))?; - - Ok(( - self, - GenesisDbcShare { - ringct_material, - dbc_content, - amount_secrets, - owner_once, - transaction, - mintnode_sig_share, - input_key_image, - }, - )) - } - pub fn key_manager(&self) -> &K { &self.key_manager } @@ -221,8 +166,8 @@ mod tests { use crate::{ tests::{TinyInt, TinyVec}, - AmountSecrets, Dbc, DbcBuilder, GenesisBuilderMock, Owner, OwnerOnce, - ReissueRequestBuilder, SimpleKeyManager, SimpleSigner, SpentBookNodeMock, + Amount, AmountSecrets, Dbc, DbcBuilder, DbcContent, GenesisBuilderMock, GenesisMaterial, + Owner, OwnerOnce, ReissueRequestBuilder, SimpleKeyManager, SimpleSigner, SpentBookNodeMock, SpentProofContent, }; @@ -231,8 +176,8 @@ mod tests { let mut rng8 = rand8::rngs::StdRng::from_seed([0u8; 32]); let mut rng = rand::rngs::StdRng::from_seed([0u8; 32]); - let (mint_node, _spentbook, genesis, genesis_dbc) = - GenesisBuilderMock::init_genesis_single(1000, &mut rng, &mut rng8)?; + let (mint_node, _spentbook, genesis_dbc, genesis, _amount_secrets) = + GenesisBuilderMock::init_genesis_single(&mut rng, &mut rng8)?; let validation = genesis_dbc.confirm_valid( &genesis.owner_once.owner_base().secret_key()?, @@ -248,13 +193,16 @@ mod tests { let mut rng8 = rand8::rngs::StdRng::from_seed([0u8; 32]); let mut rng = rand::rngs::StdRng::from_seed([0u8; 32]); - let output_amounts = + let mut output_amounts = Vec::from_iter(output_amounts.into_iter().map(TinyInt::coerce::)); + output_amounts + .push(GenesisMaterial::GENESIS_AMOUNT - output_amounts.iter().sum::()); + let n_outputs = output_amounts.len(); let output_amount = output_amounts.iter().sum(); - let (mint_node, mut spentbook, _genesis, genesis_dbc) = - GenesisBuilderMock::init_genesis_single(output_amount, &mut rng, &mut rng8)?; + let (mint_node, mut spentbook, genesis_dbc, _genesis, _amount_secrets) = + GenesisBuilderMock::init_genesis_single(&mut rng, &mut rng8)?; let owners: Vec = (0..output_amounts.len()) .map(|_| OwnerOnce::from_owner_base(Owner::from_random_secret_key(&mut rng), &mut rng8)) @@ -366,11 +314,14 @@ mod tests { let mut rng8 = rand8::rngs::StdRng::from_seed([0u8; 32]); let mut rng = rand::rngs::StdRng::from_seed([0u8; 32]); - let input_amounts = + let mut input_amounts = Vec::from_iter(input_amounts.into_iter().map(TinyInt::coerce::)); + input_amounts.push(GenesisMaterial::GENESIS_AMOUNT - input_amounts.iter().sum::()); - let output_amounts = + let mut output_amounts = Vec::from_iter(output_amounts.into_iter().map(TinyInt::coerce::)); + output_amounts + .push(GenesisMaterial::GENESIS_AMOUNT - output_amounts.iter().sum::()); let invalid_spent_proofs = BTreeSet::from_iter( invalid_spent_proofs @@ -378,15 +329,13 @@ mod tests { .map(TinyInt::coerce::), ); - let genesis_amount: Amount = input_amounts.iter().sum(); - // We apply mod 2 because there is only one available decoy (genesis pubkey) // in the spentbook. To test decoys further, we would need to devise a test // something like: genesis --> 100 outputs --> x outputs --> y outputs. let num_decoy_inputs: usize = num_decoy_inputs.coerce::() % 2; - let (mint_node, mut spentbook, _genesis, genesis_dbc) = - GenesisBuilderMock::init_genesis_single(genesis_amount, &mut rng, &mut rng8)?; + let (mint_node, mut spentbook, genesis_dbc, _genesis, _amount_secrets) = + GenesisBuilderMock::init_genesis_single(&mut rng, &mut rng8)?; let (reissue_tx, revealed_commitments, _material, output_owners) = crate::TransactionBuilder::default() @@ -507,7 +456,7 @@ mod tests { Error::RingCt( blst_ringct::Error::InputPseudoCommitmentsDoNotSumToOutputCommitments, ) => { - if genesis_amount == output_total_amount { + if GenesisMaterial::GENESIS_AMOUNT == output_total_amount { // This can correctly occur if there are 0 outputs and inputs sum to zero. // // The error occurs because there is no output with a commitment @@ -592,7 +541,7 @@ mod tests { match many_to_many_result { Ok(rs) => { - assert_eq!(genesis_amount, output_total_amount); + assert_eq!(GenesisMaterial::GENESIS_AMOUNT, output_total_amount); assert!(invalid_spent_proofs .iter() .all(|i| i >= &reissue_tx2.mlsags.len())); @@ -736,8 +685,8 @@ mod tests { let output_amount = 1000; - let (mint_node, mut spentbook, genesis, genesis_dbc) = - GenesisBuilderMock::init_genesis_single(output_amount, &mut rng, &mut rng8)?; + let (mint_node, mut spentbook, genesis_dbc, starting_dbc, _change_dbc) = + crate::dbc::tests::generate_dbc_of_value(output_amount, &mut rng, &mut rng8)?; // ---------- // 2. reissue genesis DBC (a) to Dbc (b) with value 1000. @@ -752,8 +701,8 @@ mod tests { let (tx, revealed_commitments, _ringct_material, output_owner_map) = crate::TransactionBuilder::default() .add_input_dbc( - &genesis_dbc, - &genesis_dbc.owner_base().secret_key()?, + &starting_dbc, + &starting_dbc.owner_base().secret_key()?, vec![], // genesis is only input, so no decoys. &mut rng8, )? @@ -780,15 +729,16 @@ mod tests { let output_dbcs = dbc_builder.build()?; // ---------- - // 3. modify b's amount secrets.amount to 2000, thereby creating b_fudged + // 3. modify b's amount secrets.amount to AMOUNT/2, thereby creating b_fudged // (which a bad actor could pass to innocent recipient). // ---------- // Replace the encrypted secret amount with an encrypted secret claiming // twice the committed value. + let starting_amount_secrets = starting_dbc.amount_secrets_bearer()?; let fudged_amount_secrets = AmountSecrets::from(( - genesis.amount_secrets.amount() * 2, // Claim we are paying twice the committed value - genesis.amount_secrets.blinding_factor(), // Use the real blinding factor + starting_amount_secrets.amount() * 2, // Claim we are paying twice the committed value + starting_amount_secrets.blinding_factor(), // Use the real blinding factor )); let (true_output_dbc, ..) = output_dbcs[0].clone(); @@ -808,7 +758,7 @@ mod tests { fudged_output_dbc.amount_secrets(&output_owner.owner_base().secret_key()?)?; // confirm the secret amount is 2000. - assert_eq!(fudged_secrets.amount(), 1000 * 2); + assert_eq!(fudged_secrets.amount(), output_amount * 2); // confirm the dbc is considered valid using the mint-accessible api. assert!(fudged_output_dbc .confirm_valid( @@ -954,14 +904,20 @@ mod tests { // // For the test case, we can remedy by: // - // Make a new spentbook and replay the first two tx, plus the new tx_true + // Make a new spentbook and replay the first three tx, plus the new tx_true // Note that the new spentbook uses the same signing key as the original, which // MintNode's key_manager trusts. // let mut new_spentbook = SpentBookNodeMock::from(spentbook.key_manager); - new_spentbook.set_genesis(&genesis.ringct_material); - let _genesis_spent_proof_share = - new_spentbook.log_spent(genesis.input_key_image, genesis.transaction.clone())?; + // new_spentbook.set_genesis(&genesis.ringct_material); + let _genesis_spent_proof_share = new_spentbook.log_spent( + genesis_dbc.transaction.mlsags[0].key_image.into(), + genesis_dbc.transaction.clone(), + )?; + let _starting_spent_proof_share = new_spentbook.log_spent( + starting_dbc.transaction.mlsags[0].key_image.into(), + starting_dbc.transaction.clone(), + )?; let _spent_proof_share = new_spentbook.log_spent(tx.mlsags[0].key_image.into(), tx.clone())?; let spent_proof_share_true = diff --git a/src/spentbook.rs b/src/spentbook.rs index 6b7d2c8..61eb8af 100644 --- a/src/spentbook.rs +++ b/src/spentbook.rs @@ -6,7 +6,7 @@ // KIND, either express or implied. Please review the Licences for the specific language governing // permissions and limitations relating to use of the SAFE Network Software. -use blst_ringct::ringct::{OutputProof, RingCtMaterial, RingCtTransaction}; +use blst_ringct::ringct::{OutputProof, RingCtTransaction}; use blst_ringct::DecoyInput; use blstrs::group::Curve; use std::collections::{BTreeMap, HashMap}; @@ -14,8 +14,8 @@ use std::collections::{BTreeMap, HashMap}; use rand8::prelude::IteratorRandom; use crate::{ - Commitment, Hash, KeyImage, KeyManager, PublicKeyBlstMappable, Result, SimpleKeyManager, - SpentProofContent, SpentProofShare, + Commitment, GenesisMaterial, Hash, KeyImage, KeyManager, PublicKeyBlstMappable, Result, + SimpleKeyManager, SpentProofContent, SpentProofShare, }; /// This is a mock SpentBook used for our test cases. A proper implementation @@ -48,31 +48,24 @@ pub struct SpentBookNodeMock { pub key_images: BTreeMap, pub outputs: BTreeMap, - pub genesis: Option<(KeyImage, Commitment)>, // genesis input (keyimage, public_commitment) + pub genesis: (KeyImage, Commitment), // genesis input (keyimage, public_commitment) } impl From for SpentBookNodeMock { fn from(key_manager: SimpleKeyManager) -> Self { - Self { - key_manager, - transactions: Default::default(), - key_images: Default::default(), - outputs: Default::default(), - genesis: None, - } - } -} - -impl From<(SimpleKeyManager, KeyImage, Commitment)> for SpentBookNodeMock { - fn from(params: (SimpleKeyManager, KeyImage, Commitment)) -> Self { - let (key_manager, key_image, public_commitment) = params; + let genesis_material = GenesisMaterial::default(); + let public_commitment = genesis_material.ringct_material.inputs[0] + .true_input + .revealed_commitment() + .commit(&bulletproofs::PedersenGens::default()) + .to_affine(); Self { key_manager, transactions: Default::default(), key_images: Default::default(), outputs: Default::default(), - genesis: Some((key_image, public_commitment)), + genesis: (genesis_material.input_key_image, public_commitment), } } } @@ -125,10 +118,7 @@ impl SpentBookNodeMock { // If this is the very first tx logged and genesis key_image was not // provided, then it becomes the genesis tx. - let (genesis_key_image, genesis_public_commitment) = match &self.genesis { - Some((k, pc)) => (k, pc), - None => panic!("Genesis key_image and public commitments unavailable"), - }; + let (genesis_key_image, genesis_public_commitment) = &self.genesis; // public_commitments are not available in spentbook for genesis transaction. let public_commitments_info: Vec<(KeyImage, Vec)> = @@ -225,17 +215,6 @@ impl SpentBookNodeMock { } } - pub fn set_genesis(&mut self, material: &RingCtMaterial) { - let key_image = KeyImage::from(material.inputs[0].true_input.key_image().to_affine()); - let public_commitment = material.inputs[0] - .true_input - .revealed_commitment - .commit(&Default::default()) - .to_affine(); - - self.genesis = Some((key_image, public_commitment)); - } - // return a list of DecoyInput built from randomly // selected OutputProof, from set of all OutputProof in Spentbook. pub fn random_decoys(