From 53331387d8bd24a96ab4b85b6416ea877abf1287 Mon Sep 17 00:00:00 2001 From: bochaco Date: Thu, 4 Aug 2022 10:11:29 -0300 Subject: [PATCH] feat: expose Token type in public API --- benches/reissue.rs | 21 ++-- examples/mint-repl/mint-repl.rs | 22 ++-- src/amount_secrets.rs | 16 ++- src/builder.rs | 28 ++--- src/dbc.rs | 33 +++--- src/error.rs | 12 ++- src/lib.rs | 4 +- src/mint.rs | 61 ++++++----- src/mock/error.rs | 2 +- src/mock/genesis_builder.rs | 3 +- src/mock/genesis_material.rs | 4 +- src/token.rs | 182 ++++++++++++++++++++++++++++++++ 12 files changed, 296 insertions(+), 92 deletions(-) create mode 100644 src/token.rs diff --git a/benches/reissue.rs b/benches/reissue.rs index 8be71dc..58fa8dd 100644 --- a/benches/reissue.rs +++ b/benches/reissue.rs @@ -11,18 +11,18 @@ use sn_dbc::{ mock, rand::{CryptoRng, RngCore}, - rng, Amount, Dbc, Owner, OwnerOnce, Result, TransactionVerifier, + rng, Dbc, Owner, OwnerOnce, Result, Token, TransactionVerifier, }; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -const N_OUTPUTS: u32 = 100; +const N_OUTPUTS: u64 = 100; fn bench_reissue_1_to_100(c: &mut Criterion) { let mut rng = rng::from_seed([0u8; 32]); let (mut spentbook, starting_dbc) = - generate_dbc_of_value(N_OUTPUTS as Amount, &mut rng).unwrap(); + generate_dbc_of_value(Token::from_nano(N_OUTPUTS), &mut rng).unwrap(); let mut dbc_builder = sn_dbc::TransactionBuilder::default() .set_require_all_decoys(false) // no decoys! @@ -37,7 +37,7 @@ fn bench_reissue_1_to_100(c: &mut Criterion) { .add_outputs_by_amount((0..N_OUTPUTS).into_iter().map(|_| { let owner_once = OwnerOnce::from_owner_base(Owner::from_random_secret_key(&mut rng), &mut rng); - (1, owner_once) + (Token::from_nano(1), owner_once) })) .build(&mut rng) .unwrap(); @@ -70,7 +70,7 @@ fn bench_reissue_100_to_1(c: &mut Criterion) { let mut rng = rng::from_seed([0u8; 32]); let (mut spentbook_node, starting_dbc) = - generate_dbc_of_value(N_OUTPUTS as Amount, &mut rng).unwrap(); + generate_dbc_of_value(Token::from_nano(N_OUTPUTS), &mut rng).unwrap(); let mut dbc_builder = sn_dbc::TransactionBuilder::default() .set_require_all_decoys(false) // no decoys! @@ -85,7 +85,7 @@ fn bench_reissue_100_to_1(c: &mut Criterion) { .add_outputs_by_amount((0..N_OUTPUTS).into_iter().map(|_| { let owner_once = OwnerOnce::from_owner_base(Owner::from_random_secret_key(&mut rng), &mut rng); - (1, owner_once) + (Token::from_nano(1), owner_once) })) .build(&mut rng) .unwrap(); @@ -108,7 +108,7 @@ fn bench_reissue_100_to_1(c: &mut Criterion) { }) .collect(), ) - .add_output_by_amount(N_OUTPUTS as Amount, output_owner_once) + .add_output_by_amount(Token::from_nano(N_OUTPUTS), output_owner_once) .build(&mut rng) .unwrap(); @@ -137,13 +137,16 @@ fn bench_reissue_100_to_1(c: &mut Criterion) { } fn generate_dbc_of_value( - amount: Amount, + amount: Token, rng: &mut (impl RngCore + CryptoRng), ) -> Result<(mock::SpentBookNode, Dbc)> { let (mut spentbook_node, genesis_dbc, _genesis_material, _amount_secrets) = mock::GenesisBuilder::init_genesis_single(rng)?; - let output_amounts = vec![amount, mock::GenesisMaterial::GENESIS_AMOUNT - amount]; + let output_amounts = vec![ + amount, + Token::from_nano(mock::GenesisMaterial::GENESIS_AMOUNT - amount.as_nano()), + ]; let mut dbc_builder = sn_dbc::TransactionBuilder::default() .set_require_all_decoys(false) // no decoys! diff --git a/examples/mint-repl/mint-repl.rs b/examples/mint-repl/mint-repl.rs index ee229d8..28af0b8 100644 --- a/examples/mint-repl/mint-repl.rs +++ b/examples/mint-repl/mint-repl.rs @@ -23,8 +23,8 @@ use sn_dbc::{ }, mock, rand::{seq::IteratorRandom, Rng}, - rng, Amount, Dbc, DbcBuilder, OutputOwnerMap, Owner, OwnerOnce, RevealedCommitment, - RingCtMaterial, RingCtTransaction, TransactionBuilder, + rng, Dbc, DbcBuilder, OutputOwnerMap, Owner, OwnerOnce, RevealedCommitment, RingCtMaterial, + RingCtTransaction, Token, TransactionBuilder, }; use std::collections::{BTreeMap, HashMap}; @@ -604,26 +604,24 @@ fn prepare_tx(mintinfo: &MintInfo) -> Result { // note, we upcast to i128 to allow negative value. // This permits unbalanced inputs/outputs to reach sn_dbc layer for verification. let inputs_amount_sum = tx_builder.inputs_amount_sum(); - while inputs_amount_sum as i128 - tx_builder.outputs_amount_sum() as i128 > 0 { + while let Some(remaining) = inputs_amount_sum.checked_sub(tx_builder.outputs_amount_sum()) { println!(); println!("------------"); println!("Output #{}", i); println!("------------\n"); - let remaining = inputs_amount_sum - tx_builder.outputs_amount_sum(); - println!( "Inputs total: {}. Remaining: {}", inputs_amount_sum, remaining ); - let line = readline_prompt("Amount, or '[c]ancel': ")?; - let amount: Amount = if line == "c" { + let line = readline_prompt("Token, or '[c]ancel': ")?; + let amount: Token = if line == "c" { println!("\nprepare_tx cancelled\n"); break; } else { line.parse()? }; - if amount > remaining || amount == 0 { + if amount > remaining || amount == Token::zero() { let answer = readline_prompt(&format!( "\nThe amount should normally be in the range 1..{}. Change it? [y/n]: ", remaining @@ -759,16 +757,16 @@ fn reissue_auto_cli(mintinfo: &mut MintInfo) -> Result<()> { while tx_builder.outputs_amount_sum() < inputs_sum || tx_builder.outputs().is_empty() { let amount = if tx_builder.outputs().len() >= max_outputs - 1 { - inputs_sum - tx_builder.outputs_amount_sum() + inputs_sum.as_nano() - tx_builder.outputs_amount_sum().as_nano() } else { // randomize output amount - let diff = inputs_sum - tx_builder.outputs_amount_sum(); + let diff = inputs_sum.as_nano() - tx_builder.outputs_amount_sum().as_nano(); let is_last = rng.gen_range(0..max_outputs + 1) == max_outputs; if is_last { diff } else { - let range_max = if diff == Amount::MAX { diff } else { diff + 1 }; + let range_max = if diff == u64::MAX { diff } else { diff + 1 }; rng.gen_range(0..range_max) } }; @@ -776,7 +774,7 @@ fn reissue_auto_cli(mintinfo: &mut MintInfo) -> Result<()> { let owner_once = OwnerOnce::from_owner_base(Owner::from_random_secret_key(&mut rng), &mut rng); - tx_builder = tx_builder.add_output_by_amount(amount, owner_once); + tx_builder = tx_builder.add_output_by_amount(Token::from_nano(amount), owner_once); } let mut dbc_builder = tx_builder.build(&mut rng)?; diff --git a/src/amount_secrets.rs b/src/amount_secrets.rs index 0a96fb4..8008da7 100644 --- a/src/amount_secrets.rs +++ b/src/amount_secrets.rs @@ -6,22 +6,18 @@ // 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 crate::rand::RngCore; -use crate::{BlindingFactor, Error}; -use bls_ringct::RevealedCommitment; +use crate::{rand::RngCore, BlindingFactor, Error, Token}; +use bls_ringct::{ringct::Amount, RevealedCommitment}; use blsttc::{ Ciphertext, DecryptionShare, IntoFr, PublicKey, PublicKeySet, SecretKey, SecretKeySet, SecretKeyShare, }; -use std::collections::BTreeMap; -use std::convert::TryFrom; - -pub use bls_ringct::ringct::Amount; +use std::{collections::BTreeMap, convert::TryFrom}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -const AMT_SIZE: usize = std::mem::size_of::(); // Amount size: 8 bytes (u64) +const AMT_SIZE: usize = std::mem::size_of::(); // Amount size: 8 bytes (u64) const BF_SIZE: usize = std::mem::size_of::(); // Blinding factor size: 32 bytes (BlindingFactor) /// AmountSecrets wraps bls_ringct::RevealedCommitment to provide some methods @@ -39,8 +35,8 @@ pub struct AmountSecrets(RevealedCommitment); impl AmountSecrets { /// amount getter - pub fn amount(&self) -> Amount { - self.0.value + pub fn amount(&self) -> Token { + Token::from_nano(self.0.value) } /// blinding factor getter diff --git a/src/builder.rs b/src/builder.rs index ddeb760..37d4bcd 100644 --- a/src/builder.rs +++ b/src/builder.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 bls_ringct::{bls_bulletproofs::PedersenGens, group::Curve, ringct::Amount}; +use bls_ringct::{bls_bulletproofs::PedersenGens, group::Curve}; pub use bls_ringct::{ ringct::RingCtTransaction, DecoyInput, MlsagMaterial, Output, RevealedCommitment, RingCtMaterial, TrueInput, @@ -20,7 +20,7 @@ use std::{ use crate::{ rand::{CryptoRng, RngCore}, AmountSecrets, Commitment, Dbc, DbcContent, Error, Hash, KeyImage, OwnerOnce, Result, - SpentProof, SpentProofKeyVerifier, SpentProofShare, TransactionVerifier, + SpentProof, SpentProofKeyVerifier, SpentProofShare, Token, TransactionVerifier, }; #[cfg(feature = "serde")] @@ -201,19 +201,19 @@ impl TransactionBuilder { self } - /// add an output by providing Amount and OwnerOnce - pub fn add_output_by_amount(mut self, amount: Amount, owner: OwnerOnce) -> Self { + /// add an output by providing Token and OwnerOnce + pub fn add_output_by_amount(mut self, amount: Token, owner: OwnerOnce) -> Self { let pk = owner.as_owner().public_key(); - let output = Output::new(pk, amount); + let output = Output::new(pk, amount.as_nano()); self.output_owner_map.insert(pk, owner); self.ringct_material.outputs.push(output); self } - /// add an output by providing iter of (Amount, OwnerOnce) + /// add an output by providing iter of (Token, OwnerOnce) pub fn add_outputs_by_amount( mut self, - outputs: impl IntoIterator, + outputs: impl IntoIterator, ) -> Self { for (amount, owner) in outputs.into_iter() { self = self.add_output_by_amount(amount, owner); @@ -230,16 +230,20 @@ impl TransactionBuilder { } /// get sum of input amounts - pub fn inputs_amount_sum(&self) -> Amount { - self.true_inputs + pub fn inputs_amount_sum(&self) -> Token { + let amount = self + .true_inputs .iter() .map(|t| t.revealed_commitment.value) - .sum() + .sum(); + + Token::from_nano(amount) } /// get sum of output amounts - pub fn outputs_amount_sum(&self) -> Amount { - self.ringct_material.outputs.iter().map(|o| o.amount).sum() + pub fn outputs_amount_sum(&self) -> Token { + let amount = self.ringct_material.outputs.iter().map(|o| o.amount).sum(); + Token::from_nano(amount) } /// get true inputs diff --git a/src/dbc.rs b/src/dbc.rs index 2a0710e..287d737 100644 --- a/src/dbc.rs +++ b/src/dbc.rs @@ -325,19 +325,19 @@ pub(crate) mod tests { use crate::{ mock, rand::{CryptoRng, RngCore}, - Amount, AmountSecrets, DbcBuilder, Hash, Owner, OwnerOnce, SpentProofContent, + AmountSecrets, DbcBuilder, Hash, Owner, OwnerOnce, SpentProofContent, Token, }; use bls_ringct::{bls_bulletproofs::PedersenGens, ringct::RingCtMaterial, Output}; use blsttc::PublicKey; use std::convert::TryInto; - fn divide(amount: Amount, n_ways: u8) -> impl Iterator { + fn divide(amount: Token, n_ways: u8) -> impl Iterator { (0..n_ways).into_iter().map(move |i| { - let equal_parts = amount / (n_ways as Amount); - let leftover = amount % (n_ways as Amount); + let equal_parts = amount.as_nano() / n_ways as u64; + let leftover = amount.as_nano() % n_ways as u64; - let odd_compensation = if (i as Amount) < leftover { 1 } else { 0 }; - equal_parts + odd_compensation + let odd_compensation = if (i as u64) < leftover { 1 } else { 0 }; + Token::from_nano(equal_parts + odd_compensation) }) } @@ -376,7 +376,7 @@ pub(crate) mod tests { fn from_hex_should_deserialize_a_hex_encoded_string_to_a_dbc() -> Result<(), Error> { let dbc = Dbc::from_hex(DBC_WITH_1_530_000_000)?; let amount = dbc.amount_secrets_bearer()?.amount(); - assert_eq!(amount, 1_530_000_000); + assert_eq!(amount, Token::from_nano(1_530_000_000)); Ok(()) } @@ -586,7 +586,7 @@ pub(crate) mod tests { .set_require_all_decoys(false) .add_decoy_inputs(decoy_inputs) .add_inputs_dbc(inputs)? - .add_output_by_amount(amount, owner_once.clone()) + .add_output_by_amount(Token::from_nano(amount), owner_once.clone()) .build(&mut rng)?; for (key_image, tx) in dbc_builder.inputs() { @@ -611,7 +611,7 @@ pub(crate) mod tests { .collect(); let fuzzed_amt_secrets = AmountSecrets::from(( - amount + extra_output_amount.coerce::(), + amount + extra_output_amount.coerce::(), amount_secrets_list[0].blinding_factor(), )); let dbc_amount = fuzzed_amt_secrets.amount(); @@ -736,7 +736,7 @@ pub(crate) mod tests { assert_eq!(n_wrong_signer_sigs.coerce::(), 0); assert_eq!(n_wrong_msg_sigs.coerce::(), 0); - assert_eq!(dbc_amount, amount); + assert_eq!(dbc_amount, Token::from_nano(amount)); assert_eq!(extra_output_amount.coerce::(), 0); } Err(Error::SpentProofInputLenMismatch { current, expected }) => { @@ -758,7 +758,7 @@ pub(crate) mod tests { assert_eq!(n_inputs.coerce::(), 0); } Err(Error::AmountCommitmentsDoNotMatch) => { - assert_ne!(amount, dbc_amount); + assert_ne!(Token::from_nano(amount), dbc_amount); assert_ne!(extra_output_amount, TinyInt(0)); } Err(Error::InvalidSpentProofSignature(_pk, _msg)) => { @@ -784,14 +784,14 @@ pub(crate) mod tests { } pub(crate) fn generate_bearer_dbc_of_value( - amount: Amount, + amount: u64, rng: &mut (impl RngCore + CryptoRng), ) -> Result<(mock::SpentBookNode, Dbc, Dbc, Dbc)> { generate_dbc_of_value(amount, Owner::from_random_secret_key(rng), rng) } pub(crate) fn generate_owned_dbc_of_value( - amount: Amount, + amount: u64, pk_hex: &str, rng: &mut (impl RngCore + CryptoRng), ) -> Result<(mock::SpentBookNode, Dbc, Dbc, Dbc)> { @@ -810,14 +810,17 @@ pub(crate) mod tests { } fn generate_dbc_of_value( - amount: Amount, + amount: u64, owner: Owner, rng: &mut (impl RngCore + CryptoRng), ) -> Result<(mock::SpentBookNode, Dbc, Dbc, Dbc)> { let (mut spentbook_node, genesis_dbc, _genesis_material, _amount_secrets) = mock::GenesisBuilder::init_genesis_single(rng)?; - let output_amounts = vec![amount, mock::GenesisMaterial::GENESIS_AMOUNT - amount]; + let output_amounts = vec![ + Token::from_nano(amount), + Token::from_nano(mock::GenesisMaterial::GENESIS_AMOUNT - amount), + ]; let mut dbc_builder = crate::TransactionBuilder::default() .set_require_all_decoys(false) diff --git a/src/error.rs b/src/error.rs index 4e6a31d..9ab88fb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,10 +13,20 @@ use crate::KeyImage; pub type Result = std::result::Result; #[allow(clippy::large_enum_variant)] -#[derive(Error, Debug, Clone)] +#[derive(Error, Debug, Clone, PartialEq)] #[non_exhaustive] /// Node error variants. pub enum Error { + /// While parsing a `Token`, precision would be lost. + #[error("Lost precision on the number of coins during parsing")] + LossOfTokenPrecision, + /// The amount would exceed the maximum value for `Token` (u64::MAX). + #[error("The token amount would exceed the maximum value (u64::MAX)")] + ExcessiveTokenValue, + /// Failed to parse a `Token` from a string. + #[error("Failed to parse: {0}")] + FailedToParseToken(String), + #[error("Failed signature check.")] FailedSignature, diff --git a/src/lib.rs b/src/lib.rs index 321518f..f1001b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ mod error; mod mint; mod owner; mod spent_proof; +mod token; mod verification; #[cfg(feature = "mock")] @@ -36,7 +37,7 @@ pub use bls_ringct::rand; pub use blsttc::{PublicKey, PublicKeySet, Signature, SignatureShare}; pub use crate::{ - amount_secrets::{Amount, AmountSecrets}, + amount_secrets::AmountSecrets, blst::{BlindingFactor, Commitment, KeyImage}, builder::{ DbcBuilder, DecoyInput, MlsagMaterial, Output, OutputOwnerMap, RevealedCommitment, @@ -50,6 +51,7 @@ pub use crate::{ IndexedSignatureShare, SpentProof, SpentProofContent, SpentProofKeyVerifier, SpentProofShare, }, + token::Token, verification::TransactionVerifier, }; diff --git a/src/mint.rs b/src/mint.rs index cd0ff1e..155d129 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -16,8 +16,8 @@ mod tests { use std::iter::FromIterator; use crate::{ - mock, Amount, AmountSecrets, Dbc, DbcContent, Error, IndexedSignatureShare, KeyImage, - Owner, OwnerOnce, Result, SpentProofContent, SpentProofShare, TransactionBuilder, + mock, AmountSecrets, Dbc, DbcContent, Error, IndexedSignatureShare, KeyImage, Owner, + OwnerOnce, Result, SpentProofContent, SpentProofShare, Token, TransactionBuilder, }; #[test] @@ -41,9 +41,9 @@ mod tests { let mut rng = crate::rng::from_seed([0u8; 32]); let mut output_amounts = - Vec::from_iter(output_amounts.into_iter().map(TinyInt::coerce::)); + Vec::from_iter(output_amounts.into_iter().map(TinyInt::coerce::)); output_amounts - .push(mock::GenesisMaterial::GENESIS_AMOUNT - output_amounts.iter().sum::()); + .push(mock::GenesisMaterial::GENESIS_AMOUNT - output_amounts.iter().sum::()); let n_outputs = output_amounts.len(); let output_amount = output_amounts.iter().sum(); @@ -62,7 +62,7 @@ mod tests { output_amounts .iter() .enumerate() - .map(|(idx, a)| (*a, owners[idx].clone())), + .map(|(idx, a)| (Token::from_nano(*a), owners[idx].clone())), ) .build(&mut rng)?; @@ -98,7 +98,9 @@ mod tests { for (dbc, owner_once, amount_secrets) in output_dbcs.iter() { let dbc_amount = amount_secrets.amount(); - assert!(output_amounts.iter().any(|a| *a == dbc_amount)); + assert!(output_amounts + .iter() + .any(|a| Token::from_nano(*a) == dbc_amount)); assert!(dbc .verify( &owner_once.owner_base().secret_key().unwrap(), @@ -109,13 +111,14 @@ mod tests { assert_eq!( { - let mut sum: Amount = 0; - for (dbc, owner_once, _amount_secrets) in output_dbcs.iter() { + let mut sum: u64 = 0; + for (dbc, owner_once, _) in output_dbcs.iter() { // note: we could just use amount_secrets provided by DbcBuilder::build() // but we go further to verify the correct value is encrypted in the Dbc. sum += dbc .amount_secrets(&owner_once.owner_base().secret_key()?)? .amount() + .as_nano() } sum }, @@ -144,14 +147,14 @@ mod tests { // let num_decoy_inputs = TinyInt(0); let mut input_amounts = - Vec::from_iter(input_amounts.into_iter().map(TinyInt::coerce::)); + Vec::from_iter(input_amounts.into_iter().map(TinyInt::coerce::)); input_amounts - .push(mock::GenesisMaterial::GENESIS_AMOUNT - input_amounts.iter().sum::()); + .push(mock::GenesisMaterial::GENESIS_AMOUNT - input_amounts.iter().sum::()); let mut output_amounts = - Vec::from_iter(output_amounts.into_iter().map(TinyInt::coerce::)); + Vec::from_iter(output_amounts.into_iter().map(TinyInt::coerce::)); output_amounts - .push(mock::GenesisMaterial::GENESIS_AMOUNT - output_amounts.iter().sum::()); + .push(mock::GenesisMaterial::GENESIS_AMOUNT - output_amounts.iter().sum::()); let invalid_spent_proofs = BTreeSet::from_iter( invalid_spent_proofs @@ -174,10 +177,10 @@ mod tests { .set_require_all_decoys(false) .add_decoy_inputs(decoy_inputs.clone()) .add_input_dbc(&genesis_dbc, &genesis_dbc.owner_base().secret_key()?)? - .add_outputs_by_amount(input_amounts.iter().copied().map(|amount| { + .add_outputs_by_amount(input_amounts.iter().map(|amount| { let owner_once = OwnerOnce::from_owner_base(Owner::from_random_secret_key(&mut rng), &mut rng); - (amount, owner_once) + (Token::from_nano(*amount), owner_once) })) .build(&mut rng)?; @@ -221,18 +224,22 @@ mod tests { .map(|_| OwnerOnce::from_owner_base(Owner::from_random_secret_key(&mut rng), &mut rng)) .collect(); - let outputs = output_amounts.clone().into_iter().zip(owners); + let outputs = output_amounts + .clone() + .into_iter() + .map(Token::from_nano) + .zip(owners); let mut dbc_builder = TransactionBuilder::default() .set_decoys_per_input(num_decoy_inputs) .set_require_all_decoys(false) .add_decoy_inputs(decoy_inputs) .add_inputs_dbc(inputs_dbcs.clone())? - .add_outputs_by_amount(outputs.clone()) + .add_outputs_by_amount(outputs) .build(&mut rng)?; - let dbc_output_amounts: Vec = outputs.map(|(amt, _)| amt).collect(); - let output_total_amount: Amount = dbc_output_amounts.iter().sum(); + let dbc_output_amounts = output_amounts.clone(); + let output_total_amount = dbc_output_amounts.iter().sum(); assert_eq!(inputs_dbcs.len(), dbc_builder.transaction.mlsags.len()); assert_eq!(inputs_dbcs.len(), dbc_builder.inputs().len()); @@ -260,7 +267,7 @@ mod tests { // to match against the input commitment, and also no way to // know that the input amount is zero. assert!(output_amounts.is_empty()); - assert_eq!(input_amounts.iter().sum::(), 0); + assert_eq!(input_amounts.iter().sum::(), 0); assert!(!input_amounts.is_empty()); } } @@ -355,7 +362,7 @@ mod tests { .iter() .enumerate() .map(|(idx, _dbc)| { dbc_output_amounts[idx] }) - .sum::(), + .sum::(), output_total_amount ); Ok(()) @@ -387,7 +394,7 @@ mod tests { OwnerOnce::from_owner_base(Owner::from_random_secret_key(&mut rng), &mut rng); let dbc_builder = TransactionBuilder::default() - .add_output_by_amount(100, output1_owner.clone()) + .add_output_by_amount(Token::from_nano(100), output1_owner.clone()) .build(&mut rng)?; let amount_secrets = AmountSecrets::from(dbc_builder.revealed_commitments[0]); @@ -400,7 +407,7 @@ mod tests { let fraud_dbc_builder = TransactionBuilder::default() .set_require_all_decoys(false) .add_input_by_secrets(secret_key, amount_secrets) - .add_output_by_amount(100, output2_owner) + .add_output_by_amount(Token::from_nano(100), output2_owner) .build(&mut rng)?; let result = fraud_dbc_builder.build(&spentbook_node.key_manager); @@ -468,7 +475,7 @@ mod tests { let mut dbc_builder = TransactionBuilder::default() .set_require_all_decoys(false) .add_input_dbc(&starting_dbc, &starting_dbc.owner_base().secret_key()?)? - .add_output_by_amount(output_amount, output_owner.clone()) + .add_output_by_amount(Token::from_nano(output_amount), output_owner.clone()) .build(&mut rng)?; for (key_image, tx) in dbc_builder.inputs() { @@ -490,8 +497,8 @@ mod tests { // twice the committed value. let starting_amount_secrets = starting_dbc.amount_secrets_bearer()?; let fudged_amount_secrets = AmountSecrets::from(( - starting_amount_secrets.amount() * 2, // Claim we are paying twice the committed value - starting_amount_secrets.blinding_factor(), // Use the real blinding factor + starting_amount_secrets.amount().as_nano() * 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(); @@ -511,7 +518,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(), output_amount * 2); + assert_eq!(fudged_secrets.amount(), Token::from_nano(output_amount * 2)); // ---------- // 4. Check if the amounts match, using the provided API. @@ -532,7 +539,7 @@ mod tests { fudged_output_dbc .amount_secrets(&output_owner.owner_base().secret_key()?)? .amount(), - output_amount + Token::from_nano(output_amount) ); // ---------- diff --git a/src/mock/error.rs b/src/mock/error.rs index 92e4608..dd0f1e2 100644 --- a/src/mock/error.rs +++ b/src/mock/error.rs @@ -11,7 +11,7 @@ use thiserror::Error; use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Error, Debug, Clone)] +#[derive(Error, Debug, Clone, PartialEq)] /// Mock error variants. pub enum Error { #[error("Key image has already been spent")] diff --git a/src/mock/genesis_builder.rs b/src/mock/genesis_builder.rs index 7a1766e..ccbc38c 100644 --- a/src/mock/genesis_builder.rs +++ b/src/mock/genesis_builder.rs @@ -10,7 +10,7 @@ use super::GenesisMaterial; use crate::{ mock, rand::{CryptoRng, RngCore}, - Amount, AmountSecrets, Dbc, Result, TransactionBuilder, + AmountSecrets, Dbc, Result, TransactionBuilder, }; use blsttc::SecretKeySet; @@ -21,7 +21,6 @@ use blsttc::SecretKeySet; /// single Spentbook section. #[derive(Default)] pub struct GenesisBuilder { - pub genesis_amount: Amount, pub spentbook_nodes: Vec, } diff --git a/src/mock/genesis_material.rs b/src/mock/genesis_material.rs index f2af9b3..0d465ea 100644 --- a/src/mock/genesis_material.rs +++ b/src/mock/genesis_material.rs @@ -6,12 +6,12 @@ // 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 crate::{Amount, KeyImage, Owner, OwnerOnce}; +use crate::{KeyImage, Owner, OwnerOnce}; use bls_ringct::{ blstrs::Scalar, group::Curve, mlsag::{MlsagMaterial, TrueInput}, - ringct::RingCtMaterial, + ringct::{Amount, RingCtMaterial}, {Output, RevealedCommitment}, }; use blsttc::IntoFr; diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 0000000..1ab18d0 --- /dev/null +++ b/src/token.rs @@ -0,0 +1,182 @@ +// Copyright 2022 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// 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 super::error::{Error, Result}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use std::{ + fmt::{self, Display, Formatter}, + str::FromStr, +}; + +/// The conversion from Token to raw value +const TOKEN_TO_RAW_POWER_OF_10_CONVERSION: u32 = 9; + +/// The conversion from Token to raw value +const TOKEN_TO_RAW_CONVERSION: u64 = 1_000_000_000; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +/// Structure representing a Token amount. +pub struct Token(u64); + +impl Token { + /// Type safe representation of zero Token. + pub const fn zero() -> Self { + Self(0) + } + + /// New value from a number of nano tokens. + pub const fn from_nano(value: u64) -> Self { + Self(value) + } + + /// Total Token expressed in number of nano tokens. + pub const fn as_nano(self) -> u64 { + self.0 + } + + /// Computes `self + rhs`, returning `None` if overflow occurred. + pub fn checked_add(self, rhs: Token) -> Option { + self.0.checked_add(rhs.0).map(Self::from_nano) + } + + /// Computes `self - rhs`, returning `None` if overflow occurred. + pub fn checked_sub(self, rhs: Token) -> Option { + self.0.checked_sub(rhs.0).map(Self::from_nano) + } +} + +impl FromStr for Token { + type Err = Error; + + fn from_str(value_str: &str) -> Result { + let mut itr = value_str.splitn(2, '.'); + let converted_units = { + let units = itr + .next() + .and_then(|s| s.parse::().ok()) + .ok_or_else(|| Error::FailedToParseToken("Can't parse token units".to_string()))?; + + units + .checked_mul(TOKEN_TO_RAW_CONVERSION) + .ok_or(Error::ExcessiveTokenValue)? + }; + + let remainder = { + let remainder_str = itr.next().unwrap_or_default().trim_end_matches('0'); + + if remainder_str.is_empty() { + 0 + } else { + let parsed_remainder = remainder_str.parse::().map_err(|_| { + Error::FailedToParseToken("Can't parse token remainder".to_string()) + })?; + + let remainder_conversion = TOKEN_TO_RAW_POWER_OF_10_CONVERSION + .checked_sub(remainder_str.len() as u32) + .ok_or(Error::LossOfTokenPrecision)?; + parsed_remainder * 10_u64.pow(remainder_conversion) + } + }; + + Ok(Self::from_nano(converted_units + remainder)) + } +} + +impl Display for Token { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + let unit = self.0 / TOKEN_TO_RAW_CONVERSION; + let remainder = self.0 % TOKEN_TO_RAW_CONVERSION; + write!(formatter, "{}.{:09}", unit, remainder) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::u64; + + #[test] + fn from_str() -> Result<()> { + assert_eq!(Token(0), Token::from_str("0")?); + assert_eq!(Token(0), Token::from_str("0.")?); + assert_eq!(Token(0), Token::from_str("0.0")?); + assert_eq!(Token(1), Token::from_str("0.000000001")?); + assert_eq!(Token(1_000_000_000), Token::from_str("1")?); + assert_eq!(Token(1_000_000_000), Token::from_str("1.")?); + assert_eq!(Token(1_000_000_000), Token::from_str("1.0")?); + assert_eq!(Token(1_000_000_001), Token::from_str("1.000000001")?); + assert_eq!(Token(1_100_000_000), Token::from_str("1.1")?); + assert_eq!(Token(1_100_000_001), Token::from_str("1.100000001")?); + assert_eq!( + Token(4_294_967_295_000_000_000), + Token::from_str("4294967295")? + ); + assert_eq!( + Token(4_294_967_295_999_999_999), + Token::from_str("4294967295.999999999")?, + ); + assert_eq!( + Token(4_294_967_295_999_999_999), + Token::from_str("4294967295.9999999990000")?, + ); + + assert_eq!( + Err(Error::FailedToParseToken( + "Can't parse token units".to_string() + )), + Token::from_str("a") + ); + assert_eq!( + Err(Error::FailedToParseToken( + "Can't parse token remainder".to_string() + )), + Token::from_str("0.a") + ); + assert_eq!( + Err(Error::FailedToParseToken( + "Can't parse token remainder".to_string() + )), + Token::from_str("0.0.0") + ); + assert_eq!( + Err(Error::LossOfTokenPrecision), + Token::from_str("0.0000000009") + ); + assert_eq!( + Err(Error::ExcessiveTokenValue), + Token::from_str("18446744074") + ); + Ok(()) + } + + #[test] + fn display() { + assert_eq!("0.000000000", format!("{}", Token(0))); + assert_eq!("0.000000001", format!("{}", Token(1))); + assert_eq!("0.000000010", format!("{}", Token(10))); + assert_eq!("1.000000000", format!("{}", Token(1_000_000_000))); + assert_eq!("1.000000001", format!("{}", Token(1_000_000_001))); + assert_eq!( + "4294967295.000000000", + format!("{}", Token(4_294_967_295_000_000_000)) + ); + } + + #[test] + fn checked_add_sub() { + assert_eq!(Some(Token(3)), Token(1).checked_add(Token(2))); + assert_eq!(None, Token(u64::MAX).checked_add(Token(1))); + assert_eq!(None, Token(u64::MAX).checked_add(Token(u64::MAX))); + + assert_eq!(Some(Token(0)), Token(u64::MAX).checked_sub(Token(u64::MAX))); + assert_eq!(None, Token(0).checked_sub(Token(u64::MAX))); + assert_eq!(None, Token(10).checked_sub(Token(11))); + } +}