Răsfoiți Sursa

apply DEP 0003: Token Mint Authorization

zero 2 ani în urmă
părinte
comite
7edb0cd217
35 a modificat fișierele cu 767 adăugiri și 458 ștergeri
  1. 1 1
      doc/src/SUMMARY.md
  2. 1 5
      doc/src/dep/0003.md
  3. 2 4
      src/contract/dao/src/client/auth_xfer.rs
  4. 1 2
      src/contract/dao/src/client/exec.rs
  5. 2 4
      src/contract/dao/src/client/propose.rs
  6. 2 4
      src/contract/dao/src/client/vote.rs
  7. 64 0
      src/contract/money/proof/auth_token_mint_v1.zk
  8. 11 7
      src/contract/money/proof/token_freeze_v1.zk
  9. 23 48
      src/contract/money/proof/token_mint_v1.zk
  10. 122 0
      src/contract/money/src/client/auth_token_mint_v1.rs
  11. 3 3
      src/contract/money/src/client/genesis_mint_v1.rs
  12. 42 0
      src/contract/money/src/client/mod.rs
  13. 24 45
      src/contract/money/src/client/token_freeze_v1.rs
  14. 31 180
      src/contract/money/src/client/token_mint_v1.rs
  15. 21 2
      src/contract/money/src/entrypoint.rs
  16. 112 0
      src/contract/money/src/entrypoint/auth_token_mint_v1.rs
  17. 9 12
      src/contract/money/src/entrypoint/token_freeze_v1.rs
  18. 13 55
      src/contract/money/src/entrypoint/token_mint_v1.rs
  19. 4 0
      src/contract/money/src/lib.rs
  20. 33 8
      src/contract/money/src/model.rs
  21. 12 6
      src/contract/money/tests/genesis_mint.rs
  22. 2 2
      src/contract/money/tests/integration.rs
  23. 10 8
      src/contract/money/tests/mint_pay_swap.rs
  24. 8 4
      src/contract/money/tests/pow_reward.rs
  25. 7 2
      src/contract/money/tests/token_mint.rs
  26. 11 4
      src/contract/money/tests/txs_verification.rs
  27. 4 2
      src/contract/money/tests/verification_bench.rs
  28. 20 12
      src/contract/test-harness/src/lib.rs
  29. 1 1
      src/contract/test-harness/src/money_airdrop.rs
  30. 4 4
      src/contract/test-harness/src/money_genesis_mint.rs
  31. 97 29
      src/contract/test-harness/src/money_token.rs
  32. 7 4
      src/contract/test-harness/src/vks.rs
  33. 48 0
      src/sdk/src/crypto/func_ref.rs
  34. 4 0
      src/sdk/src/crypto/mod.rs
  35. 11 0
      src/sdk/src/crypto/token_id.rs

+ 1 - 1
doc/src/SUMMARY.md

@@ -67,7 +67,7 @@
 
 - [DEP 0001: Version Message Info (accepted)](dep/0001.md)
 - [DEP 0002: Smart Contract Composability (deprecated)](dep/0002.md)
-- [DEP 0003: Token Mint Authorization (draft)](dep/0003.md)
+- [DEP 0003: Token Mint Authorization (accepted)](dep/0003.md)
 
 # Specs
 

+ 1 - 5
doc/src/dep/0003.md

@@ -1,7 +1,7 @@
 # DEP 0003: Token Mint Authorization
 
 ```
-status: draft
+status: accepted
 ```
 
 ## Current Situation
@@ -43,8 +43,6 @@ For each coin $Cᵢ$, let there be corresponding proofs $πᵢ$ such that
 **Token ID integrity**   $T$ is calculated correctly committing to
 `auth_parent`.
 
-**User data commitment**   $U = \t{PoseidonHash}(u, bᵤ)$
-
 **Coin commitment integrity**   $Cᵢ = \t{PoseidonHash}(…, T, …)$
 
 Additionally the contract checks that `auth_parent` is the function ID of
@@ -69,8 +67,6 @@ The contract performs the following checks:
 * Constructs a pedersen commit $V$ to the value in the coin, along with a proof.
   This allows auditing the supply since all commitments are linked publicly with
   the token ID.
-* Unwrap the `user_data` exported from `Money::token_mint_v1()` which should
-  be a commitment to the public key. Prove ownership of the public key.
 
 ### `Money::auth_mint_freeze_v1()`
 

+ 2 - 4
src/contract/dao/src/client/auth_xfer.rs

@@ -97,8 +97,7 @@ impl DaoAuthMoneyTransferCall {
 
             let circuit = ZkCircuit::new(prover_witnesses, auth_xfer_enc_coin_zkbin);
             let proof =
-                Proof::create(auth_xfer_enc_coin_pk, &[circuit], &public_inputs, &mut OsRng)
-                    .expect("DAO::exec() proving error!)");
+                Proof::create(auth_xfer_enc_coin_pk, &[circuit], &public_inputs, &mut OsRng)?;
             proofs.push(proof);
 
             enc_attrs.push(enc_note);
@@ -169,8 +168,7 @@ impl DaoAuthMoneyTransferCall {
         ];
 
         let circuit = ZkCircuit::new(prover_witnesses, auth_xfer_zkbin);
-        let proof = Proof::create(auth_xfer_pk, &[circuit], &public_inputs, &mut OsRng)
-            .expect("DAO::exec() proving error!)");
+        let proof = Proof::create(auth_xfer_pk, &[circuit], &public_inputs, &mut OsRng)?;
         proofs.push(proof);
 
         Ok((params, proofs))

+ 1 - 2
src/contract/dao/src/client/exec.rs

@@ -109,8 +109,7 @@ impl DaoExecCall {
         //export_witness_json("witness.json", &prover_witnesses, &public_inputs);
 
         let circuit = ZkCircuit::new(prover_witnesses, exec_zkbin);
-        let input_proof = Proof::create(exec_pk, &[circuit], &public_inputs, &mut OsRng)
-            .expect("DAO::exec() proving error!)");
+        let input_proof = Proof::create(exec_pk, &[circuit], &public_inputs, &mut OsRng)?;
         proofs.push(input_proof);
 
         let params = DaoExecParams {

+ 2 - 4
src/contract/dao/src/client/propose.rs

@@ -143,8 +143,7 @@ impl DaoProposeCall {
             let circuit = ZkCircuit::new(prover_witnesses, burn_zkbin);
 
             let proving_key = &burn_pk;
-            let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
-                .expect("DAO::propose() proving error!");
+            let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)?;
             proofs.push(input_proof);
 
             let input =
@@ -203,8 +202,7 @@ impl DaoProposeCall {
         ];
         let circuit = ZkCircuit::new(prover_witnesses, main_zkbin);
 
-        let main_proof = Proof::create(main_pk, &[circuit], &public_inputs, &mut OsRng)
-            .expect("DAO::propose() proving error!");
+        let main_proof = Proof::create(main_pk, &[circuit], &public_inputs, &mut OsRng)?;
         proofs.push(main_proof);
 
         let enc_note =

+ 2 - 4
src/contract/dao/src/client/vote.rs

@@ -166,8 +166,7 @@ impl DaoVoteCall {
 
             let circuit = ZkCircuit::new(prover_witnesses, burn_zkbin);
             debug!(target: "dao", "input_proof Proof::create()");
-            let input_proof = Proof::create(burn_pk, &[circuit], &public_inputs, &mut OsRng)
-                .expect("DAO::vote() proving error!");
+            let input_proof = Proof::create(burn_pk, &[circuit], &public_inputs, &mut OsRng)?;
             proofs.push(input_proof);
 
             let input = DaoVoteParamsInput {
@@ -270,8 +269,7 @@ impl DaoVoteCall {
         let circuit = ZkCircuit::new(prover_witnesses, main_zkbin);
 
         debug!(target: "dao", "main_proof = Proof::create()");
-        let main_proof = Proof::create(main_pk, &[circuit], &public_inputs, &mut OsRng)
-            .expect("DAO::vote() proving error!");
+        let main_proof = Proof::create(main_pk, &[circuit], &public_inputs, &mut OsRng)?;
         proofs.push(main_proof);
 
         let params =

+ 64 - 0
src/contract/money/proof/auth_token_mint_v1.zk

@@ -0,0 +1,64 @@
+# Circuit used to mint arbitrary coins given a mint authority secret.
+k = 13;
+field = "pallas";
+
+constant "AuthTokenMint_V1" {
+    EcFixedPointShort VALUE_COMMIT_VALUE,
+    EcFixedPoint VALUE_COMMIT_RANDOM,
+    EcFixedPointBase NULLIFIER_K,
+}
+
+witness "AuthTokenMint_V1" {
+    # CoinAttributes {
+    Base coin_public_x,
+    Base coin_public_y,
+    Base coin_value,
+    Base coin_spend_hook,
+    Base coin_user_data,
+    Base coin_blind,
+    # }
+
+    # TokenAttributes {
+    Base token_auth_parent,
+    Base token_blind,
+    # }
+
+    # Secret key used by mint
+    Base mint_secret,
+
+    # Random blinding factor for the value commitment
+    Scalar value_commit_blind,
+}
+
+circuit "AuthTokenMint_V1" {
+    # Derive public key for the mint authority
+    mint_public = ec_mul_base(mint_secret, NULLIFIER_K);
+    mint_x = ec_get_x(mint_public);
+    mint_y = ec_get_y(mint_public);
+    constrain_instance(mint_x);
+    constrain_instance(mint_y);
+
+    # Derive the token ID
+    token_user_data = poseidon_hash(mint_x, mint_y);
+    token_id = poseidon_hash(token_auth_parent, token_user_data, token_blind);
+    constrain_instance(token_id);
+
+    # Poseidon hash of the minted coin
+    coin = poseidon_hash(
+        coin_public_x,
+        coin_public_y,
+        coin_value,
+        token_id,
+        coin_spend_hook,
+        coin_user_data,
+        coin_blind
+    );
+    constrain_instance(coin);
+
+    # Pedersen commitment for the coin's value
+    vcv = ec_mul_short(coin_value, VALUE_COMMIT_VALUE);
+    vcr = ec_mul(value_commit_blind, VALUE_COMMIT_RANDOM);
+    value_commit = ec_add(vcv, vcr);
+    constrain_instance(ec_get_x(value_commit));
+    constrain_instance(ec_get_y(value_commit));
+}

+ 11 - 7
src/contract/money/proof/token_freeze_v1.zk

@@ -6,22 +6,26 @@ constant "TokenFreeze_V1" {
 }
 
 witness "TokenFreeze_V1" {
-    # Token mint authority secret
-    Base mint_authority,
+    # TokenAttributes {
+    Base token_auth_parent,
+    Base token_blind,
+    # }
+
+    # Secret key used by mint
+    Base mint_secret,
 }
 
 circuit "TokenFreeze_V1" {
-    # TokenID derivation path (See darkfi_sdk::crypto::ContractId)
-    derivation_path = witness_base(69);
-
     # Derive public key for the mint authority
-    mint_public = ec_mul_base(mint_authority, NULLIFIER_K);
+    mint_public = ec_mul_base(mint_secret, NULLIFIER_K);
     mint_x = ec_get_x(mint_public);
     mint_y = ec_get_y(mint_public);
     constrain_instance(mint_x);
     constrain_instance(mint_y);
 
     # Derive the token ID
-    token_id = poseidon_hash(derivation_path, mint_x, mint_y);
+    token_user_data = poseidon_hash(mint_x, mint_y);
+    token_id = poseidon_hash(token_auth_parent, token_user_data, token_blind);
     constrain_instance(token_id);
 }
+

+ 23 - 48
src/contract/money/proof/token_mint_v1.zk

@@ -9,61 +9,36 @@ constant "TokenMint_V1" {
 }
 
 witness "TokenMint_V1" {
-    # Token mint authority secret
-    Base mint_authority,
-    # Token supply
-    Base supply,
-    # Recipient's public key x coordinate
-    Base rcpt_x,
-    # Recipient's public key y coordinate
-    Base rcpt_y,
-    # Unique serial number for the minted coin
-    Base serial,
-    # Allows composing this ZK proof to invoke other contracts
+    # CoinAttributes {
+    Base public_x,
+    Base public_y,
+    Base value,
     Base spend_hook,
-    # Data passed from this coin to the invoked contract
-    Base user_data,
-    # Random blinding factor for the value commitment
-    Scalar value_blind,
-    # Random blinding factor for the token ID
+    Base coin_user_data,
+    Base coin_blind,
+    # }
+
+    # TokenAttributes {
+    Base auth_parent,
+    Base token_user_data,
     Base token_blind,
+    # }
 }
 
 circuit "TokenMint_V1" {
-    # TokenID derivation path (See darkfi_sdk::crypto::TokenId)
-    derivation_path = witness_base(69);
-
-    # Derive public key for the mint authority
-    mint_public = ec_mul_base(mint_authority, NULLIFIER_K);
-    mint_x = ec_get_x(mint_public);
-    mint_y = ec_get_y(mint_public);
-    constrain_instance(mint_x);
-    constrain_instance(mint_y);
-
     # Derive the token ID
-    token_id = poseidon_hash(derivation_path, mint_x, mint_y);
-    constrain_instance(token_id);
-
-    # Poseidon hash of the minted coin
-    C = poseidon_hash(
-        rcpt_x,
-        rcpt_y,
-        supply,
+    token_id = poseidon_hash(auth_parent, token_user_data, token_blind);
+    constrain_instance(auth_parent);
+
+    # Then show the coin contains the token ID
+    coin = poseidon_hash(
+        public_x,
+        public_y,
+        value,
         token_id,
-        serial,
         spend_hook,
-        user_data,
+        coin_user_data,
+        coin_blind,
     );
-    constrain_instance(C);
-
-    # Pedersen commitment for the coin's value
-    vcv = ec_mul_short(supply, VALUE_COMMIT_VALUE);
-    vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM);
-    value_commit = ec_add(vcv, vcr);
-    constrain_instance(ec_get_x(value_commit));
-    constrain_instance(ec_get_y(value_commit));
-
-    # Commitment for the coin's token ID
-    token_commit = poseidon_hash(token_id, token_blind);
-    constrain_instance(token_commit);
+    constrain_instance(coin);
 }

+ 122 - 0
src/contract/money/src/client/auth_token_mint_v1.rs

@@ -0,0 +1,122 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi::{
+    zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit},
+    zkas::ZkBinary,
+    Result,
+};
+use darkfi_sdk::{
+    crypto::{note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_u64, Keypair},
+    pasta::pallas,
+};
+use log::info;
+use rand::rngs::OsRng;
+
+use crate::{
+    client::MoneyNote,
+    model::{CoinAttributes, MoneyAuthTokenMintParamsV1, TokenAttributes},
+};
+
+pub struct AuthTokenMintCallDebris {
+    pub params: MoneyAuthTokenMintParamsV1,
+    pub proofs: Vec<Proof>,
+}
+
+/// Struct holding necessary information to build a `Money::AuthTokenMintV1` contract call.
+pub struct AuthTokenMintCallBuilder {
+    pub coin_attrs: CoinAttributes,
+    pub token_attrs: TokenAttributes,
+
+    /// Mint authority keypair
+    pub mint_keypair: Keypair,
+
+    /// `AuthTokenMint_V1` zkas circuit ZkBinary
+    pub auth_mint_zkbin: ZkBinary,
+    /// Proving key for the `AuthTokenMint_V1` zk circuit,
+    pub auth_mint_pk: ProvingKey,
+}
+
+impl AuthTokenMintCallBuilder {
+    pub fn build(&self) -> Result<AuthTokenMintCallDebris> {
+        info!("Building Money::AuthTokenMintV1 contract call");
+
+        let value_blind = pallas::Scalar::random(&mut OsRng);
+        let value_commit = pedersen_commitment_u64(self.coin_attrs.value, value_blind);
+
+        // Create the proof
+
+        let (public_x, public_y) = self.coin_attrs.public_key.xy();
+
+        let prover_witnesses = vec![
+            // Coin attributes
+            Witness::Base(Value::known(public_x)),
+            Witness::Base(Value::known(public_y)),
+            Witness::Base(Value::known(pallas::Base::from(self.coin_attrs.value))),
+            Witness::Base(Value::known(self.coin_attrs.spend_hook)),
+            Witness::Base(Value::known(self.coin_attrs.user_data)),
+            Witness::Base(Value::known(self.coin_attrs.blind)),
+            // Token attributes
+            Witness::Base(Value::known(self.token_attrs.auth_parent.inner())),
+            Witness::Base(Value::known(self.token_attrs.blind)),
+            // Secret key used by mint
+            Witness::Base(Value::known(self.mint_keypair.secret.inner())),
+            // Random blinding factor for the value commitment
+            Witness::Scalar(Value::known(value_blind)),
+        ];
+
+        let mint_pubkey = self.mint_keypair.public;
+        let value_coords = value_commit.to_affine().coordinates().unwrap();
+
+        let public_inputs = vec![
+            mint_pubkey.x(),
+            mint_pubkey.y(),
+            self.token_attrs.to_token_id().inner(),
+            self.coin_attrs.to_coin().inner(),
+            *value_coords.x(),
+            *value_coords.y(),
+        ];
+
+        let circuit = ZkCircuit::new(prover_witnesses, &self.auth_mint_zkbin);
+        let proof = Proof::create(&self.auth_mint_pk, &[circuit], &public_inputs, &mut OsRng)?;
+
+        // Create the note
+
+        let note = MoneyNote {
+            value: self.coin_attrs.value,
+            token_id: self.coin_attrs.token_id,
+            spend_hook: self.coin_attrs.spend_hook,
+            user_data: self.coin_attrs.user_data,
+            coin_blind: self.coin_attrs.blind,
+            value_blind,
+            token_blind: pallas::Base::ZERO,
+            memo: vec![],
+        };
+
+        let enc_note = AeadEncryptedNote::encrypt(&note, &self.coin_attrs.public_key, &mut OsRng)?;
+
+        let params = MoneyAuthTokenMintParamsV1 {
+            token_id: self.token_attrs.to_token_id(),
+            value_commit,
+            enc_note,
+            mint_pubkey,
+        };
+        let debris = AuthTokenMintCallDebris { params, proofs: vec![proof] };
+        Ok(debris)
+    }
+}

+ 3 - 3
src/contract/money/src/client/genesis_mint_v1.rs

@@ -35,11 +35,11 @@ use crate::{
         },
         MoneyNote,
     },
-    model::{ClearInput, Coin, MoneyTokenMintParamsV1, Output},
+    model::{ClearInput, Coin, MoneyGenesisMintParamsV1, Output},
 };
 
 pub struct GenesisMintCallDebris {
-    pub params: MoneyTokenMintParamsV1,
+    pub params: MoneyGenesisMintParamsV1,
     pub proofs: Vec<Proof>,
 }
 
@@ -147,7 +147,7 @@ impl GenesisMintCallBuilder {
             note: encrypted_note,
         };
 
-        let params = MoneyTokenMintParamsV1 { input: c_input, output: c_output };
+        let params = MoneyGenesisMintParamsV1 { input: c_input, output: c_output };
         let debris = GenesisMintCallDebris { params, proofs: vec![proof] };
         Ok(debris)
     }

+ 42 - 0
src/contract/money/src/client/mod.rs

@@ -56,6 +56,48 @@ pub mod token_freeze_v1;
 /// `Money::PoWRewardV1` API
 pub mod pow_reward_v1;
 
+/// `Money::AuthTokenMintV1` API
+pub mod auth_token_mint_v1;
+
+// Wallet SQL table constant names. These have to represent the `wallet.sql`
+// SQL schema.
+// TODO: They should also 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_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_MINT_AUTHORITY: &str = "mint_authority";
+pub const MONEY_TOKENS_COL_TOKEN_ID: &str = "token_id";
+pub const MONEY_TOKENS_COL_IS_FROZEN: &str = "is_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";
+
 /// `MoneyNote` holds the inner attributes of a `Coin`
 /// It does not store the public key since it's encrypted for that key,
 /// and so is not needed to infer the coin attributes.

+ 24 - 45
src/contract/money/src/client/token_freeze_v1.rs

@@ -21,40 +21,26 @@ use darkfi::{
     zkas::ZkBinary,
     Result,
 };
-use darkfi_sdk::{
-    crypto::{Keypair, PublicKey, TokenId},
-    pasta::pallas,
-};
-use log::{debug, info};
+use darkfi_sdk::crypto::Keypair;
+use log::info;
 use rand::rngs::OsRng;
 
-use crate::model::MoneyTokenFreezeParamsV1;
+use crate::model::{MoneyTokenFreezeParamsV1, TokenAttributes};
 
 pub struct TokenFreezeCallDebris {
     pub params: MoneyTokenFreezeParamsV1,
     pub proofs: Vec<Proof>,
 }
 
-pub struct TokenFreezeRevealed {
-    pub signature_public: PublicKey,
-    pub token_id: TokenId,
-}
-
-impl TokenFreezeRevealed {
-    pub fn to_vec(&self) -> Vec<pallas::Base> {
-        let (sig_x, sig_y) = self.signature_public.xy();
-        vec![sig_x, sig_y, self.token_id.inner()]
-    }
-}
-
 /// Struct holding necessary information to build a `Money::TokenFreezeV1` contract call.
 pub struct TokenFreezeCallBuilder {
     /// Mint authority keypair
-    pub mint_authority: Keypair,
+    pub mint_keypair: Keypair,
+    pub token_attrs: TokenAttributes,
     /// `TokenFreeze_V1` zkas circuit ZkBinary
-    pub token_freeze_zkbin: ZkBinary,
+    pub freeze_zkbin: ZkBinary,
     /// Proving key for the `TokenFreeze_V1` zk circuit,
-    pub token_freeze_pk: ProvingKey,
+    pub freeze_pk: ProvingKey,
 }
 
 impl TokenFreezeCallBuilder {
@@ -63,32 +49,25 @@ impl TokenFreezeCallBuilder {
 
         // For the TokenFreeze call, we just need to produce a valid signature,
         // and enforce the correct derivation inside ZK.
-        debug!("Creating token freeze ZK proof");
-        let (proof, _public_inputs) = create_token_freeze_proof(
-            &self.token_freeze_zkbin,
-            &self.token_freeze_pk,
-            &self.mint_authority,
-        )?;
+        let prover_witnesses = vec![
+            // Token attributes
+            Witness::Base(Value::known(self.token_attrs.auth_parent.inner())),
+            Witness::Base(Value::known(self.token_attrs.blind)),
+            // Secret key used by mint
+            Witness::Base(Value::known(self.mint_keypair.secret.inner())),
+        ];
 
-        let params = MoneyTokenFreezeParamsV1 { signature_public: self.mint_authority.public };
-        let debris = TokenFreezeCallDebris { params, proofs: vec![proof] };
-        Ok(debris)
-    }
-}
-
-pub(crate) fn create_token_freeze_proof(
-    zkbin: &ZkBinary,
-    pk: &ProvingKey,
-    mint_authority: &Keypair,
-) -> Result<(Proof, TokenFreezeRevealed)> {
-    let token_id = TokenId::derive(mint_authority.secret);
-
-    let public_inputs = TokenFreezeRevealed { signature_public: mint_authority.public, token_id };
+        let mint_pubkey = self.mint_keypair.public;
+        let token_id = self.token_attrs.to_token_id();
 
-    let prover_witnesses = vec![Witness::Base(Value::known(mint_authority.secret.inner()))];
+        let public_inputs = vec![mint_pubkey.x(), mint_pubkey.y(), token_id.inner()];
+        darkfi::zk::export_witness_json("witness.json", &prover_witnesses, &public_inputs);
 
-    let circuit = ZkCircuit::new(prover_witnesses, zkbin);
-    let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
+        let circuit = ZkCircuit::new(prover_witnesses, &self.freeze_zkbin);
+        let proof = Proof::create(&self.freeze_pk, &[circuit], &public_inputs, &mut OsRng)?;
 
-    Ok((proof, public_inputs))
+        let params = MoneyTokenFreezeParamsV1 { mint_public: self.mint_keypair.public, token_id };
+        let debris = TokenFreezeCallDebris { params, proofs: vec![proof] };
+        Ok(debris)
+    }
 }

+ 31 - 180
src/contract/money/src/client/token_mint_v1.rs

@@ -21,205 +21,56 @@ use darkfi::{
     zkas::ZkBinary,
     Result,
 };
-use darkfi_sdk::{
-    crypto::{
-        note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, Keypair,
-        PublicKey, TokenId,
-    },
-    pasta::pallas,
-};
+use darkfi_sdk::pasta::pallas;
 use log::info;
 use rand::rngs::OsRng;
 
-use crate::{
-    client::{
-        transfer_v1::{TransferCallClearInput, TransferCallOutput},
-        MoneyNote,
-    },
-    model::{ClearInput, Coin, MoneyTokenMintParamsV1, Output},
-};
+use crate::model::{CoinAttributes, MoneyTokenMintParamsV1, TokenAttributes};
 
 pub struct TokenMintCallDebris {
     pub params: MoneyTokenMintParamsV1,
     pub proofs: Vec<Proof>,
 }
 
-pub struct TokenMintRevealed {
-    pub signature_public: PublicKey,
-    pub token_id: TokenId,
-    pub coin: Coin,
-    pub value_commit: pallas::Point,
-    pub token_commit: pallas::Base,
-}
-
-impl TokenMintRevealed {
-    pub fn to_vec(&self) -> Vec<pallas::Base> {
-        let (sig_x, sig_y) = self.signature_public.xy();
-        let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
-
-        // NOTE: It's important to keep these in the same order
-        // as the `constrain_instance` calls in the zkas code.
-        vec![
-            sig_x,
-            sig_y,
-            self.token_id.inner(),
-            self.coin.inner(),
-            *valcom_coords.x(),
-            *valcom_coords.y(),
-            self.token_commit,
-        ]
-    }
-}
-
 /// Struct holding necessary information to build a `Money::TokenMintV1` contract call.
 pub struct TokenMintCallBuilder {
-    /// Mint authority keypair
-    pub mint_authority: Keypair,
-    /// Recipient of the minted tokens
-    pub recipient: PublicKey,
-    /// Amount of tokens we want to mint
-    pub amount: u64,
-    /// Spend hook for the output
-    pub spend_hook: pallas::Base,
-    /// User data for the output
-    pub user_data: pallas::Base,
+    pub coin_attrs: CoinAttributes,
+    pub token_attrs: TokenAttributes,
+
     /// `TokenMint_V1` zkas circuit ZkBinary
-    pub token_mint_zkbin: ZkBinary,
+    pub mint_zkbin: ZkBinary,
     /// Proving key for the `TokenMint_V1` zk circuit,
-    pub token_mint_pk: ProvingKey,
+    pub mint_pk: ProvingKey,
 }
 
 impl TokenMintCallBuilder {
     pub fn build(&self) -> Result<TokenMintCallDebris> {
         info!("Building Money::TokenMintV1 contract call");
-        assert!(self.amount != 0);
-
-        // In this call, we will build one clear input and one anonymous output.
-        // The mint authority pubkey is used to derive the token ID.
-        let token_id = TokenId::derive(self.mint_authority.secret);
-
-        let input = TransferCallClearInput {
-            value: self.amount,
-            token_id,
-            signature_secret: self.mint_authority.secret,
-        };
-
-        let output = TransferCallOutput {
-            public_key: self.recipient,
-            value: self.amount,
-            token_id,
-            spend_hook: pallas::Base::ZERO,
-            user_data: pallas::Base::ZERO,
-            blind: pallas::Base::random(&mut OsRng),
-        };
-
-        // We just create the pedersen commitment blinds here. We simply
-        // enforce that the clear input and the anon output have the same
-        // commitments. Not sure if this can be avoided, but also is it
-        // really necessary to avoid?
-        let value_blind = pallas::Scalar::random(&mut OsRng);
-        let token_blind = pallas::Base::random(&mut OsRng);
-
-        let c_input = ClearInput {
-            value: input.value,
-            token_id: input.token_id,
-            value_blind,
-            token_blind,
-            signature_public: PublicKey::from_secret(input.signature_secret),
-        };
-
-        let coin_blind = pallas::Base::random(&mut OsRng);
-
-        info!("Creating token mint proof for output");
-        let (proof, public_inputs) = create_token_mint_proof(
-            &self.token_mint_zkbin,
-            &self.token_mint_pk,
-            &output,
-            &self.mint_authority,
-            value_blind,
-            token_blind,
-            self.spend_hook,
-            self.user_data,
-            coin_blind,
-        )?;
-
-        let note = MoneyNote {
-            value: output.value,
-            token_id: output.token_id,
-            spend_hook: self.spend_hook,
-            user_data: self.user_data,
-            coin_blind,
-            value_blind,
-            token_blind,
-            memo: vec![],
-        };
-
-        let encrypted_note = AeadEncryptedNote::encrypt(&note, &output.public_key, &mut OsRng)?;
-
-        let c_output = Output {
-            value_commit: public_inputs.value_commit,
-            token_commit: public_inputs.token_commit,
-            coin: public_inputs.coin,
-            note: encrypted_note,
-        };
-
-        let params = MoneyTokenMintParamsV1 { input: c_input, output: c_output };
+        let (public_x, public_y) = self.coin_attrs.public_key.xy();
+
+        let prover_witnesses = vec![
+            // Coin attributes
+            Witness::Base(Value::known(public_x)),
+            Witness::Base(Value::known(public_y)),
+            Witness::Base(Value::known(pallas::Base::from(self.coin_attrs.value))),
+            Witness::Base(Value::known(self.coin_attrs.spend_hook)),
+            Witness::Base(Value::known(self.coin_attrs.user_data)),
+            Witness::Base(Value::known(self.coin_attrs.blind)),
+            // Token attributes
+            Witness::Base(Value::known(self.token_attrs.auth_parent.inner())),
+            Witness::Base(Value::known(self.token_attrs.user_data)),
+            Witness::Base(Value::known(self.token_attrs.blind)),
+        ];
+
+        let coin = self.coin_attrs.to_coin();
+
+        let public_inputs = vec![self.token_attrs.auth_parent.inner(), coin.inner()];
+
+        let circuit = ZkCircuit::new(prover_witnesses, &self.mint_zkbin);
+        let proof = Proof::create(&self.mint_pk, &[circuit], &public_inputs, &mut OsRng)?;
+
+        let params = MoneyTokenMintParamsV1 { coin };
         let debris = TokenMintCallDebris { params, proofs: vec![proof] };
         Ok(debris)
     }
 }
-
-#[allow(clippy::too_many_arguments)]
-pub fn create_token_mint_proof(
-    zkbin: &ZkBinary,
-    pk: &ProvingKey,
-    output: &TransferCallOutput,
-    mint_authority: &Keypair,
-    value_blind: pallas::Scalar,
-    token_blind: pallas::Base,
-    spend_hook: pallas::Base,
-    user_data: pallas::Base,
-    coin_blind: pallas::Base,
-) -> Result<(Proof, TokenMintRevealed)> {
-    let token_id = TokenId::derive(mint_authority.secret);
-
-    let value_commit = pedersen_commitment_u64(output.value, value_blind);
-    let token_commit = poseidon_hash([token_id.inner(), token_blind]);
-
-    let (rcpt_x, rcpt_y) = output.public_key.xy();
-
-    let coin = Coin::from(poseidon_hash([
-        rcpt_x,
-        rcpt_y,
-        pallas::Base::from(output.value),
-        token_id.inner(),
-        spend_hook,
-        user_data,
-        coin_blind,
-    ]));
-
-    let public_inputs = TokenMintRevealed {
-        signature_public: mint_authority.public,
-        token_id,
-        coin,
-        value_commit,
-        token_commit,
-    };
-
-    let prover_witnesses = vec![
-        Witness::Base(Value::known(mint_authority.secret.inner())),
-        Witness::Base(Value::known(pallas::Base::from(output.value))),
-        Witness::Base(Value::known(rcpt_x)),
-        Witness::Base(Value::known(rcpt_y)),
-        Witness::Base(Value::known(spend_hook)),
-        Witness::Base(Value::known(user_data)),
-        Witness::Base(Value::known(coin_blind)),
-        Witness::Scalar(Value::known(value_blind)),
-        Witness::Base(Value::known(token_blind)),
-    ];
-
-    let circuit = ZkCircuit::new(prover_witnesses, zkbin);
-    let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
-
-    Ok((proof, public_inputs))
-}

+ 21 - 2
src/contract/money/src/entrypoint.rs

@@ -29,8 +29,9 @@ use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 use crate::{
     model::{
-        MoneyFeeUpdateV1, MoneyGenesisMintUpdateV1, MoneyPoWRewardUpdateV1,
-        MoneyTokenFreezeUpdateV1, MoneyTokenMintUpdateV1, MoneyTransferUpdateV1,
+        MoneyAuthTokenMintUpdateV1, MoneyFeeUpdateV1, MoneyGenesisMintUpdateV1,
+        MoneyPoWRewardUpdateV1, MoneyTokenFreezeUpdateV1, MoneyTokenMintUpdateV1,
+        MoneyTransferUpdateV1,
     },
     MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
     MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_DB_VERSION, MONEY_CONTRACT_FAUCET_PUBKEYS,
@@ -86,6 +87,13 @@ use pow_reward_v1::{
     money_pow_reward_process_update_v1,
 };
 
+/// `Money::AuthTokenMint` functions
+mod auth_token_mint_v1;
+use auth_token_mint_v1::{
+    money_auth_token_mint_get_metadata_v1, money_auth_token_mint_process_instruction_v1,
+    money_auth_token_mint_process_update_v1,
+};
+
 darkfi_sdk::define_contract!(
     init: init_contract,
     exec: process_instruction,
@@ -196,6 +204,9 @@ fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
         MoneyFunction::TokenMintV1 => money_token_mint_get_metadata_v1(cid, call_idx, calls)?,
         MoneyFunction::TokenFreezeV1 => money_token_freeze_get_metadata_v1(cid, call_idx, calls)?,
         MoneyFunction::PoWRewardV1 => money_pow_reward_get_metadata_v1(cid, call_idx, calls)?,
+        MoneyFunction::AuthTokenMintV1 => {
+            money_auth_token_mint_get_metadata_v1(cid, call_idx, calls)?
+        }
     };
 
     set_return_data(&metadata)
@@ -232,6 +243,9 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
         MoneyFunction::PoWRewardV1 => {
             money_pow_reward_process_instruction_v1(cid, call_idx, calls)?
         }
+        MoneyFunction::AuthTokenMintV1 => {
+            money_auth_token_mint_process_instruction_v1(cid, call_idx, calls)?
+        }
     };
 
     set_return_data(&update_data)
@@ -279,5 +293,10 @@ fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
             let update: MoneyPoWRewardUpdateV1 = deserialize(&update_data[1..])?;
             Ok(money_pow_reward_process_update_v1(cid, update)?)
         }
