diff --git a/Cargo.lock b/Cargo.lock index 14babf3189..6f6a0e326a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5071,7 +5071,9 @@ dependencies = [ "rand_core 0.6.4", "regex", "serde", + "tap", "tendermint", + "thiserror", "tokio", "tonic", "tracing", diff --git a/crates/core/component/governance/Cargo.toml b/crates/core/component/governance/Cargo.toml index ae2c44dc7e..ef2f3b8fa9 100644 --- a/crates/core/component/governance/Cargo.toml +++ b/crates/core/component/governance/Cargo.toml @@ -69,7 +69,9 @@ rand_chacha = {workspace = true} rand_core = {workspace = true, features = ["getrandom"]} regex = {workspace = true} serde = {workspace = true, features = ["derive"]} +tap = {workspace = true} tendermint = {workspace = true} +thiserror = {workspace = true} tokio = {workspace = true, features = ["full", "tracing"], optional = true} tonic = {workspace = true, optional = true} tracing = {workspace = true} diff --git a/crates/core/component/governance/src/delegator_vote/proof.rs b/crates/core/component/governance/src/delegator_vote/proof.rs index 9fc1a670e0..aa1ae6f0fa 100644 --- a/crates/core/component/governance/src/delegator_vote/proof.rs +++ b/crates/core/component/governance/src/delegator_vote/proof.rs @@ -1,33 +1,37 @@ -use base64::{engine::general_purpose, Engine as _}; -use std::str::FromStr; - use anyhow::Result; -use ark_groth16::r1cs_to_qap::LibsnarkReduction; -use ark_r1cs_std::{prelude::*, uint8::UInt8}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use decaf377::r1cs::ElementVar; -use decaf377::FieldExt; -use decaf377::{r1cs::FqVar, Bls12_377, Fq, Fr}; - use ark_ff::ToConstraintField; -use ark_groth16::{Groth16, PreparedVerifyingKey, Proof, ProvingKey}; -use ark_r1cs_std::prelude::AllocVar; +use ark_groth16::{ + r1cs_to_qap::LibsnarkReduction, Groth16, PreparedVerifyingKey, Proof, ProvingKey, +}; +use ark_r1cs_std::{prelude::*, uint8::UInt8}; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_snark::SNARK; +use base64::{engine::general_purpose, Engine as _}; +use decaf377::{ + r1cs::{ElementVar, FqVar}, + Bls12_377, FieldExt, Fq, Fr, +}; use decaf377_rdsa::{SpendAuth, VerificationKey}; -use penumbra_proto::{core::component::governance::v1 as pb, DomainType}; -use penumbra_tct as tct; -use penumbra_tct::r1cs::StateCommitmentVar; -use tct::r1cs::PositionVar; - -use penumbra_asset::{balance, balance::commitment::BalanceCommitmentVar, Value}; +use penumbra_asset::{ + balance::{self, commitment::BalanceCommitmentVar, Commitment}, + Value, +}; use penumbra_keys::keys::{ AuthorizationKeyVar, Bip44Path, IncomingViewingKeyVar, NullifierKey, NullifierKeyVar, RandomizedVerificationKey, SeedPhrase, SpendAuthRandomizerVar, SpendKey, }; use penumbra_proof_params::{DummyWitness, VerifyingKeyExt, GROTH16_PROOF_LENGTH_BYTES}; +use penumbra_proto::{core::component::governance::v1 as pb, DomainType}; use penumbra_sct::{Nullifier, NullifierVar}; use penumbra_shielded_pool::{note, Note, Rseed}; +use penumbra_tct::{ + self as tct, + r1cs::{PositionVar, StateCommitmentVar}, + Root, +}; +use std::str::FromStr; +use tap::Tap; /// The public input for a [`DelegatorVoteProof`]. #[derive(Clone, Debug)] @@ -294,6 +298,28 @@ impl DummyWitness for DelegatorVoteCircuit { } } +#[derive(Debug, thiserror::Error)] +pub enum VerificationError { + #[error("error deserializing compressed proof: {0:?}")] + ProofDeserialize(ark_serialize::SerializationError), + #[error("Fq types are Bls12-377 field members")] + Anchor, + #[error("balance commitment is a Bls12-377 field member")] + BalanceCommitment, + #[error("nullifier is a Bls12-377 field member")] + Nullifier, + #[error("could not decompress element points: {0:?}")] + DecompressRk(decaf377::EncodingError), + #[error("randomized spend key is a Bls12-377 field member")] + Rk, + #[error("start position is a Bls12-377 field member")] + StartPosition, + #[error("error verifying proof: {0:?}")] + SynthesisError(ark_relations::r1cs::SynthesisError), + #[error("delegator vote proof did not verify")] + InvalidProof, +} + #[derive(Clone, Debug, Copy)] pub struct DelegatorVoteProof([u8; GROTH16_PROOF_LENGTH_BYTES]); @@ -318,58 +344,61 @@ impl DelegatorVoteProof { /// Called to verify the proof using the provided public inputs. // For debugging proof verification failures, // to check that the proof data and verification keys are consistent. - #[tracing::instrument(level="debug", skip(self, vk), fields(self = ?general_purpose::STANDARD.encode(self.clone().encode_to_vec()), vk = ?vk.debug_id()))] + #[tracing::instrument( + level="debug", + skip(self, vk), + fields( + self = ?general_purpose::STANDARD.encode(self.clone().encode_to_vec()), + vk = ?vk.debug_id() + ) + )] pub fn verify( &self, vk: &PreparedVerifyingKey, - public: DelegatorVoteProofPublic, - ) -> anyhow::Result<()> { - let proof = - Proof::deserialize_compressed_unchecked(&self.0[..]).map_err(|e| anyhow::anyhow!(e))?; - - let mut public_inputs = Vec::new(); - public_inputs.extend( - Fq::from(public.anchor.0) - .to_field_elements() - .expect("valid field element"), - ); - public_inputs.extend( - public - .balance_commitment - .0 - .to_field_elements() - .expect("valid field element"), - ); - public_inputs.extend( - public - .nullifier - .0 - .to_field_elements() - .expect("valid field element"), - ); - let element_rk = decaf377::Encoding(public.rk.to_bytes()) + DelegatorVoteProofPublic { + anchor: Root(anchor), + balance_commitment: Commitment(balance_commitment), + nullifier: Nullifier(nullifier), + rk, + start_position, + }: DelegatorVoteProofPublic, + ) -> Result<(), VerificationError> { + let proof = Proof::deserialize_compressed_unchecked(&self.0[..]) + .map_err(VerificationError::ProofDeserialize)?; + let element_rk = decaf377::Encoding(rk.to_bytes()) .vartime_decompress() - .expect("expect only valid element points"); - public_inputs.extend(element_rk.to_field_elements().expect("valid field element")); - public_inputs.extend( - public - .start_position - .to_field_elements() - .expect("valid field element"), - ); - - tracing::trace!(?public_inputs); + .map_err(VerificationError::DecompressRk)?; + + /// Shorthand helper, convert expressions into field elements. + macro_rules! to_field_elements { + ($fe:expr, $err:expr) => { + $fe.to_field_elements().ok_or($err)? + }; + } + + use VerificationError::*; + let public_inputs = [ + to_field_elements!(Fq::from(anchor), Anchor), + to_field_elements!(balance_commitment, BalanceCommitment), + to_field_elements!(nullifier, Nullifier), + to_field_elements!(element_rk, Rk), + to_field_elements!(start_position, StartPosition), + ] + .into_iter() + .flatten() + .collect::>() + .tap(|public_inputs| tracing::trace!(?public_inputs)); + let start = std::time::Instant::now(); - let proof_result = Groth16::::verify_with_processed_vk( + Groth16::::verify_with_processed_vk( vk, public_inputs.as_slice(), &proof, ) - .map_err(|err| anyhow::anyhow!(err))?; - tracing::debug!(?proof_result, elapsed = ?start.elapsed()); - proof_result - .then_some(()) - .ok_or_else(|| anyhow::anyhow!("delegator vote proof did not verify")) + .map_err(VerificationError::SynthesisError)? + .tap(|proof_result| tracing::debug!(?proof_result, elapsed = ?start.elapsed())) + .then_some(()) + .ok_or(VerificationError::InvalidProof) } }