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

src/contract/consensus: constrain slot lottery parameters

aggstam 3 лет назад
Родитель
Сommit
96a834eef5

+ 5 - 2
src/blockchain/mod.rs

@@ -33,7 +33,7 @@ pub mod block_store;
 pub use block_store::{BlockOrderStore, BlockStore, HeaderStore};
 
 pub mod slot_checkpoint_store;
-pub use slot_checkpoint_store::SlotCheckpointStore;
+pub use slot_checkpoint_store::{SlotCheckpointStore, SlotCheckpointStoreOverlay};
 
 pub mod tx_store;
 pub use tx_store::{PendingTxOrderStore, PendingTxStore, TxStore};
@@ -298,6 +298,8 @@ pub type BlockchainOverlayPtr = Arc<Mutex<BlockchainOverlay>>;
 pub struct BlockchainOverlay {
     /// Main [`sled_overlay::SledDbOverlay`] to the sled db connection
     pub overlay: SledDbOverlayPtr,
+    /// Slot checkpoints overlay
+    pub slot_checkpoints: SlotCheckpointStoreOverlay,
     /// Contract states overlay
     pub contracts: ContractStateStoreOverlay,
     /// Wasm bincodes overlay
@@ -308,9 +310,10 @@ impl BlockchainOverlay {
     /// Instantiate a new `BlockchainOverlay` over the given [`Blockchain`] instance.
     pub fn new(blockchain: &Blockchain) -> Result<BlockchainOverlayPtr> {
         let overlay = Arc::new(Mutex::new(sled_overlay::SledDbOverlay::new(&blockchain.sled_db)));
+        let slot_checkpoints = SlotCheckpointStoreOverlay::new(overlay.clone())?;
         let contracts = ContractStateStoreOverlay::new(overlay.clone())?;
         let wasm_bincode = WasmStoreOverlay::new(overlay.clone())?;
 
-        Ok(Arc::new(Mutex::new(Self { overlay, contracts, wasm_bincode })))
+        Ok(Arc::new(Mutex::new(Self { overlay, slot_checkpoints, contracts, wasm_bincode })))
     }
 }

+ 19 - 1
src/blockchain/slot_checkpoint_store.rs

@@ -18,7 +18,7 @@
 
 use darkfi_serial::{deserialize, serialize};
 
-use crate::{consensus::SlotCheckpoint, Error, Result};
+use crate::{blockchain::SledDbOverlayPtr, consensus::SlotCheckpoint, Error, Result};
 
 const SLED_SLOT_CHECKPOINT_TREE: &[u8] = b"_slot_checkpoints";
 
@@ -141,3 +141,21 @@ impl SlotCheckpointStore {
         self.0.len() == 0
     }
 }
+
+/// Overlay structure over a [`SlotCheckpointStore`] instance.
+pub struct SlotCheckpointStoreOverlay(SledDbOverlayPtr);
+
+impl SlotCheckpointStoreOverlay {
+    pub fn new(overlay: SledDbOverlayPtr) -> Result<Self> {
+        overlay.lock().unwrap().open_tree(SLED_SLOT_CHECKPOINT_TREE)?;
+        Ok(Self(overlay))
+    }
+
+    /// Fetch given slot from the slotcheckpointstore.
+    pub fn get(&self, slot: u64) -> Result<Vec<u8>> {
+        match self.0.lock().unwrap().get(&SLED_SLOT_CHECKPOINT_TREE, &slot.to_be_bytes())? {
+            Some(found) => Ok(found.to_vec()),
+            None => Err(Error::SlotNotFound(slot)),
+        }
+    }
+}

+ 40 - 3
src/contract/consensus/proof/reward_v1.zk

@@ -4,18 +4,32 @@ constant "Reward_V1" {
 }
 
 contract "Reward_V1" {
+	# Burnt coin
+	Base coin,
+	# Burnt coin secret key
+	Base secret_key,
 	# The value of the burnt coin
 	Base value,
 	# The reward value
 	Base reward,
 	# Random blinding factor for the value commitment
 	Scalar value_blind,
+	# Election seed y
+	Base mu_y,
+	# Election seed rho
+	Base mu_rho,
+	# Lottery headstart
+	Base headstart,
+	# Sigma1
+	Base sigma1,
+	# Sigma2
+	Base sigma2,
 }
 
 circuit "Reward_V1" {
-	# This is a dummy check for now.
-	# TODO: Here we will constrain all the consensus
-	# parameters for the slot.
+	# Constants
+	ZERO = witness_base(0);
+	SEED_PREFIX = witness_base(3);
 
 	# Pedersen commitment for coin's value
 	vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);