+
+        MoneyFunction::AuthTokenMintV1 => {
+            let update: MoneyAuthTokenMintUpdateV1 = deserialize(&update_data[1..])?;
+            Ok(money_auth_token_mint_process_update_v1(cid, update)?)
+        }
     }
 }

+ 112 - 0
src/contract/money/src/entrypoint/auth_token_mint_v1.rs

@@ -0,0 +1,112 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi_sdk::{
+    crypto::{pasta_prelude::*, ContractId, PublicKey},
+    dark_tree::DarkLeaf,
+    db::{db_contains_key, db_lookup},
+    error::{ContractError, ContractResult},
+    msg,
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use crate::{
+    error::MoneyError,
+    model::{MoneyAuthTokenMintParamsV1, MoneyAuthTokenMintUpdateV1, MoneyTokenMintParamsV1},
+    MoneyFunction, MONEY_CONTRACT_TOKEN_FREEZE_TREE, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1,
+};
+
+/// `get_metadata` function for `Money::AuthTokenMintV1`
+pub(crate) fn money_auth_token_mint_get_metadata_v1(
+    _cid: ContractId,
+    call_idx: u32,
+    calls: Vec<DarkLeaf<ContractCall>>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_node = &calls[call_idx as usize];
+    let self_data = &self_node.data;
+    let self_params: MoneyAuthTokenMintParamsV1 = deserialize(&self_data.data[1..])?;
+
+    assert_eq!(self_node.children_indexes.len(), 1);
+    let child_idx = self_node.children_indexes[0];
+    let child_node = &calls[child_idx];
+    let child_data = &child_node.data;
+    let child_params: MoneyTokenMintParamsV1 = deserialize(&child_data.data[1..])?;
+
+    // Public inputs for the ZK proofs we have to verify
+    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+    // Public keys for the transaction signatures we have to verify.
+    let signature_pubkeys: Vec<PublicKey> = vec![self_params.mint_pubkey];
+
+    let value_commit = self_params.value_commit.to_affine().coordinates().unwrap();
+    zk_public_inputs.push((
+        MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1.to_string(),
+        vec![
+            self_params.mint_pubkey.x(),
+            self_params.mint_pubkey.y(),
+            self_params.token_id.inner(),
+            child_params.coin.inner(),
+            *value_commit.x(),
+            *value_commit.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::AuthTokenMintV1`
+pub(crate) fn money_auth_token_mint_process_instruction_v1(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<DarkLeaf<ContractCall>>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize].data;
+    let params: MoneyAuthTokenMintParamsV1 = deserialize(&self_.data[1..])?;
+
+    // We have to check if the token mint is frozen.
+    let token_freeze_db = db_lookup(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE)?;
+
+    // Check that the mint is not frozen
+    if db_contains_key(token_freeze_db, &serialize(&params.token_id))? {
+        msg!("[MintV1] Error: Token mint for {} is frozen", params.token_id);
+        return Err(MoneyError::TokenMintFrozen.into())
+    }
+
+    // Create a state update.
+    let update = MoneyAuthTokenMintUpdateV1 {};
+    let mut update_data = vec![];
+    update_data.write_u8(MoneyFunction::AuthTokenMintV1 as u8)?;
+    update.encode(&mut update_data)?;
+
+    Ok(update_data)
+}
+
+/// `process_update` function for `Money::AuthTokenMintV1`
+pub(crate) fn money_auth_token_mint_process_update_v1(
+    _cid: ContractId,
+    _update: MoneyAuthTokenMintUpdateV1,
+) -> ContractResult {
+    // Do nothing... Coin is added with token_mint() call instead.
+    Ok(())
+}

+ 9 - 12
src/contract/money/src/entrypoint/token_freeze_v1.rs

@@ -17,7 +17,7 @@
  */
 
 use darkfi_sdk::{
-    crypto::{ContractId, PublicKey, TokenId},
+    crypto::{ContractId, PublicKey},
     dark_tree::DarkLeaf,
     db::{db_contains_key, db_lookup, db_set},
     error::{ContractError, ContractResult},
@@ -45,16 +45,15 @@ pub(crate) fn money_token_freeze_get_metadata_v1(
     // Public inputs for the ZK proofs we have to verify
     let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
     // Public keys for the transaction signatures we have to verify
-    let signature_pubkeys: Vec<PublicKey> = vec![params.signature_public];
+    let signature_pubkeys: Vec<PublicKey> = vec![params.mint_public];
 
     // Derive the TokenId from the public key
-    let (sig_x, sig_y) = params.signature_public.xy();
-    let token_id = TokenId::derive_public(params.signature_public);
+    let (mint_x, mint_y) = params.mint_public.xy();
 
     // In ZK we just verify that the token ID is properly derived from the authority.
     zk_public_inputs.push((
         MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1.to_string(),
-        vec![sig_x, sig_y, token_id.inner()],
+        vec![mint_x, mint_y, params.token_id.inner()],
     ));
 
     // Serialize everything gathered and return it
@@ -76,16 +75,15 @@ pub(crate) fn money_token_freeze_process_instruction_v1(
 
     // We just check if the mint was already frozen beforehand
     let token_freeze_db = db_lookup(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE)?;
-    let token_id = TokenId::derive_public(params.signature_public);
 
     // 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);
+    if db_contains_key(token_freeze_db, &serialize(&params.token_id))? {
+        msg!("[MintV1] Error: Token mint for {} is frozen", params.token_id);
         return Err(MoneyError::TokenMintFrozen.into())
     }
 
     // Create a state update. We only need the new coin.
-    let update = MoneyTokenFreezeUpdateV1 { signature_public: params.signature_public };
+    let update = MoneyTokenFreezeUpdateV1 { token_id: params.token_id };
     let mut update_data = vec![];
     update_data.write_u8(MoneyFunction::TokenFreezeV1 as u8)?;
     update.encode(&mut update_data)?;
@@ -99,9 +97,8 @@ pub(crate) fn money_token_freeze_process_update_v1(
     update: MoneyTokenFreezeUpdateV1,
 ) -> ContractResult {
     let token_freeze_db = db_lookup(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE)?;
-    let token_id = TokenId::derive_public(update.signature_public);
-    msg!("[MintV1] Freezing mint for token {}", token_id);
-    db_set(token_freeze_db, &serialize(&token_id), &[])?;
+    msg!("[MintV1] Freezing mint for token {}", update.token_id);
+    db_set(token_freeze_db, &serialize(&update.token_id), &[])?;
 
     Ok(())
 }

+ 13 - 55
src/contract/money/src/entrypoint/token_mint_v1.rs

@@ -17,9 +17,7 @@
  */
 
 use darkfi_sdk::{
-    crypto::{
-        pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, ContractId, MerkleNode, TokenId,
-    },
+    crypto::{ContractId, FuncRef, MerkleNode, PublicKey},
     dark_tree::DarkLeaf,
     db::{db_contains_key, db_lookup, db_set},
     error::{ContractError, ContractResult},
@@ -34,7 +32,7 @@ use crate::{
     model::{MoneyTokenMintParamsV1, MoneyTokenMintUpdateV1},
     MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
     MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT,
-    MONEY_CONTRACT_TOKEN_FREEZE_TREE, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
+    MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
 };
 
 /// `get_metadata` function for `Money::TokenMintV1`
@@ -46,31 +44,22 @@ pub(crate) fn money_token_mint_get_metadata_v1(
     let self_ = &calls[call_idx as usize].data;
     let params: MoneyTokenMintParamsV1 = deserialize(&self_.data[1..])?;
 
+    let parent_idx = calls[call_idx as usize].parent_index.unwrap();
+    let parent_call = &calls[parent_idx].data;
+    let parent_contract_id = parent_call.contract_id;
+    let parent_func_code = parent_call.data[0];
+
     // Public inputs for the ZK proofs we have to verify
     let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
     // Public keys for the transaction signatures we have to verify.
-    // 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.
-    let signature_pubkeys = vec![params.input.signature_public];
-
-    // Derive the TokenId from the public key
-    let (sig_x, sig_y) = params.input.signature_public.xy();
-    let token_id = TokenId::derive_public(params.input.signature_public);
+    let signature_pubkeys: Vec<PublicKey> = vec![];
 
-    let value_coords = params.output.value_commit.to_affine().coordinates().unwrap();
+    let parent_func_id =
+        FuncRef { contract_id: parent_contract_id, func_code: parent_func_code }.to_func_id();
 
     zk_public_inputs.push((
         MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1.to_string(),
-        vec![
-            sig_x,
-            sig_y,
-            token_id.inner(),
-            params.output.coin.inner(),
-            *value_coords.x(),
-            *value_coords.y(),
-            params.output.token_commit,
-        ],
+        vec![parent_func_id.inner(), params.coin.inner()],
     ));
 
     // Serialize everything gathered and return it
@@ -93,46 +82,15 @@ pub(crate) fn money_token_mint_process_instruction_v1(
     // 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 token_id = TokenId::derive_public(params.input.signature_public);
-    if token_id != params.input.token_id {
-        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!("[MintV1] Error: Token mint for {} is frozen", token_id);
-        return Err(MoneyError::TokenMintFrozen.into())
-    }
 
     // Check that the coin from the output hasn't existed before
-    if db_contains_key(coins_db, &serialize(&params.output.coin))? {
+    if db_contains_key(coins_db, &serialize(&params.coin))? {
         msg!("[MintV1] Error: Duplicate coin in output");
         return Err(MoneyError::DuplicateCoin.into())
     }
 
-    // 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!("[MintV1] Error: Value commitment mismatch");
-        return Err(MoneyError::ValueMismatch.into())
-    }
-
-    if poseidon_hash([params.input.token_id.inner(), params.input.token_blind]) !=
-        params.output.token_commit
-    {
-        msg!("[MintV1] Error: Token commitment mismatch");
-        return Err(MoneyError::TokenMismatch.into())
-    }
-
     // Create a state update. We only need the new coin.
-    let update = MoneyTokenMintUpdateV1 { coin: params.output.coin };
+    let update = MoneyTokenMintUpdateV1 { coin: params.coin };
     let mut update_data = vec![];
     update_data.write_u8(MoneyFunction::TokenMintV1 as u8)?;
     update.encode(&mut update_data)?;

+ 4 - 0
src/contract/money/src/lib.rs

@@ -32,6 +32,7 @@ pub enum MoneyFunction {
     TokenMintV1 = 0x04,
     TokenFreezeV1 = 0x05,
     PoWRewardV1 = 0x06,
+    AuthTokenMintV1 = 0x07,
 }
 // ANCHOR_END: money-function
 
@@ -47,6 +48,7 @@ impl TryFrom<u8> for MoneyFunction {
             0x04 => Ok(Self::TokenMintV1),
             0x05 => Ok(Self::TokenFreezeV1),
             0x06 => Ok(Self::PoWRewardV1),
+            0x07 => Ok(Self::AuthTokenMintV1),
             _ => Err(ContractError::InvalidFunction),
         }
     }
@@ -90,3 +92,5 @@ 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 token auth mint circuit namespace
+pub const MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1: &str = "AuthTokenMint_V1";

+ 33 - 8
src/contract/money/src/model.rs

@@ -18,7 +18,7 @@
 
 use darkfi_sdk::{
     crypto::{
-        ecvrf::VrfProof, note::AeadEncryptedNote, pasta_prelude::PrimeField, poseidon_hash,
+        ecvrf::VrfProof, note::AeadEncryptedNote, pasta_prelude::PrimeField, poseidon_hash, FuncId,
         MerkleNode, Nullifier, PublicKey, SecretKey, TokenId,
     },
     error::ContractError,
@@ -90,6 +90,20 @@ impl CoinAttributes {
     }
 }
 
+#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
+pub struct TokenAttributes {
+    pub auth_parent: FuncId,
+    pub user_data: pallas::Base,
+    pub blind: pallas::Base,
+}
+
+impl TokenAttributes {
+    pub fn to_token_id(&self) -> TokenId {
+        let token_id = poseidon_hash([self.auth_parent.inner(), self.user_data, self.blind]);
+        TokenId::from(token_id)
+    }
+}
+
 #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
 pub struct NullifierAttributes {
     /// Secret key for the public key in the coin.
@@ -228,10 +242,8 @@ pub struct MoneyGenesisMintUpdateV1 {
 /// Parameters for `Money::TokenMint`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct MoneyTokenMintParamsV1 {
-    /// Clear input
-    pub input: ClearInput,
-    /// Anonymous output
-    pub output: Output,
+    /// The newly minted coin
+    pub coin: Coin,
 }
 
 /// State update for `Money::TokenMint`
@@ -241,20 +253,33 @@ pub struct MoneyTokenMintUpdateV1 {
     pub coin: Coin,
 }
 
+/// Parameters for `Money::auth_token_mint()`
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct MoneyAuthTokenMintParamsV1 {
+    pub token_id: TokenId,
+    pub value_commit: pallas::Point,
+    pub enc_note: AeadEncryptedNote,
+    pub mint_pubkey: PublicKey,
+}
+
+/// State update for `Money::auth_token_mint()`
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct MoneyAuthTokenMintUpdateV1 {}
+
 /// Parameters for `Money::TokenFreeze`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct MoneyTokenFreezeParamsV1 {
     /// Mint authority public key
     ///
     /// We use this to derive the token ID and verify the signature.
-    pub signature_public: PublicKey,
+    pub mint_public: PublicKey,
+    pub token_id: TokenId,
 }
 
 /// State update for `Money::TokenFreeze`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct MoneyTokenFreezeUpdateV1 {
-    /// Mint authority public key
-    pub signature_public: PublicKey,
+    pub token_id: TokenId,
 }
 
 /// Parameters for `Money::PoWReward`

+ 12 - 6
src/contract/money/tests/genesis_mint.rs

@@ -106,7 +106,8 @@ fn genesis_mint() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Alice gathers her new owncoin
-        let alice_oc = th.gather_owncoin(&Holder::Alice, &genesis_mint_params.output, None)?;
+        let alice_oc =
+            th.gather_owncoin_from_output(&Holder::Alice, &genesis_mint_params.output, None)?;
         alice_owncoins.push(alice_oc);
 
         info!(target: "money", "[Bob] ========================");
@@ -130,7 +131,8 @@ fn genesis_mint() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Bob gathers his new owncoin
-        let bob_oc = th.gather_owncoin(&Holder::Bob, &genesis_mint_params.output, None)?;
+        let bob_oc =
+            th.gather_owncoin_from_output(&Holder::Bob, &genesis_mint_params.output, None)?;
         bob_owncoins.push(bob_oc);
 
         // Now Alice can send a little bit of funds to Bob
@@ -164,11 +166,13 @@ fn genesis_mint() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Bob should have his old OwnCoin, and this new one.
-        let bob_oc = th.gather_owncoin(&Holder::Bob, &transfer_params.outputs[0], None)?;
+        let bob_oc =
+            th.gather_owncoin_from_output(&Holder::Bob, &transfer_params.outputs[0], None)?;
         bob_owncoins.push(bob_oc);
 
         // Alice should now have one OwnCoin with the change from the above transaction.
-        let alice_oc = th.gather_owncoin(&Holder::Alice, &transfer_params.outputs[1], None)?;
+        let alice_oc =
+            th.gather_owncoin_from_output(&Holder::Alice, &transfer_params.outputs[1], None)?;
         alice_owncoins.push(alice_oc);
 
         assert!(alice_owncoins.len() == 1);
@@ -205,11 +209,13 @@ fn genesis_mint() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Alice should now have two OwnCoins
-        let alice_oc = th.gather_owncoin(&Holder::Alice, &transfer_params.outputs[0], None)?;
+        let alice_oc =
+            th.gather_owncoin_from_output(&Holder::Alice, &transfer_params.outputs[0], None)?;
         alice_owncoins.push(alice_oc);
 
         // Bob should have two with the change from the above tx
-        let bob_oc = th.gather_owncoin(&Holder::Bob, &transfer_params.outputs[1], None)?;
+        let bob_oc =
+            th.gather_owncoin_from_output(&Holder::Bob, &transfer_params.outputs[1], None)?;
         bob_owncoins.push(bob_oc);
 
         // Validating transaction outcomes

+ 2 - 2
src/contract/money/tests/integration.rs

@@ -52,7 +52,7 @@ fn money_integration() -> Result<()> {
         }
 
         let alice_owncoin =
-            th.gather_owncoin(&Holder::Alice, &alice_proposal_params.output, None)?;
+            th.gather_owncoin_from_output(&Holder::Alice, &alice_proposal_params.output, None)?;
         assert!(alice_owncoin.note.value == expected_reward(current_block_height));
 
         th.assert_trees(&HOLDERS);
@@ -73,7 +73,7 @@ fn money_integration() -> Result<()> {
             .await?;
         }
 
-        let _ = th.gather_owncoin(&Holder::Bob, &bob_proposal_params.output, None)?;
+        let _ = th.gather_owncoin_from_output(&Holder::Bob, &bob_proposal_params.output, None)?;
 
         th.assert_trees(&HOLDERS);
 

+ 10 - 8
src/contract/money/tests/mint_pay_swap.rs

@@ -62,40 +62,42 @@ fn mint_pay_swap() -> Result<()> {
         info!(target: "money", "[Alice] ================================");
         info!(target: "money", "[Alice] Building token mint tx for Alice");
         info!(target: "money", "[Alice] ================================");
-        let (mint_tx, params) =
+        let (mint_tx, mint_params, mint_auth_params) =
             th.token_mint(ALICE_INITIAL, &Holder::Alice, &Holder::Alice, None, None)?;
 
         for holder in &HOLDERS {
             info!(target: "money", "[{holder:?}] ==============================");
             info!(target: "money", "[{holder:?}] Executing Alice token mint tx");
             info!(target: "money", "[{holder:?}] ==============================");
-            th.execute_token_mint_tx(holder, &mint_tx, &params, current_block_height).await?;
+            th.execute_token_mint_tx(holder, &mint_tx, &mint_params, current_block_height).await?;
         }
 
         th.assert_trees(&HOLDERS);
 
         // Alice gathers her new owncoin
-        let alice_oc = th.gather_owncoin(&Holder::Alice, &params.output, None)?;
+        let alice_oc =
+            th.gather_owncoin(&Holder::Alice, &mint_params.coin, &mint_auth_params.enc_note, None)?;
         let alice_token_id = alice_oc.note.token_id;
         alice_owncoins.push(alice_oc);
 
         info!(target: "money", "[Bob] ==============================");
         info!(target: "money", "[Bob] Building token mint tx for Bob");
         info!(target: "money", "[Bob] ==============================");
-        let (mint_tx, params) =
+        let (mint_tx, mint_params, mint_auth_params) =
             th.token_mint(BOB_INITIAL, &Holder::Bob, &Holder::Bob, None, None)?;
 
         for holder in &HOLDERS {
             info!(target: "money", "[{holder:?}] ===========================");
             info!(target: "money", "[{holder:?}] Executing Bob token mint tx");
             info!(target: "money", "[{holder:?}] ===========================");
-            th.execute_token_mint_tx(holder, &mint_tx, &params, current_block_height).await?;
+            th.execute_token_mint_tx(holder, &mint_tx, &mint_params, current_block_height).await?;
         }
 
         th.assert_trees(&HOLDERS);
 
         // Bob  gathers hist new owncoin
-        let bob_oc = th.gather_owncoin(&Holder::Bob, &params.output, None)?;
+        let bob_oc =
+            th.gather_owncoin(&Holder::Bob, &mint_params.coin, &mint_auth_params.enc_note, None)?;
         let bob_token_id = bob_oc.note.token_id;
         bob_owncoins.push(bob_oc);
 
@@ -282,7 +284,7 @@ fn mint_pay_swap() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Alice should now have a single OwnCoin with her initial airdrop
-        let alice_oc = th.gather_owncoin(&Holder::Alice, &params.outputs[0], None)?;
+        let alice_oc = th.gather_owncoin_from_output(&Holder::Alice, &params.outputs[0], None)?;
         alice_owncoins.push(alice_oc);
 
         assert!(alice_owncoins.len() == 1);
@@ -313,7 +315,7 @@ fn mint_pay_swap() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Bob should now have a single OwnCoin with his initial airdrop
-        let bob_oc = th.gather_owncoin(&Holder::Bob, &params.outputs[0], None)?;
+        let bob_oc = th.gather_owncoin_from_output(&Holder::Bob, &params.outputs[0], None)?;
         bob_owncoins.push(bob_oc);
 
         assert!(bob_owncoins.len() == 1);

+ 8 - 4
src/contract/money/tests/pow_reward.rs

@@ -105,7 +105,8 @@ fn pow_reward() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Alice gathers her new owncoin
-        let alice_oc = th.gather_owncoin(&Holder::Alice, &pow_reward_params.output, None)?;
+        let alice_oc =
+            th.gather_owncoin_from_output(&Holder::Alice, &pow_reward_params.output, None)?;
         alice_owncoins.push(alice_oc);
 
         // Now Alice can send a little bit of funds to Bob
@@ -140,11 +141,13 @@ fn pow_reward() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Bob should have this new OwnCoin.
-        let bob_oc = th.gather_owncoin(&Holder::Bob, &transfer_params.outputs[0], None)?;
+        let bob_oc =
+            th.gather_owncoin_from_output(&Holder::Bob, &transfer_params.outputs[0], None)?;
         bob_owncoins.push(bob_oc);
 
         // Alice should now have one OwnCoin with the change from the above transaction.
-        let alice_oc = th.gather_owncoin(&Holder::Alice, &transfer_params.outputs[1], None)?;
+        let alice_oc =
+            th.gather_owncoin_from_output(&Holder::Alice, &transfer_params.outputs[1], None)?;
         alice_owncoins.push(alice_oc);
 
         // Alice can also send her PoW reward directly to bob
@@ -170,7 +173,8 @@ fn pow_reward() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Bob gathers his new owncoin
-        let bob_oc = th.gather_owncoin(&Holder::Bob, &pow_reward_params.output, None)?;
+        let bob_oc =
+            th.gather_owncoin_from_output(&Holder::Bob, &pow_reward_params.output, None)?;
         bob_owncoins.push(bob_oc);
 
         // Validating transaction outcomes

+ 7 - 2
src/contract/money/tests/token_mint.rs

@@ -38,7 +38,7 @@ fn token_mint() -> Result<()> {
         let mut th = TestHarness::new(&["money".to_string()], false).await?;
 
         info!("[Bob] Building BOB token mint tx");
-        let (token_mint_tx, token_mint_params) =
+        let (token_mint_tx, token_mint_params, token_auth_mint_params) =
             th.token_mint(BOB_SUPPLY, &Holder::Bob, &Holder::Bob, None, None)?;
 
         for holder in &HOLDERS {
@@ -55,7 +55,12 @@ fn token_mint() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Bob gathers his new coin
-        th.gather_owncoin(&Holder::Bob, &token_mint_params.output, None)?;
+        th.gather_owncoin(
+            &Holder::Bob,
+            &token_mint_params.coin,
+            &token_auth_mint_params.enc_note,
+            None,
+        )?;
 
         info!("[Bob] Building BOB token freeze tx");
         let (token_frz_tx, token_frz_params) = th.token_freeze(&Holder::Bob)?;

+ 11 - 4
src/contract/money/tests/txs_verification.rs

@@ -55,7 +55,7 @@ fn txs_verification() -> Result<()> {
         info!(target: "money", "[Alice] ================================");
         info!(target: "money", "[Alice] Building token mint tx for Alice");
         info!(target: "money", "[Alice] ================================");
-        let (token_mint_tx, token_mint_params) =
+        let (token_mint_tx, token_mint_params, token_auth_mint_params) =
             th.token_mint(ALICE_INITIAL, &Holder::Alice, &Holder::Alice, None, None)?;
 
         for holder in &HOLDERS {
@@ -74,7 +74,12 @@ fn txs_verification() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Alice gathers her new owncoin
-        let alice_oc = th.gather_owncoin(&Holder::Alice, &token_mint_params.output, None)?;
+        let alice_oc = th.gather_owncoin(
+            &Holder::Alice,
+            &token_mint_params.coin,
+            &token_auth_mint_params.enc_note,
+            None,
+        )?;
         let alice_token_id = alice_oc.note.token_id;
         alice_owncoins.push(alice_oc);
 
@@ -144,11 +149,13 @@ fn txs_verification() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Bob should now have the new OwnCoin.
-        let bob_oc = th.gather_owncoin(&Holder::Bob, &txs_params[0].outputs[0], None)?;
+        let bob_oc =
+            th.gather_owncoin_from_output(&Holder::Bob, &txs_params[0].outputs[0], None)?;
         bob_owncoins.push(bob_oc);
 
         // Alice should now have one OwnCoin with the change from the above transaction.
-        let alice_oc = th.gather_owncoin(&Holder::Alice, &txs_params[0].outputs[1], None)?;
+        let alice_oc =
+            th.gather_owncoin_from_output(&Holder::Alice, &txs_params[0].outputs[1], None)?;
         alice_owncoins.push(alice_oc);
 
         assert!(alice_owncoins.len() == 1);

+ 4 - 2
src/contract/money/tests/verification_bench.rs

@@ -75,7 +75,8 @@ fn alice2alice_random_amounts() -> Result<()> {
 
         // Gather new owncoins
         let mut owncoins = vec![];
-        let owncoin = th.gather_owncoin(&Holder::Alice, &airdrop_params.outputs[0], None)?;
+        let owncoin =
+            th.gather_owncoin_from_output(&Holder::Alice, &airdrop_params.outputs[0], None)?;
         let token_id = owncoin.note.token_id;
         owncoins.push(owncoin);
 
@@ -175,7 +176,8 @@ fn alice2alice_multiplecoins_random_amounts() -> Result<()> {
             th.assert_trees(&HOLDERS);
 
             // Gather new owncoins
-            let owncoin = th.gather_owncoin(&Holder::Alice, &mint_params.output, None)?;
+            let owncoin =
+                th.gather_owncoin_from_output(&Holder::Alice, &mint_params.output, None)?;
             let token_id = owncoin.note.token_id;
             owncoins.push(vec![owncoin]);
             minted_amounts.push(amount);

+ 20 - 12
src/contract/test-harness/src/lib.rs

@@ -33,13 +33,13 @@ use darkfi::{
 use darkfi_dao_contract::model::{DaoBulla, DaoProposalBulla};
 use darkfi_money_contract::{
     client::{MoneyNote, OwnCoin},
-    model::Output,
+    model::{Coin, Output},
 };
 use darkfi_sdk::{
     bridgetree,
     crypto::{
-        pasta_prelude::Field, poseidon_hash, ContractId, Keypair, MerkleNode, MerkleTree,
-        Nullifier, PublicKey, SecretKey, TokenId,
+        note::AeadEncryptedNote, pasta_prelude::Field, poseidon_hash, ContractId, Keypair,
+        MerkleNode, MerkleTree, Nullifier, PublicKey, SecretKey,
     },
     pasta::pallas,
 };
@@ -115,6 +115,7 @@ pub enum TxAction {
 pub struct Wallet {
     pub keypair: Keypair,
     pub token_mint_authority: Keypair,
+    pub token_blind: pallas::Base,
     pub contract_deploy_authority: Keypair,
     pub validator: ValidatorPtr,
     pub money_merkle_tree: MerkleTree,
@@ -168,11 +169,13 @@ impl Wallet {
         let spent_money_coins = vec![];
 
         let token_mint_authority = Keypair::random(&mut OsRng);
+        let token_blind = pallas::Base::random(&mut OsRng);
         let contract_deploy_authority = Keypair::random(&mut OsRng);
 
         Ok(Self {
             keypair,
             token_mint_authority,
+            token_blind,
             contract_deploy_authority,
             validator,
             money_merkle_tree,
@@ -288,7 +291,8 @@ impl TestHarness {
     pub fn gather_owncoin(
         &mut self,
         holder: &Holder,
-        output: &Output,
+        coin: &Coin,
+        note: &AeadEncryptedNote,
         secret_key: Option<SecretKey>,
     ) -> Result<OwnCoin> {
         let wallet = self.holders.get_mut(holder).unwrap();
@@ -298,14 +302,14 @@ impl TestHarness {
             None => wallet.keypair.secret,
         };
 
-        let note: MoneyNote = output.note.decrypt(&secret_key)?;
+        let note: MoneyNote = note.decrypt(&secret_key)?;
         let oc = OwnCoin {
-            coin: output.coin,
+            coin: coin.clone(),
             note: note.clone(),
             secret: secret_key,
             nullifier: Nullifier::from(poseidon_hash([
                 wallet.keypair.secret.inner(),
-                output.coin.inner(),
+                coin.inner(),
             ])),
             leaf_position,
         };
@@ -315,6 +319,15 @@ impl TestHarness {
         Ok(oc)
     }
 
+    pub fn gather_owncoin_from_output(
+        &mut self,
+        holder: &Holder,
+        output: &Output,
+        secret_key: Option<SecretKey>,
+    ) -> Result<OwnCoin> {
+        self.gather_owncoin(holder, &output.coin, &output.note, secret_key)
+    }
+
     /// This should be used after transfer call, so we can mark the merkle tree
     /// before each output coin. Assumes using wallet secret key.
     pub fn gather_multiple_owncoins(
@@ -397,11 +410,6 @@ impl TestHarness {
         }
     }
 
-    pub fn token_id(&self, holder: &Holder) -> TokenId {
-        let holder = self.holders.get(holder).unwrap();
-        TokenId::derive_public(holder.token_mint_authority.public)
-    }
-
     pub fn contract_id(&self, holder: &Holder) -> ContractId {
         let holder = self.holders.get(holder).unwrap();
         ContractId::derive_public(holder.contract_deploy_authority.public)

+ 1 - 1
src/contract/test-harness/src/money_airdrop.rs

@@ -147,7 +147,7 @@ impl TestHarness {
         self.assert_trees(holders);
 
         // Gather new owncoin
-        let oc = self.gather_owncoin(holder, &airdrop_params.outputs[0], None)?;
+        let oc = self.gather_owncoin_from_output(holder, &airdrop_params.outputs[0], None)?;
 
         Ok(oc)
     }

+ 4 - 4
src/contract/test-harness/src/money_genesis_mint.rs

@@ -23,8 +23,8 @@ use darkfi::{
     Result,
 };
 use darkfi_money_contract::{
-    client::genesis_mint_v1::GenesisMintCallBuilder, model::MoneyTokenMintParamsV1, MoneyFunction,
-    MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+    client::genesis_mint_v1::GenesisMintCallBuilder, model::MoneyGenesisMintParamsV1,
+    MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
 };
 use darkfi_sdk::{
     crypto::{MerkleNode, MONEY_CONTRACT_ID},
@@ -41,7 +41,7 @@ impl TestHarness {
         &mut self,
         holder: &Holder,
         amount: u64,
-    ) -> Result<(Transaction, MoneyTokenMintParamsV1)> {
+    ) -> Result<(Transaction, MoneyGenesisMintParamsV1)> {
         let wallet = self.holders.get(holder).unwrap();
 
         let (mint_pk, mint_zkbin) =
@@ -92,7 +92,7 @@ impl TestHarness {
         &mut self,
         holder: &Holder,
         tx: &Transaction,
-        params: &MoneyTokenMintParamsV1,
+        params: &MoneyGenesisMintParamsV1,
         block_height: u64,
     ) -> Result<()> {
         let wallet = self.holders.get_mut(holder).unwrap();

+ 97 - 29
src/contract/test-harness/src/money_token.rs

@@ -24,12 +24,20 @@ use darkfi::{
     Result,
 };
 use darkfi_money_contract::{
-    client::{token_freeze_v1::TokenFreezeCallBuilder, token_mint_v1::TokenMintCallBuilder},
-    model::{MoneyTokenFreezeParamsV1, MoneyTokenMintParamsV1},
-    MoneyFunction, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
+    client::{
+        auth_token_mint_v1::AuthTokenMintCallBuilder, token_freeze_v1::TokenFreezeCallBuilder,
+        token_mint_v1::TokenMintCallBuilder,
+    },
+    model::{
+        CoinAttributes, MoneyAuthTokenMintParamsV1, MoneyTokenFreezeParamsV1,
+        MoneyTokenMintParamsV1, TokenAttributes,
+    },
+    MoneyFunction, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1,
+    MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
 };
 use darkfi_sdk::{
-    crypto::{MerkleNode, MONEY_CONTRACT_ID},
+    crypto::{poseidon_hash, FuncRef, MerkleNode, MONEY_CONTRACT_ID},
+    dark_tree::DarkLeaf,
     pasta::pallas,
     ContractCall,
 };
@@ -46,40 +54,85 @@ impl TestHarness {
         recipient: &Holder,
         spend_hook: Option<pallas::Base>,
         user_data: Option<pallas::Base>,
-    ) -> Result<(Transaction, MoneyTokenMintParamsV1)> {
+    ) -> Result<(Transaction, MoneyTokenMintParamsV1, MoneyAuthTokenMintParamsV1)> {
         let wallet = self.holders.get(holder).unwrap();
         let mint_authority = wallet.token_mint_authority;
+        let token_blind = wallet.token_blind;
 
         let rcpt = self.holders.get(recipient).unwrap().keypair.public;
 
-        let (mint_pk, mint_zkbin) =
-            self.proving_keys.get(&MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1.to_string()).unwrap();
+        let (mint_pk, mint_zkbin) = self
+            .proving_keys
+            .get(&MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1.to_string())
+            .unwrap()
+            .clone();
+        let (auth_mint_pk, auth_mint_zkbin) = self
+            .proving_keys
+            .get(&MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1.to_string())
+            .unwrap()
+            .clone();
 
         let tx_action_benchmark =
             self.tx_action_benchmarks.get_mut(&TxAction::MoneyTokenMint).unwrap();
 
         let timer = Instant::now();
 
-        let builder = TokenMintCallBuilder {
-            mint_authority,
-            recipient: rcpt,
-            amount,
+        let auth_func_id = FuncRef {
+            contract_id: *MONEY_CONTRACT_ID,
+            func_code: MoneyFunction::AuthTokenMintV1 as u8,
+        }
+        .to_func_id();
+
+        let token_attrs = TokenAttributes {
+            auth_parent: auth_func_id,
+            user_data: poseidon_hash([mint_authority.public.x(), mint_authority.public.y()]),
+            blind: token_blind,
+        };
+        let token_id = token_attrs.to_token_id();
+
+        let coin_attrs = CoinAttributes {
+            public_key: rcpt,
+            value: amount,
+            token_id,
             spend_hook: spend_hook.unwrap_or(pallas::Base::ZERO),
             user_data: user_data.unwrap_or(pallas::Base::ZERO),
-            token_mint_zkbin: mint_zkbin.clone(),
-            token_mint_pk: mint_pk.clone(),
+            blind: pallas::Base::random(&mut OsRng),
         };
 
-        let debris = builder.build()?;
-
+        let builder = TokenMintCallBuilder {
+            coin_attrs: coin_attrs.clone(),
+            token_attrs: token_attrs.clone(),
+            mint_zkbin,
+            mint_pk,
+        };
+        let mint_debris = builder.build()?;
         let mut data = vec![MoneyFunction::TokenMintV1 as u8];
-        debris.params.encode(&mut data)?;
-        let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?;
-        tx.signatures = vec![sigs];
+        mint_debris.params.encode(&mut data)?;
+        let mint_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
+
+        let builder = AuthTokenMintCallBuilder {
+            coin_attrs,
+            token_attrs,
+            mint_keypair: mint_authority,
+            auth_mint_zkbin,
+            auth_mint_pk,
+        };
+        let auth_debris = builder.build()?;
+        let mut data = vec![MoneyFunction::AuthTokenMintV1 as u8];
+        auth_debris.params.encode(&mut data)?;
+        let auth_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
+
+        let mut tx = Transaction {
+            calls: vec![
+                DarkLeaf { data: mint_call, parent_index: Some(1), children_indexes: vec![] },
+                DarkLeaf { data: auth_call, parent_index: None, children_indexes: vec![0] },
+            ],
+            proofs: vec![mint_debris.proofs, auth_debris.proofs],
+            signatures: vec![],
+        };
+        let mint_sigs = tx.create_sigs(&mut OsRng, &[])?;
+        let auth_sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?;
+        tx.signatures = vec![mint_sigs, auth_sigs];
         tx_action_benchmark.creation_times.push(timer.elapsed());
 
         // Calculate transaction sizes
@@ -90,7 +143,7 @@ impl TestHarness {
         let size = std::mem::size_of_val(&*base58);
         tx_action_benchmark.broadcasted_sizes.push(size);
 
-        Ok((tx, debris.params))
+        Ok((tx, mint_debris.params, auth_debris.params))
     }
 
     pub async fn execute_token_mint_tx(
@@ -106,7 +159,8 @@ impl TestHarness {
         let timer = Instant::now();
 
         wallet.validator.add_transactions(&[tx.clone()], block_height, true).await?;
-        wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
+        wallet.money_merkle_tree.append(MerkleNode::from(params.coin.inner()));
+
         tx_action_benchmark.verify_times.push(timer.elapsed());
 
         Ok(())
@@ -117,7 +171,8 @@ impl TestHarness {
         holder: &Holder,
     ) -> Result<(Transaction, MoneyTokenFreezeParamsV1)> {
         let wallet = self.holders.get(holder).unwrap();
-        let mint_authority = wallet.token_mint_authority;
+        let mint_keypair = wallet.token_mint_authority;
+        let token_blind = wallet.token_blind;
 
         let (frz_pk, frz_zkbin) =
             self.proving_keys.get(&MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1.to_string()).unwrap();
@@ -127,10 +182,23 @@ impl TestHarness {
 
         let timer = Instant::now();
 
+        let auth_func_id = FuncRef {
+            contract_id: *MONEY_CONTRACT_ID,
+            func_code: MoneyFunction::AuthTokenMintV1 as u8,
+        }
+        .to_func_id();
+
+        let token_attrs = TokenAttributes {
+            auth_parent: auth_func_id,
+            user_data: poseidon_hash([mint_keypair.public.x(), mint_keypair.public.y()]),
+            blind: token_blind,
+        };
+
         let builder = TokenFreezeCallBuilder {
-            mint_authority,
-            token_freeze_zkbin: frz_zkbin.clone(),
-            token_freeze_pk: frz_pk.clone(),
+            mint_keypair,
+            token_attrs,
+            freeze_zkbin: frz_zkbin.clone(),
+            freeze_pk: frz_pk.clone(),
         };
 
         let debris = builder.build()?;
@@ -141,7 +209,7 @@ impl TestHarness {
         let mut tx_builder =
             TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
         let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?;
+        let sigs = tx.create_sigs(&mut OsRng, &[mint_keypair.secret])?;
         tx.signatures = vec![sigs];
         tx_action_benchmark.creation_times.push(timer.elapsed());
 

+ 7 - 4
src/contract/test-harness/src/vks.rs

@@ -38,7 +38,8 @@ use darkfi_dao_contract::{
 };
 use darkfi_deployooor_contract::DEPLOY_CONTRACT_ZKAS_DERIVE_NS_V1;
 use darkfi_money_contract::{
-    MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+    MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_BURN_NS_V1,
+    MONEY_CONTRACT_ZKAS_FEE_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
     MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
 };
 use darkfi_sdk::crypto::{contract_id::DEPLOYOOOR_CONTRACT_ID, DAO_CONTRACT_ID, MONEY_CONTRACT_ID};
@@ -46,8 +47,8 @@ use darkfi_serial::{deserialize, serialize};
 use log::debug;
 
 /// Update this if any circuits are changed
-const VKS_HASH: &str = "8d491e5f127c14ddaa4eb9ac0de25fa3971c5ce7c794a62807c1c7283bcdaeae";
-const PKS_HASH: &str = "a9e4e440db9d467bbd61fb9ddc900c9bd155bbbd02f7c73e9012b558daf4af00";
+const VKS_HASH: &str = "e775e5a40a07a5048819bf137040c644a881624f3cf0785487f7a2253400d105";
+const PKS_HASH: &str = "a3307265fb6fbf8620080634a6ae10d82ceaf0394faeaad2a4bdf00c92bb8ade";
 
 fn pks_path(typ: &str) -> Result<PathBuf> {
     let output = Command::new("git").arg("rev-parse").arg("--show-toplevel").output()?.stdout;
@@ -119,6 +120,7 @@ pub fn read_or_gen_vks_and_pks() -> Result<(Pks, Vks)> {
         &include_bytes!("../../money/proof/burn_v1.zk.bin")[..],
         &include_bytes!("../../money/proof/token_mint_v1.zk.bin")[..],
         &include_bytes!("../../money/proof/token_freeze_v1.zk.bin")[..],
+        &include_bytes!("../../money/proof/auth_token_mint_v1.zk.bin")[..],
         // DAO
         &include_bytes!("../../dao/proof/dao-mint.zk.bin")[..],
         &include_bytes!("../../dao/proof/dao-propose-input.zk.bin")[..],
@@ -187,7 +189,8 @@ pub fn inject(sled_db: &sled::Db, vks: &Vks) -> Result<()> {
             MONEY_CONTRACT_ZKAS_MINT_NS_V1 |
             MONEY_CONTRACT_ZKAS_BURN_NS_V1 |
             MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1 |
-            MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1 => {
+            MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1 |
+            MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1 => {
                 let key = serialize(&namespace.as_str());
                 let value = serialize(&(bincode.clone(), vk.clone()));
                 money_zkas_tree.insert(key, value)?;

+ 48 - 0
src/sdk/src/crypto/func_ref.rs

@@ -0,0 +1,48 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+#[cfg(feature = "async")]
+use darkfi_serial::async_trait;
+use darkfi_serial::{SerialDecodable, SerialEncodable};
+use pasta_curves::pallas;
+
+use super::{poseidon_hash, ContractId};
+
+pub type FunctionCode = u8;
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, SerialEncodable, SerialDecodable)]
+pub struct FuncRef {
+    pub contract_id: ContractId,
+    pub func_code: FunctionCode,
+}
+
+impl FuncRef {
+    pub fn to_func_id(&self) -> FuncId {
+        let func_id =
+            poseidon_hash([self.contract_id.inner(), pallas::Base::from(self.func_code as u64)]);
+        FuncId(func_id)
+    }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, SerialEncodable, SerialDecodable)]
+pub struct FuncId(pallas::Base);
+
+impl FuncId {
+    pub fn inner(&self) -> pallas::Base {
+        self.0
+    }
+}

+ 4 - 0
src/sdk/src/crypto/mod.rs

@@ -34,6 +34,10 @@ pub use keypair::{Keypair, PublicKey, SecretKey};
 pub mod contract_id;
 pub use contract_id::{ContractId, DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID};
 
+/// Function ID definitions and methods
+pub mod func_ref;
+pub use func_ref::{FuncId, FuncRef};
+
 /// Token ID definitions and methods
 pub mod token_id;
 pub use token_id::{TokenId, DARK_TOKEN_ID};

+ 11 - 0
src/sdk/src/crypto/token_id.rs

@@ -45,6 +45,7 @@ pub struct TokenId(pallas::Base);
 
 impl TokenId {
     /// Derives a `TokenId` from a `SecretKey` (mint authority)
+    #[deprecated]
     pub fn derive(mint_authority: SecretKey) -> Self {
         let public_key = PublicKey::from_secret(mint_authority);
         let (x, y) = public_key.xy();
@@ -53,12 +54,22 @@ impl TokenId {
     }
 
     /// Derives a `TokenId` from a `PublicKey`
+    #[deprecated]
     pub fn derive_public(public_key: PublicKey) -> Self {
         let (x, y) = public_key.xy();
         let hash = poseidon_hash([*TOKEN_ID_PREFIX, x, y]);
         Self(hash)
     }
 
+    pub fn derive_from(
+        func_id: pallas::Base,
+        user_data: pallas::Base,
+        blind: pallas::Base,
+    ) -> Self {
+        let token_id = poseidon_hash([func_id, user_data, blind]);
+        Self(token_id)
+    }
+
     /// Get the inner `pallas::Base` element.
     pub fn inner(&self) -> pallas::Base {
         self.0