From 59c21f8541abcfad79c1654d162b0e5d14b6793d Mon Sep 17 00:00:00 2001 From: oetyng Date: Wed, 22 Mar 2023 15:44:55 +0100 Subject: [PATCH] refactor!: rename commitment to blinded amount This makes it much clearer what we in the domain are handling. The concept "commitment" comes from "Pedersen Commitment", which is the tech we use to implement blinded amounts. What we were doing was to try reflect an impl detail, by calling it "commitment", thus totally missing the aspect of it being a blinded amount, which was and is the important aspect in our domain. --- benches/reissue.rs | 12 +- examples/mint-repl/mint-repl.rs | 25 +-- examples/mint-repl/sample_runs/decode.txt | 24 +-- src/amount_secrets.rs | 202 ---------------------- src/blst.rs | 4 +- src/builder.rs | 58 +++---- src/dbc.rs | 138 ++++++++------- src/dbc_content.rs | 32 ++-- src/error.rs | 19 +- src/lib.rs | 8 +- src/mint.rs | 138 ++++++++------- src/mock/genesis_builder.rs | 20 +-- src/mock/genesis_material.rs | 6 +- src/mock/spentbook.rs | 45 +++-- src/spent_proof.rs | 72 ++++---- src/transaction/error.rs | 22 +-- src/transaction/input.rs | 38 ++-- src/transaction/mod.rs | 49 +----- src/transaction/output.rs | 164 +++++++++--------- src/transaction/revealed_amount.rs | 182 +++++++++++++++++++ src/verification.rs | 46 +++-- 21 files changed, 623 insertions(+), 681 deletions(-) delete mode 100644 src/amount_secrets.rs create mode 100644 src/transaction/revealed_amount.rs diff --git a/benches/reissue.rs b/benches/reissue.rs index 5f9bb55..4cdeb74 100644 --- a/benches/reissue.rs +++ b/benches/reissue.rs @@ -31,7 +31,7 @@ fn bench_reissue_1_to_100(c: &mut Criterion) { .unwrap() .secret_key() .unwrap(), - starting_dbc.amount_secrets_bearer().unwrap(), + starting_dbc.revealed_amount_bearer().unwrap(), ) .add_outputs_by_amount((0..N_OUTPUTS).map(|_| { let owner_once = @@ -80,7 +80,7 @@ fn bench_reissue_100_to_1(c: &mut Criterion) { .unwrap() .secret_key() .unwrap(), - starting_dbc.amount_secrets_bearer().unwrap(), + starting_dbc.revealed_amount_bearer().unwrap(), ) .add_outputs_by_amount((0..N_OUTPUTS).map(|_| { let owner_once = @@ -104,8 +104,8 @@ fn bench_reissue_100_to_1(c: &mut Criterion) { let mut merge_dbc_builder = sn_dbc::TransactionBuilder::default() .add_inputs_by_secrets( dbcs.into_iter() - .map(|(_dbc, owner_once, amount_secrets)| { - (owner_once.as_owner().secret_key().unwrap(), amount_secrets) + .map(|(_dbc, owner_once, revealed_amount)| { + (owner_once.as_owner().secret_key().unwrap(), revealed_amount) }) .collect(), ) @@ -143,7 +143,7 @@ fn generate_dbc_of_value( amount: Token, rng: &mut (impl RngCore + CryptoRng), ) -> Result<(mock::SpentBookNode, Dbc)> { - let (mut spentbook_node, genesis_dbc, _genesis_material, _amount_secrets) = + let (mut spentbook_node, genesis_dbc, _genesis_material, _revealed_amount) = mock::GenesisBuilder::init_genesis_single(rng)?; let output_amounts = vec![ @@ -154,7 +154,7 @@ fn generate_dbc_of_value( let mut dbc_builder = sn_dbc::TransactionBuilder::default() .add_input_by_secrets( genesis_dbc.owner_once_bearer()?.secret_key()?, - genesis_dbc.amount_secrets_bearer()?, + genesis_dbc.revealed_amount_bearer()?, ) .add_outputs_by_amount(output_amounts.into_iter().map(|amount| { let owner_once = OwnerOnce::from_owner_base(Owner::from_random_secret_key(rng), rng); diff --git a/examples/mint-repl/mint-repl.rs b/examples/mint-repl/mint-repl.rs index 9851042..61271f5 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, Dbc, DbcBuilder, DbcTransaction, Hash, OutputOwnerMap, Owner, OwnerOnce, - RevealedCommitment, RevealedTransaction, Token, TransactionBuilder, + rng, Dbc, DbcBuilder, DbcTransaction, Hash, OutputOwnerMap, Owner, OwnerOnce, RevealedAmount, + RevealedTransaction, Token, TransactionBuilder, }; use std::collections::{BTreeMap, HashMap}; @@ -60,7 +60,7 @@ impl MintInfo { #[derive(Debug, Clone)] struct DbcTransactionRevealed { inner: DbcTransaction, - revealed_commitments: Vec, + revealed_amounts: Vec, revealed_tx: RevealedTransaction, output_owner_map: OutputOwnerMap, } @@ -183,9 +183,10 @@ fn mk_new_mint(sks: SecretKeySet, poly: Poly) -> Result { let num_spentbook_nodes = sks.threshold() + 1; - let (spentbook_nodes, genesis_dbc, _genesis, _amount_secrets) = mock::GenesisBuilder::default() - .gen_spentbook_nodes_with_sks(num_spentbook_nodes, &sks) - .build(&mut rng)?; + let (spentbook_nodes, genesis_dbc, _genesis, _revealed_amount) = + mock::GenesisBuilder::default() + .gen_spentbook_nodes_with_sks(num_spentbook_nodes, &sks) + .build(&mut rng)?; let reissue_auto = ReissueAuto::from(vec![genesis_dbc.clone()]); @@ -372,22 +373,22 @@ fn print_dbc_human(dbc: &Dbc, outputs: bool, secret_key_base: Option) let result = match secret_key_base { // use base SecretKey from input param if available. - Some(key_base) => Some((dbc.owner_once(&key_base)?, dbc.amount_secrets(&key_base)?)), + Some(key_base) => Some((dbc.owner_once(&key_base)?, dbc.revealed_amount(&key_base)?)), // use base SecretKey from dbc if available (bearer) - None if dbc.is_bearer() => Some((dbc.owner_once_bearer()?, dbc.amount_secrets_bearer()?)), + None if dbc.is_bearer() => Some((dbc.owner_once_bearer()?, dbc.revealed_amount_bearer()?)), // Otherwise, have only the pubkey _ => None, }; match result { - Some((ref _owner_once, ref amount_secrets)) => { + Some((ref _owner_once, ref revealed_amount)) => { println!("*** Secrets (decrypted) ***"); - println!(" amount: {}\n", amount_secrets.amount()); + println!(" amount: {}\n", revealed_amount.value()); println!( " blinding_factor: {}\n", - to_be_hex(&amount_secrets.blinding_factor())? + to_be_hex(&revealed_amount.blinding_factor())? ); } None => { @@ -813,7 +814,7 @@ fn reissue(mintinfo: &mut MintInfo, dbc_builder: DbcBuilder) -> Result<()> { let output_dbcs = dbc_builder.build(&mintinfo.spentbook_nodes[0].key_manager)?; // for each output, construct Dbc and display - for (dbc, _owner_once, _amount_secrets) in output_dbcs.iter() { + for (dbc, _owner_once, _revealed_amount) in output_dbcs.iter() { println!("\n-- Begin DBC --"); print_dbc_human(dbc, false, None)?; println!("-- End DBC --\n"); diff --git a/examples/mint-repl/sample_runs/decode.txt b/examples/mint-repl/sample_runs/decode.txt index 84684e1..9f73817 100644 --- a/examples/mint-repl/sample_runs/decode.txt +++ b/examples/mint-repl/sample_runs/decode.txt @@ -79,7 +79,7 @@ ReissueTransactionUnblinded { "0000000000000000000000000000000000000000000000000000000000000000", ), }, - amount_secrets_cipher: Ciphertext( + revealed_amount_cipher: Ciphertext( G1 { x: Fq(0x130719c576b35a1b371d5382925e7dc2415e286f9f20c615b8b692a18677b1df27cb706072fc282c8390c51d113cebeb), y: Fq(0x0297a358c296fa9eb03bd7a4c5b2d8c91f5ae62b93c346651cb6387f927b8a01e029a83c7f36ff4120c02432d9cf80b4), @@ -101,7 +101,7 @@ ReissueTransactionUnblinded { }, }, ), - commitment: CompressedRistretto: [204, 45, 239, 130, 62, 21, 78, 203, 204, 100, 52, 129, 191, 119, 252, 229, 226, 179, 112, 117, 218, 160, 246, 253, 217, 28, 111, 200, 241, 149, 30, 41], + blinded_amount: CompressedRistretto: [204, 45, 239, 130, 62, 21, 78, 203, 204, 100, 52, 129, 191, 119, 252, 229, 226, 179, 112, 117, 218, 160, 246, 253, 217, 28, 111, 200, 241, 149, 30, 41], range_proof_bytes: [...], output_number: 0, owner: BlindedOwner( @@ -139,7 +139,7 @@ ReissueTransactionUnblinded { "21c5e168173d79fea90489226ed5ce4de64353700f5231e8bf882a0cfb391561", ), }, - amount_secrets_cipher: Ciphertext( + revealed_amount_cipher: Ciphertext( G1 { x: Fq(0x07b545ec6fbe47eef82fb2d7524a1a91b4464fc6ed23578a307b73875d7b46cf6683864414e1c437db7e66ad46d16acf), y: Fq(0x1580abf66f1b988873d330277f96a778b255e441650e31dff591554f26f97481dff75f65de3e7216b94ec88ac086c075), @@ -161,7 +161,7 @@ ReissueTransactionUnblinded { }, }, ), - commitment: CompressedRistretto: [92, 249, 1, 19, 34, 185, 36, 228, 200, 31, 127, 166, 227, 52, 94, 147, 89, 105, 121, 86, 177, 185, 251, 236, 150, 210, 244, 167, 33, 121, 239, 65], + blinded_amount: CompressedRistretto: [92, 249, 1, 19, 34, 185, 36, 228, 200, 31, 127, 166, 227, 52, 94, 147, 89, 105, 121, 86, 177, 185, 251, 236, 150, 210, 244, 167, 33, 121, 239, 65], range_proof_bytes: [...], output_number: 0, owner: BlindedOwner( @@ -176,7 +176,7 @@ ReissueTransactionUnblinded { "21c5e168173d79fea90489226ed5ce4de64353700f5231e8bf882a0cfb391561", ), }, - amount_secrets_cipher: Ciphertext( + revealed_amount_cipher: Ciphertext( G1 { x: Fq(0x0656e69e25038db6903e3e4d435ab94afbe84f6859b8ecf4745576ce2e18618582535919ae1d791a8434050dfcafd6f7), y: Fq(0x014d885eb03664fd475558af505d2427a2390c4d73d1fd2a687169b5d326947237fe7b2589fcf91a38a1728f3b7e367b), @@ -198,7 +198,7 @@ ReissueTransactionUnblinded { }, }, ), - commitment: CompressedRistretto: [10, 40, 2, 63, 154, 131, 103, 22, 244, 229, 126, 227, 200, 207, 43, 177, 196, 158, 109, 247, 103, 225, 223, 37, 254, 85, 80, 245, 30, 25, 123, 10], + blinded_amount: CompressedRistretto: [10, 40, 2, 63, 154, 131, 103, 22, 244, 229, 126, 227, 200, 207, 43, 177, 196, 158, 109, 247, 103, 225, 223, 37, 254, 85, 80, 245, 30, 25, 123, 10], range_proof_bytes: [...], output_number: 1, owner: BlindedOwner( @@ -277,7 +277,7 @@ ReissueRequestUnblinded { "0000000000000000000000000000000000000000000000000000000000000000", ), }, - amount_secrets_cipher: Ciphertext( + revealed_amount_cipher: Ciphertext( G1 { x: Fq(0x130719c576b35a1b371d5382925e7dc2415e286f9f20c615b8b692a18677b1df27cb706072fc282c8390c51d113cebeb), y: Fq(0x0297a358c296fa9eb03bd7a4c5b2d8c91f5ae62b93c346651cb6387f927b8a01e029a83c7f36ff4120c02432d9cf80b4), @@ -299,7 +299,7 @@ ReissueRequestUnblinded { }, }, ), - commitment: CompressedRistretto: [204, 45, 239, 130, 62, 21, 78, 203, 204, 100, 52, 129, 191, 119, 252, 229, 226, 179, 112, 117, 218, 160, 246, 253, 217, 28, 111, 200, 241, 149, 30, 41], + blinded_amount: CompressedRistretto: [204, 45, 239, 130, 62, 21, 78, 203, 204, 100, 52, 129, 191, 119, 252, 229, 226, 179, 112, 117, 218, 160, 246, 253, 217, 28, 111, 200, 241, 149, 30, 41], range_proof_bytes: [...], output_number: 0, owner: BlindedOwner( @@ -337,7 +337,7 @@ ReissueRequestUnblinded { "21c5e168173d79fea90489226ed5ce4de64353700f5231e8bf882a0cfb391561", ), }, - amount_secrets_cipher: Ciphertext( + revealed_amount_cipher: Ciphertext( G1 { x: Fq(0x0656e69e25038db6903e3e4d435ab94afbe84f6859b8ecf4745576ce2e18618582535919ae1d791a8434050dfcafd6f7), y: Fq(0x014d885eb03664fd475558af505d2427a2390c4d73d1fd2a687169b5d326947237fe7b2589fcf91a38a1728f3b7e367b), @@ -359,7 +359,7 @@ ReissueRequestUnblinded { }, }, ), - commitment: CompressedRistretto: [10, 40, 2, 63, 154, 131, 103, 22, 244, 229, 126, 227, 200, 207, 43, 177, 196, 158, 109, 247, 103, 225, 223, 37, 254, 85, 80, 245, 30, 25, 123, 10], + blinded_amount: CompressedRistretto: [10, 40, 2, 63, 154, 131, 103, 22, 244, 229, 126, 227, 200, 207, 43, 177, 196, 158, 109, 247, 103, 225, 223, 37, 254, 85, 80, 245, 30, 25, 123, 10], range_proof_bytes: [...], output_number: 1, owner: BlindedOwner( @@ -374,7 +374,7 @@ ReissueRequestUnblinded { "21c5e168173d79fea90489226ed5ce4de64353700f5231e8bf882a0cfb391561", ), }, - amount_secrets_cipher: Ciphertext( + revealed_amount_cipher: Ciphertext( G1 { x: Fq(0x07b545ec6fbe47eef82fb2d7524a1a91b4464fc6ed23578a307b73875d7b46cf6683864414e1c437db7e66ad46d16acf), y: Fq(0x1580abf66f1b988873d330277f96a778b255e441650e31dff591554f26f97481dff75f65de3e7216b94ec88ac086c075), @@ -396,7 +396,7 @@ ReissueRequestUnblinded { }, }, ), - commitment: CompressedRistretto: [92, 249, 1, 19, 34, 185, 36, 228, 200, 31, 127, 166, 227, 52, 94, 147, 89, 105, 121, 86, 177, 185, 251, 236, 150, 210, 244, 167, 33, 121, 239, 65], + blinded_amount: CompressedRistretto: [92, 249, 1, 19, 34, 185, 36, 228, 200, 31, 127, 166, 227, 52, 94, 147, 89, 105, 121, 86, 177, 185, 251, 236, 150, 210, 244, 167, 33, 121, 239, 65], range_proof_bytes: [...], output_number: 0, owner: BlindedOwner( diff --git a/src/amount_secrets.rs b/src/amount_secrets.rs deleted file mode 100644 index 001da47..0000000 --- a/src/amount_secrets.rs +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2023 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 crate::transaction::{Amount, RevealedCommitment}; -use crate::{rand::RngCore, BlindingFactor, Commitment, Error, Token}; -use blsttc::rand::CryptoRng; -use blsttc::{ - Ciphertext, DecryptionShare, IntoFr, PublicKey, PublicKeySet, SecretKey, SecretKeySet, - SecretKeyShare, -}; -use bulletproofs::PedersenGens; -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 BF_SIZE: usize = std::mem::size_of::(); // Blinding factor size: 32 bytes (BlindingFactor) - -/// AmountSecrets wraps crate::transaction::RevealedCommitment to provide some methods -/// for ergonomic usage, eg: decrypting from Ciphertext using various blsttc -/// components, eg SecretKey, SecretKeyShare, SecretKeySet, DecryptionShare -// -// todo: perhaps AmountSecrets should be renamed to be more consistent with -// RevealedCommitment, since it is just a NewType wrapper. -// -// Once crate::transaction uses blsttc, perhaps AmountSecrets functionality could -// move into RevealedCommitment, and AmountSecrets goes away entirely. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone)] -pub struct AmountSecrets(RevealedCommitment); - -impl AmountSecrets { - /// amount getter - pub fn amount(&self) -> Token { - Token::from_nano(self.0.value) - } - - /// blinding factor getter - pub fn blinding_factor(&self) -> BlindingFactor { - self.0.blinding - } - - pub fn commitment(&self) -> Commitment { - self.0.commit(&PedersenGens::default()) - } - - /// Convert to bytes - pub fn to_bytes(&self) -> Vec { - self.0.to_bytes() - } - - /// build AmountSecrets from fixed size byte array. - pub fn from_bytes(bytes: [u8; AMT_SIZE + BF_SIZE]) -> Self { - let amount = Amount::from_le_bytes({ - let mut b = [0u8; AMT_SIZE]; - b.copy_from_slice(&bytes[0..AMT_SIZE]); - b - }); - let mut b = [0u8; BF_SIZE]; - let blinding_factor = BlindingFactor::from_bytes_mod_order({ - b.copy_from_slice(&bytes[AMT_SIZE..]); - b - }); - - Self(RevealedCommitment { - value: amount, - blinding: blinding_factor, - }) - } - - /// build AmountSecrets from byte array reference - pub fn from_bytes_ref(bytes: &[u8]) -> Result { - if bytes.len() != AMT_SIZE + BF_SIZE { - return Err(Error::AmountSecretsBytesInvalid); - } - let amount = Amount::from_le_bytes({ - let mut b = [0u8; AMT_SIZE]; - b.copy_from_slice(&bytes[0..AMT_SIZE]); - b - }); - let mut b = [0u8; BF_SIZE]; - let blinding_factor = BlindingFactor::from_bytes_mod_order({ - b.copy_from_slice(&bytes[AMT_SIZE..]); - b - }); - - Ok(Self(RevealedCommitment { - value: amount, - blinding: blinding_factor, - })) - } - - /// build AmountSecrets from an Amount. - /// A blinding factor will be randomly generated. - pub fn from_amount(amount: Amount, rng: &mut (impl RngCore + CryptoRng)) -> Self { - Self(RevealedCommitment::from_value(amount, rng)) - } - - /// encrypt secrets to public_key producing Ciphertext - pub fn encrypt(&self, public_key: &PublicKey) -> Ciphertext { - public_key.encrypt(self.to_bytes()) - } -} - -impl From for AmountSecrets { - /// create AmountSecrets from an amount and a randomly generated blinding factor - fn from(revealed_commitment: RevealedCommitment) -> Self { - Self(revealed_commitment) - } -} - -impl From<(Amount, BlindingFactor)> for AmountSecrets { - /// create AmountSecrets from an amount and a randomly generated blinding factor - fn from(params: (Amount, BlindingFactor)) -> Self { - let (value, blinding) = params; - - Self(RevealedCommitment { value, blinding }) - } -} - -impl From for RevealedCommitment { - /// convert AmountSecrets into RevealedCommitment - fn from(a: AmountSecrets) -> RevealedCommitment { - a.0 - } -} - -impl From for Amount { - /// convert AmountSecrets into an Amount - fn from(a: AmountSecrets) -> Amount { - a.0.value() - } -} - -impl TryFrom<(&SecretKey, &Ciphertext)> for AmountSecrets { - type Error = Error; - - /// Decrypt AmountSecrets ciphertext using a SecretKey - fn try_from(params: (&SecretKey, &Ciphertext)) -> Result { - let (secret_key, ciphertext) = params; - let bytes_vec = secret_key - .decrypt(ciphertext) - .ok_or(Error::DecryptionBySecretKeyFailed)?; - Self::from_bytes_ref(&bytes_vec) - } -} - -impl TryFrom<(&SecretKeySet, &Ciphertext)> for AmountSecrets { - type Error = Error; - - /// Decrypt AmountSecrets ciphertext using a SecretKeySet - fn try_from(params: (&SecretKeySet, &Ciphertext)) -> Result { - let (secret_key_set, ciphertext) = params; - Self::try_from((&secret_key_set.secret_key(), ciphertext)) - } -} - -impl TryFrom<(&PublicKeySet, &BTreeMap, &Ciphertext)> - for AmountSecrets -{ - type Error = Error; - - /// Decrypt AmountSecrets ciphertext using threshold+1 SecretKeyShares - fn try_from( - params: (&PublicKeySet, &BTreeMap, &Ciphertext), - ) -> Result { - let (public_key_set, secret_key_shares, ciphertext) = params; - - let mut decryption_shares: BTreeMap = Default::default(); - for (idx, sec_share) in secret_key_shares.iter() { - let share = sec_share.decrypt_share_no_verify(ciphertext); - decryption_shares.insert(*idx, share); - } - Self::try_from((public_key_set, &decryption_shares, ciphertext)) - } -} - -impl TryFrom<(&PublicKeySet, &BTreeMap, &Ciphertext)> - for AmountSecrets -{ - type Error = Error; - - /// Decrypt AmountSecrets using threshold+1 DecryptionShares - /// - /// This fn should be used when keys (SecretKeyShare) are distributed across multiple parties. - /// In which case each party will need to call SecretKeyShare::decrypt_share() or - /// decrypt_share_no_verify() to generate a DecryptionShare and one party will need to - /// obtain/aggregate all the shares together somehow. - fn try_from( - params: (&PublicKeySet, &BTreeMap, &Ciphertext), - ) -> Result { - let (public_key_set, decryption_shares, ciphertext) = params; - let bytes_vec = public_key_set.decrypt(decryption_shares, ciphertext)?; - Self::from_bytes_ref(&bytes_vec) - } -} diff --git a/src/blst.rs b/src/blst.rs index 976cc01..13f5c2e 100644 --- a/src/blst.rs +++ b/src/blst.rs @@ -14,8 +14,8 @@ //! //! sn_dbc internally uses the type aliases rather than directly using the blstrs types. -/// a Commitment -pub type Commitment = curve25519_dalek::ristretto::RistrettoPoint; +/// An amount that is blinded, by the means of a `Pedersen commitment`. +pub type BlindedAmount = curve25519_dalek::ristretto::RistrettoPoint; /// a BlindingFactor pub type BlindingFactor = curve25519_dalek::scalar::Scalar; diff --git a/src/builder.rs b/src/builder.rs index 4debf43..04ee002 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -14,11 +14,11 @@ use std::{ }; use crate::transaction::{ - DbcTransaction, Output, RevealedCommitment, RevealedInput, RevealedTransaction, + DbcTransaction, Output, RevealedAmount, RevealedInput, RevealedTransaction, }; use crate::{ rand::{CryptoRng, RngCore}, - AmountSecrets, Commitment, Dbc, DbcContent, Error, Hash, OwnerOnce, Result, SpentProof, + BlindedAmount, Dbc, DbcContent, Error, Hash, OwnerOnce, Result, SpentProof, SpentProofKeyVerifier, SpentProofShare, Token, TransactionVerifier, }; @@ -72,7 +72,7 @@ impl TransactionBuilder { Ok(self) } - /// add an input given a list of bearer Dbcs and associated SecretKey + /// Add an input given a list of bearer Dbcs and associated SecretKey. pub fn add_inputs_dbc_bearer(mut self, dbcs: impl Iterator) -> Result where D: Borrow, @@ -83,21 +83,21 @@ impl TransactionBuilder { Ok(self) } - /// add an input given a SecretKey, AmountSecrets + /// Add an input given a SecretKey and a RevealedAmount. pub fn add_input_by_secrets( mut self, secret_key: SecretKey, - amount_secrets: AmountSecrets, + revealed_amount: RevealedAmount, ) -> Self { - let revealed_input = RevealedInput::new(secret_key, amount_secrets.into()); + let revealed_input = RevealedInput::new(secret_key, revealed_amount); self = self.add_input(revealed_input); self } - /// add an input given a list of (SecretKey, AmountSecrets, and list of decoys) - pub fn add_inputs_by_secrets(mut self, secrets: Vec<(SecretKey, AmountSecrets)>) -> Self { - for (secret_key, amount_secrets) in secrets.into_iter() { - self = self.add_input_by_secrets(secret_key, amount_secrets); + /// Add an input given a list of (SecretKey, RevealedAmount). + pub fn add_inputs_by_secrets(mut self, secrets: Vec<(SecretKey, RevealedAmount)>) -> Self { + for (secret_key, revealed_amount) in secrets.into_iter() { + self = self.add_input_by_secrets(secret_key, revealed_amount); } self } @@ -152,7 +152,7 @@ impl TransactionBuilder { .revealed_tx .inputs .iter() - .map(|t| t.revealed_commitment.value) + .map(|t| t.revealed_amount.value) .sum(); Token::from_nano(amount) @@ -176,11 +176,11 @@ impl TransactionBuilder { /// build a DbcTransaction and associated secrets pub fn build(self, rng: impl RngCore + CryptoRng) -> Result { - let (transaction, revealed_commitments) = self.revealed_tx.sign(rng)?; + let (transaction, revealed_amounts) = self.revealed_tx.sign(rng)?; Ok(DbcBuilder::new( transaction, - revealed_commitments, + revealed_amounts, self.output_owner_map, self.revealed_tx, )) @@ -192,7 +192,7 @@ impl TransactionBuilder { #[derive(Debug, Clone)] pub struct DbcBuilder { pub transaction: DbcTransaction, - pub revealed_commitments: Vec, + pub revealed_amounts: Vec, pub output_owner_map: OutputOwnerMap, pub revealed_tx: RevealedTransaction, pub spent_proofs: HashSet, @@ -204,13 +204,13 @@ impl DbcBuilder { /// Create a new DbcBuilder pub fn new( transaction: DbcTransaction, - revealed_commitments: Vec, + revealed_amounts: Vec, output_owner_map: OutputOwnerMap, revealed_tx: RevealedTransaction, ) -> Self { Self { transaction, - revealed_commitments, + revealed_amounts, output_owner_map, revealed_tx, spent_proofs: Default::default(), @@ -272,7 +272,7 @@ impl DbcBuilder { pub fn build( self, verifier: &K, - ) -> Result> { + ) -> Result> { let spent_proofs = self.spent_proofs()?; // verify the Tx, along with spent proofs. @@ -292,7 +292,7 @@ impl DbcBuilder { } /// Build the output DBCs (no verification over Tx or spentproof is performed). - pub fn build_without_verifying(self) -> Result> { + pub fn build_without_verifying(self) -> Result> { let spent_proofs = self.spent_proofs()?; self.build_output_dbcs(spent_proofs) } @@ -301,12 +301,12 @@ impl DbcBuilder { fn build_output_dbcs( self, spent_proofs: BTreeSet, - ) -> Result> { + ) -> Result> { let pc_gens = PedersenGens::default(); - let output_commitments: Vec<(Commitment, RevealedCommitment)> = self - .revealed_commitments + let output_blinded_and_revealed_amounts: Vec<(BlindedAmount, RevealedAmount)> = self + .revealed_amounts .iter() - .map(|r| (r.commit(&pc_gens), *r)) + .map(|r| (r.blinded_amount(&pc_gens), *r)) .collect(); let owner_once_list: Vec<&OwnerOnce> = self @@ -321,23 +321,23 @@ impl DbcBuilder { .collect::>()?; // Form the final output DBCs - let output_dbcs: Vec<(Dbc, OwnerOnce, AmountSecrets)> = self + let output_dbcs: Vec<(Dbc, OwnerOnce, RevealedAmount)> = self .transaction .outputs .iter() .zip(owner_once_list) .map(|(output, owner_once)| { - let amount_secrets_list: Vec = output_commitments + let revealed_amounts: Vec = output_blinded_and_revealed_amounts .iter() - .filter(|(c, _)| *c == output.commitment()) - .map(|(_, r)| AmountSecrets::from(*r)) + .filter(|(c, _)| *c == output.blinded_amount()) + .map(|(_, r)| *r) .collect(); - assert_eq!(amount_secrets_list.len(), 1); + assert_eq!(revealed_amounts.len(), 1); let content = DbcContent::from(( owner_once.owner_base.clone(), owner_once.derivation_index, - amount_secrets_list[0].clone(), + revealed_amounts[0], )); let public_key = owner_once .owner_base @@ -350,7 +350,7 @@ impl DbcBuilder { inputs_spent_proofs: spent_proofs.clone(), inputs_spent_transactions: self.spent_transactions.values().cloned().collect(), }; - (dbc, owner_once.clone(), amount_secrets_list[0].clone()) + (dbc, owner_once.clone(), revealed_amounts[0]) }) .collect(); diff --git a/src/dbc.rs b/src/dbc.rs index 64cd5e7..ba00070 100644 --- a/src/dbc.rs +++ b/src/dbc.rs @@ -13,9 +13,9 @@ use tiny_keccak::{Hasher, Sha3}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use crate::transaction::{DbcTransaction, OutputProof, RevealedCommitment, RevealedInput}; +use crate::transaction::{DbcTransaction, OutputProof, RevealedAmount, RevealedInput}; use crate::{ - AmountSecrets, Commitment, DbcContent, DerivationIndex, Error, Hash, Owner, Result, SpentProof, + BlindedAmount, DbcContent, DerivationIndex, Error, Hash, Owner, Result, SpentProof, SpentProofKeyVerifier, TransactionVerifier, }; @@ -50,13 +50,13 @@ use crate::{ /// /// To spend or work with a Bearer Dbc, wallet software can either: /// 1. use the bearer API methods that do not require a SecretKey, eg: -/// `dbc.amount_secrets_bearer()` +/// `dbc.revealed_amount_bearer()` /// /// -- or -- /// /// 2. obtain the Owner Base SecretKey from the Dbc and then call /// the Owner API methods that require a SecretKey. eg: -/// `dbc.amount_secrets(&dbc.dbc.owner_base().secret_key()?)` +/// `dbc.revealed_amount(&dbc.dbc.owner_base().secret_key()?)` /// /// Sometimes the latter method can be better when working with mixed /// types of Dbcs. A useful pattern is to check up-front if the Dbc is bearer @@ -125,16 +125,16 @@ impl Dbc { self.owner_base().has_secret_key() } - /// decypts and returns the AmountSecrets - pub fn amount_secrets(&self, base_sk: &SecretKey) -> Result { + /// Decrypt and return the revealed amount. + pub fn revealed_amount(&self, base_sk: &SecretKey) -> Result { let sk = self.owner_once(base_sk)?.secret_key()?; - AmountSecrets::try_from((&sk, &self.content.amount_secrets_cipher)) + RevealedAmount::try_from((&sk, &self.content.revealed_amount_cipher)) } - /// decypts and returns the AmountSecrets - /// will return an error if the SecretKey is not available. (not bearer) - pub fn amount_secrets_bearer(&self) -> Result { - self.amount_secrets(&self.owner_base().secret_key()?) + /// Decrypt and return the RevealedAmount, only if this Dbc is a bearer. + /// Thus, it will return an error if a SecretKey is not available. + pub fn revealed_amount_bearer(&self) -> Result { + self.revealed_amount(&self.owner_base().secret_key()?) } /// returns the reason (if any) why this dbc was spent @@ -155,20 +155,20 @@ impl Dbc { Ok(secret_key.public_key()) } - /// returns PublicKey of the DBC + /// Return public key of this Dbc. pub fn public_key(&self) -> PublicKey { self.public_key } - /// returns the amount commitment for this DBC - pub fn commitment(&self) -> Result { + /// Return the blinded amount for this Dbc. + pub fn blinded_amount(&self) -> Result { Ok(self .transaction .outputs .iter() .find(|o| &self.public_key() == o.public_key()) .ok_or(Error::OutputProofNotFound)? - .commitment()) + .blinded_amount()) } /// returns PublicKey for the owner's derived public key @@ -183,7 +183,7 @@ impl Dbc { pub fn as_revealed_input(&self, base_sk: &SecretKey) -> Result { Ok(RevealedInput::new( self.owner_once(base_sk)?.secret_key()?, - self.amount_secrets(base_sk)?.into(), + self.revealed_amount(base_sk)?, )) } @@ -228,8 +228,8 @@ impl Dbc { /// see TransactionVerifier::verify() for a description of /// verifier requirements. /// - /// see comments for Dbc::verify_amount_matches_commitment() for a - /// description of how to handle Error::AmountCommitmentsDoNotMatch + /// see comments for Dbc::verify_amounts() for a + /// description of how to handle Error::BlindedAmountsDoNotMatch pub fn verify( &self, base_sk: &SecretKey, @@ -267,7 +267,7 @@ impl Dbc { return Err(Error::SpentProofShareReasonMismatch(owner)); } - self.verify_amount_matches_commitment(base_sk) + self.verify_amounts(base_sk) } /// bearer version of verify() @@ -317,35 +317,41 @@ impl Dbc { Ok(()) } - /// Checks if the provided AmountSecrets matches the amount commitment. - /// note that both the amount and blinding_factor must be correct. + /// Checks if the encrypted amount + blinding factor in the Dbc equals + /// the blinded amount in the transaction. + /// This is done by + /// 1. Decrypting the `revealed_amount_cipher` into a RevealedAmount. + /// 2. Forming a BlindedAmount out of the RevealedAmount. + /// 3. Comparing that instance with the one in the dbc output proof in the tx. /// - /// If the commitments do not match, then the Dbc cannot be spent - /// using the AmountSecrets provided. + /// If the blinded amounts do not match, then the Dbc cannot be spent + /// using the RevealedAmount provided. /// /// To clarify, the Dbc is still spendable, however the correct - /// AmountSecrets need to be obtained from the sender somehow. + /// RevealedAmount need to be obtained from the sender somehow. /// /// As an example, if the Dbc recipient is a merchant, they typically /// would not provide goods to the purchaser if this check fails. /// However the purchaser may still be able to remedy the situation by - /// providing the correct AmountSecrets to the merchant. + /// providing the correct RevealedAmount to the merchant. /// /// If the merchant were to send the goods without first performing /// this check, then they could be stuck with an unspendable Dbc /// and no recourse. - pub(crate) fn verify_amount_matches_commitment(&self, base_sk: &SecretKey) -> Result<()> { - let rc: RevealedCommitment = self.amount_secrets(base_sk)?.into(); - let secrets_commitment = rc.commit(&Default::default()); - let tx_commitment = self.my_output_proof(base_sk)?.commitment(); + pub(crate) fn verify_amounts(&self, base_sk: &SecretKey) -> Result<()> { + let revealed_amount: RevealedAmount = self.revealed_amount(base_sk)?; + let blinded_amount = revealed_amount.blinded_amount(&Default::default()); + let blinded_amount_in_tx = self.output_proof(base_sk)?.blinded_amount(); - match secrets_commitment == tx_commitment { + match blinded_amount == blinded_amount_in_tx { true => Ok(()), - false => Err(Error::AmountCommitmentsDoNotMatch), + false => Err(Error::BlindedAmountsDoNotMatch), } } - fn my_output_proof(&self, base_sk: &SecretKey) -> Result<&OutputProof> { + /// The output proof for this Dbc, is found in + /// the transaction that gave rise to this Dbc. + fn output_proof(&self, base_sk: &SecretKey) -> Result<&OutputProof> { let owner = self.owner_once(base_sk)?.public_key(); self.transaction .outputs @@ -364,7 +370,7 @@ pub(crate) mod tests { use crate::{ mock, rand::{CryptoRng, RngCore}, - AmountSecrets, DbcBuilder, Hash, Owner, OwnerOnce, SpentProofContent, Token, + DbcBuilder, Hash, Owner, OwnerOnce, SpentProofContent, Token, }; use blsttc::PublicKey; use bulletproofs::PedersenGens; @@ -383,16 +389,16 @@ pub(crate) mod tests { fn prepare_even_split( dbc_owner: SecretKey, - amount_secrets: AmountSecrets, + revealed_amount: RevealedAmount, n_ways: u8, output_owners: Vec, spentbook_node: &mut mock::SpentBookNode, rng: &mut (impl RngCore + CryptoRng), ) -> Result { - let amount = amount_secrets.amount(); + let amount = Token::from_nano(revealed_amount.value()); let mut dbc_builder = crate::TransactionBuilder::default() - .add_input_by_secrets(dbc_owner, amount_secrets) + .add_input_by_secrets(dbc_owner, revealed_amount) .add_outputs_by_amount(divide(amount, n_ways).zip(output_owners.into_iter())) .build(rng)?; @@ -419,13 +425,13 @@ pub(crate) mod tests { inputs: vec![], outputs: vec![Output::new(owner_once.as_owner().public_key(), amount)], }; - let (transaction, revealed_commitments) = tx_material + let (transaction, revealed_amounts) = tx_material .sign(&mut rng) .expect("Failed to sign transaction"); let input_content = DbcContent::from(( owner_once.owner_base.clone(), owner_once.derivation_index, - AmountSecrets::from(revealed_commitments[0]), + revealed_amounts[0], )); let public_key = owner_once .owner_base @@ -442,8 +448,8 @@ pub(crate) mod tests { let hex = dbc.to_hex()?; let dbc = Dbc::from_hex(&hex)?; - let amount = dbc.amount_secrets_bearer()?.amount(); - assert_eq!(amount, Token::from_nano(1_530_000_000)); + let amount = dbc.revealed_amount_bearer()?.value(); + assert_eq!(amount, 1_530_000_000); Ok(()) } @@ -457,13 +463,13 @@ pub(crate) mod tests { inputs: vec![], outputs: vec![Output::new(owner_once.as_owner().public_key(), amount)], }; - let (transaction, revealed_commitments) = tx_material + let (transaction, revealed_amounts) = tx_material .sign(&mut rng) .expect("Failed to sign transaction"); let input_content = DbcContent::from(( owner_once.owner_base.clone(), owner_once.derivation_index, - AmountSecrets::from(revealed_commitments[0]), + revealed_amounts[0], )); let public_key = owner_once .owner_base @@ -480,8 +486,8 @@ pub(crate) mod tests { let hex = dbc.to_hex()?; let dbc_from_hex = Dbc::from_hex(&hex)?; - let left = dbc.amount_secrets_bearer()?.amount(); - let right = dbc_from_hex.amount_secrets_bearer()?.amount(); + let left = dbc.revealed_amount_bearer()?.value(); + let right = dbc_from_hex.revealed_amount_bearer()?.value(); assert_eq!(left, right); Ok(()) } @@ -553,16 +559,16 @@ pub(crate) mod tests { outputs: vec![Output::new(owner_once.as_owner().public_key(), amount)], }; - let (transaction, revealed_commitments) = tx_material + let (transaction, revealed_amounts) = tx_material .sign(&mut rng) .expect("Failed to sign transaction"); - assert_eq!(revealed_commitments.len(), 1); + assert_eq!(revealed_amounts.len(), 1); let input_content = DbcContent::from(( owner_once.owner_base.clone(), owner_once.derivation_index, - AmountSecrets::from(revealed_commitments[0]), + revealed_amounts[0], )); let public_key = owner_once @@ -628,7 +634,7 @@ pub(crate) mod tests { let dbc_builder = prepare_even_split( starting_dbc.owner_once_bearer()?.secret_key()?, - starting_dbc.amount_secrets_bearer()?, + starting_dbc.revealed_amount_bearer()?, n_inputs.coerce(), input_owners, &mut spentbook_node, @@ -650,7 +656,7 @@ pub(crate) mod tests { // The outputs become inputs for next tx. let inputs: Vec<(Dbc, SecretKey)> = output_dbcs .into_iter() - .map(|(dbc, owner_once, _amount_secrets)| { + .map(|(dbc, owner_once, _revealed_amount)| { (dbc, owner_once.owner_base().secret_key().unwrap()) }) .collect(); @@ -673,31 +679,31 @@ pub(crate) mod tests { .add_spent_transaction(tx); } - // We must obtain the RevealedCommitment for our output in order to + // We must obtain the RevealedAmount for our output in order to // know the correct blinding factor when creating fuzzed_amt_secrets. let output = dbc_builder.transaction.outputs.get(0).unwrap(); let pc_gens = PedersenGens::default(); - let output_commitments: Vec<(Commitment, RevealedCommitment)> = dbc_builder - .revealed_commitments + let output_blinded_and_revealed_amounts: Vec<(BlindedAmount, RevealedAmount)> = dbc_builder + .revealed_amounts .iter() - .map(|r| (r.commit(&pc_gens), *r)) + .map(|r| (r.blinded_amount(&pc_gens), *r)) .collect(); - let amount_secrets_list: Vec = output_commitments + let revealed_amount_list: Vec = output_blinded_and_revealed_amounts .iter() - .filter(|(c, _)| *c == output.commitment()) - .map(|(_, r)| AmountSecrets::from(*r)) + .filter(|(c, _)| *c == output.blinded_amount()) + .map(|(_, r)| *r) .collect(); - let fuzzed_amt_secrets = AmountSecrets::from(( + let fuzzed_revealed_amount = RevealedAmount::from(( amount + extra_output_amount.coerce::(), - amount_secrets_list[0].blinding_factor(), + revealed_amount_list[0].blinding_factor(), )); - let dbc_amount = fuzzed_amt_secrets.amount(); + let dbc_amount = fuzzed_revealed_amount.value(); let fuzzed_content = DbcContent::from(( owner_once.owner_base.clone(), owner_once.derivation_index, - fuzzed_amt_secrets, + fuzzed_revealed_amount, )); let mut fuzzed_spent_proofs: BTreeSet = BTreeSet::new(); @@ -764,7 +770,7 @@ pub(crate) mod tests { public_key: secret_key.public_key(), transaction_hash: spent_proof.transaction_hash(), reason: Hash::default(), - public_commitment: *spent_proof.public_commitment(), + blinded_amount: *spent_proof.blinded_amount(), }; let sig_share = spentbook_node.key_manager.sign(&content.hash()); @@ -819,7 +825,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, Token::from_nano(amount)); + assert_eq!(dbc_amount, amount); assert_eq!(extra_output_amount.coerce::(), 0); } Err(Error::SpentProofInputLenMismatch { current, expected }) => { @@ -840,8 +846,8 @@ pub(crate) mod tests { Err(Error::Transaction(crate::transaction::Error::TransactionMustHaveAnInput)) => { assert_eq!(n_inputs.coerce::(), 0); } - Err(Error::AmountCommitmentsDoNotMatch) => { - assert_ne!(Token::from_nano(amount), dbc_amount); + Err(Error::BlindedAmountsDoNotMatch) => { + assert_ne!(amount, dbc_amount); assert_ne!(extra_output_amount, TinyInt(0)); } Err(Error::InvalidSpentProofSignature(_) | Error::FailedKnownKeyCheck(_)) => { @@ -897,7 +903,7 @@ pub(crate) mod tests { owner: Owner, rng: &mut (impl RngCore + CryptoRng), ) -> Result<(mock::SpentBookNode, Dbc, Dbc, Dbc)> { - let (mut spentbook_node, genesis_dbc, _genesis_material, _amount_secrets) = + let (mut spentbook_node, genesis_dbc, _genesis_material, _revealed_amount) = mock::GenesisBuilder::init_genesis_single(rng)?; let output_amounts = vec![ @@ -908,7 +914,7 @@ pub(crate) mod tests { let mut dbc_builder = crate::TransactionBuilder::default() .add_input_by_secrets( genesis_dbc.owner_once_bearer()?.secret_key()?, - genesis_dbc.amount_secrets_bearer()?, + genesis_dbc.revealed_amount_bearer()?, ) .add_outputs_by_amount( output_amounts diff --git a/src/dbc_content.rs b/src/dbc_content.rs index 45d8768..3d9796f 100644 --- a/src/dbc_content.rs +++ b/src/dbc_content.rs @@ -12,7 +12,7 @@ use tiny_keccak::{Hasher, Sha3}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use crate::{AmountSecrets, DerivationIndex, Owner}; +use crate::{DerivationIndex, Owner, RevealedAmount}; use crate::{Error, Hash, Result}; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -35,50 +35,50 @@ pub struct DbcContent { /// derivation index, which is stored (encrypted) in owner_derivation_cipher. pub owner_base: Owner, - /// This indicates which index to use when deriving the "real" owner key from - /// the owner_base. + /// This indicates which index to use when deriving the publicly visible owner key of the + /// Dbc, from the hidden owner key, i.e. the owner base. /// - /// This index is stored in encrypted form, and is encrypted to owner_base.public_key(). - /// So the true owner is unknown to anyone not in posession of owner_base.secret_key(). + /// This index is stored in encrypted form, and is encrypted to `owner_base.public_key()`. + /// So the true owner is unknown to anyone not in posession of `owner_base.secret_key()`. pub owner_derivation_cipher: Ciphertext, - /// This is the AmountSecrets (aka RevealedCommitment) encypted to the derived public key, + /// This is the RevealedAmount encypted to the derived public key, /// which can be obtained via: /// self.owner_base.derive( /// self.owner_base.secret_key().decrypt(self.owner_derivation.cipher() /// ).public_key() - pub amount_secrets_cipher: Ciphertext, + pub revealed_amount_cipher: Ciphertext, } /// Represents the content of a DBC. impl From<(Owner, Ciphertext, Ciphertext)> for DbcContent { // Create a new DbcContent for signing. fn from(params: (Owner, Ciphertext, Ciphertext)) -> Self { - let (owner_base, owner_derivation_cipher, amount_secrets_cipher) = params; + let (owner_base, owner_derivation_cipher, revealed_amount_cipher) = params; Self { owner_base, owner_derivation_cipher, - amount_secrets_cipher, + revealed_amount_cipher, } } } /// Represents the content of a DBC. -impl From<(Owner, DerivationIndex, AmountSecrets)> for DbcContent { +impl From<(Owner, DerivationIndex, RevealedAmount)> for DbcContent { // Create a new DbcContent for signing. - fn from(params: (Owner, DerivationIndex, AmountSecrets)) -> Self { - let (owner_base, derivation_index, amount_secrets) = params; + fn from(params: (Owner, DerivationIndex, RevealedAmount)) -> Self { + let (owner_base, derivation_index, revealed_amount) = params; let owner_derivation_cipher = owner_base.public_key().encrypt(derivation_index); - let amount_secrets_cipher = owner_base + let revealed_amount_cipher = owner_base .derive(&derivation_index) .public_key() - .encrypt(amount_secrets.to_bytes()); + .encrypt(revealed_amount.to_bytes()); Self { owner_base, owner_derivation_cipher, - amount_secrets_cipher, + revealed_amount_cipher, } } } @@ -101,7 +101,7 @@ impl DbcContent { bytes.extend(&self.owner_base.to_bytes()); bytes.extend(&self.owner_derivation_cipher.to_bytes()); - bytes.extend(&self.amount_secrets_cipher.to_bytes()); + bytes.extend(&self.revealed_amount_cipher.to_bytes()); bytes } diff --git a/src/error.rs b/src/error.rs index 534c3be..fb4ce5e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,7 @@ // 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 thiserror::Error; use crate::transaction; @@ -57,11 +58,13 @@ pub enum Error { )] SpentProofInputLenMismatch { current: usize, expected: usize }, - #[error("Missing commitment for public key: {0:?}")] - MissingCommitmentForPubkey(PublicKey), + #[error( + "Missing amount for public key: {0:?}. There must be exactly one amount per public key." + )] + MissingAmountForPubkey(PublicKey), - #[error("Multiple commitments found for public key: {0:?}")] - MultipleCommitmentsForPubkey(PublicKey), + #[error("Multiple amounts found for public key: {0:?}. There must be exactly one amount per public key.")] + MultipleAmountsForPubkey(PublicKey), #[error("A SpentProof PublicKey does not match an MlsagSignature PublicKey")] SpentProofInputPublicKeyMismatch, @@ -75,11 +78,11 @@ pub enum Error { #[error("Decryption failed")] DecryptionBySecretKeyFailed, - #[error("Invalid AmountSecret bytes")] - AmountSecretsBytesInvalid, + #[error("Invalid RevealedAmount bytes")] + InvalidRevealedAmountBytes, - #[error("Amount Commitments do not match")] - AmountCommitmentsDoNotMatch, + #[error("Blinded amounts do not match")] + BlindedAmountsDoNotMatch, #[error("Secret key unavailable")] SecretKeyUnavailable, diff --git a/src/lib.rs b/src/lib.rs index 467c958..7d664d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,6 @@ use std::fmt; #[cfg(feature = "serde")] use std::str::FromStr; -mod amount_secrets; mod blst; mod builder; mod dbc; @@ -34,8 +33,7 @@ pub use blsttc::{Ciphertext, PublicKey, PublicKeySet, Signature, SignatureShare} pub use bulletproofs::PedersenGens; pub use crate::{ - amount_secrets::AmountSecrets, - blst::{BlindingFactor, Commitment}, + blst::{BlindedAmount, BlindingFactor}, builder::{DbcBuilder, OutputOwnerMap, TransactionBuilder}, dbc::Dbc, dbc_content::DbcContent, @@ -47,10 +45,10 @@ pub use crate::{ }, token::Token, transaction::{ - Amount, DbcTransaction, Input, Output, OutputProof, RevealedCommitment, RevealedInput, + Amount, DbcTransaction, Input, Output, OutputProof, RevealedAmount, RevealedInput, RevealedTransaction, }, - verification::{get_public_commitments_from_transaction, TransactionVerifier}, + verification::{get_blinded_amounts_from_transaction, TransactionVerifier}, }; #[cfg(feature = "serde")] diff --git a/src/mint.rs b/src/mint.rs index 9a1fef3..698893e 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -10,7 +10,7 @@ mod tests { use crate::{ tests::{TinyInt, TinyVec}, - Hash, + Hash, RevealedAmount, }; use blsttc::{PublicKey, SecretKey, SecretKeySet}; use quickcheck_macros::quickcheck; @@ -18,15 +18,15 @@ mod tests { use std::iter::FromIterator; use crate::{ - mock, AmountSecrets, Dbc, DbcContent, Error, IndexedSignatureShare, Owner, OwnerOnce, - Result, SpentProofContent, SpentProofShare, Token, TransactionBuilder, + mock, Dbc, DbcContent, Error, IndexedSignatureShare, Owner, OwnerOnce, Result, + SpentProofContent, SpentProofShare, Token, TransactionBuilder, }; #[test] fn issue_genesis() -> Result<(), Error> { let mut rng = crate::rng::from_seed([0u8; 32]); - let (spentbook_node, genesis_dbc, genesis, _amount_secrets) = + let (spentbook_node, genesis_dbc, genesis, _revealed_amount) = mock::GenesisBuilder::init_genesis_single(&mut rng)?; let verified = genesis_dbc.verify( @@ -50,7 +50,7 @@ mod tests { let n_outputs = output_amounts.len(); let output_amount: u64 = output_amounts.iter().sum(); - let (mut spentbook_node, genesis_dbc, _genesis, _amount_secrets) = + let (mut spentbook_node, genesis_dbc, _genesis, _revealed_amount) = mock::GenesisBuilder::init_genesis_single(&mut rng)?; let owners: Vec = (0..output_amounts.len()) @@ -70,14 +70,12 @@ mod tests { // We make this a closure to keep the spentbook loop readable. let check_error = |error: Error| -> Result<()> { match error { - Error::Transaction( - crate::transaction::Error::InputPseudoCommitmentsDoNotSumToOutputCommitments, - ) => { + Error::Transaction(crate::transaction::Error::InconsistentDbcTransaction) => { // Verify that no outputs were present and we got correct verification error. assert_eq!(n_outputs, 0); Ok(()) } - Error::Transaction(crate::transaction::Error::InvalidCommitment) => { + Error::Transaction(crate::transaction::Error::InvalidInputBlindedAmount) => { // Verify that no outputs were present and we got correct verification error. assert_eq!(n_outputs, 0); Ok(()) @@ -98,11 +96,9 @@ mod tests { } let output_dbcs = dbc_builder.build(&spentbook_node.key_manager)?; - for (dbc, owner_once, amount_secrets) in output_dbcs.iter() { - let dbc_amount = amount_secrets.amount(); - assert!(output_amounts - .iter() - .any(|a| Token::from_nano(*a) == dbc_amount)); + for (dbc, owner_once, revealed_amount) in output_dbcs.iter() { + let dbc_amount = revealed_amount.value(); + assert!(output_amounts.iter().any(|amount| *amount == dbc_amount)); assert!(dbc .verify( &owner_once.owner_base().secret_key().unwrap(), @@ -115,12 +111,11 @@ mod tests { { let mut sum: u64 = 0; for (dbc, owner_once, _) in output_dbcs.iter() { - // note: we could just use amount_secrets provided by DbcBuilder::build() + // note: we could just use revealed amount 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() + .revealed_amount(&owner_once.owner_base().secret_key()?)? + .value() } sum }, @@ -162,7 +157,7 @@ mod tests { .map(TinyInt::coerce::), ); - let (mut spentbook_node, genesis_dbc, _genesis, _amount_secrets) = + let (mut spentbook_node, genesis_dbc, _genesis, _revealed_amount) = mock::GenesisBuilder::init_genesis_single(&mut rng)?; let mut dbc_builder = TransactionBuilder::default() @@ -177,9 +172,7 @@ mod tests { // note: we make this a closure to keep the spentbook loop readable. let check_tx_error = |error: Error| -> Result<()> { match error { - Error::Transaction( - crate::transaction::Error::InputPseudoCommitmentsDoNotSumToOutputCommitments, - ) => { + Error::Transaction(crate::transaction::Error::InconsistentDbcTransaction) => { // Verify that no inputs were present and we got correct verification error. assert!(input_amounts.is_empty()); Ok(()) @@ -208,7 +201,7 @@ mod tests { // The outputs become inputs for next tx. let inputs_dbcs: Vec<(Dbc, SecretKey)> = output_dbcs .into_iter() - .map(|(dbc, owner_once, _amount_secrets)| { + .map(|(dbc, owner_once, _revealed_amount)| { (dbc, owner_once.owner_base().secret_key().unwrap()) }) .collect(); @@ -247,14 +240,12 @@ mod tests { Error::SpentProofInputPublicKeyMismatch => { assert!(!invalid_spent_proofs.is_empty()); } - Error::Transaction( - crate::transaction::Error::InputPseudoCommitmentsDoNotSumToOutputCommitments, - ) => { + Error::Transaction(crate::transaction::Error::InconsistentDbcTransaction) => { if mock::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 - // to match against the input commitment, and also no way to + // The error occurs because there is no output + // to match against the input amount, and also no way to // know that the input amount is zero. assert!(output_amounts.is_empty()); assert_eq!(input_amounts.iter().sum::(), 0); @@ -300,7 +291,7 @@ mod tests { public_key: *spent_proof_share.public_key(), transaction_hash: spent_proof_share.transaction_hash(), reason: Hash::default(), - public_commitment: *spent_proof_share.public_commitment(), + blinded_amount: *spent_proof_share.blinded_amount(), }, spentbook_pks: spent_proof_share.spentbook_pks, spentbook_sig_share: IndexedSignatureShare::new( @@ -338,7 +329,7 @@ mod tests { BTreeSet::from_iter(output_amounts) ); - for (dbc, owner_once, _amount_secrets) in output_dbcs.iter() { + for (dbc, owner_once, _revealed_amount) in output_dbcs.iter() { let dbc_confirm_result = dbc.verify( &owner_once.owner_base().secret_key()?, &spentbook_node.key_manager, @@ -376,7 +367,7 @@ mod tests { fn test_inputs_are_verified() -> Result<(), Error> { let mut rng = crate::rng::from_seed([0u8; 32]); - let (spentbook_node, _genesis_dbc, _genesis, _amount_secrets) = + let (spentbook_node, _genesis_dbc, _genesis, _revealed_amount) = mock::GenesisBuilder::init_genesis_single(&mut rng)?; let output1_owner = @@ -386,14 +377,14 @@ mod tests { .add_output_by_amount(Token::from_nano(100), output1_owner.clone()) .build(&mut rng)?; - let amount_secrets = AmountSecrets::from(dbc_builder.revealed_commitments[0]); + let revealed_amount = dbc_builder.revealed_amounts[0]; let secret_key = output1_owner.as_owner().secret_key()?; let output2_owner = OwnerOnce::from_owner_base(Owner::from_random_secret_key(&mut rng), &mut rng); let fraud_dbc_builder = TransactionBuilder::default() - .add_input_by_secrets(secret_key, amount_secrets) + .add_input_by_secrets(secret_key, revealed_amount) .add_output_by_amount(Token::from_nano(100), output2_owner) .build(&mut rng)?; @@ -405,14 +396,14 @@ mod tests { } /// This tests (and demonstrates) how the system handles a mis-match between the - /// committed amount and amount encrypted in AmountSecrets. + /// blinded amount and the encrypted revealed amount. /// /// Normally these should be the same, however a malicious user or buggy /// implementation could produce different values. The spentbook never sees the - /// AmountSecrets and thus cannot detect or prevent this this situation. + /// RevealedAmount and thus cannot detect or prevent this this situation. /// /// A correct spentbook implementation must verify the transaction before - /// writing, including checking that commitments match. So the spentbook + /// writing, including checking that (blinded) amounts are equal. So the spentbook /// will reject a tx with an output using an invalid amount, thereby preventing /// the input from becoming burned (unspendable). /// @@ -423,22 +414,22 @@ mod tests { /// /// 1. produce a standard genesis DBC (a) with value 1000 /// 2. reissue genesis DBC (a) to Dbc (b) with value 1000. - /// 3. modify b's amount secrets.amount to 2000, thereby creating b_fudged + /// 3. modify b's revealed_amount.value to 2000, thereby creating b_fudged /// (which a bad actor could pass to innocent recipient). /// 4. Check if the amounts match, using the provided API. /// assert that APIs report they do not match. /// 5. create a tx with (b_fudged) as input, and Dbc (c) with amount 2000 as output. /// 6. Attempt to write this tx to the spentbook. - /// This will fail because the input and output commitments do not match. + /// This will fail because the input and output amounts are not equal. /// 7. Force an invalid write to the spentbook /// 8. Attempt to write to spentbook again using the correct amount (1000). /// This will fail because b was already marked as spent in the spentbook. /// This demonstrates how an input can become burned if spentbook does /// not verify tx. - /// 9. Re-write spentbook log correctly using the correct amount that was - /// committed to. Verify that the write succeeds. + /// 9. Re-write spentbook log correctly using the correct amount. + /// Verify that the write succeeds. #[test] - fn test_mismatched_amount_and_commitment() -> Result<(), Error> { + fn test_mismatched_amount_and_blinded_amount() -> Result<(), Error> { // ---------- // 1. produce a standard genesis DBC (a) with value 1000 // ---------- @@ -479,16 +470,16 @@ mod tests { let (b_dbc, ..) = &output_dbcs[0]; // ---------- - // 3. modify b's amount secrets.amount to AMOUNT/2, thereby creating b_fudged + // 3. modify b's revealed_amount.value 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(( - 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 + // twice the amount. + let starting_revealed_amount = starting_dbc.revealed_amount_bearer()?; + let fudged_revealed_amount = RevealedAmount::from(( + starting_revealed_amount.value() * 2, // Claim we are paying twice the amount + starting_revealed_amount.blinding_factor(), // Use the real blinding factor )); let (true_output_dbc, ..) = output_dbcs[0].clone(); @@ -498,17 +489,17 @@ mod tests { fudged_output_dbc.content = DbcContent::from(( c.owner_base.clone(), output_owner.derivation_index, - fudged_amount_secrets, + fudged_revealed_amount, )); - // obtain amount secrets (true and fudged) - let true_secrets = - true_output_dbc.amount_secrets(&output_owner.owner_base().secret_key()?)?; - let fudged_secrets = - fudged_output_dbc.amount_secrets(&output_owner.owner_base().secret_key()?)?; + // obtain revealed amount (true and fudged) + let true_amount = + true_output_dbc.revealed_amount(&output_owner.owner_base().secret_key()?)?; + let fudged_amount = + fudged_output_dbc.revealed_amount(&output_owner.owner_base().secret_key()?)?; - // confirm the secret amount is 2000. - assert_eq!(fudged_secrets.amount(), Token::from_nano(output_amount * 2)); + // confirm the amount is 2000. + assert_eq!(fudged_amount.value(), output_amount * 2); // ---------- // 4. Check if the amounts match, using the provided API. @@ -521,15 +512,15 @@ mod tests { &output_owner.owner_base().secret_key()?, &spentbook.key_manager ), - Err(Error::AmountCommitmentsDoNotMatch) + Err(Error::BlindedAmountsDoNotMatch) )); - // confirm that the sum of output secrets does not match the committed amount. + // confirm that the sum of output revealed does not match the blinded amount. assert_ne!( fudged_output_dbc - .amount_secrets(&output_owner.owner_base().secret_key()?)? - .amount(), - Token::from_nano(output_amount) + .revealed_amount(&output_owner.owner_base().secret_key()?)? + .value(), + output_amount ); // ---------- @@ -544,18 +535,23 @@ mod tests { &fudged_output_dbc, &fudged_output_dbc.owner_base().secret_key()?, )? - .add_output_by_amount(fudged_secrets.amount(), output_owner.clone()) + .add_output_by_amount( + Token::from_nano(fudged_amount.value()), + output_owner.clone(), + ) .build(&mut rng)?; // ---------- // 6. Attempt to write this tx to the spentbook. - // This will fail because the input and output commitments do not match. + // This will fail because the input and output amounts are not equal. // ---------- for (public_key, tx) in dbc_builder_fudged.inputs() { match spentbook.log_spent(public_key, tx, Hash::default()) { - Err(Error::Transaction(crate::transaction::Error::InvalidCommitment)) => {} - _ => panic!("Expecting Transaction Error::InvalidCommitment"), + Err(Error::Transaction(crate::transaction::Error::InvalidInputBlindedAmount)) => {} + _ => panic!( + "Expecting `Error::Transaction(transaction::Error::InvalidInputBlindedAmount)`" + ), } } @@ -581,8 +577,10 @@ mod tests { let result_fudged = dbc_builder_fudged.build(&spentbook.key_manager); match result_fudged { - Err(Error::Transaction(crate::transaction::Error::InvalidCommitment)) => {} - _ => panic!("Expecting Transaction Error::InvalidCommitment"), + Err(Error::Transaction(crate::transaction::Error::InvalidInputBlindedAmount)) => {} + _ => panic!( + "Expecting `Error::Transaction(transaction::Error::InvalidInputBlindedAmount)`" + ), } // ---------- @@ -603,9 +601,9 @@ mod tests { let mut dbc_builder_true = TransactionBuilder::default() .add_input_by_secrets( fudged_output_dbc.owner_once_bearer()?.secret_key()?, - true_secrets.clone(), + true_amount, ) - .add_output_by_amount(true_secrets.amount(), output_owner) + .add_output_by_amount(Token::from_nano(true_amount.value()), output_owner) .build(&mut rng)?; let dbc_builder_bad_proof = dbc_builder_true.clone(); @@ -616,13 +614,13 @@ mod tests { // The builder should return an error because the spentproof does not match the tx. match result { Err(Error::Mock(mock::Error::PublicKeyAlreadySpent)) => {} - _ => panic!("Expected Error::Mock::Error::PublicKeyAlreadySpent"), + _ => panic!("Expected `Error::Mock(mock::Error::PublicKeyAlreadySpent)`"), } } // ---------- // 9. Re-write spentbook log correctly and attempt to spend using the - // correct amount that was committed to. + // correct amount. // Verify that this spend succeeds. // ---------- diff --git a/src/mock/genesis_builder.rs b/src/mock/genesis_builder.rs index b2e525d..456a2bb 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}, - AmountSecrets, Dbc, Hash, Result, TransactionBuilder, + Dbc, Hash, Result, RevealedAmount, TransactionBuilder, }; use blsttc::SecretKeySet; @@ -76,7 +76,7 @@ impl GenesisBuilder { Vec, Dbc, GenesisMaterial, - AmountSecrets, + RevealedAmount, )> { let genesis_material = GenesisMaterial::default(); let mut dbc_builder = TransactionBuilder::default() @@ -102,7 +102,7 @@ impl GenesisBuilder { // have the same public key. (in the same section) let spentbook_node_arbitrary = &self.spentbook_nodes[0]; - let (genesis_dbc, _owner_once, amount_secrets) = dbc_builder + let (genesis_dbc, _owner_once, revealed_amount) = dbc_builder .build(&spentbook_node_arbitrary.key_manager)? .into_iter() .next() @@ -111,7 +111,7 @@ impl GenesisBuilder { self.spentbook_nodes, genesis_dbc, genesis_material, - amount_secrets, + revealed_amount, )) } @@ -125,21 +125,21 @@ impl GenesisBuilder { Vec, Dbc, GenesisMaterial, - AmountSecrets, + RevealedAmount, )> { Self::default() .gen_spentbook_nodes(num_spentbook_nodes, rng)? .build(rng) } - /// builds and returns a single spentbook, single genesis_dbc_shares, + /// Builds and returns a single spentbook, single genesis_dbc_shares, /// and genesis dbc. - /// the spentbook node uses a shared randomly generated SecretKeySet + /// The spentbook node uses a shared randomly generated SecretKeySet. #[allow(clippy::type_complexity)] pub fn init_genesis_single( rng: &mut (impl RngCore + CryptoRng), - ) -> Result<(mock::SpentBookNode, Dbc, GenesisMaterial, AmountSecrets)> { - let (spentbook_nodes, genesis_dbc, genesis_material, amount_secrets) = + ) -> Result<(mock::SpentBookNode, Dbc, GenesisMaterial, RevealedAmount)> { + let (spentbook_nodes, genesis_dbc, genesis_material, revealed_amount) = Self::default().gen_spentbook_nodes(1, rng)?.build(rng)?; // Note: these unwraps are safe because the above call returned Ok. @@ -151,7 +151,7 @@ impl GenesisBuilder { spentbook_nodes.into_iter().next().unwrap(), genesis_dbc, genesis_material, - amount_secrets, + revealed_amount, )) } } diff --git a/src/mock/genesis_material.rs b/src/mock/genesis_material.rs index 9e7530d..2e31883 100644 --- a/src/mock/genesis_material.rs +++ b/src/mock/genesis_material.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 crate::transaction::{Amount, Output, RevealedCommitment, RevealedInput, RevealedTransaction}; +use crate::transaction::{Amount, Output, RevealedAmount, RevealedInput, RevealedTransaction}; use crate::{Owner, OwnerOnce, PublicKey}; use blsttc::IntoFr; @@ -50,9 +50,9 @@ impl Default for GenesisMaterial { // build our TrueInput let revealed_input = RevealedInput::new( input_sk, - RevealedCommitment { + RevealedAmount { value: Self::GENESIS_AMOUNT, - blinding: 42u32.into(), // just a random number + blinding_factor: 42u32.into(), // just a random number }, ); let input_public_key: PublicKey = revealed_input.public_key(); diff --git a/src/mock/spentbook.rs b/src/mock/spentbook.rs index 4ce8b6a..5655d1d 100644 --- a/src/mock/spentbook.rs +++ b/src/mock/spentbook.rs @@ -12,7 +12,7 @@ use bulletproofs::PedersenGens; use std::collections::{BTreeMap, HashMap}; use super::GenesisMaterial; -use crate::{mock, Commitment, Error, Hash, Result, SpentProofContent, SpentProofShare}; +use crate::{mock, BlindedAmount, Error, Hash, Result, SpentProofContent, SpentProofShare}; /// This is a mock SpentBook used for our test cases. A proper implementation /// will be distributed, persistent, and auditable. @@ -44,22 +44,22 @@ pub struct SpentBookNode { pub public_keys: BTreeMap, pub outputs: BTreeMap, - pub genesis: (PublicKey, Commitment), // genesis input (PublicKey, public_commitment) + pub genesis: (PublicKey, BlindedAmount), // genesis input (PublicKey, BlindedAmount) } impl From for SpentBookNode { fn from(key_manager: mock::KeyManager) -> Self { let genesis_material = GenesisMaterial::default(); - let public_commitment = genesis_material.revealed_tx.inputs[0] - .revealed_commitment() - .commit(&PedersenGens::default()); + let blinded_amount = genesis_material.revealed_tx.inputs[0] + .revealed_amount() + .blinded_amount(&PedersenGens::default()); Self { key_manager, transactions: Default::default(), public_keys: Default::default(), outputs: Default::default(), - genesis: (genesis_material.input_public_key, public_commitment), + genesis: (genesis_material.input_public_key, blinded_amount), } } } @@ -115,12 +115,12 @@ impl SpentBookNode { // If this is the very first tx logged and genesis public_key was not // provided, then it becomes the genesis tx. - let (genesis_public_key, genesis_public_commitment) = &self.genesis; + let (genesis_public_key, genesis_blinded_amount) = &self.genesis; - // public_commitments are not available in spentbook for genesis transaction. - let public_commitments_info: Vec<(PublicKey, Commitment)> = + // Input amounts are not available in spentbook for genesis transaction. + let tx_keys_and_blinded_amounts: Vec<(PublicKey, BlindedAmount)> = if public_key == *genesis_public_key { - vec![(public_key, *genesis_public_commitment)] + vec![(public_key, *genesis_blinded_amount)] } else { tx.inputs .iter() @@ -129,34 +129,33 @@ impl SpentBookNode { let pk = input.public_key(); let output_proof = self.outputs.get(&pk); match output_proof { - Some(p) => Ok((input.public_key, p.commitment())), - None => Err(Error::MissingCommitmentForPubkey(pk)), + Some(p) => Ok((input.public_key, p.blinded_amount())), + None => Err(Error::MissingAmountForPubkey(pk)), } }) .collect::>()? }; - // Grab all commitments, grouped by input PublicKey + // Grab all blinded amounts, grouped by input PublicKey // Needed for Tx verification. - let tx_public_commitments: Vec = public_commitments_info + let tx_blinded_amounts: Vec = tx_keys_and_blinded_amounts .clone() .into_iter() .map(|(_, c)| c) .collect(); - // Grab the commitment specific to the input PublicKey + // Grab the blinded amount specific to the input PublicKey // Needed for SpentProofShare - let public_commitments: Commitment = public_commitments_info + let blinded_amount: BlindedAmount = tx_keys_and_blinded_amounts .into_iter() .find(|(k, _)| k == &public_key) - .map_or( - Err(Error::MissingCommitmentForPubkey(public_key)), - |(_, c)| Ok(c), - )?; + .map_or(Err(Error::MissingAmountForPubkey(public_key)), |(_, c)| { + Ok(c) + })?; if verify_tx { - // do not permit invalid tx to be logged. - tx.verify(&tx_public_commitments)?; + // Do not permit invalid tx to be logged. + tx.verify(&tx_blinded_amounts)?; } // Add public_key:tx_hash to public_key index. @@ -179,7 +178,7 @@ impl SpentBookNode { public_key, transaction_hash: tx_hash, reason, - public_commitment: public_commitments, + blinded_amount, }; let spentbook_pks = self.key_manager.public_key_set(); diff --git a/src/spent_proof.rs b/src/spent_proof.rs index 2b40f1d..7faaf98 100644 --- a/src/spent_proof.rs +++ b/src/spent_proof.rs @@ -6,7 +6,9 @@ // 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::{Commitment, Error, Hash, PublicKey, PublicKeySet, Result, Signature, SignatureShare}; +use crate::{ + BlindedAmount, Error, Hash, PublicKey, PublicKeySet, Result, Signature, SignatureShare, +}; use std::{cmp::Ordering, collections::HashSet}; @@ -21,16 +23,13 @@ use serde::{Deserialize, Serialize}; pub struct SpentProofContent { /// PublicKey of input Dbc that this SpentProof is proving to be spent. pub public_key: PublicKey, - - /// Hash of transaction that input Dbc is being spent in. + /// Hash of transaction that the input Dbc is being spent in. pub transaction_hash: Hash, - - /// Reason why this DBC was spent + /// Reason why this Dbc was spent. pub reason: Hash, - #[debug(skip)] - /// public commitment for the transaction - pub public_commitment: Commitment, + /// The amount of the input Dbc. + pub blinded_amount: BlindedAmount, } impl SpentProofContent { @@ -41,7 +40,7 @@ impl SpentProofContent { bytes.extend(self.public_key.to_bytes()); bytes.extend(self.transaction_hash.as_ref()); bytes.extend(self.reason.as_ref()); - bytes.extend(self.public_commitment.compress().to_bytes()); + bytes.extend(self.blinded_amount.compress().to_bytes()); bytes } @@ -121,37 +120,37 @@ impl std::hash::Hash for SpentProofShare { } impl SpentProofShare { - /// get PublicKey of input Dbc + /// Get the public key of input Dbc that this SpentProof is proving to be spent. pub fn public_key(&self) -> &PublicKey { &self.content.public_key } - /// get transaction hash + /// Get the hash of the transaction that the input Dbc is spent in. pub fn transaction_hash(&self) -> Hash { self.content.transaction_hash } - /// get reason + /// Get the specified reason that the input Dbc was spent. pub fn reason(&self) -> Hash { self.content.reason } - /// get public commitments - pub fn public_commitment(&self) -> &Commitment { - &self.content.public_commitment + /// Get the (blinded) amount of the input Dbc. + pub fn blinded_amount(&self) -> &BlindedAmount { + &self.content.blinded_amount } - /// get spentbook's signature share + /// Get the spentbook's signature share. pub fn spentbook_sig_share(&self) -> &IndexedSignatureShare { &self.spentbook_sig_share } - /// get spentbook's PublicKeySet + /// Get the spentbook's PublicKeySet. pub fn spentbook_pks(&self) -> &PublicKeySet { &self.spentbook_pks } - /// represent this SpentProofShare as bytes + /// Represent this SpentProofShare as bytes. pub fn to_bytes(&self) -> Vec { let mut bytes = self.content.to_bytes(); @@ -173,14 +172,11 @@ pub trait SpentProofKeyVerifier { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialOrd, Ord)] pub struct SpentProof { - /// data to be signed + /// The details of the spend, which together with signature over it, constitutes the proof. pub content: SpentProofContent, - - /// The Spentbook who notarized that this DBC was spent. + /// The Spentbook who notarized that this Dbc was spent. pub spentbook_pub_key: PublicKey, - - /// The Spentbook's signature notarizing the DBC was spent. - /// signing over SpentProofContent. (PublicKey, DbcTransaction, and public_commitment). + /// The Spentbook's signature over (the hash of) SpentProofContent, notarizing that the Dbc was spent. pub spentbook_sig: Signature, } @@ -209,13 +205,13 @@ impl SpentProof { .map(IndexedSignatureShare::threshold_crypto), )?; - let public_commitment = *any_share.public_commitment(); + let blinded_amount = *any_share.blinded_amount(); Ok(SpentProof { content: SpentProofContent { public_key, transaction_hash, - public_commitment, + blinded_amount, reason, }, spentbook_pub_key, @@ -223,27 +219,27 @@ impl SpentProof { }) } - /// get PublicKey of input Dbc + /// Get public key of input Dbc. pub fn public_key(&self) -> &PublicKey { &self.content.public_key } - /// get transaction hash + /// Get transaction hash. pub fn transaction_hash(&self) -> Hash { self.content.transaction_hash } - /// get public commitments - pub fn public_commitment(&self) -> &Commitment { - &self.content.public_commitment + /// Get blinded amount. + pub fn blinded_amount(&self) -> &BlindedAmount { + &self.content.blinded_amount } - /// get reason + /// Get reason. pub fn reason(&self) -> Hash { self.content.reason } - /// represent this SpentProof as bytes + /// Represent this SpentProof as bytes. pub fn to_bytes(&self) -> Vec { let mut bytes: Vec = Default::default(); @@ -254,19 +250,19 @@ impl SpentProof { bytes } - /// verify this SpentProof + /// Verify this SpentProof /// - /// checks that the input transaction hash matches the tx_hash that was + /// Checks that the input transaction hash matches the tx_hash that was /// signed by the spentbook and verifies that spentbook signature is /// valid for this SpentProof. /// - /// note that the verifier must already hold (trust) the spentbook's public key. + /// Note that the verifier must already hold (trust) the spentbook's public key. pub fn verify( &self, tx_hash: Hash, proof_key_verifier: &K, ) -> Result<()> { - // verify input tx_hash matches our tx_hash which was signed by spentbook. + // Verify that input tx_hash matches our tx_hash which was signed by spentbook. if tx_hash != self.content.transaction_hash { return Err(Error::InvalidTransactionHash); } @@ -283,7 +279,7 @@ impl SpentProof { } } -// impl manually to avoid clippy complaint about Hash conflict. +// Impl manually to avoid clippy complaint about Hash conflict. impl PartialEq for SpentProof { fn eq(&self, other: &Self) -> bool { self.content == other.content diff --git a/src/transaction/error.rs b/src/transaction/error.rs index e18f566..562ef21 100644 --- a/src/transaction/error.rs +++ b/src/transaction/error.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2022, MaidSafe. +// Copyright (c) 2023, MaidSafe. // All rights reserved. // // This SAFE Network Software is licensed under the BSD-3-Clause license. @@ -8,18 +8,18 @@ use thiserror::Error; #[derive(Clone, Debug, Eq, PartialEq, Error)] pub enum Error { - #[error("Failed to decompress commitment")] - FailedToDecompressCommitment, - #[error("The commitment in the input doesn't match the public commitment")] - InvalidCommitment, - #[error("InputPseudoCommitmentsDoNotSumToOutputCommitments")] - InputPseudoCommitmentsDoNotSumToOutputCommitments, - #[error("The signature is not valid")] + #[error("Failed to decompress blinded amount.")] + FailedToDecompressBlindedAmount, + #[error("The blinded amount in the input doesn't match the known amount.")] + InvalidInputBlindedAmount, + #[error("The input and output amounts of the transaction do not match.")] + InconsistentDbcTransaction, + #[error("The signature is not valid.")] InvalidSignature, - #[error("BulletProofs Error: {0}")] + #[error("BulletProofs Error: {0}.")] BulletProofs(#[from] bulletproofs::ProofError), - #[error("The DBC transaction must have at least one input")] + #[error("The DBC transaction must have at least one input.")] TransactionMustHaveAnInput, - #[error("public key is not unique across all transaction inputs")] + #[error("Public key is not unique across all transaction inputs.")] PublicKeyNotUniqueAcrossInputs, } diff --git a/src/transaction/input.rs b/src/transaction/input.rs index 64ada61..edaaf26 100644 --- a/src/transaction/input.rs +++ b/src/transaction/input.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2022, MaidSafe. +// Copyright (c) 2023, MaidSafe. // All rights reserved. // // This SAFE Network Software is licensed under the BSD-3-Clause license. @@ -10,22 +10,22 @@ use bulletproofs::PedersenGens; #[cfg(feature = "serde")] use serde::{self, Deserialize, Serialize}; -use super::{Error, Result, RevealedCommitment}; -use crate::Commitment; +use super::{Error, Result, RevealedAmount}; +use crate::BlindedAmount; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone)] pub struct RevealedInput { #[cfg_attr(feature = "serde", serde(skip_serializing))] pub secret_key: SecretKey, - pub revealed_commitment: RevealedCommitment, + pub revealed_amount: RevealedAmount, } impl RevealedInput { - pub fn new>(secret_key: S, revealed_commitment: RevealedCommitment) -> Self { + pub fn new>(secret_key: S, revealed_amount: RevealedAmount) -> Self { Self { secret_key: secret_key.into(), - revealed_commitment, + revealed_amount, } } @@ -33,22 +33,22 @@ impl RevealedInput { self.secret_key.public_key() } - pub fn revealed_commitment(&self) -> &RevealedCommitment { - &self.revealed_commitment + pub fn revealed_amount(&self) -> &RevealedAmount { + &self.revealed_amount } - pub fn commitment(&self, pc_gens: &PedersenGens) -> Commitment { - self.revealed_commitment.commit(pc_gens) + pub fn blinded_amount(&self, pc_gens: &PedersenGens) -> BlindedAmount { + self.revealed_amount.blinded_amount(pc_gens) } pub fn sign(&self, msg: &[u8], pc_gens: &PedersenGens) -> Input { let public_key = self.public_key(); - let commitment = self.commitment(pc_gens); + let blinded_amount = self.blinded_amount(pc_gens); let signature = self.secret_key.sign(msg); Input { public_key, - commitment, + blinded_amount, signature, } } @@ -58,7 +58,7 @@ impl RevealedInput { #[derive(Eq, PartialEq, Debug, Clone)] pub struct Input { pub public_key: PublicKey, - pub commitment: Commitment, + pub blinded_amount: BlindedAmount, pub signature: Signature, } @@ -66,7 +66,7 @@ impl Input { pub fn to_bytes(&self) -> Vec { let mut v: Vec = Default::default(); v.extend(self.public_key.to_bytes().as_ref()); - v.extend(self.commitment.compress().as_bytes()); + v.extend(self.blinded_amount.compress().as_bytes()); v.extend(self.signature.to_bytes().as_ref()); v } @@ -75,10 +75,12 @@ impl Input { self.public_key } - pub fn verify(&self, msg: &[u8], public_commitment: Commitment) -> Result<()> { - // check that the public commitments matches the one in the input - if self.commitment != public_commitment { - return Err(Error::InvalidCommitment); + /// Verify if a blinded amount you know of, is the same as the one in the input, + /// and that the bytes passed in, are what the signature of this input was made over, + /// and that the public key of this input was the signer. + pub fn verify(&self, msg: &[u8], blinded_amount: BlindedAmount) -> Result<()> { + if self.blinded_amount != blinded_amount { + return Err(Error::InvalidInputBlindedAmount); } // check the signature diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index 3691c6f..c93d4ee 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2022, MaidSafe. +// Copyright (c) 2023, MaidSafe. // All rights reserved. // // This SAFE Network Software is licensed under the BSD-3-Clause license. @@ -7,54 +7,11 @@ mod error; mod input; mod output; - -use crate::{BlindingFactor, Commitment}; - -use crate::rand::RngCore; -use blsttc::rand::CryptoRng; -use bulletproofs::PedersenGens; +mod revealed_amount; pub(crate) use error::Error; pub use input::{Input, RevealedInput}; pub use output::{Amount, DbcTransaction, Output, OutputProof, RevealedTransaction}; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; +pub use revealed_amount::RevealedAmount; type Result = std::result::Result; - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy)] -pub struct RevealedCommitment { - pub value: u64, - pub blinding: BlindingFactor, -} - -impl RevealedCommitment { - pub fn to_bytes(&self) -> Vec { - let mut v: Vec = Default::default(); - v.extend(self.value.to_le_bytes()); - v.extend(self.blinding.to_bytes()); - v - } - - /// Construct a revealed commitment from a value, generating a blinding randomly - pub fn from_value(value: u64, mut rng: impl RngCore + CryptoRng) -> Self { - Self { - value, - blinding: BlindingFactor::random(&mut rng), - } - } - - pub fn commit(&self, pc_gens: &PedersenGens) -> Commitment { - pc_gens.commit(BlindingFactor::from(self.value), self.blinding) - } - - pub fn value(&self) -> u64 { - self.value - } - - pub fn blinding(&self) -> BlindingFactor { - self.blinding - } -} diff --git a/src/transaction/output.rs b/src/transaction/output.rs index 13d17f4..dd4e199 100644 --- a/src/transaction/output.rs +++ b/src/transaction/output.rs @@ -15,14 +15,14 @@ use tiny_keccak::{Hasher, Sha3}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use super::{Error, Input, Result, RevealedCommitment, RevealedInput}; +use super::{Error, Input, Result, RevealedAmount, RevealedInput}; pub(super) const RANGE_PROOF_BITS: usize = 64; // note: Range Proof max-bits is 64. allowed are: 8, 16, 32, 64 (only) // This limits our amount field to 64 bits also. pub(super) const RANGE_PROOF_PARTIES: usize = 1; // The maximum number of parties that can produce an aggregated proof pub(super) const MERLIN_TRANSCRIPT_LABEL: &[u8] = b"SN_DBC"; use crate::rand::{CryptoRng, RngCore}; -use crate::Commitment; +use crate::BlindedAmount; /// Represents a Dbc's value. pub type Amount = u64; @@ -50,17 +50,17 @@ impl Output { self.amount } - /// Generate a commitment to the input amount - pub fn random_commitment(&self, rng: impl RngCore + CryptoRng) -> RevealedCommitment { - RevealedCommitment::from_value(self.amount, rng) + /// Generate a revealed amount, with random blinding factor, which will be used for an input in a tx. + pub fn revealed_amount(&self, rng: impl RngCore + CryptoRng) -> RevealedAmount { + RevealedAmount::from_amount(self.amount, rng) } } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone)] -struct RevealedOutputCommitment { +struct RevealedOutputAmount { pub public_key: PublicKey, - pub revealed_commitment: RevealedCommitment, + pub revealed_amount: RevealedAmount, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -74,27 +74,27 @@ impl RevealedTransaction { pub fn sign( &self, mut rng: impl RngCore + CryptoRng, - ) -> Result<(DbcTransaction, Vec)> { + ) -> Result<(DbcTransaction, Vec)> { // We need to gather a bunch of things for our message to sign. // All public keys in all inputs - // All input commitments + // All input blinded amounts // All output public keys. - // All output commitments + // All output blinded amounts // All output range proofs // // notes: - // 1. output commitments, range_proofs, and public_keys are bundled + // 1. output blinded amounts, range_proofs, and public_keys are bundled // together in OutputProofs - let revealed_input_commitments = self.revealed_input_commitments(); - let input_commitments = self.input_commitments(); + let revealed_input_amounts = self.revealed_input_amounts(); + let input_amounts = self.blinded_input_amounts(); - let revealed_output_commitments = - self.revealed_output_commitments(&revealed_input_commitments, &mut rng); - let output_proofs = self.output_range_proofs(&revealed_output_commitments, &mut rng)?; + let revealed_output_amounts = + self.revealed_output_amounts(&revealed_input_amounts, &mut rng); + let output_proofs = self.output_range_proofs(&revealed_output_amounts, &mut rng)?; // Generate message to sign. // note: must match message generated by DbcTransaction::verify() - let msg = gen_message_for_signing(&self.public_keys(), &input_commitments, &output_proofs); + let msg = gen_message_for_signing(&self.public_keys(), &input_amounts, &output_proofs); // We create a signature for each input let signed_inputs: Vec = self @@ -103,9 +103,9 @@ impl RevealedTransaction { .map(|input| input.sign(&msg, &Self::pc_gens())) .collect(); - let revealed_output_commitments = revealed_output_commitments + let revealed_output_amounts = revealed_output_amounts .iter() - .map(|r| r.revealed_commitment) + .map(|r| r.revealed_amount) .collect::>(); Ok(( @@ -113,7 +113,7 @@ impl RevealedTransaction { inputs: signed_inputs, outputs: output_proofs, }, - revealed_output_commitments, + revealed_output_amounts, )) } @@ -129,97 +129,98 @@ impl RevealedTransaction { self.inputs.iter().map(|input| input.public_key()).collect() } - fn revealed_input_commitments(&self) -> Vec { + fn revealed_input_amounts(&self) -> Vec { self.inputs .iter() - .map(|input| *input.revealed_commitment()) + .map(|input| *input.revealed_amount()) .collect() } - fn input_commitments(&self) -> Vec { + fn blinded_input_amounts(&self) -> Vec { self.inputs .iter() - .map(|input| input.commitment(&Self::pc_gens())) + .map(|input| input.blinded_amount(&Self::pc_gens())) .collect() } - fn revealed_output_commitments( + fn revealed_output_amounts( &self, - revealed_input_commitments: &[RevealedCommitment], + revealed_input_amounts: &[RevealedAmount], mut rng: impl RngCore + CryptoRng, - ) -> Vec { - // avoid subtraction underflow in next step. + ) -> Vec { + // Avoid subtraction underflow in next step. if self.outputs.is_empty() { return vec![]; } - let mut revealed_output_commitments: Vec = self + let mut revealed_output_amounts: Vec = self .outputs .iter() - .map(|out| RevealedOutputCommitment { + .map(|out| RevealedOutputAmount { public_key: out.public_key, - revealed_commitment: out.random_commitment(&mut rng), + revealed_amount: out.revealed_amount(&mut rng), }) .take(self.outputs.len() - 1) .collect(); // todo: replace fold() with sum() when supported in blstrs - let input_sum: Scalar = revealed_input_commitments + let input_summed_blinding_factors: Scalar = revealed_input_amounts .iter() - .map(RevealedCommitment::blinding) + .map(RevealedAmount::blinding_factor) .fold(Scalar::zero(), |sum, x| sum + x); // todo: replace fold() with sum() when supported in blstrs - let output_sum: Scalar = revealed_output_commitments + let output_summed_blinding_factors: Scalar = revealed_output_amounts .iter() - .map(|r| r.revealed_commitment.blinding()) + .map(|r| r.revealed_amount.blinding_factor()) .fold(Scalar::zero(), |sum, x| sum + x); - let output_blinding_correction = input_sum - output_sum; + let output_blinding_correction = + input_summed_blinding_factors - output_summed_blinding_factors; if let Some(last_output) = self.outputs.last() { - revealed_output_commitments.push(RevealedOutputCommitment { + revealed_output_amounts.push(RevealedOutputAmount { public_key: last_output.public_key, - revealed_commitment: RevealedCommitment { + revealed_amount: RevealedAmount { value: last_output.amount, - blinding: output_blinding_correction, + blinding_factor: output_blinding_correction, }, }); } else { panic!("Expected at least one output") } - revealed_output_commitments + revealed_output_amounts } fn output_range_proofs( &self, - revealed_output_commitments: &[RevealedOutputCommitment], + revealed_output_amounts: &[RevealedOutputAmount], mut rng: impl RngCore + CryptoRng, ) -> Result> { let mut prover_ts = Transcript::new(MERLIN_TRANSCRIPT_LABEL); let bp_gens = Self::bp_gens(); - revealed_output_commitments + revealed_output_amounts .iter() .map(|c| { - let (range_proof, compressed_commitment) = RangeProof::prove_single_with_rng( + let (range_proof, compressed_blinded_amount) = RangeProof::prove_single_with_rng( &bp_gens, &Self::pc_gens(), &mut prover_ts, - c.revealed_commitment.value, - &c.revealed_commitment.blinding, + c.revealed_amount.value, + &c.revealed_amount.blinding_factor, RANGE_PROOF_BITS, &mut rng, )?; - let commitment = compressed_commitment + let blinded_amount = compressed_blinded_amount .decompress() - .ok_or(Error::FailedToDecompressCommitment)?; + .ok_or(Error::FailedToDecompressBlindedAmount)?; Ok(OutputProof { public_key: c.public_key, range_proof, - commitment, + blinded_amount, }) }) .collect::>>() @@ -230,7 +231,7 @@ impl RevealedTransaction { // which must match. fn gen_message_for_signing( public_keys: &[PublicKey], - input_commitments: &[Commitment], + input_amounts: &[BlindedAmount], output_proofs: &[OutputProof], ) -> Vec { // Generate message to sign. @@ -239,8 +240,8 @@ fn gen_message_for_signing( for pk in public_keys.iter() { msg.extend(pk.to_bytes().as_ref()); } - msg.extend("commitments".as_bytes()); - for r in input_commitments.iter() { + msg.extend("input_amounts".as_bytes()); + for r in input_amounts.iter() { msg.extend(r.compress().as_bytes()); } msg.extend("output_proofs".as_bytes()); @@ -256,7 +257,7 @@ fn gen_message_for_signing( pub struct OutputProof { public_key: PublicKey, range_proof: RangeProof, - commitment: Commitment, + blinded_amount: BlindedAmount, } impl OutputProof { @@ -264,7 +265,7 @@ impl OutputProof { let mut v: Vec = Default::default(); v.extend(self.public_key.to_bytes().as_ref()); v.extend(&self.range_proof.to_bytes()); - v.extend(self.commitment.compress().as_bytes()); + v.extend(self.blinded_amount.compress().as_bytes()); v } @@ -276,8 +277,8 @@ impl OutputProof { &self.range_proof } - pub fn commitment(&self) -> Commitment { - self.commitment + pub fn blinded_amount(&self) -> BlindedAmount { + self.blinded_amount } } @@ -337,18 +338,21 @@ impl DbcTransaction { pub fn gen_message(&self) -> Vec { // All public keys let public_keys: Vec = self.inputs.iter().map(|m| m.public_key).collect(); - - // All input commitments - let input_commitments: Vec = self.inputs.iter().map(|i| i.commitment).collect(); - - gen_message_for_signing(&public_keys, &input_commitments, &self.outputs) + // All input blinded amounts + let input_amounts: Vec = + self.inputs.iter().map(|i| i.blinded_amount).collect(); + gen_message_for_signing(&public_keys, &input_amounts, &self.outputs) } - pub fn verify(&self, public_commitments: &[Commitment]) -> Result<()> { + /// Verify if the blinded amounts of the inputs, are + /// the same as the set of blinded amounts you know of. + /// This also checks that every input has the signature over this very tx, + /// and that each public key of the inputs was the signer. + pub fn verify(&self, blinded_amounts: &[BlindedAmount]) -> Result<()> { // check input sigs let msg = self.gen_message(); - for (input, commit) in self.inputs.iter().zip(public_commitments) { - input.verify(&msg, *commit)? + for (input, blinded_amount) in self.inputs.iter().zip(blinded_amounts) { + input.verify(&msg, *blinded_amount)? } let mut prover_ts = Transcript::new(MERLIN_TRANSCRIPT_LABEL); @@ -360,7 +364,7 @@ impl DbcTransaction { &bp_gens, &RevealedTransaction::pc_gens(), &mut prover_ts, - &output.commitment.compress(), + &output.blinded_amount.compress(), RANGE_PROOF_BITS, )?; } @@ -370,29 +374,29 @@ impl DbcTransaction { return Err(Error::TransactionMustHaveAnInput); } - // Verify that each public_key is unique + // Verify that each public_key is unique. let pk_count = self.inputs.len(); let pk_unique: BTreeSet<_> = self.inputs.iter().map(|input| input.public_key).collect(); if pk_unique.len() != pk_count { return Err(Error::PublicKeyNotUniqueAcrossInputs); } - // check that the inputs equal the outputs (with commitments) + // Check that the input and output blinded amounts are equal. let input_sum: RistrettoPoint = self .inputs .iter() - .map(|i| i.commitment) + .map(|i| i.blinded_amount) .map(RistrettoPoint::from) .sum(); let output_sum: RistrettoPoint = self .outputs .iter() - .map(OutputProof::commitment) + .map(OutputProof::blinded_amount) .map(RistrettoPoint::from) .sum(); if input_sum != output_sum { - Err(Error::InputPseudoCommitmentsDoNotSumToOutputCommitments) + Err(Error::InconsistentDbcTransaction) } else { Ok(()) } @@ -412,16 +416,16 @@ mod tests { #[derive(Default)] struct TestLedger { - commitments: BTreeMap, // Compressed public keys -> Commitments + blinded_amounts: BTreeMap, // Compressed public keys -> BlindedAmounts } impl TestLedger { - fn log(&mut self, public_key: PublicKey, commitment: Commitment) { - self.commitments.insert(public_key, commitment); + fn log(&mut self, public_key: PublicKey, blinded_amount: BlindedAmount) { + self.blinded_amounts.insert(public_key, blinded_amount); } - fn lookup(&self, public_key: PublicKey) -> Option { - self.commitments.get(&public_key).copied() + fn lookup(&self, public_key: PublicKey) -> Option { + self.blinded_amounts.get(&public_key).copied() } } @@ -433,16 +437,16 @@ mod tests { let input_sk_seed: u64 = rand::random(); let true_input = RevealedInput { secret_key: SecretKey::from_mut(&mut input_sk_seed.into_fr()), - revealed_commitment: RevealedCommitment { + revealed_amount: RevealedAmount { value: 3, - blinding: 5u32.into(), + blinding_factor: 5u32.into(), }, }; let mut ledger = TestLedger::default(); ledger.log( true_input.public_key(), - true_input.revealed_commitment.commit(&pc_gens), + true_input.revealed_amount.blinded_amount(&pc_gens), ); ledger.log( SecretKey::random().public_key(), @@ -462,16 +466,16 @@ mod tests { }], }; - let (signed_tx, _revealed_output_commitments) = + let (signed_tx, _revealed_output_amounts) = revealed_tx.sign(rng).expect("Failed to sign transaction"); - let public_commitments = Vec::from_iter( + let blinded_amounts = Vec::from_iter( signed_tx .inputs .iter() .map(|input| ledger.lookup(input.public_key()).unwrap()), ); - assert!(signed_tx.verify(&public_commitments).is_ok()); + assert!(signed_tx.verify(&blinded_amounts).is_ok()); } } diff --git a/src/transaction/revealed_amount.rs b/src/transaction/revealed_amount.rs new file mode 100644 index 0000000..e88bf3b --- /dev/null +++ b/src/transaction/revealed_amount.rs @@ -0,0 +1,182 @@ +// Copyright (c) 2023, MaidSafe. +// All rights reserved. +// +// This SAFE Network Software is licensed under the BSD-3-Clause license. +// Please see the LICENSE file for more details. + +use crate::{Error, Result}; + +use crate::rand::RngCore; +use crate::{Amount, BlindedAmount, BlindingFactor}; + +use blsttc::{ + rand::CryptoRng, Ciphertext, DecryptionShare, IntoFr, PublicKey, PublicKeySet, SecretKey, + SecretKeySet, SecretKeyShare, +}; +use bulletproofs::PedersenGens; +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 BF_SIZE: usize = std::mem::size_of::(); // Blinding factor size: 32 bytes (BlindingFactor) + +/// A RevealedAmount is a plain text value and a +/// blinding factor, which together can create a `BlindedAmount`. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy)] +pub struct RevealedAmount { + pub value: Amount, + pub blinding_factor: BlindingFactor, +} + +impl RevealedAmount { + pub fn to_bytes(&self) -> Vec { + let mut v: Vec = Default::default(); + v.extend(self.value.to_le_bytes()); + v.extend(self.blinding_factor.to_bytes()); + v + } + + /// Construct a RevealedAmount instance from an amount, generating a random blinding factor. + pub fn from_amount(amount: Amount, mut rng: impl RngCore + CryptoRng) -> Self { + Self { + value: amount, + blinding_factor: BlindingFactor::random(&mut rng), + } + } + + pub fn blinded_amount(&self, pc_gens: &PedersenGens) -> BlindedAmount { + pc_gens.commit(BlindingFactor::from(self.value), self.blinding_factor) + } + + pub fn value(&self) -> u64 { + self.value + } + + pub fn blinding_factor(&self) -> BlindingFactor { + self.blinding_factor + } + + /// Encrypt this instance to given public key, producing `Ciphertext`. + pub fn encrypt(&self, public_key: &PublicKey) -> Ciphertext { + public_key.encrypt(self.to_bytes()) + } + + /// build RevealedAmount from fixed size byte array. + pub fn from_bytes(bytes: [u8; AMT_SIZE + BF_SIZE]) -> Self { + let amount = Amount::from_le_bytes({ + let mut b = [0u8; AMT_SIZE]; + b.copy_from_slice(&bytes[0..AMT_SIZE]); + b + }); + let mut b = [0u8; BF_SIZE]; + let blinding_factor = BlindingFactor::from_bytes_mod_order({ + b.copy_from_slice(&bytes[AMT_SIZE..]); + b + }); + + Self { + value: amount, + blinding_factor, + } + } + + /// build RevealedAmount from byte array reference + pub fn from_bytes_ref(bytes: &[u8]) -> Result { + if bytes.len() != AMT_SIZE + BF_SIZE { + return Err(Error::InvalidRevealedAmountBytes); + } + let amount = Amount::from_le_bytes({ + let mut b = [0u8; AMT_SIZE]; + b.copy_from_slice(&bytes[0..AMT_SIZE]); + b + }); + let mut b = [0u8; BF_SIZE]; + let blinding_factor = BlindingFactor::from_bytes_mod_order({ + b.copy_from_slice(&bytes[AMT_SIZE..]); + b + }); + + Ok(Self { + value: amount, + blinding_factor, + }) + } +} + +impl From<(Amount, BlindingFactor)> for RevealedAmount { + /// create RevealedAmount from an amount and a randomly generated blinding factor + fn from(params: (Amount, BlindingFactor)) -> Self { + let (amount, blinding_factor) = params; + + Self { + value: amount, + blinding_factor, + } + } +} + +impl TryFrom<(&SecretKey, &Ciphertext)> for RevealedAmount { + type Error = Error; + + /// Decrypt RevealedAmount ciphertext using a SecretKey + fn try_from(params: (&SecretKey, &Ciphertext)) -> Result { + let (secret_key, ciphertext) = params; + let bytes_vec = secret_key + .decrypt(ciphertext) + .ok_or(Error::DecryptionBySecretKeyFailed)?; + Self::from_bytes_ref(&bytes_vec) + } +} + +impl TryFrom<(&SecretKeySet, &Ciphertext)> for RevealedAmount { + type Error = Error; + + /// Decrypt RevealedAmount ciphertext using a SecretKeySet + fn try_from(params: (&SecretKeySet, &Ciphertext)) -> Result { + let (secret_key_set, ciphertext) = params; + Self::try_from((&secret_key_set.secret_key(), ciphertext)) + } +} + +impl TryFrom<(&PublicKeySet, &BTreeMap, &Ciphertext)> + for RevealedAmount +{ + type Error = Error; + + /// Decrypt RevealedAmount ciphertext using [threshold + 1] SecretKeyShares + fn try_from( + params: (&PublicKeySet, &BTreeMap, &Ciphertext), + ) -> Result { + let (public_key_set, secret_key_shares, ciphertext) = params; + + let mut decryption_shares: BTreeMap = Default::default(); + for (idx, sec_share) in secret_key_shares.iter() { + let share = sec_share.decrypt_share_no_verify(ciphertext); + decryption_shares.insert(*idx, share); + } + Self::try_from((public_key_set, &decryption_shares, ciphertext)) + } +} + +impl TryFrom<(&PublicKeySet, &BTreeMap, &Ciphertext)> + for RevealedAmount +{ + type Error = Error; + + /// Decrypt RevealedAmount using threshold+1 DecryptionShares + /// + /// This fn should be used when keys (SecretKeyShare) are distributed across multiple parties. + /// In which case each party will need to call SecretKeyShare::decrypt_share() or + /// decrypt_share_no_verify() to generate a DecryptionShare and one party will need to + /// obtain/aggregate all the shares together somehow. + fn try_from( + params: (&PublicKeySet, &BTreeMap, &Ciphertext), + ) -> Result { + let (public_key_set, decryption_shares, ciphertext) = params; + let bytes_vec = public_key_set.decrypt(decryption_shares, ciphertext)?; + Self::from_bytes_ref(&bytes_vec) + } +} diff --git a/src/verification.rs b/src/verification.rs index 77c00c7..32813db 100644 --- a/src/verification.rs +++ b/src/verification.rs @@ -7,7 +7,7 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::transaction::DbcTransaction; -use crate::{Commitment, Error, Hash, PublicKey, Result, SpentProof, SpentProofKeyVerifier}; +use crate::{BlindedAmount, Error, Hash, PublicKey, Result, SpentProof, SpentProofKeyVerifier}; use std::collections::BTreeSet; // Here we are putting transaction verification logic that is beyond @@ -73,8 +73,8 @@ impl TransactionVerifier { spent_proof.verify(transaction_hash, verifier)?; } - // We must get the spent_proofs into the same order as inputs - // so that resulting public_commitments will be in the right order. + // We must get the spent proofs into the same order as inputs + // so that resulting blinded amounts will be in the right order. // Note: we could use itertools crate to sort in one loop. let mut spent_proofs_found: Vec<(usize, &SpentProof)> = spent_proofs .iter() @@ -91,28 +91,26 @@ impl TransactionVerifier { let spent_proofs_sorted: Vec<&SpentProof> = spent_proofs_found.into_iter().map(|s| s.1).collect(); - let public_commitments: Vec = spent_proofs_sorted + let blinded_amounts: Vec = spent_proofs_sorted .iter() - .map(|s| *s.public_commitment()) + .map(|s| *s.blinded_amount()) .collect(); - transaction.verify(&public_commitments)?; + transaction.verify(&blinded_amounts)?; Ok(()) } } -/// Get the public commitments for the transaction. -/// -/// They will be assigned to the spent proof share that is generated. -/// -/// In the process of doing so, we verify the correct set of spent proofs and transactions have -/// been provided. -pub fn get_public_commitments_from_transaction( +/// Get the blinded amounts for the transaction. +/// They will be part of the spent proof share that is generated. +/// In the process of doing so, we verify the correct set of spent +/// proofs and transactions have been provided. +pub fn get_blinded_amounts_from_transaction( tx: &DbcTransaction, spent_proofs: &BTreeSet, spent_transactions: &BTreeSet, -) -> Result> { +) -> Result> { // get txs that are referenced by the spent proofs let mut referenced_spent_txs: Vec<&DbcTransaction> = vec![]; for spent_prf in spent_proofs { @@ -124,28 +122,28 @@ pub fn get_public_commitments_from_transaction( } } - // for each input's public key, look up the matching Commitment - // in those referenced Txs - let mut public_commitments_info = Vec::<(PublicKey, Commitment)>::new(); + // For each input's public key, look up the matching + // blinded amount in those referenced Txs. + let mut tx_keys_and_blinded_amounts = Vec::<(PublicKey, BlindedAmount)>::new(); for input in &tx.inputs { let input_pk = input.public_key(); - let matching_commitments: Vec = referenced_spent_txs + let matching_amounts: Vec = referenced_spent_txs .iter() .flat_map(|tx| { tx.outputs .iter() .find(|output| output.public_key() == &input_pk) - .map(|output| output.commitment()) + .map(|output| output.blinded_amount()) }) .collect(); - match matching_commitments[..] { - [] => return Err(Error::MissingCommitmentForPubkey(input_pk)), - [one_commit] => public_commitments_info.push((input_pk, one_commit)), - [_, _, ..] => return Err(Error::MultipleCommitmentsForPubkey(input_pk)), + match matching_amounts[..] { + [] => return Err(Error::MissingAmountForPubkey(input_pk)), + [one_amount] => tx_keys_and_blinded_amounts.push((input_pk, one_amount)), + [_, _, ..] => return Err(Error::MultipleAmountsForPubkey(input_pk)), } } - Ok(public_commitments_info) + Ok(tx_keys_and_blinded_amounts) }