diff --git a/src/contract/money/Cargo.toml b/src/contract/money/Cargo.toml
index 0a5582344..564563a80 100644
--- a/src/contract/money/Cargo.toml
+++ b/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
diff --git a/src/contract/money/proof/token_freeze_v1.zk b/src/contract/money/proof/token_freeze_v1.zk
index 679e284fa..95aed526f 100644
--- a/src/contract/money/proof/token_freeze_v1.zk
+++ b/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);
}
diff --git a/src/contract/money/src/client.rs b/src/contract/money/src/client.rs
deleted file mode 100644
index 78dd7baf8..000000000
--- a/src/contract/money/src/client.rs
+++ /dev/null
@@ -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 .
- */
-
-//! 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 {
- match pallas::Base::from_repr(bytes).into() {
- Some(v) => Ok(Self(v)),
- None => Err(Error::CoinFromBytes),
- }
- }
-}
-
-impl From 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,
-}
-
-impl Note {
- /// Encrypt the note to some given `PublicKey` using an AEAD cipher.
- pub fn encrypt(&self, public_key: &PublicKey) -> Result {
- 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,
- /// 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 {
- 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,
- 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,
- 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(), ¤t, sibling)
- } else {
- MerkleNode::combine(level.into(), sibling, ¤t)
- };
- }
- 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 {
- 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 {
- 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,
- 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 {
- 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 {
- 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,
- commitment_merkle_path: Vec,
- 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,
- Vec,
- Vec,
- Vec,
- Vec,
-)> {
- 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;
- let tok_blinds: Vec;
-
- // 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, Vec, Vec)> {
- 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(¶ms.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, Vec, Vec, Vec)> {
- // convert owncoins to leadcoins.
- // TODO: verify this token blind usage
- let token_blind = ValueBlind::random(&mut OsRng);
- let mut leadcoins: Vec = 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, Vec, Vec, Vec)> {
- // convert leadcoin to owncoin
- // TODO: verify this token blind usage
- let token_blind = ValueBlind::random(&mut OsRng);
- //let owncoins : Vec= 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);
- }
-}
diff --git a/src/contract/money/src/client/token_mint.rs b/src/contract/money/src/client/token_mint.rs
deleted file mode 100644
index 2fa71ee6f..000000000
--- a/src/contract/money/src/client/token_mint.rs
+++ /dev/null
@@ -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 .
- */
-
-//! 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 {
- 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 {
- 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)
- }
-}
diff --git a/src/contract/money/src/entrypoint.rs b/src/contract/money/src/entrypoint.rs
new file mode 100644
index 000000000..05a398a58
--- /dev/null
+++ b/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 .
+ */
+
+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 = 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) = 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) = 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)?)
+ }
+ }
+}
diff --git a/src/contract/money/src/entrypoint/freeze_v1.rs b/src/contract/money/src/entrypoint/freeze_v1.rs
new file mode 100644
index 000000000..7b43fd33e
--- /dev/null
+++ b/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 .
+ */
+
+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,
+) -> Result, 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)> = vec![];
+ // Public keys for the transaction signatures we have to verify
+ let signature_pubkeys: Vec = 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,
+) -> Result, 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(())
+}
diff --git a/src/contract/money/src/mint.rs b/src/contract/money/src/entrypoint/mint_v1.rs
similarity index 54%
rename from src/contract/money/src/mint.rs
rename to src/contract/money/src/entrypoint/mint_v1.rs
index 2703bfcdf..bac69158b 100644
--- a/src/contract/money/src/mint.rs
+++ b/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,
) -> Result, 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)> = vec![];
+ // Public inputs for the ZK proofs we have to verify
+ let mut zk_public_inputs: Vec<(String, Vec)> = vec![];
+ // Public keys for the transaction signatures we have to verify
let mut signature_pubkeys: Vec = 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,
) -> Result, 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(¶ms.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(())
diff --git a/src/contract/money/src/entrypoint/swap_v1.rs b/src/contract/money/src/entrypoint/swap_v1.rs
new file mode 100644
index 000000000..1311902f8
--- /dev/null
+++ b/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 .
+ */
+
+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,
+) -> Result, 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,
+) -> Result, 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)?)
+}
diff --git a/src/contract/money/src/entrypoint/transfer_v1.rs b/src/contract/money/src/entrypoint/transfer_v1.rs
new file mode 100644
index 000000000..9f6a35b6f
--- /dev/null
+++ b/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 .
+ */
+
+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,
+) -> Result, 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)> = vec![];
+ // Public keys for the transaction signatures we have to verify
+ let mut signature_pubkeys: Vec = vec![];
+
+ // Take all the pubkeys from any clear inputs
+ for input in ¶ms.clear_inputs {
+ signature_pubkeys.push(input.signature_public);
+ }
+
+ // Grab the pedersen commitments and signature pubkeys from the
+ // anonymous inputs
+ for input in ¶ms.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 ¶ms.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,
+) -> Result, 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 = 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(())
+}
diff --git a/src/contract/money/src/error.rs b/src/contract/money/src/error.rs
new file mode 100644
index 000000000..a27c8d861
--- /dev/null
+++ b/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 .
+ */
+
+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 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),
+ }
+ }
+}
diff --git a/src/contract/money/src/lib.rs b/src/contract/money/src/lib.rs
index 9621157e5..1bc0bf804 100644
--- a/src/contract/money/src/lib.rs
+++ b/src/contract/money/src/lib.rs
@@ -16,98 +16,59 @@
* along with this program. If not, see .
*/
-#[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 for MoneyFunction {
type Error = ContractError;
- fn try_from(b: u8) -> core::result::Result {
+ fn try_from(b: u8) -> core::result::Result {
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
+/// Internal contract errors
+pub mod error;
+
+#[cfg(not(feature = "no-entrypoint"))]
+/// WASM entrypoint functions
+pub mod entrypoint;
+
+/// Call parameters 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,
-};
-
-#[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;
-
-#[cfg(not(feature = "no-entrypoint"))]
-darkfi_sdk::define_contract!(
- init: init_contract,
- exec: process_instruction,
- apply: process_update,
- metadata: get_metadata
-);
-
// 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 = 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) = 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)> = vec![];
- let mut signature_pubkeys: Vec = vec![];
-
- for input in ¶ms.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 ¶ms.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)> = vec![];
-
- for input in ¶ms.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 ¶ms.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) = 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!();
- }
- }
-}
diff --git a/src/contract/money/src/model.rs b/src/contract/money/src/model.rs
index 6e178eb23..0f7e11ee2 100644
--- a/src/contract/money/src/model.rs
+++ b/src/contract/money/src/model.rs
@@ -16,102 +16,13 @@
* along with this program. If not, see .
*/
-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};
-#[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,
- /// Anonymous outputs for staking
- pub outputs: Vec,
- /// 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,
- /// Anonymous outputs
- pub outputs: Vec