213 changes: 47 additions & 166 deletions examples/mint-repl/mint-repl.rs
Expand Up @@ -18,15 +18,14 @@ use blsttc::serde_impl::SerdeSecret;
use blsttc::{PublicKey, PublicKeySet, SecretKey, SecretKeySet, SecretKeyShare};
use rand::seq::IteratorRandom;
use rand::Rng;
use rand8::SeedableRng;
use rand::SeedableRng;
use rustyline::config::Configurer;
use rustyline::error::ReadlineError;
use rustyline::Editor;
use serde::{Deserialize, Serialize};
use sn_dbc::{
Amount, Dbc, DbcBuilder, GenesisBuilderMock, MintNode, Output, OutputOwnerMap, Owner,
OwnerOnce, ReissueRequest, ReissueRequestBuilder, SimpleKeyManager, SpentBookNodeMock,
TransactionBuilder,
Amount, Dbc, DbcBuilder, GenesisBuilderMock, OutputOwnerMap, Owner, OwnerOnce,
SpentBookNodeMock, TransactionBuilder,
};
use std::collections::{BTreeMap, HashMap};
use std::iter::FromIterator;
Expand All @@ -42,7 +41,6 @@ const STD_DECOYS: usize = 3; // how many decoys to use (when available in spentb
/// Holds information about the Mint, which may be comprised
/// of 1 or more nodes.
struct MintInfo {
mintnodes: Vec<MintNode<SimpleKeyManager>>,
spentbook_nodes: Vec<SpentBookNodeMock>,
genesis: Dbc,
secret_key_set: SecretKeySet,
Expand All @@ -51,13 +49,6 @@ struct MintInfo {
}

impl MintInfo {
// returns the first mint node.
fn mintnode(&self) -> Result<&MintNode<SimpleKeyManager>> {
self.mintnodes
.get(0)
.ok_or_else(|| anyhow!("Mint not yet created"))
}

// returns the first spentbook node.
fn spentbook(&self) -> Result<&SpentBookNodeMock> {
self.spentbook_nodes
Expand All @@ -75,14 +66,6 @@ struct RingCtTransactionRevealed {
output_owner_map: OutputOwnerMap,
}

/// A ReissueRequest with pubkey set for all the input and output Dbcs
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ReissueRequestRevealed {
inner: ReissueRequest,
revealed_commitments: Vec<RevealedCommitment>,
output_owner_map: OutputOwnerMap,
}

/// program entry point and interactive command handler.
fn main() -> Result<()> {
// Disable TTY ICANON. So readline() can read more than 4096 bytes.
Expand Down Expand Up @@ -197,23 +180,18 @@ fn mk_new_random_mint(threshold: usize) -> Result<MintInfo> {

/// creates a new mint from an existing SecretKeySet that was seeded by poly.
fn mk_new_mint(sks: SecretKeySet, poly: Poly) -> Result<MintInfo> {
let mut rng8 = rand8::rngs::StdRng::from_seed([0u8; 32]);
let mut rng = rand::rngs::StdRng::from_seed([0u8; 32]);

// make as many spentbook nodes as mintnodes. (for now)
let num_mint_nodes = sks.threshold() + 1;
let num_spentbook_nodes = num_mint_nodes;
let num_spentbook_nodes = sks.threshold() + 1;

let (mint_nodes, spentbook_nodes, genesis_dbc, _genesis, _amount_secrets) =
GenesisBuilderMock::default()
.gen_mint_nodes_with_sks(num_mint_nodes, &sks)
.gen_spentbook_nodes_with_sks(num_spentbook_nodes, &sks)
.build(&mut rng8)?;
let (spentbook_nodes, genesis_dbc, _genesis, _amount_secrets) = GenesisBuilderMock::default()
.gen_spentbook_nodes_with_sks(num_spentbook_nodes, &sks)
.build(&mut rng)?;

let reissue_auto = ReissueAuto::from(vec![genesis_dbc.clone()]);

// Bob's your uncle.
Ok(MintInfo {
mintnodes: mint_nodes,
spentbook_nodes,
genesis: genesis_dbc,
secret_key_set: sks,
Expand Down Expand Up @@ -321,9 +299,12 @@ fn newkeys() -> Result<()> {
fn print_mintinfo_human(mintinfo: &MintInfo) -> Result<()> {
println!();

println!("Number of Mint Nodes: {}\n", mintinfo.mintnodes.len());
println!(
"Number of Spentbook Nodes: {}\n",
mintinfo.spentbook_nodes.len()
);

println!("-- Mint Keys --\n");
println!("-- Spentbook Keys --\n");
println!("SecretKeySet (Poly): {}\n", to_be_hex(&mintinfo.poly)?);

println!(
Expand Down Expand Up @@ -447,7 +428,9 @@ fn print_dbc_human(dbc: &Dbc, outputs: bool, secret_key_base: Option<SecretKey>)

/// handles decode command.
fn decode_input() -> Result<()> {
let t = readline_prompt("\n[d: DBC, rt: RingCtTransaction, rr: ReissueRequest, pks: PublicKeySet, sks: SecretKeySet]\nType: ")?;
let t = readline_prompt(
"\n[d: DBC, rt: RingCtTransaction, pks: PublicKeySet, sks: SecretKeySet]\nType: ",
)?;
let input = readline_prompt_nl("\nPaste Data: ")?;
let bytes = decode(input)?;

Expand Down Expand Up @@ -514,10 +497,6 @@ fn decode_input() -> Result<()> {
"\n\n-- RingCtTransaction --\n\n{:#?}",
from_be_bytes::<RingCtTransactionRevealed>(&bytes)?
),
"rr" => println!(
"\n\n-- ReissueRequest --\n\n{:#?}",
from_be_bytes::<ReissueRequestRevealed>(&bytes)?
),
_ => println!("Unknown type!"),
}
println!();
Expand Down Expand Up @@ -566,7 +545,7 @@ fn verify(mintinfo: &MintInfo) -> Result<()> {
}
};

match dbc.verify(&secret_key, mintinfo.mintnode()?.key_manager()) {
match dbc.verify(&secret_key, &mintinfo.spentbook()?.key_manager) {
Ok(_) => match mintinfo.spentbook()?.is_spent(&dbc.key_image(&secret_key)?) {
true => println!("\nThis DBC is unspendable. (valid but has already been spent)\n"),
false => println!("\nThis DBC is spendable. (valid and has not been spent)\n"),
Expand All @@ -578,9 +557,8 @@ fn verify(mintinfo: &MintInfo) -> Result<()> {
}

/// Implements prepare_tx command.
fn prepare_tx(mintinfo: &MintInfo) -> Result<RingCtTransactionRevealed> {
fn prepare_tx(mintinfo: &MintInfo) -> Result<DbcBuilder> {
let mut rng = rand::thread_rng();
let mut rng8 = rand8::thread_rng();
let mut tx_builder: TransactionBuilder = Default::default();

// Get DBC inputs from user
Expand Down Expand Up @@ -610,8 +588,8 @@ fn prepare_tx(mintinfo: &MintInfo) -> Result<RingCtTransactionRevealed> {
}
};

let decoy_inputs = mintinfo.spentbook()?.random_decoys(STD_DECOYS, &mut rng8);
tx_builder = tx_builder.add_input_dbc(&dbc, &base_secret_key, decoy_inputs, &mut rng8)?;
let decoy_inputs = mintinfo.spentbook()?.random_decoys(STD_DECOYS, &mut rng);
tx_builder = tx_builder.add_input_dbc(&dbc, &base_secret_key, decoy_inputs, &mut rng)?;
}

let mut i = 0u32;
Expand Down Expand Up @@ -677,95 +655,30 @@ fn prepare_tx(mintinfo: &MintInfo) -> Result<RingCtTransactionRevealed> {
}
};

let owner_once = OwnerOnce::from_owner_base(owner_base, &mut rng8);
let owner_once = OwnerOnce::from_owner_base(owner_base, &mut rng);

tx_builder = tx_builder.add_output(
Output {
amount,
public_key: owner_once.as_owner().public_key_blst(),
},
owner_once,
);
tx_builder = tx_builder.add_output_by_amount(amount, owner_once);

i += 1;
}

println!("\n\nPreparing RingCtTransaction...\n\n");

let (rr_builder, dbc_builder, ringct_material) = tx_builder.build(&mut rng8)?;

Ok(RingCtTransactionRevealed {
inner: rr_builder.transaction,
revealed_commitments: dbc_builder.revealed_commitments,
ringct_material,
output_owner_map: dbc_builder.output_owner_map,
})
}

// Not necessary until multisig Dbc owner is supported
#[allow(dead_code)]
fn prepare_tx_cli(mintinfo: &MintInfo) -> Result<()> {
let transaction = prepare_tx(mintinfo)?;

println!("\n-- RingCtTransaction --");
println!("{}", to_be_hex(&transaction)?);
println!("-- End RingCtTransaction --\n");
let dbc_builder = tx_builder.build(&mut rng)?;

Ok(())
Ok(dbc_builder)
}

/// Implements prepare_reissue command.
fn prepare_reissue(
mintinfo: &mut MintInfo,
tx: RingCtTransactionRevealed,
) -> Result<ReissueRequestRevealed> {
let mut rr_builder = ReissueRequestBuilder::new(tx.inner.clone());
for mlsag in tx.inner.mlsags.iter() {
fn write_to_spentbook(mintinfo: &mut MintInfo, mut dbc_builder: DbcBuilder) -> Result<DbcBuilder> {
println!("\nWriting to Spentbook...\n\n");
for (key_image, tx) in dbc_builder.inputs() {
for (sp_idx, sb_node) in mintinfo.spentbook_nodes.iter_mut().enumerate() {
println!("logging input {:?}, spentbook {}", mlsag.key_image, sp_idx);
let spent_proof_share = sb_node.log_spent(mlsag.key_image.into(), tx.inner.clone())?;
rr_builder = rr_builder.add_spent_proof_share(spent_proof_share);
println!("logging input {:?}, spentbook {}", key_image, sp_idx);
dbc_builder =
dbc_builder.add_spent_proof_share(sb_node.log_spent(key_image, tx.clone())?);
}
}

println!("\n\nThank-you. Preparing ReissueRequest...\n\n");
let rr = rr_builder.build()?;

let reissue_request = ReissueRequestRevealed {
inner: rr,
revealed_commitments: tx.revealed_commitments,
output_owner_map: tx.output_owner_map,
};

Ok(reissue_request)
}

/// Implements prepare_reissue command.
// Not necessary until multisig Dbc owner is supported
#[allow(dead_code)]
fn prepare_reissue_cli(mintinfo: &mut MintInfo) -> Result<()> {
let tx_input = readline_prompt_nl("\nRingCtTransaction: ")?;
let tx: RingCtTransactionRevealed = from_be_hex(&tx_input)?;

let reissue_request = prepare_reissue(mintinfo, tx)?;

println!("\n-- ReissueRequest --");
println!("{}", to_be_hex(&reissue_request)?);
println!("-- End ReissueRequest --\n");

Ok(())
}

/// Implements reissue command.
// Not necessary until multisig Dbc owner is supported
#[allow(dead_code)]
fn reissue_prepared_cli(mintinfo: &mut MintInfo) -> Result<()> {
let mr_input = readline_prompt_nl("\nReissueRequest: ")?;
let reissue_request: ReissueRequestRevealed = from_be_hex(&mr_input)?;

println!("\n\nThank-you. Generating DBC(s)...\n\n");

reissue(mintinfo, reissue_request)
Ok(dbc_builder)
}

struct ReissueAuto {
Expand All @@ -782,14 +695,13 @@ impl From<Vec<Dbc>> for ReissueAuto {

fn reissue_auto_cli(mintinfo: &mut MintInfo) -> Result<()> {
let mut rng = rand::thread_rng();
let mut rng8 = rand8::thread_rng();

let num_reissues: usize =
readline_prompt_nl_default("\nHow many reissues to perform [10]: ", "10")?.parse()?;

for _i in 1..=num_reissues {
let max_inputs = std::cmp::min(mintinfo.reissue_auto.unspent_dbcs.len(), 10);
let num_inputs = rng.gen_range(1, max_inputs + 1);
let num_inputs = rng.gen_range(1..max_inputs + 1);

// subset of unspent_dbcs become the inputs for next reissue.
let input_dbcs: Vec<Dbc> = mintinfo
Expand All @@ -805,9 +717,9 @@ fn reissue_auto_cli(mintinfo: &mut MintInfo) -> Result<()> {

for dbc in input_dbcs.iter() {
let base_sk = dbc.owner_base().secret_key()?;
let decoy_inputs = mintinfo.spentbook()?.random_decoys(STD_DECOYS, &mut rng8);
let decoy_inputs = mintinfo.spentbook()?.random_decoys(STD_DECOYS, &mut rng);

tx_builder = tx_builder.add_input_dbc(dbc, &base_sk, decoy_inputs, &mut rng8)?;
tx_builder = tx_builder.add_input_dbc(dbc, &base_sk, decoy_inputs, &mut rng)?;
}

let inputs_sum = tx_builder.inputs_amount_sum();
Expand All @@ -816,34 +728,23 @@ fn reissue_auto_cli(mintinfo: &mut MintInfo) -> Result<()> {
// randomize output amount
let diff = inputs_sum - tx_builder.outputs_amount_sum();
let range_max = if diff == Amount::MAX { diff } else { diff + 1 };
let amount = rng.gen_range(0, range_max);
let amount = rng.gen_range(0..range_max);

let owner_once =
OwnerOnce::from_owner_base(Owner::from_random_secret_key(&mut rng), &mut rng8);

tx_builder = tx_builder.add_output(
Output {
amount,
public_key: owner_once.as_owner().public_key_blst(),
},
owner_once,
);
OwnerOnce::from_owner_base(Owner::from_random_secret_key(&mut rng), &mut rng);

tx_builder = tx_builder.add_output_by_amount(amount, owner_once);
}

let (mut rr_builder, mut dbc_builder, _material) = tx_builder.build(&mut rng8)?;
let mut dbc_builder = tx_builder.build(&mut rng)?;

for (key_image, tx) in rr_builder.inputs() {
for (key_image, tx) in dbc_builder.inputs() {
for spentbook_node in mintinfo.spentbook_nodes.iter_mut() {
rr_builder = rr_builder
dbc_builder = dbc_builder
.add_spent_proof_share(spentbook_node.log_spent(key_image, tx.clone())?);
}
}
let rr = rr_builder.build()?;

for mint_node in mintinfo.mintnodes.iter() {
dbc_builder = dbc_builder.add_reissue_share(mint_node.reissue(rr.clone())?);
}
let outputs = dbc_builder.build(mintinfo.mintnodes[0].key_manager())?;
let outputs = dbc_builder.build(&mintinfo.spentbook()?.key_manager)?;
let output_dbcs: Vec<Dbc> = outputs.into_iter().map(|(dbc, ..)| dbc).collect();

for dbc in input_dbcs.iter() {
Expand All @@ -868,34 +769,14 @@ fn reissue_auto_cli(mintinfo: &mut MintInfo) -> Result<()> {

/// Implements reissue command.
fn reissue_cli(mintinfo: &mut MintInfo) -> Result<()> {
let tx = prepare_tx(mintinfo)?;
let rr = prepare_reissue(mintinfo, tx)?;
reissue(mintinfo, rr)
let dbc_builder = prepare_tx(mintinfo)?;
let dbc_builder = write_to_spentbook(mintinfo, dbc_builder)?;
reissue(mintinfo, dbc_builder)
}

/// Performs reissue
fn reissue(mintinfo: &mut MintInfo, reissue_request: ReissueRequestRevealed) -> Result<()> {
let ReissueRequestRevealed {
inner,
revealed_commitments,
output_owner_map,
} = reissue_request;
let mut dbc_builder = DbcBuilder::new(revealed_commitments, output_owner_map);

// Mint is multi-node. So each mint node must execute Mint::reissue() and
// provide its SignatureShare, which the client must then combine together
// to form the mint's Signature. This loop would exec on the client.
for (idx, mint) in mintinfo.mintnodes.iter_mut().enumerate() {
// here we pretend the client has made a network request to a single mint node
// so this mint.reissue() execs on the Mint node and returns data to client.
println!("Sending reissue request to mint node {}...", idx);
let reissue_share = mint.reissue(inner.clone())?;

// and now we are back to client code.
dbc_builder = dbc_builder.add_reissue_share(reissue_share);
}

let output_dbcs = dbc_builder.build(mintinfo.mintnodes[0].key_manager())?;
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() {
Expand Down
6 changes: 4 additions & 2 deletions src/amount_secrets.rs
Expand Up @@ -6,6 +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::{BlindingFactor, Error};
use blst_ringct::RevealedCommitment;
use blsttc::{
Ciphertext, DecryptionShare, IntoFr, PublicKey, PublicKeySet, SecretKey, SecretKeySet,
Expand All @@ -15,7 +16,8 @@ use rand_core::RngCore;
use std::collections::BTreeMap;
use std::convert::TryFrom;

use crate::{Amount, BlindingFactor, Error, SecretKeyBlst};
// we re-export this.
pub use blst_ringct::ringct::Amount;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -43,7 +45,7 @@ impl AmountSecrets {
}

/// blinding factor getter
pub fn blinding_factor(&self) -> SecretKeyBlst {
pub fn blinding_factor(&self) -> BlindingFactor {
self.0.blinding
}

Expand Down
128 changes: 12 additions & 116 deletions src/blst.rs
Expand Up @@ -6,131 +6,27 @@
// 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.

//! This module defines blstrs aliases, wrappers, and helpers.
//!
//! blstrs types Scalar and G1Affine are used to represent distinct concepts
//! in ringct such as:
//! Scalar: SecretKey, BlindingFactor
//! G1Affine: Commitment, PublicKey, KeyImage
//! This module defines blstrs aliases
//!
//! We provide type aliases to make the usage in each context clearer and to make the
//! the sn_dbc public API simpler so that the caller should not need to depend on blstrs
//! and use its types directly.
//!
//! Even sn_dbc uses the type aliases rather than directly using the blstrs types.
//! sn_dbc internally uses the type aliases rather than directly using the blstrs types.
//!
//! We could consider moving some or all of this lower into blst_ringct to make these
//! crates consistent.

use blstrs::group::{Curve, Group};
use blstrs::{G1Affine, G1Projective, Scalar};
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};

use blsttc::{PublicKey, SecretKey};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// a SecretKey in Blst format
pub type SecretKeyBlst = Scalar;

/// a PublicKey in Blst format
pub type PublicKeyBlst = G1Affine;

/// a Commitment
pub type Commitment = G1Affine;
pub type Commitment = blstrs::G1Affine;

/// a BlindingFactor
pub type BlindingFactor = Scalar;

/// A KeyImage, which is derived from pk and sk
pub type KeyImage = PublicKeyBlstMappable;

// This is a NewType wrapper for blstrs::G1Affine because in places we
// need to use it as a key in a BTreeMap.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub struct PublicKeyBlstMappable(G1Affine);

impl PublicKeyBlstMappable {
pub fn to_bytes(&self) -> [u8; 48] {
self.0.to_compressed()
}

pub fn random(rng: &mut impl rand8::RngCore) -> Self {
Self(G1Projective::random(rng).to_affine())
}
}

impl PartialEq for KeyImage {
fn eq(&self, other: &Self) -> bool {
self.0.to_compressed() == other.0.to_compressed()
}
}

impl AsRef<G1Affine> for KeyImage {
fn as_ref(&self) -> &G1Affine {
&self.0
}
}

impl Eq for PublicKeyBlstMappable {}

impl PartialOrd for KeyImage {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for PublicKeyBlstMappable {
fn cmp(&self, other: &Self) -> Ordering {
self.0.to_compressed().cmp(&other.0.to_compressed())
}
}

impl Hash for PublicKeyBlstMappable {
fn hash<H: Hasher>(&self, state: &mut H) {
let bytes = self.0.to_compressed();
bytes.hash(state);
}
}

impl From<G1Affine> for PublicKeyBlstMappable {
fn from(k: G1Affine) -> Self {
Self(k)
}
}

// temporary: should go away once blsttc is integrated with with blstrs
// For this reason, we allow unwrap() in these methods so that
// it doesn't force higher APIs to return a Result when they should
// not need to do so once the integration is complete.
pub struct BlsHelper {}

impl BlsHelper {
#[allow(dead_code)]
pub fn blsttc_to_blstrs_secret_key(sk: SecretKey) -> SecretKeyBlst {
let bytes = sk.to_bytes();
// fixme: unwrap
SecretKeyBlst::from_bytes_be(&bytes).unwrap()
}

pub fn blsttc_to_blstrs_public_key(pk: &PublicKey) -> PublicKeyBlst {
let bytes = pk.to_bytes();
// fixme: unwrap
PublicKeyBlst::from_compressed(&bytes).unwrap()
}

pub fn blstrs_to_blsttc_public_key(pk: &PublicKeyBlst) -> PublicKey {
let bytes = pk.to_compressed();
// fixme: unwrap
PublicKey::from_bytes(bytes).unwrap()
}

pub fn blstrs_to_blsttc_secret_key(sk: SecretKeyBlst) -> SecretKey {
let bytes = sk.to_bytes_be();
// fixme: unwrap
SecretKey::from_bytes(bytes).unwrap()
}
}
pub type BlindingFactor = blstrs::Scalar;

/// A KeyImage can be thought of as a specific type
/// of public key. blsttc::PublicKey is a newtype
/// wrapper around blstrs::G1Affine. We use
/// PublicKey because it impls Hash and Ord traits
/// that are useful for storing the KeyImage in
/// a map.
pub type KeyImage = blsttc::PublicKey;
451 changes: 147 additions & 304 deletions src/builder.rs

Large diffs are not rendered by default.

403 changes: 175 additions & 228 deletions src/dbc.rs

Large diffs are not rendered by default.

8 changes: 1 addition & 7 deletions src/dbc_content.rs
Expand Up @@ -15,12 +15,6 @@ use serde::{Deserialize, Serialize};

use crate::{Error, Hash, Result};

// note: Amount should move into blst_ringct crate.
// (or else blst_ringct::RevealedCommitment should be made generic over Amount type)

/// Represents a Dbc's value.
pub type Amount = u64;

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DbcContent {
Expand All @@ -35,7 +29,7 @@ pub struct DbcContent {
/// holding the SecretKey, ie the Dbc recipient that generated the PublicKey.
///
/// This key is only a client/wallet concept. It is NOT actually used in the transaction
/// and never seen by the Mint or Spentbook.
/// and never seen by the Spentbook.
///
/// The "real" key used in the transaction is derived from this key using a random
/// derivation index, which is stored (encrypted) in owner_derivation_cipher.
Expand Down
23 changes: 7 additions & 16 deletions src/error.rs
Expand Up @@ -21,23 +21,14 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
#[non_exhaustive]
/// Node error variants.
pub enum Error {
#[error("An error occured when signing {0}")]
Signing(String),

#[error("Failed signature check.")]
FailedSignature,

#[error("Unrecognised authority.")]
UnrecognisedAuthority,

#[error("At least one transaction input is missing a signature.")]
MissingSignatureForInput,

#[error("The number of mint signatures does not match the number of transaction inputs.")]
MintSignatureInputMismatch,

#[error("Invalid SpentProof Signature for {0:?}")]
InvalidSpentProofSignature(KeyImage),
#[error("Invalid SpentProof Signature for {0:?}. Error: {1}")]
InvalidSpentProofSignature(KeyImage, String),

#[error("Transaction hash does not match the transaction signed by spentbook")]
InvalidTransactionHash,
Expand All @@ -52,13 +43,13 @@ pub enum Error {
PublicKeyNotUniqueAcrossOutputs,

#[error("The number of SpentProof does not match the number of input MlsagSignature")]
SpentProofInputMismatch,
SpentProofInputLenMismatch,

#[error("We need at least one spent proof share for {0:?} to build a SpentProof")]
ReissueRequestMissingSpentProofShare(KeyImage),
#[error("A SpentProof KeyImage does not match an MlsagSignature KeyImage")]
SpentProofInputKeyImageMismatch,

#[error("ReissueShare do not match")]
ReissueShareMismatch,
#[error("We need at least one spent proof share for {0:?} to build a SpentProof")]
MissingSpentProofShare(KeyImage),

#[error("Decryption failed")]
DecryptionBySecretKeyFailed,
Expand Down
18 changes: 9 additions & 9 deletions src/genesis.rs
@@ -1,4 +1,4 @@
use crate::{Amount, BlsHelper, KeyImage, Owner, OwnerOnce};
use crate::{Amount, KeyImage, Owner, OwnerOnce};
use blst_ringct::mlsag::{MlsagMaterial, TrueInput};
use blst_ringct::ringct::RingCtMaterial;
use blst_ringct::{Output, RevealedCommitment};
Expand Down Expand Up @@ -52,13 +52,13 @@ impl Default for GenesisMaterial {
let output_sk_once = output_sk.derive_child(&output_owner_once.derivation_index);

// build our TrueInput
let true_input = TrueInput {
secret_key: BlsHelper::blsttc_to_blstrs_secret_key(input_sk),
revealed_commitment: RevealedCommitment {
let true_input = TrueInput::new(
input_sk,
RevealedCommitment {
value: Self::GENESIS_AMOUNT,
blinding: 1776.into(), // freedom baby!
},
};
);

// make things a bit easier for our callers.
let input_key_image: KeyImage = true_input.key_image().to_affine().into();
Expand All @@ -76,10 +76,10 @@ impl Default for GenesisMaterial {
// onward to RingCtMaterial
let ringct_material = RingCtMaterial {
inputs: vec![mlsag_material],
outputs: vec![Output {
public_key: BlsHelper::blsttc_to_blstrs_public_key(&output_sk_once.public_key()),
amount: Self::GENESIS_AMOUNT,
}],
outputs: vec![Output::new(
output_sk_once.public_key(),
Self::GENESIS_AMOUNT,
)],
};

// Voila!
Expand Down
13 changes: 4 additions & 9 deletions src/lib.rs
Expand Up @@ -23,22 +23,18 @@ mod spentbook;
mod verification;

pub use crate::{
amount_secrets::AmountSecrets,
blst::{
BlindingFactor, BlsHelper, Commitment, KeyImage, PublicKeyBlst, PublicKeyBlstMappable,
SecretKeyBlst,
},
amount_secrets::{Amount, AmountSecrets},
blst::{BlindingFactor, Commitment, KeyImage},
builder::mock::GenesisBuilderMock,
builder::{DbcBuilder, Output, OutputOwnerMap, ReissueRequestBuilder, TransactionBuilder},
builder::{DbcBuilder, Output, OutputOwnerMap, TransactionBuilder},
dbc::Dbc,
dbc_content::{Amount, DbcContent},
dbc_content::DbcContent,
error::{Error, Result},
genesis::GenesisMaterial,
key_manager::{
IndexedSignatureShare, KeyManager, PublicKey, PublicKeySet, Signature, SimpleKeyManager,
SimpleSigner,
},
mint::{MintNode, ReissueRequest, ReissueShare},
owner::{DerivationIndex, Owner, OwnerOnce},
spent_proof::{SpentProof, SpentProofContent, SpentProofShare},
spentbook::SpentBookNodeMock,
Expand Down Expand Up @@ -67,7 +63,6 @@ impl From<[u8; 32]> for Hash {
}

// Display Hash value as hex in Debug output. consolidates 36 lines to 3 for pretty output
// and the hex value is the same as sn_dbc_mint display of DBC IDs.
impl fmt::Debug for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Hash").field(&hex::encode(self.0)).finish()
Expand Down
573 changes: 151 additions & 422 deletions src/mint.rs

Large diffs are not rendered by default.

22 changes: 5 additions & 17 deletions src/owner.rs
Expand Up @@ -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::{BlsHelper, Error, PublicKey, PublicKeyBlst, Result, SecretKeyBlst};
use crate::{Error, PublicKey, Result};
use blsttc::{serde_impl::SerdeSecret, SecretKey};
use std::fmt;

Expand All @@ -30,10 +30,10 @@ pub type DerivationIndex = [u8; 32];
///
/// The base Owner public key is given out to other people
/// in order to receive payments from them. It is never
/// seen by the Mint or SpentBook.
/// seen by the SpentBook.
///
/// The one-time-use Owner public key is not normally given
/// to 3rd parties. It is used by Mint and SpentBook exactly
/// to 3rd parties. It is used by SpentBook exactly
/// once, when spending the associated Dbc.
///
/// See OwnerOnce which relates the two.
Expand Down Expand Up @@ -98,18 +98,6 @@ impl Owner {
}
}

/// returns owner BLST PublicKey derived from owner base PublicKey
// note: can go away once blsttc integrated with blst_ringct.
pub fn public_key_blst(&self) -> PublicKeyBlst {
BlsHelper::blsttc_to_blstrs_public_key(&self.public_key())
}

/// returns owner BLST SecretKey derived from owner base SecretKey, if available.
// note: can go away once blsttc integrated with blst_ringct.
pub fn secret_key_blst(&self) -> Result<SecretKeyBlst> {
Ok(BlsHelper::blsttc_to_blstrs_secret_key(self.secret_key()?))
}

/// derives new Owner from provided DerivationIndex
pub fn derive(&self, i: &DerivationIndex) -> Self {
match self {
Expand Down Expand Up @@ -170,15 +158,15 @@ impl OwnerOnce {
}

/// create OwnerOnce from a base Owner
pub fn from_owner_base(owner_base: Owner, rng: &mut impl rand8::RngCore) -> Self {
pub fn from_owner_base(owner_base: Owner, rng: &mut impl rand::RngCore) -> Self {
Self {
owner_base,
derivation_index: Self::random_derivation_index(rng),
}
}

// generates a random derivation index
pub(crate) fn random_derivation_index(rng: &mut impl rand8::RngCore) -> [u8; 32] {
pub(crate) fn random_derivation_index(rng: &mut impl rand::RngCore) -> [u8; 32] {
let mut bytes = [0u8; 32];
rng.fill_bytes(&mut bytes);
bytes
Expand Down
2 changes: 1 addition & 1 deletion src/spent_proof.rs
Expand Up @@ -189,7 +189,7 @@ impl SpentProof {
&self.spentbook_pub_key,
&self.spentbook_sig,
)
.map_err(|_| Error::InvalidSpentProofSignature(*self.key_image()))?;
.map_err(|e| Error::InvalidSpentProofSignature(*self.key_image(), e.to_string()))?;
Ok(())
}
}
22 changes: 10 additions & 12 deletions src/spentbook.rs
Expand Up @@ -9,13 +9,14 @@
use blst_ringct::ringct::{OutputProof, RingCtTransaction};
use blst_ringct::DecoyInput;
use blstrs::group::Curve;
use blsttc::PublicKey;
use std::collections::{BTreeMap, HashMap};

use rand8::prelude::IteratorRandom;
use rand::prelude::IteratorRandom;

use crate::{
Commitment, GenesisMaterial, Hash, KeyImage, KeyManager, PublicKeyBlstMappable, Result,
SimpleKeyManager, SpentProofContent, SpentProofShare,
Commitment, GenesisMaterial, Hash, KeyImage, KeyManager, Result, SimpleKeyManager,
SpentProofContent, SpentProofShare,
};

/// This is a mock SpentBook used for our test cases. A proper implementation
Expand Down Expand Up @@ -46,7 +47,7 @@ pub struct SpentBookNodeMock {

pub transactions: HashMap<Hash, RingCtTransaction>,
pub key_images: BTreeMap<KeyImage, Hash>,
pub outputs: BTreeMap<PublicKeyBlstMappable, OutputProof>,
pub outputs: BTreeMap<PublicKey, OutputProof>,

pub genesis: (KeyImage, Commitment), // genesis input (keyimage, public_commitment)
}
Expand Down Expand Up @@ -133,10 +134,7 @@ impl SpentBookNodeMock {
let output_proofs: Vec<&OutputProof> = mlsag
.public_keys()
.iter()
.flat_map(|pk| {
let pkbm: PublicKeyBlstMappable = (*pk).into();
self.outputs.get(&pkbm)
})
.flat_map(|pk| self.outputs.get(&(*pk).into()))
.collect();

if output_proofs.len() != mlsag.public_keys().len() {
Expand Down Expand Up @@ -188,8 +186,8 @@ impl SpentBookNodeMock {

// Add public_key:output_proof to public_key index.
for output in existing_tx.outputs.iter() {
let pkbm: PublicKeyBlstMappable = (*output.public_key()).into();
self.outputs.entry(pkbm).or_insert_with(|| output.clone());
let pk = PublicKey::from(*output.public_key());
self.outputs.entry(pk).or_insert_with(|| output.clone());
}

let sp_content = SpentProofContent {
Expand Down Expand Up @@ -217,14 +215,14 @@ impl SpentBookNodeMock {
pub fn random_decoys(
&self,
target_num: usize,
rng: &mut impl rand8::RngCore,
rng: &mut impl rand::RngCore,
) -> Vec<DecoyInput> {
// Get a unique list of all OutputProof
// note: Tx are duplicated in Spentbook. We use a BTreeMap
// with KeyImage to dedup.
// note: Once we refactor to avoid Tx duplication, this
// map can go away.
let outputs_unique: BTreeMap<PublicKeyBlstMappable, OutputProof> = self
let outputs_unique: BTreeMap<PublicKey, OutputProof> = self
.transactions
.values()
.flat_map(|tx| {
Expand Down
112 changes: 22 additions & 90 deletions src/verification.rs
Expand Up @@ -6,112 +6,40 @@
// 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, KeyImage, KeyManager, PublicKey, Result, SpentProof};
use crate::{Commitment, Error, Hash, KeyImage, KeyManager, Result, SpentProof};
use blst_ringct::ringct::RingCtTransaction;
use blsttc::Signature;
use std::collections::{BTreeMap, BTreeSet};
use std::collections::BTreeSet;

// Here we are putting transaction verification logic that is common to both
// MintNode::reissue() and Dbc::confirm_valid().
//
// It is best to have the verification logic in one place only!
//
// Note also that MintNode is server-side (mint) and Dbc is client-side (wallet).
// In a future refactor, we intend to break the code into 3 modules:
// server, client, and common. (or maybe mint, wallet, and common)
// So TransactionValidator would go into common.
// Here we are putting transaction verification logic that is beyond
// what RingCtTransaction::verify() provides.
//
// Another way to do this would be to create a NewType wrapper for RingCtTransaction.
// We can discuss if that is better or not.

pub struct TransactionVerifier {}

impl TransactionVerifier {
/// Verifies a transaction including mint signatures and spent proofs.
/// Verifies a transaction including spent proofs.
///
/// This function relies/assumes that the caller (wallet/client) obtains
/// the mint's and spentbook's public keys (held by KeyManager) in a
/// the spentbook's public keys (held by KeyManager) in a
/// trustless/verified way. ie, the caller should not simply obtain keys
/// from a MintNode directly, but must somehow verify that the MintNode is
/// from a SpentBookNode directly, but must somehow verify that the node is
/// a valid authority.
///
/// note: for spent_proofs to verify, the verifier must have/know the
/// public key of each spentbook section that recorded a tx input as spent.
/// note: for mint_sigs to verify, the verifier must have/know the
/// public key of the mint section that performed the reissue and signed tx.
pub fn verify<K: KeyManager>(
verifier: &K,
transaction: &RingCtTransaction,
mint_sigs: &BTreeMap<KeyImage, (PublicKey, Signature)>,
spent_proofs: &BTreeSet<SpentProof>,
) -> Result<(), Error> {
// Do quick checks first to reduce potential DOS vectors.

if mint_sigs.len() != transaction.mlsags.len() {
return Err(Error::MintSignatureInputMismatch);
}

// Verify that we received a mint pk/sig for each tx input.
for mlsag in transaction.mlsags.iter() {
if mint_sigs.get(&mlsag.key_image.into()).is_none() {
return Err(Error::MissingSignatureForInput);
}
}

// Obtain unique list of pk/sig, to avoid duplicate verify() calls when
// 2 or more inputs to our tx (a) were outputs of the same source tx (b).
let mint_sigs_unique: BTreeMap<Vec<u8>, (&PublicKey, &Signature)> = mint_sigs
.iter()
.map(|(_k, (pk, s))| (Self::pk_sig_bytes(pk, s), (pk, s)))
.collect();

// Verify that each unique mint signature is valid.
let tx_hash = Hash::from(transaction.hash());
for (_, (mint_key, mint_sig)) in mint_sigs_unique.iter() {
verifier
.verify(&tx_hash, mint_key, mint_sig)
.map_err(|e| Error::Signing(e.to_string()))?;
}

Self::verify_without_sigs_internal(verifier, transaction, tx_hash, spent_proofs)
}

fn pk_sig_bytes(pk: &PublicKey, sig: &Signature) -> Vec<u8> {
let mut bytes: Vec<u8> = Default::default();
bytes.extend(&pk.to_bytes());
bytes.extend(&sig.to_bytes());
bytes
}

/// Verifies a transaction including including spent proofs and excluding mint signatures.
///
/// This function relies/assumes that the caller (wallet/client) obtains
/// spentbook's public keys (held by KeyManager) in a
/// trustless/verified way. ie, the caller should not simply obtain keys
/// from a SpentBookNode directly, but must somehow verify that the
/// SpentBookNode is a valid authority.
///
/// note: for spent_proofs to verify, the verifier must have/know the
/// public key of each spentbook section that recorded a tx input as spent.
pub fn verify_without_sigs<K: KeyManager>(
verifier: &K,
transaction: &RingCtTransaction,
spent_proofs: &BTreeSet<SpentProof>,
) -> Result<(), Error> {
let tx_hash = Hash::from(transaction.hash());
Self::verify_without_sigs_internal(verifier, transaction, tx_hash, spent_proofs)
}

fn verify_without_sigs_internal<K: KeyManager>(
verifier: &K,
transaction: &RingCtTransaction,
transaction_hash: Hash,
spent_proofs: &BTreeSet<SpentProof>,
) -> Result<(), Error> {
if spent_proofs.len() != transaction.mlsags.len() {
return Err(Error::SpentProofInputMismatch);
return Err(Error::SpentProofInputLenMismatch);
}

let transaction_hash = Hash::from(transaction.hash());

// Verify that each pubkey is unique in this transaction.
let pubkey_unique: BTreeSet<KeyImage> = transaction
.outputs
Expand All @@ -122,19 +50,23 @@ impl TransactionVerifier {
return Err(Error::PublicKeyNotUniqueAcrossOutputs);
}

// Verify that each input has a corresponding valid spent proof.
//
// note: for the proofs to verify, our key_manager must have/know
// the pubkey of the spentbook section that signed the proof.
// This is a responsibility of our caller, not this crate.
// Verify that each input has a corresponding spent proof.
for spent_proof in spent_proofs.iter() {
if !transaction
.mlsags
.iter()
.any(|m| m.key_image == *spent_proof.key_image().as_ref())
.any(|m| Into::<KeyImage>::into(m.key_image) == *spent_proof.key_image())
{
return Err(Error::SpentProofInputMismatch);
return Err(Error::SpentProofInputKeyImageMismatch);
}
}

// Verify that each spent proof is valid
//
// note: for the proofs to verify, our key_manager must have/know
// the pubkey of the spentbook section that signed the proof.
// This is a responsibility of our caller, not this crate.
for spent_proof in spent_proofs.iter() {
spent_proof.verify(transaction_hash, verifier)?;
}

Expand All @@ -147,7 +79,7 @@ impl TransactionVerifier {
transaction
.mlsags
.iter()
.position(|m| m.key_image == *s.key_image().as_ref())
.position(|m| Into::<KeyImage>::into(m.key_image) == *s.key_image())
.map(|idx| (idx, s))
})
.collect();
Expand Down