فهرست منبع

contract/consensus: stake entrypoint implemented

aggstam 3 سال پیش
والد
کامیت
846b9d7f4b

+ 74 - 6
src/contract/consensus/src/entrypoint.rs

@@ -16,15 +16,22 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use darkfi_money_contract::{MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1};
 use darkfi_sdk::{
-    crypto::ContractId,
-    db::{db_init, db_lookup, set_return_data, SMART_CONTRACT_ZKAS_DB_NAME},
+    crypto::{ContractId, MerkleTree},
+    db::{db_init, db_lookup, db_set, set_return_data, SMART_CONTRACT_ZKAS_DB_NAME},
     error::{ContractError, ContractResult},
     msg, ContractCall,
 };
-use darkfi_serial::deserialize;
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
-use crate::{model::ConsensusStakeUpdateV1, ConsensusFunction};
+use crate::{
+    model::ConsensusStakeUpdateV1, ConsensusFunction, CONSENSUS_CONTRACT_COINS_TREE,
+    CONSENSUS_CONTRACT_COIN_MERKLE_TREE, CONSENSUS_CONTRACT_COIN_ROOTS_TREE,
+    CONSENSUS_CONTRACT_DB_VERSION, CONSENSUS_CONTRACT_INFO_TREE,
+    CONSENSUS_CONTRACT_NULLIFIERS_TREE, CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1,
+    CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
+};
 
 /// `Consensus::Stake` functions
 mod stake_v1;
@@ -50,12 +57,73 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
     // The lookups can be done by `contract_id+_zkas+namespace`.
     // TODO: For the zkas tree, external host checks should be done to ensure
     //       that the bincode is actually valid and not arbitrary.
-    let _zkas_db = match db_lookup(cid, SMART_CONTRACT_ZKAS_DB_NAME) {
+    let zkas_db = match db_lookup(cid, SMART_CONTRACT_ZKAS_DB_NAME) {
         Ok(v) => v,
         Err(_) => db_init(cid, SMART_CONTRACT_ZKAS_DB_NAME)?,
     };
 
-    // TODO: implement
+    let money_mint_v1_bincode = include_bytes!("../../money/proof/mint_v1.zk.bin");
+    let money_burn_v1_bincode = include_bytes!("../../money/proof/burn_v1.zk.bin");
+
+    // TODO: for now we use same proof for mint and burn as Money
+    let consensus_mint_v1_bincode = include_bytes!("../../money/proof/mint_v1.zk.bin");
+    let consensus_burn_v1_bincode = include_bytes!("../../money/proof/burn_v1.zk.bin");
+
+    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_MINT_NS_V1), &money_mint_v1_bincode[..])?;
+    db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_BURN_NS_V1), &money_burn_v1_bincode[..])?;
+    db_set(
+        zkas_db,
+        &serialize(&CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1),
+        &consensus_mint_v1_bincode[..],
+    )?;
+    db_set(
+        zkas_db,
+        &serialize(&CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1),
+        &consensus_burn_v1_bincode[..],
+    )?;
+
+    // Set up a database tree to hold Merkle roots of all coins
+    // k=MerkleNode, v=[]
+    if db_lookup(cid, CONSENSUS_CONTRACT_COIN_ROOTS_TREE).is_err() {
+        db_init(cid, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
+    }
+
+    // Set up a database tree to hold all coins ever seen
+    // k=Coin, v=[]
+    if db_lookup(cid, CONSENSUS_CONTRACT_COINS_TREE).is_err() {
+        db_init(cid, CONSENSUS_CONTRACT_COINS_TREE)?;
+    }
+
+    // Set up a database tree to hold nullifiers of all spent coins
+    // k=Nullifier, v=[]
+    if db_lookup(cid, CONSENSUS_CONTRACT_NULLIFIERS_TREE).is_err() {
+        db_init(cid, CONSENSUS_CONTRACT_NULLIFIERS_TREE)?;
+    }
+
+    // Set up a database tree for arbitrary data
+    let info_db = match db_lookup(cid, CONSENSUS_CONTRACT_INFO_TREE) {
+        Ok(v) => v,
+        Err(_) => {
+            let info_db = db_init(cid, CONSENSUS_CONTRACT_INFO_TREE)?;
+
+            // Create the incrementalmerkletree for seen coins
+            let coin_tree = MerkleTree::new(100);
+            let mut coin_tree_data = vec![];
+
+            coin_tree_data.write_u32(0)?;
+            coin_tree.encode(&mut coin_tree_data)?;
+
+            db_set(info_db, &serialize(&CONSENSUS_CONTRACT_COIN_MERKLE_TREE), &coin_tree_data)?;
+            info_db
+        }
+    };
+
+    // Update db version
+    db_set(
+        info_db,
+        &serialize(&CONSENSUS_CONTRACT_DB_VERSION),
+        &serialize(&env!("CARGO_PKG_VERSION")),
+    )?;
 
     Ok(())
 }

