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

contract/money: decoupled auth token freeze

skoupidi 2 месяцев назад
Родитель
Сommit
7acdb1dc98

+ 12 - 11
bin/drk/src/token.rs

@@ -32,7 +32,8 @@ use darkfi_money_contract::{
         auth_token_mint_v1::AuthTokenMintCallBuilder, token_mint_v1::TokenMintCallBuilder,
     },
     model::{CoinAttributes, TokenAttributes, TokenId},
-    MoneyFunction, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
+    MoneyFunction, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_FREEZE_NS_V1,
+    MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
     MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
 };
 use darkfi_sdk::{
@@ -398,10 +399,10 @@ impl Drk {
         // We also do this through the RPC.
         let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
 
-        let Some(auth_mint_zkbin) =
-            zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1)
+        let Some(auth_freeze_zkbin) =
+            zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_AUTH_TOKEN_FREEZE_NS_V1)
         else {
-            return Err(Error::Custom("Auth token mint circuit not found".to_string()))
+            return Err(Error::Custom("Auth token freeze circuit not found".to_string()))
         };
 
         let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
@@ -409,23 +410,23 @@ impl Drk {
             return Err(Error::Custom("Fee circuit not found".to_string()))
         };
 
-        let auth_mint_zkbin = ZkBinary::decode(&auth_mint_zkbin.1, false)?;
+        let auth_freeze_zkbin = ZkBinary::decode(&auth_freeze_zkbin.1, false)?;
         let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
 
-        let auth_mint_circuit =
-            ZkCircuit::new(empty_witnesses(&auth_mint_zkbin)?, &auth_mint_zkbin);
+        let auth_freeze_circuit =
+            ZkCircuit::new(empty_witnesses(&auth_freeze_zkbin)?, &auth_freeze_zkbin);
         let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
 
-        // Creating AuthTokenMint and Fee circuits proving keys
-        let auth_mint_pk = ProvingKey::build(auth_mint_zkbin.k, &auth_mint_circuit);
+        // Creating AuthTokenFreeze and Fee circuits proving keys
+        let auth_freeze_pk = ProvingKey::build(auth_freeze_zkbin.k, &auth_freeze_circuit);
         let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
 
         // Create the freeze call
         let builder = AuthTokenFreezeCallBuilder {
             mint_keypair: mint_authority,
             token_attrs,
-            auth_mint_zkbin,
-            auth_mint_pk,
+            auth_freeze_zkbin,
+            auth_freeze_pk,
         };
         let freeze_debris = builder.build()?;
         let mut data = vec![MoneyFunction::AuthTokenFreezeV1 as u8];

+ 36 - 0
src/contract/money/proof/auth_token_freeze_v1.zk

@@ -0,0 +1,36 @@
+k = 11;
+field = "pallas";
+
+constant "AuthTokenFreeze_V1" {
+    EcFixedPointBase NULLIFIER_K,
+}
+
+witness "AuthTokenFreeze_V1" {
+    # Secret key used by the mint authority
+    Base mint_secret,
+
+    # TokenAttributes {
+    Base token_auth_function,
+    Base token_blind,
+    # }
+}
+
+circuit "AuthTokenFreeze_V1" {
+    # Derive and constrain the public key of the mint authority for the
+    # signature.
+    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);
+
+    # Constrain the token authority function
+    constrain_instance(token_auth_function);
+
+    # Derive and constrain the token ID
+    token_user_data = poseidon_hash(mint_x, mint_y);
+    token_id = poseidon_hash(token_auth_function, token_user_data, token_blind);
+    constrain_instance(token_id);
+
+    # At this point we've enforced all of our public inputs.
+}

+ 18 - 13
src/contract/money/src/client/auth_token_freeze_v1.rs

