Skip to content

Commit

Permalink
governance: 🎾 DelegatorVoteProof::verify is fallible (#3833)
Browse files Browse the repository at this point in the history
see #3777.

this changes `DelegatorVoteProof::verify` so that it is fallible. errors
will be propagated to the caller, rather than inducing a panic.

* #3777
* #3829

as with #3829, i've applied some tlc to the code as i've addressed the
core issue at hand (_panics_). noop code transformations are applied in
distinct commits, to facilitate ease of review.
  • Loading branch information
cratelyn authored Feb 16, 2024
1 parent 1da81f9 commit b62446a
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 61 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/core/component/governance/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
151 changes: 90 additions & 61 deletions crates/core/component/governance/src/delegator_vote/proof.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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]);

Expand All @@ -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<Bls12_377>,
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::<Vec<_>>()
.tap(|public_inputs| tracing::trace!(?public_inputs));

let start = std::time::Instant::now();
let proof_result = Groth16::<Bls12_377, LibsnarkReduction>::verify_with_processed_vk(
Groth16::<Bls12_377, LibsnarkReduction>::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)
}
}

Expand Down

0 comments on commit b62446a

Please sign in to comment.