From 90f8a01505211262b0dec04fff6d2477c57e12a2 Mon Sep 17 00:00:00 2001 From: danda Date: Thu, 23 Dec 2021 22:39:19 -0800 Subject: [PATCH] feat: include AmountSecrets ciphertext in DbcContent --- src/amount_secrets.rs | 10 ++++ src/builder.rs | 71 ++++++++++++++++++-------- src/dbc_content.rs | 34 +++++++++++-- src/lib.rs | 12 +++++ src/mint.rs | 114 +++++++++++++++++++++--------------------- 5 files changed, 159 insertions(+), 82 deletions(-) diff --git a/src/amount_secrets.rs b/src/amount_secrets.rs index e2a5af5..6fe9231 100644 --- a/src/amount_secrets.rs +++ b/src/amount_secrets.rs @@ -16,6 +16,7 @@ use blsttc::{DecryptionShare, IntoFr, SecretKey, SecretKeySet, SecretKeyShare, C use std::convert::TryFrom; use std::collections::BTreeMap; use rand_core::OsRng; +use std::convert::Into; use crate::{Amount, Error}; @@ -28,6 +29,7 @@ use crate::{Amount, Error}; const AMT_SIZE: usize = 8; // Amount size: 8 bytes (u64) const BF_SIZE: usize = 32; // Blinding factor size: 32 bytes (Scalar) +#[derive(Clone)] pub struct AmountSecrets(RevealedCommitment); impl AmountSecrets { @@ -113,6 +115,14 @@ impl From for AmountSecrets { } } +#[allow(clippy::from_over_into)] +impl Into for AmountSecrets { + fn into(self) -> RevealedCommitment { + self.0 + } +} + + impl From for AmountSecrets { /// create AmountSecrets from an amount and a randomly generated blinding factor fn from(amount: Amount) -> Self { diff --git a/src/builder.rs b/src/builder.rs index 6042ef8..5987f45 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,12 +1,14 @@ use blsttc::{PublicKeySet, SignatureShare}; use std::collections::{BTreeMap, HashSet}; -pub use blstrs::G1Affine; -pub use blst_ringct::{MlsagMaterial, Output, RevealedCommitment}; +pub use blstrs::{G1Affine, Scalar}; +pub use blst_ringct::{MlsagMaterial, Output, RevealedCommitment, TrueInput}; +use blstrs::group::Curve; use blst_ringct::ringct::{RingCtTransaction, RingCtMaterial}; use rand_core::OsRng; +use bulletproofs::PedersenGens; use crate::{ - Amount, Dbc, DbcContent, Error, KeyImage, NodeSignature, ReissueRequest, + Amount, AmountSecrets, Dbc, DbcContent, Error, KeyImage, NodeSignature, ReissueRequest, ReissueShare, Result, SpentProof, SpentProofShare, }; @@ -32,6 +34,19 @@ impl TransactionBuilder { self } + pub fn add_input_by_secrets(mut self, secret_key: Scalar, amount_secrets: AmountSecrets) -> Self { + + let mut rng = OsRng::default(); + let true_input = TrueInput { + secret_key, + revealed_commitment: amount_secrets.into(), + }; + + let decoy_inputs = vec![]; // todo. + self.0.inputs.push(MlsagMaterial::new(true_input, decoy_inputs, &mut rng)); + self + } + pub fn add_output(mut self, output: Output) -> Self { self.0.outputs.push(output); self @@ -148,14 +163,16 @@ impl ReissueRequestBuilder { #[derive(Debug)] pub struct DbcBuilder { pub transaction: RingCtTransaction, + pub revealed_commitments: Vec, pub reissue_shares: Vec, } impl DbcBuilder { /// Create a new DbcBuilder from a ReissueTransaction - pub fn new(transaction: RingCtTransaction) -> Self { + pub fn new(transaction: RingCtTransaction, revealed_commitments: Vec) -> Self { Self { transaction, + revealed_commitments, reissue_shares: Default::default(), } } @@ -245,27 +262,41 @@ impl DbcBuilder { // Combine signatures from all the mint nodes to obtain Mint's Signature. let mint_sig = mint_public_key_set.combine_signatures(mint_sig_shares_ref)?; + let pc_gens = PedersenGens::default(); + let output_commitments: Vec<(G1Affine, RevealedCommitment)> = self.revealed_commitments.iter() + .map(|r| (r.commit(&pc_gens).to_affine(), r.clone()) ) + .collect(); + // Form the final output DBCs, with Mint's Signature for each. let output_dbcs: Vec = self .transaction .outputs .iter() - .map(|proof| Dbc { - content: DbcContent { - owner: *proof.public_key(), - }, - transaction: transaction.clone(), - transaction_sigs: self - .transaction - .mlsags - .iter() - .map(|mlsag| { - ( - mlsag.key_image.to_compressed(), - (mint_public_key_set.public_key(), mint_sig.clone()), - ) - }) - .collect(), + .map(|proof| { + let amount_secrets_list: Vec = output_commitments.iter() + .filter(|(c,_)| *c == proof.commitment()) + .map(|(_, r)| AmountSecrets::from((*r).clone())) + .collect(); + assert!(amount_secrets_list.len() == 1); + + Dbc { + content: DbcContent::from(( + *proof.public_key(), + amount_secrets_list[0].clone(), + )), + transaction: transaction.clone(), + transaction_sigs: self + .transaction + .mlsags + .iter() + .map(|mlsag| { + ( + mlsag.key_image.to_compressed(), + (mint_public_key_set.public_key(), mint_sig.clone()), + ) + }) + .collect(), + } }) .collect(); diff --git a/src/dbc_content.rs b/src/dbc_content.rs index bf53313..e29b22d 100644 --- a/src/dbc_content.rs +++ b/src/dbc_content.rs @@ -6,10 +6,11 @@ // 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 blsttc::PublicKey; +use blsttc::{Ciphertext, PublicKey}; use blstrs::group::GroupEncoding; use blstrs::G1Affine; use serde::{Deserialize, Serialize}; +use crate::{AmountSecrets, DbcHelper}; // use tiny_keccak::{Hasher, Sha3}; use crate::Hash; @@ -24,16 +25,41 @@ pub type OwnerPublicKey = G1Affine; pub struct DbcContent { // pub owner: PublicKey, pub owner: OwnerPublicKey, // Todo: what should this type be? + pub amount_secrets_cipher: Ciphertext, } /// Represents the content of a DBC. -impl From for DbcContent { +impl From<(OwnerPublicKey, Ciphertext)> for DbcContent { // Create a new DbcContent for signing. - fn from(owner: OwnerPublicKey) -> Self { - Self { owner } + fn from(params: (OwnerPublicKey, Ciphertext)) -> Self { + let (owner, amount_secrets_cipher) = params; + Self { owner, amount_secrets_cipher } } } +impl From<(OwnerPublicKey, AmountSecrets)> for DbcContent { + // Create a new DbcContent for signing. + fn from(params: (OwnerPublicKey, AmountSecrets)) -> Self { + let (owner, amount_secrets) = params; + let pubkey = DbcHelper::blstrs_to_blsttc_pubkey(&owner); + let amount_secrets_cipher = pubkey.encrypt(&amount_secrets.to_bytes()); + + Self { owner, amount_secrets_cipher } + } +} + +impl From<(PublicKey, AmountSecrets)> for DbcContent { + // Create a new DbcContent for signing. + fn from(params: (PublicKey, AmountSecrets)) -> Self { + let (pubkey, amount_secrets) = params; + let amount_secrets_cipher = pubkey.encrypt(&amount_secrets.to_bytes()); + let owner = DbcHelper::blsttc_to_blstrs_pubkey(&pubkey); + + Self { owner, amount_secrets_cipher } + } +} + + impl DbcContent { pub fn hash(&self) -> Hash { Hash::hash(self.owner.to_bytes().as_ref()) diff --git a/src/lib.rs b/src/lib.rs index b5f17cb..af2881c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; use std::fmt; +use blstrs::G1Affine; mod builder; mod amount_secrets; @@ -105,6 +106,17 @@ pub struct DbcHelper {} #[cfg(feature = "dkg")] impl DbcHelper { + + pub(crate) fn blsttc_to_blstrs_pubkey(pk: &PublicKey) -> G1Affine { + let bytes = pk.to_bytes(); + G1Affine::from_compressed(&bytes).unwrap() + } + + pub(crate) fn blstrs_to_blsttc_pubkey(pk: &G1Affine) -> PublicKey { + let bytes = pk.to_compressed(); + PublicKey::from_bytes(bytes).unwrap() + } + pub fn decrypt_amount_secrets( owner: &bls_dkg::outcome::Outcome, ciphertext: &Ciphertext, diff --git a/src/mint.rs b/src/mint.rs index f2e0098..ffe168a 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -14,7 +14,7 @@ // Outputs <= input value use crate::{ - Amount, DbcContent, Error, Hash, KeyImage, KeyManager, NodeSignature, PublicKeySet, Result, + Amount, AmountSecrets, DbcContent, Error, Hash, KeyImage, KeyManager, NodeSignature, PublicKeySet, Result, SpentProof, }; // use curve25519_dalek_ng::ristretto::RistrettoPoint; @@ -105,7 +105,6 @@ impl MintNode { // let pk = G1Projective::from_compressed(&pk_bytes).unwrap(); // let parents = BTreeSet::from_iter([genesis_dbc_input()]); - let dbc_content = DbcContent::from(public_key); let true_input = TrueInput { secret_key, @@ -120,7 +119,7 @@ impl MintNode { let ringct_material = RingCtMaterial { inputs: vec![MlsagMaterial::new(true_input, decoy_inputs, &mut rng)], outputs: vec![Output { - public_key: dbc_content.owner, + public_key, amount, }], }; @@ -130,6 +129,8 @@ impl MintNode { .sign(rng) .expect("Failed to sign transaction"); + let dbc_content = DbcContent::from((public_key, AmountSecrets::from(revealed_commitments[0].clone()))); + // let transaction = RingCtTransaction { // inputs: BTreeSet::from_iter([genesis_dbc_input()]), // outputs: BTreeSet::from_iter([dbc_content.owner]), @@ -223,24 +224,20 @@ impl MintNode { mod tests { use super::*; // use blsttc::{Ciphertext, DecryptionShare, SecretKeyShare}; - use blsttc::{PublicKey}; + // use blsttc::{PublicKey}; use quickcheck_macros::quickcheck; // use blstrs::group::GroupEncoding; use blstrs::G1Projective; + // use std::collections::BTreeSet; use crate::{ tests::{TinyInt, TinyVec}, // AmountSecrets, DbcHelper, - DbcBuilder, ReissueRequestBuilder, SimpleKeyManager, SimpleSigner, + DbcBuilder, DbcHelper, ReissueRequestBuilder, SimpleKeyManager, SimpleSigner, Dbc, SpentProofShare, }; - fn blsttc_to_blstrs_pubkey(pk: &PublicKey) -> G1Affine { - let bytes = pk.to_bytes(); - G1Affine::from_compressed(&bytes).unwrap() - } - #[quickcheck] fn prop_genesis() -> Result<(), Error> { let genesis_owner = crate::bls_dkg_id(); @@ -309,9 +306,9 @@ mod tests { // let genesis_amount_secrets = AmountSecrets::from(genesis.revealed_commitment); let output_owner = crate::bls_dkg_id(); - let output_owner_pk = blsttc_to_blstrs_pubkey(&output_owner.public_key_set.public_key()); + let output_owner_pk = DbcHelper::blsttc_to_blstrs_pubkey(&output_owner.public_key_set.public_key()); - let (reissue_tx, _revealed_commitments) = crate::TransactionBuilder::default() + let (reissue_tx, revealed_commitments) = crate::TransactionBuilder::default() .add_inputs(genesis.ringct_material.inputs) .add_outputs(output_amounts.iter().map(|a| crate::Output { amount: *a, @@ -372,23 +369,23 @@ mod tests { }; // Aggregate ReissueShare to build output DBCs - let mut dbc_builder = DbcBuilder::new(reissue_tx); + let mut dbc_builder = DbcBuilder::new(reissue_tx, revealed_commitments); dbc_builder = dbc_builder.add_reissue_share(reissue_share); - let _output_dbcs = dbc_builder.build()?; - - // for dbc in output_dbcs.iter() { - // let dbc_amount = DbcHelper::decrypt_amount(&output_owner, &dbc.content)?; - // assert!(output_amounts.iter().any(|a| *a == dbc_amount)); - // assert!(dbc.confirm_valid(&key_manager).is_ok()); - // } - - // assert_eq!( - // output_dbcs - // .iter() - // .map(|dbc| { DbcHelper::decrypt_amount(&output_owner, &dbc.content) }) - // .sum::>()?, - // output_amount - // ); + let output_dbcs = dbc_builder.build()?; + + for dbc in output_dbcs.iter() { + let dbc_amount = DbcHelper::decrypt_amount(&output_owner, &dbc.content.amount_secrets_cipher)?; + assert!(output_amounts.iter().any(|a| *a == dbc_amount)); + // assert!(dbc.confirm_valid(&key_manager).is_ok()); + } + + assert_eq!( + output_dbcs + .iter() + .map(|dbc| { DbcHelper::decrypt_amount(&output_owner, &dbc.content.amount_secrets_cipher) }) + .sum::>()?, + output_amount + ); Ok(()) } @@ -688,6 +685,7 @@ mod tests { fn prop_reject_invalid_prefix() { todo!(); } +*/ #[test] fn test_inputs_are_validated() -> Result<(), Error> { @@ -699,42 +697,42 @@ mod tests { let mut genesis_node = MintNode::new(key_manager); let input_owner = crate::bls_dkg_id(); - let input_content = DbcContent::new( - Default::default(), - 100, - input_owner.public_key_set.public_key(), - AmountSecrets::random_blinding_factor(), - )?; - let input_owners = BTreeSet::from_iter([input_content.owner]); - - let in_dbc = Dbc { - content: input_content, - transaction: DbcTransaction { - inputs: Default::default(), - outputs: input_owners, - }, - transaction_sigs: Default::default(), - }; + let owner_pubkey = DbcHelper::blsttc_to_blstrs_pubkey(&input_owner.public_key_set.public_key()); - let in_dbc_spend_keys = BTreeSet::from_iter([in_dbc.spend_key()]); - - let fraudulant_reissue_result = genesis_node.reissue(ReissueRequest { - transaction: ReissueTransaction { - inputs: HashSet::from_iter([in_dbc]), - outputs: HashSet::from_iter([DbcContent::new( - in_dbc_spend_keys, - 100, - crate::bls_dkg_id().public_key_set.public_key(), - AmountSecrets::random_blinding_factor(), - )?]), - }, - spent_proofs: Default::default(), - }); + let output = Output{ public_key: owner_pubkey, amount: 100}; + let (_transaction, revealed_commitments) = crate::TransactionBuilder::default() + .add_output(output) + .build()?; + + let amount_secrets = AmountSecrets::from(revealed_commitments[0]); + // let input_content = DbcContent::from((owner_pubkey, amount_secrets.clone())); + + // let in_dbc = Dbc { + // content: input_content, + // transaction, + // transaction_sigs: Default::default(), + // }; + + let secret_key = Scalar::default(); // fixme + + let (fraud_tx, _) = crate::TransactionBuilder::default() + .add_input_by_secrets(secret_key, amount_secrets) + .add_output(Output{ public_key: owner_pubkey, amount: 100}) + .build()?; + + let fraud_rr = ReissueRequestBuilder::new(fraud_tx) + .build()?; + + let fraudulant_reissue_result = genesis_node.reissue(fraud_rr); + + // fixme: more/better assertions. assert!(fraudulant_reissue_result.is_err()); Ok(()) } +/* + /// This tests how the system handles a mis-match between the /// committed amount and amount encrypted in AmountSecrets. /// Normally these should be the same, however a malicious user or buggy