diff --git a/src/contract/money/proof/token_freeze_v1.zk b/src/contract/money/proof/token_freeze_v1.zk
index 95aed526f..e05502645 100644
--- a/src/contract/money/proof/token_freeze_v1.zk
+++ b/src/contract/money/proof/token_freeze_v1.zk
@@ -12,8 +12,9 @@ circuit "TokenFreeze_V1" {
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);
+ # I don't think we need to enforce these two as public inputs
+ #constrain_instance(mint_x);
+ #constrain_instance(mint_y);
# Derive the token ID
token_id = poseidon_hash(mint_x, mint_y);
diff --git a/src/contract/money/src/client/freeze_v1.rs b/src/contract/money/src/client/freeze_v1.rs
new file mode 100644
index 000000000..676fde8ab
--- /dev/null
+++ b/src/contract/money/src/client/freeze_v1.rs
@@ -0,0 +1,94 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+use darkfi::{
+ zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit},
+ zkas::ZkBinary,
+ Result,
+};
+use darkfi_sdk::{
+ crypto::{poseidon_hash, Keypair, TokenId},
+ pasta::pallas,
+};
+use log::{debug, info};
+use rand::rngs::OsRng;
+
+use crate::model::MoneyFreezeParamsV1;
+
+pub struct FreezeCallDebris {
+ pub params: MoneyFreezeParamsV1,
+ pub proofs: Vec,
+}
+
+pub struct TokenFreezeRevealed {
+ pub token_id: TokenId,
+}
+
+impl TokenFreezeRevealed {
+ pub fn to_vec(&self) -> Vec {
+ vec![self.token_id.inner()]
+ }
+}
+
+/// Struct holding necessary information to build a `Money::FreezeV1` contract call.
+pub struct FreezeCallBuilder {
+ /// Mint authority keypair
+ pub mint_authority: Keypair,
+ /// `TokenFreeze_V1` zkas circuit ZkBinary
+ pub token_freeze_zkbin: ZkBinary,
+ /// Proving key for the `TokenFreeze_V1` zk circuit,
+ pub token_freeze_pk: ProvingKey,
+}
+
+impl FreezeCallBuilder {
+ pub fn build(&self) -> Result {
+ debug!("Building Money::FreezeV1 contract call");
+
+ // For the Freeze call, we just need to produce a valid signature,
+ // and enforce the correct derivation inside ZK.
+
+ info!("Creating token freeze proof");
+ let (proof, _public_inputs) = create_token_freeze_proof(
+ &self.token_freeze_zkbin,
+ &self.token_freeze_pk,
+ &self.mint_authority,
+ )?;
+
+ let params = MoneyFreezeParamsV1 { signature_public: self.mint_authority.public };
+ let debris = FreezeCallDebris { params, proofs: vec![proof] };
+ Ok(debris)
+ }
+}
+
+pub(crate) fn create_token_freeze_proof(
+ zkbin: &ZkBinary,
+ pk: &ProvingKey,
+ mint_authority: &Keypair,
+) -> Result<(Proof, TokenFreezeRevealed)> {
+ let (mint_x, mint_y) = mint_authority.public.xy();
+ let token_id = TokenId::from(poseidon_hash([mint_x, mint_y]));
+
+ let public_inputs = TokenFreezeRevealed { token_id };
+
+ let prover_witnesses = vec![Witness::Base(Value::known(mint_authority.secret.inner()))];
+
+ let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
+ let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
+
+ Ok((proof, public_inputs))
+}
diff --git a/src/contract/money/src/client/mod.rs b/src/contract/money/src/client/mod.rs
index 8a1559597..0d754e0b8 100644
--- a/src/contract/money/src/client/mod.rs
+++ b/src/contract/money/src/client/mod.rs
@@ -41,6 +41,9 @@ pub mod swap_v1;
/// `Money::MintV1` API
pub mod mint_v1;
+/// `Money::FreezeV1` API
+pub mod freeze_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.
diff --git a/src/contract/money/src/entrypoint/freeze_v1.rs b/src/contract/money/src/entrypoint/freeze_v1.rs
index 7b43fd33e..f3bb0b439 100644
--- a/src/contract/money/src/entrypoint/freeze_v1.rs
+++ b/src/contract/money/src/entrypoint/freeze_v1.rs
@@ -49,8 +49,8 @@ pub(crate) fn money_freeze_get_metadata_v1(
let (mint_x, mint_y) = params.signature_public.xy();
let token_id = poseidon_hash([mint_x, mint_y]);
- zk_public_inputs
- .push((MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1.to_string(), vec![mint_x, mint_y, token_id]));
+ // 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![token_id]));
// Serialize everything gathered and return it
let mut metadata = vec![];