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:
parent
58d3c6a236
commit
29545f03fc
31 changed files with 696 additions and 273 deletions
|
|
@ -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"
|
||||
);
|
||||
79
src/backends/plonky2/primitives/merkletree/error.rs
Normal file
79
src/backends/plonky2/primitives/merkletree/error.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
@ -1,4 +1,2 @@
|
|||
pub mod merkletree;
|
||||
mod merkletree_circuit;
|
||||
pub mod signature;
|
||||
mod signature_circuit;
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue