Repository: ergoplatform/sigma-rust (language: rust)

Issue:
Draft Context for Evaluator
Pass blockchain data context into `Evaluator`.


Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-lib-wasm/src/wallet.rs b/bindings/ergo-lib-wasm/src/wallet.rs
index 5d122bfce..37d31ba85 100644
--- a/bindings/ergo-lib-wasm/src/wallet.rs
+++ b/bindings/ergo-lib-wasm/src/wallet.rs
@@ -1,5 +1,5 @@
 //! Wallet-like features
-use ergo_lib::chain::ergo_box::ErgoBox;
+use ergo_lib::chain;
 use wasm_bindgen::prelude::*;
 
 use crate::{
@@ -28,7 +28,9 @@ impl Wallet {
     }
 
     /// Sign a transaction:
-    /// `boxes_to_spend` - unspent boxes [`ErgoBoxCandidate`] used as inputs in the transaction
+    /// `tx` - transaction to sign
+    /// `boxes_to_spend` - boxes corresponding to [`UnsignedTransaction::inputs`]
+    /// `data_boxes` - boxes corresponding to [`UnsignedTransaction::data_inputs`]
     #[wasm_bindgen]
     pub fn sign_transaction(
         &self,
@@ -37,15 +39,15 @@ impl Wallet {
         boxes_to_spend: &ErgoBoxes,
         data_boxes: &ErgoBoxes,
     ) -> Result<Transaction, JsValue> {
-        let boxes_to_spend: Vec<ErgoBox> = boxes_to_spend.clone().into();
-        let data_boxes: Vec<ErgoBox> = data_boxes.clone().into();
+        let boxes_to_spend: Vec<chain::ergo_box::ErgoBox> = boxes_to_spend.clone().into();
+        let data_boxes: Vec<chain::ergo_box::ErgoBox> = data_boxes.clone().into();
+        let tx_context = ergo_lib::wallet::signing::TransactionContext {
+            spending_tx: tx.clone().into(),
+            boxes_to_spend,
+            data_boxes,
+        };
         self.0
-            .sign_transaction(
-                tx.clone().into(),
-                boxes_to_spend.as_slice(),
-                data_boxes.as_slice(),
-                &_state_context.clone().into(),
-            )
+            .sign_transaction(tx_context, &_state_context.clone().into())
             .map_err(|e| JsValue::from_str(&format!("{}", e)))
             .map(Transaction::from)
     }
diff --git a/ergo-lib/CHANGELOG.md b/ergo-lib/CHANGELOG.md
index 5cd0794d6..165796375 100644
--- a/ergo-lib/CHANGELOG.md
+++ b/ergo-lib/CHANGELOG.md
@@ -16,7 +16,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Changed
 
-- moved and change visibility of various modules(input, data_input, prover_result, etc.) [#135](https://github.com/ergoplatform/sigma-rust/pull/135) 
+- Moved and changed visibility of various modules(input, data_input, prover_result, etc.) [#135](https://github.com/ergoplatform/sigma-rust/pull/135)
+- Added Context parameter to Prover::prove, Verifier::verify [#139](https://github.com/ergoplatform/sigma-rust/pull/139)
+- Moved all transaction-related parameters into TransactionContext parameter in Wallet::sign_transaction [#139](https://github.com/ergoplatform/sigma-rust/pull/139)
 
 ## [0.3.0] - 2020-11-04
 
diff --git a/ergo-lib/src/ast.rs b/ergo-lib/src/ast.rs
index e39109e1f..178b02347 100644
--- a/ergo-lib/src/ast.rs
+++ b/ergo-lib/src/ast.rs
@@ -1,4 +1,7 @@
 //! AST for ErgoTree
+use crate::eval::cost_accum::CostAccumulator;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
 use crate::{serialization::op_code::OpCode, types::*};
 use core::fmt;
 use Expr::*;
@@ -121,6 +124,22 @@ pub enum ContextMethods {
     Inputs,
     /// Tx outputs
     Outputs,
+    /// Current blockchain height
+    Height,
+}
+
+impl Evaluable for ContextMethods {
+    fn eval(
+        &self,
+        _env: &crate::eval::Env,
+        _ca: &mut CostAccumulator,
+        ctx: &crate::eval::context::Context,
+    ) -> Result<Constant, EvalError> {
+        match self {
+            ContextMethods::Height => Ok(ctx.height.clone()),
+            _ => Err(EvalError::UnexpectedExpr),
+        }
+    }
 }
 
 #[derive(PartialEq, Eq, Debug, Clone)]
diff --git a/ergo-lib/src/eval.rs b/ergo-lib/src/eval.rs
index 69dc8fafe..4f9d6381b 100644
--- a/ergo-lib/src/eval.rs
+++ b/ergo-lib/src/eval.rs
@@ -1,19 +1,20 @@
 //! Interpreter
 use crate::{
-    ast::{ops::BinOp, ops::NumOp, Constant, ConstantVal, Expr},
+    ast::{Constant, ConstantVal, Expr},
     sigma_protocol::sigma_boolean::SigmaBoolean,
     types::SType,
 };
 
 use cost_accum::CostAccumulator;
 use thiserror::Error;
-use value::Value;
 
-mod cost_accum;
+use self::context::Context;
+
+pub(crate) mod context;
+pub(crate) mod cost_accum;
 mod costs;
-mod value;
 
-/// Environment vars for script interpreter
+/// Environment for the interpreter
 pub struct Env();
 
 impl Env {
@@ -35,61 +36,101 @@ pub enum EvalError {
     UnexpectedExpr,
 }
 
-/// Result of ErgoTree reduction procedure (see `reduce_to_crypto`).
+/// Result of expression reduction procedure (see `reduce_to_crypto`).
 pub struct ReductionResult {
     /// value of SigmaProp type which represents a statement verifiable via sigma protocol.
     pub sigma_prop: SigmaBoolean,
-    /// estimated cost of contract execution
+    /// estimated cost of expression evaluation
     pub cost: u64,
 }
 
 /// Interpreter
 pub trait Evaluator {
-    /// This method is used in both prover and verifier to compute SigmaBoolean value.
-    fn reduce_to_crypto(&self, expr: &Expr, env: &Env) -> Result<ReductionResult, EvalError> {
+    /// Evaluate the given expression by reducing it to SigmaBoolean value.
+    fn reduce_to_crypto(
+        &self,
+        expr: &Expr,
+        env: &Env,
+        ctx: &Context,
+    ) -> Result<ReductionResult, EvalError> {
         let mut ca = CostAccumulator::new(0, None);
-        eval(expr, env, &mut ca).and_then(|v| match v {
-            Value::Boolean(b) => Ok(ReductionResult {
-                sigma_prop: SigmaBoolean::TrivialProp(b),
-                cost: 0,
-            }),
-            Value::SigmaProp(sb) => Ok(ReductionResult {
-                sigma_prop: *sb,
-                cost: 0,
-            }),
-            _ => Err(EvalError::InvalidResultType),
+        eval(expr, env, &mut ca, ctx).and_then(|v| -> Result<ReductionResult, EvalError> {
+            match v {
+                Constant {
+                    tpe: SType::SBoolean,
+                    v: ConstantVal::Boolean(b),
+                } => Ok(ReductionResult {
+                    sigma_prop: SigmaBoolean::TrivialProp(b),
+                    cost: 0,
+                }),
+                Constant {
+                    tpe: SType::SSigmaProp,
+                    v: ConstantVal::SigmaProp(sp),
+                } => Ok(ReductionResult {
+                    sigma_prop: sp.value().clone(),
+                    cost: 0,
+                }),
+                _ => Err(EvalError::InvalidResultType),
+            }
         })
     }
 }
 
+/// Expression evaluation.
+/// Should be implemented by every node that can be evaluated.
+pub trait Evaluable {
+    /// Evaluation routine to be implement by each node
+    fn eval(
+        &self,
+        env: &Env,
+        ca: &mut CostAccumulator,
+        ctx: &Context,
+    ) -> Result<Constant, EvalError>;
+}
+
 #[allow(unconditional_recursion)]
-fn eval(expr: &Expr, env: &Env, ca: &mut CostAccumulator) -> Result<Value, EvalError> {
+fn eval(
+    expr: &Expr,
+    env: &Env,
+    ca: &mut CostAccumulator,
+    ctx: &Context,
+) -> Result<Constant, EvalError> {
     match expr {
-        Expr::Const(Constant {
-            tpe: SType::SBoolean,
-            v: ConstantVal::Boolean(b),
-        }) => Ok(Value::Boolean(*b)),
-        Expr::Const(Constant {
-            tpe: SType::SSigmaProp,
-            v: ConstantVal::SigmaProp(sp),
-        }) => Ok(Value::SigmaProp(Box::new((*sp.value()).clone()))),
+        Expr::Const(c) => Ok(c.clone()),
         Expr::Coll { .. } => todo!(),
         Expr::Tup { .. } => todo!(),
         Expr::PredefFunc(_) => todo!(),
         Expr::CollM(_) => todo!(),
         Expr::BoxM(_) => todo!(),
-        Expr::CtxM(_) => todo!(),
+        Expr::CtxM(v) => v.eval(env, ca, ctx),
         Expr::MethodCall { .. } => todo!(),
-        Expr::BinOp(bin_op, l, r) => {
-            let v_l = eval(l, env, ca)?;
-            let v_r = eval(r, env, ca)?;
+        Expr::BinOp(_bin_op, l, r) => {
+            let _v_l = eval(l, env, ca, ctx)?;
+            let _v_r = eval(r, env, ca, ctx)?;
             ca.add_cost_of(expr);
-            Ok(match bin_op {
-                BinOp::Num(op) => match op {
-                    NumOp::Add => v_l + v_r,
-                },
-            })
+            todo!()
+            // Ok(match bin_op {
+            //     BinOp::Num(op) => match op {
+            //         NumOp::Add => v_l + v_r,
+            //     },
+            // })
         }
         _ => Err(EvalError::UnexpectedExpr),
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::ast::ContextMethods;
+    use crate::ast::TryExtractFrom;
+
+    use super::*;
+
+    #[test]
+    fn height() {
+        let expr = Expr::CtxM(ContextMethods::Height);
+        let mut ca = CostAccumulator::new(0, None);
+        let res = eval(&expr, &Env::empty(), &mut ca, &Context::dummy()).unwrap();
+        assert_eq!(i32::try_extract_from(res).unwrap(), 0);
+    }
+}
diff --git a/ergo-lib/src/eval/context.rs b/ergo-lib/src/eval/context.rs
new file mode 100644
index 000000000..f4d79ec69
--- /dev/null
+++ b/ergo-lib/src/eval/context.rs
@@ -0,0 +1,28 @@
+use crate::ast::Constant;
+use crate::chain::ergo_state_context::ErgoStateContext;
+use crate::wallet::signing::TransactionContext;
+
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct Context {
+    pub height: Constant,
+}
+
+impl Context {
+    #[cfg(test)]
+    pub fn dummy() -> Self {
+        Context {
+            height: 0i32.into(),
+        }
+    }
+
+    pub fn new(
+        _state_ctx: &ErgoStateContext,
+        _tx_ctx: &TransactionContext,
+        _self_index: usize,
+    ) -> Self {
+        // TODO: implement
+        Context {
+            height: 0i32.into(),
+        }
+    }
+}
diff --git a/ergo-lib/src/eval/value.rs b/ergo-lib/src/eval/value.rs
deleted file mode 100644
index b89f660e6..000000000
--- a/ergo-lib/src/eval/value.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-use crate::{chain::ergo_box::ErgoBox, sigma_protocol::sigma_boolean::SigmaBoolean};
-use std::ops::Add;
-
-#[allow(dead_code)]
-pub enum Value {
-    Boolean(bool),
-    Byte(i8),
-    Short(i16),
-    Int(i32),
-    Long(i64),
-    BigInt,
-    GroupElement,
-    SigmaProp(Box<SigmaBoolean>),
-    Box(Box<ErgoBox>),
-    AvlTree,
-    Coll(Vec<Value>),
-    Tup(Vec<Value>),
-}
-
-impl Value {
-    #[inline]
-    pub fn byte(value: i8) -> Self {
-        Value::Byte(value)
-    }
-}
-
-impl Add for Value {
-    type Output = Self;
-    fn add(self, other: Self) -> Self {
-        match (self, other) {
-            (Value::Byte(s), Value::Byte(o)) => Self::byte(s + o),
-            _ => todo!(),
-        }
-    }
-}
diff --git a/ergo-lib/src/sigma_protocol/prover.rs b/ergo-lib/src/sigma_protocol/prover.rs
index 1817447de..57a0e1ed1 100644
--- a/ergo-lib/src/sigma_protocol/prover.rs
+++ b/ergo-lib/src/sigma_protocol/prover.rs
@@ -16,6 +16,7 @@ use super::{
     UncheckedTree, UnprovenLeaf, UnprovenSchnorr, UnprovenTree,
 };
 use crate::ergo_tree::{ErgoTree, ErgoTreeParsingError};
+use crate::eval::context::Context;
 use crate::eval::{Env, EvalError, Evaluator};
 use thiserror::Error;
 
@@ -67,11 +68,12 @@ pub trait Prover: Evaluator {
         &self,
         tree: &ErgoTree,
         env: &Env,
+        ctx: &Context,
         message: &[u8],
     ) -> Result<ProverResult, ProverError> {
         let expr = tree.proposition()?;
         let proof = self
-            .reduce_to_crypto(expr.as_ref(), env)
+            .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),
@@ -314,7 +316,12 @@ mod tests {
         let message = vec![0u8; 100];
 
         let prover = TestProver { secrets: vec![] };
-        let res = prover.prove(&bool_true_tree, &Env::empty(), message.as_slice());
+        let res = prover.prove(
+            &bool_true_tree,
+            &Env::empty(),
+            &Context::dummy(),
+            message.as_slice(),
+        );
         assert!(res.is_ok());
         assert_eq!(res.unwrap().proof, ProofBytes::Empty);
     }
@@ -328,7 +335,12 @@ mod tests {
         let message = vec![0u8; 100];
 
         let prover = TestProver { secrets: vec![] };
-        let res = prover.prove(&bool_false_tree, &Env::empty(), message.as_slice());
+        let res = prover.prove(
+            &bool_false_tree,
+            &Env::empty(),
+            &Context::dummy(),
+            message.as_slice(),
+        );
         assert!(res.is_err());
         assert_eq!(res.err().unwrap(), ProverError::ReducedToFalse);
     }
@@ -346,7 +358,7 @@ mod tests {
         let prover = TestProver {
             secrets: vec![PrivateInput::DlogProverInput(secret)],
         };
-        let res = prover.prove(&tree, &Env::empty(), message.as_slice());
+        let res = prover.prove(&tree, &Env::empty(), &Context::dummy(), message.as_slice());
         assert!(res.is_ok());
         assert_ne!(res.unwrap().proof, ProofBytes::Empty);
     }
diff --git a/ergo-lib/src/sigma_protocol/verifier.rs b/ergo-lib/src/sigma_protocol/verifier.rs
index 9bacf4829..7da2186af 100644
--- a/ergo-lib/src/sigma_protocol/verifier.rs
+++ b/ergo-lib/src/sigma_protocol/verifier.rs
@@ -9,6 +9,7 @@ use super::{
     SigmaBoolean, UncheckedSigmaTree, UncheckedTree,
 };
 use crate::ergo_tree::{ErgoTree, ErgoTreeParsingError};
+use crate::eval::context::Context;
 use crate::eval::{Env, EvalError, Evaluator};
 use dlog_protocol::FirstDlogProverMessage;
 
@@ -51,11 +52,12 @@ pub trait Verifier: Evaluator {
         &self,
         tree: &ErgoTree,
         env: &Env,
+        ctx: &Context,
         proof: &ProofBytes,
         message: &[u8],
     ) -> Result<VerificationResult, VerifierError> {
         let expr = tree.proposition()?;
-        let cprop = self.reduce_to_crypto(expr.as_ref(), env)?.sigma_prop;
+        let cprop = self.reduce_to_crypto(expr.as_ref(), env, ctx)?.sigma_prop;
         let res: bool = match cprop {
             SigmaBoolean::TrivialProp(b) => b,
             sb => {
@@ -144,11 +146,11 @@ mod tests {
             let prover = TestProver {
                 secrets: vec![PrivateInput::DlogProverInput(secret)],
             };
-            let res = prover.prove(&tree, &Env::empty(), message.as_slice());
+            let res = prover.prove(&tree, &Env::empty(), &Context::dummy(), message.as_slice());
             let proof = res.unwrap().proof;
 
             let verifier = TestVerifier;
-            let ver_res = verifier.verify(&tree, &Env::empty(), &proof, message.as_slice());
+            let ver_res = verifier.verify(&tree, &Env::empty(), &Context::dummy(),  &proof, message.as_slice());
             prop_assert_eq!(ver_res.unwrap().result, true);
         }
     }
@@ -247,6 +249,7 @@ mod tests {
         let ver_res = verifier.verify(
             &ergo_tree,
             &Env::empty(),
+            &Context::dummy(),
             &tx.inputs.get(1).unwrap().spending_proof.proof,
             message.as_slice(),
         );
diff --git a/ergo-lib/src/wallet.rs b/ergo-lib/src/wallet.rs
index 482f5566b..439a5d51f 100644
--- a/ergo-lib/src/wallet.rs
+++ b/ergo-lib/src/wallet.rs
@@ -9,17 +9,15 @@ use secret_key::SecretKey;
 use signing::{sign_transaction, TxSigningError};
 use thiserror::Error;
 
-use crate::{
-    chain::{
-        ergo_box::ErgoBox, ergo_state_context::ErgoStateContext,
-        transaction::unsigned::UnsignedTransaction, transaction::Transaction,
-    },
-    sigma_protocol::{
-        private_input::PrivateInput,
-        prover::{Prover, TestProver},
-    },
+use crate::chain::ergo_state_context::ErgoStateContext;
+use crate::chain::transaction::Transaction;
+use crate::sigma_protocol::{
+    private_input::PrivateInput,
+    prover::{Prover, TestProver},
 };
 
+use self::signing::TransactionContext;
+
 /// Wallet
 pub struct Wallet {
     prover: Box<dyn Prover>,
@@ -53,18 +51,9 @@ impl Wallet {
     /// Signs a transaction
     pub fn sign_transaction(
         &self,
-        tx: UnsignedTransaction,
-        boxes_to_spend: &[ErgoBox],
-        _data_boxes: &[ErgoBox],
-        _state_context: &ErgoStateContext,
+        tx_context: TransactionContext,
+        state_context: &ErgoStateContext,
     ) -> Result<Transaction, WalletError> {
-        sign_transaction(
-            self.prover.as_ref(),
-            tx,
-            boxes_to_spend,
-            _data_boxes,
-            _state_context,
-        )
-        .map_err(WalletError::from)
+        sign_transaction(self.prover.as_ref(), tx_context, state_context).map_err(WalletError::from)
     }
 }
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index 741ade0b7..5a1812f26 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -1,6 +1,7 @@
 //! Transaction signing
 
 use crate::chain::transaction::Input;
+use crate::eval::context::Context;
 use crate::{
     chain::{
         ergo_box::ErgoBox,
@@ -24,25 +25,38 @@ pub enum TxSigningError {
     InputBoxNotFound(usize),
 }
 
+/// Transaction and an additional info required for signing
+#[derive(PartialEq, Debug, Clone)]
+pub struct TransactionContext {
+    /// Unsigned transaction to sign
+    pub spending_tx: UnsignedTransaction,
+    /// Boxes corresponding to [`UnsignedTransaction::inputs`]
+    pub boxes_to_spend: Vec<ErgoBox>,
+    /// Boxes corresponding to [`UnsignedTransaction::data_inputs`]
+    pub data_boxes: Vec<ErgoBox>,
+}
+
 /// Signs a transaction (generating proofs for inputs)
 pub fn sign_transaction(
     prover: &dyn Prover,
-    tx: UnsignedTransaction,
-    boxes_to_spend: &[ErgoBox],
-    _data_boxes: &[ErgoBox],
-    _state_context: &ErgoStateContext,
+    tx_context: TransactionContext,
+    state_context: &ErgoStateContext,
 ) -> Result<Transaction, TxSigningError> {
+    let tx = tx_context.spending_tx.clone();
     let message_to_sign = tx.bytes_to_sign();
     let mut signed_inputs: Vec<Input> = vec![];
-    boxes_to_spend
+    tx_context
+        .boxes_to_spend
         .iter()
         .enumerate()
         .try_for_each(|(idx, input_box)| {
             if let Some(unsigned_input) = tx.inputs.get(idx) {
+                let ctx = Context::new(state_context, &tx_context, idx);
                 prover
                     .prove(
                         &input_box.ergo_tree,
                         &Env::empty(),
+                        &ctx,
                         message_to_sign.as_slice(),
                     )
                     .map(|proof| {
@@ -99,6 +113,7 @@ mod tests {
                 let res = verifier.verify(
                     &b.ergo_tree,
                     &Env::empty(),
+                    &Context::dummy(),
                     &input.spending_proof.proof,
                     &message,
                 )?;
@@ -138,9 +153,9 @@ mod tests {
                 .build().unwrap();
             let output_candidates = vec![candidate];
             let tx = UnsignedTransaction::new(inputs, vec![], output_candidates);
-
-            let res = sign_transaction(Box::new(prover).as_ref(), tx, boxes_to_spend.as_slice(), vec![].as_slice(),
-                                       &ErgoStateContext::dummy());
+            let tx_context = TransactionContext { spending_tx: tx,
+                                                  boxes_to_spend: boxes_to_spend.clone(), data_boxes: vec![] };
+            let res = sign_transaction(Box::new(prover).as_ref(), tx_context, &ErgoStateContext::dummy());
             let signed_tx = res.unwrap();
             prop_assert!(verify_tx_proofs(&signed_tx, &boxes_to_spend).unwrap());
         }


Test patch: