mod context_extension;
mod prover_result;
use std::rc::Rc;
pub use context_extension::*;
use ergotree_ir::ergo_tree::ErgoTree;
use ergotree_ir::ergo_tree::ErgoTreeParsingError;
use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
pub use prover_result::*;
use super::{
dlog_protocol,
fiat_shamir::{fiat_shamir_hash_fn, fiat_shamir_tree_to_bytes},
private_input::PrivateInput,
sig_serializer::serialize_sig,
unchecked_tree::UncheckedSchnorr,
Challenge, ProofTree, SigmaBoolean, UncheckedSigmaTree, UncheckedTree, UnprovenLeaf,
UnprovenSchnorr, UnprovenTree,
};
use crate::eval::context::Context;
use crate::eval::env::Env;
use crate::eval::{EvalError, Evaluator};
use thiserror::Error;
#[derive(Error, PartialEq, Eq, Debug, Clone)]
pub enum ProverError {
#[error("Ergo tree error: {0}")]
ErgoTreeError(ErgoTreeParsingError),
#[error("Evaluation error: {0}")]
EvalError(EvalError),
#[error("Script reduced to false")]
ReducedToFalse,
#[error("Failed on step2(prover does not have enough witnesses to perform the proof)")]
TreeRootIsNotReal,
#[error("Simulated leaf does not have challenge")]
SimulatedLeafWithoutChallenge,
#[error("Lacking challenge on step 9 for \"real\" unproven tree")]
RealUnprovenTreeWithoutChallenge,
#[error("Cannot find a secret for \"real\" unproven leaf")]
SecretNotFound,
}
impl From<ErgoTreeParsingError> for ProverError {
fn from(err: ErgoTreeParsingError) -> Self {
ProverError::ErgoTreeError(err)
}
}
pub trait Prover: Evaluator {
fn secrets(&self) -> &[PrivateInput];
fn prove(
&self,
tree: &ErgoTree,
env: &Env,
ctx: Rc<Context>,
message: &[u8],
) -> Result<ProverResult, ProverError> {
let expr = tree.proposition()?;
let proof = self
.reduce_to_crypto(expr.as_ref(), env, ctx)
.map_err(ProverError::EvalError)
.and_then(|v| match v.sigma_prop {
SigmaBoolean::TrivialProp(true) => Ok(UncheckedTree::NoProof),
SigmaBoolean::TrivialProp(false) => Err(ProverError::ReducedToFalse),
sb => {
let tree = convert_to_unproven(sb);
let unchecked_tree = self.prove_to_unchecked(tree, message)?;
Ok(UncheckedTree::UncheckedSigmaTree(unchecked_tree))
}
});
proof.map(|v| ProverResult {
proof: serialize_sig(v),
extension: ContextExtension::empty(),
})
}
fn prove_to_unchecked(
&self,
unproven_tree: UnprovenTree,
message: &[u8],
) -> Result<UncheckedSigmaTree, ProverError> {
let step1 = self.mark_real(unproven_tree);
if !step1.is_real() {
return Err(ProverError::TreeRootIsNotReal);
}
let step6 = self.simulate_and_commit(step1)?;
let var_name = fiat_shamir_tree_to_bytes(&step6);
let mut s = var_name;
s.append(&mut message.to_vec());
let root_challenge: Challenge = fiat_shamir_hash_fn(s.as_slice()).into();
let step8 = step6.with_challenge(root_challenge);
let step9 = self.proving(step8)?;
match step9 {
ProofTree::UncheckedTree(UncheckedTree::UncheckedSigmaTree(tree)) => Ok(tree),
_ => todo!(),
}
}
fn mark_real(&self, unproven_tree: UnprovenTree) -> UnprovenTree {
match unproven_tree {
UnprovenTree::UnprovenLeaf(UnprovenLeaf::UnprovenSchnorr(us)) => {
let secret_known = self.secrets().iter().any(|s| match s {
PrivateInput::DlogProverInput(dl) => dl.public_image() == us.proposition,
_ => false,
});
UnprovenSchnorr {
simulated: !secret_known,
..us
}
.into()
}
}
}
fn polish_simulated(&self, _unproven_tree: UnprovenTree) -> UnprovenTree {
todo!()
}
fn simulate_and_commit(&self, tree: UnprovenTree) -> Result<ProofTree, ProverError> {
match tree {
UnprovenTree::UnprovenLeaf(UnprovenLeaf::UnprovenSchnorr(us)) => {
if us.simulated {
if let Some(challenge) = us.challenge_opt {
let (fm, sm) = dlog_protocol::interactive_prover::simulate(
&us.proposition,
&challenge,
);
Ok(ProofTree::UncheckedTree(
UncheckedSchnorr {
proposition: us.proposition,
commitment_opt: Some(fm),
challenge,
second_message: sm,
}
.into(),
))
} else {
Err(ProverError::SimulatedLeafWithoutChallenge)
}
} else {
let (r, commitment) = dlog_protocol::interactive_prover::first_message();
Ok(ProofTree::UnprovenTree(
UnprovenSchnorr {
commitment_opt: Some(commitment),
randomness_opt: Some(r),
..us
}
.into(),
))
}
}
}
}
fn proving(&self, tree: ProofTree) -> Result<ProofTree, ProverError> {
match tree {
ProofTree::UncheckedTree(_) => Ok(tree),
ProofTree::UnprovenTree(unproven_tree) => match unproven_tree {
UnprovenTree::UnprovenLeaf(UnprovenLeaf::UnprovenSchnorr(us))
if unproven_tree.is_real() =>
{
if let Some(challenge) = us.challenge_opt.clone() {
if let Some(priv_key) = self
.secrets()
.iter()
.flat_map(|s| match s {
PrivateInput::DlogProverInput(dl) => vec![dl],
_ => vec![],
})
.find(|prover_input| prover_input.public_image() == us.proposition)
{
let z = dlog_protocol::interactive_prover::second_message(
&priv_key,
us.randomness_opt.unwrap(),
&challenge,
);
Ok(UncheckedSchnorr {
proposition: us.proposition,
commitment_opt: None,
challenge,
second_message: z,
}
.into())
} else {
Err(ProverError::SecretNotFound)
}
} else {
Err(ProverError::RealUnprovenTreeWithoutChallenge)
}
}
_ => todo!(),
},
}
}
}
fn convert_to_unproven(ergo_lib: SigmaBoolean) -> UnprovenTree {
match ergo_lib {
SigmaBoolean::TrivialProp(_) => todo!(),
SigmaBoolean::ProofOfKnowledge(pok) => match pok {
SigmaProofOfKnowledgeTree::ProveDhTuple(_) => todo!(),
SigmaProofOfKnowledgeTree::ProveDlog(prove_dlog) => UnprovenSchnorr {
proposition: prove_dlog,
commitment_opt: None,
randomness_opt: None,
challenge_opt: None,
simulated: false,
}
.into(),
},
SigmaBoolean::Cand(_) => todo!(),
}
}
pub struct TestProver {
pub secrets: Vec<PrivateInput>,
}
impl Evaluator for TestProver {}
impl Prover for TestProver {
fn secrets(&self) -> &[PrivateInput] {
self.secrets.as_ref()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sigma_protocol::private_input::DlogProverInput;
use ergotree_ir::mir::constant::Constant;
use ergotree_ir::mir::expr::Expr;
use ergotree_ir::mir::value::Value;
use ergotree_ir::types::stype::SType;
use sigma_test_util::force_any_val;
use std::rc::Rc;
#[test]
fn test_prove_true_prop() {
let bool_true_tree = ErgoTree::from(Expr::Const(Constant {
tpe: SType::SBoolean,
v: Value::Boolean(true),
}));
let message = vec![0u8; 100];
let prover = TestProver { secrets: vec![] };
let res = prover.prove(
&bool_true_tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
message.as_slice(),
);
assert!(res.is_ok());
assert_eq!(res.unwrap().proof, ProofBytes::Empty);
}
#[test]
fn test_prove_false_prop() {
let bool_false_tree = ErgoTree::from(Expr::Const(Constant {
tpe: SType::SBoolean,
v: Value::Boolean(false),
}));
let message = vec![0u8; 100];
let prover = TestProver { secrets: vec![] };
let res = prover.prove(
&bool_false_tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
message.as_slice(),
);
assert!(res.is_err());
assert_eq!(res.err().unwrap(), ProverError::ReducedToFalse);
}
#[test]
fn test_prove_pk_prop() {
let secret = DlogProverInput::random();
let pk = secret.public_image();
let tree = ErgoTree::from(Expr::Const(pk.into()));
let message = vec![0u8; 100];
let prover = TestProver {
secrets: vec![PrivateInput::DlogProverInput(secret)],
};
let res = prover.prove(
&tree,
&Env::empty(),
Rc::new(force_any_val::<Context>()),
message.as_slice(),
);
assert!(res.is_ok());
assert_ne!(res.unwrap().proof, ProofBytes::Empty);
}
}