ソースを参照

contract/money&consensus: add extra verifications in StakeV1

aggstam 3 年 前
コミット
8545aa31e1

+ 29 - 7
src/contract/consensus/src/entrypoint/stake_v1.rs

@@ -17,15 +17,15 @@
  */
 
 use darkfi_money_contract::{
-    error::MoneyError, CONSENSUS_CONTRACT_COINS_TREE, CONSENSUS_CONTRACT_COIN_MERKLE_TREE,
-    CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_INFO_TREE,
-    CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1, MONEY_CONTRACT_COIN_ROOTS_TREE,
-    MONEY_CONTRACT_NULLIFIERS_TREE,
+    error::MoneyError, model::MoneyStakeParamsV1, CONSENSUS_CONTRACT_COINS_TREE,
+    CONSENSUS_CONTRACT_COIN_MERKLE_TREE, CONSENSUS_CONTRACT_COIN_ROOTS_TREE,
+    CONSENSUS_CONTRACT_INFO_TREE, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
+    MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
 };
 use darkfi_sdk::{
     crypto::{
         pasta_prelude::*, pedersen_commitment_base, Coin, ContractId, MerkleNode, PublicKey,
-        DARK_TOKEN_ID, MONEY_CONTRACT_ID,
+        CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID, MONEY_CONTRACT_ID,
     },
     db::{db_contains_key, db_lookup, db_set},
     error::{ContractError, ContractResult},
@@ -130,17 +130,39 @@ pub(crate) fn consensus_stake_process_instruction_v1(
 
     // Check previous call is money contract
     if call_idx == 0 {
-        msg!("[MoneyStakeV1] Error: previous_call_idx will be out of bounds");
+        msg!("[ConsensusStakeV1] Error: previous_call_idx will be out of bounds");
         return Err(MoneyError::SpendHookOutOfBounds.into())
     }
 
     let previous_call_idx = call_idx - 1;
     let previous = &calls[previous_call_idx as usize];
     if previous.contract_id.inner() != MONEY_CONTRACT_ID.inner() {
-        msg!("[MoneyStakeV1] Error: Previous contract call is not consensus contract");
+        msg!("[ConsensusStakeV1] Error: Previous contract call is not consensus contract");
         return Err(MoneyError::StakePreviousCallNotMoneyContract.into())
     }
 
+    // Verify previous call corresponds to Money::StakeV1 (0x05)
+    if previous.data[0] != 0x05 {
+        msg!("[ConsensusStakeV1] Error: Previous call function mismatch");
+        return Err(MoneyError::PreviousCallFunctionMissmatch.into())
+    }
+
+    // Verify previous call input is the same as this calls StakeInput
+    let previous_params: MoneyStakeParamsV1 = deserialize(&previous.data[1..])?;
+    let previous_input = &previous_params.input;
+    if &previous_input != &input {
+        msg!("[ConsensusStakeV1] Error: Previous call input mismatch");
+        return Err(MoneyError::PreviousCallInputMissmatch.into())
+    }
+
+    // If spend hook is set, check its correctness
+    if previous_input.spend_hook != pallas::Base::zero() &&
+        previous_input.spend_hook != CONSENSUS_CONTRACT_ID.inner()
+    {
+        msg!("[ConsensusStakeV1] Error: Invoking contract call does not match spend hook in input");
+        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(consensus_coins_db, &serialize(&output.coin))? {

+ 21 - 9
src/contract/money/src/entrypoint/stake_v1.rs

@@ -31,7 +31,7 @@ use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 use crate::{
     error::MoneyError,
-    model::{MoneyStakeParamsV1, MoneyStakeUpdateV1},
+    model::{MoneyStakeParamsV1, MoneyStakeUpdateV1, MoneyUnstakeParamsV1},
     MoneyFunction, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
     MONEY_CONTRACT_ZKAS_BURN_NS_V1,
 };
@@ -125,12 +125,6 @@ pub(crate) fn money_stake_process_instruction_v1(
         return Err(MoneyError::DuplicateNullifier.into())
     }
 
-    // TODO: (See Money::Transfer) Enforce spend hook for the input in case it's set.
-    // We can allow protocols to execute a staking operation as well.
-
-    // TODO: Check that the value commitment of the input == value commitment in
-    // the Consensus stake mint call.
-
     // Check next call is consensus contract
     let next_call_idx = call_idx + 1;
     if next_call_idx >= calls.len() as u32 {
@@ -144,8 +138,26 @@ pub(crate) fn money_stake_process_instruction_v1(
         return Err(MoneyError::StakeNextCallNotConsensusContract.into())
     }
 
-    // TODO: Also check that the function in Consensus contract being called is the
-    // correct one.
+    // If spend hook is set, check its correctness
+    if input.spend_hook != pallas::Base::zero() && next.contract_id.inner() != input.spend_hook {
+        msg!("[MoneyStakeV1] Error: Invoking contract call does not match spend hook in input");
+        return Err(MoneyError::SpendHookMismatch.into())
+    }
+
+    // Verify next call corresponds to Consensus::StakeV1 (0x00)
+    if next.data[0] != 0x00 {
+        msg!("[MoneyStakeV1] Error: Next call function mismatch");
+        return Err(MoneyError::NextCallFunctionMissmatch.into())
+    }
+
+    // Verify next call StakeInput is the same as this calls input
+    // Note: ConsensusStakeParamsV1 is the same as MoneyUnstakeParamsV1
+    // TODO: maybe create a common models src folder accessible by all contracts?
+    let next_params: MoneyUnstakeParamsV1 = deserialize(&next.data[1..])?;
+    if input != &next_params.input {
+        msg!("[MoneyStakeV1] Error: Next call input mismatch");
+        return Err(MoneyError::NextCallInputMissmatch.into())
+    }
 
     // At this point the state transition has passed, so we create a state update
     let update = MoneyStakeUpdateV1 { nullifier: input.nullifier };

+ 16 - 0
src/contract/money/src/error.rs

@@ -91,6 +91,18 @@ pub enum MoneyError {
 
     #[error("Spend hook is not money contract")]
     UnstakeSpendHookNotMoneyContract,
+
+    #[error("Next call function mismatch")]
+    NextCallFunctionMissmatch,
+
+    #[error("Next call input mismatch")]
+    NextCallInputMissmatch,
+
+    #[error("Previous call function mismatch")]
+    PreviousCallFunctionMissmatch,
+
+    #[error("Previous call input mismatch")]
+    PreviousCallInputMissmatch,
 }
 
 impl From<MoneyError> for ContractError {
@@ -120,6 +132,10 @@ impl From<MoneyError> for ContractError {
             MoneyError::StakeNextCallNotConsensusContract => Self::Custom(22),
             MoneyError::StakePreviousCallNotMoneyContract => Self::Custom(23),
             MoneyError::UnstakeSpendHookNotMoneyContract => Self::Custom(24),
+            MoneyError::NextCallFunctionMissmatch => Self::Custom(25),
+            MoneyError::NextCallInputMissmatch => Self::Custom(26),
+            MoneyError::PreviousCallFunctionMissmatch => Self::Custom(27),
+            MoneyError::PreviousCallInputMissmatch => Self::Custom(28),
         }
     }
 }

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

@@ -38,7 +38,7 @@ pub struct ClearInput {
 }
 
 /// A contract call's anonymous input
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Debug, PartialEq, SerialEncodable, SerialDecodable)]
 pub struct Input {
     /// Pedersen commitment for the input's value
     pub value_commit: pallas::Point,
@@ -61,7 +61,7 @@ pub struct Input {
 }
 
 /// Anonymous input for staking contract calls
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Debug, PartialEq, SerialEncodable, SerialDecodable)]
 pub struct StakeInput {
     /// Blinding factor for `token_id`
     pub token_blind: pallas::Scalar,
@@ -75,6 +75,15 @@ pub struct StakeInput {
     pub signature_public: PublicKey,
 }
 
+impl PartialEq<StakeInput> for Input {
+    fn eq(&self, other: &StakeInput) -> bool {
+        self.value_commit == other.value_commit &&
+            self.nullifier == other.nullifier &&
+            self.merkle_root == other.merkle_root &&
+            self.signature_public == other.signature_public
+    }
+}
+
 /// A contract call's anonymous output
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct Output {