@@ -36,11 +36,12 @@ pub struct AuthTokenFreezeCallDebris {
 pub struct AuthTokenFreezeCallBuilder {
     /// Mint authority keypair
     pub mint_keypair: Keypair,
+    /// Token attributes
     pub token_attrs: TokenAttributes,
-    /// `AuthTokenMint_V1` zkas circuit ZkBinary
-    pub auth_mint_zkbin: ZkBinary,
-    /// Proving key for the `AuthTokenMint_V1` zk circuit,
-    pub auth_mint_pk: ProvingKey,
+    /// `AuthTokenFreeze_V1` zkas circuit ZkBinary
+    pub auth_freeze_zkbin: ZkBinary,
+    /// Proving key for the `AuthTokenFreeze_V1` zk circuit,
+    pub auth_freeze_pk: ProvingKey,
 }
 
 impl AuthTokenFreezeCallBuilder {
@@ -50,23 +51,27 @@ impl AuthTokenFreezeCallBuilder {
         // For the AuthTokenFreeze call, we just need to produce a valid signature,
         // and enforce the correct derivation inside ZK.
         let prover_witnesses = vec![
+            // Secret key used by the mint authority
+            Witness::Base(Value::known(self.mint_keypair.secret.inner())),
             // Token attributes
             Witness::Base(Value::known(self.token_attrs.auth_parent.inner())),
             Witness::Base(Value::known(self.token_attrs.blind.inner())),
-            // Secret key used by mint
-            Witness::Base(Value::known(self.mint_keypair.secret.inner())),
         ];
 
-        let mint_pubkey = self.mint_keypair.public;
+        let mint_public = 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("proof/witness/auth_token_mint_v1.json", &prover_witnesses, &public_inputs);
-        let circuit = ZkCircuit::new(prover_witnesses, &self.auth_mint_zkbin);
-        let proof = Proof::create(&self.auth_mint_pk, &[circuit], &public_inputs, &mut OsRng)?;
+        let public_inputs = vec![
+            mint_public.x(),
+            mint_public.y(),
+            self.token_attrs.auth_parent.inner(),
+            token_id.inner(),
+        ];
+        //darkfi::zk::export_witness_json("proof/witness/auth_token_freeze_v1.json", &prover_witnesses, &public_inputs);
+        let circuit = ZkCircuit::new(prover_witnesses, &self.auth_freeze_zkbin);
+        let proof = Proof::create(&self.auth_freeze_pk, &[circuit], &public_inputs, &mut OsRng)?;
 
-        let params =
-            MoneyAuthTokenFreezeParamsV1 { mint_public: self.mint_keypair.public, token_id };
+        let params = MoneyAuthTokenFreezeParamsV1 { mint_public, token_id };
         let debris = AuthTokenFreezeCallDebris { params, proofs: vec![proof] };
         Ok(debris)
     }

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

@@ -122,6 +122,7 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
     let burn_v1_bincode = include_bytes!("../proof/burn_v1.zk.bin");
     let token_mint_v1_bincode = include_bytes!("../proof/token_mint_v1.zk.bin");
     let auth_token_mint_v1_bincode = include_bytes!("../proof/auth_token_mint_v1.zk.bin");
+    let auth_token_freeze_v1_bincode = include_bytes!("../proof/auth_token_freeze_v1.zk.bin");
 
     // For that, we use `wasm::db::zkas_wasm::db::db_set` and pass in the bincode.
     wasm::db::zkas_db_set(&fee_v1_bincode[..])?;
@@ -129,6 +130,7 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
     wasm::db::zkas_db_set(&burn_v1_bincode[..])?;
     wasm::db::zkas_db_set(&token_mint_v1_bincode[..])?;
     wasm::db::zkas_db_set(&auth_token_mint_v1_bincode[..])?;
+    wasm::db::zkas_db_set(&auth_token_freeze_v1_bincode[..])?;
 
     let tx_hash = wasm::util::get_tx_hash()?;
     // The max outputs for a tx in BTC is 2501

+ 14 - 7
src/contract/money/src/entrypoint/auth_token_freeze_v1.rs

@@ -17,7 +17,7 @@
  */
 
 use darkfi_sdk::{
-    crypto::{ContractId, PublicKey},
+    crypto::{ContractId, FuncRef, PublicKey},
     dark_tree::DarkLeaf,
     error::{ContractError, ContractResult},
     msg,
@@ -29,7 +29,8 @@ use darkfi_serial::{deserialize, serialize, Encodable};
 use crate::{
     error::MoneyError,
     model::{MoneyAuthTokenFreezeParamsV1, MoneyAuthTokenFreezeUpdateV1},
-    MONEY_CONTRACT_TOKEN_FREEZE_TREE, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1,
+    MoneyFunction::AuthTokenMintV1,
+    MONEY_CONTRACT_TOKEN_FREEZE_TREE, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_FREEZE_NS_V1,
 };
 
 /// `get_metadata` function for `Money::AuthTokenFreezeV1`
@@ -46,13 +47,19 @@ pub(crate) fn money_auth_token_freeze_get_metadata_v1(
     // Public keys for the transaction signatures we have to verify
     let signature_pubkeys: Vec<PublicKey> = vec![params.mint_public];
 
-    // Derive the TokenId from the public key
+    // Grab the mint authority pubkey coords
     let (mint_x, mint_y) = params.mint_public.xy();
 
-    // In ZK we just verify that the token ID is properly derived from the authority.
+    // Derive the function ID corresponding to the money contract token
+    // mint auth function.
+    let func_id =
+        FuncRef { contract_id: self_.contract_id, func_code: AuthTokenMintV1 as u8 }.to_func_id();
+
+    // In ZK we just verify that the token ID is properly derived from
+    // the authority.
     zk_public_inputs.push((
-        MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1.to_string(),
-        vec![mint_x, mint_y, params.token_id.inner()],
+        MONEY_CONTRACT_ZKAS_AUTH_TOKEN_FREEZE_NS_V1.to_string(),
+        vec![mint_x, mint_y, func_id.inner(), params.token_id.inner()],
     ));
 
     // Serialize everything gathered and return it
@@ -81,7 +88,7 @@ pub(crate) fn money_auth_token_freeze_process_instruction_v1(
         return Err(MoneyError::TokenMintFrozen.into())
     }
 
-    // Create a state update. We only need the new coin.
+    // Create a state update. We only need the frozen token ID.
     let update = MoneyAuthTokenFreezeUpdateV1 { token_id: params.token_id };
     Ok(serialize(&update))
 }

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

@@ -103,5 +103,7 @@ pub const MONEY_CONTRACT_ZKAS_MINT_NS_V1: &str = "Mint_V1";
 pub const MONEY_CONTRACT_ZKAS_BURN_NS_V1: &str = "Burn_V1";
 /// zkas token auth mint circuit namespace
 pub const MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1: &str = "AuthTokenMint_V1";
+/// zkas token auth freeze circuit namespace
+pub const MONEY_CONTRACT_ZKAS_AUTH_TOKEN_FREEZE_NS_V1: &str = "AuthTokenFreeze_V1";
 /// zkas token mint circuit namespace
 pub const MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1: &str = "TokenMint_V1";

+ 3 - 2
src/contract/money/tests/mint_pay_swap.rs

@@ -48,8 +48,9 @@ fn mint_pay_swap() -> Result<()> {
 
         // Mint tokens for Alice and Bob
         info!(target: "money", "Minting tokens for Alice and Bob");
-        let alice_token = th.token_mint_to_all(ALICE_INITIAL, &Alice, &Alice, block_height).await?;
-        let bob_token = th.token_mint_to_all(BOB_INITIAL, &Bob, &Bob, block_height).await?;
+        let (alice_token, _) =
+            th.token_mint_to_all(ALICE_INITIAL, &Alice, &Alice, block_height).await?;
+        let (bob_token, _) = th.token_mint_to_all(BOB_INITIAL, &Bob, &Bob, block_height).await?;
 
         // Alice sends some tokens to Bob
         info!(target: "money", "Alice sends {ALICE_SEND} to Bob");

+ 15 - 3
src/contract/money/tests/token_mint_burn.rs

@@ -27,7 +27,7 @@ fn token_mint_burn() -> Result<()> {
 
         use Holder::{Alice, Bob};
 
-        const BOB_SUPPLY: u64 = 2000000000; // 10 BOB
+        const BOB_SUPPLY: u64 = 2000000000; // 20 BOB
 
         let block_height = 0;
 
@@ -35,14 +35,26 @@ fn token_mint_burn() -> Result<()> {
 
         // Mint BOB token
         info!("Minting BOB token");
-        let bob_token = th.token_mint_to_all(BOB_SUPPLY, &Bob, &Bob, block_height).await?;
+        let (bob_token, bob_token_blind) =
+            th.token_mint_to_all(BOB_SUPPLY, &Bob, &Bob, block_height).await?;
 
         assert_eq!(th.coins(&Bob).len(), 1);
         assert_eq!(th.balance(&Bob, bob_token), BOB_SUPPLY);
 
         // Freeze BOB token authority
         info!("Freezing BOB token authority");
-        th.token_freeze_to_all(&Bob, block_height).await?;
+        th.token_freeze_to_all(&Bob, bob_token_blind, block_height).await?;
+
+        // Malicious: verify token authority cannot freeze again
+        info!("Checking freezing BOB token authority again");
+        assert!(th.token_freeze_to_all(&Bob, bob_token_blind, block_height).await.is_err());
+
+        // Malicious: verify we cant mint new BOB tokens
+        info!("Checking minting BOB token after freeze");
+        assert!(th
+            .token_mint_with_blind_to_all(BOB_SUPPLY, &Bob, &Bob, bob_token_blind, block_height)
+            .await
+            .is_err());
 
         // Burn the BOB tokens (single coin supply)
         info!("Burning BOB token");

+ 14 - 26
src/contract/test-harness/src/lib.rs

@@ -460,32 +460,12 @@ impl TestHarness {
         holder: &Holder,
         recipient: &Holder,
         block_height: u32,
-    ) -> Result<TokenId> {
+    ) -> Result<(TokenId, BaseBlind)> {
         let token_blind = BaseBlind::random(&mut OsRng);
-        let (tx, mint_params, auth_params, fee_params) = self
-            .token_mint(amount, holder, recipient, token_blind, None, None, block_height)
-            .await?;
-
-        // Derive the Token ID
-        let token_id = self.derive_token_id(recipient, token_blind);
-
-        let holders = self.holder_keys.clone();
-        for h in &holders {
-            self.execute_token_mint_tx(
-                h,
-                tx.clone(),
-                &mint_params,
-                &auth_params,
-                &fee_params,
-                block_height,
-                true,
-            )
+        let token_id = self
+            .token_mint_with_blind_to_all(amount, holder, recipient, token_blind, block_height)
             .await?;
-        }
-
-        self.assert_all_trees();
-
-        Ok(token_id)
+        Ok((token_id, token_blind))
     }
 
     /// Mint a token with a specific `token_blind` and execute on all
@@ -604,8 +584,14 @@ impl TestHarness {
 
     /// Freeze a token authority for `holder` and execute on all registered
     /// holders.
-    pub async fn token_freeze_to_all(&mut self, holder: &Holder, block_height: u32) -> Result<()> {
-        let (tx, freeze_params, fee_params) = self.token_freeze(holder, block_height).await?;
+    pub async fn token_freeze_to_all(
+        &mut self,
+        holder: &Holder,
+        token_blind: BaseBlind,
+        block_height: u32,
+    ) -> Result<()> {
+        let (tx, freeze_params, fee_params) =
+            self.token_freeze(holder, token_blind, block_height).await?;
 
         let holders = self.holder_keys.clone();
         for h in &holders {
@@ -619,7 +605,9 @@ impl TestHarness {
             )
             .await?;
         }
+
         self.assert_all_trees();
+
         Ok(())
     }
 

+ 7 - 6
src/contract/test-harness/src/money_token.rs

@@ -31,7 +31,8 @@ use darkfi_money_contract::{
         CoinAttributes, MoneyAuthTokenFreezeParamsV1, MoneyAuthTokenMintParamsV1, MoneyFeeParamsV1,
         MoneyTokenMintParamsV1, TokenAttributes,
     },
-    MoneyFunction, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
+    MoneyFunction, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_FREEZE_NS_V1,
+    MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
 };
 use darkfi_sdk::{
     crypto::{poseidon_hash, BaseBlind, Blind, FuncId, FuncRef, MerkleNode, MONEY_CONTRACT_ID},
@@ -211,13 +212,14 @@ impl TestHarness {
     pub async fn token_freeze(
         &mut self,
         holder: &Holder,
+        token_blind: BaseBlind,
         block_height: u32,
     ) -> Result<(Transaction, MoneyAuthTokenFreezeParamsV1, Option<MoneyFeeParamsV1>)> {
         let wallet = self.wallet(holder);
         let mint_authority = wallet.token_mint_authority;
 
-        let (auth_mint_pk, auth_mint_zkbin) =
-            self.proving_keys.get(MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1).unwrap();
+        let (auth_freeze_pk, auth_freeze_zkbin) =
+            self.proving_keys.get(MONEY_CONTRACT_ZKAS_AUTH_TOKEN_FREEZE_NS_V1).unwrap();
 
         let auth_func_id = FuncRef {
             contract_id: *MONEY_CONTRACT_ID,
@@ -226,7 +228,6 @@ impl TestHarness {
         .to_func_id();
 
         let (mint_auth_x, mint_auth_y) = mint_authority.public.xy();
-        let token_blind = BaseBlind::random(&mut OsRng);
 
         let token_attrs = TokenAttributes {
             auth_parent: auth_func_id,
@@ -238,8 +239,8 @@ impl TestHarness {
         let builder = AuthTokenFreezeCallBuilder {
             mint_keypair: mint_authority,
             token_attrs,
-            auth_mint_pk: auth_mint_pk.clone(),
-            auth_mint_zkbin: auth_mint_zkbin.clone(),
+            auth_freeze_zkbin: auth_freeze_zkbin.clone(),
+            auth_freeze_pk: auth_freeze_pk.clone(),
         };
         let freeze_debris = builder.build()?;
         let mut data = vec![MoneyFunction::AuthTokenFreezeV1 as u8];

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

@@ -36,8 +36,8 @@ use darkfi_dao_contract::{
     DAO_CONTRACT_ZKAS_VOTE_INPUT_NS, DAO_CONTRACT_ZKAS_VOTE_MAIN_NS,
 };
 use darkfi_money_contract::{
-    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_AUTH_TOKEN_FREEZE_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_MINT_NS_V1,
 };
 use darkfi_sdk::crypto::contract_id::{
@@ -49,8 +49,8 @@ use tracing::debug;
 
 /// Update these if any circuits are changed.
 /// Delete the existing cachefiles, and enable debug logging, you will see the new hashes.
-const PKS_HASH: &str = "f618ddf6916fb043200fdc1611b9a914e08d20a17e97c5a262dde874c3212fd8";
-const VKS_HASH: &str = "e083ec986b85ec3435a880363f266b793708b5fe47714a180ca548273891c1e1";
+const PKS_HASH: &str = "76c13b755770efd5d2fd4f7f635ab5c20366686482ab59f5e7ec7315ee2b8a82";
+const VKS_HASH: &str = "917098f91e004cd28fe12e51a4204b7d3ee676a9dd8bb89e5a935c9d2f55ca99";
 
 /// Build a `PathBuf` to a cachefile
 fn cache_path(typ: &str) -> Result<PathBuf> {
@@ -127,6 +127,7 @@ pub fn get_cached_pks_and_vks() -> Result<(Pks, Vks)> {
         &include_bytes!("../../money/proof/burn_v1.zk.bin")[..],
         &include_bytes!("../../money/proof/token_mint_v1.zk.bin")[..],
         &include_bytes!("../../money/proof/auth_token_mint_v1.zk.bin")[..],
+        &include_bytes!("../../money/proof/auth_token_freeze_v1.zk.bin")[..],
         // DAO
         &include_bytes!("../../dao/proof/mint.zk.bin")[..],
         &include_bytes!("../../dao/proof/propose-input.zk.bin")[..],
@@ -199,7 +200,8 @@ pub fn inject(overlay: &BlockchainOverlayPtr, 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_AUTH_TOKEN_MINT_NS_V1 => {
+            MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1 |
+            MONEY_CONTRACT_ZKAS_AUTH_TOKEN_FREEZE_NS_V1 => {
                 let key = serialize(&namespace.as_str());
                 let value = serialize(&(bincode.clone(), vk.clone()));
                 overlay.insert(&money_db_name, &key, &value)?;