use std::rc::Rc;
use super::dht_protocol;
use super::dht_protocol::FirstDhTupleProverMessage;
use super::fiat_shamir::FiatShamirTreeSerializationError;
use super::prover::ProofBytes;
use super::sig_serializer::SigParsingError;
use super::unchecked_tree::UncheckedDhTuple;
use super::{
dlog_protocol,
fiat_shamir::{fiat_shamir_hash_fn, fiat_shamir_tree_to_bytes},
sig_serializer::parse_sig_compute_challenges,
unchecked_tree::{UncheckedLeaf, UncheckedSchnorr},
SigmaBoolean, UncheckedTree,
};
use crate::eval::context::Context;
use crate::eval::env::Env;
use crate::eval::EvalError;
use crate::eval::{reduce_to_crypto, ReductionDiagnosticInfo};
use dlog_protocol::FirstDlogProverMessage;
use ergotree_ir::ergo_tree::ErgoTree;
use ergotree_ir::ergo_tree::ErgoTreeError;
use derive_more::From;
use thiserror::Error;
#[derive(Error, Debug, From)]
pub enum VerifierError {
#[error("ErgoTreeError: {0}")]
ErgoTreeError(ErgoTreeError),
#[error("EvalError: {0}")]
EvalError(EvalError),
#[error("SigParsingError: {0}")]
SigParsingError(SigParsingError),
#[error("Fiat-Shamir tree serialization error: {0}")]
FiatShamirTreeSerializationError(FiatShamirTreeSerializationError),
}
pub struct VerificationResult {
pub result: bool,
pub cost: u64,
pub diag: ReductionDiagnosticInfo,
}
pub trait Verifier {
fn verify(
&self,
tree: &ErgoTree,
env: &Env,
ctx: Rc<Context>,
proof: ProofBytes,
message: &[u8],
) -> Result<VerificationResult, VerifierError> {
let expr = tree.proposition()?;
let reduction_result = reduce_to_crypto(&expr, env, ctx)?;
let res: bool = match reduction_result.sigma_prop {
SigmaBoolean::TrivialProp(b) => b,
sb => {
match proof {
ProofBytes::Empty => false,
ProofBytes::Some(proof_bytes) => {
let unchecked_tree = parse_sig_compute_challenges(&sb, proof_bytes)?;
check_commitments(unchecked_tree, message)?
}
}
}
};
Ok(VerificationResult {
result: res,
cost: 0,
diag: reduction_result.diag,
})
}
}
pub fn verify_signature(
sigma_tree: SigmaBoolean,
message: &[u8],
signature: &[u8],
) -> Result<bool, VerifierError> {
let res: bool = match sigma_tree {
SigmaBoolean::TrivialProp(b) => b,
sb => {
match signature {
[] => false,
_ => {
let unchecked_tree = parse_sig_compute_challenges(&sb, signature.to_vec())?;
check_commitments(unchecked_tree, message)?
}
}
}
};
Ok(res)
}
fn check_commitments(sp: UncheckedTree, message: &[u8]) -> Result<bool, VerifierError> {
let new_root = compute_commitments(sp);
let mut s = fiat_shamir_tree_to_bytes(&new_root.clone().into())?;
s.append(&mut message.to_vec());
let expected_challenge = fiat_shamir_hash_fn(s.as_slice());
Ok(new_root.challenge() == expected_challenge.into())
}
pub fn compute_commitments(sp: UncheckedTree) -> UncheckedTree {
match sp {
UncheckedTree::UncheckedLeaf(leaf) => match leaf {
UncheckedLeaf::UncheckedSchnorr(sn) => {
let a = dlog_protocol::interactive_prover::compute_commitment(
&sn.proposition,
&sn.challenge,
&sn.second_message,
);
UncheckedSchnorr {
commitment_opt: Some(FirstDlogProverMessage { a: a.into() }),
..sn
}
.into()
}
UncheckedLeaf::UncheckedDhTuple(dh) => {
let (a, b) = dht_protocol::interactive_prover::compute_commitment(
&dh.proposition,
&dh.challenge,
&dh.second_message,
);
UncheckedDhTuple {
commitment_opt: Some(FirstDhTupleProverMessage::new(a, b)),
..dh
}
.into()
}
},
UncheckedTree::UncheckedConjecture(conj) => conj
.clone()
.with_children(conj.children_ust().mapped(compute_commitments))
.into(),
}
}
pub struct TestVerifier;
impl Verifier for TestVerifier {}
#[allow(clippy::unwrap_used)]
#[allow(clippy::panic)]
#[cfg(test)]
#[cfg(feature = "arbitrary")]
mod tests {
use std::convert::TryFrom;
use crate::sigma_protocol::private_input::{DhTupleProverInput, DlogProverInput, PrivateInput};
use crate::sigma_protocol::prover::hint::HintsBag;
use crate::sigma_protocol::prover::{Prover, TestProver};
use super::*;
use ergotree_ir::mir::atleast::Atleast;
use ergotree_ir::mir::constant::{Constant, Literal};
use ergotree_ir::mir::expr::Expr;
use ergotree_ir::mir::sigma_and::SigmaAnd;
use ergotree_ir::mir::sigma_or::SigmaOr;
use ergotree_ir::mir::value::CollKind;
use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
use ergotree_ir::types::stype::SType;
use proptest::collection::vec;
use proptest::prelude::*;
use sigma_test_util::force_any_val;
fn proof_append_some_byte(proof: &ProofBytes) -> ProofBytes {
match proof {
ProofBytes::Empty => panic!(),
ProofBytes::Some(bytes) => {
let mut new_bytes = bytes.clone();
new_bytes.push(1u8);
ProofBytes::Some(new_bytes)
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(16))]
#[test]
fn test_prover_verifier_p2pk(secret in any::<DlogProverInput>(), message in vec(any::<u8>(), 100..200)) {
let pk = secret.public_image();
let tree = ErgoTree::try_from(Expr::Const(pk.into())).unwrap();
let prover = TestProver {
secrets: vec![PrivateInput::DlogProverInput(secret)],
};
let res = prover.prove(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
message.as_slice(),
&HintsBag::empty());
let proof = res.unwrap().proof;
let verifier = TestVerifier;
prop_assert_eq!(verifier.verify(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
proof.clone(),
message.as_slice())
.unwrap().result,
true);
prop_assert_eq!(verifier.verify(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
proof_append_some_byte(&proof),
message.as_slice())
.unwrap().result,
true);
prop_assert_eq!(verifier.verify(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
proof,
vec![1u8; 100].as_slice())
.unwrap().result,
false);
}
#[test]
fn test_prover_verifier_dht(secret in any::<DhTupleProverInput>(), message in vec(any::<u8>(), 100..200)) {
let pk = secret.public_image().clone();
let tree = ErgoTree::try_from(Expr::Const(pk.into())).unwrap();
let prover = TestProver {
secrets: vec![PrivateInput::DhTupleProverInput(secret)],
};
let res = prover.prove(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
message.as_slice(),
&HintsBag::empty());
let proof = res.unwrap().proof;
let verifier = TestVerifier;
prop_assert_eq!(verifier.verify(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
proof.clone(),
message.as_slice())
.unwrap().result,
true);
prop_assert_eq!(verifier.verify(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
proof_append_some_byte(&proof),
message.as_slice())
.unwrap().result,
true);
prop_assert_eq!(verifier.verify(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
proof,
vec![1u8; 100].as_slice())
.unwrap().result,
false);
}
#[test]
fn test_prover_verifier_conj_and(secret1 in any::<PrivateInput>(),
secret2 in any::<PrivateInput>(),
message in vec(any::<u8>(), 100..200)) {
let pk1 = secret1.public_image();
let pk2 = secret2.public_image();
let expr: Expr = SigmaAnd::new(vec![Expr::Const(pk1.into()), Expr::Const(pk2.into())])
.unwrap()
.into();
let tree = ErgoTree::try_from(expr).unwrap();
let prover = TestProver {
secrets: vec![secret1, secret2],
};
let res = prover.prove(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
message.as_slice(),
&HintsBag::empty());
let proof = res.unwrap().proof;
let verifier = TestVerifier;
let ver_res = verifier.verify(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
proof,
message.as_slice());
prop_assert_eq!(ver_res.unwrap().result, true);
}
#[test]
fn test_prover_verifier_conj_and_and(secret1 in any::<PrivateInput>(),
secret2 in any::<PrivateInput>(),
secret3 in any::<PrivateInput>(),
message in vec(any::<u8>(), 100..200)) {
let pk1 = secret1.public_image();
let pk2 = secret2.public_image();
let pk3 = secret3.public_image();
let expr: Expr = SigmaAnd::new(vec![
Expr::Const(pk1.into()),
SigmaAnd::new(vec![Expr::Const(pk2.into()), Expr::Const(pk3.into())])
.unwrap()
.into(),
]).unwrap().into();
let tree = ErgoTree::try_from(expr).unwrap();
let prover = TestProver { secrets: vec![secret1, secret2, secret3] };
let res = prover.prove(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
message.as_slice(),
&HintsBag::empty());
let proof = res.unwrap().proof;
let verifier = TestVerifier;
let ver_res = verifier.verify(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
proof,
message.as_slice());
prop_assert_eq!(ver_res.unwrap().result, true);
}
#[test]
fn test_prover_verifier_conj_or(secret1 in any::<PrivateInput>(),
secret2 in any::<PrivateInput>(),
message in vec(any::<u8>(), 100..200)) {
let pk1 = secret1.public_image();
let pk2 = secret2.public_image();
let expr: Expr = SigmaOr::new(vec![Expr::Const(pk1.into()), Expr::Const(pk2.into())])
.unwrap()
.into();
let tree = ErgoTree::try_from(expr).unwrap();
let secrets = vec![secret1, secret2];
for secret in secrets {
let prover = TestProver {
secrets: vec![secret.clone()],
};
let res = prover.prove(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
message.as_slice(),
&HintsBag::empty());
let proof = res.unwrap_or_else(|_| panic!("proof failed for secret: {:?}", secret)).proof;
let verifier = TestVerifier;
let ver_res = verifier.verify(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
proof,
message.as_slice());
prop_assert_eq!(ver_res.unwrap().result, true, "verify failed on secret: {:?}", &secret);
}
}
#[test]
fn test_prover_verifier_conj_or_or(secret1 in any::<PrivateInput>(),
secret2 in any::<PrivateInput>(),
secret3 in any::<PrivateInput>(),
message in vec(any::<u8>(), 100..200)) {
let pk1 = secret1.public_image();
let pk2 = secret2.public_image();
let pk3 = secret3.public_image();
let expr: Expr = SigmaOr::new(vec![
Expr::Const(pk1.into()),
SigmaOr::new(vec![Expr::Const(pk2.into()), Expr::Const(pk3.into())])
.unwrap()
.into(),
]).unwrap().into();
let tree = ErgoTree::try_from(expr).unwrap();
let secrets = vec![secret1, secret2, secret3];
for secret in secrets {
let prover = TestProver {
secrets: vec![secret.clone()],
};
let res = prover.prove(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
message.as_slice(),
&HintsBag::empty());
let proof = res.unwrap_or_else(|_| panic!("proof failed for secret: {:?}", secret)).proof;
let verifier = TestVerifier;
let ver_res = verifier.verify(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
proof,
message.as_slice());
prop_assert_eq!(ver_res.unwrap().result, true, "verify failed on secret: {:?}", &secret);
}
}
#[test]
fn test_prover_verifier_atleast(secret1 in any::<DlogProverInput>(),
secret2 in any::<DlogProverInput>(),
secret3 in any::<DlogProverInput>(),
message in vec(any::<u8>(), 100..200)) {
let bound = Expr::Const(2i32.into());
let inputs = Literal::Coll(
CollKind::from_vec(
SType::SSigmaProp,
vec![
SigmaProp::from(secret1.public_image()).into(),
SigmaProp::from(secret2.public_image()).into(),
SigmaProp::from(secret3.public_image()).into(),
],
)
.unwrap(),
);
let input = Constant {
tpe: SType::SColl(SType::SSigmaProp.into()),
v: inputs,
}
.into();
let expr: Expr = Atleast::new(bound, input).unwrap().into();
let tree = ErgoTree::try_from(expr).unwrap();
let prover = TestProver {
secrets: vec![secret1.into(), secret3.into()],
};
let res = prover.prove(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
message.as_slice(),
&HintsBag::empty());
let proof = res.unwrap().proof;
let verifier = TestVerifier;
let ver_res = verifier.verify(&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
proof,
message.as_slice());
prop_assert_eq!(ver_res.unwrap().result, true)
}
}
}