@@ -36,5 +50,28 @@ circuit "Reward_V1" {
 	constrain_instance(ec_get_x(new_value_commit));
 	constrain_instance(ec_get_y(new_value_commit));
 
+	# Coin y:
+	seed = poseidon_hash(SEED_PREFIX, coin, secret_key, ZERO);
+	y = poseidon_hash(seed, mu_y);
+	constrain_instance(mu_y);
+	constrain_instance(y);
+
+	# Coin rho(seed):
+	rho = poseidon_hash(seed, mu_rho);
+	constrain_instance(mu_rho);
+	constrain_instance(rho);
+
+	# Calculate lottery target
+	term_1 =  base_mul(sigma1, value);
+	term_2 = base_mul(sigma2, value);
+	shifted_term_2 = base_mul(term_2, value);
+	target = base_add(term_1, shifted_term_2);
+	shifted_target = base_add(target, headstart);
+	constrain_instance(sigma1);
+	constrain_instance(sigma2);
+
+	# Play lottery
+	less_than_loose(y, shifted_target);
+
 	# At this point we've enforced all of our public inputs.
 }

+ 71 - 7
src/contract/consensus/src/client/proposal_v1.rs

@@ -19,6 +19,7 @@
 //! This API is crufty. Please rework it into something nice to read and nice to use.
 
 use darkfi::{
+    consensus::SlotCheckpoint,
     zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit},
     zkas::ZkBinary,
     Result,
