/ src / tx / mod.rs
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  }