use crate::{
dbc_id::PublicAddress, transaction::DbcTransaction, DbcId, DbcSecrets, DerivationIndex,
DerivedKey, Error, FeeOutput, Hash, MainKey, Result, SignedSpend, Token,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use tiny_keccak::{Hasher, Sha3};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(custom_debug::Debug, Clone, Eq, PartialEq)]
pub struct Dbc {
pub id: DbcId,
#[debug(skip)]
pub src_tx: DbcTransaction,
#[debug(skip)]
pub secrets: DbcSecrets,
pub signed_spends: BTreeSet<SignedSpend>,
}
impl Dbc {
pub fn id(&self) -> DbcId {
self.id
}
pub fn public_address(&self) -> &PublicAddress {
&self.secrets.public_address
}
pub fn derived_key(&self, main_key: &MainKey) -> Result<DerivedKey> {
if &main_key.public_address() != self.public_address() {
return Err(Error::MainKeyDoesNotMatchPublicAddress);
}
Ok(main_key.derive_key(&self.derivation_index()))
}
pub fn derivation_index(&self) -> DerivationIndex {
self.secrets.derivation_index
}
pub fn fee_output(&self) -> &FeeOutput {
&self.src_tx.fee
}
pub fn reason(&self) -> Hash {
self.signed_spends
.iter()
.next()
.map(|c| c.reason())
.unwrap_or_default()
}
pub fn token(&self) -> Result<Token> {
Ok(self
.src_tx
.outputs
.iter()
.find(|o| &self.id() == o.dbc_id())
.ok_or(Error::OutputNotFound)?
.token)
}
pub fn hash(&self) -> Hash {
let mut sha3 = Sha3::v256();
sha3.update(self.src_tx.hash().as_ref());
sha3.update(&self.secrets.to_bytes());
for sp in self.signed_spends.iter() {
sha3.update(&sp.to_bytes());
}
sha3.update(self.reason().as_ref());
let mut hash = [0u8; 32];
sha3.finalize(&mut hash);
Hash::from(hash)
}
pub fn verify(&self, main_key: &MainKey) -> Result<(), Error> {
self.src_tx
.verify_against_inputs_spent(&self.signed_spends)?;
let dbc_id = self.derived_key(main_key)?.dbc_id();
if !self.src_tx.outputs.iter().any(|o| dbc_id.eq(o.dbc_id())) {
return Err(Error::DbcCiphersNotPresentInTransactionOutput);
}
let reason = self.reason();
let reasons_are_equal = |s: &SignedSpend| reason == s.reason();
if !self.signed_spends.iter().all(reasons_are_equal) {
return Err(Error::SignedSpendReasonMismatch(dbc_id));
}
Ok(())
}
#[cfg(feature = "serde")]
pub fn from_hex(hex: &str) -> Result<Self, Error> {
let mut bytes =
hex::decode(hex).map_err(|e| Error::HexDeserializationFailed(e.to_string()))?;
bytes.reverse();
let dbc: Dbc = bincode::deserialize(&bytes)
.map_err(|e| Error::HexDeserializationFailed(e.to_string()))?;
Ok(dbc)
}
#[cfg(feature = "serde")]
pub fn to_hex(&self) -> Result<String, Error> {
let mut serialized =
bincode::serialize(&self).map_err(|e| Error::HexSerializationFailed(e.to_string()))?;
serialized.reverse();
Ok(hex::encode(serialized))
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::{
dbc_id::random_derivation_index,
mock,
rand::{CryptoRng, RngCore},
transaction::Output,
FeeOutput, Hash, Token,
};
use blsttc::{PublicKey, SecretKey};
use std::convert::TryInto;
#[test]
fn from_hex_should_deserialize_a_hex_encoded_string_to_a_dbc() -> Result<(), Error> {
let mut rng = crate::rng::from_seed([0u8; 32]);
let amount = 1_530_000_000;
let main_key = MainKey::random_from_rng(&mut rng);
let derivation_index = random_derivation_index(&mut rng);
let derived_key = main_key.derive_key(&derivation_index);
let tx = DbcTransaction {
inputs: vec![],
outputs: vec![Output::new(derived_key.dbc_id(), amount)],
fee: FeeOutput::new(Hash::default(), 3_500, Hash::default()),
};
let secrets = DbcSecrets::from((&main_key.public_address(), &derivation_index));
let dbc = Dbc {
id: derived_key.dbc_id(),
src_tx: tx,
secrets,
signed_spends: Default::default(),
};
let hex = dbc.to_hex()?;
let dbc = Dbc::from_hex(&hex)?;
assert_eq!(dbc.token()?.as_nano(), 1_530_000_000);
let fee_amount = dbc.fee_output().token;
assert_eq!(fee_amount, Token::from_nano(3_500));
Ok(())
}
#[test]
fn to_hex_should_serialize_a_dbc_to_a_hex_encoded_string() -> Result<(), Error> {
let mut rng = crate::rng::from_seed([0u8; 32]);
let amount = 100;
let main_key = MainKey::random_from_rng(&mut rng);
let derivation_index = random_derivation_index(&mut rng);
let derived_key = main_key.derive_key(&derivation_index);
let tx = DbcTransaction {
inputs: vec![],
outputs: vec![Output::new(derived_key.dbc_id(), amount)],
fee: FeeOutput::new(Hash::default(), 2_500, Hash::default()),
};
let secrets = DbcSecrets::from((&main_key.public_address(), &derivation_index));
let dbc = Dbc {
id: derived_key.dbc_id(),
src_tx: tx,
secrets,
signed_spends: Default::default(),
};
let hex = dbc.to_hex()?;
let dbc_from_hex = Dbc::from_hex(&hex)?;
assert_eq!(dbc.token()?, dbc_from_hex.token()?);
let fee_amount = dbc.fee_output().token;
assert_eq!(fee_amount, Token::from_nano(2_500));
Ok(())
}
#[test]
fn input_should_error_if_dbc_id_is_not_derived_from_main_key() -> Result<(), Error> {
let mut rng = crate::rng::from_seed([0u8; 32]);
let (_, _, (dbc, _)) = generate_dbc_of_value_from_pk_hex(
100,
"a14a1887c61f95d5bdf6d674da3032dad77f2168fe6bf5e282aa02394bd45f41f0\
fe722b61fa94764da42a9b628701db",
&mut rng,
)?;
let sk = get_secret_key_from_hex(
"d823b03be25ad306ce2c2ef8f67d8a49322ed2a8636de5dbf01f6cc3467dc91e",
)?;
let main_key = MainKey::new(sk);
let result = dbc.derived_key(&main_key);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Main key does not match public address."
);
Ok(())
}
#[test]
fn test_dbc_without_inputs_fails_verification() -> Result<(), Error> {
let mut rng = crate::rng::from_seed([0u8; 32]);
let amount = 100;
let main_key = MainKey::random_from_rng(&mut rng);
let derivation_index = random_derivation_index(&mut rng);
let derived_key = main_key.derive_key(&derivation_index);
let tx = DbcTransaction {
inputs: vec![],
outputs: vec![Output::new(derived_key.dbc_id(), amount)],
fee: FeeOutput::default(),
};
let secrets = DbcSecrets::from((&main_key.public_address(), &derivation_index));
let dbc = Dbc {
id: derived_key.dbc_id(),
src_tx: tx,
secrets,
signed_spends: Default::default(),
};
assert!(matches!(dbc.verify(&main_key), Err(Error::MissingTxInputs)));
Ok(())
}
pub(crate) fn generate_dbc_of_value_from_pk_hex(
amount: u64,
pk_hex: &str,
rng: &mut (impl RngCore + CryptoRng),
) -> Result<(mock::SpentbookNode, Dbc, (Dbc, Dbc))> {
let pk_bytes =
hex::decode(pk_hex).map_err(|e| Error::HexDeserializationFailed(e.to_string()))?;
let pk_bytes: [u8; blsttc::PK_SIZE] = pk_bytes.try_into().unwrap_or_else(|v: Vec<u8>| {
panic!(
"Expected vec of length {} but received vec of length {}",
blsttc::PK_SIZE,
v.len()
)
});
let pk = PublicKey::from_bytes(pk_bytes)?;
let public_address = PublicAddress::new(pk);
generate_dbc_of_value(amount, public_address, rng)
}
fn generate_dbc_of_value(
amount: u64,
recipient: PublicAddress,
rng: &mut (impl RngCore + CryptoRng),
) -> Result<(mock::SpentbookNode, Dbc, (Dbc, Dbc))> {
let (mut spentbook_node, genesis_dbc, genesis_material, _) =
mock::GenesisBuilder::init_genesis_single()?;
let output_tokens = vec![
Token::from_nano(amount),
Token::from_nano(mock::GenesisMaterial::GENESIS_AMOUNT - amount),
];
let derived_key = genesis_dbc.derived_key(&genesis_material.main_key)?;
let dbc_builder = crate::TransactionBuilder::default()
.add_input_dbc(&genesis_dbc, &derived_key)
.unwrap()
.add_outputs(
output_tokens
.into_iter()
.map(|token| (token, recipient, random_derivation_index(rng))),
)
.build(Hash::default())?;
let tx = &dbc_builder.spent_tx;
for signed_spend in dbc_builder.signed_spends() {
spentbook_node.log_spent(tx, signed_spend)?
}
let mut iter = dbc_builder.build()?.into_iter();
let (starting_dbc, _) = iter.next().unwrap();
let (change_dbc, _) = iter.next().unwrap();
Ok((spentbook_node, genesis_dbc, (starting_dbc, change_dbc)))
}
fn get_secret_key_from_hex(sk_hex: &str) -> Result<SecretKey, Error> {
let sk_bytes =
hex::decode(sk_hex).map_err(|e| Error::HexDeserializationFailed(e.to_string()))?;
let mut sk_bytes: [u8; blsttc::SK_SIZE] =
sk_bytes.try_into().unwrap_or_else(|v: Vec<u8>| {
panic!(
"Expected vec of length {} but received vec of length {}",
blsttc::SK_SIZE,
v.len()
)
});
sk_bytes.reverse();
Ok(SecretKey::from_bytes(sk_bytes)?)
}
}