Просмотр исходного кода

contract/money: Refactor and cleanup.

Tests probably broken.
parazyd 3 лет назад
Родитель
Сommit
e9ffa10541

+ 1 - 0
src/contract/money/Cargo.toml

@@ -11,6 +11,7 @@ crate-type = ["cdylib", "rlib"]
 [dependencies]
 darkfi-sdk = { path = "../../sdk" }
 darkfi-serial = { path = "../../serial", features = ["derive", "crypto"] }
+thiserror = "1.0.38"
 
 # The following dependencies are used for the client API and
 # probably shouldn't be in WASM

+ 0 - 10
src/contract/money/proof/token_freeze_v1.zk

@@ -1,16 +1,10 @@
 constant "TokenFreeze_V1" {
-	EcFixedPointShort VALUE_COMMIT_VALUE,
-	EcFixedPoint VALUE_COMMIT_RANDOM,
 	EcFixedPointBase NULLIFIER_K,
 }
 
 contract "TokenFreeze_V1" {
 	# Token mint authority secret
 	Base mint_authority,
-	# Leaf position in the Merkle tree of tokens
-	Uint32 leaf_pos,
-	# Merkle authentication path
-	MerklePath path,
 }
 
 circuit "TokenFreeze_V1" {
@@ -24,8 +18,4 @@ circuit "TokenFreeze_V1" {
 	# Derive the token ID
 	token_id = poseidon_hash(mint_x, mint_y);
 	constrain_instance(token_id);
-
-	# Prove that this token was minted first
-	root = merkle_root(leaf_pos, path, token_id);
-	constrain_instance(root);
 }

+ 0 - 1335
src/contract/money/src/client.rs

@@ -1,1335 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-//! This module implements the client-side of this contract's interaction.
-//! What we basically do here is implement an API that creates the necessary
-//! structures and is able to export them to create a DarkFi Transaction
-//! object that can be broadcasted to the network when we want to make a
-//! payment with some coins in our wallet.
-//! Note that this API doesn't involve any wallet interaction, but only
-//! takes the necessary objects provided by the caller. This is so we can
-//! abstract away the wallet interface to client implementations.
-
-use chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, KeyInit};
-use darkfi::{
-    consensus::LeadCoin,
-    zk::{Proof, ProvingKey, Witness, ZkCircuit},
-    zkas::ZkBinary,
-    ClientFailed, Error, Result,
-};
-use darkfi_sdk::crypto::{
-    diffie_hellman::{kdf_sapling, sapling_ka_agree},
-    merkle_prelude::*,
-    pallas,
-    pasta_prelude::*,
-    pedersen_commitment_base, pedersen_commitment_u64, poseidon_hash, Keypair, MerkleNode,
-    MerklePosition, MerkleTree, Nullifier, PublicKey, SecretKey, TokenId, ValueBlind, ValueCommit,
-};
-use darkfi_serial::{serialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
-use halo2_proofs::circuit::Value;
-use log::{debug, error, info};
-use rand::rngs::OsRng;
-
-use crate::model::{
-    ClearInput, Input, MoneyStakeParams, MoneyTransferParams, MoneyUnstakeParams, Output,
-    StakedInput, StakedOutput,
-};
-
-/// Client API for token minting and freezing
-pub mod token_mint;
-
-// Wallet SQL table constant names. These have to represent the SQL schema.
-// TODO: They should also ideally be prefixed with the contract ID to avoid
-//       collisions.
-pub const MONEY_INFO_TABLE: &str = "money_info";
-pub const MONEY_INFO_COL_LAST_SCANNED_SLOT: &str = "last_scanned_slot";
-
-pub const MONEY_TREE_TABLE: &str = "money_tree";
-pub const MONEY_TREE_COL_TREE: &str = "tree";
-
-pub const MONEY_KEYS_TABLE: &str = "money_keys";
-pub const MONEY_KEYS_COL_KEY_ID: &str = "key_id";
-pub const MONEY_KEYS_COL_IS_DEFAULT: &str = "is_default";
-pub const MONEY_KEYS_COL_PUBLIC: &str = "public";
-pub const MONEY_KEYS_COL_SECRET: &str = "secret";
-
-pub const MONEY_COINS_TABLE: &str = "money_coins";
-pub const MONEY_COINS_COL_COIN: &str = "coin";
-pub const MONEY_COINS_COL_IS_SPENT: &str = "is_spent";
-pub const MONEY_COINS_COL_SERIAL: &str = "serial";
-pub const MONEY_COINS_COL_VALUE: &str = "value";
-pub const MONEY_COINS_COL_TOKEN_ID: &str = "token_id";
-pub const MONEY_COINS_COL_SPEND_HOOK: &str = "spend_hook";
-pub const MONEY_COINS_COL_USER_DATA: &str = "user_data";
-pub const MONEY_COINS_COL_COIN_BLIND: &str = "coin_blind";
-pub const MONEY_COINS_COL_VALUE_BLIND: &str = "value_blind";
-pub const MONEY_COINS_COL_TOKEN_BLIND: &str = "token_blind";
-pub const MONEY_COINS_COL_SECRET: &str = "secret";
-pub const MONEY_COINS_COL_NULLIFIER: &str = "nullifier";
-pub const MONEY_COINS_COL_LEAF_POSITION: &str = "leaf_position";
-pub const MONEY_COINS_COL_MEMO: &str = "memo";
-
-pub const MONEY_TOKENS_TABLE: &str = "money_tokens";
-pub const MONEY_TOKENS_COL_SECRET: &str = "secret";
-pub const MONEY_TOKENS_COL_TOKEN_ID: &str = "token_id";
-pub const MONEY_TOKENS_COL_FROZEN: &str = "frozen";
-
-pub const MONEY_ALIASES_TABLE: &str = "money_aliases";
-pub const MONEY_ALIASES_COL_ALIAS: &str = "alias";
-pub const MONEY_ALIASES_COL_TOKEN_ID: &str = "token_id";
-
-/// Byte length of the AEAD tag of the chacha20 cipher used for note encryption
-pub const AEAD_TAG_SIZE: usize = 16;
-
-/// The `Coin` is represented as a base field element.
-#[derive(Debug, Clone, Copy, Eq, PartialEq, SerialEncodable, SerialDecodable)]
-pub struct Coin(pub pallas::Base);
-
-impl Coin {
-    /// Reference the raw inner base field element
-    pub fn inner(&self) -> pallas::Base {
-        self.0
-    }
-
-    /// Try to create a `Coin` type from the given 32 bytes.
-    /// Returns an error if the bytes don't fit in the base field.
-    pub fn from_bytes(bytes: [u8; 32]) -> Result<Self> {
-        match pallas::Base::from_repr(bytes).into() {
-            Some(v) => Ok(Self(v)),
-            None => Err(Error::CoinFromBytes),
-        }
-    }
-}
-
-impl From<pallas::Base> for Coin {
-    fn from(x: pallas::Base) -> Self {
-        Self(x)
-    }
-}
-
-/// The `OwnCoin` is a representation of `Coin` with its respective metadata.
-#[derive(Debug, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
-pub struct OwnCoin {
-    /// The coin hash
-    pub coin: Coin,
-    /// The attached Note
-    pub note: Note,
-    /// Coin's secret key
-    pub secret: SecretKey,
-    /// Coin's nullifier,
-    pub nullifier: Nullifier,
-    /// Coin's leaf position in the Merkle tree of coins
-    pub leaf_position: MerklePosition,
-}
-
-/// The `Note` holds the inner attributes of a `Coin`
-#[derive(Debug, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
-pub struct Note {
-    /// Serial number of the coin, used for the nullifier
-    pub serial: pallas::Base,
-    /// Value of the coin
-    pub value: u64,
-    /// Token ID of the coin
-    pub token_id: TokenId,
-    /// Spend hook used for protocol owned liquidity.
-    /// Specifies which contract owns this coin.
-    pub spend_hook: pallas::Base,
-    /// User data used by protocol when spend hook is enabled.
-    pub user_data: pallas::Base,
-    /// Blinding factor for the coin bulla
-    pub coin_blind: pallas::Base,
-    /// Blinding factor for the value pedersen commitment
-    pub value_blind: ValueBlind,
-    /// Blinding factor for the token ID pedersen commitment
-    pub token_blind: ValueBlind,
-    /// Attached memo (arbitrary data)
-    pub memo: Vec<u8>,
-}
-
-impl Note {
-    /// Encrypt the note to some given `PublicKey` using an AEAD cipher.
-    pub fn encrypt(&self, public_key: &PublicKey) -> Result<EncryptedNote> {
-        let ephem_keypair = Keypair::random(&mut OsRng);
-        let shared_secret = sapling_ka_agree(&ephem_keypair.secret, public_key);
-        let key = kdf_sapling(&shared_secret, &ephem_keypair.public);
-
-        let mut input = vec![];
-        self.encode(&mut input)?;
-        let input_len = input.len();
-
-        let mut ciphertext = vec![0_u8; input_len + AEAD_TAG_SIZE];
-        ciphertext[..input_len].copy_from_slice(&input);
-
-        ChaCha20Poly1305::new(key.as_ref().into())
-            .encrypt_in_place([0u8; 12][..].into(), &[], &mut ciphertext)
-            .unwrap();
-
-        Ok(EncryptedNote { ciphertext, ephem_public: ephem_keypair.public })
-    }
-}
-
-/// The `EncryptedNote` represents a structure holding the ciphertext (which is
-/// an encryption of the `Note` object, and the ephemeral `PublicKey` created at
-/// the time when the encryption was done
-#[derive(Debug, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
-pub struct EncryptedNote {
-    /// Ciphertext of the encrypted `Note`
-    pub ciphertext: Vec<u8>,
-    /// Ephemeral public key created at the time of encrypting the note
-    pub ephem_public: PublicKey,
-}
-
-impl EncryptedNote {
-    /// Attempt to decrypt an `EncryptedNote` given a secret key.
-    pub fn decrypt(&self, secret: &SecretKey) -> Result<Note> {
-        let shared_secret = sapling_ka_agree(secret, &self.ephem_public);
-        let key = kdf_sapling(&shared_secret, &self.ephem_public);
-
-        let ciphertext_len = self.ciphertext.len();
-        let mut plaintext = vec![0_u8; ciphertext_len];
-        plaintext.copy_from_slice(&self.ciphertext);
-
-        match ChaCha20Poly1305::new(key.as_ref().into()).decrypt_in_place(
-            [0u8; 12][..].into(),
-            &[],
-            &mut plaintext,
-        ) {
-            Ok(()) => Ok(Note::decode(&plaintext[..ciphertext_len - AEAD_TAG_SIZE])?),
-            Err(e) => Err(Error::NoteDecryptionFailed(e.to_string())),
-        }
-    }
-}
-
-// TODO: we can put all these in an internal module like:
-// money_transfer::builder::ClearInputInfo
-
-struct TransactionBuilderClearInputInfo {
-    pub value: u64,
-    pub token_id: TokenId,
-    pub signature_secret: SecretKey,
-}
-
-struct TransactionBuilderInputInfo {
-    pub leaf_position: MerklePosition,
-    pub merkle_path: Vec<MerkleNode>,
-    pub secret: SecretKey,
-    pub note: Note,
-}
-
-struct TransactionBuilderOutputInfo {
-    pub value: u64,
-    pub token_id: TokenId,
-    pub public_key: PublicKey,
-}
-
-pub struct TransferBurnRevealed {
-    pub value_commit: ValueCommit,
-    pub token_commit: ValueCommit,
-    pub nullifier: Nullifier,
-    pub merkle_root: MerkleNode,
-    pub spend_hook: pallas::Base,
-    pub user_data_enc: pallas::Base,
-    pub signature_public: PublicKey,
-}
-
-impl TransferBurnRevealed {
-    #[allow(clippy::too_many_arguments)]
-    pub fn compute(
-        value: u64,
-        token_id: TokenId,
-        value_blind: ValueBlind,
-        token_blind: ValueBlind,
-        serial: pallas::Base,
-        spend_hook: pallas::Base,
-        user_data: pallas::Base,
-        user_data_blind: pallas::Base,
-        coin_blind: pallas::Base,
-        secret_key: SecretKey,
-        leaf_position: MerklePosition,
-        merkle_path: Vec<MerkleNode>,
-        signature_secret: SecretKey,
-    ) -> Self {
-        let nullifier = Nullifier::from(poseidon_hash([secret_key.inner(), serial]));
-
-        let public_key = PublicKey::from_secret(secret_key);
-        let (pub_x, pub_y) = public_key.xy();
-
-        let coin = poseidon_hash([
-            pub_x,
-            pub_y,
-            pallas::Base::from(value),
-            token_id.inner(),
-            serial,
-            spend_hook,
-            user_data,
-            coin_blind,
-        ]);
-
-        let merkle_root = {
-            let position: u64 = leaf_position.into();
-            let mut current = MerkleNode::from(coin);
-            for (level, sibling) in merkle_path.iter().enumerate() {
-                let level = level as u8;
-                current = if position & (1 << level) == 0 {
-                    MerkleNode::combine(level.into(), &current, sibling)
-                } else {
-                    MerkleNode::combine(level.into(), sibling, &current)
-                };
-            }
-            current
-        };
-
-        let user_data_enc = poseidon_hash([user_data, user_data_blind]);
-
-        let value_commit = pedersen_commitment_u64(value, value_blind);
-        let token_commit = pedersen_commitment_base(token_id.inner(), token_blind);
-
-        Self {
-            value_commit,
-            token_commit,
-            nullifier,
-            merkle_root,
-            spend_hook,
-            user_data_enc,
-            signature_public: PublicKey::from_secret(signature_secret),
-        }
-    }
-
-    pub fn to_vec(&self) -> Vec<pallas::Base> {
-        let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
-        let tokcom_coords = self.token_commit.to_affine().coordinates().unwrap();
-        let sigpub_coords = self.signature_public.inner().to_affine().coordinates().unwrap();
-
-        // NOTE: It's important to keep this order the same as the `constrain_instance`
-        //       calls in the zkas code.
-        vec![
-            self.nullifier.inner(),
-            *valcom_coords.x(),
-            *valcom_coords.y(),
-            *tokcom_coords.x(),
-            *tokcom_coords.y(),
-            self.merkle_root.inner(),
-            self.user_data_enc,
-            *sigpub_coords.x(),
-            *sigpub_coords.y(),
-            // TODO: Why is spend_hook in the struct but not here?
-        ]
-    }
-}
-
-pub struct TransferMintRevealed {
-    pub coin: Coin,
-    pub value_commit: ValueCommit,
-    pub token_commit: ValueCommit,
-}
-
-impl TransferMintRevealed {
-    #[allow(clippy::too_many_arguments)]
-    pub fn compute(
-        value: u64,
-        token_id: TokenId,
-        value_blind: ValueBlind,
-        token_blind: ValueBlind,
-        serial: pallas::Base,
-        spend_hook: pallas::Base,
-        user_data: pallas::Base,
-        coin_blind: pallas::Base,
-        public_key: PublicKey,
-    ) -> Self {
-        let value_commit = pedersen_commitment_u64(value, value_blind);
-        let token_commit = pedersen_commitment_base(token_id.inner(), token_blind);
-
-        let (pub_x, pub_y) = public_key.xy();
-
-        let coin = Coin::from(poseidon_hash([
-            pub_x,
-            pub_y,
-            pallas::Base::from(value),
-            token_id.inner(),
-            serial,
-            spend_hook,
-            user_data,
-            coin_blind,
-        ]));
-
-        Self { coin, value_commit, token_commit }
-    }
-
-    pub fn to_vec(&self) -> Vec<pallas::Base> {
-        let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
-        let tokcom_coords = self.token_commit.to_affine().coordinates().unwrap();
-
-        // NOTE: It's important to keep this order the same as the `constrain_instance`
-        //       calls in the zkas code.
-        vec![
-            self.coin.inner(),
-            *valcom_coords.x(),
-            *valcom_coords.y(),
-            *tokcom_coords.x(),
-            *tokcom_coords.y(),
-        ]
-    }
-}
-
-#[allow(clippy::too_many_arguments)]
-pub fn create_transfer_mint_proof(
-    zkbin: &ZkBinary,
-    pk: &ProvingKey,
-    value: u64,
-    token_id: TokenId,
-    value_blind: ValueBlind,
-    token_blind: ValueBlind,
-    serial: pallas::Base,
-    spend_hook: pallas::Base,
-    user_data: pallas::Base,
-    coin_blind: pallas::Base,
-    public_key: PublicKey,
-) -> Result<(Proof, TransferMintRevealed)> {
-    let revealed = TransferMintRevealed::compute(
-        value,
-        token_id,
-        value_blind,
-        token_blind,
-        serial,
-        spend_hook,
-        user_data,
-        coin_blind,
-        public_key,
-    );
-
-    let (pub_x, pub_y) = public_key.xy();
-
-    // NOTE: It's important to keep these in the same order as the zkas code.
-    let prover_witnesses = vec![
-        Witness::Base(Value::known(pub_x)),
-        Witness::Base(Value::known(pub_y)),
-        Witness::Base(Value::known(pallas::Base::from(value))),
-        Witness::Base(Value::known(token_id.inner())),
-        Witness::Base(Value::known(serial)),
-        Witness::Base(Value::known(coin_blind)),
-        Witness::Base(Value::known(spend_hook)),
-        Witness::Base(Value::known(user_data)),
-        Witness::Scalar(Value::known(value_blind)),
-        Witness::Scalar(Value::known(token_blind)),
-    ];
-
-    let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
-    let proof = Proof::create(pk, &[circuit], &revealed.to_vec(), &mut OsRng)?;
-
-    Ok((proof, revealed))
-}
-
-#[allow(clippy::too_many_arguments)]
-pub fn create_transfer_burn_proof(
-    zkbin: &ZkBinary,
-    pk: &ProvingKey,
-    value: u64,
-    token_id: TokenId,
-    value_blind: ValueBlind,
-    token_blind: ValueBlind,
-    serial: pallas::Base,
-    spend_hook: pallas::Base,
-    user_data: pallas::Base,
-    user_data_blind: pallas::Base,
-    coin_blind: pallas::Base,
-    secret_key: SecretKey,
-    leaf_position: MerklePosition,
-    merkle_path: Vec<MerkleNode>,
-    signature_secret: SecretKey,
-) -> Result<(Proof, TransferBurnRevealed)> {
-    let revealed = TransferBurnRevealed::compute(
-        value,
-        token_id,
-        value_blind,
-        token_blind,
-        serial,
-        spend_hook,
-        user_data,
-        user_data_blind,
-        coin_blind,
-        secret_key,
-        leaf_position,
-        merkle_path.clone(),
-        signature_secret,
-    );
-
-    // NOTE: It's important to keep these in the same order as the zkas code.
-    let prover_witnesses = vec![
-        Witness::Base(Value::known(pallas::Base::from(value))),
-        Witness::Base(Value::known(token_id.inner())),
-        Witness::Scalar(Value::known(value_blind)),
-        Witness::Scalar(Value::known(token_blind)),
-        Witness::Base(Value::known(serial)),
-        Witness::Base(Value::known(spend_hook)),
-        Witness::Base(Value::known(user_data)),
-        Witness::Base(Value::known(user_data_blind)),
-        Witness::Base(Value::known(coin_blind)),
-        Witness::Base(Value::known(secret_key.inner())),
-        Witness::Uint32(Value::known(u64::from(leaf_position).try_into().unwrap())),
-        Witness::MerklePath(Value::known(merkle_path.try_into().unwrap())),
-        Witness::Base(Value::known(signature_secret.inner())),
-    ];
-
-    let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
-    let proof = Proof::create(pk, &[circuit], &revealed.to_vec(), &mut OsRng)?;
-
-    Ok((proof, revealed))
-}
-
-struct StakeLeadMintRevealed {
-    pub value_commit: ValueCommit,
-    pub pk: pallas::Base,
-    pub commitment_x: pallas::Base,
-    pub commitment_y: pallas::Base,
-}
-
-impl StakeLeadMintRevealed {
-    pub fn compute(
-        value: pallas::Base,
-        pk: pallas::Base,
-        value_blind: pallas::Scalar,
-        commitment: pallas::Point,
-    ) -> Self {
-        let value_commit = pedersen_commitment_base(value, value_blind);
-        let coord = commitment.to_affine().coordinates().unwrap();
-        Self { value_commit, pk, commitment_x: *coord.x(), commitment_y: *coord.y() }
-    }
-    pub fn to_vec(&self) -> Vec<pallas::Base> {
-        let value_coord = self.value_commit.to_affine().coordinates().unwrap();
-        let value_cm_x = *value_coord.x();
-        let value_cm_y = *value_coord.y();
-        vec![value_cm_x, value_cm_y, self.pk, self.commitment_x, self.commitment_y]
-    }
-}
-
-fn create_stake_mint_proof(
-    zkbin: &ZkBinary, // LeadMint contract binary
-    pk: &ProvingKey,
-    public_key: pallas::Base,
-    coin_commitment: pallas::Point,
-    value: pallas::Base,
-    value_blind: ValueBlind,
-    coin_blind: ValueBlind,
-    sk: pallas::Base,
-    sk_root: pallas::Base,
-    tau: pallas::Base,
-    nonce: pallas::Base, // rho
-) -> Result<(Proof, StakeLeadMintRevealed)> {
-    let revealed = StakeLeadMintRevealed::compute(value, public_key, value_blind, coin_commitment);
-
-    let prover_witnesses = vec![
-        Witness::Base(Value::known(sk)),
-        Witness::Base(Value::known(sk_root)),
-        Witness::Base(Value::known(tau)),
-        Witness::Base(Value::known(nonce)),
-        Witness::Scalar(Value::known(coin_blind)),
-        Witness::Base(Value::known(value)),
-        Witness::Scalar(Value::known(value_blind)),
-    ];
-    let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
-    let proof = Proof::create(pk, &[circuit], &revealed.to_vec(), &mut OsRng)?;
-
-    Ok((proof, revealed))
-}
-
-struct UnstakeLeadBurnRevealed {
-    pub value_commit: ValueCommit,
-    pub pk: pallas::Base,
-    pub commitment_x: pallas::Base,
-    pub commitment_y: pallas::Base,
-    pub commitment_root: pallas::Base,
-    pub sk_root: pallas::Base,
-    pub nullifier: pallas::Base,
-}
-
-impl UnstakeLeadBurnRevealed {
-    pub fn compute(
-        value: pallas::Base,
-        value_blind: ValueBlind,
-        pk: pallas::Base,
-        commitment: pallas::Point,
-        commitment_root: pallas::Base,
-        sk_root: pallas::Base,
-        nullifier: pallas::Base,
-    ) -> Self {
-        let value_commit = pedersen_commitment_base(value, value_blind);
-        let coord = commitment.to_affine().coordinates().unwrap();
-        let commitment_x = *coord.x();
-        let commitment_y = *coord.y();
-        Self { value_commit, pk, commitment_x, commitment_y, commitment_root, sk_root, nullifier }
-    }
-
-    pub fn to_vec(&self) -> Vec<pallas::Base> {
-        let coord = self.value_commit.to_affine().coordinates().unwrap();
-        let value_cm_x = *coord.x();
-        let value_cm_y = *coord.y();
-        vec![
-            value_cm_x,
-            value_cm_y,
-            self.pk,
-            self.commitment_x,
-            self.commitment_y,
-            self.commitment_root,
-            self.sk_root,
-            self.nullifier,
-        ]
-    }
-}
-
-fn create_unstake_burn_proof(
-    zkbin: &ZkBinary,
-    pk: &ProvingKey,
-    value: pallas::Base,
-    value_blind: ValueBlind,
-    coin_blind: ValueBlind,
-    public_key: pallas::Base,
-    sk: pallas::Base,
-    sk_root: pallas::Base,
-    sk_pos: MerklePosition,
-    sk_path: Vec<MerkleNode>,
-    commitment_merkle_path: Vec<MerkleNode>,
-    commitment: pallas::Point,
-    commitment_root: pallas::Base,
-    commitment_pos: MerklePosition,
-    slot: u64,
-    nonce: pallas::Base,
-    nullifier: pallas::Base,
-) -> Result<(Proof, UnstakeLeadBurnRevealed)> {
-    let revealed = UnstakeLeadBurnRevealed::compute(
-        value,
-        value_blind,
-        public_key,
-        commitment,
-        commitment_root,
-        sk_root,
-        nullifier,
-    );
-
-    let prover_witnesses = vec![
-        Witness::MerklePath(Value::known(commitment_merkle_path.try_into().unwrap())),
-        Witness::Uint32(Value::known(u64::from(commitment_pos).try_into().unwrap())), // u32
-        Witness::Uint32(Value::known(u64::from(sk_pos).try_into().unwrap())),         // u32
-        Witness::Base(Value::known(sk)),
-        Witness::Base(Value::known(sk_root)),
-        Witness::MerklePath(Value::known(sk_path.try_into().unwrap())),
-        Witness::Base(Value::known(pallas::Base::from(slot))),
-        Witness::Base(Value::known(nonce)),
-        Witness::Scalar(Value::known(coin_blind)),
-        Witness::Base(Value::known(value)),
-        Witness::Scalar(Value::known(value_blind)),
-    ];
-    let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
-    let proof = Proof::create(pk, &[circuit], &revealed.to_vec(), &mut OsRng)?;
-
-    Ok((proof, revealed))
-}
-
-/// Build half of the money contract OTC swap transaction parameters with the given data:
-/// * `value_send` - Amount to send
-/// * `token_id_send` - Token ID to send
-/// * `value_recv` - Amount to receive
-/// * `token_id_recv` - Token ID to receive
-/// * `value_blinds` - Value blinds to use if we're the second half
-/// * `token_blinds` - Token blinds to use if we're the second half
-/// * `coins` - Set of coins we're able to spend
-/// * `tree` - Current Merkle tree of coins
-/// * `mint_zkbin` - ZkBinary of the mint circuit
-/// * `mint_pk` - Proving key for the ZK mint proof
-/// * `burn_zkbin` - ZkBinary of the burn circuit
-/// * `burn_pk` - Proving key for the ZK burn proof
-#[allow(clippy::too_many_arguments)]
-#[allow(clippy::type_complexity)]
-pub fn build_half_swap_tx(
-    pubkey: &PublicKey,
-    value_send: u64,
-    token_id_send: TokenId,
-    value_recv: u64,
-    token_id_recv: TokenId,
-    value_blinds: &[ValueBlind],
-    token_blinds: &[ValueBlind],
-    coins: &[OwnCoin],
-    tree: &MerkleTree,
-    mint_zkbin: &ZkBinary,
-    mint_pk: &ProvingKey,
-    burn_zkbin: &ZkBinary,
-    burn_pk: &ProvingKey,
-) -> Result<(
-    MoneyTransferParams,
-    Vec<Proof>,
-    Vec<SecretKey>,
-    Vec<OwnCoin>,
-    Vec<ValueBlind>,
-    Vec<ValueBlind>,
-)> {
-    debug!(target: "money", "Building OTC swap transaction half");
-    assert!(value_send != 0);
-    assert!(value_recv != 0);
-    assert!(!coins.is_empty());
-
-    debug!(target: "money", "Money::build_half_swap_tx(): Building anonymous inputs");
-    // We'll take any coin that has correct value
-    let Some(coin) = coins.iter().find(|x| x.note.value == value_send && x.note.token_id == token_id_send) else {
-        error!(target: "money", "Money::build_half_swap_tx(): Did not find a coin with enough value to swap");
-        return Err(ClientFailed::NotEnoughValue(value_send).into())
-    };
-
-    let leaf_position = coin.leaf_position;
-    let root = tree.root(0).unwrap();
-    let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
-
-    let input = TransactionBuilderInputInfo {
-        leaf_position,
-        merkle_path,
-        secret: coin.secret,
-        note: coin.note.clone(),
-    };
-
-    let spent_coins = vec![coin.clone()];
-
-    let output = TransactionBuilderOutputInfo {
-        value: value_recv,
-        token_id: token_id_recv,
-        public_key: *pubkey,
-    };
-
-    // We now fill this with necessary stuff
-    let mut params = MoneyTransferParams { clear_inputs: vec![], inputs: vec![], outputs: vec![] };
-
-    let val_blinds: Vec<ValueBlind>;
-    let tok_blinds: Vec<ValueBlind>;
-
-    // If we got non-empty `value_blinds` passed into this function, we use them here.
-    // They should be sent to the second party by the swap initiator.
-    let (value_send_blind, value_recv_blind) = {
-        if value_blinds.is_empty() {
-            let value_send_blind = ValueBlind::random(&mut OsRng);
-            let value_recv_blind = ValueBlind::random(&mut OsRng);
-            val_blinds = vec![value_send_blind, value_recv_blind];
-            (value_send_blind, value_recv_blind)
-        } else {
-            val_blinds = vec![value_blinds[1], value_blinds[0]];
-            (value_blinds[1], value_blinds[0])
-        }
-    };
-
-    // The same goes for token blinds
-    let (token_send_blind, token_recv_blind) = {
-        if token_blinds.is_empty() {
-            let token_send_blind = ValueBlind::random(&mut OsRng);
-            let token_recv_blind = ValueBlind::random(&mut OsRng);
-            tok_blinds = vec![token_send_blind, token_recv_blind];
-            (token_send_blind, token_recv_blind)
-        } else {
-            tok_blinds = vec![token_blinds[1], token_blinds[0]];
-            (token_blinds[1], token_blinds[0])
-        }
-    };
-
-    // The ephemeral secret key we're using here.
-    let signature_secret = SecretKey::random(&mut OsRng);
-
-    // Disable composability for this old obsolete API
-    let spend_hook = pallas::Base::zero();
-    let user_data = pallas::Base::zero();
-    let user_data_blind = pallas::Base::random(&mut OsRng);
-
-    let mut zk_proofs = vec![];
-
-    info!(target: "money", "Creating swap burn proof for input 0");
-    let (proof, revealed) = create_transfer_burn_proof(
-        burn_zkbin,
-        burn_pk,
-        input.note.value,
-        input.note.token_id,
-        value_send_blind,
-        token_send_blind,
-        input.note.serial,
-        spend_hook,
-        user_data,
-        user_data_blind,
-        input.note.coin_blind,
-        input.secret,
-        input.leaf_position,
-        input.merkle_path,
-        signature_secret,
-    )?;
-
-    params.inputs.push(Input {
-        value_commit: revealed.value_commit,
-        token_commit: revealed.token_commit,
-        nullifier: revealed.nullifier,
-        merkle_root: revealed.merkle_root,
-        spend_hook: revealed.spend_hook,
-        user_data_enc: revealed.user_data_enc,
-        signature_public: revealed.signature_public,
-    });
-
-    zk_proofs.push(proof);
-
-    let serial = pallas::Base::random(&mut OsRng);
-    let coin_blind = pallas::Base::random(&mut OsRng);
-
-    // Disable composability for this old obsolete API
-    let spend_hook = pallas::Base::zero();
-    let user_data = pallas::Base::zero();
-
-    info!(target: "money", "Creating swap mint proof for output 0");
-    let (proof, revealed) = create_transfer_mint_proof(
-        mint_zkbin,
-        mint_pk,
-        output.value,
-        output.token_id,
-        value_recv_blind,
-        token_recv_blind,
-        serial,
-        spend_hook,
-        user_data,
-        coin_blind,
-        output.public_key,
-    )?;
-
-    zk_proofs.push(proof);
-
-    // Encrypted note
-    let note = Note {
-        serial,
-        value: output.value,
-        token_id: output.token_id,
-        spend_hook: pallas::Base::zero(),
-        user_data: pallas::Base::zero(),
-        coin_blind,
-        value_blind: value_recv_blind,
-        token_blind: token_recv_blind,
-        // Here we store our secret key we use for signing
-        memo: serialize(&signature_secret),
-    };
-
-    let encrypted_note = note.encrypt(&output.public_key)?;
-
-    params.outputs.push(Output {
-        value_commit: revealed.value_commit,
-        token_commit: revealed.token_commit,
-        coin: revealed.coin.inner(),
-        ciphertext: encrypted_note.ciphertext,
-        ephem_public: encrypted_note.ephem_public,
-    });
-
-    // Now we should have all the params, zk proofs, and signature secrets.
-    // We return it all and let the caller deal with it.
-    Ok((params, zk_proofs, vec![signature_secret], spent_coins, val_blinds, tok_blinds))
-}
-
-/// Build money contract transfer transaction parameters with the given data:
-/// * `keypair` - Caller's keypair
-/// * `pubkey` - Public key of the recipient
-/// * `value` - Value of the transfer
-/// * `token_id` - Token ID to transfer
-/// * `spend_hook` - Spend hook
-/// * `user_data` - User data
-/// * `user_data_blind` - Blinding for user data
-/// * `coins` - Set of coins we're able to spend
-/// * `tree` - Current Merkle tree of coins
-/// * `mint_zkbin` - ZkBinary of the mint circuit
-/// * `mint_pk` - Proving key for the ZK mint proof
-/// * `burn_zkbin` - ZkBinary of the burn circuit
-/// * `burn_pk` - Proving key for the ZK burn proof
-/// * `clear_input` - Marks if we're creating clear or anonymous inputs
-#[allow(clippy::too_many_arguments)]
-#[allow(clippy::type_complexity)]
-pub fn build_transfer_tx(
-    keypair: &Keypair,
-    pubkey: &PublicKey,
-    value: u64,
-    token_id: TokenId,
-    spend_hook: pallas::Base,
-    user_data: pallas::Base,
-    user_data_blind: pallas::Base,
-    coins: &[OwnCoin],
-    tree: &MerkleTree,
-    mint_zkbin: &ZkBinary,
-    mint_pk: &ProvingKey,
-    burn_zkbin: &ZkBinary,
-    burn_pk: &ProvingKey,
-    clear_input: bool,
-) -> Result<(MoneyTransferParams, Vec<Proof>, Vec<SecretKey>, Vec<OwnCoin>)> {
-    debug!(target: "money", "Building money contract transfer transaction");
-    assert!(value != 0);
-    if !clear_input {
-        assert!(!coins.is_empty());
-    }
-    // Ensure the coins given to us are all of the same token_id.
-    // The money contract base transfer doesn't allow conversions.
-    for coin in coins.iter() {
-        assert_eq!(token_id, coin.note.token_id);
-    }
-
-    let mut clear_inputs = vec![];
-    let mut inputs = vec![];
-    let mut outputs = vec![];
-    let mut change_outputs = vec![];
-    let mut spent_coins = vec![];
-
-    if clear_input {
-        debug!(target: "money", "Money::build_transfer_tx(): Building clear input");
-        let input =
-            TransactionBuilderClearInputInfo { value, token_id, signature_secret: keypair.secret };
-        clear_inputs.push(input);
-    } else {
-        debug!(target: "money", "Money::build_transfer_tx(): Building anonymous inputs");
-        let mut inputs_value = 0;
-        for coin in coins.iter() {
-            if inputs_value >= value {
-                debug!(target: "money", "inputs_value >= value");
-                break
-            }
-
-            let leaf_position = coin.leaf_position;
-            let root = tree.root(0).unwrap();
-            let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
-            inputs_value += coin.note.value;
-
-            let input = TransactionBuilderInputInfo {
-                leaf_position,
-                merkle_path,
-                secret: coin.secret,
-                note: coin.note.clone(),
-            };
-
-            inputs.push(input);
-            spent_coins.push(coin.clone());
-        }
-
-        if inputs_value < value {
-            error!(target: "money", "Money::build_transfer_tx(): Not enough value to build tx inputs");
-            return Err(ClientFailed::NotEnoughValue(inputs_value).into())
-        }
-
-        if inputs_value > value {
-            let return_value = inputs_value - value;
-            change_outputs.push(TransactionBuilderOutputInfo {
-                value: return_value,
-                token_id,
-                public_key: keypair.public,
-            });
-        }
-
-        debug!(target: "money", "Money::build_transfer_tx(): Finished building inputs");
-    }
-
-    outputs.push(TransactionBuilderOutputInfo { value, token_id, public_key: *pubkey });
-    assert!(clear_inputs.len() + inputs.len() > 0);
-
-    // We now fill this with necessary stuff
-    let mut params = MoneyTransferParams { clear_inputs: vec![], inputs: vec![], outputs: vec![] };
-
-    // I assumed this vec will contain a secret key for each clear input and anonymous input.
-    let mut signature_secrets = vec![];
-
-    let token_blind = ValueBlind::random(&mut OsRng);
-    for input in clear_inputs {
-        // TODO: FIXME: What to do with this signature secret?
-        let signature_public = PublicKey::from_secret(input.signature_secret);
-        signature_secrets.push(input.signature_secret);
-        let value_blind = ValueBlind::random(&mut OsRng);
-
-        params.clear_inputs.push(ClearInput {
-            value: input.value,
-            token_id: input.token_id,
-            value_blind,
-            token_blind,
-            signature_public,
-        });
-    }
-
-    let mut input_blinds = vec![];
-    let mut output_blinds = vec![];
-    let mut zk_proofs = vec![];
-
-    for (i, input) in inputs.iter().enumerate() {
-        let value_blind = ValueBlind::random(&mut OsRng);
-        input_blinds.push(value_blind);
-
-        let signature_secret = SecretKey::random(&mut OsRng);
-        signature_secrets.push(signature_secret);
-
-        info!(target: "money", "Creating transfer burn proof for input {}", i);
-        let (proof, revealed) = create_transfer_burn_proof(
-            burn_zkbin,
-            burn_pk,
-            input.note.value,
-            input.note.token_id,
-            value_blind,
-            token_blind,
-            input.note.serial,
-            pallas::Base::zero(),
-            pallas::Base::zero(),
-            user_data_blind, // <-- FIXME: This api needs rework to support normal and DAO transfers
-            input.note.coin_blind,
-            input.secret,
-            input.leaf_position,
-            input.merkle_path.clone(),
-            signature_secret,
-        )?;
-
-        params.inputs.push(Input {
-            value_commit: revealed.value_commit,
-            token_commit: revealed.token_commit,
-            nullifier: revealed.nullifier,
-            merkle_root: revealed.merkle_root,
-            spend_hook: revealed.spend_hook,
-            user_data_enc: revealed.user_data_enc,
-            signature_public: revealed.signature_public,
-        });
-
-        zk_proofs.push(proof);
-    }
-
-    // This value_blind calc assumes there will always be at least a single output
-    assert!(!outputs.is_empty());
-
-    for (i, output) in change_outputs.iter().chain(outputs.iter()).enumerate() {
-        let value_blind = if i == outputs.len() + change_outputs.len() - 1 {
-            compute_remainder_blind(&params.clear_inputs, &input_blinds, &output_blinds)
-        } else {
-            ValueBlind::random(&mut OsRng)
-        };
-
-        output_blinds.push(value_blind);
-
-        let serial = pallas::Base::random(&mut OsRng);
-        let coin_blind = pallas::Base::random(&mut OsRng);
-
-        // A hacky way to zeroize spend hooks for the change outputs
-        let (scoped_spend_hook, scoped_user_data) = {
-            if i >= change_outputs.len() {
-                (spend_hook, user_data)
-            } else {
-                (pallas::Base::zero(), pallas::Base::zero())
-            }
-        };
-
-        info!(target: "money", "Creating transfer mint proof for output {}", i);
-        let (proof, revealed) = create_transfer_mint_proof(
-            mint_zkbin,
-            mint_pk,
-            output.value,
-            output.token_id,
-            value_blind,
-            token_blind,
-            serial,
-            scoped_spend_hook,
-            scoped_user_data,
-            coin_blind,
-            output.public_key,
-        )?;
-
-        zk_proofs.push(proof);
-
-        // Encrypted note
-        let note = Note {
-            serial,
-            value: output.value,
-            token_id: output.token_id,
-            spend_hook: scoped_spend_hook,
-            user_data: scoped_user_data,
-            coin_blind,
-            value_blind,
-            token_blind,
-            // NOTE: Perhaps pass in memos to this entire function with
-            //       VecDeque and then pop front to add here.
-            memo: vec![],
-        };
-
-        let encrypted_note = note.encrypt(&output.public_key)?;
-
-        params.outputs.push(Output {
-            value_commit: revealed.value_commit,
-            token_commit: revealed.token_commit,
-            coin: revealed.coin.inner(),
-            ciphertext: encrypted_note.ciphertext,
-            ephem_public: encrypted_note.ephem_public,
-        })
-    }
-
-    // Now we should have all the params, zk proofs, and signature secrets.
-    // We return it all and let the caller deal with it.
-    Ok((params, zk_proofs, signature_secrets, spent_coins))
-}
-
-pub fn build_stake_tx(
-    //pubkey: &PublicKey,
-    coins: &[OwnCoin],
-    tx_tree: &mut MerkleTree,
-    cm_tree: &mut MerkleTree,
-    sk_tree: &mut MerkleTree,
-    mint_zkbin: &ZkBinary,
-    mint_pk: &ProvingKey,
-    burn_zkbin: &ZkBinary,
-    burn_pk: &ProvingKey,
-    slot_index: u64,
-) -> Result<(MoneyStakeParams, Vec<Proof>, Vec<LeadCoin>, Vec<ValueBlind>, Vec<ValueBlind>)> {
-    // convert owncoins to leadcoins.
-    // TODO: verify this token blind usage
-    let token_blind = ValueBlind::random(&mut OsRng);
-    let mut leadcoins: Vec<LeadCoin> = vec![];
-    let mut params = MoneyStakeParams { inputs: vec![], outputs: vec![], token_blind };
-    let mut proofs = vec![];
-    let mut own_blinds = vec![];
-    let mut lead_blinds = vec![];
-    for coin in coins.iter() {
-        // burn the coin
-        let value_blind = ValueBlind::random(&mut OsRng);
-        own_blinds.push(value_blind);
-        let spend_hook = pallas::Base::zero();
-        let user_data = pallas::Base::zero();
-        let user_data_blind = pallas::Base::random(&mut OsRng);
-        let tx_leaf_position = coin.leaf_position;
-        let tx_root = tx_tree.root(0).unwrap();
-        let tx_merkle_path = tx_tree.authentication_path(tx_leaf_position, &tx_root).unwrap();
-        let signature_secret = SecretKey::random(&mut OsRng);
-        //signature_secrets.push(signature_secret);
-        let (own_proof, own_revealed) = create_transfer_burn_proof(
-            burn_zkbin,
-            burn_pk,
-            coin.note.value,
-            coin.note.token_id,
-            coin.note.value_blind,
-            coin.note.token_blind,
-            coin.note.serial,
-            spend_hook,
-            user_data,
-            user_data_blind,
-            coin.note.coin_blind,
-            coin.secret,
-            coin.leaf_position,
-            tx_merkle_path.clone(),
-            signature_secret,
-        )?;
-        params.inputs.push(Input {
-            value_commit: own_revealed.value_commit,
-            token_commit: own_revealed.token_commit,
-            nullifier: own_revealed.nullifier,
-            merkle_root: own_revealed.merkle_root,
-            spend_hook: own_revealed.spend_hook,
-            user_data_enc: own_revealed.user_data_enc,
-            signature_public: own_revealed.signature_public,
-        });
-        proofs.push(own_proof);
-        let lead_value_blind = ValueBlind::random(&mut OsRng);
-        lead_blinds.push(lead_value_blind);
-        sk_tree.append(&MerkleNode::from(coin.secret.inner()));
-        let sk_pos = sk_tree.witness().unwrap();
-        let sk_root = sk_tree.root(0).unwrap();
-        let sk_merkle_path = sk_tree.authentication_path(sk_pos, &sk_root).unwrap();
-        let leadcoin = LeadCoin::new(
-            coin.note.value,
-            slot_index,          // tau
-            coin.secret.inner(), // coin secret key
-            sk_root,
-            sk_pos.try_into().unwrap(),
-            sk_merkle_path,
-            coin.note.serial,
-            cm_tree,
-        );
-        leadcoins.push(leadcoin.clone());
-        let lead_coin_blind = ValueBlind::random(&mut OsRng);
-        let public_key = leadcoin.pk();
-        let (lead_proof, lead_revealed) = create_stake_mint_proof(
-            mint_zkbin,
-            mint_pk,
-            public_key,
-            leadcoin.coin1_commitment,
-            pallas::Base::from(coin.note.value),
-            lead_value_blind,
-            lead_coin_blind,
-            coin.secret.inner(),
-            sk_root.inner(),
-            pallas::Base::from(slot_index), // tau
-            coin.note.serial,               // nonce
-        )?;
-        let coin_commit_coords = [lead_revealed.commitment_x, lead_revealed.commitment_y];
-        let coin_commit_hash = poseidon_hash(coin_commit_coords);
-        params.outputs.push(StakedOutput {
-            value_commit: lead_revealed.value_commit,
-            coin_commit_hash,
-            coin_pk_hash: public_key,
-        });
-        proofs.push(lead_proof);
-    }
-    Ok((params, proofs, leadcoins, own_blinds, lead_blinds))
-}
-
-pub fn build_unstake_tx(
-    pubkey: &PublicKey, //recepient of owncoin public key
-    token_id_recv: TokenId,
-    coins: &[LeadCoin],
-    mint_zkbin: &ZkBinary, // stake own mint binary
-    mint_pk: &ProvingKey,
-    burn_zkbin: &ZkBinary, // unstake lead burn binary
-    burn_pk: &ProvingKey,
-) -> Result<(MoneyUnstakeParams, Vec<Proof>, Vec<SecretKey>, Vec<ValueBlind>, Vec<ValueBlind>)> {
-    // convert leadcoin to owncoin
-    // TODO: verify this token blind usage
-    let token_blind = ValueBlind::random(&mut OsRng);
-    //let owncoins : Vec<OwnCoin>= vec![];
-    let mut params = MoneyUnstakeParams { inputs: vec![], outputs: vec![], token_blind };
-    let mut proofs = vec![];
-    let mut own_blinds = vec![];
-    let mut lead_blinds = vec![];
-    for coin in coins.iter() {
-        // burn lead coin
-        let value_blind = ValueBlind::random(&mut OsRng);
-        lead_blinds.push(value_blind);
-        let pk = coin.pk();
-        let nullifier = coin.sn();
-        let (unstake_proof, unstake_revealed) = create_unstake_burn_proof(
-            burn_zkbin,
-            burn_pk,
-            pallas::Base::from(coin.value),
-            value_blind,
-            coin.coin1_blind,
-            pk,
-            coin.coin1_sk,
-            coin.coin1_sk_root.inner(),
-            MerklePosition::from(coin.coin1_sk_pos as usize),
-            coin.coin1_sk_merkle_path.to_vec(),
-            coin.coin1_commitment_merkle_path.to_vec(),
-            coin.coin1_commitment,
-            coin.coin1_commitment_root.inner(),
-            MerklePosition::from(coin.coin1_commitment_pos as usize),
-            coin.slot,
-            coin.nonce,
-            nullifier,
-        )?;
-        let commitment_coord = [unstake_revealed.commitment_x, unstake_revealed.commitment_y];
-        let coin_commitment_hash = poseidon_hash(commitment_coord);
-        params.inputs.push(StakedInput {
-            nullifier: nullifier.into(),
-            value_commit: unstake_revealed.value_commit,
-            coin_commit_hash: coin_commitment_hash,
-            coin_pk_hash: unstake_revealed.pk,
-            coin_commit_root: unstake_revealed.commitment_root.into(),
-            sk_root: unstake_revealed.sk_root.into(),
-        });
-        proofs.push(unstake_proof);
-        let own_value_blind = ValueBlind::random(&mut OsRng);
-        own_blinds.push(own_value_blind);
-        // mint own coin
-        let serial = pallas::Base::random(&mut OsRng);
-        let coin_blind = pallas::Base::random(&mut OsRng);
-        let token_recv_blind = ValueBlind::random(&mut OsRng);
-        // Disable composability for this old obsolete API
-        let spend_hook = pallas::Base::zero();
-        let user_data = pallas::Base::zero();
-        let (proof, revealed) = create_transfer_mint_proof(
-            mint_zkbin,
-            mint_pk,
-            coin.value,
-            token_id_recv,
-            own_value_blind,
-            token_recv_blind,
-            serial,
-            spend_hook,
-            user_data,
-            coin_blind,
-            *pubkey, //receipient public_key
-        )?;
-        proofs.push(proof);
-        // Encrypted note
-        let note = Note {
-            serial,
-            value: coin.value,
-            token_id: token_id_recv,
-            spend_hook: pallas::Base::zero(),
-            user_data: pallas::Base::zero(),
-            coin_blind,
-            value_blind,
-            token_blind: token_recv_blind,
-            // Here we store our secret key we use for signing
-            memo: vec![],
-        };
-
-        let encrypted_note = note.encrypt(&pubkey)?;
-
-        params.outputs.push(Output {
-            value_commit: revealed.value_commit,
-            token_commit: revealed.token_commit,
-            coin: revealed.coin.inner(),
-            ciphertext: encrypted_note.ciphertext,
-            ephem_public: encrypted_note.ephem_public,
-        });
-    }
-    Ok((params, proofs, vec![], lead_blinds, own_blinds))
-}
-
-fn compute_remainder_blind(
-    clear_inputs: &[ClearInput],
-    input_blinds: &[ValueBlind],
-    output_blinds: &[ValueBlind],
-) -> ValueBlind {
-    let mut total = ValueBlind::zero();
-
-    for input in clear_inputs {
-        total += input.value_blind;
-    }
-
-    for input_blind in input_blinds {
-        total += input_blind
-    }
-
-    for output_blind in output_blinds {
-        total -= output_blind;
-    }
-
-    total
-}
-
-#[cfg(test)]
-mod tests {
-    use darkfi_sdk::pasta::group::ff::Field;
-
-    use super::*;
-
-    #[test]
-    fn test_note_encdec() {
-        let note = Note {
-            serial: pallas::Base::random(&mut OsRng),
-            value: 100,
-            token_id: TokenId::from(pallas::Base::random(&mut OsRng)),
-            spend_hook: pallas::Base::zero(),
-            user_data: pallas::Base::zero(),
-            coin_blind: pallas::Base::random(&mut OsRng),
-            value_blind: pallas::Scalar::random(&mut OsRng),
-            token_blind: pallas::Scalar::random(&mut OsRng),
-            memo: vec![32, 223, 231, 3, 1, 1],
-        };
-
-        let keypair = Keypair::random(&mut OsRng);
-
-        let encrypted_note = note.encrypt(&keypair.public).unwrap();
-        let note2 = encrypted_note.decrypt(&keypair.secret).unwrap();
-        assert_eq!(note.serial, note2.serial);
-        assert_eq!(note.value, note2.value);
-        assert_eq!(note.token_id, note2.token_id);
-        assert_eq!(note.coin_blind, note2.coin_blind);
-        assert_eq!(note.value_blind, note2.value_blind);
-        assert_eq!(note.token_blind, note2.token_blind);
-        assert_eq!(note.memo, note2.memo);
-        assert_eq!(note, note2);
-    }
-}

+ 0 - 97
src/contract/money/src/client/token_mint.rs

@@ -1,97 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-//! This API is experimental. Have to evaluate the best approach still.
-
-pub struct MoneyMintClient {
-    pub mint_authority: SecretKey,
-    pub amount: u64,
-    pub recipient: PublicKey,
-    pub spend_hook: pallas::Base,
-    pub user_data: pallas::Base,
-}
-
-impl MoneyMintClient {
-    pub fn build(&self) -> Result<MoneyMintParams> {
-        debug!(target: "money::client::token_mint", "Building params");
-        assert!(self.amount != 0);
-
-        let token_id = TokenId::derive(self.mint_authority);
-        let value_blind = pallas::Scalar::random(&mut OsRng);
-        let token_blind = pallas::Scalar::random(&mut OsRng);
-
-        let serial = pallas::Base::random(&mut OsRng);
-        let coin_blind = pallas::Base::random(&mut OsRng);
-
-        let (pub_x, pub_y) = self.recipient.xy();
-
-        // Create the clear input
-        let input = ClearInput {
-            value: self.amount,
-            token_id,
-            value_blind,
-            token_blind,
-            signature_public: PublicKey::from_secret(mint_authority),
-        };
-
-        // Create the anonymous output
-        let coin = poseidon_hash([
-            pub_x,
-            pub_y,
-            pallas::Base::from(self.amount),
-            token_id.inner(),
-            serial,
-            self.spend_hook,
-            self.user_data,
-            coin_blind,
-        ]);
-
-        let note = MoneyNote {
-            serial,
-            value: self.amount,
-            token_id,
-            spend_hook,
-            user_data,
-            coin_blind,
-            value_blind,
-            token_blind,
-            memo: vec![],
-        };
-
-        let encrypted_note = note.encrypt(&self.recipient)?;
-
-        let output = Output {
-            value_commit: pedersen_commitment_u64(self.amount, value_blind),
-            token_commit: pedersen_commitment_base(token_id, token_blind),
-            coin,
-            ciphertext: encrypted_note.ciphertext,
-            ephem_public: encrypted_note.ephem_public,
-        };
-
-        Ok(MoneyMintParams { input, output })
-    }
-
-    pub fn prove(&self, params: &MoneyMintParams, zkbin: &ZkBinary, proving_key: &ProvingKey) -> Result<Proof> {
-        let prover_witnesses = vec![];
-
-        let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
-        let proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)?;
-
-        Ok(proof)
-    }
-}

