| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- use std::any::{Any, TypeId};
- use darkfi_sdk::crypto::{MerkleNode, Nullifier};
- use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
- use incrementalmerkletree::Tree;
- use log::{debug, error};
- use pasta_curves::{group::Group, pallas};
- use darkfi::{
- crypto::{
- coin::Coin,
- keypair::PublicKey,
- types::{DrkCircuitField, DrkTokenId, DrkValueBlind, DrkValueCommit},
- util::{pedersen_commitment_base, pedersen_commitment_u64},
- BurnRevealedValues, MintRevealedValues,
- },
- Error as DarkFiError,
- };
- use crate::{
- contract::{
- dao_contract,
- money_contract::{state::State, CONTRACT_ID},
- },
- note::EncryptedNote2,
- util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
- };
- const TARGET: &str = "money_contract::transfer::validate::state_transition()";
- /// A struct representing a state update.
- /// This gets applied on top of an existing state.
- #[derive(Clone)]
- pub struct Update {
- /// All nullifiers in a transaction
- pub nullifiers: Vec<Nullifier>,
- /// All coins in a transaction
- pub coins: Vec<Coin>,
- /// All encrypted notes in a transaction
- pub enc_notes: Vec<EncryptedNote2>,
- }
- impl UpdateBase for Update {
- fn apply(mut self: Box<Self>, states: &mut StateRegistry) {
- let state = states.lookup_mut::<State>(*CONTRACT_ID).unwrap();
- // Extend our list of nullifiers with the ones from the update
- state.nullifiers.append(&mut self.nullifiers);
- //// Update merkle tree and witnesses
- for (coin, enc_note) in self.coins.into_iter().zip(self.enc_notes.into_iter()) {
- // Add the new coins to the Merkle tree
- let node = MerkleNode::from(coin.0);
- state.tree.append(&node);
- // Keep track of all Merkle roots that have existed
- state.merkle_roots.push(state.tree.root(0).unwrap());
- state.wallet_cache.try_decrypt_note(coin, enc_note, &mut state.tree);
- }
- }
- }
- pub fn state_transition(
- states: &StateRegistry,
- func_call_index: usize,
- parent_tx: &Transaction,
- ) -> Result<Box<dyn UpdateBase + Send>> {
- // Check the public keys in the clear inputs to see if they're coming
- // from a valid cashier or faucet.
- debug!(target: TARGET, "Iterate clear_inputs");
- let func_call = &parent_tx.func_calls[func_call_index];
- let call_data = func_call.call_data.as_any();
- assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
- let call_data = call_data.downcast_ref::<CallData>();
- // This will be inside wasm so unwrap is fine.
- let call_data = call_data.unwrap();
- let state = states.lookup::<State>(*CONTRACT_ID).expect("Return type is not of type State");
- // Code goes here
- for (i, input) in call_data.clear_inputs.iter().enumerate() {
- let pk = &input.signature_public;
- // TODO: this depends on the token ID
- if !state.is_valid_cashier_public_key(pk) && !state.is_valid_faucet_public_key(pk) {
- error!(target: TARGET, "Invalid pubkey for clear input: {:?}", pk);
- return Err(Error::VerifyFailed(VerifyFailed::InvalidCashierOrFaucetKey(i)))
- }
- }
- // Nullifiers in the transaction
- let mut nullifiers = Vec::with_capacity(call_data.inputs.len());
- debug!(target: TARGET, "Iterate inputs");
- for (i, input) in call_data.inputs.iter().enumerate() {
- let merkle = &input.revealed.merkle_root;
- // The Merkle root is used to know whether this is a coin that
- // existed in a previous state.
- if !state.is_valid_merkle(merkle) {
- error!(target: TARGET, "Invalid Merkle root (input {})", i);
- debug!(target: TARGET, "root: {:?}", merkle);
- return Err(Error::VerifyFailed(VerifyFailed::InvalidMerkle(i)))
- }
- // Check the spend_hook is satisfied
- // The spend_hook says a coin must invoke another contract function when being spent
- // If the value is set, then we check the function call exists
- let spend_hook = &input.revealed.spend_hook;
- if spend_hook != &pallas::Base::from(0) {
- // spend_hook is set so we enforce the rules
- let mut is_found = false;
- for (i, func_call) in parent_tx.func_calls.iter().enumerate() {
- // Skip current func_call
- if i == func_call_index {
- continue
- }
- // TODO: we need to change these to pallas::Base
- // temporary workaround for now
- // if func_call.func_id == spend_hook ...
- if func_call.func_id == *dao_contract::exec::FUNC_ID {
- is_found = true;
- break
- }
- }
- if !is_found {
- return Err(Error::VerifyFailed(VerifyFailed::SpendHookNotSatisfied))
- }
- }
- // The nullifiers should not already exist.
- // It is the double-spend protection.
- let nullifier = &input.revealed.nullifier;
- if state.nullifier_exists(nullifier) ||
- (1..nullifiers.len()).any(|i| nullifiers[i..].contains(&nullifiers[i - 1]))
- {
- error!(target: TARGET, "Duplicate nullifier found (input {})", i);
- debug!(target: TARGET, "nullifier: {:?}", nullifier);
- return Err(Error::VerifyFailed(VerifyFailed::NullifierExists(i)))
- }
- nullifiers.push(input.revealed.nullifier);
- }
- debug!(target: TARGET, "Verifying call data");
- match call_data.verify() {
- Ok(()) => {
- debug!(target: TARGET, "Verified successfully")
- }
- Err(e) => {
- error!(target: TARGET, "Failed verifying zk proofs: {}", e);
- return Err(Error::VerifyFailed(VerifyFailed::ProofVerifyFailed(e.to_string())))
- }
- }
- // Newly created coins for this transaction
- let mut coins = Vec::with_capacity(call_data.outputs.len());
- let mut enc_notes = Vec::with_capacity(call_data.outputs.len());
- for output in &call_data.outputs {
- // Gather all the coins
- coins.push(output.revealed.coin);
- enc_notes.push(output.enc_note.clone());
- }
- Ok(Box::new(Update { nullifiers, coins, enc_notes }))
- }
- /// A DarkFi transaction
- #[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
- pub struct CallData {
- /// Clear inputs
- pub clear_inputs: Vec<ClearInput>,
- /// Anonymous inputs
- pub inputs: Vec<Input>,
- /// Anonymous outputs
- pub outputs: Vec<Output>,
- }
- impl CallDataBase for CallData {
- fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
- let mut public_values = Vec::new();
- for input in &self.inputs {
- public_values.push(("money-transfer-burn".to_string(), input.revealed.make_outputs()));
- }
- for output in &self.outputs {
- public_values.push(("money-transfer-mint".to_string(), output.revealed.make_outputs()));
- }
- public_values
- }
- fn as_any(&self) -> &dyn Any {
- self
- }
- fn signature_public_keys(&self) -> Vec<PublicKey> {
- let mut signature_public_keys = Vec::new();
- for input in self.clear_inputs.clone() {
- signature_public_keys.push(input.signature_public);
- }
- signature_public_keys
- }
- fn encode_bytes(
- &self,
- mut writer: &mut dyn std::io::Write,
- ) -> core::result::Result<usize, std::io::Error> {
- self.encode(&mut writer)
- }
- }
- impl CallData {
- /// Verify the transaction
- pub fn verify(&self) -> VerifyResult<()> {
- // must have minimum 1 clear or anon input, and 1 output
- if self.clear_inputs.len() + self.inputs.len() == 0 {
- error!("tx::verify(): Missing inputs");
- return Err(VerifyFailed::LackingInputs)
- }
- if self.outputs.is_empty() {
- error!("tx::verify(): Missing outputs");
- return Err(VerifyFailed::LackingOutputs)
- }
- // Accumulator for the value commitments
- let mut valcom_total = DrkValueCommit::identity();
- // Add values from the clear inputs
- for input in &self.clear_inputs {
- valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
- }
- // Add values from the inputs
- for input in &self.inputs {
- valcom_total += &input.revealed.value_commit;
- }
- // Subtract values from the outputs
- for output in &self.outputs {
- valcom_total -= &output.revealed.value_commit;
- }
- // If the accumulator is not back in its initial state,
- // there's a value mismatch.
- if valcom_total != DrkValueCommit::identity() {
- error!("tx::verify(): Missing funds");
- return Err(VerifyFailed::MissingFunds)
- }
- // Verify that the token commitments match
- if !self.verify_token_commitments() {
- error!("tx::verify(): Token ID mismatch");
- return Err(VerifyFailed::TokenMismatch)
- }
- Ok(())
- }
- fn verify_token_commitments(&self) -> bool {
- assert_ne!(self.outputs.len(), 0);
- let token_commit_value = self.outputs[0].revealed.token_commit;
- let mut failed =
- self.inputs.iter().any(|input| input.revealed.token_commit != token_commit_value);
- failed = failed ||
- self.outputs.iter().any(|output| output.revealed.token_commit != token_commit_value);
- failed = failed ||
- self.clear_inputs.iter().any(|input| {
- pedersen_commitment_base(input.token_id, input.token_blind) != token_commit_value
- });
- !failed
- }
- }
- /// A transaction's clear input
- #[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
- pub struct ClearInput {
- /// Input's value (amount)
- pub value: u64,
- /// Input's token ID
- pub token_id: DrkTokenId,
- /// Blinding factor for `value`
- pub value_blind: DrkValueBlind,
- /// Blinding factor for `token_id`
- pub token_blind: DrkValueBlind,
- /// Public key for the signature
- pub signature_public: PublicKey,
- }
- /// A transaction's anonymous input
- #[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
- pub struct Input {
- /// Public inputs for the zero-knowledge proof
- pub revealed: BurnRevealedValues,
- }
- /// A transaction's anonymous output
- #[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
- pub struct Output {
- /// Public inputs for the zero-knowledge proof
- pub revealed: MintRevealedValues,
- /// The encrypted note
- pub enc_note: EncryptedNote2,
- }
- #[derive(Debug, Clone, thiserror::Error)]
- pub enum Error {
- #[error(transparent)]
- VerifyFailed(#[from] VerifyFailed),
- #[error("DarkFi error: {0}")]
- DarkFiError(String),
- }
- /// Transaction verification errors
- #[derive(Debug, Clone, thiserror::Error)]
- pub enum VerifyFailed {
- #[error("Transaction has no inputs")]
- LackingInputs,
- #[error("Transaction has no outputs")]
- LackingOutputs,
- #[error("Invalid cashier/faucet public key for clear input {0}")]
- InvalidCashierOrFaucetKey(usize),
- #[error("Invalid Merkle root for input {0}")]
- InvalidMerkle(usize),
- #[error("Spend hook invoking function is not attached")]
- SpendHookNotSatisfied,
- #[error("Nullifier already exists for input {0}")]
- NullifierExists(usize),
- #[error("Token commitments in inputs or outputs to not match")]
- TokenMismatch,
- #[error("Money in does not match money out (value commitments)")]
- MissingFunds,
- #[error("Failed verifying zk proofs: {0}")]
- ProofVerifyFailed(String),
- #[error("Internal error: {0}")]
- InternalError(String),
- #[error("DarkFi error: {0}")]
- DarkFiError(String),
- }
- type Result<T> = std::result::Result<T, Error>;
- impl From<Error> for VerifyFailed {
- fn from(err: Error) -> Self {
- Self::InternalError(err.to_string())
- }
- }
- impl From<DarkFiError> for VerifyFailed {
- fn from(err: DarkFiError) -> Self {
- Self::DarkFiError(err.to_string())
- }
- }
- impl From<DarkFiError> for Error {
- fn from(err: DarkFiError) -> Self {
- Self::DarkFiError(err.to_string())
- }
- }
- /// Result type used in transaction verifications
- pub type VerifyResult<T> = std::result::Result<T, VerifyFailed>;
|