Przeglądaj źródła

contract/consensus: stake client implemented

aggstam 3 lat temu
rodzic
commit
9202c8850f

+ 195 - 0
src/contract/consensus/src/client/stake_v1.rs

@@ -17,3 +17,198 @@
  */
 
 //! 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,
+    Result,
+};
+use darkfi_money_contract::{
+    client::{MoneyNote, OwnCoin},
+    model::Output,
+};
+use darkfi_sdk::{
+    crypto::{
+        note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_base,
+        pedersen_commitment_u64, poseidon_hash, Coin, MerkleNode, Nullifier, PublicKey, TokenId,
+        DARK_TOKEN_ID,
+    },
+    pasta::pallas,
+};
+use log::{debug, info};
+use rand::rngs::OsRng;
+
+use crate::model::{ConsensusStakeParamsV1, StakeInput};
+
+pub struct ConsensusStakeCallDebris {
+    pub params: ConsensusStakeParamsV1,
+    pub proofs: Vec<Proof>,
+}
+
+pub struct ConsensusMintRevealed {
+    pub coin: Coin,
+    pub value_commit: pallas::Point,
+    pub token_commit: pallas::Point,
+}
+
+impl ConsensusMintRevealed {
+    pub fn to_vec(&self) -> Vec<pallas::Base> {
+        let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
+        let tokcom_coords = self.token_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(),
+            *tokcom_coords.x(),
+            *tokcom_coords.y(),
+        ]
+    }
+}
+
+pub struct TransactionBuilderOutputInfo {
+    pub value: u64,
+    pub token_id: TokenId,
+    pub public_key: PublicKey,
+}
+
+/// Struct holding necessary information to build a `Consensus::StakeV1` contract call.
+pub struct ConsensusStakeCallBuilder {
+    /// `OwnCoin` we're given to use in this builder
+    pub coin: OwnCoin,
+    /// Recipient's public key
+    pub recipient: PublicKey,
+    /// Blinding factor for value commitment
+    pub value_blind: pallas::Scalar,
+    /// Blinding factor for `token_id`
+    pub token_blind: pallas::Scalar,
+    /// Revealed nullifier
+    pub nullifier: Nullifier,
+    /// Revealed Merkle root
+    pub merkle_root: MerkleNode,
+    /// `Mint_V1` zkas circuit ZkBinary
+    pub mint_zkbin: ZkBinary,
+    /// Proving key for the `Mint_V1` zk circuit
+    pub mint_pk: ProvingKey,
+}
+
+impl ConsensusStakeCallBuilder {
+    pub fn build(&self) -> Result<ConsensusStakeCallDebris> {
+        debug!("Building Consensus::StakeV1 contract call");
+        assert!(self.coin.note.value != 0);
+        assert!(self.coin.note.token_id == *DARK_TOKEN_ID);
+
+        debug!("Building anonymous output");
+        let output = TransactionBuilderOutputInfo {
+            value: self.coin.note.value,
+            token_id: self.coin.note.token_id,
+            public_key: self.recipient,
+        };
+        debug!("Finished building output");
+
+        let serial = pallas::Base::random(&mut OsRng);
+        let spend_hook = DARK_TOKEN_ID.inner();
+        let user_data = pallas::Base::random(&mut OsRng);
+        let coin_blind = pallas::Base::random(&mut OsRng);
+
+        info!("Creating transfer mint proof for output");
+        let (proof, public_inputs) = create_stake_mint_proof(
+            &self.mint_zkbin,
+            &self.mint_pk,
+            &output,
+            self.value_blind,
+            self.token_blind,
+            serial,
+            spend_hook,
+            user_data,
+            coin_blind,
+        )?;
+
+        // Encrypted note
+        let note = MoneyNote {
+            serial,
+            value: output.value,
+            token_id: output.token_id,
+            spend_hook,
+            user_data,
+            coin_blind,
+            value_blind: self.value_blind,
+            token_blind: self.token_blind,
+            memo: vec![],
+        };
+
+        let encrypted_note = AeadEncryptedNote::encrypt(&note, &output.public_key, &mut OsRng)?;
+
+        let output = Output {
+            value_commit: public_inputs.value_commit,
+            token_commit: public_inputs.token_commit,
+            coin: public_inputs.coin,
+            note: encrypted_note,
+        };
+
+        let input = StakeInput {
+            token_blind: self.token_blind,
+            value_commit: public_inputs.value_commit,
+            nullifier: self.nullifier,
+            merkle_root: self.merkle_root,
+        };
+
+        // We now fill this with necessary stuff
+        let params = ConsensusStakeParamsV1 { input, output };
+        let proofs = vec![proof];
+
+        // Now we should have all the params and zk proof.
+        // We return it all and let the caller deal with it.
+        let debris = ConsensusStakeCallDebris { params, proofs };
+        Ok(debris)
+    }
+}
+
+pub fn create_stake_mint_proof(
+    zkbin: &ZkBinary,
+    pk: &ProvingKey,
+    output: &TransactionBuilderOutputInfo,
+    value_blind: pallas::Scalar,
+    token_blind: pallas::Scalar,
+    serial: pallas::Base,
+    spend_hook: pallas::Base,
+    user_data: pallas::Base,
+    coin_blind: pallas::Base,
+) -> Result<(Proof, ConsensusMintRevealed)> {
+    let value_commit = pedersen_commitment_u64(output.value, value_blind);
+    let token_commit = pedersen_commitment_base(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,
+        coin_blind,
+    ]));
+
+    let public_inputs = ConsensusMintRevealed { 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(coin_blind)),
+        Witness::Base(Value::known(spend_hook)),
+        Witness::Base(Value::known(user_data)),
+        Witness::Scalar(Value::known(value_blind)),
+        Witness::Scalar(Value::known(token_blind)),
+    ];
+
+    let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
+    let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
+
+    Ok((proof, public_inputs))
+}