+ 251 - 0
src/contract/money/src/entrypoint.rs

@@ -0,0 +1,251 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi_sdk::{
+    crypto::{ContractId, MerkleTree, PublicKey},
+    db::{db_init, db_lookup, db_set, set_return_data, SMART_CONTRACT_ZKAS_DB_NAME},
+    error::{ContractError, ContractResult},
+    msg, ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use crate::{
+    model::{MoneyFreezeUpdateV1, MoneyMintUpdateV1, MoneyTransferUpdateV1},
+    MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
+    MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_FAUCET_PUBKEYS, MONEY_CONTRACT_INFO_TREE,
+    MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_TOKEN_FREEZE_TREE,
+    MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+    MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
+};
+
+/// `Money::Transfer` functions
+mod transfer_v1;
+use transfer_v1::{
+    money_transfer_get_metadata_v1, money_transfer_process_instruction_v1,
+    money_transfer_process_update_v1,
+};
+
+/// `Money::OtcSwap` functions
+mod swap_v1;
+use swap_v1::{
+    money_otcswap_get_metadata_v1, money_otcswap_process_instruction_v1,
+    money_otcswap_process_update_v1,
+};
+
+/// `Money::Mint` functions
+mod mint_v1;
+use mint_v1::{
+    money_mint_get_metadata_v1, money_mint_process_instruction_v1, money_mint_process_update_v1,
+};
+
+/// `Money::Freeze` functions
+mod freeze_v1;
+use freeze_v1::{
+    money_freeze_get_metadata_v1, money_freeze_process_instruction_v1,
+    money_freeze_process_update_v1,
+};
+
+darkfi_sdk::define_contract!(
+    init: init_contract,
+    exec: process_instruction,
+    apply: process_update,
+    metadata: get_metadata
+);
+
+/// This entrypoint function runs when the contract is (re)deployed and initialized.
+/// We use this function to initialize all the necessary databases and prepare them
+/// with initial data if necessary. This is also the place where we bundle the zkas
+/// circuits that are to be used with functions provided by the contract.
+fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult {
+    // The payload for now contains a vector of `PublicKey` used to
+    // whitelist faucets that can create clear inputs.
+    let faucet_pubkeys: Vec<PublicKey> = deserialize(ix)?;
+
+    // The zkas circuit can simply be embedded in the wasm and set up by
+    // the initialization. Note that the tree should then be called "zkas".
+    // The lookups can be done by `contract_id+_zkas+namespace`.
+    // TODO: For the zkas tree, external host checks should be done to ensure
+    //       that the bincode is actually valid and not arbitrary.
+    let zkas_db = match db_lookup(cid, SMART_CONTRACT_ZKAS_DB_NAME) {
+        Ok(v) => v,
+        Err(_) => db_init(cid, SMART_CONTRACT_ZKAS_DB_NAME)?,
+    };
+
+    let mint_v1_bincode = include_bytes!("../proof/mint_v1.zk.bin");
+    let burn_v1_bincode = include_bytes!("../proof/burn_v1.zk.bin");
+
+    let token_mint_v1_bincode = include_bytes!("../proof/token_mint_v1.zk.bin");
+    let token_frz_v1_bincode = include_bytes!("../proof/token_freeze_v1.zk.bin");
+
+    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_MINT_NS_V1), &mint_v1_bincode[..])?;
+    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_BURN_NS_V1), &burn_v1_bincode[..])?;
+    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1), &token_mint_v1_bincode[..])?;
+    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1), &token_frz_v1_bincode[..])?;
+
+    // Set up a database tree to hold Merkle roots of all coins
+    // k=MerkleNode, v=[]
+    if db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE).is_err() {
+        db_init(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+    }
+
+    // Set up a database tree to hold all coins ever seen
+    // k=Coin, v=[]
+    if db_lookup(cid, MONEY_CONTRACT_COINS_TREE).is_err() {
+        db_init(cid, MONEY_CONTRACT_COINS_TREE)?;
+    }
+
+    // Set up a database tree to hold nullifiers of all spent coins
+    // k=Nullifier, v=[]
+    if db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE).is_err() {
+        db_init(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
+    }
+
+    // Set up a database tree to hold the set of frozen token mints
+    // k=TokenId, v=[]
+    if db_lookup(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE).is_err() {
+        db_init(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE)?;
+    }
+
+    // Set up a database tree for arbitrary data
+    let info_db = match db_lookup(cid, MONEY_CONTRACT_INFO_TREE) {
+        Ok(v) => v,
+        Err(_) => {
+            let info_db = db_init(cid, MONEY_CONTRACT_INFO_TREE)?;
+
+            // Create the incrementalmerkletree for seen coins
+            let coin_tree = MerkleTree::new(100);
+            let mut coin_tree_data = vec![];
+
+            coin_tree_data.write_u32(0)?;
+            coin_tree.encode(&mut coin_tree_data)?;
+
+            db_set(info_db, &serialize(&MONEY_CONTRACT_COIN_MERKLE_TREE), &coin_tree_data)?;
+            info_db
+        }
+    };
+
+    // Whitelisted faucets
+    db_set(info_db, &serialize(&MONEY_CONTRACT_FAUCET_PUBKEYS), &serialize(&faucet_pubkeys))?;
+
+    Ok(())
+}
+
+/// This function is used by the wasm VM's host to fetch the necessary metadata
+/// for verifying signatures and zk proofs. The payload given here are all the
+/// contract calls in the transaction.
+fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
+    let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
+    if call_idx < calls.len() as u32 {
+        msg!("Error: call_idx >= calls.len()");
+        return Err(ContractError::Internal)
+    }
+
+    match MoneyFunction::try_from(calls[call_idx as usize].data[0])? {
+        MoneyFunction::TransferV1 => {
+            // We pass everything into the correct function, and it will return
+            // the metadata for us, which we can then copy into the host with
+            // the `set_return_data` function. On the host, this metadata will
+            // be used to do external verification (zk proofs, and signatures).
+            let metadata = money_transfer_get_metadata_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&metadata)?)
+        }
+
+        MoneyFunction::OtcSwapV1 => {
+            let metadata = money_otcswap_get_metadata_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&metadata)?)
+        }
+
+        MoneyFunction::MintV1 => {
+            let metadata = money_mint_get_metadata_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&metadata)?)
+        }
+
+        MoneyFunction::FreezeV1 => {
+            let metadata = money_freeze_get_metadata_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&metadata)?)
+        }
+    }
+}
+
+/// This function verifies a state transition and produces a state update
+/// if everything is successful. This step should happen **after** the host
+/// has successfully verified the metadata from `get_metadata()`.
+fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
+    let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
+    if call_idx < calls.len() as u32 {
+        msg!("Error: call_idx => calls.len()");
+        return Err(ContractError::Internal)
+    }
+
+    match MoneyFunction::try_from(calls[call_idx as usize].data[0])? {
+        MoneyFunction::TransferV1 => {
+            // Again, we pass everything into the correct function.
+            // If it executes successfully, we'll get a state update
+            // which we can copy into the host using `set_return_data`.
+            // This update can then be written with `process_update()`
+            // if everything is in order.
+            let update_data = money_transfer_process_instruction_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&update_data)?)
+        }
+
+        MoneyFunction::OtcSwapV1 => {
+            let update_data = money_otcswap_process_instruction_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&update_data)?)
+        }
+
+        MoneyFunction::MintV1 => {
+            let update_data = money_mint_process_instruction_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&update_data)?)
+        }
+
+        MoneyFunction::FreezeV1 => {
+            let update_data = money_freeze_process_instruction_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&update_data)?)
+        }
+    }
+}
+
+/// This function attempts to write a given state update provided the previous steps
+/// of the contract call execution all were successful. It's the last in line, and
+/// assumes that the transaction/call was successful. The payload given to the function
+/// is the update data retrieved from `process_instruction()`.
+fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
+    match MoneyFunction::try_from(update_data[0])? {
+        MoneyFunction::TransferV1 => {
+            let update: MoneyTransferUpdateV1 = deserialize(&update_data[1..])?;
+            Ok(money_transfer_process_update_v1(cid, update)?)
+        }
+
+        MoneyFunction::OtcSwapV1 => {
+            // For the atomic swaps, we use the same state update like we would
+            // use for `Money::Transfer`.
+            let update: MoneyTransferUpdateV1 = deserialize(&update_data[1..])?;
+            Ok(money_otcswap_process_update_v1(cid, update)?)
+        }
+
+        MoneyFunction::MintV1 => {
+            let update: MoneyMintUpdateV1 = deserialize(&update_data[1..])?;
+            Ok(money_mint_process_update_v1(cid, update)?)
+        }
+
+        MoneyFunction::FreezeV1 => {
+            let update: MoneyFreezeUpdateV1 = deserialize(&update_data[1..])?;
+            Ok(money_freeze_process_update_v1(cid, update)?)
+        }
+    }
+}

