From 4e521f282604eb467f5b60a63eb58402c27b71a3 Mon Sep 17 00:00:00 2001 From: aggstam Date: Wed, 7 Jun 2023 16:42:34 +0300 Subject: [PATCH] contract/consensus: use previous proposal/block hash in vrf input --- src/consensus/state.rs | 3 +-- src/contract/consensus/src/client/proposal_v1.rs | 8 +++++++- src/contract/consensus/src/entrypoint/proposal_v1.rs | 9 ++++++++- src/contract/consensus/src/error.rs | 10 +++++++--- src/contract/consensus/src/model.rs | 2 ++ src/contract/consensus/tests/genesis_stake_unstake.rs | 6 ++++-- src/contract/consensus/tests/harness.rs | 6 +++++- src/contract/consensus/tests/stake_unstake.rs | 6 +++--- 8 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/consensus/state.rs b/src/consensus/state.rs index ff4f1b334..08ce0f745 100644 --- a/src/consensus/state.rs +++ b/src/consensus/state.rs @@ -599,8 +599,7 @@ impl ConsensusState { } if hashes.is_empty() { - let (_, hash) = self.blockchain.last().unwrap(); - hashes.push(hash); + hashes.push(self.genesis_block); } hashes diff --git a/src/contract/consensus/src/client/proposal_v1.rs b/src/contract/consensus/src/client/proposal_v1.rs index c1f4141e3..28283cba3 100644 --- a/src/contract/consensus/src/client/proposal_v1.rs +++ b/src/contract/consensus/src/client/proposal_v1.rs @@ -115,6 +115,8 @@ pub struct ConsensusProposalCallBuilder { pub coin: ConsensusOwnCoin, /// Rewarded slot checkpoint pub slot_checkpoint: SlotCheckpoint, + /// Extending fork last proposal/block hash + pub previous_hash: blake3::Hash, /// Merkle tree of coins used to create inclusion proofs pub tree: MerkleTree, /// `Proposal_V1` zkas circuit ZkBinary @@ -162,6 +164,7 @@ impl ConsensusProposalCallBuilder { &input, &output, &self.slot_checkpoint, + self.previous_hash, )?; let input = ConsensusInput { @@ -205,6 +208,7 @@ impl ConsensusProposalCallBuilder { reward_blind, new_serial_commit, slot, + previous_hash: self.previous_hash, vrf_proof, y, rho, @@ -225,6 +229,7 @@ pub fn create_proposal_proof( input: &ConsensusBurnInputInfo, output: &ConsensusMintOutputInfo, slot_checkpoint: &SlotCheckpoint, + previous_hash: blake3::Hash, ) -> Result<(Proof, ConsensusProposalRevealed)> { // Proof parameters let nullifier = Nullifier::from(poseidon_hash([input.secret.inner(), input.note.serial])); @@ -278,8 +283,9 @@ pub fn create_proposal_proof( let slot_pallas = pallas::Base::from(slot_checkpoint.slot); let seed = poseidon_hash([SEED_PREFIX, input.note.serial]); - let mut vrf_input = Vec::with_capacity(32 + 32); + let mut vrf_input = Vec::with_capacity(32 + blake3::OUT_LEN + 32); vrf_input.extend_from_slice(&slot_checkpoint.previous_eta.to_repr()); + vrf_input.extend_from_slice(previous_hash.as_bytes()); vrf_input.extend_from_slice(&slot_pallas.to_repr()); let vrf_proof = VrfProof::prove(input.secret, &vrf_input, &mut OsRng); let mut eta = [0u8; 64]; diff --git a/src/contract/consensus/src/entrypoint/proposal_v1.rs b/src/contract/consensus/src/entrypoint/proposal_v1.rs index 90ee67eed..a4eb577d1 100644 --- a/src/contract/consensus/src/entrypoint/proposal_v1.rs +++ b/src/contract/consensus/src/entrypoint/proposal_v1.rs @@ -95,10 +95,17 @@ pub(crate) fn consensus_proposal_get_metadata_v1( }; let slot_checkpoint: SlotCheckpoint = deserialize(&slot_checkpoint)?; + // Verify proposal extends a known fork + if !slot_checkpoint.fork_hashes.contains(¶ms.previous_hash) { + msg!("[ConsensusProposalV1] Error: Proposal extends unknown fork {}", params.previous_hash); + return Err(ConsensusError::ProposalExtendsUnknownFork.into()) + } + // Verify eta VRF proof let slot_pallas = pallas::Base::from(slot_checkpoint.slot); - let mut vrf_input = Vec::with_capacity(32 + 32); + let mut vrf_input = Vec::with_capacity(32 + blake3::OUT_LEN + 32); vrf_input.extend_from_slice(&slot_checkpoint.previous_eta.to_repr()); + vrf_input.extend_from_slice(params.previous_hash.as_bytes()); vrf_input.extend_from_slice(&slot_pallas.to_repr()); let vrf_proof = ¶ms.vrf_proof; if !vrf_proof.verify(params.input.signature_public, &vrf_input) { diff --git a/src/contract/consensus/src/error.rs b/src/contract/consensus/src/error.rs index 15c55eac2..f2e96e8d4 100644 --- a/src/contract/consensus/src/error.rs +++ b/src/contract/consensus/src/error.rs @@ -23,6 +23,9 @@ pub enum ConsensusError { #[error("Missing slot checkpoint from db")] ProposalMissingSlotCheckpoint, + #[error("Proposal extends unknown fork")] + ProposalExtendsUnknownFork, + #[error("Eta VRF proof couldn't be verified")] ProposalErroneousVrfProof, @@ -37,9 +40,10 @@ impl From for ContractError { fn from(e: ConsensusError) -> Self { match e { ConsensusError::ProposalMissingSlotCheckpoint => Self::Custom(1), - ConsensusError::ProposalErroneousVrfProof => Self::Custom(2), - ConsensusError::CoinStillInGracePeriod => Self::Custom(3), - ConsensusError::CoinNotInUnstakeSet => Self::Custom(4), + ConsensusError::ProposalExtendsUnknownFork => Self::Custom(2), + ConsensusError::ProposalErroneousVrfProof => Self::Custom(3), + ConsensusError::CoinStillInGracePeriod => Self::Custom(4), + ConsensusError::CoinNotInUnstakeSet => Self::Custom(5), } } } diff --git a/src/contract/consensus/src/model.rs b/src/contract/consensus/src/model.rs index bcfc67a31..3e958c3f7 100644 --- a/src/contract/consensus/src/model.rs +++ b/src/contract/consensus/src/model.rs @@ -49,6 +49,8 @@ pub struct ConsensusProposalParamsV1 { pub new_serial_commit: pallas::Point, /// Rewarded slot pub slot: u64, + /// Extending fork last proposal/block hash + pub previous_hash: blake3::Hash, /// VRF proof for eta calculation pub vrf_proof: VrfProof, /// Coin y diff --git a/src/contract/consensus/tests/genesis_stake_unstake.rs b/src/contract/consensus/tests/genesis_stake_unstake.rs index 84e360203..4008de8f7 100644 --- a/src/contract/consensus/tests/genesis_stake_unstake.rs +++ b/src/contract/consensus/tests/genesis_stake_unstake.rs @@ -112,7 +112,9 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> { assert!(ALICE_INITIAL == alice_staked_oc.note.value); // We simulate the proposal of genesis slot - let slot_checkpoint = th.get_slot_checkpoint_by_slot(current_slot).await?; + // We progress 1 slot and simulate its proposal + current_slot += 1; + let slot_checkpoint = th.generate_slot_checkpoint(current_slot).await?; // With alice's current coin value she can become the slot proposer, // so she creates a proposal transaction to burn her staked coin, @@ -121,7 +123,7 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> { info!(target: "consensus", "[Alice] Building proposal tx"); info!(target: "consensus", "[Alice] ===================="); let (proposal_tx, proposal_params, proposal_secret_key) = - th.proposal(Holder::Alice, slot_checkpoint, alice_staked_oc.clone())?; + th.proposal(Holder::Alice, slot_checkpoint, alice_staked_oc.clone()).await?; info!(target: "consensus", "[Faucet] ==========================="); info!(target: "consensus", "[Faucet] Executing Alice proposal tx"); diff --git a/src/contract/consensus/tests/harness.rs b/src/contract/consensus/tests/harness.rs index 9a1cc7221..5fa4ec52a 100644 --- a/src/contract/consensus/tests/harness.rs +++ b/src/contract/consensus/tests/harness.rs @@ -511,7 +511,7 @@ impl ConsensusTestHarness { Ok(()) } - pub fn proposal( + pub async fn proposal( &mut self, holder: Holder, slot_checkpoint: SlotCheckpoint, @@ -523,10 +523,14 @@ impl ConsensusTestHarness { let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::Proposal).unwrap(); let timer = Instant::now(); + // Proposals always extend genesis block + let previous_hash = wallet.state.read().await.consensus.genesis_block; + // Building Consensus::Unstake params let proposal_call_debris = ConsensusProposalCallBuilder { coin: staked_oc, slot_checkpoint, + previous_hash, tree: wallet.consensus_merkle_tree.clone(), proposal_zkbin: proposal_zkbin.clone(), proposal_pk: proposal_pk.clone(), diff --git a/src/contract/consensus/tests/stake_unstake.rs b/src/contract/consensus/tests/stake_unstake.rs index f323d9777..df60f1fc6 100644 --- a/src/contract/consensus/tests/stake_unstake.rs +++ b/src/contract/consensus/tests/stake_unstake.rs @@ -102,7 +102,7 @@ async fn consensus_contract_stake_unstake() -> Result<()> { info!(target: "consensus", "[Malicious] Checking proposal before grace period"); info!(target: "consensus", "[Malicious] ====================================="); let (proposal_tx, _, _) = - th.proposal(Holder::Alice, slot_checkpoint, alice_staked_oc.clone())?; + th.proposal(Holder::Alice, slot_checkpoint, alice_staked_oc.clone()).await?; th.execute_erroneous_proposal_txs(Holder::Alice, vec![proposal_tx], current_slot, 1).await?; // We progress after grace period @@ -116,7 +116,7 @@ async fn consensus_contract_stake_unstake() -> Result<()> { info!(target: "consensus", "[Alice] Building proposal tx"); info!(target: "consensus", "[Alice] ===================="); let (proposal_tx, proposal_params, proposal_secret_key) = - th.proposal(Holder::Alice, slot_checkpoint, alice_staked_oc.clone())?; + th.proposal(Holder::Alice, slot_checkpoint, alice_staked_oc.clone()).await?; info!(target: "consensus", "[Faucet] ==========================="); info!(target: "consensus", "[Faucet] Executing Alice proposal tx"); @@ -194,7 +194,7 @@ async fn consensus_contract_stake_unstake() -> Result<()> { info!(target: "consensus", "[Malicious] Checking using unstaked coin in proposal"); info!(target: "consensus", "[Malicious] ========================================"); let (proposal_tx, _, _) = - th.proposal(Holder::Alice, slot_checkpoint, alice_unstake_request_oc.clone())?; + th.proposal(Holder::Alice, slot_checkpoint, alice_unstake_request_oc.clone()).await?; th.execute_erroneous_proposal_txs(Holder::Alice, vec![proposal_tx], current_slot, 1).await?; info!(target: "consensus", "[Malicious] =============================");