+ 11 - 12
src/contract/consensus/src/entrypoint/stake_v1.rs

@@ -21,9 +21,8 @@ use darkfi_money_contract::{
 };
 use darkfi_sdk::{
     crypto::{
-        contract_id::{CONSENSUS_CONTRACT_ID, MONEY_CONTRACT_ID},
-        pasta_prelude::*,
-        pedersen_commitment_base, Coin, ContractId, MerkleNode, DARK_TOKEN_ID,
+        pasta_prelude::*, pedersen_commitment_base, Coin, ContractId, MerkleNode,
+        CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID, MONEY_CONTRACT_ID,
     },
     db::{db_contains_key, db_lookup, db_set},
     error::{ContractError, ContractResult},
@@ -94,46 +93,46 @@ pub(crate) fn consensus_stake_process_instruction_v1(
     // Perform the actual state transition
     // ===================================
 
-    msg!("[StakeV1] Validating anonymous output");
+    msg!("[ConsensusStakeV1] Validating anonymous output");
     let input = &params.input;
     let output = &params.output;
 
     // Only native token can be staked
     if output.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), input.token_blind) {
-        msg!("[StakeV1] Error: Input used non-native token");
+        msg!("[ConsensusStakeV1] Error: Input used non-native token");
         return Err(MoneyError::StakeInputNonNativeToken.into())
     }
 
     // Verify value commits match
     if output.value_commit != input.value_commit {
-        msg!("[StakeV1] Error: Value commitments do not match");
+        msg!("[ConsensusStakeV1] Error: Value commitments do not match");
         return Err(MoneyError::ValueMismatch.into())
     }
 
     // The Merkle root is used to know whether this is a coin that
     // existed in a previous state.
     if !db_contains_key(money_coin_roots_db, &serialize(&input.merkle_root))? {
-        msg!("[StakeV1] Error: Merkle root not found in previous state");
+        msg!("[ConsensusStakeV1] Error: Merkle root not found in previous state");
         return Err(MoneyError::TransferMerkleRootNotFound.into())
     }
 
     // The nullifiers should already exist. It is the double-mint protection.
     if !db_contains_key(money_nullifiers_db, &serialize(&input.nullifier))? {
-        msg!("[StakeV1] Error: Duplicate nullifier found");
+        msg!("[ConsensusStakeV1] Error: Duplicate nullifier found");
         return Err(MoneyError::DuplicateNullifier.into())
     }
 
     // Check caller matches stake spend hook and its correctness
     let caller = &calls[call_idx as usize];
     if caller.contract_id.inner() != CONSENSUS_CONTRACT_ID.inner() {
-        msg!("[StakeV1] Error: Invoking contract call does not match spend hook");
+        msg!("[ConsensusStakeV1] Error: Invoking contract call does not match spend hook");
         return Err(MoneyError::SpendHookMismatch.into())
     }
 
     // Newly created coin for this call is in the output. Here we gather it,
     // and we also check that it hasn't existed before.
     if db_contains_key(consenus_coins_db, &serialize(&output.coin))? {
-        msg!("[StakeV1] Error: Duplicate coin found in output");
+        msg!("[ConsensusStakeV1] Error: Duplicate coin found in output");
         return Err(MoneyError::DuplicateCoin.into())
     }
     let coin = Coin::from(output.coin);