+ 107 - 0
src/contract/money/src/entrypoint/freeze_v1.rs

@@ -0,0 +1,107 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi_sdk::{
+    crypto::{poseidon_hash, ContractId, PublicKey, TokenId},
+    db::{db_contains_key, db_lookup, db_set},
+    error::{ContractError, ContractResult},
+    msg,
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use crate::{
+    error::MoneyError,
+    model::{MoneyFreezeParamsV1, MoneyFreezeUpdateV1},
+    MoneyFunction, MONEY_CONTRACT_TOKEN_FREEZE_TREE, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1,
+};
+
+/// `get_metadata` function for `Money::FreezeV1`
+pub(crate) fn money_freeze_get_metadata_v1(
+    _cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: MoneyFreezeParamsV1 = deserialize(&self_.data[1..])?;
+
+    // Public inputs for the ZK proofs we have to verify
+    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+    // Public keys for the transaction signatures we have to verify
+    let signature_pubkeys: Vec<PublicKey> = vec![params.signature_public];
+
+    let (mint_x, mint_y) = params.signature_public.xy();
+    let token_id = poseidon_hash([mint_x, mint_y]);
+
+    zk_public_inputs
+        .push((MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1.to_string(), vec![mint_x, mint_y, token_id]));
+
+    // Serialize everything gathered and return it
+    let mut metadata = vec![];
+    zk_public_inputs.encode(&mut metadata)?;
+    signature_pubkeys.encode(&mut metadata)?;
+
+    Ok(metadata)
+}
+
+/// `process_instruction` function for `Money::FreezeV1`
+pub(crate) fn money_freeze_process_instruction_v1(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: MoneyFreezeParamsV1 = deserialize(&self_.data[1..])?;
+
+    // We just check if the mint was already frozen beforehand
+    let token_freeze_db = db_lookup(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE)?;
+
+    let (mint_x, mint_y) = params.signature_public.xy();
+    let token_id = TokenId::from(poseidon_hash([mint_x, mint_y]));
+
+    // Check that the mint is not frozen
+    if db_contains_key(token_freeze_db, &serialize(&token_id))? {
+        msg!("[MintV1] Error: Token mint for {} is frozen", token_id);
+        return Err(MoneyError::MintFrozen.into())
+    }
+
+    // Create a state update. We only need the new coin.
+    let update = MoneyFreezeUpdateV1 { signature_public: params.signature_public };
+    let mut update_data = vec![];
+    update_data.write_u8(MoneyFunction::FreezeV1 as u8)?;
+    update.encode(&mut update_data)?;
+
+    Ok(update_data)
+}
+
+/// `process_update` function for `Money::FreezeV1`
+pub(crate) fn money_freeze_process_update_v1(
+    cid: ContractId,
+    update: MoneyFreezeUpdateV1,
+) -> ContractResult {
+    let token_freeze_db = db_lookup(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE)?;
+
+    let (mint_x, mint_y) = update.signature_public.xy();
+    let token_id = TokenId::from(poseidon_hash([mint_x, mint_y]));
+
+    msg!("[MintV1] Freezing mint for token {}", token_id);
+    db_set(token_freeze_db, &serialize(&token_id), &[])?;
+
+    Ok(())
+}

+ 54 - 32
src/contract/money/src/mint.rs → src/contract/money/src/entrypoint/mint_v1.rs

@@ -21,8 +21,8 @@ use darkfi_sdk::{
         pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, poseidon_hash, Coin,
         ContractId, MerkleNode, PublicKey, TokenId,
     },
-    db::{db_contains_key, db_lookup},
-    error::ContractError,
+    db::{db_contains_key, db_lookup, db_set},
+    error::{ContractError, ContractResult},
     merkle_add, msg,
     pasta::pallas,
     ContractCall,
@@ -30,31 +30,41 @@ use darkfi_sdk::{
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 use crate::{
-    MoneyFunction, MoneyMintParams, MoneyMintUpdate, MONEY_CONTRACT_COIN_MERKLE_TREE,
+    error::MoneyError,
+    model::{MoneyMintParamsV1, MoneyMintUpdateV1},
+    MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
     MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_TOKEN_FREEZE_TREE,
     MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
 };
 
-pub fn money_mint_get_metadata(
+/// `get_metadata` function for `Money::MintV1`
+pub(crate) fn money_mint_get_metadata_v1(
     _cid: ContractId,
     call_idx: u32,
     calls: Vec<ContractCall>,
 ) -> Result<Vec<u8>, ContractError> {
     let self_ = &calls[call_idx as usize];
-    let params: MoneyMintParams = deserialize(&self_.data[1..])?;
+    let params: MoneyMintParamsV1 = deserialize(&self_.data[1..])?;
 
-    let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
+    // Public inputs for the ZK proofs we have to verify
+    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+    // Public keys for the transaction signatures we have to verify
     let mut signature_pubkeys: Vec<PublicKey> = vec![];
 
+    // The minting transaction creates 1 clear input and 1 anonymous output.
+    // We check the signature from the clear input, which is supposed to be
+    // signed by the mint authority.
     signature_pubkeys.push(params.input.signature_public);
 
     let value_coords = params.output.value_commit.to_affine().coordinates().unwrap();
-    let token_coords = params.output.token_commit.to_affine().coordinates().unwrap();
+    let token_coords = params.output.value_commit.to_affine().coordinates().unwrap();
 
+    // Since we expect a signature from the mint authority, we use those coordinates
+    // as public inputs for the ZK proof:
     let (sig_x, sig_y) = params.input.signature_public.xy();
     let token_id = poseidon_hash([sig_x, sig_y]);
 
-    zk_public_values.push((
+    zk_public_inputs.push((
         MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1.to_string(),
         vec![
             sig_x,
@@ -68,77 +78,89 @@ pub fn money_mint_get_metadata(
         ],
     ));
 
+    // Serialize everything gathered and return it
     let mut metadata = vec![];
-    zk_public_values.encode(&mut metadata)?;
+    zk_public_inputs.encode(&mut metadata)?;
     signature_pubkeys.encode(&mut metadata)?;
 
     Ok(metadata)
 }
 
-pub fn money_mint_process_instruction(
+/// `process_instruction` function for `Money::MintV1`
+pub(crate) fn money_mint_process_instruction_v1(
     cid: ContractId,
     call_idx: u32,
     calls: Vec<ContractCall>,
 ) -> Result<Vec<u8>, ContractError> {
     let self_ = &calls[call_idx as usize];
-    let params: MoneyMintParams = deserialize(&self_.data[1..])?;
+    let params: MoneyMintParamsV1 = deserialize(&self_.data[1..])?;
 
-    //let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
-    //let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+    // We have to check if the token mint is frozen, and if by some chance
+    // the minted coin has existed already.
+    let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
     let token_freeze_db = db_lookup(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE)?;
 
     // Check that the signature public key is actually the token ID
     let (mint_x, mint_y) = params.input.signature_public.xy();
     let token_id = TokenId::from(poseidon_hash([mint_x, mint_y]));
     if token_id != params.input.token_id {
-        msg!("[Mint] Token ID does not derive from mint authority");
-        return Err(ContractError::Custom(18))
+        msg!("[MintV1] Error: Token ID does not derive from mint authority");
+        return Err(MoneyError::TokenIdDoesNotDeriveFromMint.into())
     }
 
     // Check that the mint is not frozen
     if db_contains_key(token_freeze_db, &serialize(&token_id))? {
-        msg!("[Mint] Error: The mint for token {} is frozen", token_id);
-        return Err(ContractError::Custom(19))
+        msg!("[MintV1] Error: Token mint for {} is frozen", token_id);
+        return Err(MoneyError::MintFrozen.into())
     }
 
-    // TODO: Check that the new coin did not exist before. We should
-    //       probably have a sled tree of all coins ever in order to
-    //       assert against duplicates.
+    // Check that the coin from the output hasn't existed before
+    if db_contains_key(coins_db, &serialize(&params.output.coin))? {
+        msg!("[MintV1] Error: Duplicate coin in output");
+        return Err(MoneyError::DuplicateCoin.into())
+    }
 
-    // Verify that the value and token commitments match
+    // Verify that the value and token commitments match. In here we just
+    // confirm that the clear input and the anon output have the same
+    // commitments.
     if pedersen_commitment_u64(params.input.value, params.input.value_blind) !=
         params.output.value_commit
     {
-        msg!("[Mint] Error: Value commitments do not match");
-        return Err(ContractError::Custom(10))
+        msg!("[MintV1] Error: Value commitment mismatch");
+        return Err(MoneyError::ValueMismatch.into())
     }
 
     if pedersen_commitment_base(params.input.token_id.inner(), params.input.token_blind) !=
         params.output.token_commit
     {
-        msg!("[Mint] Error: Token commitments do not match");
-        return Err(ContractError::Custom(11))
+        msg!("[MintV1] Error: Token commitment mismatch");
+        return Err(MoneyError::TokenMismatch.into())
     }
 
     // Create a state update. We only need the new coin.
-    let update = MoneyMintUpdate { coin: Coin::from(params.output.coin) };
+    let update = MoneyMintUpdateV1 { coin: Coin::from(params.output.coin) };
     let mut update_data = vec![];
-    update_data.write_u8(MoneyFunction::Mint as u8)?;
+    update_data.write_u8(MoneyFunction::MintV1 as u8)?;
     update.encode(&mut update_data)?;
 
     Ok(update_data)
 }
 
-pub fn money_mint_process_update(
+/// `process_update` function for `Money::MintV1`
+pub(crate) fn money_mint_process_update_v1(
     cid: ContractId,
-    update: MoneyMintUpdate,
-) -> Result<(), ContractError> {
+    update: MoneyMintUpdateV1,
+) -> ContractResult {
+    // Grab all db handles we want to work on
     let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
+    let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
     let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
 
-    let coins = vec![MerkleNode::from(update.coin.inner())];
+    msg!("[MintV1] Adding new coin to the set");
+    db_set(coins_db, &serialize(&update.coin), &[])?;
 
-    msg!("[Mint] Adding new coin to Merkle tree");
+    msg!("[MintV1] Adding new coin to the Merkle tree");
+    let coins = vec![MerkleNode::from(update.coin.inner())];
     merkle_add(info_db, coin_roots_db, &serialize(&MONEY_CONTRACT_COIN_MERKLE_TREE), &coins)?;
 
     Ok(())

+ 171 - 0
src/contract/money/src/entrypoint/swap_v1.rs

@@ -0,0 +1,171 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi_sdk::{
+    crypto::{Coin, ContractId},
+    db::{db_contains_key, db_lookup},
+    error::{ContractError, ContractResult},
+    msg,
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use super::transfer_v1::{money_transfer_get_metadata_v1, money_transfer_process_update_v1};
+use crate::{
+    error::MoneyError,
+    model::{MoneyTransferParamsV1, MoneyTransferUpdateV1},
+    MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_ROOTS_TREE,
+    MONEY_CONTRACT_NULLIFIERS_TREE,
+};
+
+/// `get_metadata` function for `Money::OtcSwapV1`
+pub(crate) fn money_otcswap_get_metadata_v1(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    // In here we can use the same function as we use in `TransferV1`.
+    Ok(money_transfer_get_metadata_v1(cid, call_idx, calls)?)
+}
+
+/// `process_instruction` function for `Money::OtcSwapV1`
+pub(crate) fn money_otcswap_process_instruction_v1(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: MoneyTransferParamsV1 = deserialize(&self_.data[1..])?;
+
+    // The atomic swap is able to use the same parameters as `TransferV1`.
+    // In here we just have a different state transition where we enforce
+    // 2 anonymous inputs and 2 anonymous outputs. This is enforced so that
+    // every atomic swap looks the same on the network, therefore there is
+    // no special anonymity leak for different swaps that are being done,
+    // at least in the scope of this contract call.
+
+    if !params.clear_inputs.is_empty() {
+        msg!("[OtcSwapV1] Error: Clear inputs are not empty");
+        return Err(MoneyError::InvalidNumberOfInputs.into())
+    }
+
+    if params.inputs.len() != 2 {
+        msg!("[OtcSwapV1] Error: Expected 2 inputs");
+        return Err(MoneyError::InvalidNumberOfInputs.into())
+    }
+
+    if params.outputs.len() != 2 {
+        msg!("[OtcSwapV1] Error: Expected 2 outputs");
+        return Err(MoneyError::InvalidNumberOfOutputs.into())
+    }
+
+    // Grab the db handles we'll be using here
+    let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
+    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+
+    // We expect two new nullifiers and two new coins
+    let mut new_nullifiers = Vec::with_capacity(2);
+    let mut new_coins = Vec::with_capacity(2);
+
+    // inputs[0] is being swapped to outputs[1]
+    // inputs[1] is being swapped to outputs[0]
+    // so that's how we check the value and token commitments.
+    if params.inputs[0].value_commit != params.outputs[1].value_commit {
+        msg!("[OtcSwapV1] Error: Value commitments for input 0 and output 1 mismatch");
+        return Err(MoneyError::ValueMismatch.into())
+    }
+
+    if params.inputs[1].value_commit != params.outputs[0].value_commit {
+        msg!("[OtcSwapV1] Error: Value commitments for input 1 and ouptut 0 mismatch");
+        return Err(MoneyError::ValueMismatch.into())
+    }
+
+    if params.inputs[0].token_commit != params.outputs[1].token_commit {
+        msg!("[OtcSwapV1] Error: Token commitments for input 0 and output 1 mismatch");
+        return Err(MoneyError::TokenMismatch.into())
+    }
+
+    if params.inputs[1].token_commit != params.outputs[0].token_commit {
+        msg!("[OtcSwapV1] Error: Token commitments for input 1 and output 0 mismatch");
+        return Err(MoneyError::TokenMismatch.into())
+    }
+
+    msg!("[OtcSwapV1] Iterating over anonymous inputs");
+    for (i, input) in params.inputs.iter().enumerate() {
+        // For now, make sure that the inputs' spend hooks are zero.
+        // This should however be allowed to some extent, e.g. if we
+        // want a DAO to be able to do an atomic swap.
+        if input.spend_hook != pallas::Base::zero() {
+            msg!("[OtcSwapV1] Error: Unable to swap coins with spend_hook != 0 (input {})", i);
+            return Err(MoneyError::SpendHookNonZero.into())
+        }
+
+        // The Merkle root is used to know whether this coin
+        // has existed in a previous state.
+        if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
+            msg!("[OtcSwapV1] Error: Merkle root not found in previous state (input {})", i);
+            return Err(MoneyError::SwapMerkleRootNotFound.into())
+        }
+
+        // The nullifiers should not already exist. It is the double-spend protection.
+        if new_nullifiers.contains(&input.nullifier) ||
+            db_contains_key(nullifiers_db, &serialize(&input.nullifier))?
+        {
+            msg!("[OtcSwapV1] Error: Duplicate nullifier found in input {}", i);
+            return Err(MoneyError::DuplicateNullifier.into())
+        }
+
+        new_nullifiers.push(input.nullifier);
+    }
+
+    // Newly created coins for this call are in the outputs
+    for (i, output) in params.outputs.iter().enumerate() {
+        if new_coins.contains(&Coin::from(output.coin)) ||
+            db_contains_key(coins_db, &serialize(&output.coin))?
+        {
+            msg!("[OtcSwapV1] Error: Duplicate coin found in output {}", i);
+            return Err(MoneyError::DuplicateCoin.into())
+        }
+
+        new_coins.push(Coin::from(output.coin));
+    }
+
+    // Create a state update. We also use `MoneyTransferUpdateV1` because
+    // they're essentially the same thing, just with a different transition
+    // ruleset.
+    // FIXME: The function should not actually be written here. It should
+    //        be prepended by the host to enforce correctness. The host can
+    //        simply copy it from the payload.
+    let update = MoneyTransferUpdateV1 { nullifiers: new_nullifiers, coins: new_coins };
+    let mut update_data = vec![];
+    update_data.write_u8(MoneyFunction::OtcSwapV1 as u8)?;
+    update.encode(&mut update_data)?;
+
+    Ok(update_data)
+}
+
+/// `process_update` function for `Money::OtcSwapV1`
+pub(crate) fn money_otcswap_process_update_v1(
+    cid: ContractId,
+    update: MoneyTransferUpdateV1,
+) -> ContractResult {
+    // In here we can use the same function as we use in `TransferV1`.
+    Ok(money_transfer_process_update_v1(cid, update)?)
+}

+ 291 - 0
src/contract/money/src/entrypoint/transfer_v1.rs

@@ -0,0 +1,291 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi_sdk::{
+    crypto::{
+        pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, Coin, ContractId,
+        MerkleNode, PublicKey, DARK_TOKEN_ID,
+    },
+    db::{db_contains_key, db_get, db_lookup, db_set},
+    error::{ContractError, ContractResult},
+    merkle_add, msg,
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use crate::{
+    error::MoneyError,
+    model::{MoneyTransferParamsV1, MoneyTransferUpdateV1},
+    MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
+    MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_FAUCET_PUBKEYS, MONEY_CONTRACT_INFO_TREE,
+    MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+};
+
+/// `get_metadata` function for `Money::TransferV1`
+pub(crate) fn money_transfer_get_metadata_v1(
+    _cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: MoneyTransferParamsV1 = deserialize(&self_.data[1..])?;
+
+    // Public inputs for the ZK proofs we have to verify
+    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+    // Public keys for the transaction signatures we have to verify
+    let mut signature_pubkeys: Vec<PublicKey> = vec![];
+
+    // Take all the pubkeys from any clear inputs
+    for input in &params.clear_inputs {
+        signature_pubkeys.push(input.signature_public);
+    }
+
+    // Grab the pedersen commitments and signature pubkeys from the
+    // anonymous inputs
+    for input in &params.inputs {
+        let value_coords = input.value_commit.to_affine().coordinates().unwrap();
+        let token_coords = input.token_commit.to_affine().coordinates().unwrap();
+        let (sig_x, sig_y) = input.signature_public.xy();
+
+        // It is very important that these are in the same order as the
+        // `constrain_instance` calls in the zkas code.
+        // Otherwise verification will fail.
+        zk_public_inputs.push((
+            MONEY_CONTRACT_ZKAS_BURN_NS_V1.to_string(),
+            vec![
+                input.nullifier.inner(),
+                *value_coords.x(),
+                *value_coords.y(),
+                *token_coords.x(),
+                *token_coords.y(),
+                input.merkle_root.inner(),
+                input.user_data_enc,
+                sig_x,
+                sig_y,
+            ],
+        ));
+
+        signature_pubkeys.push(input.signature_public);
+    }
+
+    // Grab the pedersen commitments from the anonymous outputs
+    for output in &params.outputs {
+        let value_coords = output.value_commit.to_affine().coordinates().unwrap();
+        let token_coords = output.token_commit.to_affine().coordinates().unwrap();
+
+        zk_public_inputs.push((
+            MONEY_CONTRACT_ZKAS_MINT_NS_V1.to_string(),
+            vec![
+                output.coin,
+                *value_coords.x(),
+                *value_coords.y(),
+                *token_coords.x(),
+                *token_coords.y(),
+            ],
+        ));
+    }
+
+    // Serialize everything gathered and return it
+    let mut metadata = vec![];
+    zk_public_inputs.encode(&mut metadata)?;
+    signature_pubkeys.encode(&mut metadata)?;
+
+    Ok(metadata)
+}
+
+/// `process_instruction` function for `Money::TransferV1`
+pub(crate) fn money_transfer_process_instruction_v1(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: MoneyTransferParamsV1 = deserialize(&self_.data[1..])?;
+
+    if params.clear_inputs.len() + params.inputs.len() < 1 {
+        msg!("[TransferV1] Error: No inputs in the call");
+        return Err(MoneyError::TransferMissingInputs.into())
+    }
+
+    if params.outputs.is_empty() {
+        msg!("[TransferV1] Error: No outputs in the call");
+        return Err(MoneyError::TransferMissingOutputs.into())
+    }
+
+    // Access the necessary databases where there is information to
+    // validate this state transition.
+    let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
+    let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
+    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+
+    // Grab faucet pubkeys. They're allowed to create clear inputs.
+    // Currently we use them for airdrops in the testnet.
+    let Some(faucet_pubkeys) = db_get(info_db, &serialize(&MONEY_CONTRACT_FAUCET_PUBKEYS))? else {
+        msg!("[TransferV1] Error: Missing faucet pubkeys from info db");
+        return Err(MoneyError::TransferMissingFaucetKeys.into())
+    };
+    let faucet_pubkeys: Vec<PublicKey> = deserialize(&faucet_pubkeys)?;
+
+    // Accumulator for the value commitments. We add inputs to it, and subtract
+    // outputs from it. For the commitments to be valid, the accumulator must
+    // be in its initial state after performing the arithmetics.
+    let mut valcom_total = pallas::Point::identity();
+
+    // ===================================
+    // Perform the actual state transition
+    // ===================================
+
+    // For clear inputs, we only allow the whitelisted faucet(s) to create them.
+    // Additionally, only DARK_TOKEN_ID is able to be here. For any arbitrary
+    // tokens, there is another functionality in this contract called `Mint` which
+    // allows users to mint their own tokens.
+    msg!("[TransferV1] Iterating over clear inputs");
+    for (i, input) in params.clear_inputs.iter().enumerate() {
+        if input.token_id != *DARK_TOKEN_ID {
+            msg!("[TransferV1] Error: Clear input {} used non-native token", i);
+            return Err(MoneyError::TransferClearInputNonNativeToken.into())
+        }
+
+        if !faucet_pubkeys.contains(&input.signature_public) {
+            msg!("[TransferV1] Error: Clear input {} used unauthorised pubkey", i);
+            return Err(MoneyError::TransferClearInputUnauthorised.into())
+        }
+
+        // Add this input to the value commitment accumulator
+        valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
+    }
+
+    // For anonymous inputs, we must also gather all the new nullifiers
+    // that are introduced.
+    let mut new_nullifiers = Vec::with_capacity(params.inputs.len());
+    msg!("[TransferV1] Iterating over anonymous inputs");
+    for (i, input) in params.inputs.iter().enumerate() {
+        // The Merkle root is used to know whether this is a coin that
+        // existed in a previous state.
+        if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
+            msg!("[TransferV1] Error: Merkle root not found in previous state (input {})", i);
+            return Err(MoneyError::TransferMerkleRootNotFound.into())
+        }
+
+        // The nullifiers should not already exist. It is the double-spend protection.
+        if new_nullifiers.contains(&input.nullifier) ||
+            db_contains_key(nullifiers_db, &serialize(&input.nullifier))?
+        {
+            msg!("[TransferV1] Error: Duplicate nullifier found (input {})", i);
+            return Err(MoneyError::DuplicateNullifier.into())
+        }
+
+        // If spend hook is set, check its correctness
+        if input.spend_hook != pallas::Base::zero() {
+            let next_call_idx = call_idx + 1;
+            if next_call_idx >= calls.len() as u32 {
+                msg!("[TransferV1] Error: next_call_idx out of bounds (input {})", i);
+                return Err(MoneyError::SpendHookOutOfBounds.into())
+            }
+
+            let next = &calls[next_call_idx as usize];
+            if next.contract_id.inner() != input.spend_hook {
+                msg!("[TransferV1] Error: Invoking contract call does not match spend hook in input {}", i);
+                return Err(MoneyError::SpendHookMismatch.into())
+            }
+        }
+
+        // Append this new nullifier to seen nullifiers, and accumulate the value commitment
+        new_nullifiers.push(input.nullifier);
+        valcom_total += input.value_commit;
+    }
+
+    // Newly created coins for this call are in the outputs. Here we gather them,
+    // and we also check that they haven't existed before.
+    let mut new_coins = Vec::with_capacity(params.outputs.len());
+    for (i, output) in params.outputs.iter().enumerate() {
+        if new_coins.contains(&Coin::from(output.coin)) ||
+            db_contains_key(coins_db, &serialize(&output.coin))?
+        {
+            msg!("[TransferV1] Error: Duplicate coin found in output {}", i);
+            return Err(MoneyError::DuplicateCoin.into())
+        }
+
+        // Append this new coin to seen coins, and subtract the value commitment
+        new_coins.push(Coin::from(output.coin));
+        valcom_total -= output.value_commit;
+    }
+
+    // If the accumulator is not back in its initial state, that means there
+    // is a value mismatch between inputs and outputs.
+    if valcom_total != pallas::Point::identity() {
+        msg!("[TransferV1] Error: Value commitments do not result in identity");
+        return Err(MoneyError::ValueMismatch.into())
+    }
+
+    // We also need to verify that all token commitments are the same.
+    // In the basic transfer, we only allow the same token type to be
+    // transferred. For exchanging we use another functionality of this
+    // contract called `OtcSwap`.
+    let tokcom = params.outputs[0].token_commit;
+    let mut failed_tokcom = params.inputs.iter().any(|x| x.token_commit != tokcom);
+    failed_tokcom = failed_tokcom || params.outputs.iter().any(|x| x.token_commit != tokcom);
+    failed_tokcom = failed_tokcom ||
+        params
+            .clear_inputs
+            .iter()
+            .any(|x| pedersen_commitment_base(x.token_id.inner(), x.token_blind) != tokcom);
+
+    if failed_tokcom {
+        msg!("[TransferV1] Error: Token commitments do not match");
+        return Err(MoneyError::TokenMismatch.into())
+    }
+
+    // At this point the state transition has passed, so we create a state update
+    let update = MoneyTransferUpdateV1 { nullifiers: new_nullifiers, coins: new_coins };
+    let mut update_data = vec![];
+    update_data.write_u8(MoneyFunction::TransferV1 as u8)?;
+    update.encode(&mut update_data)?;
+    // and return it
+    Ok(update_data)
+}
+
+/// `process_update` function for `Money::TransferV1`
+pub(crate) fn money_transfer_process_update_v1(
+    cid: ContractId,
+    update: MoneyTransferUpdateV1,
+) -> ContractResult {
+    // Grab all necessary db handles for where we want to write
+    let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
+    let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
+    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+
+    msg!("[TransferV1] Adding new nullifiers to the set");
+    for nullifier in update.nullifiers {
+        db_set(nullifiers_db, &serialize(&nullifier), &[])?;
+    }
+
+    msg!("[TransferV1] Adding new coins to the set");
+    for coin in &update.coins {
+        db_set(coins_db, &serialize(coin), &[])?;
+    }
+
+    msg!("[TransferV1] Adding new coins to the Merkle tree");
+    let coins: Vec<_> = update.coins.iter().map(|x| MerkleNode::from(x.inner())).collect();
+    merkle_add(info_db, coin_roots_db, &serialize(&MONEY_CONTRACT_COIN_MERKLE_TREE), &coins)?;
+
+    Ok(())
+}

+ 101 - 0
src/contract/money/src/error.rs

@@ -0,0 +1,101 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi_sdk::error::ContractError;
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum MoneyError {
+    #[error("Missing inputs in transfer call")]
+    TransferMissingInputs,
+
+    #[error("Missing outputs in transfer call")]
+    TransferMissingOutputs,
+
+    #[error("Missing faucet pubkeys from info db")]
+    TransferMissingFaucetKeys,
+
+    #[error("Clear input used non-native token")]
+    TransferClearInputNonNativeToken,
+
+    #[error("Clear input used unauthorised pubkey")]
+    TransferClearInputUnauthorised,
+
+    #[error("Merkle root not found in previous state")]
+    TransferMerkleRootNotFound,
+
+    #[error("Duplicate nullifier found")]
+    DuplicateNullifier,
+
+    #[error("Spend hook out of bounds")]
+    SpendHookOutOfBounds,
+
+    #[error("Spend hook mismatch")]
+    SpendHookMismatch,
+
+    #[error("Duplicate coin found")]
+    DuplicateCoin,
+
+    #[error("Value commitment mismatch")]
+    ValueMismatch,
+
+    #[error("Token commitment mismatch")]
+    TokenMismatch,
+
+    #[error("Invalid number of inputs")]
+    InvalidNumberOfInputs,
+
+    #[error("Invalid number of outputs")]
+    InvalidNumberOfOutputs,
+
+    #[error("Spend hook is not zero")]
+    SpendHookNonZero,
+
+    #[error("Merkle root not found in previous state")]
+    SwapMerkleRootNotFound,
+
+    #[error("Token ID does not derive from mint authority")]
+    TokenIdDoesNotDeriveFromMint,
+
+    #[error("Token mint is frozen")]
+    MintFrozen,
+}
+
+impl From<MoneyError> for ContractError {
+    fn from(e: MoneyError) -> Self {
+        match e {
+            MoneyError::TransferMissingInputs => Self::Custom(1),
+            MoneyError::TransferMissingOutputs => Self::Custom(2),
+            MoneyError::TransferMissingFaucetKeys => Self::Custom(3),
+            MoneyError::TransferClearInputNonNativeToken => Self::Custom(4),
+            MoneyError::TransferClearInputUnauthorised => Self::Custom(5),
+            MoneyError::TransferMerkleRootNotFound => Self::Custom(6),
+            MoneyError::DuplicateNullifier => Self::Custom(7),
+            MoneyError::SpendHookOutOfBounds => Self::Custom(8),
+            MoneyError::SpendHookMismatch => Self::Custom(9),
+            MoneyError::DuplicateCoin => Self::Custom(10),
+            MoneyError::ValueMismatch => Self::Custom(11),
+            MoneyError::TokenMismatch => Self::Custom(12),
+            MoneyError::InvalidNumberOfInputs => Self::Custom(13),
+            MoneyError::InvalidNumberOfOutputs => Self::Custom(14),
+            MoneyError::SpendHookNonZero => Self::Custom(15),
+            MoneyError::SwapMerkleRootNotFound => Self::Custom(16),
+            MoneyError::TokenIdDoesNotDeriveFromMint => Self::Custom(17),
+            MoneyError::MintFrozen => Self::Custom(18),
+        }
+    }
+}

+ 27 - 546
src/contract/money/src/lib.rs

@@ -16,98 +16,59 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-#[cfg(not(feature = "no-entrypoint"))]
-use darkfi_sdk::{
-    crypto::{
-        pallas, pasta_prelude::*, pedersen_commitment_base, Coin, ContractId, MerkleNode,
-        MerkleTree, PublicKey, DARK_TOKEN_ID,
-    },
-    db::{
-        db_contains_key, db_init, db_lookup, db_set, set_return_data, SMART_CONTRACT_ZKAS_DB_NAME,
-    },
-    error::ContractResult,
-    merkle::merkle_add,
-    msg, ContractCall,
-};
+//! Smart contract implementing money transfers, atomic swaps, token
+//! minting and freezing, and staking/unstaking of consensus tokens.
 
 use darkfi_sdk::error::ContractError;
 
-#[cfg(not(feature = "no-entrypoint"))]
-use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
-
-/// Functions we allow in this contract
+/// Functions available in the contract
 #[repr(u8)]
 pub enum MoneyFunction {
-    Transfer = 0x00,
-    OtcSwap = 0x01,
-    Stake = 0x02,
-    Unstake = 0x03,
-    Mint = 0x04,
-    Freeze = 0x05,
+    TransferV1 = 0x00,
+    OtcSwapV1 = 0x01,
+    MintV1 = 0x02,
+    FreezeV1 = 0x03,
+    //Fee = 0x04,
+    //Stake = 0x05,
+    //Unstake = 0x06,
 }
 
 impl TryFrom<u8> for MoneyFunction {
     type Error = ContractError;
 
-    fn try_from(b: u8) -> core::result::Result<MoneyFunction, Self::Error> {
+    fn try_from(b: u8) -> core::result::Result<Self, Self::Error> {
         match b {
-            0x00 => Ok(Self::Transfer),
-            0x01 => Ok(Self::OtcSwap),
-            0x02 => Ok(Self::Stake),
-            0x03 => Ok(Self::Unstake),
-            0x04 => Ok(Self::Mint),
-            0x05 => Ok(Self::Freeze),
+            0x00 => Ok(Self::TransferV1),
+            0x01 => Ok(Self::OtcSwapV1),
+            0x02 => Ok(Self::MintV1),
+            0x03 => Ok(Self::FreezeV1),
+            //0x04 => Ok(Self::Fee),
+            //0x05 => Ok(Self::Stake),
+            //0x06 => Ok(Self::Unstake),
             _ => Err(ContractError::InvalidFunction),
         }
     }
 }
 
-/// Structures and object definitions
-pub mod model;
-
-// Contract functionalities
-mod mint;
-use mint::{money_mint_get_metadata, money_mint_process_instruction, money_mint_process_update};
-mod swap;
-use swap::{
-    money_otcswap_get_metadata, money_otcswap_process_instruction, money_otcswap_process_update,
-};
-mod transfer;
-use transfer::{
-    money_transfer_get_metadata, money_transfer_process_instruction, money_transfer_process_update,
-};
+/// Internal contract errors
+pub mod error;
 
 #[cfg(not(feature = "no-entrypoint"))]
-use model::{
-    MoneyMintParams, MoneyMintUpdate, MoneyStakeParams, MoneyStakeUpdate, MoneyTransferParams,
-    MoneyTransferUpdate, MoneyUnstakeParams,
-};
-
-#[cfg(feature = "client")]
-/// Transaction building API for clients interacting with this contract.
-pub mod client;
+/// WASM entrypoint functions
+pub mod entrypoint;
 
-#[cfg(not(feature = "no-entrypoint"))]
-darkfi_sdk::define_contract!(
-    init: init_contract,
-    exec: process_instruction,
-    apply: process_update,
-    metadata: get_metadata
-);
+/// Call parameters definitions
+pub mod model;
 
 // These are the different sled trees that will be created
+pub const MONEY_CONTRACT_INFO_TREE: &str = "info";
+pub const MONEY_CONTRACT_COINS_TREE: &str = "coins";
 pub const MONEY_CONTRACT_COIN_ROOTS_TREE: &str = "coin_roots";
 pub const MONEY_CONTRACT_NULLIFIERS_TREE: &str = "nullifiers";
 pub const MONEY_CONTRACT_TOKEN_FREEZE_TREE: &str = "token_freezes";
-pub const MONEY_CONTRACT_INFO_TREE: &str = "info";
-// lead coin, nullifier sled trees.
-pub const MONEY_CONTRACT_LEAD_COIN_ROOTS_TREE: &str = "lead_coin_roots";
-pub const MONEY_CONTRACT_LEAD_NULLIFIERS_TREE: &str = "lead_nullifiers";
-pub const MONEY_CONTRACT_LEAD_INFO_TREE: &str = "lead_info";
 
-// This is a key inside the info tree
+// These are keys inside the info tree
 pub const MONEY_CONTRACT_COIN_MERKLE_TREE: &str = "coin_tree";
-pub const MONEY_CONTRACT_LEAD_COIN_MERKLE_TREE: &str = "lead_coin_tree";
 pub const MONEY_CONTRACT_FAUCET_PUBKEYS: &str = "faucet_pubkeys";
 
 /// zkas mint circuit namespace
@@ -118,483 +79,3 @@ pub const MONEY_CONTRACT_ZKAS_BURN_NS_V1: &str = "Burn_V1";
 pub const MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1: &str = "TokenMint_V1";
 /// zkas token freeze circuit namespace
 pub const MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1: &str = "TokenFreeze_V1";
-/// zkas staking coin mint circuit namespace
-pub const MONEY_CONTRACT_ZKAS_LEAD_MINT_NS_V1: &str = "Lead_Mint_V1";
-/// zkas staking coin burn circuit namespace
-pub const MONEY_CONTRACT_ZKAS_LEAD_BURN_NS_V1: &str = "Lead_Burn_V1";
-
-/// This function runs when the contract is (re)deployed and initialized.
-#[cfg(not(feature = "no-entrypoint"))]
-fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult {
-    // The payload for now contains a vector of `PublicKey` used to
-    // whitelist faucets that can create clear inputs.
-    let faucet_pubkeys: Vec<PublicKey> = deserialize(ix)?;
-
-    // The zkas circuits can simply be embedded in the wasm and set up by
-    // the initialization. Note that the tree should then be called "zkas".
-    // The lookups can then be done by `contract_id+_zkas+namespace`.
-    let zkas_db = match db_lookup(cid, SMART_CONTRACT_ZKAS_DB_NAME) {
-        Ok(v) => v,
-        Err(_) => db_init(cid, SMART_CONTRACT_ZKAS_DB_NAME)?,
-    };
-
-    let mint_v1_bincode = include_bytes!("../proof/mint_v1.zk.bin");
-    let burn_v1_bincode = include_bytes!("../proof/burn_v1.zk.bin");
-
-    let token_mint_v1_bincode = include_bytes!("../proof/token_mint_v1.zk.bin");
-    let token_frz_v1_bincode = include_bytes!("../proof/token_freeze_v1.zk.bin");
-
-    let lead_mint_v1_bincode = include_bytes!("../proof/lead_mint_v1.zk.bin");
-    let lead_burn_v1_bincode = include_bytes!("../proof/lead_burn_v1.zk.bin");
-
-    /* For now we take anything, but the zkas db needs protection against
-       arbitrary data.
-    let zkbin = ZkBinary::decode(mint_bincode)?;
-    let mint_namespace = zkbin.namespace.clone();
-    assert_eq!(&mint_namespace, ZKAS_MINT_NS);
-    let zkbin = ZkBinary::decode(burn_bincode)?;
-    let burn_namespace = zkbin.namespace.clone();
-    assert_eq!(&burn_namespace, ZKAS_BURN_NS);
-    db_set(zkas_db, &serialize(&mint_namespace), &mint_bincode[..])?;
-    db_set(zkas_db, &serialize(&burn_namespace), &burn_bincode[..])?;
-    */
-
-    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_MINT_NS_V1), &mint_v1_bincode[..])?;
-    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_BURN_NS_V1), &burn_v1_bincode[..])?;
-    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1), &token_mint_v1_bincode[..])?;
-    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1), &token_frz_v1_bincode[..])?;
-    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_LEAD_MINT_NS_V1), &lead_mint_v1_bincode[..])?;
-    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_LEAD_BURN_NS_V1), &lead_burn_v1_bincode[..])?;
-
-    // Set up a database tree to hold Merkle roots of all coins
-    if db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE).is_err() {
-        db_init(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
-    }
-
-    // Set up a database tree to hold nullifiers of all spent coins
-    if db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE).is_err() {
-        db_init(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
-    }
-
-    // Set up a database tree to hold a set of frozen token mints
-    if db_lookup(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE).is_err() {
-        db_init(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE)?;
-    }
-
-    /*
-    // Set up a database tree to hold lead Merkle roots
-    if db_lookup(cid, MONEY_CONTRACT_LEAD_COIN_ROOTS_TREE).is_err() {
-        db_init(cid, MONEY_CONTRACT_LEAD_COIN_ROOTS_TREE)?;
-    }
-
-    // Set up a database tree to hold nullifiers
-    if db_lookup(cid, MONEY_CONTRACT_LEAD_NULLIFIERS_TREE).is_err() {
-        db_init(cid, MONEY_CONTRACT_LEAD_NULLIFIERS_TREE)?;
-    }
-    */
-
-    // Set up a database tree for arbitrary data
-    let info_db = match db_lookup(cid, MONEY_CONTRACT_INFO_TREE) {
-        Ok(v) => v,
-        Err(_) => {
-            let info_db = db_init(cid, MONEY_CONTRACT_INFO_TREE)?;
-            // Add a Merkle tree to the info db:
-            let coin_tree = MerkleTree::new(100);
-            let mut coin_tree_data = vec![];
-
-            coin_tree_data.write_u32(0)?;
-            coin_tree.encode(&mut coin_tree_data)?;
-
-            db_set(info_db, &serialize(&MONEY_CONTRACT_COIN_MERKLE_TREE), &coin_tree_data)?;
-            info_db
-        }
-    };
-
-    // Whitelisted faucets
-    db_set(info_db, &serialize(&MONEY_CONTRACT_FAUCET_PUBKEYS), &serialize(&faucet_pubkeys))?;
-
-    Ok(())
-}
-
-/// This function is used by the VM's host to fetch the necessary metadata for
-/// verifying signatures and zk proofs.
-#[cfg(not(feature = "no-entrypoint"))]
-fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
-    let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
-    assert!(call_idx < calls.len() as u32);
-
-    let self_ = &calls[call_idx as usize];
-
-    match MoneyFunction::try_from(self_.data[0])? {
-        MoneyFunction::Transfer => {
-            let metadata = money_transfer_get_metadata(cid, call_idx, calls)?;
-            // Using this, we pass the above data to the host.
-            set_return_data(&metadata)?;
-            Ok(())
-        }
-
-        MoneyFunction::OtcSwap => {
-            let metadata = money_otcswap_get_metadata(cid, call_idx, calls)?;
-            set_return_data(&metadata)?;
-            Ok(())
-        }
-
-        MoneyFunction::Stake => {
-            let params: MoneyStakeParams = deserialize(&self_.data[1..])?;
-
-            let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
-            let mut signature_pubkeys: Vec<PublicKey> = vec![];
-
-            for input in &params.inputs {
-                let value_coords = input.value_commit.to_affine().coordinates().unwrap();
-                let token_coords = input.token_commit.to_affine().coordinates().unwrap();
-                let (sig_x, sig_y) = input.signature_public.xy();
-
-                zk_public_values.push((
-                    MONEY_CONTRACT_ZKAS_BURN_NS_V1.to_string(),
-                    vec![
-                        input.nullifier.inner(),
-                        *value_coords.x(),
-                        *value_coords.y(),
-                        *token_coords.x(),
-                        *token_coords.y(),
-                        input.merkle_root.inner(),
-                        input.user_data_enc,
-                        sig_x,
-                        sig_y,
-                    ],
-                ));
-
-                signature_pubkeys.push(input.signature_public);
-            }
-
-            for output in &params.outputs {
-                let value_coords = output.value_commit.to_affine().coordinates().unwrap();
-
-                zk_public_values.push((
-                    MONEY_CONTRACT_ZKAS_LEAD_MINT_NS_V1.to_string(),
-                    vec![
-                        *value_coords.x(),
-                        *value_coords.y(),
-                        output.coin_pk_hash,
-                        output.coin_commit_hash,
-                    ],
-                ));
-            }
-
-            let mut metadata = vec![];
-            zk_public_values.encode(&mut metadata)?;
-            signature_pubkeys.encode(&mut metadata)?;
-
-            // Using this, we pass the above data to the host.
-            set_return_data(&metadata)?;
-            Ok(())
-        }
-
-        MoneyFunction::Unstake => {
-            let params: MoneyUnstakeParams = deserialize(&self_.data[1..])?;
-
-            let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
-
-            for input in &params.inputs {
-                let value_coords = input.value_commit.to_affine().coordinates().unwrap();
-                zk_public_values.push((
-                    MONEY_CONTRACT_ZKAS_LEAD_BURN_NS_V1.to_string(),
-                    vec![
-                        *value_coords.x(),
-                        *value_coords.y(),
-                        input.coin_pk_hash,
-                        input.coin_commit_hash,
-                        input.coin_commit_root.inner(),
-                        input.sk_root.inner(),
-                        input.nullifier.inner(),
-                    ],
-                ));
-            }
-
-            for output in &params.outputs {
-                let value_coords = output.value_commit.to_affine().coordinates().unwrap();
-                let token_coords = output.token_commit.to_affine().coordinates().unwrap();
-
-                zk_public_values.push((
-                    MONEY_CONTRACT_ZKAS_MINT_NS_V1.to_string(),
-                    vec![
-                        output.coin,
-                        *value_coords.x(),
-                        *value_coords.y(),
-                        *token_coords.x(),
-                        *token_coords.y(),
-                    ],
-                ));
-            }
-            let mut metadata = vec![];
-            zk_public_values.encode(&mut metadata)?;
-
-            // Using this, we pass the above data to the host.
-            set_return_data(&metadata)?;
-            Ok(())
-        }
-
-        MoneyFunction::Mint => {
-            let metadata = money_mint_get_metadata(cid, call_idx, calls)?;
-            // Using this, we pass the above data to the host.
-            set_return_data(&metadata)?;
-            Ok(())
-        }
-
-        MoneyFunction::Freeze => {
-            msg!("[Freeze] Entered match arm");
-            unimplemented!();
-        }
-    }
-}
-
-/// This function verifies a state transition and produces an
-/// update if everything is successful.
-#[cfg(not(feature = "no-entrypoint"))]
-fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
-    let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
-
-    if call_idx >= calls.len() as u32 {
-        msg!("Error: call_idx >= calls.len()");
-        return Err(ContractError::Internal)
-    }
-
-    let self_ = &calls[call_idx as usize];
-
-    match MoneyFunction::try_from(self_.data[0])? {
-        MoneyFunction::Transfer => {
-            msg!("[Transfer] Entered match arm");
-            let update_data = money_transfer_process_instruction(cid, call_idx, calls)?;
-            set_return_data(&update_data)?;
-            msg!("[Transfer] State update set!");
-            Ok(())
-        }
-
-        MoneyFunction::OtcSwap => {
-            msg!("[OtcSwap] Entered match arm");
-            let update_data = money_otcswap_process_instruction(cid, call_idx, calls)?;
-            set_return_data(&update_data)?;
-            msg!("[OtcSwap] State update set!");
-            Ok(())
-        }
-
-        MoneyFunction::Stake => {
-            msg!("[Stake] Entered match arm");
-            let params: MoneyStakeParams = deserialize(&self_.data[1..])?;
-
-            assert!(params.inputs.len() == params.outputs.len());
-
-            // Verify token commitment
-            let tokcom = pedersen_commitment_base(DARK_TOKEN_ID.inner(), params.token_blind);
-            if params.inputs.iter().any(|input| input.token_commit != tokcom) {
-                msg!("[Stake] Error: Tried to stake non-native token. Unable to proceed");
-                return Err(ContractError::Custom(26))
-            }
-
-            let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_LEAD_NULLIFIERS_TREE)?;
-            let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_LEAD_COIN_ROOTS_TREE)?;
-
-            // Accumulator for the value commitments
-            let mut valcom_total = pallas::Point::identity();
-
-            // State transition for payments
-            let mut new_nullifiers = Vec::with_capacity(params.inputs.len());
-
-            msg!("[Stake] Iterating over anonymous inputs");
-            for (i, input) in params.inputs.iter().enumerate() {
-                // The Merkle root is used to know whether this is a coin that existed
-                // in a previous state.
-                if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
-                    msg!("[Stake] Error: Merkle root not found in previous state (input {})", i);
-                    return Err(ContractError::Custom(21))
-                }
-
-                // The nullifiers should not already exist. It is the double-spend protection.
-                if new_nullifiers.contains(&input.nullifier) ||
-                    db_contains_key(nullifiers_db, &serialize(&input.nullifier))?
-                {
-                    msg!("[Stake] Error: Duplicate nullifier found in input {}", i);
-                    return Err(ContractError::Custom(22))
-                }
-
-                new_nullifiers.push(input.nullifier);
-                valcom_total += input.value_commit;
-            }
-
-            // Newly created coins for this transaction are in the outputs.
-            let mut new_coins = Vec::with_capacity(params.outputs.len());
-            for (i, output) in params.outputs.iter().enumerate() {
-                // TODO: Should we have coins in a sled tree too to check dupes?
-                if new_coins.contains(&Coin::from(output.coin_commit_hash)) {
-                    msg!("[Stake] Error: Duplicate coin found in output {}", i);
-                    return Err(ContractError::Custom(23))
-                }
-                new_coins.push(Coin::from(output.coin_commit_hash));
-                valcom_total -= output.value_commit;
-            }
-
-            // If the accumulator is not back in its initial state, there's a value mismatch.
-            if valcom_total != pallas::Point::identity() {
-                msg!("[Stake] Error: Value commitments do not result in identity");
-                return Err(ContractError::Custom(24))
-            }
-
-            // Create a state update
-            let update = MoneyStakeUpdate { nullifiers: new_nullifiers, coins: new_coins };
-            let mut update_data = vec![];
-            update_data.write_u8(MoneyFunction::Stake as u8)?;
-            update.encode(&mut update_data)?;
-            set_return_data(&update_data)?;
-            msg!("[Stake] State update set!");
-
-            Ok(())
-        }
-
-        MoneyFunction::Unstake => {
-            msg!("[Unstake] Entered match arm");
-            let params: MoneyUnstakeParams = deserialize(&self_.data[1..])?;
-
-            assert!(params.inputs.len() == params.outputs.len());
-
-            // Verify token commitment
-            let tokcom = pedersen_commitment_base(DARK_TOKEN_ID.inner(), params.token_blind);
-            if params.outputs.iter().any(|output| output.token_commit != tokcom) {
-                msg!("[Stake] Error: Tried to unstake non-native token. Unable to proceed");
-                return Err(ContractError::Custom(26))
-            }
-
-            let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_LEAD_NULLIFIERS_TREE)?;
-            let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_LEAD_COIN_ROOTS_TREE)?;
-            //let sk_roots_db = db_lookup(cid, MONEY_CONTRACT_LEAD_SK_ROOTS_TREE)?;
-
-            // Accumulator for the value commitments
-            let mut valcom_total = pallas::Point::identity();
-
-            // State transition for payments
-            let mut new_nullifiers = Vec::with_capacity(params.inputs.len());
-
-            msg!("[Stake] Iterating over anonymous inputs");
-            for (i, input) in params.inputs.iter().enumerate() {
-                // The Merkle root is used to know whether this is a coin that existed
-                // in a previous state.
-                if !db_contains_key(coin_roots_db, &serialize(&input.coin_commit_root))? {
-                    msg!("[Unstake] Error: Merkle root not found in previous state (input {})", i);
-                    return Err(ContractError::Custom(21))
-                }
-
-                //TODO adde sk root to db.
-                /*
-                    if !db_contains_key(sk_roots_db, &serialize(&input.sk_root))? {
-                        msg!("[Unstake] Error: sk merkle root not found in previous state (input {})", i);
-                        return Err(ContractError::Custom(21))
-                }
-
-                    */
-
-                // The nullifiers should not already exist. It is the double-spend protection.
-                if new_nullifiers.contains(&input.nullifier) ||
-                    db_contains_key(nullifiers_db, &serialize(&input.nullifier))?
-                {
-                    msg!("[Unstake] Error: Duplicate nullifier found in input {}", i);
-                    return Err(ContractError::Custom(22))
-                }
-
-                new_nullifiers.push(input.nullifier);
-                valcom_total += input.value_commit;
-            }
-
-            // Newly created coins for this transaction are in the outputs.
-            let mut new_coins = Vec::with_capacity(params.outputs.len());
-            for (i, output) in params.outputs.iter().enumerate() {
-                // TODO: Should we have coins in a sled tree too to check dupes?
-                if new_coins.contains(&Coin::from(output.coin)) {
-                    msg!("[Unstake] Error: Duplicate coin found in output {}", i);
-                    return Err(ContractError::Custom(23))
-                }
-                new_coins.push(Coin::from(output.coin));
-                valcom_total -= output.value_commit;
-            }
-
-            // If the accumulator is not back in its initial state, there's a value mismatch.
-            if valcom_total != pallas::Point::identity() {
-                msg!("[UnStake] Error: Value commitments do not result in identity");
-                return Err(ContractError::Custom(24))
-            }
-
-            // Create a state update
-            let update = MoneyStakeUpdate { nullifiers: new_nullifiers, coins: new_coins };
-            let mut update_data = vec![];
-            update_data.write_u8(MoneyFunction::Unstake as u8)?;
-            update.encode(&mut update_data)?;
-            set_return_data(&update_data)?;
-            msg!("[Unstake] State update set!");
-
-            Ok(())
-        }
-
-        MoneyFunction::Mint => {
-            msg!("[Mint] Entered match arm");
-            let update_data = money_mint_process_instruction(cid, call_idx, calls)?;
-            set_return_data(&update_data)?;
-            msg!("[Mint] State update set!");
-            Ok(())
-        }
-
-        MoneyFunction::Freeze => {
-            msg!("[Freeze] Entered match arm");
-            unimplemented!();
-        }
-    }
-}
-
-#[cfg(not(feature = "no-entrypoint"))]
-fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
-    match MoneyFunction::try_from(update_data[0])? {
-        MoneyFunction::Transfer => {
-            let update: MoneyTransferUpdate = deserialize(&update_data[1..])?;
-            money_transfer_process_update(cid, update)?;
-            Ok(())
-        }
-
-        MoneyFunction::OtcSwap => {
-            let update: MoneyTransferUpdate = deserialize(&update_data[1..])?;
-            money_otcswap_process_update(cid, update)?;
-            Ok(())
-        }
-
-        MoneyFunction::Stake | MoneyFunction::Unstake => {
-            let update: MoneyStakeUpdate = deserialize(&update_data[1..])?;
-
-            let info_db = db_lookup(cid, MONEY_CONTRACT_LEAD_INFO_TREE)?;
-            let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_LEAD_NULLIFIERS_TREE)?;
-            let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_LEAD_COIN_ROOTS_TREE)?;
-
-            for nullifier in update.nullifiers {
-                db_set(nullifiers_db, &serialize(&nullifier), &[])?;
-            }
-
-            msg!("Adding coins {:?} to Merkle tree", update.coins);
-            let coins: Vec<_> = update.coins.iter().map(|x| MerkleNode::from(x.inner())).collect();
-            merkle_add(
-                info_db,
-                coin_roots_db,
-                &serialize(&MONEY_CONTRACT_LEAD_COIN_MERKLE_TREE),
-                &coins,
-            )?;
-
-            Ok(())
-        }
-
-        MoneyFunction::Mint => {
-            let update: MoneyMintUpdate = deserialize(&update_data[1..])?;
-            money_mint_process_update(cid, update)?;
-            Ok(())
-        }
-
-        MoneyFunction::Freeze => {
-            msg!("[Freeze] Entered match arm");
-            unimplemented!();
-        }
-    }
-}

