diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 427256c90..fa9d1cfb7 100644 --- a/doc/src/SUMMARY.md +++ b/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 diff --git a/doc/src/dep/0003.md b/doc/src/dep/0003.md index 84660bf54..b665815ce 100644 --- a/doc/src/dep/0003.md +++ b/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()` diff --git a/src/contract/dao/src/client/auth_xfer.rs b/src/contract/dao/src/client/auth_xfer.rs index 9910bb4de..2bbdbe677 100644 --- a/src/contract/dao/src/client/auth_xfer.rs +++ b/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)) diff --git a/src/contract/dao/src/client/exec.rs b/src/contract/dao/src/client/exec.rs index 78d25314d..3b983f003 100644 --- a/src/contract/dao/src/client/exec.rs +++ b/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 { diff --git a/src/contract/dao/src/client/propose.rs b/src/contract/dao/src/client/propose.rs index 3696b518a..3a448843d 100644 --- a/src/contract/dao/src/client/propose.rs +++ b/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 = diff --git a/src/contract/dao/src/client/vote.rs b/src/contract/dao/src/client/vote.rs index 5917a0961..beb051657 100644 --- a/src/contract/dao/src/client/vote.rs +++ b/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 = diff --git a/src/contract/money/proof/auth_token_mint_v1.zk b/src/contract/money/proof/auth_token_mint_v1.zk new file mode 100644 index 000000000..cf769831b --- /dev/null +++ b/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)); +} diff --git a/src/contract/money/proof/token_freeze_v1.zk b/src/contract/money/proof/token_freeze_v1.zk index 223d8582a..5daa71220 100644 --- a/src/contract/money/proof/token_freeze_v1.zk +++ b/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); } + diff --git a/src/contract/money/proof/token_mint_v1.zk b/src/contract/money/proof/token_mint_v1.zk index f2445a31b..c9013334f 100644 --- a/src/contract/money/proof/token_mint_v1.zk +++ b/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); + token_id = poseidon_hash(auth_parent, token_user_data, token_blind); + constrain_instance(auth_parent); - # Poseidon hash of the minted coin - C = poseidon_hash( - rcpt_x, - rcpt_y, - supply, + # 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); } diff --git a/src/contract/money/src/client/auth_token_mint_v1.rs b/src/contract/money/src/client/auth_token_mint_v1.rs new file mode 100644 index 000000000..b31e225c6 --- /dev/null +++ b/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 . + */ + +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, +} + +/// 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 { + 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(¬e, &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) + } +} diff --git a/src/contract/money/src/client/genesis_mint_v1.rs b/src/contract/money/src/client/genesis_mint_v1.rs index 3f4e6e11b..222eb44d4 100644 --- a/src/contract/money/src/client/genesis_mint_v1.rs +++ b/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, } @@ -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) } diff --git a/src/contract/money/src/client/mod.rs b/src/contract/money/src/client/mod.rs index 320efbf2b..1f7dd4b4f 100644 --- a/src/contract/money/src/client/mod.rs +++ b/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. diff --git a/src/contract/money/src/client/token_freeze_v1.rs b/src/contract/money/src/client/token_freeze_v1.rs index 28b3529a5..686ad3fbd 100644 --- a/src/contract/money/src/client/token_freeze_v1.rs +++ b/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, } -pub struct TokenFreezeRevealed { - pub signature_public: PublicKey, - pub token_id: TokenId, -} - -impl TokenFreezeRevealed { - pub fn to_vec(&self) -> Vec { - 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 mint_pubkey = self.mint_keypair.public; + let token_id = self.token_attrs.to_token_id(); + + 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, &self.freeze_zkbin); + let proof = Proof::create(&self.freeze_pk, &[circuit], &public_inputs, &mut OsRng)?; + + let params = MoneyTokenFreezeParamsV1 { mint_public: self.mint_keypair.public, token_id }; 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 prover_witnesses = vec![Witness::Base(Value::known(mint_authority.secret.inner()))]; - - let circuit = ZkCircuit::new(prover_witnesses, zkbin); - let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?; - - Ok((proof, public_inputs)) -} diff --git a/src/contract/money/src/client/token_mint_v1.rs b/src/contract/money/src/client/token_mint_v1.rs index 0507a481c..228680daf 100644 --- a/src/contract/money/src/client/token_mint_v1.rs +++ b/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, } -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 { - 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 { info!("Building Money::TokenMintV1 contract call"); - assert!(self.amount != 0); + let (public_x, public_y) = self.coin_attrs.public_key.xy(); - // 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 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 input = TransferCallClearInput { - value: self.amount, - token_id, - signature_secret: self.mint_authority.secret, - }; + let coin = self.coin_attrs.to_coin(); - 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), - }; + let public_inputs = vec![self.token_attrs.auth_parent.inner(), coin.inner()]; - // 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 circuit = ZkCircuit::new(prover_witnesses, &self.mint_zkbin); + let proof = Proof::create(&self.mint_pk, &[circuit], &public_inputs, &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(¬e, &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 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)) -} diff --git a/src/contract/money/src/entrypoint.rs b/src/contract/money/src/entrypoint.rs index 753193de1..c644d0a6b 100644 --- a/src/contract/money/src/entrypoint.rs +++ b/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)?) + } } } diff --git a/src/contract/money/src/entrypoint/auth_token_mint_v1.rs b/src/contract/money/src/entrypoint/auth_token_mint_v1.rs new file mode 100644 index 000000000..e926644b1 --- /dev/null +++ b/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 . + */ + +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>, +) -> Result, 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)> = vec![]; + // Public keys for the transaction signatures we have to verify. + let signature_pubkeys: Vec = 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>, +) -> Result, 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(¶ms.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(()) +} diff --git a/src/contract/money/src/entrypoint/token_freeze_v1.rs b/src/contract/money/src/entrypoint/token_freeze_v1.rs index 79af6891b..7dc26f8a0 100644 --- a/src/contract/money/src/entrypoint/token_freeze_v1.rs +++ b/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)> = vec![]; // Public keys for the transaction signatures we have to verify - let signature_pubkeys: Vec = vec![params.signature_public]; + let signature_pubkeys: Vec = 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(¶ms.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(()) } diff --git a/src/contract/money/src/entrypoint/token_mint_v1.rs b/src/contract/money/src/entrypoint/token_mint_v1.rs index 46fb67dad..e6e2efc6b 100644 --- a/src/contract/money/src/entrypoint/token_mint_v1.rs +++ b/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)> = 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]; + let signature_pubkeys: Vec = vec![]; - // 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 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(¶ms.output.coin))? { + if db_contains_key(coins_db, &serialize(¶ms.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)?; diff --git a/src/contract/money/src/lib.rs b/src/contract/money/src/lib.rs index 9bc28be15..a62b3b922 100644 --- a/src/contract/money/src/lib.rs +++ b/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 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"; diff --git a/src/contract/money/src/model.rs b/src/contract/money/src/model.rs index 04765a7e7..366781784 100644 --- a/src/contract/money/src/model.rs +++ b/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` diff --git a/src/contract/money/tests/genesis_mint.rs b/src/contract/money/tests/genesis_mint.rs index 9bf50b2ce..1909fb385 100644 --- a/src/contract/money/tests/genesis_mint.rs +++ b/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 diff --git a/src/contract/money/tests/integration.rs b/src/contract/money/tests/integration.rs index b18dbe42e..2c276c1a6 100644 --- a/src/contract/money/tests/integration.rs +++ b/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); diff --git a/src/contract/money/tests/mint_pay_swap.rs b/src/contract/money/tests/mint_pay_swap.rs index 1178255f6..d36743bfe 100644 --- a/src/contract/money/tests/mint_pay_swap.rs +++ b/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, ¶ms, 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, ¶ms.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, ¶ms, 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, ¶ms.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, ¶ms.outputs[0], None)?; + let alice_oc = th.gather_owncoin_from_output(&Holder::Alice, ¶ms.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, ¶ms.outputs[0], None)?; + let bob_oc = th.gather_owncoin_from_output(&Holder::Bob, ¶ms.outputs[0], None)?; bob_owncoins.push(bob_oc); assert!(bob_owncoins.len() == 1); diff --git a/src/contract/money/tests/pow_reward.rs b/src/contract/money/tests/pow_reward.rs index ab3089ae4..e36e73434 100644 --- a/src/contract/money/tests/pow_reward.rs +++ b/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 diff --git a/src/contract/money/tests/token_mint.rs b/src/contract/money/tests/token_mint.rs index e0ce6e375..79f852475 100644 --- a/src/contract/money/tests/token_mint.rs +++ b/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)?; diff --git a/src/contract/money/tests/txs_verification.rs b/src/contract/money/tests/txs_verification.rs index 047bb3e60..c3ee75e3a 100644 --- a/src/contract/money/tests/txs_verification.rs +++ b/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); diff --git a/src/contract/money/tests/verification_bench.rs b/src/contract/money/tests/verification_bench.rs index 7ec30f741..506f28870 100644 --- a/src/contract/money/tests/verification_bench.rs +++ b/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); diff --git a/src/contract/test-harness/src/lib.rs b/src/contract/test-harness/src/lib.rs index 281dfdb4f..f75126e66 100644 --- a/src/contract/test-harness/src/lib.rs +++ b/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, ) -> Result { 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, + ) -> Result { + 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) diff --git a/src/contract/test-harness/src/money_airdrop.rs b/src/contract/test-harness/src/money_airdrop.rs index eed5bbefa..f739f4d8f 100644 --- a/src/contract/test-harness/src/money_airdrop.rs +++ b/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) } diff --git a/src/contract/test-harness/src/money_genesis_mint.rs b/src/contract/test-harness/src/money_genesis_mint.rs index 7ef9da494..f7d73bbd0 100644 --- a/src/contract/test-harness/src/money_genesis_mint.rs +++ b/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(); diff --git a/src/contract/test-harness/src/money_token.rs b/src/contract/test-harness/src/money_token.rs index 9cbfb41de..881738cbe 100644 --- a/src/contract/test-harness/src/money_token.rs +++ b/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, user_data: Option, - ) -> 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()); diff --git a/src/contract/test-harness/src/vks.rs b/src/contract/test-harness/src/vks.rs index 0b59bae9f..c6cf2294b 100644 --- a/src/contract/test-harness/src/vks.rs +++ b/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 { 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)?; diff --git a/src/sdk/src/crypto/func_ref.rs b/src/sdk/src/crypto/func_ref.rs new file mode 100644 index 000000000..8cbd59e46 --- /dev/null +++ b/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 . + */ +#[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 + } +} diff --git a/src/sdk/src/crypto/mod.rs b/src/sdk/src/crypto/mod.rs index a0c386864..ec6aa3ba5 100644 --- a/src/sdk/src/crypto/mod.rs +++ b/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}; diff --git a/src/sdk/src/crypto/token_id.rs b/src/sdk/src/crypto/token_id.rs index 5f95ad31d..b7dcccfea 100644 --- a/src/sdk/src/crypto/token_id.rs +++ b/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