@@ -157,10 +156,10 @@ pub(crate) fn consensus_stake_process_update_v1(
     let coins_db = db_lookup(cid, CONSENSUS_CONTRACT_COINS_TREE)?;
     let coin_roots_db = db_lookup(cid, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
 
-    msg!("[StakeV1] Adding new coin to the set");
+    msg!("[ConsensusStakeV1] Adding new coin to the set");
     db_set(coins_db, &serialize(&update.coin), &[])?;
 
-    msg!("[StakeV1] Adding new coin to the Merkle tree");
+    msg!("[ConsensusStakeV1] Adding new coin to the Merkle tree");
     let coins: Vec<_> = vec![MerkleNode::from(update.coin.inner())];
     merkle_add(info_db, coin_roots_db, &serialize(&CONSENSUS_CONTRACT_COIN_MERKLE_TREE), &coins)?;
 

+ 1 - 9
src/contract/consensus/tests/harness.rs

@@ -102,8 +102,6 @@ impl Wallet {
 pub struct ConsensusTestHarness {
     pub faucet: Wallet,
     pub alice: Wallet,
-    pub bob: Wallet,
-    pub charlie: Wallet,
     pub proving_keys: HashMap<&'static str, (ProvingKey, ZkBinary)>,
 }
 
@@ -116,12 +114,6 @@ impl ConsensusTestHarness {
         let alice_kp = Keypair::random(&mut OsRng);
         let alice = Wallet::new(alice_kp, &faucet_pubkeys).await?;
 
-        let bob_kp = Keypair::random(&mut OsRng);
-        let bob = Wallet::new(bob_kp, &faucet_pubkeys).await?;
-
-        let charlie_kp = Keypair::random(&mut OsRng);
-        let charlie = Wallet::new(charlie_kp, &faucet_pubkeys).await?;
-
         // Get the zkas circuits and build proving keys
         let mut proving_keys = HashMap::new();
         let alice_sled = alice.state.read().await.blockchain.sled_db.clone();
@@ -147,7 +139,7 @@ impl ConsensusTestHarness {
         mkpk!(MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1);
         mkpk!(MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1);
 
-        Ok(Self { faucet, alice, bob, charlie, proving_keys })
+        Ok(Self { faucet, alice, proving_keys })
     }
 
     pub fn airdrop_native(

+ 100 - 16
src/contract/consensus/tests/stake_unstake.rs

@@ -26,11 +26,24 @@
 //!
 //! TODO: Malicious cases
 
-use darkfi::Result;
-use darkfi_sdk::crypto::{merkle_prelude::*, poseidon_hash, Coin, MerkleNode, Nullifier};
+use darkfi::{tx::Transaction, Result};
+use darkfi_sdk::{
+    crypto::{
+        merkle_prelude::*, poseidon_hash, Coin, MerkleNode, Nullifier, CONSENSUS_CONTRACT_ID,
+        MONEY_CONTRACT_ID,
+    },
+    ContractCall,
+};
+use darkfi_serial::Encodable;
 use log::info;
+use rand::rngs::OsRng;
 
-use darkfi_money_contract::client::{MoneyNote, OwnCoin};
+use darkfi_money_contract::{
+    client::{stake_v1::MoneyStakeCallBuilder, MoneyNote, OwnCoin},
+    MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+};
+
+use darkfi_consensus_contract::{client::stake_v1::ConsensusStakeCallBuilder, ConsensusFunction};
 
 mod harness;
 use harness::{init_logger, ConsensusTestHarness};
@@ -43,35 +56,106 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
 
     // Initialize harness
     let mut th = ConsensusTestHarness::new().await?;
-    info!(target: "money", "[Faucet] ===================================================");
-    info!(target: "money", "[Faucet] Building Money::Transfer params for Alice's airdrop");
-    info!(target: "money", "[Faucet] ===================================================");
+    info!(target: "consensus", "[Faucet] ===================================================");
+    info!(target: "consensus", "[Faucet] Building Money::Transfer params for Alice's airdrop");
+    info!(target: "consensus", "[Faucet] ===================================================");
     let (airdrop_tx, airdrop_params) = th.airdrop_native(ALICE_AIRDROP, th.alice.keypair.public)?;
+    let (mint_pk, mint_zkbin) = th.proving_keys.get(&MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
+    let (burn_pk, burn_zkbin) = th.proving_keys.get(&MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
 
-    info!(target: "money", "[Faucet] ==========================");
-    info!(target: "money", "[Faucet] Executing Alice airdrop tx");
-    info!(target: "money", "[Faucet] ==========================");
+    info!(target: "consensus", "[Faucet] ==========================");
+    info!(target: "consensus", "[Faucet] Executing Alice airdrop tx");
+    info!(target: "consensus", "[Faucet] ==========================");
     th.faucet.state.read().await.verify_transactions(&[airdrop_tx.clone()], true).await?;
     th.faucet.merkle_tree.append(&MerkleNode::from(airdrop_params.outputs[0].coin.inner()));
-    info!(target: "money", "[Alice] ==========================");
-    info!(target: "money", "[Alice] Executing Alice airdrop tx");
-    info!(target: "money", "[Alice] ==========================");
+    info!(target: "consensus", "[Alice] ==========================");
+    info!(target: "consensus", "[Alice] Executing Alice airdrop tx");
+    info!(target: "consensus", "[Alice] ==========================");
     th.alice.state.read().await.verify_transactions(&[airdrop_tx.clone()], true).await?;
     th.alice.merkle_tree.append(&MerkleNode::from(airdrop_params.outputs[0].coin.inner()));
 
     assert!(th.faucet.merkle_tree.root(0).unwrap() == th.alice.merkle_tree.root(0).unwrap());
 
-    // Gather new owncoins
-    let mut owncoins = vec![];
+    // Gather new owncoin
     let leaf_position = th.alice.merkle_tree.witness().unwrap();
     let note: MoneyNote = airdrop_params.outputs[0].note.decrypt(&th.alice.keypair.secret)?;
-    owncoins.push(OwnCoin {
+    let alice_oc = OwnCoin {
         coin: Coin::from(airdrop_params.outputs[0].coin),
         note: note.clone(),
         secret: th.alice.keypair.secret,
         nullifier: Nullifier::from(poseidon_hash([th.alice.keypair.secret.inner(), note.serial])),
         leaf_position,
-    });
+    };
+
+    // Now Alice can stake her owncoin
+    info!(target: "consensus", "[Alice] ============================");
+    info!(target: "consensus", "[Alice] Building Money::Stake params");
+    info!(target: "consensus", "[Alice] ============================");
+    let alice_money_stake_call_debris = MoneyStakeCallBuilder {
+        coin: alice_oc.clone(),
+        tree: th.alice.merkle_tree.clone(),
+        burn_zkbin: burn_zkbin.clone(),
+        burn_pk: burn_pk.clone(),
+    }
+    .build()?;
+    let (
+        alice_money_stake_params,
+        alice_money_stake_proofs,
+        alice_money_stake_secret_key,
+        alice_money_stake_value_blind,
+    ) = (
+        alice_money_stake_call_debris.params,
+        alice_money_stake_call_debris.proofs,
+        alice_money_stake_call_debris.signature_secret,
+        alice_money_stake_call_debris.value_blind,
+    );
+
+    info!(target: "consensus", "[Alice] ================================");
+    info!(target: "consensus", "[Alice] Building Consensus::Stake params");
+    info!(target: "consensus", "[Alice] ================================");
+    let alice_consensus_stake_call_debris = ConsensusStakeCallBuilder {
+        coin: alice_oc.clone(),
+        recipient: th.alice.keypair.public,
+        value_blind: alice_money_stake_value_blind,
+        token_blind: alice_money_stake_params.token_blind,
+        nullifier: alice_money_stake_params.input.nullifier,
+        merkle_root: alice_money_stake_params.input.merkle_root,
+        mint_zkbin: mint_zkbin.clone(),
+        mint_pk: mint_pk.clone(),
+    }
+    .build()?;
+    let (alice_consensus_stake_params, alice_consensus_stake_proofs) =
+        (alice_consensus_stake_call_debris.params, alice_consensus_stake_call_debris.proofs);
+
+    info!(target: "consensus", "[Alice] =================");
+    info!(target: "consensus", "[Alice] Building stake tx");
+    info!(target: "consensus", "[Alice] =================");
+    let mut data = vec![MoneyFunction::StakeV1 as u8];
+    alice_money_stake_params.encode(&mut data)?;
+    let money_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
+
+    let mut data = vec![ConsensusFunction::StakeV1 as u8];
+    alice_consensus_stake_params.encode(&mut data)?;
+    let consensus_call = ContractCall { contract_id: *CONSENSUS_CONTRACT_ID, data };
+
+    let calls = vec![money_call, consensus_call];
+    let proofs = vec![alice_money_stake_proofs, alice_consensus_stake_proofs];
+    let mut alice_stake_tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = alice_stake_tx.create_sigs(&mut OsRng, &[alice_money_stake_secret_key])?;
+    alice_stake_tx.signatures = vec![sigs];
+
+    info!(target: "consensus", "[Faucet] ========================");
+    info!(target: "consensus", "[Faucet] Executing Alice stake tx");
+    info!(target: "consensus", "[Faucet] ========================");
+    th.faucet.state.read().await.verify_transactions(&[alice_stake_tx.clone()], true).await?;
+    info!(target: "consensus", "[Alice] ========================");
+    info!(target: "consensus", "[Alice] Executing Alice stake tx");
+    info!(target: "consensus", "[Alice] ========================");
+    th.alice.state.read().await.verify_transactions(&[alice_stake_tx.clone()], true).await?;
+
+    assert!(th.faucet.merkle_tree.root(0).unwrap() == th.alice.merkle_tree.root(0).unwrap());
+
+    // TODO: Execute unstake transaction
 
     // Thanks for reading
     Ok(())

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

@@ -44,6 +44,9 @@ pub mod mint_v1;
 /// `Money::FreezeV1` API
 pub mod freeze_v1;
 
+/// `Money::StakeV1` API
+pub mod stake_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.

+ 232 - 0
src/contract/money/src/client/stake_v1.rs

@@ -0,0 +1,232 @@
+/* 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,
+    Result,
+};
+use darkfi_sdk::{
+    crypto::{
+        pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, poseidon_hash,
+        MerkleNode, MerklePosition, MerkleTree, Nullifier, PublicKey, SecretKey, DARK_TOKEN_ID,
+    },
+    incrementalmerkletree::{Hashable, Tree},
+    pasta::pallas,
+};
+use log::{debug, info};
+use rand::rngs::OsRng;
+
+use crate::{
+    client::{MoneyNote, OwnCoin},
+    model::{Input, MoneyStakeParamsV1},
+};
+
+pub struct MoneyStakeCallDebris {
+    pub params: MoneyStakeParamsV1,
+    pub proofs: Vec<Proof>,
+    pub signature_secret: SecretKey,
+    pub value_blind: pallas::Scalar,
+}
+
+pub struct MoneyStakeBurnRevealed {
+    pub value_commit: pallas::Point,
+    pub token_commit: pallas::Point,
+    pub nullifier: Nullifier,
+    pub merkle_root: MerkleNode,
+    pub spend_hook: pallas::Base,
+    pub user_data_enc: pallas::Base,
+    pub signature_public: PublicKey,
+}
+
+impl MoneyStakeBurnRevealed {
+    pub fn to_vec(&self) -> Vec<pallas::Base> {
+        let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
+        let tokcom_coords = self.token_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(),
+            *tokcom_coords.x(),
+            *tokcom_coords.y(),
+            self.merkle_root.inner(),
+            // TODO: Why is spend hook in the struct but not here?
+            self.user_data_enc,
+            *sigpub_coords.x(),
+            *sigpub_coords.y(),
+        ]
+    }
+}
+
+pub struct TransactionBuilderInputInfo {
+    pub leaf_position: MerklePosition,
+    pub merkle_path: Vec<MerkleNode>,
+    pub secret: SecretKey,
+    pub note: MoneyNote,
+}
+
+/// Struct holding necessary information to build a `Money::StakeV1` contract call.
+pub struct MoneyStakeCallBuilder {
+    /// `OwnCoin` we're given to use in this builder
+    pub coin: OwnCoin,
+    /// Merkle tree of coins used to create inclusion proofs
+    pub tree: MerkleTree,
+    /// `Burn_V1` zkas circuit ZkBinary
+    pub burn_zkbin: ZkBinary,
+    /// Proving key for the `Burn_V1` zk circuit
+    pub burn_pk: ProvingKey,
+}
+
+impl MoneyStakeCallBuilder {
+    pub fn build(&self) -> Result<MoneyStakeCallDebris> {
+        debug!("Building Money::StakeV1 contract call");
+        assert!(self.coin.note.value != 0);
+        assert!(self.coin.note.token_id == *DARK_TOKEN_ID);
+
+        debug!("Building anonymous input");
+        let leaf_position = self.coin.leaf_position;
+        let root = self.tree.root(0).unwrap();
+        let merkle_path = self.tree.authentication_path(leaf_position, &root).unwrap();
+        let input = TransactionBuilderInputInfo {
+            leaf_position,
+            merkle_path,
+            secret: self.coin.secret,
+            note: self.coin.note.clone(),
+        };
+        debug!("Finished building input");
+
+        let value_blind = pallas::Scalar::random(&mut OsRng);
+        let token_blind = pallas::Scalar::random(&mut OsRng);
+        let signature_secret = SecretKey::random(&mut OsRng);
+        let spend_hook = DARK_TOKEN_ID.inner();
+        let user_data_blind = pallas::Base::random(&mut OsRng);
+        info!("Creating stake burn proof for input");
+        let (proof, public_inputs) = create_stake_burn_proof(
+            &self.burn_zkbin,
+            &self.burn_pk,
+            &input,
+            value_blind,
+            token_blind,
+            spend_hook,
+            user_data_blind,
+            signature_secret,
+        )?;
+
+        let input = 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,
+            user_data_enc: public_inputs.user_data_enc,
+            signature_public: public_inputs.signature_public,
+        };
+
+        // We now fill this with necessary stuff
+        let params = MoneyStakeParamsV1 { token_blind, input };
+        let proofs = vec![proof];
+
+        // Now we should have all the params, zk proof, signature secret and token blind.
+        // We return it all and let the caller deal with it.
+        let debris = MoneyStakeCallDebris { params, proofs, signature_secret, value_blind };
+        Ok(debris)
+    }
+}
+
+pub fn create_stake_burn_proof(
+    zkbin: &ZkBinary,
+    pk: &ProvingKey,
+    input: &TransactionBuilderInputInfo,
+    value_blind: pallas::Scalar,
+    token_blind: pallas::Scalar,
+    spend_hook: pallas::Base,
+    user_data_blind: pallas::Base,
+    signature_secret: SecretKey,
+) -> Result<(Proof, MoneyStakeBurnRevealed)> {
+    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,
+        spend_hook,
+        input.note.user_data,
+        input.note.coin_blind,
+    ]);
+
+    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, user_data_blind]);
+    let value_commit = pedersen_commitment_u64(input.note.value, value_blind);
+    let token_commit = pedersen_commitment_base(input.note.token_id.inner(), token_blind);
+
+    let public_inputs = MoneyStakeBurnRevealed {
+        value_commit,
+        token_commit,
+        nullifier,
+        merkle_root,
+        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::Scalar(Value::known(token_blind)),
+        Witness::Base(Value::known(input.note.serial)),
+        Witness::Base(Value::known(spend_hook)),
+        Witness::Base(Value::known(input.note.user_data)),
+        Witness::Base(Value::known(user_data_blind)),
+        Witness::Base(Value::known(input.note.coin_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.clone());
+    let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
+
+    Ok((proof, public_inputs))
+}

+ 11 - 11
src/contract/money/src/entrypoint/stake_v1.rs

@@ -18,8 +18,8 @@
 
 use darkfi_sdk::{
     crypto::{
-        contract_id::CONSENSUS_CONTRACT_ID, pasta_prelude::*, pedersen_commitment_base, ContractId,
-        PublicKey, DARK_TOKEN_ID,
+        pasta_prelude::*, pedersen_commitment_base, ContractId, PublicKey, CONSENSUS_CONTRACT_ID,
+        DARK_TOKEN_ID,
     },
     db::{db_contains_key, db_lookup, db_set},
     error::{ContractError, ContractResult},
@@ -103,48 +103,48 @@ pub(crate) fn money_stake_process_instruction_v1(
     // Perform the actual state transition
     // ===================================
 
-    msg!("[StakeV1] Validating anonymous input");
+    msg!("[MoneyStakeV1] Validating anonymous input");
     let input = &params.input;
 
     // Only native token can be staked
     if input.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), params.token_blind) {
-        msg!("[StakeV1] Error: Input used non-native token");
+        msg!("[MoneyStakeV1] Error: Input used non-native token");
         return Err(MoneyError::StakeInputNonNativeToken.into())
     }
 
     // The Merkle root is used to know whether this is a coin that
     // existed in a previous state.
     if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
-        msg!("[StakeV1] Error: Merkle root not found in previous state");
+        msg!("[MoneyStakeV1] Error: Merkle root not found in previous state");
         return Err(MoneyError::TransferMerkleRootNotFound.into())
     }
 
     // The nullifiers should not already exist. It is the double-spend protection.
     if db_contains_key(nullifiers_db, &serialize(&input.nullifier))? {
-        msg!("[StakeV1] Error: Duplicate nullifier found");
+        msg!("[MoneyStakeV1] Error: Duplicate nullifier found");
         return Err(MoneyError::DuplicateNullifier.into())
     }
 
     // Check if spend hook is set and its correctness
     if input.spend_hook == pallas::Base::zero() {
-        msg!("[StakeV1] Error: Missing spend hook");
+        msg!("[MoneyStakeV1] Error: Missing spend hook");
         return Err(MoneyError::StakeMissingSpendHook.into())
     }
 
     let next_call_idx = call_idx + 1;
     if next_call_idx >= calls.len() as u32 {
-        msg!("[StakeV1] Error: next_call_idx out of bounds");
+        msg!("[MoneyStakeV1] Error: next_call_idx out of bounds");
         return Err(MoneyError::SpendHookOutOfBounds.into())
     }
 
     let next = &calls[next_call_idx as usize];
     if next.contract_id.inner() != input.spend_hook {
-        msg!("[StakeV1] Error: Invoking contract call does not match spend hook");
+        msg!("[MoneyStakeV1] Error: Invoking contract call does not match spend hook");
         return Err(MoneyError::SpendHookMismatch.into())
     }
 
     if input.spend_hook != CONSENSUS_CONTRACT_ID.inner() {
-        msg!("[StakeV1] Error: Spend hook is not consensus contract");
+        msg!("[MoneyStakeV1] Error: Spend hook is not consensus contract");
         return Err(MoneyError::StakeSpendHookNonConsensusContract.into())
     }
 
@@ -165,7 +165,7 @@ pub(crate) fn money_stake_process_update_v1(
     // Grab all necessary db handles for where we want to write
     let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
 
-    msg!("[StakeV1] Adding new nullifier to the set");
+    msg!("[MoneyStakeV1] Adding new nullifier to the set");
     db_set(nullifiers_db, &serialize(&update.nullifier), &[])?;
 
     Ok(())

+ 1 - 1
src/sdk/src/crypto/mod.rs

@@ -36,7 +36,7 @@ pub use coin::Coin;
 
 /// Contract ID definitions and methods
 pub mod contract_id;
-pub use contract_id::{ContractId, DAO_CONTRACT_ID, MONEY_CONTRACT_ID};
+pub use contract_id::{ContractId, CONSENSUS_CONTRACT_ID, DAO_CONTRACT_ID, MONEY_CONTRACT_ID};
 
 /// Token ID definitions and methods
 pub mod token_id;