migrate from anyhow to thiserror (#197)

* migrate from anyhow to thiserror (#190). pending polish error msgs

* Add backtrace and compartmentalize errors

- Include backtraces in the errors we generate.  To get this we can't
  just return a literal enum, because the backtrace requires a call.
- Related to the previous point: add methods to create errors so
  we can include the backtrace conveniently without changing too much
  the syntax.  So instead of `Err(Error::KeyNotFound(key))` (literal
  enum) it will be `Err(Error::key_not_found(key))` (method call)
- Each error should be local to its scope, and each scope should
  only return its own error.
  - The merkle tree should return `TreeError` and not Error
  - The middleware should return `MiddlewareError` and not Error
- With a global Error we can't easily include backend/frontend types in
  the error fields, so declare a `BackendError` and a `FrontendError`
  and follow the pattern from the previous point
- The Pod traits should be able to return backend errors and will be
  used in the frontend; for that we change them to use trait object
  Error: `dyn std::error::Error`

* fix error

* apply suggestions from @arnaucube

* rename XError and XResult to Error and Result

* reorg signature

* make frontend custom error more ergonomic

* remove unnecessary feature

---------

Co-authored-by: Eduard S. <eduardsanou@posteo.net>
This commit is contained in:
arnaucube 2025-04-22 15:07:04 +02:00 committed by GitHub
parent 58d3c6a236
commit 29545f03fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 696 additions and 273 deletions

View file

@ -2,7 +2,6 @@
use std::{array, iter};
use anyhow::Result;
use plonky2::{
field::{
extension::Extendable,
@ -19,6 +18,7 @@ use plonky2::{
use crate::{
backends::plonky2::{
basetypes::D,
error::Result,
mainpod::{Operation, OperationArg, Statement},
primitives::merkletree::MerkleClaimAndProofTarget,
},
@ -75,7 +75,7 @@ impl StatementArgTarget {
params: &Params,
arg: &StatementArg,
) -> Result<()> {
pw.set_target_arr(&self.elements, &arg.to_fields(params))
Ok(pw.set_target_arr(&self.elements, &arg.to_fields(params))?)
}
fn new(first: ValueTarget, second: ValueTarget) -> Self {

View file

@ -1,4 +1,3 @@
use anyhow::Result;
use itertools::zip_eq;
use plonky2::{
hash::{hash_types::HashOutTarget, poseidon::PoseidonHash},
@ -16,6 +15,7 @@ use crate::{
},
signedpod::{SignedPodVerifyGadget, SignedPodVerifyTarget},
},
error::Result,
mainpod,
primitives::merkletree::{
MerkleClaimAndProof, MerkleClaimAndProofTarget, MerkleProofGadget,

View file

@ -1,6 +1,5 @@
use std::iter;
use anyhow::Result;
use itertools::Itertools;
use plonky2::{
hash::hash_types::{HashOut, HashOutTarget},
@ -15,6 +14,7 @@ use crate::{
backends::plonky2::{
basetypes::D,
circuits::common::{CircuitBuilderPod, StatementArgTarget, StatementTarget, ValueTarget},
error::Result,
primitives::{
merkletree::{
MerkleClaimAndProof, MerkleProofExistenceGadget, MerkleProofExistenceTarget,

View file

@ -0,0 +1,93 @@
use std::{backtrace::Backtrace, fmt::Debug};
use crate::middleware::{PodId, PodType, Value};
pub type Result<T, E = Error> = core::result::Result<T, E>;
#[derive(thiserror::Error, Debug)]
pub enum InnerError {
#[error("id does not match, expected {0}, found {1}")]
IdNotEqual(PodId, PodId),
#[error("type does not match, expected {0}, found {1}")]
TypeNotEqual(PodType, Value),
// POD related
#[error("invalid POD ID")]
PodIdInvalid,
#[error("verification failed: POD does not have type statement")]
NotTypeStatement,
#[error("repeated ValueOf")]
RepeatedValueOf,
#[error("Statement did not check")]
StatementNotCheck,
#[error("Key not found")]
KeyNotFound,
// Other
#[error("{0}")]
Custom(String),
}
#[derive(thiserror::Error)]
pub enum Error {
#[error("Inner: {inner}\n{backtrace}")]
Inner {
inner: Box<InnerError>,
backtrace: Box<Backtrace>,
},
#[error("anyhow::Error: {0}")]
Anyhow(#[from] anyhow::Error),
#[error("Plonky2 proof failed to verify: {0}")]
Plonky2ProofFail(anyhow::Error),
#[error("base64::DecodeError: {0}")]
Base64Decode(#[from] base64::DecodeError),
#[error(transparent)]
Tree(#[from] crate::backends::plonky2::primitives::merkletree::error::TreeError),
#[error(transparent)]
Middleware(#[from] crate::middleware::Error),
}
impl Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
macro_rules! new {
($inner:expr) => {
Error::Inner {
inner: Box::new($inner),
backtrace: Box::new(Backtrace::capture()),
}
};
}
use InnerError::*;
impl Error {
pub(crate) fn custom(s: String) -> Self {
new!(Custom(s))
}
pub(crate) fn plonky2_proof_fail(e: anyhow::Error) -> Self {
Self::Plonky2ProofFail(e)
}
pub(crate) fn key_not_found() -> Self {
new!(KeyNotFound)
}
pub(crate) fn statement_not_check() -> Self {
new!(StatementNotCheck)
}
pub(crate) fn repeated_value_of() -> Self {
new!(RepeatedValueOf)
}
pub(crate) fn not_type_statement() -> Self {
new!(NotTypeStatement)
}
pub(crate) fn pod_id_invalid() -> Self {
new!(PodIdInvalid)
}
pub(crate) fn id_not_equal(expected: PodId, found: PodId) -> Self {
new!(IdNotEqual(expected, found))
}
pub(crate) fn type_not_equal(expected: PodType, found: Value) -> Self {
new!(TypeNotEqual(expected, found))
}
}

View file

@ -2,7 +2,6 @@ pub mod operation;
pub mod statement;
use std::any::Any;
use anyhow::{anyhow, Result};
use itertools::Itertools;
pub use operation::*;
use plonky2::{
@ -19,12 +18,13 @@ use crate::{
backends::plonky2::{
basetypes::{C, D},
circuits::mainpod::{MainPodVerifyCircuit, MainPodVerifyInput},
error::{Error, Result},
primitives::merkletree::MerkleClaimAndProof,
signedpod::SignedPod,
},
middleware::{
self, AnchoredKey, Hash, MainPodInputs, NativeOperation, NonePod, OperationType, Params,
Pod, PodId, PodProver, PodType, StatementArg, ToFields, F, KEY_TYPE, SELF,
self, AnchoredKey, DynError, Hash, MainPodInputs, NativeOperation, NonePod, OperationType,
Params, Pod, PodId, PodProver, PodType, StatementArg, ToFields, F, KEY_TYPE, SELF,
},
};
@ -70,11 +70,11 @@ pub(crate) fn extract_merkle_proofs(
})
.collect();
if merkle_proofs.len() > params.max_merkle_proofs {
return Err(anyhow!(
return Err(Error::custom(format!(
"The number of required Merkle proofs ({}) exceeds the maximum number ({}).",
merkle_proofs.len(),
params.max_merkle_proofs
));
)));
}
Ok(merkle_proofs)
}
@ -90,10 +90,10 @@ fn find_op_arg(statements: &[Statement], op_arg: &middleware::Statement) -> Resu
(&middleware::Statement::try_from(s.clone()).ok()? == op_arg).then_some(i)
})
.map(OperationArg::Index)
.ok_or(anyhow!(
.ok_or(Error::custom(format!(
"Statement corresponding to op arg {} not found",
op_arg
)),
))),
}
}
@ -109,10 +109,10 @@ fn find_op_aux(
.enumerate()
.find_map(|(i, pf)| (pf.proof == *pf_arg).then_some(i))
.map(OperationAux::MerkleProofIndex)
.ok_or(anyhow!(
.ok_or(Error::custom(format!(
"Merkle proof corresponding to op arg {} not found",
op_aux
)),
))),
}
}
@ -279,8 +279,8 @@ pub(crate) fn process_public_statements_operations(
pub struct Prover {}
impl PodProver for Prover {
fn prove(&mut self, params: &Params, inputs: MainPodInputs) -> Result<Box<dyn Pod>> {
impl Prover {
fn _prove(&mut self, params: &Params, inputs: MainPodInputs) -> Result<MainPod> {
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let main_pod = MainPodVerifyCircuit {
@ -328,12 +328,22 @@ impl PodProver for Prover {
let data = builder.build::<C>();
let proof = data.prove(pw)?;
Ok(Box::new(MainPod {
Ok(MainPod {
params: params.clone(),
id,
public_statements,
proof,
}))
})
}
}
impl PodProver for Prover {
fn prove(
&mut self,
params: &Params,
inputs: MainPodInputs,
) -> Result<Box<dyn Pod>, Box<DynError>> {
Ok(self._prove(params, inputs).map(Box::new)?)
}
}
@ -364,16 +374,12 @@ pub(crate) fn normalize_statement(statement: &Statement, self_id: PodId) -> midd
.unwrap()
}
impl Pod for MainPod {
fn verify(&self) -> Result<()> {
impl MainPod {
fn _verify(&self) -> Result<()> {
// 2. get the id out of the public statements
let id: PodId = PodId(hash_statements(&self.public_statements, &self.params));
if id != self.id {
return Err(anyhow!(
"id does not match, expected {}, computed {}",
self.id,
id
));
return Err(Error::id_not_equal(self.id, id));
}
// 1, 3, 4, 5 verification via the zkSNARK proof
@ -387,7 +393,13 @@ impl Pod for MainPod {
let data = builder.build::<C>();
data.verify(self.proof.clone())
.map_err(|e| anyhow!("MainPod proof verification failure: {:?}", e))
.map_err(|e| Error::custom(format!("MainPod proof verification failure: {:?}", e)))
}
}
impl Pod for MainPod {
fn verify(&self) -> Result<(), Box<DynError>> {
Ok(self._verify()?)
}
fn id(&self) -> PodId {
@ -418,13 +430,14 @@ pub mod tests {
signedpod::Signer,
},
examples::{zu_kyc_pod_builder, zu_kyc_sign_pod_builders},
frontend, middleware,
frontend::{self},
middleware,
middleware::RawValue,
op,
};
#[test]
fn test_main_zu_kyc() -> Result<()> {
fn test_main_zu_kyc() -> frontend::Result<()> {
let params = middleware::Params {
// Currently the circuit uses random access that only supports vectors of length 64.
// With max_input_main_pods=3 we need random access to a vector of length 73.
@ -447,7 +460,7 @@ pub mod tests {
let kyc_pod = kyc_builder.prove(&mut prover, &params)?;
let pod = (kyc_pod.pod as Box<dyn Any>).downcast::<MainPod>().unwrap();
pod.verify()
Ok(pod.verify()?)
}
#[test]

View file

@ -1,11 +1,14 @@
use std::fmt;
use anyhow::{anyhow, Result};
use plonky2::field::types::Field;
use serde::{Deserialize, Serialize};
use crate::{
backends::plonky2::{mainpod::Statement, primitives::merkletree::MerkleClaimAndProof},
backends::plonky2::{
error::{Error, Result},
mainpod::Statement,
primitives::merkletree::MerkleClaimAndProof,
},
middleware::{self, OperationType, Params, ToFields, F},
};
@ -78,12 +81,16 @@ impl Operation {
OperationAux::MerkleProofIndex(i) => crate::middleware::OperationAux::MerkleProof(
merkle_proofs
.get(i)
.ok_or(anyhow!("Missing Merkle proof index {}", i))?
.ok_or(Error::custom(format!("Missing Merkle proof index {}", i)))?
.proof
.clone(),
),
};
middleware::Operation::op(self.0.clone(), &deref_args, &deref_aux)
Ok(middleware::Operation::op(
self.0.clone(),
&deref_args,
&deref_aux,
)?)
}
}

View file

@ -1,10 +1,10 @@
use std::fmt;
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use crate::middleware::{
self, NativePredicate, Params, Predicate, StatementArg, ToFields, WildcardValue,
use crate::{
backends::plonky2::error::{Error, Result},
middleware::{self, NativePredicate, Params, Predicate, StatementArg, ToFields, WildcardValue},
};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -36,7 +36,7 @@ impl ToFields for Statement {
}
impl TryFrom<Statement> for middleware::Statement {
type Error = anyhow::Error;
type Error = Error;
fn try_from(s: Statement) -> Result<Self> {
type S = middleware::Statement;
type NP = NativePredicate;
@ -78,7 +78,10 @@ impl TryFrom<Statement> for middleware::Statement {
(NP::MaxOf, (Some(SA::Key(ak1)), Some(SA::Key(ak2)), Some(SA::Key(ak3))), 3) => {
S::MaxOf(ak1, ak2, ak3)
}
_ => Err(anyhow!("Ill-formed statement expression {:?}", s))?,
_ => Err(Error::custom(format!(
"Ill-formed statement expression {:?}",
s
)))?,
},
Predicate::Custom(cpr) => {
let vs: Vec<WildcardValue> = proper_args

View file

@ -4,12 +4,12 @@
use std::fmt;
use anyhow::{anyhow, Result};
use base64::{prelude::BASE64_STANDARD, Engine};
use serde::{Deserialize, Serialize};
use crate::{
backends::plonky2::{
error::{Error, Result},
mainpod::{
extract_merkle_proofs, hash_statements, layout_statements, normalize_statement,
process_private_statements_operations, process_public_statements_operations, Operation,
@ -18,15 +18,19 @@ use crate::{
primitives::merkletree::MerkleClaimAndProof,
},
middleware::{
self, hash_str, AnchoredKey, MainPodInputs, NativePredicate, Params, Pod, PodId, PodProver,
Predicate, StatementArg, KEY_TYPE, SELF,
self, hash_str, AnchoredKey, DynError, MainPodInputs, NativePredicate, Params, Pod, PodId,
PodProver, Predicate, StatementArg, KEY_TYPE, SELF,
},
};
pub struct MockProver {}
impl PodProver for MockProver {
fn prove(&mut self, params: &Params, inputs: MainPodInputs) -> Result<Box<dyn Pod>> {
fn prove(
&mut self,
params: &Params,
inputs: MainPodInputs,
) -> Result<Box<dyn Pod>, Box<DynError>> {
Ok(Box::new(MockMainPod::new(params, inputs)?))
}
}
@ -177,10 +181,8 @@ impl MockMainPod {
Ok(pod)
}
}
impl Pod for MockMainPod {
fn verify(&self) -> Result<()> {
fn _verify(&self) -> Result<()> {
// 1. TODO: Verify input pods
let input_statement_offset = self.offset_input_statements();
@ -245,24 +247,28 @@ impl Pod for MockMainPod {
.unwrap()
.check_and_log(&self.params, &s.clone().try_into().unwrap())
})
.collect::<Result<Vec<_>>>()
.collect::<Result<Vec<_>, middleware::Error>>()
.unwrap();
if !ids_match {
return Err(anyhow!("Verification failed: POD ID is incorrect."));
return Err(Error::pod_id_invalid());
}
if !has_type_statement {
return Err(anyhow!(
"Verification failed: POD does not have type statement."
));
return Err(Error::not_type_statement());
}
if !value_ofs_unique {
return Err(anyhow!("Verification failed: Repeated ValueOf"));
return Err(Error::repeated_value_of());
}
if !statement_check.iter().all(|b| *b) {
return Err(anyhow!("Verification failed: Statement did not check."));
return Err(Error::statement_not_check());
}
Ok(())
}
}
impl Pod for MockMainPod {
fn verify(&self) -> Result<(), Box<DynError>> {
Ok(self._verify()?)
}
fn id(&self) -> PodId {
self.id
}
@ -293,11 +299,12 @@ pub mod tests {
great_boy_pod_full_flow, tickets_pod_full_flow, zu_kyc_pod_builder,
zu_kyc_sign_pod_builders,
},
frontend,
middleware::{self},
};
#[test]
fn test_mock_main_zu_kyc() -> Result<()> {
fn test_mock_main_zu_kyc() -> frontend::Result<()> {
let params = middleware::Params::default();
let (gov_id_builder, pay_stub_builder, sanction_list_builder) =
zu_kyc_sign_pod_builders(&params);
@ -331,7 +338,7 @@ pub mod tests {
}
#[test]
fn test_mock_main_great_boy() -> Result<()> {
fn test_mock_main_great_boy() -> frontend::Result<()> {
let params = middleware::Params::default();
let great_boy_builder = great_boy_pod_full_flow()?;
@ -349,7 +356,7 @@ pub mod tests {
}
#[test]
fn test_mock_main_tickets() -> Result<()> {
fn test_mock_main_tickets() -> frontend::Result<()> {
let params = middleware::Params::default();
let tickets_builder = tickets_pod_full_flow()?;
let mut prover = MockProver {};

View file

@ -1,14 +1,16 @@
use std::collections::HashMap;
use anyhow::{anyhow, Result};
use itertools::Itertools;
use crate::{
backends::plonky2::primitives::merkletree::MerkleTree,
backends::plonky2::{
error::{Error, Result},
primitives::merkletree::MerkleTree,
},
constants::MAX_DEPTH,
middleware::{
containers::Dictionary, hash_str, AnchoredKey, Hash, Key, Params, Pod, PodId, PodSigner,
PodType, RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE,
containers::Dictionary, hash_str, AnchoredKey, DynError, Hash, Key, Params, Pod, PodId,
PodSigner, PodType, RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE,
},
};
@ -22,8 +24,8 @@ impl MockSigner {
}
}
impl PodSigner for MockSigner {
fn sign(&mut self, _params: &Params, kvs: &HashMap<Key, Value>) -> Result<Box<dyn Pod>> {
impl MockSigner {
fn _sign(&mut self, _params: &Params, kvs: &HashMap<Key, Value>) -> Result<MockSignedPod> {
let mut kvs = kvs.clone();
let pubkey = self.pubkey();
kvs.insert(Key::from(KEY_SIGNER), Value::from(pubkey));
@ -32,7 +34,17 @@ impl PodSigner for MockSigner {
let dict = Dictionary::new(kvs.clone())?;
let id = PodId(dict.commitment());
let signature = format!("{}_signed_by_{}", id, pubkey);
Ok(Box::new(MockSignedPod { id, signature, kvs }))
Ok(MockSignedPod { id, signature, kvs })
}
}
impl PodSigner for MockSigner {
fn sign(
&mut self,
params: &Params,
kvs: &HashMap<Key, Value>,
) -> Result<Box<dyn Pod>, Box<DynError>> {
Ok(self._sign(params, kvs).map(Box::new)?)
}
}
@ -49,8 +61,8 @@ impl MockSignedPod {
}
}
impl Pod for MockSignedPod {
fn verify(&self) -> Result<()> {
impl MockSignedPod {
fn _verify(&self) -> Result<()> {
// 1. Verify id
let mt = MerkleTree::new(
MAX_DEPTH,
@ -62,23 +74,18 @@ impl Pod for MockSignedPod {
)?;
let id = PodId(mt.root());
if id != self.id {
return Err(anyhow!(
"id does not match, expected {}, computed {}",
self.id,
id
));
return Err(Error::id_not_equal(self.id, id));
}
// 2. Verify type
let value_at_type = self
.kvs
.get(&Key::from(KEY_TYPE))
.ok_or(anyhow!("key not found"))?;
.ok_or(Error::key_not_found())?;
if &Value::from(PodType::MockSigned) != value_at_type {
return Err(anyhow!(
"type does not match, expected MockSigned ({}), found {}",
return Err(Error::type_not_equal(
PodType::MockSigned,
value_at_type
value_at_type.clone(),
));
}
@ -86,18 +93,23 @@ impl Pod for MockSignedPod {
let pk_hash = self
.kvs
.get(&Key::from(KEY_SIGNER))
.ok_or(anyhow!("key not found"))?;
.ok_or(Error::key_not_found())?;
let signature = format!("{}_signed_by_{}", id, pk_hash);
if signature != self.signature {
return Err(anyhow!(
return Err(Error::custom(format!(
"signature does not match, expected {}, computed {}",
self.id,
id
));
self.id, id
)));
}
Ok(())
}
}
impl Pod for MockSignedPod {
fn verify(&self) -> Result<(), Box<DynError>> {
Ok(self._verify()?)
}
fn id(&self) -> PodId {
self.id
@ -149,7 +161,7 @@ pub mod tests {
.downcast::<MockSignedPod>()
.unwrap();
pod.verify()?;
pod._verify()?;
println!("id: {}", pod.id());
println!("kvs: {:?}", pod.kvs());

View file

@ -1,6 +1,9 @@
pub mod basetypes;
pub mod circuits;
mod error;
pub mod mainpod;
pub mod mock;
pub mod primitives;
pub mod signedpod;
pub use error::*;

View file

@ -10,7 +10,6 @@
//!
use std::iter;
use anyhow::Result;
use plonky2::{
field::types::Field,
hash::{
@ -28,6 +27,7 @@ use crate::{
backends::plonky2::{
basetypes::D,
circuits::common::{CircuitBuilderPod, ValueTarget},
error::Result,
primitives::merkletree::MerkleClaimAndProof,
},
middleware::{EMPTY_HASH, EMPTY_VALUE, F, HASH_SIZE},
@ -408,7 +408,10 @@ pub mod tests {
use super::*;
use crate::{
backends::plonky2::{basetypes::C, primitives::merkletree::*},
backends::plonky2::{
basetypes::C,
primitives::merkletree::{keypath, kv_hash, MerkleTree},
},
middleware::{hash_value, RawValue},
};
@ -693,6 +696,8 @@ pub mod tests {
assert_eq!(
MerkleTree::verify(max_depth, tree2.root(), &proof, &key, &value)
.unwrap_err()
.inner()
.unwrap()
.to_string(),
"proof of inclusion does not verify"
);

View file

@ -0,0 +1,79 @@
//! tree errors
use std::{backtrace::Backtrace, fmt::Debug};
pub type TreeResult<T, E = TreeError> = core::result::Result<T, E>;
#[derive(Debug, thiserror::Error)]
pub enum TreeInnerError {
#[error("key not found")]
KeyNotFound,
#[error("key already exists")]
KeyExists,
#[error("max depth reached")]
MaxDepth,
#[error("reached empty node, should not have entered")]
EmptyNode,
#[error("proof of {0} does not verify")]
ProofFail(String), // inclusion / exclusion
#[error("invalid {0} proof")]
InvalidProof(String),
#[error("key too short (key length: {0}) for the max_depth: {1}")]
TooShortKey(usize, usize),
}
#[derive(thiserror::Error)]
pub enum TreeError {
#[error("Inner: {inner}\n{backtrace}")]
Inner {
inner: Box<TreeInnerError>,
backtrace: Box<Backtrace>,
},
#[error("anyhow::Error: {0}")]
Anyhow(#[from] anyhow::Error),
}
impl Debug for TreeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
macro_rules! new {
($inner:expr) => {
TreeError::Inner {
inner: Box::new($inner),
backtrace: Box::new(Backtrace::capture()),
}
};
}
use TreeInnerError::*;
impl TreeError {
pub fn inner(&self) -> Option<&TreeInnerError> {
match self {
Self::Inner { inner, .. } => Some(inner),
_ => None,
}
}
pub(crate) fn key_not_found() -> Self {
new!(KeyNotFound)
}
pub(crate) fn key_exists() -> Self {
new!(KeyExists)
}
pub(crate) fn max_depth() -> Self {
new!(MaxDepth)
}
pub(crate) fn empty_node() -> Self {
new!(EmptyNode)
}
pub(crate) fn proof_fail(obj: String) -> Self {
new!(ProofFail(obj))
}
pub(crate) fn invalid_proof(obj: String) -> Self {
new!(InvalidProof(obj))
}
pub(crate) fn too_short_key(depth: usize, max_depth: usize) -> Self {
new!(TooShortKey(depth, max_depth))
}
}

View file

@ -2,13 +2,16 @@
//! https://0xparc.github.io/pod2/merkletree.html .
use std::{collections::HashMap, fmt, iter::IntoIterator};
use anyhow::{anyhow, Result};
use plonky2::field::types::Field;
use serde::{Deserialize, Serialize};
pub use super::merkletree_circuit::*;
use crate::middleware::{hash_fields, Hash, RawValue, EMPTY_HASH, EMPTY_VALUE, F};
pub mod circuit;
pub use circuit::*;
pub mod error;
pub use error::{TreeError, TreeResult};
/// Implements the MerkleTree specified at
/// https://0xparc.github.io/pod2/merkletree.html
#[derive(Clone, Debug)]
@ -19,12 +22,12 @@ pub struct MerkleTree {
impl MerkleTree {
/// builds a new `MerkleTree` where the leaves contain the given key-values
pub fn new(max_depth: usize, kvs: &HashMap<RawValue, RawValue>) -> Result<Self> {
pub fn new(max_depth: usize, kvs: &HashMap<RawValue, RawValue>) -> TreeResult<Self> {
// Construct leaves.
let mut leaves: Vec<_> = kvs
.iter()
.map(|(k, v)| Leaf::new(max_depth, *k, *v))
.collect::<Result<_>>()?;
.collect::<TreeResult<_>>()?;
// Start with a leaf or conclude with an empty node as root.
let mut root = leaves.pop().map(Node::Leaf).unwrap_or(Node::None);
@ -50,17 +53,17 @@ impl MerkleTree {
}
/// returns the value at the given key
pub fn get(&self, key: &RawValue) -> Result<RawValue> {
pub fn get(&self, key: &RawValue) -> TreeResult<RawValue> {
let path = keypath(self.max_depth, *key)?;
let key_resolution = self.root.down(0, self.max_depth, path, None)?;
match key_resolution {
Some((k, v)) if &k == key => Ok(v),
_ => Err(anyhow!("key not found")),
_ => Err(TreeError::key_not_found()),
}
}
/// returns a boolean indicating whether the key exists in the tree
pub fn contains(&self, key: &RawValue) -> Result<bool> {
pub fn contains(&self, key: &RawValue) -> TreeResult<bool> {
let path = keypath(self.max_depth, *key)?;
match self.root.down(0, self.max_depth, path, None) {
Ok(Some((k, _))) => {
@ -77,7 +80,7 @@ impl MerkleTree {
/// returns a proof of existence, which proves that the given key exists in
/// the tree. It returns the `value` of the leaf at the given `key`, and the
/// `MerkleProof`.
pub fn prove(&self, key: &RawValue) -> Result<(RawValue, MerkleProof)> {
pub fn prove(&self, key: &RawValue) -> TreeResult<(RawValue, MerkleProof)> {
let path = keypath(self.max_depth, *key)?;
let mut siblings: Vec<Hash> = Vec::new();
@ -94,7 +97,7 @@ impl MerkleTree {
other_leaf: None,
},
)),
_ => Err(anyhow!("key not found")),
_ => Err(TreeError::key_not_found()),
}
}
@ -102,7 +105,7 @@ impl MerkleTree {
/// `key` does not exist in the tree. The return value specifies
/// the key-value pair in the leaf reached as a result of
/// resolving `key` as well as a `MerkleProof`.
pub fn prove_nonexistence(&self, key: &RawValue) -> Result<MerkleProof> {
pub fn prove_nonexistence(&self, key: &RawValue) -> TreeResult<MerkleProof> {
let path = keypath(self.max_depth, *key)?;
let mut siblings: Vec<Hash> = Vec::new();
@ -124,7 +127,7 @@ impl MerkleTree {
siblings,
other_leaf: Some((k, v)),
}),
_ => Err(anyhow!("key found")),
_ => Err(TreeError::key_not_found()),
}
// both cases prove that the given key don't exist in the tree. ∎
}
@ -136,11 +139,11 @@ impl MerkleTree {
proof: &MerkleProof,
key: &RawValue,
value: &RawValue,
) -> Result<()> {
) -> TreeResult<()> {
let h = proof.compute_root_from_leaf(max_depth, key, Some(*value))?;
if h != root {
Err(anyhow!("proof of inclusion does not verify"))
Err(TreeError::proof_fail("inclusion".to_string()))
} else {
Ok(())
}
@ -153,16 +156,18 @@ impl MerkleTree {
root: Hash,
proof: &MerkleProof,
key: &RawValue,
) -> Result<()> {
) -> TreeResult<()> {
match proof.other_leaf {
Some((k, _v)) if &k == key => Err(anyhow!("Invalid non-existence proof.")),
Some((k, _v)) if &k == key => {
Err(TreeError::invalid_proof("non-existence".to_string()))
}
_ => {
let k = proof.other_leaf.map(|(k, _)| k).unwrap_or(*key);
let v: Option<RawValue> = proof.other_leaf.map(|(_, v)| v);
let h = proof.compute_root_from_leaf(max_depth, &k, v)?;
if h != root {
Err(anyhow!("proof of exclusion does not verify"))
Err(TreeError::proof_fail("exclusion".to_string()))
} else {
Ok(())
}
@ -240,9 +245,9 @@ impl MerkleProof {
max_depth: usize,
key: &RawValue,
value: Option<RawValue>,
) -> Result<Hash> {
) -> TreeResult<Hash> {
if self.siblings.len() >= max_depth {
return Err(anyhow!("max depth reached"));
return Err(TreeError::max_depth());
}
let path = keypath(max_depth, *key)?;
@ -372,9 +377,9 @@ impl Node {
max_depth: usize,
path: Vec<bool>,
mut siblings: Option<&mut Vec<Hash>>,
) -> Result<Option<(RawValue, RawValue)>> {
) -> TreeResult<Option<(RawValue, RawValue)>> {
if lvl >= max_depth {
return Err(anyhow!("max depth reached"));
return Err(TreeError::max_depth());
}
match self {
@ -402,9 +407,9 @@ impl Node {
}
// adds the leaf at the tree from the current node (self), without computing any hash
pub(crate) fn add_leaf(&mut self, lvl: usize, max_depth: usize, leaf: Leaf) -> Result<()> {
pub(crate) fn add_leaf(&mut self, lvl: usize, max_depth: usize, leaf: Leaf) -> TreeResult<()> {
if lvl >= max_depth {
return Err(anyhow!("max depth reached"));
return Err(TreeError::max_depth());
}
match self {
@ -436,7 +441,7 @@ impl Node {
// Note: current approach returns an error when trying to
// add to a leaf where the key already exists. We could also
// ignore it if needed.
return Err(anyhow!("key already exists"));
return Err(TreeError::key_exists());
}
let old_leaf = l.clone();
// set self as an intermediate node
@ -444,7 +449,7 @@ impl Node {
return self.down_till_divergence(lvl, max_depth, old_leaf, leaf);
}
Self::None => {
return Err(anyhow!("reached empty node, should not have entered"));
return Err(TreeError::empty_node());
}
}
Ok(())
@ -460,9 +465,9 @@ impl Node {
max_depth: usize,
old_leaf: Leaf,
new_leaf: Leaf,
) -> Result<()> {
) -> TreeResult<()> {
if lvl >= max_depth {
return Err(anyhow!("max depth reached"));
return Err(TreeError::max_depth());
}
if let Node::Intermediate(ref mut n) = self {
@ -535,7 +540,7 @@ struct Leaf {
value: RawValue,
}
impl Leaf {
fn new(max_depth: usize, key: RawValue, value: RawValue) -> Result<Self> {
fn new(max_depth: usize, key: RawValue, value: RawValue) -> TreeResult<Self> {
Ok(Self {
hash: None,
path: keypath(max_depth, key)?,
@ -560,17 +565,13 @@ impl Leaf {
// max-depth? ie, what happens when two keys share the same path for more bits
// than the max_depth?
/// returns the path of the given key
pub(crate) fn keypath(max_depth: usize, k: RawValue) -> Result<Vec<bool>> {
pub(crate) fn keypath(max_depth: usize, k: RawValue) -> TreeResult<Vec<bool>> {
let bytes = k.to_bytes();
if max_depth > 8 * bytes.len() {
// note that our current keys are of Value type, which are 4 Goldilocks
// field elements, ie ~256 bits, therefore the max_depth can not be
// bigger than 256.
return Err(anyhow!(
"key to short (key length: {}) for the max_depth: {}",
8 * bytes.len(),
max_depth
));
return Err(TreeError::too_short_key(8 * bytes.len(), max_depth));
}
Ok((0..max_depth)
.map(|n| bytes[n / 8] & (1 << (n % 8)) != 0)
@ -617,7 +618,7 @@ pub mod tests {
use super::*;
#[test]
fn test_merkletree() -> Result<()> {
fn test_merkletree() -> TreeResult<()> {
let mut kvs = HashMap::new();
for i in 0..8 {
if i == 1 {

View file

@ -1,4 +1,2 @@
pub mod merkletree;
mod merkletree_circuit;
pub mod signature;
mod signature_circuit;

View file

@ -1,5 +1,4 @@
#![allow(unused)]
use anyhow::Result;
use lazy_static::lazy_static;
use plonky2::{
field::types::Field,
@ -26,8 +25,9 @@ use crate::{
backends::plonky2::{
basetypes::{Proof, C, D},
circuits::common::{CircuitBuilderPod, ValueTarget},
error::Result,
primitives::signature::{
PublicKey, SecretKey, Signature, DUMMY_PUBLIC_INPUTS, DUMMY_SIGNATURE,
PublicKey, SecretKey, Signature, DUMMY_PUBLIC_INPUTS, DUMMY_SIGNATURE, VP,
},
},
middleware::{Hash, RawValue, EMPTY_HASH, EMPTY_VALUE, F, VALUE_SIZE},
@ -67,7 +67,7 @@ impl SignatureVerifyGadget {
pub fn eval(&self, builder: &mut CircuitBuilder<F, D>) -> Result<SignatureVerifyTarget> {
let enabled = builder.add_virtual_bool_target_safe();
let common_data = super::signature::VP.0.common.clone();
let common_data = VP.0.common.clone();
// targets related to the 'public inputs' for the verification of the
// `SignatureInternalCircuit` proof.
@ -161,10 +161,7 @@ impl SignatureVerifyTarget {
)?;
}
pw.set_verifier_data_target(
&self.verifier_data_targ,
&super::signature::VP.0.verifier_only,
)?;
pw.set_verifier_data_target(&self.verifier_data_targ, &VP.0.verifier_only)?;
Ok(())
}

View file

@ -1,6 +1,5 @@
//! Proof-based signatures using Plonky2 proofs, following
//! https://eprint.iacr.org/2024/1553 .
use anyhow::Result;
use lazy_static::lazy_static;
use plonky2::{
field::types::Sample,
@ -20,9 +19,14 @@ use plonky2::{
},
};
pub use super::signature_circuit::*;
pub mod circuit;
pub use circuit::*;
use crate::{
backends::plonky2::basetypes::{Proof, C, D},
backends::plonky2::{
basetypes::{Proof, C, D},
error::{Error, Result},
},
middleware::{RawValue, F, VALUE_SIZE},
};
@ -121,6 +125,7 @@ impl Signature {
proof: self.0.clone(),
public_inputs,
})
.map_err(Error::plonky2_proof_fail)
}
}

View file

@ -1,24 +1,26 @@
use std::collections::HashMap;
use anyhow::{anyhow, Result};
use itertools::Itertools;
use crate::{
backends::plonky2::primitives::{
merkletree::MerkleTree,
signature::{PublicKey, SecretKey, Signature},
backends::plonky2::{
error::{Error, Result},
primitives::{
merkletree::MerkleTree,
signature::{PublicKey, SecretKey, Signature},
},
},
constants::MAX_DEPTH,
middleware::{
containers::Dictionary, AnchoredKey, Hash, Key, Params, Pod, PodId, PodSigner, PodType,
RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE,
containers::Dictionary, AnchoredKey, DynError, Hash, Key, Params, Pod, PodId, PodSigner,
PodType, RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE,
},
};
pub struct Signer(pub SecretKey);
impl PodSigner for Signer {
fn sign(&mut self, _params: &Params, kvs: &HashMap<Key, Value>) -> Result<Box<dyn Pod>> {
impl Signer {
fn _sign(&mut self, _params: &Params, kvs: &HashMap<Key, Value>) -> Result<SignedPod> {
let mut kvs = kvs.clone();
let pubkey = self.0.public_key();
kvs.insert(Key::from(KEY_SIGNER), Value::from(pubkey.0));
@ -28,11 +30,21 @@ impl PodSigner for Signer {
let id = RawValue::from(dict.commitment()); // PodId as Value
let signature: Signature = self.0.sign(id)?;
Ok(Box::new(SignedPod {
Ok(SignedPod {
id: PodId(Hash::from(id)),
signature,
dict,
}))
})
}
}
impl PodSigner for Signer {
fn sign(
&mut self,
params: &Params,
kvs: &HashMap<Key, Value>,
) -> Result<Box<dyn Pod>, Box<DynError>> {
Ok(self._sign(params, kvs).map(Box::new)?)
}
}
@ -43,15 +55,14 @@ pub struct SignedPod {
pub dict: Dictionary,
}
impl Pod for SignedPod {
fn verify(&self) -> Result<()> {
impl SignedPod {
fn _verify(&self) -> Result<()> {
// 1. Verify type
let value_at_type = self.dict.get(&Key::from(KEY_TYPE))?;
if Value::from(PodType::Signed) != *value_at_type {
return Err(anyhow!(
"type does not match, expected Signed ({}), found {}",
return Err(Error::type_not_equal(
PodType::Signed,
value_at_type
value_at_type.clone(),
));
}
@ -67,11 +78,7 @@ impl Pod for SignedPod {
)?;
let id = PodId(mt.root());
if id != self.id {
return Err(anyhow!(
"id does not match, expected {}, computed {}",
self.id,
id
));
return Err(Error::id_not_equal(self.id, id));
}
// 3. Verify signature
@ -81,6 +88,12 @@ impl Pod for SignedPod {
Ok(())
}
}
impl Pod for SignedPod {
fn verify(&self) -> Result<(), Box<DynError>> {
Ok(self._verify().map_err(Box::new)?)
}
fn id(&self) -> PodId {
self.id
@ -135,7 +148,7 @@ pub mod tests {
let pod = pod.sign(&mut signer).unwrap();
let pod = (pod.pod as Box<dyn Any>).downcast::<SignedPod>().unwrap();
pod.verify()?;
pod._verify()?;
println!("id: {}", pod.id());
println!("kvs: {:?}", pod.kvs());