mod.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2025 Dyne.org foundation 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation, either version 3 of the 8 * License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Affero General Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 use std::collections::HashMap; 20 21 use darkfi_sdk::{ 22 crypto::{ 23 schnorr::{SchnorrPublic, SchnorrSecret, Signature}, 24 PublicKey, SecretKey, 25 }, 26 dark_tree::{dark_forest_leaf_vec_integrity_check, DarkForest, DarkLeaf, DarkTree}, 27 error::DarkTreeResult, 28 pasta::pallas, 29 tx::{ContractCall, TransactionHash}, 30 }; 31 32 #[cfg(feature = "async-serial")] 33 use darkfi_serial::async_trait; 34 35 use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable}; 36 use tracing::{debug, error}; 37 38 use crate::{ 39 error::TxVerifyFailed, 40 zk::{proof::VerifyingKey, Proof}, 41 Error, Result, 42 }; 43 44 macro_rules! zip { 45 ($x:expr) => ($x); 46 ($x:expr, $($y:expr), +) => ( 47 $x.iter().zip(zip!($($y), +)) 48 ) 49 } 50 51 // ANCHOR: transaction 52 /// A Transaction contains an arbitrary number of `ContractCall` objects, 53 /// along with corresponding ZK proofs and Schnorr signatures. 54 /// 55 /// `DarkLeaf` is used to map relations between contract calls in the transaction. 56 #[derive(Clone, Default, Eq, PartialEq, SerialEncodable, SerialDecodable)] 57 pub struct Transaction { 58 /// Calls executed in this transaction 59 pub calls: Vec<DarkLeaf<ContractCall>>, 60 /// Attached ZK proofs 61 pub proofs: Vec<Vec<Proof>>, 62 /// Attached Schnorr signatures 63 pub signatures: Vec<Vec<Signature>>, 64 } 65 // ANCHOR_END: transaction 66 67 impl Transaction { 68 /// Verify ZK proofs for the entire transaction. 69 pub async fn verify_zkps( 70 &self, 71 verifying_keys: &HashMap<[u8; 32], HashMap<String, VerifyingKey>>, 72 zkp_table: Vec<Vec<(String, Vec<pallas::Base>)>>, 73 ) -> Result<()> { 74 // TODO: Are we sure we should assert here? 75 assert_eq!(self.calls.len(), self.proofs.len()); 76 assert_eq!(self.calls.len(), zkp_table.len()); 77 78 for (call, (proofs, pubvals)) in zip!(self.calls, self.proofs, zkp_table) { 79 assert_eq!(proofs.len(), pubvals.len()); 80 81 let Some(contract_map) = verifying_keys.get(&call.data.contract_id.to_bytes()) else { 82 error!( 83 target: "tx::verify_zkps", 84 "[TX] Verifying keys not found for contract {}", 85 call.data.contract_id, 86 ); 87 return Err(TxVerifyFailed::InvalidZkProof.into()) 88 }; 89 90 for (proof, (zk_ns, public_vals)) in proofs.iter().zip(pubvals.iter()) { 91 if let Some(vk) = contract_map.get(zk_ns) { 92 // We have a verifying key for this 93 debug!(target: "tx::verify_zkps", "[TX] public inputs: {public_vals:#?}"); 94 if let Err(e) = proof.verify(vk, public_vals) { 95 error!( 96 target: "tx::verify_zkps", 97 "[TX] Failed verifying {}::{zk_ns} ZK proof: {e:#?}", 98 call.data.contract_id 99 ); 100 return Err(TxVerifyFailed::InvalidZkProof.into()) 101 } 102 debug!( 103 target: "tx::verify_zkps", 104 "[TX] Successfully verified {}::{zk_ns} ZK proof", 105 call.data.contract_id 106 ); 107 continue 108 } 109 110 error!( 111 target: "tx::verify_zkps", 112 "[TX] {}::{zk_ns} circuit VK nonexistent", 113 call.data.contract_id 114 ); 115 return Err(TxVerifyFailed::InvalidZkProof.into()) 116 } 117 } 118 119 Ok(()) 120 } 121 122 /// Verify Schnorr signatures for the entire transaction. 123 pub fn verify_sigs(&self, pub_table: Vec<Vec<PublicKey>>) -> Result<()> { 124 // Hash the transaction without the signatures 125 let mut hasher = blake3::Hasher::new(); 126 self.calls.encode(&mut hasher)?; 127 self.proofs.encode(&mut hasher)?; 128 let data_hash = hasher.finalize(); 129 130 debug!(target: "tx::verify_sigs", "tx.verify_sigs: data_hash: {data_hash}"); 131 132 assert_eq!(self.signatures.len(), pub_table.len()); 133 134 for (i, (sigs, pubkeys)) in self.signatures.iter().zip(pub_table.iter()).enumerate() { 135 assert_eq!(sigs.len(), pubkeys.len()); 136 137 for (pubkey, signature) in pubkeys.iter().zip(sigs) { 138 debug!(target: "tx::verify_sigs", "[TX] Verifying signature with public key: {pubkey}"); 139 if !pubkey.verify(&data_hash.as_bytes()[..], signature) { 140 error!(target: "tx::verify_sigs", "[TX] tx::verify_sigs[{i}] failed to verify signature"); 141 return Err(Error::InvalidSignature) 142 } 143 } 144 145 debug!(target: "tx::verify_sigs", "[TX] tx::verify_sigs[{i}] passed"); 146 } 147 148 Ok(()) 149 } 150 151 /// Create Schnorr signatures for the entire transaction. 152 pub fn create_sigs(&self, secret_keys: &[SecretKey]) -> Result<Vec<Signature>> { 153 // Hash the transaction without the signatures 154 let mut hasher = blake3::Hasher::new(); 155 self.calls.encode(&mut hasher)?; 156 self.proofs.encode(&mut hasher)?; 157 let data_hash = hasher.finalize(); 158 159 debug!(target: "tx::create_sigs", "[TX] tx.create_sigs: data_hash: {data_hash}"); 160 161 let mut sigs = vec![]; 162 for secret in secret_keys { 163 debug!( 164 target: "tx::create_sigs", 165 "[TX] Creating signature with public key: {}", PublicKey::from_secret(*secret), 166 ); 167 let signature = secret.sign(&data_hash.as_bytes()[..]); 168 sigs.push(signature); 169 } 170 171 Ok(sigs) 172 } 173 174 /// Get the transaction hash 175 pub fn hash(&self) -> TransactionHash { 176 let mut hasher = blake3::Hasher::new(); 177 // Blake3 hasher .update() method never fails. 178 // This call returns a Result due to how the Write trait is specified. 179 // Calling unwrap() here should be safe. 180 self.encode(&mut hasher).expect("blake3 hasher"); 181 TransactionHash(hasher.finalize().into()) 182 } 183 184 /// Returns true if transaction is a PoW reward one. 185 pub fn is_pow_reward(&self) -> bool { 186 // PoW rewards must be single contract calls 187 if !self.is_single_call() { 188 return false; 189 } 190 191 self.calls[0].data.is_money_pow_reward() 192 } 193 194 /// Returns true if the transaction consists of a single call with non-empty data. 195 pub fn is_single_call(&self) -> bool { 196 self.calls.len() == 1 && !self.calls[0].data.data.is_empty() 197 } 198 } 199 200 // Avoid showing the proofs and sigs in the debug output since often they are very long. 201 impl std::fmt::Debug for Transaction { 202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 203 writeln!(f, "Transaction {{")?; 204 for (i, call) in self.calls.iter().enumerate() { 205 writeln!(f, " Call {i} {{")?; 206 writeln!(f, " contract_id: {:?}", call.data.contract_id.inner())?; 207 let calldata = &call.data.data; 208 if !calldata.is_empty() { 209 writeln!(f, " function_code: {}", calldata[0])?; 210 } 211 writeln!(f, " parent: {:?}", call.parent_index)?; 212 writeln!(f, " children: {:?}", call.children_indexes)?; 213 writeln!(f, " }},")?; 214 } 215 writeln!(f, "}}") 216 } 217 } 218 219 #[cfg(feature = "net")] 220 use crate::{ 221 net::{metering::MeteringConfiguration, Message}, 222 util::time::NanoTimestamp, 223 }; 224 225 #[cfg(feature = "net")] 226 // TODO: Fine tune 227 // Since messages are asynchronous we will define loose rules to prevent spamming. 228 // Each message score will be 1, with a threshold of 100 and expiry time of 5. 229 // We are not limiting `Transaction` size. 230 crate::impl_p2p_message!( 231 Transaction, 232 "tx", 233 0, 234 1, 235 MeteringConfiguration { 236 threshold: 100, 237 sleep_step: 500, 238 expiry_time: NanoTimestamp::from_secs(5), 239 } 240 ); 241 242 /// Calls tree bounds definitions 243 // TODO: increase min to 2 when fees are implement 244 pub const MIN_TX_CALLS: usize = 1; 245 // TODO: verify max value 246 pub const MAX_TX_CALLS: usize = 20; 247 248 /// Auxiliarry structure containing all the information 249 /// required to execute a contract call. 250 #[derive(Clone)] 251 pub struct ContractCallLeaf { 252 /// Call executed 253 pub call: ContractCall, 254 /// Attached ZK proofs 255 pub proofs: Vec<Proof>, 256 } 257 258 /// Auxiliary structure to build a full [`Transaction`] using 259 /// [`DarkTree`] to order everything. 260 pub struct TransactionBuilder { 261 /// Contract calls trees forest 262 pub calls: DarkForest<ContractCallLeaf>, 263 } 264 265 // TODO: for now we build the trees manually, but we should 266 // add all the proper functions for easier building. 267 impl TransactionBuilder { 268 /// Initialize the builder, using provided data to 269 /// generate its initial [`DarkTree`] root. 270 pub fn new( 271 data: ContractCallLeaf, 272 children: Vec<DarkTree<ContractCallLeaf>>, 273 ) -> DarkTreeResult<Self> { 274 let calls = DarkForest::new(Some(MIN_TX_CALLS), Some(MAX_TX_CALLS)); 275 let mut self_ = Self { calls }; 276 self_.append(data, children)?; 277 Ok(self_) 278 } 279 280 /// Append a new call tree to the forest 281 pub fn append( 282 &mut self, 283 data: ContractCallLeaf, 284 children: Vec<DarkTree<ContractCallLeaf>>, 285 ) -> DarkTreeResult<()> { 286 let tree = DarkTree::new(data, children, None, None); 287 self.calls.append(tree) 288 } 289 290 /// Builder builds the calls vector using the [`DarkForest`] 291 /// and generates the corresponding [`Transaction`]. 292 pub fn build(&mut self) -> DarkTreeResult<Transaction> { 293 // Build the leafs vector 294 let leafs = self.calls.build_vec()?; 295 296 // Double check integrity 297 dark_forest_leaf_vec_integrity_check(&leafs, Some(MIN_TX_CALLS), Some(MAX_TX_CALLS))?; 298 299 // Build the corresponding transaction 300 let mut calls = Vec::with_capacity(leafs.len()); 301 let mut proofs = Vec::with_capacity(leafs.len()); 302 for leaf in leafs { 303 let call = DarkLeaf { 304 data: leaf.data.call, 305 parent_index: leaf.parent_index, 306 children_indexes: leaf.children_indexes, 307 }; 308 calls.push(call); 309 proofs.push(leaf.data.proofs); 310 } 311 312 Ok(Transaction { calls, proofs, signatures: vec![] }) 313 } 314 }