@@ -29,8 +30,8 @@ use darkfi_money_contract::{
 };
 use darkfi_sdk::{
     crypto::{
-        note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_u64, MerkleTree, PublicKey,
-        SecretKey, CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID,
+        note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_u64, poseidon_hash,
+        MerkleTree, PublicKey, SecretKey, CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID,
     },
     incrementalmerkletree::Tree,
     pasta::pallas,
@@ -59,6 +60,12 @@ pub struct ConsensusProposalCallDebris {
 pub struct ConsensusProposalRewardRevealed {
     pub value_commit: pallas::Point,
     pub new_value_commit: pallas::Point,
+    pub mu_y: pallas::Base,
+    pub y: pallas::Base,
+    pub mu_rho: pallas::Base,
+    pub rho: pallas::Base,
+    pub sigma1: pallas::Base,
+    pub sigma2: pallas::Base,
 }
 
 impl ConsensusProposalRewardRevealed {
@@ -68,7 +75,18 @@ impl ConsensusProposalRewardRevealed {
 
         // NOTE: It's important to keep these in the same order
         // as the `constrain_instance` calls in the zkas code.
-        vec![*value_coords.x(), *value_coords.y(), *new_value_coords.x(), *new_value_coords.y()]
+        vec![
+            *value_coords.x(),
+            *value_coords.y(),
+            *new_value_coords.x(),
+            *new_value_coords.y(),
+            self.mu_y,
+            self.y,
+            self.mu_rho,
+            self.rho,
+            self.sigma1,
+            self.sigma2,
+        ]
     }
 }
 
@@ -78,6 +96,8 @@ pub struct ConsensusProposalCallBuilder {
     pub coin: OwnCoin,
     /// Recipient's public key
     pub recipient: PublicKey,
+    /// Rewarded slot checkpoint
+    pub slot_checkpoint: SlotCheckpoint,
     /// Merkle tree of coins used to create inclusion proofs
     pub tree: MerkleTree,
     /// `Burn_V1` zkas circuit ZkBinary
@@ -208,11 +228,24 @@ impl ConsensusProposalCallBuilder {
         let stake_input = input;
 
         debug!("Building Consensus::RewardV1 contract call for proposal");
-        let (proof, _public_inputs) =
-            create_proposal_reward_proof(&self.reward_zkbin, &self.reward_pk, value, value_blind)?;
+        let coin = self.coin.coin.inner();
+        let secret_key = self.coin.secret.inner();
+        let (proof, public_inputs) = create_proposal_reward_proof(
+            &self.reward_zkbin,
+            &self.reward_pk,
+            &self.slot_checkpoint,
+            coin,
+            secret_key,
+            value,
+            value_blind,
+        )?;
 
         // We now fill this with necessary stuff
-        let reward_params = ConsensusRewardParamsV1 { unstake_input, stake_input, output };
+        let slot = self.slot_checkpoint.slot;
+        let y = public_inputs.y;
+        let rho = public_inputs.rho;
+        let reward_params =
+            ConsensusRewardParamsV1 { unstake_input, stake_input, output, slot, y, rho };
         let reward_proofs = vec![proof];
 
         // Now we should have all the params, zk proofs and signature secret.
@@ -233,18 +266,49 @@ impl ConsensusProposalCallBuilder {
 pub fn create_proposal_reward_proof(
     zkbin: &ZkBinary,
     pk: &ProvingKey,
+    slot_checkpoint: &SlotCheckpoint,
+    coin: pallas::Base,
+    secret_key: pallas::Base,
     value: u64,
     value_blind: pallas::Scalar,
 ) -> Result<(Proof, ConsensusProposalRewardRevealed)> {
+    // Proof parameters
+    let zero = pallas::Base::from(0);
+    let seed_prefix = pallas::Base::from(3);
     let value_commit = pedersen_commitment_u64(value, value_blind);
     let new_value_commit = pedersen_commitment_u64(value + REWARD, value_blind);
+    let slot_pallas = pallas::Base::from(slot_checkpoint.slot);
+    let mu_y = poseidon_hash([pallas::Base::from(22), slot_checkpoint.eta, slot_pallas]);
+    let seed = poseidon_hash([seed_prefix, coin, secret_key, zero]);
+    let y = poseidon_hash([seed, mu_y]);
+    let mu_rho = poseidon_hash([pallas::Base::from(3), slot_checkpoint.eta, slot_pallas]);
+    let rho = poseidon_hash([seed, mu_rho]);
+    let headstart = darkfi::consensus::LeadCoin::headstart();
+    let (sigma1, sigma2) = (slot_checkpoint.sigma1, slot_checkpoint.sigma2);
 
-    let public_inputs = ConsensusProposalRewardRevealed { value_commit, new_value_commit };
+    // Generate public inputs, witnesses and proof
+    let public_inputs = ConsensusProposalRewardRevealed {
+        value_commit,
+        new_value_commit,
+        mu_y,
+        y,
+        mu_rho,
+        rho,
+        sigma1,
+        sigma2,
+    };
 
     let prover_witnesses = vec![
+        Witness::Base(Value::known(coin)),
+        Witness::Base(Value::known(secret_key)),
         Witness::Base(Value::known(pallas::Base::from(value))),
         Witness::Base(Value::known(pallas::Base::from(REWARD))),
         Witness::Scalar(Value::known(value_blind)),
+        Witness::Base(Value::known(mu_y)),
+        Witness::Base(Value::known(mu_rho)),
+        Witness::Base(Value::known(headstart)),
+        Witness::Base(Value::known(sigma1)),
+        Witness::Base(Value::known(sigma2)),
     ];
 
     let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());

+ 38 - 4
src/contract/consensus/src/entrypoint/proposal_reward_v1.rs

@@ -23,9 +23,10 @@ use darkfi_money_contract::{
 };
 use darkfi_sdk::{
     crypto::{
-        pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, ContractId, PublicKey,
-        CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID,
+        pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, poseidon_hash,
+        ContractId, PublicKey, CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID,
     },
+    db::db_get_slot_checkpoint,
     error::{ContractError, ContractResult},
     msg,
     pasta::pallas,
@@ -34,7 +35,8 @@ use darkfi_sdk::{
 use darkfi_serial::{deserialize, Encodable, WriteExt};
 
 use crate::{
-    model::{ConsensusRewardParamsV1, ConsensusRewardUpdateV1, REWARD},
+    error::ConsensusError,
+    model::{ConsensusRewardParamsV1, ConsensusRewardUpdateV1, SlotCheckpoint, REWARD},
     ConsensusFunction,
 };
 
@@ -54,12 +56,44 @@ pub(crate) fn consensus_proposal_reward_get_metadata_v1(
 
     // Grab the pedersen commitment for the burnt value
     let value_coords = &params.unstake_input.value_commit.to_affine().coordinates().unwrap();
+
     // Grab the pedersen commitment for the minted value
     let new_value_coords = &params.stake_input.value_commit.to_affine().coordinates().unwrap();
 
+    // Grab proposal coin y and rho for lottery
+    let y = &params.y;
+    let rho = &params.rho;
+
+    // Grab the slot checkpoint to validate consensus parameters against
+    let slot = &params.slot;
+    let Some(slot_checkpoint) = db_get_slot_checkpoint(*slot)? else {
+        msg!("[ConsensusProposalRewardV1] Error: Missing slot checkpoint {} from db", slot);
+        return Err(ConsensusError::ProposalMissingSlotCheckpoint.into())
+    };
+    let slot_checkpoint: SlotCheckpoint = deserialize(&slot_checkpoint)?;
+
+    // Calculate election seeds
+    let slot_pallas = pallas::Base::from(slot_checkpoint.slot);
+    let mu_y = poseidon_hash([pallas::Base::from(22), slot_checkpoint.eta, slot_pallas]);
+    let mu_rho = poseidon_hash([pallas::Base::from(3), slot_checkpoint.eta, slot_pallas]);
+
+    // Grab sigmas from slot checkpoint
+    let (sigma1, sigma2) = (slot_checkpoint.sigma1, slot_checkpoint.sigma2);
+
     zk_public_inputs.push((
         CONSENSUS_CONTRACT_ZKAS_REWARD_NS_V1.to_string(),
-        vec![*value_coords.x(), *value_coords.y(), *new_value_coords.x(), *new_value_coords.y()],
+        vec![
+            *value_coords.x(),
+            *value_coords.y(),
+            *new_value_coords.x(),
+            *new_value_coords.y(),
+            mu_y,
+            *y,
+            mu_rho,
+            *rho,
+            sigma1,
+            sigma2,
+        ],
     ));
 
     signature_pubkeys.push(params.stake_input.signature_public);

+ 3 - 3
src/contract/consensus/src/error.rs

@@ -20,14 +20,14 @@ use darkfi_sdk::error::ContractError;
 
 #[derive(Debug, Clone, thiserror::Error)]
 pub enum ConsensusError {
-    #[error("Error")]
-    SomeError,
+    #[error("Missing slot checkpoint from db")]
+    ProposalMissingSlotCheckpoint,
 }
 
 impl From<ConsensusError> for ContractError {
     fn from(e: ConsensusError) -> Self {
         match e {
-            ConsensusError::SomeError => Self::Custom(1),
+            ConsensusError::ProposalMissingSlotCheckpoint => Self::Custom(1),
         }
     }
 }

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

@@ -46,6 +46,9 @@ impl TryFrom<u8> for ConsensusFunction {
     }
 }
 
+/// Internal contract errors
+pub mod error;
+
 /// Call parameters definitions
 pub mod model;
 

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

@@ -17,11 +17,9 @@
  */
 
 use darkfi_money_contract::model::{Input, Output, StakeInput};
+use darkfi_sdk::pasta::pallas;
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 
-// TODO: Don't set this here
-pub const REWARD: u64 = 1;
-
 /// Parameters for `Consensus::Reward`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct ConsensusRewardParamsV1 {
@@ -31,8 +29,31 @@ pub struct ConsensusRewardParamsV1 {
     pub stake_input: StakeInput,
     /// Anonymous output
     pub output: Output,
+    /// Rewarded slot
+    pub slot: u64,
+    /// Coin y
+    pub y: pallas::Base,
+    /// Lottery rho used
+    pub rho: pallas::Base,
 }
 
 /// State update for `Consensus::Reward`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct ConsensusRewardUpdateV1 {}
+
+// TODO: Don't set these here
+pub const REWARD: u64 = 1;
+
+/// Auxiliary structure to decode `darkfi::consensus::state::SlotCheckpoint`
+/// to use in contract.
+#[derive(SerialDecodable)]
+pub struct SlotCheckpoint {
+    /// Slot UID
+    pub slot: u64,
+    /// Slot eta
+    pub eta: pallas::Base,
+    /// Slot sigma1
+    pub sigma1: pallas::Base,
+    /// Slot sigma2
+    pub sigma2: pallas::Base,
+}

+ 11 - 4
src/contract/consensus/tests/stake_unstake.rs

@@ -226,10 +226,16 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
     // Verify values match
     assert!(alice_oc.note.value == alice_staked_oc.note.value);
 
-    // We asume alice became the slot proposer, so she creates a
-    // proposal transaction to burn her staked coin, reward herself
-    // and mint the new coin. Burn and Mint use the same parameters
-    // as Unstake and Stake.
+    // We simulate the proposal of genesis slot
+    let slot_checkpoint =
+        th.alice.state.read().await.blockchain.get_slot_checkpoints_by_slot(&[0])?[0]
+            .clone()
+            .unwrap();
+
+    // With alice's current coin value she can become the slot proposer,
+    // so she creates a proposal transaction to burn her staked coin,
+    // reward herself and mint the new coin. Burn and Mint use the same
+    // parameters as Unstake and Stake.
     info!(target: "consensus", "[Alice] ====================================");
     info!(target: "consensus", "[Alice] Building proposal transaction params");
     info!(target: "consensus", "[Alice] ====================================");
@@ -237,6 +243,7 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
     let alice_consensus_proposal_call_debris = ConsensusProposalCallBuilder {
         coin: alice_staked_oc.clone(),
         recipient: th.alice.keypair.public,
+        slot_checkpoint,
         tree: th.alice.consensus_merkle_tree.clone(),
         burn_zkbin: burn_zkbin.clone(),
         burn_pk: burn_pk.clone(),

+ 26 - 0
src/runtime/import/db.rs

@@ -444,6 +444,32 @@ pub(crate) fn db_get(ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) -> i6
     (objects.len() - 1) as i64
 }
 
+/// Will return requested slot checkpoint from `SlotCheckpointStore`.
+pub(crate) fn db_get_slot_checkpoint(ctx: FunctionEnvMut<Env>, slot: u64) -> i64 {
+    let env = ctx.data();
+
+    if env.contract_section != ContractSection::Deploy &&
+        env.contract_section != ContractSection::Exec &&
+        env.contract_section != ContractSection::Metadata
+    {
+        error!(target: "runtime::db::db_get_slot_checkpoint()", "db_get_slot_checkpoint called in unauthorized section");
+        return CALLER_ACCESS_DENIED.into()
+    }
+
+    let ret = match env.blockchain.lock().unwrap().slot_checkpoints.get(slot) {
+        Ok(v) => v,
+        Err(e) => {
+            error!(target: "runtime::db::db_get_slot_checkpoint()", "Internal error getting from slot checkpoints tree: {}", e);
+            return DB_GET_FAILED.into()
+        }
+    };
+
+    // Copy Vec<u8> to the VM
+    let mut objects = env.objects.borrow_mut();
+    objects.push(ret.to_vec());
+    (objects.len() - 1) as i64
+}
+
 /// Everyone can call this. Will check if a given db contains given key.
 pub(crate) fn db_contains_key(ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) -> i32 {
     let env = ctx.data();

+ 6 - 0
src/runtime/vm_runtime.rs

@@ -210,6 +210,12 @@ impl Runtime {
                     import::db::db_get,
                 ),
 
+                "db_get_slot_checkpoint_" => Function::new_typed_with_env(
+                    &mut store,
+                    &ctx,
+                    import::db::db_get_slot_checkpoint,
+                ),
+
                 "db_contains_key_" => Function::new_typed_with_env(
                     &mut store,
                     &ctx,

+ 15 - 0
src/sdk/src/db.rs

@@ -96,7 +96,21 @@ pub fn db_get(db_handle: DbHandle, key: &[u8]) -> GenericResult<Option<Vec<u8>>>
     len += key.to_vec().encode(&mut buf)?;
 
     let ret = unsafe { db_get_(buf.as_ptr(), len as u32) };
+    db_ret(ret)
+}
+
+/// Everyone can call this. Will return requested slot checkpoint from `SlotCheckpointStore`.
+///
+/// ```
+/// slot_checkpoint = db_get_slot(slot);
+/// ```
+pub fn db_get_slot_checkpoint(slot: u64) -> GenericResult<Option<Vec<u8>>> {
+    let ret = unsafe { db_get_slot_checkpoint_(slot) };
+    db_ret(ret)
+}
 
+/// Auxiliary to parse db_get* calls.
+fn db_ret(ret: i64) -> GenericResult<Option<Vec<u8>>> {
     if ret < 0 {
         match ret as i32 {
             CALLER_ACCESS_DENIED => return Err(ContractError::CallerAccessDenied),
@@ -203,6 +217,7 @@ extern "C" {
     fn db_init_(ptr: *const u8, len: u32) -> i32;
     fn db_lookup_(ptr: *const u8, len: u32) -> i32;
     fn db_get_(ptr: *const u8, len: u32) -> i64;
+    fn db_get_slot_checkpoint_(slot: u64) -> i64;
     fn db_contains_key_(ptr: *const u8, len: u32) -> i32;
     fn db_set_(ptr: *const u8, len: u32) -> i32;
     fn db_del_(ptr: *const u8, len: u32) -> i32;