From 030cbb48c9051f41e900f0ab138088f7d3558fcc Mon Sep 17 00:00:00 2001 From: aggstam Date: Tue, 3 Oct 2023 16:25:29 +0300 Subject: [PATCH] validator/verification: validate producer signature using transaction public key --- src/validator/verification.rs | 67 ++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/src/validator/verification.rs b/src/validator/verification.rs index f83f16481..6df1009a5 100644 --- a/src/validator/verification.rs +++ b/src/validator/verification.rs @@ -19,7 +19,7 @@ use std::{collections::HashMap, io::Cursor}; use darkfi_sdk::{ - crypto::{PublicKey, CONSENSUS_CONTRACT_ID, MONEY_CONTRACT_ID}, + crypto::{schnorr::SchnorrPublic, PublicKey, CONSENSUS_CONTRACT_ID, MONEY_CONTRACT_ID}, pasta::pallas, }; use darkfi_serial::{Decodable, Encodable, WriteExt}; @@ -139,8 +139,8 @@ pub async fn verify_block( // Validate proposal transaction if not in testing mode if !testing_mode { let tx = block.txs.last().unwrap(); - verify_producer_transaction(overlay, time_keeper, tx).await?; - verify_producer_signature(block)?; + let public_key = verify_producer_transaction(overlay, time_keeper, tx).await?; + verify_producer_signature(block, &public_key)?; } // Verify transactions, exluding producer(last) one @@ -160,24 +160,25 @@ pub async fn verify_block( } /// Validate block proposer signature, using the proposal transaction signature as signing key -/// over blocks header, transactions and slots. -pub fn verify_producer_signature(_block: &BlockInfo) -> Result<()> { - // TODO: - // Grab public key from proposal transaction metadata on verify_proposal_transaction - // and pass it here to verify the signature +/// over blocks header hash. +pub fn verify_producer_signature(block: &BlockInfo, public_key: &PublicKey) -> Result<()> { + if !public_key.verify(&block.header.hash()?.as_bytes()[..], &block.signature) { + warn!(target: "validator::verification::verify_producer_signature", "Proposer {} signature could not be verified", public_key); + return Err(Error::InvalidSignature) + } Ok(()) } /// Validate WASM execution, signatures, and ZK proofs for a given producer [`Transaction`], -/// and apply it to the provided overlay. +/// and apply it to the provided overlay. Returns transaction signature public key. pub async fn verify_producer_transaction( overlay: &BlockchainOverlayPtr, time_keeper: &TimeKeeper, tx: &Transaction, -) -> Result<()> { +) -> Result { let tx_hash = tx.hash()?; - debug!(target: "validator::verification::verify_proposal_transaction", "Validating proposal transaction {}", tx_hash); + debug!(target: "validator::verification::verify_producer_transaction", "Validating proposal transaction {}", tx_hash); // Transaction must contain a single call, either Money::PoWReward(0x08) or Consensus::Proposal(0x02) if tx.calls.len() != 1 || @@ -186,7 +187,7 @@ pub async fn verify_producer_transaction( (tx.calls[0].contract_id == *MONEY_CONTRACT_ID && tx.calls[0].data[0] != 0x08) || (tx.calls[0].contract_id == *CONSENSUS_CONTRACT_ID && tx.calls[0].data[0] != 0x02) { - error!(target: "validator::verification::verify_proposal_transaction", "Proposal transaction is malformed"); + error!(target: "validator::verification::verify_producer_transaction", "Proposal transaction is malformed"); return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into()) } @@ -203,19 +204,19 @@ pub async fn verify_producer_transaction( // Table of public keys used for signature verification let mut sig_table = vec![]; - debug!(target: "validator::verification::verify_proposal_transaction", "Executing contract call"); + debug!(target: "validator::verification::verify_producer_transaction", "Executing contract call"); // Write the actual payload data let mut payload = vec![]; payload.write_u32(0)?; // Call index tx.calls.encode(&mut payload)?; // Actual call data - debug!(target: "validator::verification::verify_proposal_transaction", "Instantiating WASM runtime"); + debug!(target: "validator::verification::verify_producer_transaction", "Instantiating WASM runtime"); let wasm = overlay.lock().unwrap().wasm_bincode.get(call.contract_id)?; let mut runtime = Runtime::new(&wasm, overlay.clone(), call.contract_id, time_keeper.clone())?; - debug!(target: "validator::verification::verify_proposal_transaction", "Executing \"metadata\" call"); + debug!(target: "validator::verification::verify_producer_transaction", "Executing \"metadata\" call"); let metadata = runtime.metadata(&payload)?; // Decode the metadata retrieved from the execution @@ -224,11 +225,18 @@ pub async fn verify_producer_transaction( // The tuple is (zkas_ns, public_inputs) let zkp_pub: Vec<(String, Vec)> = Decodable::decode(&mut decoder)?; let sig_pub: Vec = Decodable::decode(&mut decoder)?; + + // Check that only one ZK proof and signature public key exist + if zkp_pub.len() != 1 || sig_pub.len() != 1 { + error!(target: "validator::verification::verify_producer_transaction", "Proposal contains multiple ZK proofs or signature public keys"); + return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into()) + } + // TODO: Make sure we've read all the bytes above. - debug!(target: "validator::verification::verify_proposal_transaction", "Successfully executed \"metadata\" call"); + debug!(target: "validator::verification::verify_producer_transaction", "Successfully executed \"metadata\" call"); // Here we'll look up verifying keys and insert them into the map. - debug!(target: "validator::verification::verify_proposal_transaction", "Performing VerifyingKey lookups from the sled db"); + debug!(target: "validator::verification::verify_producer_transaction", "Performing VerifyingKey lookups from the sled db"); for (zkas_ns, _) in &zkp_pub { // TODO: verify this is correct behavior let inner_vk_map = verifying_keys.get_mut(&call.contract_id.to_bytes()).unwrap(); @@ -240,46 +248,47 @@ pub async fn verify_producer_transaction( } zkp_table.push(zkp_pub); + let signature_public_key = *sig_pub.last().unwrap(); sig_table.push(sig_pub); // After getting the metadata, we run the "exec" function with the same runtime // and the same payload. - debug!(target: "validator::verification::verify_proposal_transaction", "Executing \"exec\" call"); + debug!(target: "validator::verification::verify_producer_transaction", "Executing \"exec\" call"); let state_update = runtime.exec(&payload)?; - debug!(target: "validator::verification::verify_proposal_transaction", "Successfully executed \"exec\" call"); + debug!(target: "validator::verification::verify_producer_transaction", "Successfully executed \"exec\" call"); // If that was successful, we apply the state update in the ephemeral overlay. - debug!(target: "validator::verification::verify_proposal_transaction", "Executing \"apply\" call"); + debug!(target: "validator::verification::verify_producer_transaction", "Executing \"apply\" call"); runtime.apply(&state_update)?; - debug!(target: "validator::verification::verify_proposal_transaction", "Successfully executed \"apply\" call"); + debug!(target: "validator::verification::verify_producer_transaction", "Successfully executed \"apply\" call"); // When we're done executing over the tx's contract call, we now move on with verification. // First we verify the signatures as that's cheaper, and then finally we verify the ZK proofs. - debug!(target: "validator::verification::verify_proposal_transaction", "Verifying signatures for transaction {}", tx_hash); + debug!(target: "validator::verification::verify_producer_transaction", "Verifying signatures for transaction {}", tx_hash); if sig_table.len() != tx.signatures.len() { - error!(target: "validator::verification::verify_proposal_transaction", "Incorrect number of signatures in tx {}", tx_hash); + error!(target: "validator::verification::verify_producer_transaction", "Incorrect number of signatures in tx {}", tx_hash); return Err(TxVerifyFailed::MissingSignatures.into()) } // TODO: Go through the ZK circuits that have to be verified and account for the opcodes. if let Err(e) = tx.verify_sigs(sig_table) { - error!(target: "validator::verification::verify_proposal_transaction", "Signature verification for tx {} failed: {}", tx_hash, e); + error!(target: "validator::verification::verify_producer_transaction", "Signature verification for tx {} failed: {}", tx_hash, e); return Err(TxVerifyFailed::InvalidSignature.into()) } - debug!(target: "validator::verification::verify_proposal_transaction", "Signature verification successful"); + debug!(target: "validator::verification::verify_producer_transaction", "Signature verification successful"); - debug!(target: "validator::verification::verify_proposal_transaction", "Verifying ZK proofs for transaction {}", tx_hash); + debug!(target: "validator::verification::verify_producer_transaction", "Verifying ZK proofs for transaction {}", tx_hash); if let Err(e) = tx.verify_zkps(&verifying_keys, zkp_table).await { error!(target: "validator::verification::verify_proposal_transaction", "ZK proof verification for tx {} failed: {}", tx_hash, e); return Err(TxVerifyFailed::InvalidZkProof.into()) } - debug!(target: "validator::verification::verify_proposal_transaction", "ZK proof verification successful"); - debug!(target: "validator::verification::verify_proposal_transaction", "Proposal transaction {} verified successfully", tx_hash); + debug!(target: "validator::verification::verify_producer_transaction", "ZK proof verification successful"); + debug!(target: "validator::verification::verify_producer_transaction", "Proposal transaction {} verified successfully", tx_hash); - Ok(()) + Ok(signature_public_key) } /// Validate WASM execution, signatures, and ZK proofs for a given [`Transaction`],