Skip to content

Commit

Permalink
Merge pull request #723 from ergoplatform/i625-explain-reduced-to-false
Browse files Browse the repository at this point in the history
Enrich ReducedToFalse prover/verifier error with pretty printed ErgoTree and evaluation environment
  • Loading branch information
greenhat authored Oct 11, 2023
2 parents 1d41e64 + 17791ec commit 75a5def
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 31 deletions.
28 changes: 12 additions & 16 deletions ergo-lib/src/chain/transaction/reduced.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::rc::Rc;

use ergotree_interpreter::eval::env::Env;
use ergotree_interpreter::eval::reduce_to_crypto;
use ergotree_interpreter::eval::ReductionResult;
use ergotree_interpreter::sigma_protocol::prover::ContextExtension;
use ergotree_interpreter::sigma_protocol::prover::ProverError;
use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
Expand All @@ -31,8 +30,10 @@ use super::TxIoVec;
/// <https://github.com/ergoplatform/eips/blob/f280890a4163f2f2e988a0091c078e36912fc531/eip-0019.md>
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ReducedInput {
/// Input box script reduced to SigmaBoolean
pub reduction_result: ReductionResult,
/// value of SigmaProp type which represents a statement verifiable via sigma protocol.
pub sigma_prop: SigmaBoolean,
/// estimated cost of expression evaluation
pub cost: u64,
/// ContextExtension for the input
pub extension: ContextExtension,
}
Expand Down Expand Up @@ -87,8 +88,9 @@ pub fn reduce_tx(
.map_err(ProverError::EvalError)
.map_err(|e| TxSigningError::ProverError(e, idx))?;
Ok(ReducedInput {
reduction_result,
extension: input.extension,
sigma_prop: reduction_result.sigma_prop,
cost: reduction_result.cost,
})
})?;
Ok(ReducedTransaction {
Expand All @@ -104,8 +106,8 @@ impl SigmaSerializable for ReducedTransaction {
w.put_usize_as_u32_unwrapped(msg.len())?;
w.write_all(&msg)?;
self.reduced_inputs.as_vec().iter().try_for_each(|red_in| {
red_in.reduction_result.sigma_prop.sigma_serialize(w)?;
w.put_u64(red_in.reduction_result.cost)?;
red_in.sigma_prop.sigma_serialize(w)?;
w.put_u64(red_in.cost)?;
SigmaSerializeResult::Ok(())
})?;
w.put_u32(self.tx_cost)?;
Expand All @@ -123,11 +125,8 @@ impl SigmaSerializable for ReducedTransaction {
let cost = r.get_u64()?;
let extension = input.spending_proof.extension;
let reduced_input = ReducedInput {
reduction_result: ReductionResult {
sigma_prop,
cost,
env: Env::empty(),
},
sigma_prop,
cost,
extension: extension.clone(),
};
let unsigned_input = UnsignedInput {
Expand Down Expand Up @@ -168,11 +167,8 @@ pub mod arbitrary {
.prop_map(|(unsigned_tx, sb, tx_cost)| Self {
unsigned_tx: unsigned_tx.clone(),
reduced_inputs: unsigned_tx.inputs.mapped(|unsigned_input| ReducedInput {
reduction_result: ReductionResult {
sigma_prop: sb.clone(),
cost: 0,
env: Env::empty(),
},
sigma_prop: sb.clone(),
cost: 0,
extension: unsigned_input.extension,
}),
tx_cost,
Expand Down
2 changes: 1 addition & 1 deletion ergo-lib/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ impl Wallet {
.map(|secret| secret.public_image())
.collect();
for (index, input) in reduced_tx.reduced_inputs().iter().enumerate() {
let sigma_prop = input.clone().reduction_result.sigma_prop;
let sigma_prop = input.clone().sigma_prop;
let hints = generate_commitments_for(&sigma_prop, &public_keys);
tx_hints.add_hints_for_input(index, hints);
}
Expand Down
2 changes: 1 addition & 1 deletion ergo-lib/src/wallet/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ pub fn sign_reduced_transaction(
}
prover
.generate_proof(
reduced_input.reduction_result.sigma_prop.clone(),
reduced_input.sigma_prop.clone(),
message_to_sign.as_slice(),
&hints_bag,
)
Expand Down
109 changes: 101 additions & 8 deletions ergotree-interpreter/src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use ergotree_ir::mir::constant::TryExtractInto;
use ergotree_ir::pretty_printer::PosTrackingWriter;
use ergotree_ir::pretty_printer::Print;
use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
use std::fmt::Display;
use std::rc::Rc;

use ergotree_ir::mir::expr::Expr;
Expand Down Expand Up @@ -97,15 +98,33 @@ pub(crate) mod xor_of;

pub use error::EvalError;

/// Diagnostic information about the reduction (pretty printed expr and/or env)
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ReductionDiagnosticInfo {
/// environment after the evaluation
pub env: Env,
/// expression pretty-printed
pub pretty_printed_expr: Option<String>,
}

impl Display for ReductionDiagnosticInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(expr_str) = &self.pretty_printed_expr {
writeln!(f, "Pretty printed expr:\n{}", expr_str)?;
}
write!(f, "Env:\n{}", self.env)
}
}

/// Result of expression reduction procedure (see `reduce_to_crypto`).
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ReductionResult {
/// value of SigmaProp type which represents a statement verifiable via sigma protocol.
pub sigma_prop: SigmaBoolean,
/// estimated cost of expression evaluation
pub cost: u64,
/// environment after the evaluation
pub env: Env,
/// Diagnostic information about the reduction (pretty printed expr and/or env)
pub diag: ReductionDiagnosticInfo,
}

/// Evaluate the given expression by reducing it to SigmaBoolean value.
Expand All @@ -125,29 +144,53 @@ pub fn reduce_to_crypto(
Value::Boolean(b) => Ok(ReductionResult {
sigma_prop: SigmaBoolean::TrivialProp(b),
cost: 0,
env: env_mut.clone(),
diag: ReductionDiagnosticInfo {
env: env_mut.clone(),
pretty_printed_expr: None,
},
}),
Value::SigmaProp(sp) => Ok(ReductionResult {
sigma_prop: sp.value().clone(),
cost: 0,
env: env_mut.clone(),
diag: ReductionDiagnosticInfo {
env: env_mut.clone(),
pretty_printed_expr: None,
},
}),
_ => Err(EvalError::InvalidResultType),
}
})
}

let res = inner(expr, env, ctx);
if res.is_ok() {
return res;
if let Ok(reduction) = res {
if reduction.sigma_prop == SigmaBoolean::TrivialProp(false) {
let (_, printed_expr_str) = pretty_print(expr)?;
let new_reduction = ReductionResult {
sigma_prop: SigmaBoolean::TrivialProp(false),
cost: reduction.cost,
diag: ReductionDiagnosticInfo {
env: reduction.diag.env,
pretty_printed_expr: Some(printed_expr_str),
},
};
return Ok(new_reduction);
} else {
return Ok(reduction);
}
}
let (spanned_expr, printed_expr_str) = pretty_print(expr)?;
inner(&spanned_expr, env, ctx_clone)
.map_err(|e| e.wrap_spanned_with_src(printed_expr_str.to_string()))
}

fn pretty_print(expr: &Expr) -> Result<(Expr, String), EvalError> {
let mut printer = PosTrackingWriter::new();
let spanned_expr = expr
.print(&mut printer)
.map_err(|e| EvalError::Misc(format!("printer error: {}", e)))?;
let printed_expr_str = printer.get_buf();
inner(&spanned_expr, env, ctx_clone)
.map_err(|e| e.wrap_spanned_with_src(printed_expr_str.to_string()))
Ok((spanned_expr, printed_expr_str.to_owned()))
}

/// Expects SigmaProp constant value and returns it's value. Otherwise, returns an error.
Expand Down Expand Up @@ -330,14 +373,23 @@ fn smethod_eval_fn(method: &SMethod) -> Result<EvalFn, EvalError> {
#[cfg(test)]
#[cfg(feature = "arbitrary")]
#[allow(clippy::unwrap_used)]
#[allow(clippy::todo)]
pub(crate) mod tests {

#![allow(dead_code)]

use super::env::Env;
use super::*;
use ergotree_ir::mir::bin_op::BinOp;
use ergotree_ir::mir::bin_op::BinOpKind;
use ergotree_ir::mir::bin_op::RelationOp;
use ergotree_ir::mir::block::BlockValue;
use ergotree_ir::mir::constant::TryExtractFrom;
use ergotree_ir::mir::constant::TryExtractInto;
use ergotree_ir::mir::val_def::ValDef;
use ergotree_ir::mir::val_use::ValUse;
use ergotree_ir::types::stype::SType;
use expect_test::expect;
use sigma_test_util::force_any_val;

pub fn eval_out_wo_ctx<T: TryExtractFrom<Value>>(expr: &Expr) -> T {
Expand Down Expand Up @@ -370,4 +422,45 @@ pub(crate) mod tests {
let ctx = Rc::new(force_any_val::<Context>());
try_eval_out(expr, ctx)
}

#[test]
fn diag_on_reduced_to_false() {
let bin_op: Expr = BinOp {
kind: BinOpKind::Relation(RelationOp::Eq),
left: Box::new(
ValUse {
val_id: 1.into(),
tpe: SType::SInt,
}
.into(),
),
right: Box::new(0i32.into()),
}
.into();
let block: Expr = Expr::BlockValue(
BlockValue {
items: vec![ValDef {
id: 1.into(),
rhs: Box::new(Expr::Const(1i32.into())),
}
.into()],
result: Box::new(bin_op),
}
.into(),
);
let ctx = Rc::new(force_any_val::<Context>());
let res = reduce_to_crypto(&block, &Env::empty(), ctx).unwrap();
assert!(res.sigma_prop == SigmaBoolean::TrivialProp(false));
expect![[r#"
Pretty printed expr:
{
val v1 = 1
v1 == 0
}
Env:
v1: 1
"#]]
.assert_eq(&res.diag.to_string());
}
}
12 changes: 12 additions & 0 deletions ergotree-interpreter/src/eval/env.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::HashMap;
use std::fmt::Display;

use ergotree_ir::mir::val_def::ValId;
use ergotree_ir::mir::value::Value;
Expand Down Expand Up @@ -34,3 +35,14 @@ impl Env {
self.store.get(&idx)
}
}

impl Display for Env {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut keys: Vec<&ValId> = self.store.keys().collect();
keys.sort();
for k in keys {
writeln!(f, "v{}: {}", k, self.store[k])?;
}
Ok(())
}
}
30 changes: 29 additions & 1 deletion ergotree-interpreter/src/eval/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use miette::miette;
use miette::LabeledSpan;
use std::fmt::Debug;
use std::fmt::Display;

use bounded_vec::BoundedVecOutOfBounds;
Expand Down Expand Up @@ -87,7 +88,7 @@ pub struct SpannedEvalError {
}

/// Wrapped error with source span and source code
#[derive(PartialEq, Eq, Debug, Clone)]
#[derive(PartialEq, Eq, Clone)]
pub struct SpannedWithSourceEvalError {
/// eval error
error: Box<EvalError>,
Expand Down Expand Up @@ -125,6 +126,33 @@ impl Display for SpannedWithSourceEvalError {
}
}

impl Debug for SpannedWithSourceEvalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[allow(clippy::unwrap_used)]
miette::set_hook(Box::new(|_| {
Box::new(
miette::MietteHandlerOpts::new()
.terminal_links(false)
.unicode(false)
.color(false)
.context_lines(5)
.tab_width(2)
.build(),
)
}))
.unwrap();
let err_msg = self.error.to_string();
let report = miette!(
labels = vec![LabeledSpan::at(self.source_span, err_msg,)],
// help = "Help msg",
"Evaluation error"
)
.with_source_code(self.source.clone());
write!(f, "{:?}", report)?;
write!(f, "Env:\n{}", self.env)
}
}

impl EvalError {
/// Wrap eval error with source span
pub fn wrap(self, source_span: SourceSpan, env: Env) -> Self {
Expand Down
10 changes: 10 additions & 0 deletions ergotree-interpreter/src/sigma_protocol/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod prover_result;
pub mod hint;

use crate::eval::reduce_to_crypto;
use crate::eval::ReductionDiagnosticInfo;
use crate::sigma_protocol::crypto_utils::secure_random_bytes;
use crate::sigma_protocol::fiat_shamir::fiat_shamir_hash_fn;
use crate::sigma_protocol::fiat_shamir::fiat_shamir_tree_to_bytes;
Expand Down Expand Up @@ -77,6 +78,9 @@ pub enum ProverError {
/// Script reduced to false
#[error("Script reduced to false")]
ReducedToFalse,
/// Script reduced to false with diagnostic info
#[error("Script reduced to false. Diagnostic info: {0}")]
ReducedToFalseWithDiag(ReductionDiagnosticInfo),
/// Failed on step2(prover does not have enough witnesses to perform the proof)
#[error("Failed on step2(prover does not have enough witnesses to perform the proof)")]
TreeRootIsNotReal,
Expand Down Expand Up @@ -150,6 +154,12 @@ pub trait Prover {
proof: p,
extension: ctx_ext,
})
.map_err(|e| match e {
ProverError::ReducedToFalse => {
ProverError::ReducedToFalseWithDiag(reduction_result.diag.clone())
}
_ => e,
})
}

/// Generate proofs for the given message for the given Sigma boolean expression
Expand Down
Loading

0 comments on commit 75a5def

Please sign in to comment.