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

split money client transfer_v1.rs into submodules: make_transfer_call(), builder and proofs.

x 2 лет назад
Родитель
Сommit
7598243783

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

@@ -30,7 +30,9 @@ use rand::rngs::OsRng;
 
 use crate::{
     client::{
-        transfer_v1::{create_transfer_mint_proof, TransferCallClearInput, TransferCallOutput},
+        transfer_v1::{
+            proof::create_transfer_mint_proof, TransferCallClearInput, TransferCallOutput,
+        },
         MoneyNote,
     },
     model::{ClearInput, Coin, MoneyTokenMintParamsV1, Output},

+ 3 - 1
src/contract/money/src/client/pow_reward_v1.rs

@@ -34,7 +34,9 @@ use rand::rngs::OsRng;
 
 use crate::{
     client::{
-        transfer_v1::{create_transfer_mint_proof, TransferCallClearInput, TransferCallOutput},
+        transfer_v1::{
+            proof::create_transfer_mint_proof, TransferCallClearInput, TransferCallOutput,
+        },
         MoneyNote,
     },
     model::{ClearInput, Coin, MoneyPoWRewardParamsV1, Output},

+ 2 - 2
src/contract/money/src/client/swap_v1.rs

@@ -36,8 +36,8 @@ use rand::rngs::OsRng;
 use crate::{
     client::{
         transfer_v1::{
-            create_transfer_burn_proof, create_transfer_mint_proof, TransferCallInput,
-            TransferCallOutput,
+            proof::{create_transfer_burn_proof, create_transfer_mint_proof},
+            TransferCallInput, TransferCallOutput,
         },
         MoneyNote, OwnCoin,
     },

+ 0 - 557
src/contract/money/src/client/transfer_v1.rs

@@ -1,557 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-//! This API is crufty. Please rework it into something nice to read and nice to use.
-
-use darkfi::{
-    zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit},
-    zkas::ZkBinary,
-    ClientFailed, Result,
-};
-use darkfi_sdk::{
-    bridgetree,
-    bridgetree::Hashable,
-    crypto::{
-        note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, Keypair,
-        MerkleNode, MerkleTree, Nullifier, PublicKey, SecretKey, TokenId,
-    },
-    pasta::pallas,
-};
-use log::{debug, error, info};
-use rand::rngs::OsRng;
-
-use crate::{
-    client::{MoneyNote, OwnCoin},
-    model::{ClearInput, Coin, Input, MoneyTransferParamsV1, Output},
-};
-
-pub struct TransferCallSecrets {
-    /// The ZK proofs created in this builder
-    pub proofs: Vec<Proof>,
-    /// The ephemeral secret keys created for signing
-    pub signature_secrets: Vec<SecretKey>,
-
-    /// Decrypted notes associated with each output
-    pub output_notes: Vec<MoneyNote>,
-
-    /// The value blinds created for the inputs
-    pub input_value_blinds: Vec<pallas::Scalar>,
-    /// The value blinds created for the outputs
-    pub output_value_blinds: Vec<pallas::Scalar>,
-}
-
-impl TransferCallSecrets {
-    pub fn minted_coins(&self, params: &MoneyTransferParamsV1) -> Vec<OwnCoin> {
-        let mut minted_coins = vec![];
-        for (output, output_note) in params.outputs.iter().zip(self.output_notes.iter()) {
-            minted_coins.push(OwnCoin {
-                coin: output.coin,
-                note: output_note.clone(),
-                secret: SecretKey::from(pallas::Base::ZERO),
-                nullifier: Nullifier::from(pallas::Base::ZERO),
-                leaf_position: 0.into(),
-            });
-        }
-        minted_coins
-    }
-}
-
-pub struct TransferMintRevealed {
-    pub coin: Coin,
-    pub value_commit: pallas::Point,
-    pub token_commit: pallas::Base,
-}
-
-impl TransferMintRevealed {
-    pub fn to_vec(&self) -> Vec<pallas::Base> {
-        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![self.coin.inner(), *valcom_coords.x(), *valcom_coords.y(), self.token_commit]
-    }
-}
-
-pub struct TransferBurnRevealed {
-    pub value_commit: pallas::Point,
-    pub token_commit: pallas::Base,
-    pub nullifier: Nullifier,
-    pub merkle_root: MerkleNode,
-    pub spend_hook: pallas::Base,
-    pub user_data_enc: pallas::Base,
-    pub signature_public: PublicKey,
-}
-
-impl TransferBurnRevealed {
-    pub fn to_vec(&self) -> Vec<pallas::Base> {
-        let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
-        let sigpub_coords = self.signature_public.inner().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![
-            self.nullifier.inner(),
-            *valcom_coords.x(),
-            *valcom_coords.y(),
-            self.token_commit,
-            self.merkle_root.inner(),
-            self.user_data_enc,
-            self.spend_hook,
-            *sigpub_coords.x(),
-            *sigpub_coords.y(),
-        ]
-    }
-}
-
-pub struct TransferCallClearInput {
-    pub value: u64,
-    pub token_id: TokenId,
-    pub signature_secret: SecretKey,
-}
-
-pub struct TransferCallInput {
-    pub leaf_position: bridgetree::Position,
-    pub merkle_path: Vec<MerkleNode>,
-    pub secret: SecretKey,
-    pub note: MoneyNote,
-    // In the DAO all inputs must have the same user_data_enc and use the same blind
-    // So support allowing the user to set their own blind.
-    pub user_data_blind: pallas::Base,
-}
-
-pub struct TransferCallOutput {
-    pub value: u64,
-    pub token_id: TokenId,
-    pub public_key: PublicKey,
-    pub spend_hook: pallas::Base,
-    pub user_data: pallas::Base,
-}
-
-/// Struct holding necessary information to build a `Money::TransferV1` contract call.
-pub struct TransferCallBuilder {
-    /// Clear inputs
-    pub clear_inputs: Vec<TransferCallClearInput>,
-    /// Anonymous inputs
-    pub inputs: Vec<TransferCallInput>,
-    /// Anonymous outputs
-    pub outputs: Vec<TransferCallOutput>,
-    /// `Mint_V1` zkas circuit ZkBinary
-    pub mint_zkbin: ZkBinary,
-    /// Proving key for the `Mint_V1` zk circuit
-    pub mint_pk: ProvingKey,
-    /// `Burn_V1` zkas circuit ZkBinary
-    pub burn_zkbin: ZkBinary,
-    /// Proving key for the `Burn_V1` zk circuit
-    pub burn_pk: ProvingKey,
-}
-
-impl TransferCallBuilder {
-    fn compute_remainder_blind(
-        clear_inputs: &[ClearInput],
-        input_blinds: &[pallas::Scalar],
-        output_blinds: &[pallas::Scalar],
-    ) -> pallas::Scalar {
-        let mut total = pallas::Scalar::zero();
-
-        for input in clear_inputs {
-            total += input.value_blind;
-        }
-
-        for input_blind in input_blinds {
-            total += input_blind;
-        }
-
-        for output_blind in output_blinds {
-            total -= output_blind;
-        }
-
-        total
-    }
-
-    pub fn build(self) -> Result<(MoneyTransferParamsV1, TransferCallSecrets)> {
-        debug!("Building Money::TransferV1 contract call");
-        assert!(self.clear_inputs.len() + self.inputs.len() > 0);
-
-        let mut params =
-            MoneyTransferParamsV1 { clear_inputs: vec![], inputs: vec![], outputs: vec![] };
-        let mut signature_secrets = vec![];
-        let mut proofs = vec![];
-
-        let token_blind = pallas::Base::random(&mut OsRng);
-        debug!("Building clear inputs");
-        for input in self.clear_inputs {
-            let signature_public = PublicKey::from_secret(input.signature_secret);
-            let value_blind = pallas::Scalar::random(&mut OsRng);
-
-            params.clear_inputs.push(ClearInput {
-                value: input.value,
-                token_id: input.token_id,
-                value_blind,
-                token_blind,
-                signature_public,
-            });
-        }
-
-        let mut input_blinds = vec![];
-        let mut output_blinds = vec![];
-
-        debug!("Building anonymous inputs");
-        for (i, input) in self.inputs.iter().enumerate() {
-            let value_blind = pallas::Scalar::random(&mut OsRng);
-            input_blinds.push(value_blind);
-
-            let signature_secret = SecretKey::random(&mut OsRng);
-            signature_secrets.push(signature_secret);
-
-            info!("Creating transfer burn proof for input {}", i);
-            let (proof, public_inputs) = create_transfer_burn_proof(
-                &self.burn_zkbin,
-                &self.burn_pk,
-                input,
-                value_blind,
-                token_blind,
-                signature_secret,
-            )?;
-
-            params.inputs.push(Input {
-                value_commit: public_inputs.value_commit,
-                token_commit: public_inputs.token_commit,
-                nullifier: public_inputs.nullifier,
-                merkle_root: public_inputs.merkle_root,
-                spend_hook: public_inputs.spend_hook,
-                user_data_enc: public_inputs.user_data_enc,
-                signature_public: public_inputs.signature_public,
-            });
-
-            proofs.push(proof);
-        }
-
-        // This value_blind calc assumes there will always be at least a single output
-        assert!(!self.outputs.is_empty());
-
-        let mut output_notes = vec![];
-
-        for (i, output) in self.outputs.iter().enumerate() {
-            let value_blind = if i == self.outputs.len() - 1 {
-                Self::compute_remainder_blind(&params.clear_inputs, &input_blinds, &output_blinds)
-            } else {
-                pallas::Scalar::random(&mut OsRng)
-            };
-
-            output_blinds.push(value_blind);
-
-            let serial = pallas::Base::random(&mut OsRng);
-
-            info!("Creating transfer mint proof for output {}", i);
-            let (proof, public_inputs) = create_transfer_mint_proof(
-                &self.mint_zkbin,
-                &self.mint_pk,
-                output,
-                value_blind,
-                token_blind,
-                serial,
-                output.spend_hook,
-                output.user_data,
-            )?;
-
-            proofs.push(proof);
-
-            // Encrypted note
-            let note = MoneyNote {
-                serial,
-                value: output.value,
-                token_id: output.token_id,
-                spend_hook: output.spend_hook,
-                user_data: output.user_data,
-                value_blind,
-                token_blind,
-                memo: vec![],
-            };
-
-            let encrypted_note = AeadEncryptedNote::encrypt(&note, &output.public_key, &mut OsRng)?;
-            output_notes.push(note);
-
-            params.outputs.push(Output {
-                value_commit: public_inputs.value_commit,
-                token_commit: public_inputs.token_commit,
-                coin: public_inputs.coin,
-                note: encrypted_note,
-            });
-        }
-
-        // Now we should have all the params, zk proofs, and signature secrets.
-        // We return it all and let the caller deal with it.
-        let secrets = TransferCallSecrets {
-            proofs,
-            signature_secrets,
-            output_notes,
-            input_value_blinds: input_blinds,
-            output_value_blinds: output_blinds,
-        };
-        Ok((params, secrets))
-    }
-}
-
-/// Select coins from `coins` of at least `min_value` in total.
-/// Different strategies can be used. This function uses the dumb strategy
-/// of selecting coins until we reach `min_value`.
-pub fn select_coins(coins: Vec<OwnCoin>, min_value: u64) -> Result<(Vec<OwnCoin>, u64)> {
-    let mut total_value = 0;
-    let mut selected = vec![];
-
-    for coin in coins {
-        if total_value >= min_value {
-            break
-        }
-
-        total_value += coin.note.value;
-        selected.push(coin);
-    }
-
-    if total_value < min_value {
-        error!("Not enough value to build tx inputs");
-        return Err(ClientFailed::NotEnoughValue(total_value).into())
-    }
-
-    let change_value = total_value - min_value;
-
-    Ok((selected, change_value))
-}
-
-/// Make a simple anonymous transfer call.
-///
-/// * `keypair`: Caller's keypair
-/// * `recipient`: Recipient's public key
-/// * `value`: Amount that we want to send to the recipient
-/// * `token_id`: Token ID that we want to send to the recipient
-/// * `coins`: Set of `OwnCoin` we're given to use in this builder
-/// * `tree`: Merkle tree of coins used to create inclusion proofs
-/// * `mint_zkbin`: `Mint_V1` zkas circuit ZkBinary
-/// * `mint_pk`: Proving key for the `Mint_V1` zk circuit
-/// * `burn_zkbin`: `Burn_V1` zkas circuit ZkBinary
-/// * `burn_pk`: Proving key for the `Burn_V1` zk circuit
-///
-/// Returns a tuple of:
-///
-/// * The actual call data
-/// * Secret values such as blinds
-/// * A list of the spent coins
-pub fn make_transfer_call(
-    keypair: Keypair,
-    recipient: PublicKey,
-    value: u64,
-    token_id: TokenId,
-    coins: Vec<OwnCoin>,
-    tree: MerkleTree,
-    mint_zkbin: ZkBinary,
-    mint_pk: ProvingKey,
-    burn_zkbin: ZkBinary,
-    burn_pk: ProvingKey,
-) -> Result<(MoneyTransferParamsV1, TransferCallSecrets, Vec<OwnCoin>)> {
-    debug!("Building Money::TransferV1 contract call");
-    assert_ne!(value, 0);
-    assert_ne!(token_id.inner(), pallas::Base::ZERO);
-    assert!(!coins.is_empty());
-
-    // Ensure the coins given to us are all of the same token ID.
-    // The money contract base transfer doesn't allow conversions.
-    for coin in &coins {
-        assert_eq!(token_id, coin.note.token_id);
-    }
-
-    let mut inputs = vec![];
-    let mut outputs = vec![];
-
-    let (spent_coins, change_value) = select_coins(coins, value)?;
-
-    for coin in spent_coins.iter() {
-        let leaf_position = coin.leaf_position;
-        let merkle_path = tree.witness(leaf_position, 0).unwrap();
-
-        let input = TransferCallInput {
-            leaf_position,
-            merkle_path,
-            secret: coin.secret,
-            note: coin.note.clone(),
-            user_data_blind: pallas::Base::random(&mut OsRng),
-        };
-
-        inputs.push(input);
-    }
-    debug!("Selected inputs");
-
-    outputs.push(TransferCallOutput {
-        value,
-        token_id,
-        public_key: recipient,
-        spend_hook: pallas::Base::ZERO,
-        user_data: pallas::Base::ZERO,
-    });
-
-    if change_value > 0 {
-        outputs.push(TransferCallOutput {
-            value: change_value,
-            token_id,
-            public_key: keypair.public,
-            spend_hook: pallas::Base::ZERO,
-            user_data: pallas::Base::ZERO,
-        });
-    }
-
-    assert!(!inputs.is_empty());
-
-    let xfer_builder = TransferCallBuilder {
-        clear_inputs: vec![],
-        inputs,
-        outputs,
-        mint_zkbin,
-        mint_pk,
-        burn_zkbin,
-        burn_pk,
-    };
-
-    let (params, secrets) = xfer_builder.build()?;
-
-    Ok((params, secrets, spent_coins))
-}
-
-pub fn create_transfer_burn_proof(
-    zkbin: &ZkBinary,
-    pk: &ProvingKey,
-    input: &TransferCallInput,
-    value_blind: pallas::Scalar,
-    token_blind: pallas::Base,
-    signature_secret: SecretKey,
-) -> Result<(Proof, TransferBurnRevealed)> {
-    let nullifier = Nullifier::from(poseidon_hash([input.secret.inner(), input.note.serial]));
-    let public_key = PublicKey::from_secret(input.secret);
-    let (pub_x, pub_y) = public_key.xy();
-
-    let signature_public = PublicKey::from_secret(signature_secret);
-
-    let coin = poseidon_hash([
-        pub_x,
-        pub_y,
-        pallas::Base::from(input.note.value),
-        input.note.token_id.inner(),
-        input.note.serial,
-        input.note.spend_hook,
-        input.note.user_data,
-    ]);
-
-    let merkle_root = {
-        let position: u64 = input.leaf_position.into();
-        let mut current = MerkleNode::from(coin);
-        for (level, sibling) in input.merkle_path.iter().enumerate() {
-            let level = level as u8;
-            current = if position & (1 << level) == 0 {
-                MerkleNode::combine(level.into(), &current, sibling)
-            } else {
-                MerkleNode::combine(level.into(), sibling, &current)
-            };
-        }
-        current
-    };
-
-    let user_data_enc = poseidon_hash([input.note.user_data, input.user_data_blind]);
-    let value_commit = pedersen_commitment_u64(input.note.value, value_blind);
-    let token_commit = poseidon_hash([input.note.token_id.inner(), token_blind]);
-
-    let public_inputs = TransferBurnRevealed {
-        value_commit,
-        token_commit,
-        nullifier,
-        merkle_root,
-        spend_hook: input.note.spend_hook,
-        user_data_enc,
-        signature_public,
-    };
-
-    let prover_witnesses = vec![
-        Witness::Base(Value::known(pallas::Base::from(input.note.value))),
-        Witness::Base(Value::known(input.note.token_id.inner())),
-        Witness::Scalar(Value::known(value_blind)),
-        Witness::Base(Value::known(token_blind)),
-        Witness::Base(Value::known(input.note.serial)),
-        Witness::Base(Value::known(input.note.spend_hook)),
-        Witness::Base(Value::known(input.note.user_data)),
-        Witness::Base(Value::known(input.user_data_blind)),
-        Witness::Base(Value::known(input.secret.inner())),
-        Witness::Uint32(Value::known(u64::from(input.leaf_position).try_into().unwrap())),
-        Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
-        Witness::Base(Value::known(signature_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))
-}
-
-#[allow(clippy::too_many_arguments)]
-pub fn create_transfer_mint_proof(
-    zkbin: &ZkBinary,
-    pk: &ProvingKey,
-    output: &TransferCallOutput,
-    value_blind: pallas::Scalar,
-    token_blind: pallas::Base,
-    serial: pallas::Base,
-    spend_hook: pallas::Base,
-    user_data: pallas::Base,
-) -> Result<(Proof, TransferMintRevealed)> {
-    let value_commit = pedersen_commitment_u64(output.value, value_blind);
-    let token_commit = poseidon_hash([output.token_id.inner(), token_blind]);
-    let (pub_x, pub_y) = output.public_key.xy();
-
-    let coin = Coin::from(poseidon_hash([
-        pub_x,
-        pub_y,
-        pallas::Base::from(output.value),
-        output.token_id.inner(),
-        serial,
-        spend_hook,
-        user_data,
-    ]));
-    debug!("Created coin {:?}", coin);
-    debug!("  pub_x: {:?}", pub_x);
-    debug!("  pub_y: {:?}", pub_y);
-    debug!("  value: {:?}", pallas::Base::from(output.value));
-    debug!("  token_id: {:?}", output.token_id.inner());
-    debug!("  serial: {:?}", serial);
-    debug!("  spend_hook: {:?}", spend_hook);
-    debug!("  user_data: {:?}", user_data);
-
-    let public_inputs = TransferMintRevealed { coin, value_commit, token_commit };
-
-    let prover_witnesses = vec![
-        Witness::Base(Value::known(pub_x)),
-        Witness::Base(Value::known(pub_y)),
-        Witness::Base(Value::known(pallas::Base::from(output.value))),
-        Witness::Base(Value::known(output.token_id.inner())),
-        Witness::Base(Value::known(serial)),
-        Witness::Base(Value::known(spend_hook)),
-        Witness::Base(Value::known(user_data)),
-        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))
-}

+ 258 - 0
src/contract/money/src/client/transfer_v1/builder.rs

@@ -0,0 +1,258 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+use darkfi::{
+    zk::{Proof, ProvingKey},
+    zkas::ZkBinary,
+    Result,
+};
+use darkfi_sdk::{
+    bridgetree,
+    crypto::{
+        note::AeadEncryptedNote, pasta_prelude::*, MerkleNode, Nullifier, PublicKey, SecretKey,
+        TokenId,
+    },
+    pasta::pallas,
+};
+use log::{debug, info};
+use rand::rngs::OsRng;
+
+use super::proof::{create_transfer_burn_proof, create_transfer_mint_proof};
+use crate::{
+    client::{MoneyNote, OwnCoin},
+    model::{ClearInput, Input, MoneyTransferParamsV1, Output},
+};
+
+/// Struct holding necessary information to build a `Money::TransferV1` contract call.
+pub struct TransferCallBuilder {
+    /// Clear inputs
+    pub clear_inputs: Vec<TransferCallClearInput>,
+    /// Anonymous inputs
+    pub inputs: Vec<TransferCallInput>,
+    /// Anonymous outputs
+    pub outputs: Vec<TransferCallOutput>,
+    /// `Mint_V1` zkas circuit ZkBinary
+    pub mint_zkbin: ZkBinary,
+    /// Proving key for the `Mint_V1` zk circuit
+    pub mint_pk: ProvingKey,
+    /// `Burn_V1` zkas circuit ZkBinary
+    pub burn_zkbin: ZkBinary,
+    /// Proving key for the `Burn_V1` zk circuit
+    pub burn_pk: ProvingKey,
+}
+
+pub struct TransferCallClearInput {
+    pub value: u64,
+    pub token_id: TokenId,
+    pub signature_secret: SecretKey,
+}
+
+pub struct TransferCallInput {
+    pub leaf_position: bridgetree::Position,
+    pub merkle_path: Vec<MerkleNode>,
+    pub secret: SecretKey,
+    pub note: MoneyNote,
+    // In the DAO all inputs must have the same user_data_enc and use the same blind
+    // So support allowing the user to set their own blind.
+    pub user_data_blind: pallas::Base,
+}
+
+pub struct TransferCallOutput {
+    pub value: u64,
+    pub token_id: TokenId,
+    pub public_key: PublicKey,
+    pub spend_hook: pallas::Base,
+    pub user_data: pallas::Base,
+}
+
+impl TransferCallBuilder {
+    fn compute_remainder_blind(
+        clear_inputs: &[ClearInput],
+        input_blinds: &[pallas::Scalar],
+        output_blinds: &[pallas::Scalar],
+    ) -> pallas::Scalar {
+        let mut total = pallas::Scalar::zero();
+
+        for input in clear_inputs {
+            total += input.value_blind;
+        }
+
+        for input_blind in input_blinds {
+            total += input_blind;
+        }
+
+        for output_blind in output_blinds {
+            total -= output_blind;
+        }
+
+        total
+    }
+
+    pub fn build(self) -> Result<(MoneyTransferParamsV1, TransferCallSecrets)> {
+        debug!("Building Money::TransferV1 contract call");
+        assert!(self.clear_inputs.len() + self.inputs.len() > 0);
+
+        let mut params =
+            MoneyTransferParamsV1 { clear_inputs: vec![], inputs: vec![], outputs: vec![] };
+        let mut signature_secrets = vec![];
+        let mut proofs = vec![];
+
+        let token_blind = pallas::Base::random(&mut OsRng);
+        debug!("Building clear inputs");
+        for input in self.clear_inputs {
+            let signature_public = PublicKey::from_secret(input.signature_secret);
+            let value_blind = pallas::Scalar::random(&mut OsRng);
+
+            params.clear_inputs.push(ClearInput {
+                value: input.value,
+                token_id: input.token_id,
+                value_blind,
+                token_blind,
+                signature_public,
+            });
+        }
+
+        let mut input_blinds = vec![];
+        let mut output_blinds = vec![];
+
+        debug!("Building anonymous inputs");
+        for (i, input) in self.inputs.iter().enumerate() {
+            let value_blind = pallas::Scalar::random(&mut OsRng);
+            input_blinds.push(value_blind);
+
+            let signature_secret = SecretKey::random(&mut OsRng);
+            signature_secrets.push(signature_secret);
+
+            info!("Creating transfer burn proof for input {}", i);
+            let (proof, public_inputs) = create_transfer_burn_proof(
+                &self.burn_zkbin,
+                &self.burn_pk,
+                input,
+                value_blind,
+                token_blind,
+                signature_secret,
+            )?;
+
+            params.inputs.push(Input {
+                value_commit: public_inputs.value_commit,
+                token_commit: public_inputs.token_commit,
+                nullifier: public_inputs.nullifier,
+                merkle_root: public_inputs.merkle_root,
+                spend_hook: public_inputs.spend_hook,
+                user_data_enc: public_inputs.user_data_enc,
+                signature_public: public_inputs.signature_public,
+            });
+
+            proofs.push(proof);
+        }
+
+        // This value_blind calc assumes there will always be at least a single output
+        assert!(!self.outputs.is_empty());
+
+        let mut output_notes = vec![];
+
+        for (i, output) in self.outputs.iter().enumerate() {
+            let value_blind = if i == self.outputs.len() - 1 {
+                Self::compute_remainder_blind(&params.clear_inputs, &input_blinds, &output_blinds)
+            } else {
+                pallas::Scalar::random(&mut OsRng)
+            };
+
+            output_blinds.push(value_blind);
+
+            let serial = pallas::Base::random(&mut OsRng);
+
+            info!("Creating transfer mint proof for output {}", i);
+            let (proof, public_inputs) = create_transfer_mint_proof(
+                &self.mint_zkbin,
+                &self.mint_pk,
+                output,
+                value_blind,
+                token_blind,
+                serial,
+                output.spend_hook,
+                output.user_data,
+            )?;
+
+            proofs.push(proof);
+
+            // Encrypted note
+            let note = MoneyNote {
+                serial,
+                value: output.value,
+                token_id: output.token_id,
+                spend_hook: output.spend_hook,
+                user_data: output.user_data,
+                value_blind,
+                token_blind,
+                memo: vec![],
+            };
+
+            let encrypted_note = AeadEncryptedNote::encrypt(&note, &output.public_key, &mut OsRng)?;
+            output_notes.push(note);
+
+            params.outputs.push(Output {
+                value_commit: public_inputs.value_commit,
+                token_commit: public_inputs.token_commit,
+                coin: public_inputs.coin,
+                note: encrypted_note,
+            });
+        }
+
+        // Now we should have all the params, zk proofs, and signature secrets.
+        // We return it all and let the caller deal with it.
+        let secrets = TransferCallSecrets {
+            proofs,
+            signature_secrets,
+            output_notes,
+            input_value_blinds: input_blinds,
+            output_value_blinds: output_blinds,
+        };
+        Ok((params, secrets))
+    }
+}
+
+pub struct TransferCallSecrets {
+    /// The ZK proofs created in this builder
+    pub proofs: Vec<Proof>,
+    /// The ephemeral secret keys created for signing
+    pub signature_secrets: Vec<SecretKey>,
+
+    /// Decrypted notes associated with each output
+    pub output_notes: Vec<MoneyNote>,
+
+    /// The value blinds created for the inputs
+    pub input_value_blinds: Vec<pallas::Scalar>,
+    /// The value blinds created for the outputs
+    pub output_value_blinds: Vec<pallas::Scalar>,
+}
+
+impl TransferCallSecrets {
+    pub fn minted_coins(&self, params: &MoneyTransferParamsV1) -> Vec<OwnCoin> {
+        let mut minted_coins = vec![];
+        for (output, output_note) in params.outputs.iter().zip(self.output_notes.iter()) {
+            minted_coins.push(OwnCoin {
+                coin: output.coin,
+                note: output_note.clone(),
+                secret: SecretKey::from(pallas::Base::ZERO),
+                nullifier: Nullifier::from(pallas::Base::ZERO),
+                leaf_position: 0.into(),
+            });
+        }
+        minted_coins
+    }
+}

+ 157 - 0
src/contract/money/src/client/transfer_v1/mod.rs

@@ -0,0 +1,157 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+use darkfi::{zk::ProvingKey, zkas::ZkBinary, ClientFailed, Result};
+use darkfi_sdk::{
+    crypto::{pasta_prelude::*, Keypair, MerkleTree, PublicKey, TokenId},
+    pasta::pallas,
+};
+use log::{debug, error};
+use rand::rngs::OsRng;
+
+use crate::{client::OwnCoin, model::MoneyTransferParamsV1};
+
+mod builder;
+pub use builder::{
+    TransferCallBuilder, TransferCallClearInput, TransferCallInput, TransferCallOutput,
+    TransferCallSecrets,
+};
+
+pub(crate) mod proof;
+
+/// Select coins from `coins` of at least `min_value` in total.
+/// Different strategies can be used. This function uses the dumb strategy
+/// of selecting coins until we reach `min_value`.
+pub fn select_coins(coins: Vec<OwnCoin>, min_value: u64) -> Result<(Vec<OwnCoin>, u64)> {
+    let mut total_value = 0;
+    let mut selected = vec![];
+
+    for coin in coins {
+        if total_value >= min_value {
+            break
+        }
+
+        total_value += coin.note.value;
+        selected.push(coin);
+    }
+
+    if total_value < min_value {
+        error!("Not enough value to build tx inputs");
+        return Err(ClientFailed::NotEnoughValue(total_value).into())
+    }
+
+    let change_value = total_value - min_value;
+
+    Ok((selected, change_value))
+}
+
+/// Make a simple anonymous transfer call.
+///
+/// * `keypair`: Caller's keypair
+/// * `recipient`: Recipient's public key
+/// * `value`: Amount that we want to send to the recipient
+/// * `token_id`: Token ID that we want to send to the recipient
+/// * `coins`: Set of `OwnCoin` we're given to use in this builder
+/// * `tree`: Merkle tree of coins used to create inclusion proofs
+/// * `mint_zkbin`: `Mint_V1` zkas circuit ZkBinary
+/// * `mint_pk`: Proving key for the `Mint_V1` zk circuit
+/// * `burn_zkbin`: `Burn_V1` zkas circuit ZkBinary
+/// * `burn_pk`: Proving key for the `Burn_V1` zk circuit
+///
+/// Returns a tuple of:
+///
+/// * The actual call data
+/// * Secret values such as blinds
+/// * A list of the spent coins
+pub fn make_transfer_call(
+    keypair: Keypair,
+    recipient: PublicKey,
+    value: u64,
+    token_id: TokenId,
+    coins: Vec<OwnCoin>,
+    tree: MerkleTree,
+    mint_zkbin: ZkBinary,
+    mint_pk: ProvingKey,
+    burn_zkbin: ZkBinary,
+    burn_pk: ProvingKey,
+) -> Result<(MoneyTransferParamsV1, TransferCallSecrets, Vec<OwnCoin>)> {
+    debug!("Building Money::TransferV1 contract call");
+    assert_ne!(value, 0);
+    assert_ne!(token_id.inner(), pallas::Base::ZERO);
+    assert!(!coins.is_empty());
+
+    // Ensure the coins given to us are all of the same token ID.
+    // The money contract base transfer doesn't allow conversions.
+    for coin in &coins {
+        assert_eq!(token_id, coin.note.token_id);
+    }
+
+    let mut inputs = vec![];
+    let mut outputs = vec![];
+
+    let (spent_coins, change_value) = select_coins(coins, value)?;
+
+    for coin in spent_coins.iter() {
+        let leaf_position = coin.leaf_position;
+        let merkle_path = tree.witness(leaf_position, 0).unwrap();
+
+        let input = TransferCallInput {
+            leaf_position,
+            merkle_path,
+            secret: coin.secret,
+            note: coin.note.clone(),
+            user_data_blind: pallas::Base::random(&mut OsRng),
+        };
+
+        inputs.push(input);
+    }
+    debug!("Selected inputs");
+
+    outputs.push(TransferCallOutput {
+        value,
+        token_id,
+        public_key: recipient,
+        spend_hook: pallas::Base::ZERO,
+        user_data: pallas::Base::ZERO,
+    });
+
+    if change_value > 0 {
+        outputs.push(TransferCallOutput {
+            value: change_value,
+            token_id,
+            public_key: keypair.public,
+            spend_hook: pallas::Base::ZERO,
+            user_data: pallas::Base::ZERO,
+        });
+    }
+
+    assert!(!inputs.is_empty());
+
+    let xfer_builder = TransferCallBuilder {
+        clear_inputs: vec![],
+        inputs,
+        outputs,
+        mint_zkbin,
+        mint_pk,
+        burn_zkbin,
+        burn_pk,
+    };
+
+    let (params, secrets) = xfer_builder.build()?;
+
+    Ok((params, secrets, spent_coins))
+}

+ 209 - 0
src/contract/money/src/client/transfer_v1/proof.rs

@@ -0,0 +1,209 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi::{
+    zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit},
+    zkas::ZkBinary,
+    Result,
+};
+use darkfi_sdk::{
+    bridgetree::Hashable,
+    crypto::{
+        pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, MerkleNode, Nullifier, PublicKey,
+        SecretKey,
+    },
+    pasta::pallas,
+};
+use log::debug;
+use rand::rngs::OsRng;
+
+use super::{TransferCallInput, TransferCallOutput};
+use crate::model::Coin;
+
+pub struct TransferMintRevealed {
+    pub coin: Coin,
+    pub value_commit: pallas::Point,
+    pub token_commit: pallas::Base,
+}
+
+impl TransferMintRevealed {
+    pub fn to_vec(&self) -> Vec<pallas::Base> {
+        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![self.coin.inner(), *valcom_coords.x(), *valcom_coords.y(), self.token_commit]
+    }
+}
+
+pub struct TransferBurnRevealed {
+    pub value_commit: pallas::Point,
+    pub token_commit: pallas::Base,
+    pub nullifier: Nullifier,
+    pub merkle_root: MerkleNode,
+    pub spend_hook: pallas::Base,
+    pub user_data_enc: pallas::Base,
+    pub signature_public: PublicKey,
+}
+
+impl TransferBurnRevealed {
+    pub fn to_vec(&self) -> Vec<pallas::Base> {
+        let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
+        let sigpub_coords = self.signature_public.inner().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![
+            self.nullifier.inner(),
+            *valcom_coords.x(),
+            *valcom_coords.y(),
+            self.token_commit,
+            self.merkle_root.inner(),
+            self.user_data_enc,
+            self.spend_hook,
+            *sigpub_coords.x(),
+            *sigpub_coords.y(),
+        ]
+    }
+}
+
+pub fn create_transfer_burn_proof(
+    zkbin: &ZkBinary,
+    pk: &ProvingKey,
+    input: &TransferCallInput,
+    value_blind: pallas::Scalar,
+    token_blind: pallas::Base,
+    signature_secret: SecretKey,
+) -> Result<(Proof, TransferBurnRevealed)> {
+    let nullifier = Nullifier::from(poseidon_hash([input.secret.inner(), input.note.serial]));
+    let public_key = PublicKey::from_secret(input.secret);
+    let (pub_x, pub_y) = public_key.xy();
+
+    let signature_public = PublicKey::from_secret(signature_secret);
+
+    let coin = poseidon_hash([
+        pub_x,
+        pub_y,
+        pallas::Base::from(input.note.value),
+        input.note.token_id.inner(),
+        input.note.serial,
+        input.note.spend_hook,
+        input.note.user_data,
+    ]);
+
+    let merkle_root = {
+        let position: u64 = input.leaf_position.into();
+        let mut current = MerkleNode::from(coin);
+        for (level, sibling) in input.merkle_path.iter().enumerate() {
+            let level = level as u8;
+            current = if position & (1 << level) == 0 {
+                MerkleNode::combine(level.into(), &current, sibling)
+            } else {
+                MerkleNode::combine(level.into(), sibling, &current)
+            };
+        }
+        current
+    };
+
+    let user_data_enc = poseidon_hash([input.note.user_data, input.user_data_blind]);
+    let value_commit = pedersen_commitment_u64(input.note.value, value_blind);
+    let token_commit = poseidon_hash([input.note.token_id.inner(), token_blind]);
+
+    let public_inputs = TransferBurnRevealed {
+        value_commit,
+        token_commit,
+        nullifier,
+        merkle_root,
+        spend_hook: input.note.spend_hook,
+        user_data_enc,
+        signature_public,
+    };
+
+    let prover_witnesses = vec![
+        Witness::Base(Value::known(pallas::Base::from(input.note.value))),
+        Witness::Base(Value::known(input.note.token_id.inner())),
+        Witness::Scalar(Value::known(value_blind)),
+        Witness::Base(Value::known(token_blind)),
+        Witness::Base(Value::known(input.note.serial)),
+        Witness::Base(Value::known(input.note.spend_hook)),
+        Witness::Base(Value::known(input.note.user_data)),
+        Witness::Base(Value::known(input.user_data_blind)),
+        Witness::Base(Value::known(input.secret.inner())),
+        Witness::Uint32(Value::known(u64::from(input.leaf_position).try_into().unwrap())),
+        Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
+        Witness::Base(Value::known(signature_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))
+}
+
+#[allow(clippy::too_many_arguments)]
+pub fn create_transfer_mint_proof(
+    zkbin: &ZkBinary,
+    pk: &ProvingKey,
+    output: &TransferCallOutput,
+    value_blind: pallas::Scalar,
+    token_blind: pallas::Base,
+    serial: pallas::Base,
+    spend_hook: pallas::Base,
+    user_data: pallas::Base,
+) -> Result<(Proof, TransferMintRevealed)> {
+    let value_commit = pedersen_commitment_u64(output.value, value_blind);
+    let token_commit = poseidon_hash([output.token_id.inner(), token_blind]);
+    let (pub_x, pub_y) = output.public_key.xy();
+
+    let coin = Coin::from(poseidon_hash([
+        pub_x,
+        pub_y,
+        pallas::Base::from(output.value),
+        output.token_id.inner(),
+        serial,
+        spend_hook,
+        user_data,
+    ]));
+    debug!("Created coin {:?}", coin);
+    debug!("  pub_x: {:?}", pub_x);
+    debug!("  pub_y: {:?}", pub_y);
+    debug!("  value: {:?}", pallas::Base::from(output.value));
+    debug!("  token_id: {:?}", output.token_id.inner());
+    debug!("  serial: {:?}", serial);
+    debug!("  spend_hook: {:?}", spend_hook);
+    debug!("  user_data: {:?}", user_data);
+
+    let public_inputs = TransferMintRevealed { coin, value_commit, token_commit };
+
+    let prover_witnesses = vec![
+        Witness::Base(Value::known(pub_x)),
+        Witness::Base(Value::known(pub_y)),
+        Witness::Base(Value::known(pallas::Base::from(output.value))),
+        Witness::Base(Value::known(output.token_id.inner())),
+        Witness::Base(Value::known(serial)),
+        Witness::Base(Value::known(spend_hook)),
+        Witness::Base(Value::known(user_data)),
+        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))
+}