use std::collections::HashMap;
use darkfi_sdk::{
crypto::{
schnorr::{SchnorrPublic, SchnorrSecret, Signature},
PublicKey, SecretKey,
},
dark_tree::{dark_forest_leaf_vec_integrity_check, DarkForest, DarkLeaf, DarkTree},
error::DarkTreeResult,
pasta::pallas,
tx::{ContractCall, TransactionHash},
AsHex,
};
#[cfg(feature = "async-serial")]
use darkfi_serial::async_trait;
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
use log::{debug, error};
use crate::{
error::TxVerifyFailed,
zk::{proof::VerifyingKey, Proof},
Error, Result,
};
macro_rules! zip {
($x:expr) => ($x);
($x:expr, $($y:expr), +) => (
$x.iter().zip(zip!($($y), +))
)
}
#[derive(Clone, Default, Eq, PartialEq, SerialEncodable, SerialDecodable)]
pub struct Transaction {
pub calls: Vec<DarkLeaf<ContractCall>>,
pub proofs: Vec<Vec<Proof>>,
pub signatures: Vec<Vec<Signature>>,
}
impl Transaction {
pub async fn verify_zkps(
&self,
verifying_keys: &HashMap<[u8; 32], HashMap<String, VerifyingKey>>,
zkp_table: Vec<Vec<(String, Vec<pallas::Base>)>>,
) -> Result<()> {
assert_eq!(self.calls.len(), self.proofs.len());
assert_eq!(self.calls.len(), zkp_table.len());
for (call, (proofs, pubvals)) in zip!(self.calls, self.proofs, zkp_table) {
assert_eq!(proofs.len(), pubvals.len());
let Some(contract_map) = verifying_keys.get(&call.data.contract_id.to_bytes()) else {
error!(
target: "tx::verify_zkps",
"[TX] Verifying keys not found for contract {}",
call.data.contract_id,
);
return Err(TxVerifyFailed::InvalidZkProof.into())
};
for (proof, (zk_ns, public_vals)) in proofs.iter().zip(pubvals.iter()) {
if let Some(vk) = contract_map.get(zk_ns) {
debug!(target: "tx::verify_zkps", "[TX] public inputs: {:#?}", public_vals);
if let Err(e) = proof.verify(vk, public_vals) {
error!(
target: "tx::verify_zkps",
"[TX] Failed verifying {}::{} ZK proof: {:#?}",
call.data.contract_id, zk_ns, e
);
return Err(TxVerifyFailed::InvalidZkProof.into())
}
debug!(
target: "tx::verify_zkps",
"[TX] Successfully verified {}::{} ZK proof",
call.data.contract_id, zk_ns,
);
continue
}
error!(
target: "tx::verify_zkps",
"[TX] {}::{} circuit VK nonexistent",
call.data.contract_id, zk_ns,
);
return Err(TxVerifyFailed::InvalidZkProof.into())
}
}
Ok(())
}
pub fn verify_sigs(&self, pub_table: Vec<Vec<PublicKey>>) -> Result<()> {
let mut hasher = blake3::Hasher::new();
self.calls.encode(&mut hasher)?;
self.proofs.encode(&mut hasher)?;
let data_hash = hasher.finalize();
debug!(
target: "tx::verify_sigs",
"tx.verify_sigs: data_hash: {}", data_hash.as_bytes().hex(),
);
assert_eq!(self.signatures.len(), pub_table.len());
for (i, (sigs, pubkeys)) in self.signatures.iter().zip(pub_table.iter()).enumerate() {
assert_eq!(sigs.len(), pubkeys.len());
for (pubkey, signature) in pubkeys.iter().zip(sigs) {
debug!(
target: "tx::verify_sigs",
"[TX] Verifying signature with public key: {}", pubkey,
);
if !pubkey.verify(&data_hash.as_bytes()[..], signature) {
error!(
target: "tx::verify_sigs",
"[TX] tx::verify_sigs[{}] failed to verify signature", i,
);
return Err(Error::InvalidSignature)
}
}
debug!(target: "tx::verify_sigs", "[TX] tx::verify_sigs[{}] passed", i);
}
Ok(())
}
pub fn create_sigs(&self, secret_keys: &[SecretKey]) -> Result<Vec<Signature>> {
let mut hasher = blake3::Hasher::new();
self.calls.encode(&mut hasher)?;
self.proofs.encode(&mut hasher)?;
let data_hash = hasher.finalize();
debug!(
target: "tx::create_sigs",
"[TX] tx.create_sigs: data_hash: {:?}", data_hash.as_bytes().hex(),
);
let mut sigs = vec![];
for secret in secret_keys {
debug!(
target: "tx::create_sigs",
"[TX] Creating signature with public key: {}", PublicKey::from_secret(*secret),
);
let signature = secret.sign(&data_hash.as_bytes()[..]);
sigs.push(signature);
}
Ok(sigs)
}
pub fn hash(&self) -> TransactionHash {
let mut hasher = blake3::Hasher::new();
self.encode(&mut hasher).expect("blake3 hasher");
TransactionHash(hasher.finalize().into())
}
}
impl std::fmt::Debug for Transaction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Transaction {{")?;
for (i, call) in self.calls.iter().enumerate() {
writeln!(f, " Call {} {{", i)?;
writeln!(f, " contract_id: {:?}", call.data.contract_id.inner())?;
let calldata = &call.data.data;
if !calldata.is_empty() {
writeln!(f, " function_code: {}", calldata[0])?;
}
writeln!(f, " parent: {:?}", call.parent_index)?;
writeln!(f, " children: {:?}", call.children_indexes)?;
writeln!(f, " }},")?;
}
writeln!(f, "}}")
}
}
#[cfg(feature = "net")]
use crate::net::Message;
#[cfg(feature = "net")]
crate::impl_p2p_message!(Transaction, "tx");
pub const MIN_TX_CALLS: usize = 1;
pub const MAX_TX_CALLS: usize = 20;
#[derive(Clone)]
pub struct ContractCallLeaf {
pub call: ContractCall,
pub proofs: Vec<Proof>,
}
pub struct TransactionBuilder {
pub calls: DarkForest<ContractCallLeaf>,
}
impl TransactionBuilder {
pub fn new(
data: ContractCallLeaf,
children: Vec<DarkTree<ContractCallLeaf>>,
) -> DarkTreeResult<Self> {
let calls = DarkForest::new(Some(MIN_TX_CALLS), Some(MAX_TX_CALLS));
let mut self_ = Self { calls };
self_.append(data, children)?;
Ok(self_)
}
pub fn append(
&mut self,
data: ContractCallLeaf,
children: Vec<DarkTree<ContractCallLeaf>>,
) -> DarkTreeResult<()> {
let tree = DarkTree::new(data, children, None, None);
self.calls.append(tree)
}
pub fn build(&mut self) -> DarkTreeResult<Transaction> {
let leafs = self.calls.build_vec()?;
dark_forest_leaf_vec_integrity_check(&leafs, Some(MIN_TX_CALLS), Some(MAX_TX_CALLS))?;
let mut calls = Vec::with_capacity(leafs.len());
let mut proofs = Vec::with_capacity(leafs.len());
for leaf in leafs {
let call = DarkLeaf {
data: leaf.data.call,
parent_index: leaf.parent_index,
children_indexes: leaf.children_indexes,
};
calls.push(call);
proofs.push(leaf.data.proofs);
}
Ok(Transaction { calls, proofs, signatures: vec![] })
}
}