+ 104 - 17
src/contract/consensus/src/entrypoint/stake_v1.rs

@@ -16,17 +16,28 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use darkfi_money_contract::{
+    error::MoneyError, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
+};
 use darkfi_sdk::{
-    crypto::{ContractId, PublicKey},
+    crypto::{
+        contract_id::{CONSENSUS_CONTRACT_ID, MONEY_CONTRACT_ID},
+        pasta_prelude::*,
+        pedersen_commitment_base, Coin, ContractId, MerkleNode, DARK_TOKEN_ID,
+    },
+    db::{db_contains_key, db_lookup, db_set},
     error::{ContractError, ContractResult},
+    merkle_add, msg,
     pasta::pallas,
     ContractCall,
 };
-use darkfi_serial::{deserialize, Encodable, WriteExt};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 use crate::{
     model::{ConsensusStakeParamsV1, ConsensusStakeUpdateV1},
-    ConsensusFunction,
+    ConsensusFunction, CONSENSUS_CONTRACT_COINS_TREE, CONSENSUS_CONTRACT_COIN_MERKLE_TREE,
+    CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_INFO_TREE,
+    CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
 };
 
 /// `get_metadata` function for `Consensus::StakeV1`
@@ -36,33 +47,99 @@ pub(crate) fn consensus_stake_get_metadata_v1(
     calls: Vec<ContractCall>,
 ) -> Result<Vec<u8>, ContractError> {
     let self_ = &calls[call_idx as usize];
-    let _params: ConsensusStakeParamsV1 = deserialize(&self_.data[1..])?;
+    let params: ConsensusStakeParamsV1 = deserialize(&self_.data[1..])?;
 
     // Public inputs for the ZK proofs we have to verify
-    let zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
-    // Public keys for the transaction signatures we have to verify
-    let signature_pubkeys: Vec<PublicKey> = vec![];
+    let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+
+    // Grab the pedersen commitment from the anonymous output
+    let output = &params.output;
+    let value_coords = output.value_commit.to_affine().coordinates().unwrap();
+    let token_coords = output.token_commit.to_affine().coordinates().unwrap();
 
-    // TODO: implement
+    zk_public_inputs.push((
+        CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1.to_string(),
+        vec![
+            output.coin.inner(),
+            *value_coords.x(),
+            *value_coords.y(),
+            *token_coords.x(),
+            *token_coords.y(),
+        ],
+    ));
 
     // Serialize everything gathered and return it
     let mut metadata = vec![];
     zk_public_inputs.encode(&mut metadata)?;
-    signature_pubkeys.encode(&mut metadata)?;
 
     Ok(metadata)
 }
 
 /// `process_instruction` function for `Consensus::StakeV1`
 pub(crate) fn consensus_stake_process_instruction_v1(
-    _cid: ContractId,
-    _call_idx: u32,
-    _calls: Vec<ContractCall>,
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
 ) -> Result<Vec<u8>, ContractError> {
-    // TODO: implement
+    let self_ = &calls[call_idx as usize];
+    let params: ConsensusStakeParamsV1 = deserialize(&self_.data[1..])?;
+
+    // Access the necessary databases where there is information to
+    // validate this state transition.
+    let consenus_coins_db = db_lookup(cid, CONSENSUS_CONTRACT_COINS_TREE)?;
+    let money_nullifiers_db = db_lookup(*MONEY_CONTRACT_ID, MONEY_CONTRACT_NULLIFIERS_TREE)?;
+    let money_coin_roots_db = db_lookup(*MONEY_CONTRACT_ID, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+
+    // ===================================
+    // Perform the actual state transition
+    // ===================================
+
+    msg!("[StakeV1] 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");
+        return Err(MoneyError::StakeInputNonNativeToken.into())
+    }
+
+    // Verify value commits match
+    if output.value_commit != input.value_commit {
+        msg!("[StakeV1] 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");
+        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");
+        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");
+        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");
+        return Err(MoneyError::DuplicateCoin.into())
+    }
+    let coin = Coin::from(output.coin);
 
     // Create a state update.
-    let update = ConsensusStakeUpdateV1 {};
+    let update = ConsensusStakeUpdateV1 { coin };
     let mut update_data = vec![];
     update_data.write_u8(ConsensusFunction::StakeV1 as u8)?;
     update.encode(&mut update_data)?;
@@ -72,10 +149,20 @@ pub(crate) fn consensus_stake_process_instruction_v1(
 
 /// `process_update` function for `Consensus::StakeV1`
 pub(crate) fn consensus_stake_process_update_v1(
-    _cid: ContractId,
-    _update: ConsensusStakeUpdateV1,
+    cid: ContractId,
+    update: ConsensusStakeUpdateV1,
 ) -> ContractResult {
-    // TODO: implement
+    // Grab all necessary db handles for where we want to write
+    let info_db = db_lookup(cid, CONSENSUS_CONTRACT_INFO_TREE)?;
+    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");
+    db_set(coins_db, &serialize(&update.coin), &[])?;
+
+    msg!("[StakeV1] 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)?;
 
     Ok(())
 }

+ 0 - 33
src/contract/consensus/src/error.rs

@@ -1,33 +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/>.
- */
-
-use darkfi_sdk::error::ContractError;
-
-#[derive(Debug, Clone, thiserror::Error)]
-pub enum ConsensusError {
-    #[error("Error")]
-    SomeError,
-}
-
-impl From<ConsensusError> for ContractError {
-    fn from(e: ConsensusError) -> Self {
-        match e {
-            ConsensusError::SomeError => Self::Custom(1),
-        }
-    }
-}

+ 14 - 3
src/contract/consensus/src/lib.rs

@@ -45,9 +45,6 @@ impl TryFrom<u8> for ConsensusFunction {
     }
 }
 
-/// Internal contract errors
-pub mod error;
-
 /// Call parameters definitions
 pub mod model;
 
@@ -58,3 +55,17 @@ pub mod entrypoint;
 #[cfg(feature = "client")]
 /// Client API for interaction with this smart contract
 pub mod client;
+
+// These are the different sled trees that will be created
+pub const CONSENSUS_CONTRACT_INFO_TREE: &str = "info";
+pub const CONSENSUS_CONTRACT_COINS_TREE: &str = "coins";
+pub const CONSENSUS_CONTRACT_COIN_ROOTS_TREE: &str = "coin_roots";
+pub const CONSENSUS_CONTRACT_NULLIFIERS_TREE: &str = "nullifiers";
+
+// These are keys inside the info tree
+pub const CONSENSUS_CONTRACT_DB_VERSION: &str = env!("CARGO_PKG_VERSION");
+pub const CONSENSUS_CONTRACT_COIN_MERKLE_TREE: &str = "coin_tree";
+
+/// zkas mint circuit namespace
+pub const CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1: &str = "Consensus_Mint_V1";
+pub const CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1: &str = "Consensus_Burn_V1";

+ 24 - 2
src/contract/consensus/src/model.rs

@@ -16,16 +16,38 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use darkfi_money_contract::model::Output;
+use darkfi_sdk::{
+    crypto::{Coin, MerkleNode, Nullifier},
+    pasta::pallas,
+};
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 
+/// Anonymous input from `Money::Stake`
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct StakeInput {
+    /// Blinding factor for `token_id`
+    pub token_blind: pallas::Scalar,
+    /// Pedersen commitment for the staked coin's value
+    pub value_commit: pallas::Point,
+    /// Revealed nullifier
+    pub nullifier: Nullifier,
+    /// Revealed Merkle root
+    pub merkle_root: MerkleNode,
+}
+
 /// Parameters for `Consensus::Stake`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct ConsensusStakeParamsV1 {
-    // TODO: implement
+    /// Burnt token revealed info
+    pub input: StakeInput,
+    /// Anonymous output
+    pub output: Output,
 }
 
 /// State update for `Consensus::Stake`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct ConsensusStakeUpdateV1 {
-    // TODO: implement
+    /// The newly minted coin
+    pub coin: Coin,
 }

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