+ 63 - 104
src/contract/money/src/model.rs

@@ -16,75 +16,66 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi_sdk::crypto::{
-    pallas, Coin, MerkleNode, Nullifier, PublicKey, TokenId, ValueBlind, ValueCommit,
+use darkfi_sdk::{
+    crypto::{note::AeadEncryptedNote, Coin, MerkleNode, Nullifier, PublicKey, TokenId},
+    pasta::pallas,
 };
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 
+/// A contract call's clear input
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct MoneyMintParams {
-    pub input: ClearInput,
-    pub output: Output,
-}
-
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct MoneyMintUpdate {
-    pub coin: Coin,
-}
-
-/// Inputs and outputs for staking coins
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct MoneyStakeParams {
-    /// Anonymous inputs
-    pub inputs: Vec<Input>,
-    /// Anonymous outputs for staking
-    pub outputs: Vec<StakedOutput>,
-    /// Token blind to reveal token ID
-    pub token_blind: ValueBlind,
-}
-
-/// Inputs and outputs for unstaking coins
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct MoneyUnstakeParams {
-    /// Anonymous staked inputs
-    pub inputs: Vec<StakedInput>,
-    /// Anonymous outputs
-    pub outputs: Vec<Output>,
-    /// Token blind to reveal token ID
-    pub token_blind: ValueBlind,
+pub struct ClearInput {
+    /// Input's value (amount)
+    pub value: u64,
+    /// Input's token ID
+    pub token_id: TokenId,
+    /// Blinding factor for `value`
+    pub value_blind: pallas::Scalar,
+    /// Blinding factor for `token_id`
+    pub token_blind: pallas::Scalar,
+    /// Public key for the signature
+    pub signature_public: PublicKey,
 }
 
-/// Staked anonymous input
+/// A contract call's anonymous input
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct StakedInput {
+pub struct Input {
+    /// Pedersen commitment for the input's value
+    pub value_commit: pallas::Point,
+    /// Pedersen commitment for the input's token ID
+    pub token_commit: pallas::Point,
     /// Revealed nullifier
     pub nullifier: Nullifier,
-    /// Pedersen commitment for the output's value
-    pub value_commit: ValueCommit,
-    /// Minted coin
-    pub coin_commit_hash: pallas::Base,
-    /// coin pk hash
-    pub coin_pk_hash: pallas::Base,
-    /// coin commitment root
-    pub coin_commit_root: MerkleNode,
-    /// sk root of merkle tree
-    pub sk_root: MerkleNode,
+    /// Revealed Merkle root
+    pub merkle_root: MerkleNode,
+    /// Spend hook used to invoke other contracts.
+    /// If this value is nonzero then the subsequent contract call in the tx
+    /// must have this value as its ID.
+    pub spend_hook: pallas::Base,
+    /// Encrypted user data field. An encrypted commitment to arbitrary data.
+    /// When spend hook is set (it is nonzero), then this field may be user
+    /// to pass data to the invoked contract.
+    pub user_data_enc: pallas::Base,
+    /// Public key for the signature
+    pub signature_public: PublicKey,
 }
 
-/// Staked anonymous output
+/// A contract call's anonymous output
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct StakedOutput {
+pub struct Output {
     /// Pedersen commitment for the output's value
-    pub value_commit: ValueCommit,
+    pub value_commit: pallas::Point,
+    /// Pedersen commitment for the output's token ID
+    pub token_commit: pallas::Point,
     /// Minted coin
-    pub coin_commit_hash: pallas::Base,
-    /// coin pk hash
-    pub coin_pk_hash: pallas::Base,
+    pub coin: pallas::Base,
+    /// AEAD encrypted note
+    pub note: AeadEncryptedNote,
 }
 
-/// Inputs and outputs for a payment
+/// Parameters for `Money::Transfer` and `Money::OtcSwap`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct MoneyTransferParams {
+pub struct MoneyTransferParamsV1 {
     /// Clear inputs
     pub clear_inputs: Vec<ClearInput>,
     /// Anonymous inputs
@@ -93,74 +84,42 @@ pub struct MoneyTransferParams {
     pub outputs: Vec<Output>,
 }
 
-/// State update produced by a payment
+/// State update for `Money::Transfer` and `Money::OtcSwap`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct MoneyTransferUpdate {
+pub struct MoneyTransferUpdateV1 {
     /// Revealed nullifiers
     pub nullifiers: Vec<Nullifier>,
     /// Minted coins
     pub coins: Vec<Coin>,
 }
 
-/// State update produced by a staking
+/// Parameters for `Money::Mint`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct MoneyStakeUpdate {
-    /// Revealed nullifiers
-    pub nullifiers: Vec<Nullifier>,
-    /// Minted coins
-    pub coins: Vec<Coin>,
+pub struct MoneyMintParamsV1 {
+    /// Clear input
+    pub input: ClearInput,
+    /// Anonymous output
+    pub output: Output,
 }
 
-/// A transaction's clear input
+/// State update for `Money::Mint`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct ClearInput {
-    /// Input's value (amount)
-    pub value: u64,
-    /// Input's token ID
-    pub token_id: TokenId,
-    /// Blinding factor for `value`
-    pub value_blind: ValueBlind,
-    /// Blinding factor for `token_id`
-    pub token_blind: ValueBlind,
-    /// Public key for the signature
-    pub signature_public: PublicKey,
+pub struct MoneyMintUpdateV1 {
+    /// The newly minted coin
+    pub coin: Coin,
 }
 
-/// A transaction's anonymous input
+/// Parameters for `Money::Freeze`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct Input {
-    /// Pedersen commitment for the input's value
-    pub value_commit: ValueCommit,
-    /// Pedersen commitment for the input's token ID
-    pub token_commit: ValueCommit,
-    /// Revealed nullifier
-    pub nullifier: Nullifier,
-    /// Revealed Merkle root
-    pub merkle_root: MerkleNode,
-    /// Spend hook used to invoke other contracts.
-    /// If this value is nonzero then the subsequent contract call in the tx
-    /// must have this value as its ID.
-    pub spend_hook: pallas::Base,
-    /// Encrypted user data field. An encrypted commitment to arbitrary data.
-    /// When spend hook is set (it is nonzero), then this field may be used
-    /// to pass data to the invoked contract.
-    pub user_data_enc: pallas::Base,
-    /// Public key for the signature
+pub struct MoneyFreezeParamsV1 {
+    /// Mint authority public key
+    /// We also use this to derive the token ID
     pub signature_public: PublicKey,
 }
 
-/// A transaction's anonymous output
+/// State update for `Money::Freeze`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct Output {
-    /// Pedersen commitment for the output's value
-    pub value_commit: ValueCommit,
-    /// Pedersen commitment for the output's token ID
-    pub token_commit: ValueCommit,
-    /// Minted coin
-    pub coin: pallas::Base,
-    //pub coin: Coin,
-    /// The encrypted note ciphertext
-    pub ciphertext: Vec<u8>,
-    /// The ephemeral public key
-    pub ephem_public: PublicKey,
+pub struct MoneyFreezeUpdateV1 {
+    /// Mint authority public key
+    pub signature_public: PublicKey,
 }

+ 0 - 150
src/contract/money/src/swap.rs

@@ -1,150 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use darkfi_sdk::{
-    crypto::{Coin, ContractId},
-    db::{db_contains_key, db_lookup},
-    error::ContractError,
-    msg,
-    pasta::pallas,
-    ContractCall,
-};
-use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
-
-use crate::{
-    money_transfer_get_metadata, money_transfer_process_update, MoneyFunction, MoneyTransferParams,
-    MoneyTransferUpdate, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
-};
-
-pub fn money_otcswap_get_metadata(
-    cid: ContractId,
-    call_idx: u32,
-    calls: Vec<ContractCall>,
-) -> Result<Vec<u8>, ContractError> {
-    Ok(money_transfer_get_metadata(cid, call_idx, calls)?)
-}
-
-pub fn money_otcswap_process_instruction(
-    cid: ContractId,
-    call_idx: u32,
-    calls: Vec<ContractCall>,
-) -> Result<Vec<u8>, ContractError> {
-    let self_ = &calls[call_idx as usize];
-    let params: MoneyTransferParams = deserialize(&self_.data[1..])?;
-
-    // State transition for OTC atomic swaps.
-    // We enforce 2 inputs and 2 outputs so every atomic swap looks the same.
-    if !params.clear_inputs.is_empty() {
-        msg!("[OtcSwap] Error: Clear inputs are not empty");
-        return Err(ContractError::Custom(12))
-    }
-
-    if params.inputs.len() != 2 {
-        msg!("[OtcSwap] Error: Expected 2 inputs");
-        return Err(ContractError::Custom(13))
-    }
-
-    if params.outputs.len() != 2 {
-        msg!("[OtcSwap] Error: Expected 2 outputs");
-        return Err(ContractError::Custom(14))
-    }
-
-    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
-    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
-
-    let mut new_nullifiers = Vec::with_capacity(2);
-
-    // inputs[0] is being swapped to outputs[1]
-    // inputs[1] is being swapped to outputs[0]
-    // So that's how we check the value and token commitments
-    if params.inputs[0].value_commit != params.outputs[1].value_commit {
-        msg!("[OtcSwap] Error: Value commitments for input 0 and output 1 do not match");
-        return Err(ContractError::Custom(10))
-    }
-
-    if params.inputs[1].value_commit != params.outputs[0].value_commit {
-        msg!("[OtcSwap] Error: Value commitments for input 1 and output 0 do not match");
-        return Err(ContractError::Custom(10))
-    }
-
-    if params.inputs[0].token_commit != params.outputs[1].token_commit {
-        msg!("[OtcSwap] Error: Token commitments for input 0 and output 1 do not match");
-        return Err(ContractError::Custom(11))
-    }
-
-    if params.inputs[1].token_commit != params.outputs[0].token_commit {
-        msg!("[OtcSwap] Error: Token commitments for input 1 and output 0 do not match");
-        return Err(ContractError::Custom(11))
-    }
-
-    msg!("[OtcSwap] Iternating over anonymous inputs");
-    for (i, input) in params.inputs.iter().enumerate() {
-        // For now, make sure that the inputs' spend hooks are zero.
-        // This should however be allowed to some extent.
-        if input.spend_hook != pallas::Base::zero() {
-            msg!("[OtcSwap] Error: Unable to swap coins with spend_hook != 0 (input {})", i);
-            return Err(ContractError::Custom(17))
-        }
-
-        // The Merkle root is used to know whether this coin existed
-        // in a previous state.
-        if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
-            msg!("[OtcSwap] Error: Merkle root not found in previous state (input {})", i);
-            return Err(ContractError::Custom(5))
-        }
-
-        // The nullifiers should not already exist. It is the double-spend protection.
-        if new_nullifiers.contains(&input.nullifier) ||
-            db_contains_key(nullifiers_db, &serialize(&input.nullifier))?
-        {
-            msg!("[OtcSwap] Error: Duplicate nullifier found in input {}", i);
-            return Err(ContractError::Custom(6))
-        }
-
-        new_nullifiers.push(input.nullifier);
-    }
-
-    // Newly created coins for this transaction are in the outputs.
-    let mut new_coins = Vec::with_capacity(2);
-    for (i, output) in params.outputs.iter().enumerate() {
-        // TODO: Coins should exist in a sled tree in order to check dupes.
-        if new_coins.contains(&Coin::from(output.coin)) {
-            msg!("[OtcSwap] Error: Duplicate coin found in output {}", i);
-            return Err(ContractError::Custom(9))
-        }
-
-        new_coins.push(Coin::from(output.coin));
-    }
-
-    // Create a state update. We also use the `MoneyTransferUpdate` because
-    // they're essentially the same thing, just with a different transition
-    // rule set.
-    let update = MoneyTransferUpdate { nullifiers: new_nullifiers, coins: new_coins };
-    let mut update_data = vec![];
-    update_data.write_u8(MoneyFunction::OtcSwap as u8)?;
-    update.encode(&mut update_data)?;
-
-    Ok(update_data)
-}
-
-pub fn money_otcswap_process_update(
-    cid: ContractId,
-    update: MoneyTransferUpdate,
-) -> Result<(), ContractError> {
-    Ok(money_transfer_process_update(cid, update)?)
-}

+ 0 - 251
src/contract/money/src/transfer.rs

@@ -1,251 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use darkfi_sdk::{
-    crypto::{
-        pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, Coin, ContractId,
-        MerkleNode, PublicKey, DARK_TOKEN_ID,
-    },
-    db::{db_contains_key, db_get, db_lookup, db_set},
-    error::ContractError,
-    merkle_add, msg,
-    pasta::pallas,
-    ContractCall,
-};
-use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
-
-use crate::{
-    MoneyFunction, MoneyTransferParams, MoneyTransferUpdate, MONEY_CONTRACT_COIN_MERKLE_TREE,
-    MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_FAUCET_PUBKEYS, MONEY_CONTRACT_INFO_TREE,
-    MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
-};
-
-pub fn money_transfer_get_metadata(
-    _cid: ContractId,
-    call_idx: u32,
-    calls: Vec<ContractCall>,
-) -> Result<Vec<u8>, ContractError> {
-    let self_ = &calls[call_idx as usize];
-    let params: MoneyTransferParams = deserialize(&self_.data[1..])?;
-
-    let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
-    let mut signature_pubkeys: Vec<PublicKey> = vec![];
-
-    for input in &params.clear_inputs {
-        signature_pubkeys.push(input.signature_public);
-    }
-
-    for input in &params.inputs {
-        let value_coords = input.value_commit.to_affine().coordinates().unwrap();
-        let token_coords = input.token_commit.to_affine().coordinates().unwrap();
-        let (sig_x, sig_y) = input.signature_public.xy();
-
-        zk_public_values.push((
-            MONEY_CONTRACT_ZKAS_BURN_NS_V1.to_string(),
-            vec![
-                input.nullifier.inner(),
-                *value_coords.x(),
-                *value_coords.y(),
-                *token_coords.x(),
-                *token_coords.y(),
-                input.merkle_root.inner(),
-                input.user_data_enc,
-                sig_x,
-                sig_y,
-            ],
-        ));
-
-        signature_pubkeys.push(input.signature_public);
-    }
-
-    for output in &params.outputs {
-        let value_coords = output.value_commit.to_affine().coordinates().unwrap();
-        let token_coords = output.token_commit.to_affine().coordinates().unwrap();
-
-        zk_public_values.push((
-            MONEY_CONTRACT_ZKAS_MINT_NS_V1.to_string(),
-            vec![
-                output.coin,
-                *value_coords.x(),
-                *value_coords.y(),
-                *token_coords.x(),
-                *token_coords.y(),
-            ],
-        ));
-    }
-
-    let mut metadata = vec![];
-    zk_public_values.encode(&mut metadata)?;
-    signature_pubkeys.encode(&mut metadata)?;
-
-    Ok(metadata)
-}
-
-pub fn money_transfer_process_instruction(
-    cid: ContractId,
-    call_idx: u32,
-    calls: Vec<ContractCall>,
-) -> Result<Vec<u8>, ContractError> {
-    let self_ = &calls[call_idx as usize];
-    let params: MoneyTransferParams = deserialize(&self_.data[1..])?;
-
-    if params.clear_inputs.len() + params.inputs.len() < 1 {
-        msg!("[Transfer] Error: No inputs in the call");
-        return Err(ContractError::Custom(1))
-    }
-
-    if params.outputs.is_empty() {
-        msg!("[Transfer] Error: No outputs in the call");
-        return Err(ContractError::Custom(2))
-    }
-
-    let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
-    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
-    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
-
-    let Some(faucet_pubkeys) = db_get(info_db, &serialize(&MONEY_CONTRACT_FAUCET_PUBKEYS))? else {
-        msg!("[Transfer] Error: Missing faucet pubkeys from info db");
-        return Err(ContractError::Internal)
-    };
-    let faucet_pubkeys: Vec<PublicKey> = deserialize(&faucet_pubkeys)?;
-
-    // Accumulator for the value commitments
-    let mut valcom_total = pallas::Point::identity();
-
-    // State transition for payments
-    msg!("[Transfer] Iterating over clear inputs");
-    for (i, input) in params.clear_inputs.iter().enumerate() {
-        if input.token_id != *DARK_TOKEN_ID {
-            msg!("[Transfer] Error: Clear input {} used non-native token", i);
-            return Err(ContractError::Custom(3))
-        }
-
-        if !faucet_pubkeys.contains(&input.signature_public) {
-            msg!("[Transfer] Error: Clear input {} used unauthorised pubkey", i);
-            return Err(ContractError::Custom(4))
-        }
-
-        valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
-    }
-
-    let mut new_nullifiers = Vec::with_capacity(params.inputs.len());
-    msg!("[Transfer] Iterating over anonymous inputs");
-    for (i, input) in params.inputs.iter().enumerate() {
-        // The Merkle root is used to know whether this is a coin that
-        // existed in a previous state.
-        if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
-            msg!("[Transfer] Error: Merkle root not found in previous state (input {})", i);
-            return Err(ContractError::Custom(5))
-        }
-
-        // The nullifiers should not already exist. It is the double-spend protection.
-        if new_nullifiers.contains(&input.nullifier) ||
-            db_contains_key(nullifiers_db, &serialize(&input.nullifier))?
-        {
-            msg!("[Transfer] Error: Duplicate nullifier found in input {}", i);
-            return Err(ContractError::Custom(6))
-        }
-
-        // Check the invoked contract if spend hook is set
-        if input.spend_hook != pallas::Base::zero() {
-            let next_call_idx = call_idx + 1;
-            if next_call_idx >= calls.len() as u32 {
-                msg!(
-                    "[Transfer] Error: next_call_idx={} but len(calls)={} (input {})",
-                    next_call_idx,
-                    calls.len(),
-                    i
-                );
-                return Err(ContractError::Custom(7))
-            }
-
-            let next = &calls[next_call_idx as usize];
-            if next.contract_id.inner() != input.spend_hook {
-                msg!("[Transfer] Error: Invoking contract call does not match spend hook in input {}", i);
-                return Err(ContractError::Custom(8))
-            }
-        }
-
-        new_nullifiers.push(input.nullifier);
-        valcom_total += input.value_commit;
-    }
-
-    // Newly created coins for this transaction are in the outputs.
-    let mut new_coins = Vec::with_capacity(params.outputs.len());
-    for (i, output) in params.outputs.iter().enumerate() {
-        // TODO: Coins should exist in a sled tree in order to check dupes.
-        if new_coins.contains(&Coin::from(output.coin)) {
-            msg!("[Transfer] Error: Duplicate coin found in output {}", i);
-            return Err(ContractError::Custom(9))
-        }
-
-        // FIXME: Needs some work on types and their place within all these libraries
-        new_coins.push(Coin::from(output.coin));
-        valcom_total -= output.value_commit;
-    }
-
-    // If the accumulator is not back in its initial state, there's a value mismatch.
-    if valcom_total != pallas::Point::identity() {
-        msg!("[Transfer] Error: Value commitments do not result in identity");
-        return Err(ContractError::Custom(10))
-    }
-
-    // Verify that the token commitments are all for the same token
-    let tokcom = params.outputs[0].token_commit;
-    let mut failed_tokcom = params.inputs.iter().any(|input| input.token_commit != tokcom);
-    failed_tokcom =
-        failed_tokcom || params.outputs.iter().any(|output| output.token_commit != tokcom);
-    failed_tokcom = failed_tokcom ||
-        params.clear_inputs.iter().any(|input| {
-            pedersen_commitment_base(input.token_id.inner(), input.token_blind) != tokcom
-        });
-
-    if failed_tokcom {
-        msg!("[Transfer] Error: Token commitments do not match");
-        return Err(ContractError::Custom(11))
-    }
-
-    // Create a state update
-    let update = MoneyTransferUpdate { nullifiers: new_nullifiers, coins: new_coins };
-    let mut update_data = vec![];
-    update_data.write_u8(MoneyFunction::Transfer as u8)?;
-    update.encode(&mut update_data)?;
-
-    Ok(update_data)
-}
-
-pub fn money_transfer_process_update(
-    cid: ContractId,
-    update: MoneyTransferUpdate,
-) -> Result<(), ContractError> {
-    let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
-    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
-    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
-
-    msg!("[Transfer] Adding new nullifiers to the set");
-    for nullifier in update.nullifiers {
-        db_set(nullifiers_db, &serialize(&nullifier), &[])?;
-    }
-
-    let coins: Vec<_> = update.coins.iter().map(|x| MerkleNode::from(x.inner())).collect();
-
-    msg!("[Transfer] Adding new coins to Merkle tree");
-    merkle_add(info_db, coin_roots_db, &serialize(&MONEY_CONTRACT_COIN_MERKLE_TREE), &coins)?;
-
-    Ok(())
-}