Skip to content

Commit

Permalink
feat(forced-one-time-keys): derive spending key from dbc hash
Browse files Browse the repository at this point in the history
  • Loading branch information
davidrusu committed Sep 14, 2021
1 parent 72548cf commit 1e1fbd1
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 154 deletions.
6 changes: 3 additions & 3 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl TransactionBuilder {
}

pub fn build(self) -> Result<ReissueTransaction> {
let parents = BTreeSet::from_iter(self.inputs.keys().map(Dbc::name));
let parents = BTreeSet::from_iter(self.inputs.keys().map(Dbc::spending_key));
let inputs_bf_sum = self
.inputs
.values()
Expand Down Expand Up @@ -168,7 +168,7 @@ impl DbcBuilder {

// Verify that each input has a NodeSignature
for input in reissue_transaction.inputs.iter() {
if rs.mint_node_signatures.get(&input.name()).is_none() {
if rs.mint_node_signatures.get(&input.spending_key()).is_none() {
return Err(Error::ReissueShareMintNodeSignatureNotFoundForInput);
}
}
Expand Down Expand Up @@ -208,7 +208,7 @@ impl DbcBuilder {
.iter()
.map(|input| {
(
input.name(),
input.spending_key(),
(mint_public_key_set.public_key(), mint_sig.clone()),
)
})
Expand Down
74 changes: 51 additions & 23 deletions src/dbc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,49 @@
// 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::{DbcContent, DbcTransaction, Error, KeyManager, PublicKey, Result, Signature};
use crate::{
DbcContent, DbcTransaction, Error, KeyManager, PublicKey, Result, Signature, SpendingKey,
};

use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use tiny_keccak::{Hasher, Sha3};

#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct Dbc {
pub content: DbcContent,
pub transaction: DbcTransaction,
pub transaction_sigs: BTreeMap<PublicKey, (PublicKey, Signature)>,
pub transaction_sigs: BTreeMap<SpendingKey, (PublicKey, Signature)>,
}

impl Dbc {
// TODO: rename Dbc::name to Dbc::owner
pub fn spending_key(&self) -> SpendingKey {
let index = self.spending_key_index();
SpendingKey(self.content.owner.derive_child(&index))
}

// TODO: rename to owner()
pub fn name(&self) -> PublicKey {
self.content.owner
}

pub fn spending_key_index(&self) -> [u8; 32] {
let mut sha3 = Sha3::v256();

sha3.update(&self.content.hash().0);
sha3.update(&self.transaction.hash().0);

for (in_key, (mint_key, mint_sig)) in self.transaction_sigs.iter() {
sha3.update(&in_key.0.to_bytes());
sha3.update(&mint_key.to_bytes());
sha3.update(&mint_sig.to_bytes());
}

let mut hash = [0u8; 32];
sha3.finalize(&mut hash);
hash
}

// Check there exists a DbcTransaction with the output containing this Dbc
// Check there DOES NOT exist a DbcTransaction with this Dbc as parent (already minted)
pub fn confirm_valid<K: KeyManager>(&self, verifier: &K) -> Result<(), Error> {
Expand Down Expand Up @@ -94,6 +120,7 @@ mod tests {

let sig_share = dbc_owner
.secret_key_share
.derive_child(&dbc.spending_key_index())
.sign(&reissue_tx.blinded().hash());

let sig = dbc_owner
Expand All @@ -103,7 +130,7 @@ mod tests {

let request = ReissueRequest {
transaction: reissue_tx,
input_ownership_proofs: HashMap::from_iter([(dbc.name(), sig)]),
input_ownership_proofs: HashMap::from_iter([(dbc.spending_key(), sig)]),
};

Ok(request)
Expand Down Expand Up @@ -215,21 +242,19 @@ mod tests {
})
.build()?;

let sig_share = input_owner
.secret_key_share
.sign(&reissue_tx.blinded().hash());
let input_ownership_proofs = HashMap::from_iter(reissue_tx.inputs.iter().map(|input| {
let sig_share = input_owner
.secret_key_share
.derive_child(&input.spending_key_index())
.sign(&reissue_tx.blinded().hash());

let input_owner_key_set = input_owner.public_key_set.clone();
let sig = input_owner_key_set
.combine_signatures(vec![(input_owner.index, &sig_share)])
.unwrap();
let sig = input_owner
.public_key_set
.combine_signatures(vec![(input_owner.index, &sig_share)])
.unwrap();

let input_ownership_proofs = HashMap::from_iter(
reissue_tx
.inputs
.iter()
.map(|input| (input.name(), sig.clone())),
);
(input.spending_key(), sig)
}));

let reissue_request = ReissueRequest {
transaction: reissue_tx,
Expand Down Expand Up @@ -264,7 +289,7 @@ mod tests {
// add some random parents
(0..n_add_random_parents.coerce())
.into_iter()
.map(|_| rand::random::<OwnerKey>().0),
.map(|_| rand::random::<SpendingKey>()),
),
);

Expand All @@ -275,7 +300,7 @@ mod tests {
DbcContent::random_blinding_factor(),
)?;

let mut fuzzed_transaction_sigs: BTreeMap<PublicKey, (PublicKey, Signature)> =
let mut fuzzed_transaction_sigs: BTreeMap<SpendingKey, (PublicKey, Signature)> =
BTreeMap::new();

// Add valid sigs
Expand Down Expand Up @@ -307,8 +332,10 @@ mod tests {
.public_key_set
.combine_signatures(vec![trans_sig_share.threshold_crypto()])
.unwrap();
fuzzed_transaction_sigs
.insert(input.name(), (id.public_key_set.public_key(), trans_sig));
fuzzed_transaction_sigs.insert(
input.spending_key(),
(id.public_key_set.public_key(), trans_sig),
);
}
}

Expand All @@ -323,14 +350,15 @@ mod tests {
.combine_signatures(vec![wrong_msg_sig.threshold_crypto()])
.unwrap();

fuzzed_transaction_sigs.insert(input.name(), (genesis_key, wrong_msg_mint_sig));
fuzzed_transaction_sigs
.insert(input.spending_key(), (genesis_key, wrong_msg_mint_sig));
}
}

// Valid mint signatures for inputs not present in the transaction
for _ in 0..n_extra_input_sigs.coerce() {
fuzzed_transaction_sigs.insert(
rand::random::<OwnerKey>().0,
rand::random::<SpendingKey>(),
(genesis_key, mint_sig.clone()),
);
}
Expand Down
8 changes: 4 additions & 4 deletions src/dbc_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use tiny_keccak::{Hasher, Sha3};

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

pub(crate) 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.
Expand Down Expand Up @@ -90,7 +90,7 @@ impl AmountSecrets {

#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub struct DbcContent {
pub parents: BTreeSet<PublicKey>, // Parent DBC's, acts as a nonce
pub parents: BTreeSet<SpendingKey>, // Parent DBC's, acts as a nonce
pub amount_secrets_cipher: Ciphertext,
pub commitment: CompressedRistretto,
pub range_proof_bytes: Vec<u8>, // RangeProof::to_bytes() -> (2 lg n + 9) 32-byte elements, where n is # of secret bits, or 64 in our case. Gives 21 32-byte elements.
Expand All @@ -101,7 +101,7 @@ pub struct DbcContent {
impl DbcContent {
// Create a new DbcContent for signing.
pub fn new(
parents: BTreeSet<PublicKey>,
parents: BTreeSet<SpendingKey>,
amount: Amount,
owner: PublicKey,
blinding_factor: Scalar,
Expand Down Expand Up @@ -144,7 +144,7 @@ impl DbcContent {
let mut sha3 = Sha3::v256();

for parent in self.parents.iter() {
sha3.update(&parent.to_bytes());
sha3.update(&parent.0.to_bytes());
}

sha3.update(&self.amount_secrets_cipher.to_bytes());
Expand Down
22 changes: 11 additions & 11 deletions src/dbc_transaction.rs
Original file line number Diff line number Diff line change
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::{Hash, PublicKey};
use crate::{Hash, PublicKey, SpendingKey};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use tiny_keccak::{Hasher, Sha3};
Expand All @@ -16,19 +16,19 @@ use tiny_keccak::{Hasher, Sha3};
/// i.e. a Dbc can be stored anywhere, even offline.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct DbcTransaction {
pub inputs: BTreeSet<PublicKey>,
pub inputs: BTreeSet<SpendingKey>,
pub outputs: BTreeSet<PublicKey>,
}

impl DbcTransaction {
pub fn new(inputs: BTreeSet<PublicKey>, outputs: BTreeSet<PublicKey>) -> Self {
pub fn new(inputs: BTreeSet<SpendingKey>, outputs: BTreeSet<PublicKey>) -> Self {
Self { inputs, outputs }
}

pub fn hash(&self) -> Hash {
let mut sha3 = Sha3::v256();
for input in self.inputs.iter() {
sha3.update(&input.to_bytes());
sha3.update(&input.0.to_bytes());
}

for output in self.outputs.iter() {
Expand All @@ -53,24 +53,24 @@ mod tests {
#[quickcheck]
fn prop_hash_is_independent_of_order(inputs: Vec<u64>, outputs: Vec<u64>) {
// This test is here to protect us in the case that someone swaps out the BTreeSet for inputs/outputs for something else
let input_hashes: Vec<PublicKey> = inputs
let input_keys: Vec<SpendingKey> = inputs
.iter()
.map(|_| rand::random::<OwnerKey>().0)
.map(|_| rand::random::<SpendingKey>())
.collect();
let output_hashes: Vec<PublicKey> = outputs
let output_keys: Vec<PublicKey> = outputs
.iter()
.map(|_| rand::random::<OwnerKey>().0)
.collect();

let forward_hash = DbcTransaction::new(
input_hashes.iter().cloned().collect(),
output_hashes.iter().cloned().collect(),
input_keys.iter().cloned().collect(),
output_keys.iter().cloned().collect(),
)
.hash();

let reverse_hash = DbcTransaction::new(
input_hashes.into_iter().rev().collect(),
output_hashes.into_iter().rev().collect(),
input_keys.into_iter().rev().collect(),
output_keys.into_iter().rev().collect(),
)
.hash();

Expand Down
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub enum Error {
#[error("DBC already spent in transaction: {transaction:?}")]
DbcAlreadySpent {
transaction: crate::DbcTransaction,
transaction_sigs: BTreeMap<crate::PublicKey, (crate::PublicKeySet, crate::NodeSignature)>,
transaction_sigs: BTreeMap<crate::SpendingKey, (crate::PublicKeySet, crate::NodeSignature)>,
},
#[error("Genesis Input has already been spent in a different transaction")]
GenesisInputAlreadySpent,
Expand Down
18 changes: 18 additions & 0 deletions src/key_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ impl NodeSignature {

pub trait KeyManager {
type Error: std::error::Error;
fn sign_with_child_key(&self, idx: &[u8], tx_hash: &Hash)
-> Result<NodeSignature, Self::Error>;
fn sign(&self, msg_hash: &Hash) -> Result<NodeSignature, Self::Error>;
fn public_key_set(&self) -> Result<PublicKeySet, Self::Error>;
fn verify(
Expand Down Expand Up @@ -76,6 +78,14 @@ impl SimpleSigner {
fn sign<M: AsRef<[u8]>>(&self, msg: M) -> blsttc::SignatureShare {
self.secret_key_share.1.sign(msg)
}

fn derive_child(&self, index: &[u8]) -> Self {
let child_pks = self.public_key_set.derive_child(index);
let child_secret_index = self.secret_key_share.0;
let child_secret_share = self.secret_key_share.1.derive_child(index);

Self::new(child_pks, (child_secret_index, child_secret_share))
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -106,6 +116,14 @@ impl KeyManager for SimpleKeyManager {
Ok(self.signer.public_key_set())
}

fn sign_with_child_key(&self, index: &[u8], tx_hash: &Hash) -> Result<NodeSignature> {
let child_signer = self.signer.derive_child(index);
Ok(NodeSignature::new(
child_signer.index(),
child_signer.sign(tx_hash),
))
}

fn sign(&self, msg_hash: &Hash) -> Result<NodeSignature> {
Ok(NodeSignature::new(
self.signer.index(),
Expand Down
33 changes: 31 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ impl fmt::Debug for Hash {
}

impl AsRef<[u8]> for Hash {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0
}
Expand All @@ -62,17 +61,47 @@ impl AsRef<[u8]> for Hash {
#[cfg(test)]
use rand::distributions::{Distribution, Standard};

#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SpendingKey(pub PublicKey);

// 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 SpendingKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("SpendingKey")
.field(&hex::encode(self.0.to_bytes()))
.finish()
}
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct OwnerKey(pub PublicKey);

#[cfg(test)]
use rand::Rng;

// TODO: remove this
/// used when fuzzing DBC's in testing.
#[cfg(test)]
impl Distribution<OwnerKey> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> OwnerKey {
OwnerKey(crate::genesis_dbc_input().derive_child(&rng.gen::<[u8; 32]>()))
OwnerKey(
crate::genesis_dbc_input()
.0
.derive_child(&rng.gen::<[u8; 32]>()),
)
}
}

/// used when fuzzing DBC's in testing.
#[cfg(test)]
impl Distribution<SpendingKey> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> SpendingKey {
SpendingKey(
crate::genesis_dbc_input()
.0
.derive_child(&rng.gen::<[u8; 32]>()),
)
}
}

Expand Down

0 comments on commit 1e1fbd1

Please sign in to comment.