@@ -32,7 +32,7 @@ use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 use crate::{
     error::MoneyError,
     model::{MoneyStakeParamsV1, MoneyStakeUpdateV1},
-    MoneyFunction, CONSENSUS_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
+    MoneyFunction, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
     MONEY_CONTRACT_ZKAS_BURN_NS_V1,
 };
 
@@ -97,7 +97,7 @@ pub(crate) fn money_stake_process_instruction_v1(
     // Access the necessary databases where there is information to
     // validate this state transition.
     let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
-    let coin_roots_db = db_lookup(*CONSENSUS_CONTRACT_ID, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
+    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
 
     // ===================================
     // Perform the actual state transition

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

@@ -84,6 +84,3 @@ pub const MONEY_CONTRACT_ZKAS_BURN_NS_V1: &str = "Burn_V1";
 pub const MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1: &str = "TokenMint_V1";
 /// zkas token freeze circuit namespace
 pub const MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1: &str = "TokenFreeze_V1";
-
-// These are consensus sled trees we access for information
-pub const CONSENSUS_CONTRACT_COIN_ROOTS_TREE: &str = "coin_roots";

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

@@ -127,8 +127,6 @@ pub struct MoneyFreezeUpdateV1 {
 /// Parameters for `Money::Stake`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct MoneyStakeParamsV1 {
-    /// Blinding factor for `value`
-    pub value_blind: pallas::Scalar,
     /// Blinding factor for `token_id`
     pub token_blind: pallas::Scalar,
     /// Anonymous input