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

Issue:
Implement BigInt variant in Value



Discussion / hints:

Fix patch:
diff --git a/ergo-lib/CHANGELOG.md b/ergo-lib/CHANGELOG.md
index c3c4fb74d..a6fc1cea8 100644
--- a/ergo-lib/CHANGELOG.md
+++ b/ergo-lib/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - `proveDlog`, `box.creationInfo`, `Box.id`, `Coll.exists`, `SigmaProp.propBytes`, `Option.isDefined`, `Option.getOrElse` IR nodes (evaluation, serialization) [#209](https://github.com/ergoplatform/sigma-rust/pull/209);
 - `Box.R0..R3` register access [#213](https://github.com/ergoplatform/sigma-rust/pull/213);
 - `-` negation for numeric types evaluation and serialization [#214](https://github.com/ergoplatform/sigma-rust/pull/214);
+- BigInt values support, arithmetic operations (`+, -, *, /`), `-` negation, `Upcast` [#216](https://github.com/ergoplatform/sigma-rust/pull/216);
 
 ## [0.7.0] - 2021-03-03
 
diff --git a/ergo-lib/Cargo.toml b/ergo-lib/Cargo.toml
index 85626ab78..62436db57 100644
--- a/ergo-lib/Cargo.toml
+++ b/ergo-lib/Cargo.toml
@@ -21,17 +21,10 @@ ergotree-interpreter = { version = "^0.7.0", path = "../ergotree-interpreter" }
 ergoscript-compiler = { version = "^0.3.0", path = "../ergoscript-compiler" , optional = true}
 indexmap = "1.3.2"
 base16 = "0.2.1"
-k256 = {version = "0.4.2", features = ["rand", "zeroize"]}
-elliptic-curve = {version = "0.5.0", features = ["rand", "zeroize"]}
 serde = { version = "1.0", features = ["derive"], optional = true }
 serde_json = "1.0"
 thiserror = "1"
-num-bigint = "0.3.0"
-rand = "0.7"
-lazy_static = "1.4"
 derive_more = "0.99"
-impl-trait-for-tuples = "0.2.0"
-num = "0.3.1"
 
 [features]
 default = ["json", "compiler"]
diff --git a/ergo-lib/src/big_integer.rs b/ergo-lib/src/big_integer.rs
deleted file mode 100644
index b7c5feed0..000000000
--- a/ergo-lib/src/big_integer.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-// use k256::Scalar;
-use num_bigint::BigInt;
-// use std::convert::TryFrom;
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct BigInteger(BigInt);
-
-impl From<BigInt> for BigInteger {
-    fn from(b: BigInt) -> Self {
-        BigInteger(b)
-    }
-}
diff --git a/ergo-lib/src/lib.rs b/ergo-lib/src/lib.rs
index cb4311536..710847c62 100644
--- a/ergo-lib/src/lib.rs
+++ b/ergo-lib/src/lib.rs
@@ -13,8 +13,6 @@
 #![allow(clippy::unit_arg)]
 #![deny(broken_intra_doc_links)]
 
-mod big_integer;
-
 pub mod chain;
 pub mod constants;
 pub mod wallet;
diff --git a/ergotree-interpreter/Cargo.toml b/ergotree-interpreter/Cargo.toml
index 9766168a0..aae7316dd 100644
--- a/ergotree-interpreter/Cargo.toml
+++ b/ergotree-interpreter/Cargo.toml
@@ -22,16 +22,12 @@ k256 = { version = "0.4.2", features = ["rand", "zeroize"] }
 blake2 = "0.9"
 elliptic-curve = {version = "0.5.0", features = ["rand", "zeroize"]}
 thiserror = "1"
-num-bigint = "0.3.0"
-rand = "0.7"
-lazy_static = "1.4"
 derive_more = "0.99"
-impl-trait-for-tuples = "0.2.0"
-num = "0.3.1"
+num-traits = "0.2.14"
 proptest = {version = "1.0.0", optional = true }
 proptest-derive = {version = "0.3.0", optional = true }
-bs58 = "0.4.0"
 base16 = "0.2.1"
+num-bigint = "0.4.0"
 
 [features]
 default = []
diff --git a/ergotree-interpreter/src/eval/bin_op.rs b/ergotree-interpreter/src/eval/bin_op.rs
index 004403369..b703160c6 100644
--- a/ergotree-interpreter/src/eval/bin_op.rs
+++ b/ergotree-interpreter/src/eval/bin_op.rs
@@ -8,11 +8,12 @@ use ergotree_ir::mir::constant::TryExtractFrom;
 use ergotree_ir::mir::constant::TryExtractInto;
 use ergotree_ir::mir::value::Value;
 use eval::costs::Costs;
-use num::CheckedAdd;
-use num::CheckedDiv;
-use num::CheckedMul;
-use num::CheckedSub;
-use num::Num;
+use num_bigint::BigInt;
+use num_traits::CheckedAdd;
+use num_traits::CheckedDiv;
+use num_traits::CheckedMul;
+use num_traits::CheckedSub;
+use num_traits::Num;
 
 use crate::eval;
 use crate::eval::env::Env;
@@ -82,7 +83,7 @@ fn eval_ge(lv: Value, rv: Value) -> Result<Value, EvalError> {
         Value::Short(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<i16>()?).into()),
         Value::Int(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<i32>()?).into()),
         Value::Long(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<i64>()?).into()),
-        Value::BigInt => todo!(),
+        Value::BigInt(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<BigInt>()?).into()),
         _ => Err(EvalError::UnexpectedValue(format!(
             "expected BinOp::left to be numeric value, got {0:?}",
             lv
@@ -96,7 +97,7 @@ fn eval_gt(lv: Value, rv: Value) -> Result<Value, EvalError> {
         Value::Short(lv_raw) => Ok((lv_raw > rv.try_extract_into::<i16>()?).into()),
         Value::Int(lv_raw) => Ok((lv_raw > rv.try_extract_into::<i32>()?).into()),
         Value::Long(lv_raw) => Ok((lv_raw > rv.try_extract_into::<i64>()?).into()),
-        Value::BigInt => todo!(),
+        Value::BigInt(lv_raw) => Ok((lv_raw > rv.try_extract_into::<BigInt>()?).into()),
         _ => Err(EvalError::UnexpectedValue(format!(
             "expected BinOp::left to be numeric value, got {0:?}",
             lv
@@ -110,7 +111,7 @@ fn eval_lt(lv: Value, rv: Value) -> Result<Value, EvalError> {
         Value::Short(lv_raw) => Ok((lv_raw < rv.try_extract_into::<i16>()?).into()),
         Value::Int(lv_raw) => Ok((lv_raw < rv.try_extract_into::<i32>()?).into()),
         Value::Long(lv_raw) => Ok((lv_raw < rv.try_extract_into::<i64>()?).into()),
-        Value::BigInt => todo!(),
+        Value::BigInt(lv_raw) => Ok((lv_raw < rv.try_extract_into::<BigInt>()?).into()),
         _ => Err(EvalError::UnexpectedValue(format!(
             "expected BinOp::left to be numeric value, got {0:?}",
             lv
@@ -124,7 +125,7 @@ fn eval_le(lv: Value, rv: Value) -> Result<Value, EvalError> {
         Value::Short(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<i16>()?).into()),
         Value::Int(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<i32>()?).into()),
         Value::Long(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<i64>()?).into()),
-        Value::BigInt => todo!(),
+        Value::BigInt(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<BigInt>()?).into()),
         _ => Err(EvalError::UnexpectedValue(format!(
             "expected BinOp::left to be numeric value, got {0:?}",
             lv
@@ -179,7 +180,7 @@ impl Evaluable for BinOp {
                     Value::Short(lv_raw) => eval_plus(lv_raw, rv()?),
                     Value::Int(lv_raw) => eval_plus(lv_raw, rv()?),
                     Value::Long(lv_raw) => eval_plus(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
+                    Value::BigInt(lv_raw) => eval_plus(lv_raw, rv()?),
                     _ => Err(EvalError::UnexpectedValue(format!(
                         "expected BinOp::left to be numeric value, got {0:?}",
                         lv
@@ -190,7 +191,7 @@ impl Evaluable for BinOp {
                     Value::Short(lv_raw) => eval_minus(lv_raw, rv()?),
                     Value::Int(lv_raw) => eval_minus(lv_raw, rv()?),
                     Value::Long(lv_raw) => eval_minus(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
+                    Value::BigInt(lv_raw) => eval_minus(lv_raw, rv()?),
                     _ => Err(EvalError::UnexpectedValue(format!(
                         "expected BinOp::left to be numeric value, got {0:?}",
                         lv
@@ -201,7 +202,7 @@ impl Evaluable for BinOp {
                     Value::Short(lv_raw) => eval_mul(lv_raw, rv()?),
                     Value::Int(lv_raw) => eval_mul(lv_raw, rv()?),
                     Value::Long(lv_raw) => eval_mul(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
+                    Value::BigInt(lv_raw) => eval_mul(lv_raw, rv()?),
                     _ => Err(EvalError::UnexpectedValue(format!(
                         "expected BinOp::left to be numeric value, got {0:?}",
                         lv
@@ -212,7 +213,7 @@ impl Evaluable for BinOp {
                     Value::Short(lv_raw) => eval_div(lv_raw, rv()?),
                     Value::Int(lv_raw) => eval_div(lv_raw, rv()?),
                     Value::Long(lv_raw) => eval_div(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
+                    Value::BigInt(lv_raw) => eval_div(lv_raw, rv()?),
                     _ => Err(EvalError::UnexpectedValue(format!(
                         "expected BinOp::left to be numeric value, got {0:?}",
                         lv
@@ -223,7 +224,7 @@ impl Evaluable for BinOp {
                     Value::Short(lv_raw) => eval_max(lv_raw, rv()?),
                     Value::Int(lv_raw) => eval_max(lv_raw, rv()?),
                     Value::Long(lv_raw) => eval_max(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
+                    Value::BigInt(lv_raw) => eval_max(lv_raw, rv()?),
                     _ => Err(EvalError::UnexpectedValue(format!(
                         "expected BinOp::left to be numeric value, got {0:?}",
                         lv
@@ -234,7 +235,7 @@ impl Evaluable for BinOp {
                     Value::Short(lv_raw) => eval_min(lv_raw, rv()?),
                     Value::Int(lv_raw) => eval_min(lv_raw, rv()?),
                     Value::Long(lv_raw) => eval_min(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
+                    Value::BigInt(lv_raw) => eval_min(lv_raw, rv()?),
                     _ => Err(EvalError::UnexpectedValue(format!(
                         "expected BinOp::left to be numeric value, got {0:?}",
                         lv
@@ -253,6 +254,7 @@ mod tests {
     use crate::eval::tests::try_eval_out;
     use ergotree_ir::mir::constant::Constant;
     use ergotree_ir::mir::expr::Expr;
+    use num_bigint::ToBigInt;
     use proptest::prelude::*;
     use sigma_test_util::force_any_val;
     use std::rc::Rc;
@@ -459,6 +461,24 @@ mod tests {
             prop_assert_eq!(eval_relation_op(RelationOp::LE, l.into(), r.into()), l <= r);
         }
 
+        #[test]
+        fn test_num_bigint(l_long in any::<i64>(), r_long in any::<i64>()) {
+            let l = l_long.to_bigint().unwrap();
+            let r = r_long.to_bigint().unwrap();
+            prop_assert_eq!(eval_num_op(ArithOp::Plus, l.clone().into(), r.clone().into()).ok(), l.checked_add(&r));
+            prop_assert_eq!(eval_num_op(ArithOp::Minus, l.clone().into(), r.clone().into()).ok(), l.checked_sub(&r));
+            prop_assert_eq!(eval_num_op(ArithOp::Multiply, l.clone().into(), r.clone().into()).ok(), l.checked_mul(&r));
+            prop_assert_eq!(eval_num_op(ArithOp::Divide, l.clone().into(), r.clone().into()).ok(), l.checked_div(&r));
+            prop_assert_eq!(eval_num_op::<BigInt>(ArithOp::Max, l.clone().into(),
+                    r.clone().into()).unwrap(), l.clone().max(r.clone()));
+            prop_assert_eq!(eval_num_op::<BigInt>(ArithOp::Min, l.clone().into(),
+                    r.clone().into()).unwrap(), l.clone().min(r.clone()));
+            prop_assert_eq!(eval_relation_op(RelationOp::GT, l.clone().into(), r.clone().into()), l > r);
+            prop_assert_eq!(eval_relation_op(RelationOp::LT, l.clone().into(), r.clone().into()), l < r);
+            prop_assert_eq!(eval_relation_op(RelationOp::GE, l.clone().into(), r.clone().into()), l >= r);
+            prop_assert_eq!(eval_relation_op(RelationOp::LE, l.clone().into(), r.clone().into()), l <= r);
+        }
+
         #[test]
         fn test_and_or(l in any::<bool>(), r in any::<bool>()) {
             prop_assert_eq!(eval_relation_op(RelationOp::And, l.into(), r.into()), l && r);
diff --git a/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs b/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs
index a909903b7..ee2e189ef 100644
--- a/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs
+++ b/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs
@@ -74,14 +74,13 @@ impl IrErgoBox for IrErgoBoxDummy {
 #[cfg(feature = "arbitrary")]
 /// Arbitrary impl
 pub(crate) mod arbitrary {
+    use num_traits::abs;
     use sigma_util::DIGEST32_SIZE;
 
     use super::*;
     use proptest::collection::vec;
     use proptest::prelude::*;
 
-    use num::abs;
-
     impl Arbitrary for IrErgoBoxDummy {
         type Parameters = ();
 
diff --git a/ergotree-interpreter/src/eval/negation.rs b/ergotree-interpreter/src/eval/negation.rs
index d0406ea24..cadf88b50 100644
--- a/ergotree-interpreter/src/eval/negation.rs
+++ b/ergotree-interpreter/src/eval/negation.rs
@@ -14,6 +14,7 @@ impl Evaluable for Negation {
             Value::Short(v) => Ok((-v).into()),
             Value::Int(v) => Ok((-v).into()),
             Value::Long(v) => Ok((-v).into()),
+            Value::BigInt(v) => Ok((-v).into()),
             _ => Err(EvalError::UnexpectedValue(format!(
                 "Expected Negation input to be numeric value, got {:?}",
                 input_v
@@ -30,7 +31,8 @@ mod tests {
     use ergotree_ir::mir::constant::Constant;
     use ergotree_ir::mir::constant::TryExtractFrom;
     use ergotree_ir::mir::expr::Expr;
-    use num::Num;
+    use num_bigint::ToBigInt;
+    use num_traits::Num;
 
     fn run_eval<T: Num + Into<Constant> + TryExtractFrom<Value>>(input: T) -> T {
         let expr: Expr = Negation::new(Expr::Const(input.into())).unwrap().into();
@@ -43,5 +45,9 @@ mod tests {
         assert_eq!(run_eval(1i16), -1i16);
         assert_eq!(run_eval(1i32), -1i32);
         assert_eq!(run_eval(1i64), -1i64);
+        assert_eq!(
+            run_eval(1i64.to_bigint().unwrap()),
+            (-1i64).to_bigint().unwrap()
+        );
     }
 }
diff --git a/ergotree-interpreter/src/eval/upcast.rs b/ergotree-interpreter/src/eval/upcast.rs
index 454fbacb9..8244bbf18 100644
--- a/ergotree-interpreter/src/eval/upcast.rs
+++ b/ergotree-interpreter/src/eval/upcast.rs
@@ -1,12 +1,27 @@
 use ergotree_ir::mir::upcast::Upcast;
 use ergotree_ir::mir::value::Value;
 use ergotree_ir::types::stype::SType;
+use num_bigint::ToBigInt;
 
 use crate::eval::env::Env;
 use crate::eval::EvalContext;
 use crate::eval::EvalError;
 use crate::eval::Evaluable;
 
+fn upcast_to_bigint(in_v: Value) -> Result<Value, EvalError> {
+    match in_v {
+        Value::Byte(v) => Ok(v.to_bigint().unwrap().into()),
+        Value::Short(v) => Ok(v.to_bigint().unwrap().into()),
+        Value::Int(v) => Ok(v.to_bigint().unwrap().into()),
+        Value::Long(v) => Ok(v.to_bigint().unwrap().into()),
+        Value::BigInt(_) => Ok(in_v),
+        _ => Err(EvalError::UnexpectedValue(format!(
+            "Upcast: cannot upcast {0:?} to Long",
+            in_v
+        ))),
+    }
+}
+
 fn upcast_to_long(in_v: Value) -> Result<Value, EvalError> {
     match in_v {
         Value::Byte(v) => Ok((v as i64).into()),
@@ -57,7 +72,7 @@ impl Evaluable for Upcast {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
         let input_v = self.input.eval(env, ctx)?;
         match self.tpe {
-            SType::SBigInt => todo!("upcasting to BigInt is not yet supported"),
+            SType::SBigInt => upcast_to_bigint(input_v),
             SType::SLong => upcast_to_long(input_v),
             SType::SInt => upcast_to_int(input_v),
             SType::SShort => upcast_to_short(input_v),
@@ -69,3 +84,104 @@ impl Evaluable for Upcast {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use ergotree_ir::mir::constant::Constant;
+    use num_bigint::BigInt;
+    use sigma_test_util::force_any_val;
+
+    use crate::eval::tests::eval_out_wo_ctx;
+
+    use super::*;
+
+    #[test]
+    fn from_byte() {
+        let v = force_any_val::<i8>();
+        let c: Constant = v.into();
+        assert_eq!(
+            eval_out_wo_ctx::<i8>(&Upcast::new(c.clone().into(), SType::SByte).unwrap().into()),
+            v
+        );
+        assert_eq!(
+            eval_out_wo_ctx::<i16>(&Upcast::new(c.clone().into(), SType::SShort).unwrap().into()),
+            v as i16
+        );
+        assert_eq!(
+            eval_out_wo_ctx::<i32>(&Upcast::new(c.clone().into(), SType::SInt).unwrap().into()),
+            v as i32
+        );
+        assert_eq!(
+            eval_out_wo_ctx::<i64>(&Upcast::new(c.clone().into(), SType::SLong).unwrap().into()),
+            v as i64
+        );
+        assert_eq!(
+            eval_out_wo_ctx::<BigInt>(&Upcast::new(c.into(), SType::SBigInt).unwrap().into()),
+            v.to_bigint().unwrap()
+        );
+    }
+
+    #[test]
+    fn from_short() {
+        let v = force_any_val::<i16>();
+        let c: Constant = v.into();
+        assert_eq!(
+            eval_out_wo_ctx::<i16>(&Upcast::new(c.clone().into(), SType::SShort).unwrap().into()),
+            v as i16
+        );
+        assert_eq!(
+            eval_out_wo_ctx::<i32>(&Upcast::new(c.clone().into(), SType::SInt).unwrap().into()),
+            v as i32
+        );
+        assert_eq!(
+            eval_out_wo_ctx::<i64>(&Upcast::new(c.clone().into(), SType::SLong).unwrap().into()),
+            v as i64
+        );
+        assert_eq!(
+            eval_out_wo_ctx::<BigInt>(&Upcast::new(c.into(), SType::SBigInt).unwrap().into()),
+            v.to_bigint().unwrap()
+        );
+    }
+
+    #[test]
+    fn from_int() {
+        let v = force_any_val::<i32>();
+        let c: Constant = v.into();
+        assert_eq!(
+            eval_out_wo_ctx::<i32>(&Upcast::new(c.clone().into(), SType::SInt).unwrap().into()),
+            v as i32
+        );
+        assert_eq!(
+            eval_out_wo_ctx::<i64>(&Upcast::new(c.clone().into(), SType::SLong).unwrap().into()),
+            v as i64
+        );
+        assert_eq!(
+            eval_out_wo_ctx::<BigInt>(&Upcast::new(c.into(), SType::SBigInt).unwrap().into()),
+            v.to_bigint().unwrap()
+        );
+    }
+
+    #[test]
+    fn from_long() {
+        let v = force_any_val::<i64>();
+        let c: Constant = v.into();
+        assert_eq!(
+            eval_out_wo_ctx::<i64>(&Upcast::new(c.clone().into(), SType::SLong).unwrap().into()),
+            v as i64
+        );
+        assert_eq!(
+            eval_out_wo_ctx::<BigInt>(&Upcast::new(c.into(), SType::SBigInt).unwrap().into()),
+            v.to_bigint().unwrap()
+        );
+    }
+
+    #[test]
+    fn from_bigint() {
+        let v = force_any_val::<i64>().to_bigint().unwrap();
+        let c: Constant = v.clone().into();
+        assert_eq!(
+            eval_out_wo_ctx::<BigInt>(&Upcast::new(c.into(), SType::SBigInt).unwrap().into()),
+            v.to_bigint().unwrap()
+        );
+    }
+}
diff --git a/ergotree-ir/Cargo.toml b/ergotree-ir/Cargo.toml
index 3731364da..a1e88825b 100644
--- a/ergotree-ir/Cargo.toml
+++ b/ergotree-ir/Cargo.toml
@@ -16,21 +16,18 @@ crate-type = ["cdylib", "rlib"]
 [dependencies]
 sigma-ser = { version = "^0.2.0", path = "../sigma-ser" }
 sigma-util = { version = "^0.2.0", path = "../sigma-util" }
-indexmap = "1.3.2"
 k256 = { version = "0.4.2", features = ["rand", "zeroize"] }
-# blake2 = "0.9"
 elliptic-curve = {version = "0.5.0", features = ["rand", "zeroize"]}
 thiserror = "1"
-# num-bigint = "0.3.0"
 rand = "0.7"
 lazy_static = "1.4"
 derive_more = "0.99"
 impl-trait-for-tuples = "0.2.0"
-num = "0.3.1"
 proptest = {version = "1.0.0", optional = true }
 proptest-derive = {version = "0.3.0", optional = true }
 bs58 = "0.4.0"
 base16 = "0.2.1"
+num-bigint = "0.4.0"
 
 [features]
 default = []
diff --git a/ergotree-ir/README.md b/ergotree-ir/README.md
index 25f3907f2..e70d4de62 100644
--- a/ergotree-ir/README.md
+++ b/ergotree-ir/README.md
@@ -68,56 +68,6 @@ Descriptions for the operations can be found in [ErgoTree Specification](https:/
 
 ### Object properties and methods
 
-#### Byte
-
-- toByte
-- toShort
-- toInt
-- toLong
-- toBigInt
-- toBytes
-- toBits
-
-#### Short
-
-- toByte
-- toShort
-- toInt
-- toLong
-- toBigInt
-- toBytes
-- toBits
-
-#### Int
-
-- toByte
-- toShort
-- toInt
-- toLong
-- toBigInt
-- toBytes
-- toBits
-
-#### Long
-
-- toByte
-- toShort
-- toInt
-- toLong
-- toBigInt
-- toBytes
-- toBits
-
-#### BigInt
-
-- toByte
-- toShort
-- toInt
-- toLong
-- toBigInt
-- toBytes
-- toBits
-
 #### GroupElement
 
 - getEncoded
diff --git a/ergotree-ir/src/mir/constant.rs b/ergotree-ir/src/mir/constant.rs
index 566fad35d..9d5688962 100644
--- a/ergotree-ir/src/mir/constant.rs
+++ b/ergotree-ir/src/mir/constant.rs
@@ -8,6 +8,7 @@ use crate::sigma_protocol::{dlog_group::EcPoint, sigma_boolean::SigmaProp};
 use crate::types::stype::LiftIntoSType;
 use crate::types::stype::SType;
 use impl_trait_for_tuples::impl_for_tuples;
+use num_bigint::BigInt;
 use std::convert::TryInto;
 
 mod constant_placeholder;
@@ -148,6 +149,15 @@ impl From<ProveDlog> for Constant {
     }
 }
 
+impl From<BigInt> for Constant {
+    fn from(b: BigInt) -> Self {
+        Constant {
+            tpe: SType::SBigInt,
+            v: b.into(),
+        }
+    }
+}
+
 #[impl_for_tuples(2, 4)]
 impl Into<Constant> for Tuple {
     fn into(self) -> Constant {
@@ -198,6 +208,7 @@ pub(crate) mod arbitrary {
 
     use super::*;
     use crate::types::stuple::STuple;
+    use num_bigint::ToBigInt;
     use proptest::collection::vec;
     use proptest::prelude::*;
 
@@ -212,6 +223,7 @@ pub(crate) mod arbitrary {
             any::<i16>().prop_map_into(),
             any::<i32>().prop_map_into(),
             any::<i64>().prop_map_into(),
+            any::<i64>().prop_map(|v| v.to_bigint().unwrap().into()),
             any::<EcPoint>().prop_map_into(),
             any::<SigmaProp>().prop_map_into(),
             // although it's not strictly a primitive type, byte array is widely used as one
@@ -251,7 +263,9 @@ pub(crate) mod arbitrary {
             SType::SShort => any::<i16>().prop_map_into().boxed(),
             SType::SInt => any::<i32>().prop_map_into().boxed(),
             SType::SLong => any::<i64>().prop_map_into().boxed(),
-            // SType::SBigInt => {}
+            SType::SBigInt => any::<i64>()
+                .prop_map(|v| v.to_bigint().unwrap().into())
+                .boxed(),
             SType::SGroupElement => any::<EcPoint>().prop_map_into().boxed(),
             SType::SSigmaProp => any::<SigmaProp>().prop_map_into().boxed(),
             // SType::SBox => {}
@@ -348,6 +362,7 @@ pub(crate) mod arbitrary {
 pub mod tests {
     use super::*;
     use core::fmt;
+    use num_bigint::ToBigInt;
     use proptest::prelude::*;
 
     fn test_constant_roundtrip<T>(v: T)
@@ -361,6 +376,8 @@ pub mod tests {
 
     proptest! {
 
+        #![proptest_config(ProptestConfig::with_cases(8))]
+
         #[test]
         fn bool_roundtrip(v in any::<bool>()) {
             test_constant_roundtrip(v);
@@ -386,6 +403,12 @@ pub mod tests {
             test_constant_roundtrip(v);
         }
 
+        #[test]
+        fn bigint_roundtrip(raw in any::<i64>()) {
+            let v = raw.to_bigint().unwrap();
+            test_constant_roundtrip(v);
+        }
+
         #[test]
         fn group_element_roundtrip(v in any::<EcPoint>()) {
             test_constant_roundtrip(v);
@@ -421,6 +444,18 @@ pub mod tests {
             test_constant_roundtrip(v);
         }
 
+        #[test]
+        fn vec_bigint_roundtrip(raw in any::<Vec<i64>>()) {
+            let v: Vec<BigInt> = raw.into_iter().map(|i| i.to_bigint().unwrap()).collect();
+            test_constant_roundtrip(v);
+        }
+
+        #[test]
+        fn vec_option_bigint_roundtrip(raw in any::<Vec<i64>>()) {
+            let v: Vec<Option<BigInt>> = raw.into_iter().map(|i| i.to_bigint()).collect();
+            test_constant_roundtrip(v);
+        }
+
         #[test]
         fn vec_sigmaprop_roundtrip(v in any::<Vec<SigmaProp>>()) {
             test_constant_roundtrip(v);
diff --git a/ergotree-ir/src/mir/negation.rs b/ergotree-ir/src/mir/negation.rs
index bb35a58f7..12e199259 100644
--- a/ergotree-ir/src/mir/negation.rs
+++ b/ergotree-ir/src/mir/negation.rs
@@ -19,15 +19,16 @@ impl Negation {
 
     /// Create new object, returns an error if any of the requirements failed
     pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        match input.post_eval_tpe() {
-            SType::SByte | SType::SShort | SType::SInt | SType::SLong => Ok(Self {
-                input: input.into(),
-            }),
-            tpe => Err(InvalidArgumentError(format!(
+        let post_eval_tpe = input.post_eval_tpe();
+        if !post_eval_tpe.is_numeric() {
+            return Err(InvalidArgumentError(format!(
                 "Negation: expected input type to be numeric, got {:?}",
-                tpe
-            ))),
+                post_eval_tpe
+            )));
         }
+        Ok(Self {
+            input: input.into(),
+        })
     }
 
     /// Type
diff --git a/ergotree-ir/src/mir/upcast.rs b/ergotree-ir/src/mir/upcast.rs
index 98031d9fc..5692faf4f 100644
--- a/ergotree-ir/src/mir/upcast.rs
+++ b/ergotree-ir/src/mir/upcast.rs
@@ -1,6 +1,7 @@
 //! Numerical upcast
 
 use super::expr::Expr;
+use super::expr::InvalidArgumentError;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -22,6 +23,28 @@ pub struct Upcast {
 impl Upcast {
     pub(crate) const OP_CODE: OpCode = OpCode::UPCAST;
 
+    /// Create new object, returns an error if any of the requirements failed
+    pub fn new(input: Expr, target_tpe: SType) -> Result<Self, InvalidArgumentError> {
+        if !target_tpe.is_numeric() {
+            return Err(InvalidArgumentError(format!(
+                "Upcast: expected target type to be numeric, got {:?}",
+                target_tpe
+            )));
+        }
+        let post_eval_tpe = input.post_eval_tpe();
+        if post_eval_tpe.is_numeric() {
+            Ok(Self {
+                input: input.into(),
+                tpe: target_tpe,
+            })
+        } else {
+            Err(InvalidArgumentError(format!(
+                "Upcast: expected input value type to be numeric, got {:?}",
+                post_eval_tpe
+            )))
+        }
+    }
+
     pub(crate) fn op_code(&self) -> OpCode {
         Self::OP_CODE
     }
@@ -62,10 +85,7 @@ mod arbitrary {
                 tpe: SType::SInt,
                 depth: 2,
             })
-            .prop_map(|input| Upcast {
-                input: Box::new(input),
-                tpe: SType::SLong,
-            })
+            .prop_map(|input| Upcast::new(input, SType::SLong).unwrap())
             .boxed()
         }
     }
diff --git a/ergotree-ir/src/mir/value.rs b/ergotree-ir/src/mir/value.rs
index 943395baa..209d198e2 100644
--- a/ergotree-ir/src/mir/value.rs
+++ b/ergotree-ir/src/mir/value.rs
@@ -4,6 +4,7 @@ use std::convert::TryFrom;
 use std::convert::TryInto;
 
 use impl_trait_for_tuples::impl_for_tuples;
+use num_bigint::BigInt;
 
 use crate::ir_ergo_box::IrBoxId;
 use crate::sigma_protocol::dlog_group::EcPoint;
@@ -105,7 +106,7 @@ pub enum Value {
     /// Long
     Long(i64),
     /// Big integer
-    BigInt,
+    BigInt(BigInt),
     /// GroupElement
     GroupElement(Box<EcPoint>),
     /// Sigma property
@@ -170,6 +171,7 @@ impl StoreWrapped for bool {}
 impl StoreWrapped for i16 {}
 impl StoreWrapped for i32 {}
 impl StoreWrapped for i64 {}
+impl StoreWrapped for BigInt {}
 impl StoreWrapped for IrBoxId {}
 impl StoreWrapped for EcPoint {}
 impl StoreWrapped for SigmaProp {}
@@ -336,6 +338,19 @@ impl TryExtractFrom<Value> for Value {
     }
 }
 
+impl TryExtractFrom<Value> for BigInt {
+    fn try_extract_from(v: Value) -> Result<Self, TryExtractFromError> {
+        match v {
+            Value::BigInt(bi) => Ok(bi),
+            _ => Err(TryExtractFromError(format!(
+                "expected {:?}, found {:?}",
+                std::any::type_name::<Self>(),
+                v
+            ))),
+        }
+    }
+}
+
 impl TryFrom<Value> for ProveDlog {
     type Error = TryExtractFromError;
     fn try_from(cv: Value) -> Result<Self, Self::Error> {
diff --git a/ergotree-ir/src/serialization/data.rs b/ergotree-ir/src/serialization/data.rs
index cdd3cd08c..fe7822eda 100644
--- a/ergotree-ir/src/serialization/data.rs
+++ b/ergotree-ir/src/serialization/data.rs
@@ -1,3 +1,5 @@
+use num_bigint::BigInt;
+
 use crate::mir::constant::TryExtractFromError;
 use crate::mir::constant::TryExtractInto;
 use crate::mir::value::CollKind;
@@ -28,7 +30,11 @@ impl DataSerializer {
             Value::Short(v) => w.put_i16(*v),
             Value::Int(v) => w.put_i32(*v),
             Value::Long(v) => w.put_i64(*v),
-            Value::BigInt => todo!(),
+            Value::BigInt(v) => {
+                let bytes = v.to_signed_bytes_be();
+                w.put_u16(bytes.len() as u16)?;
+                w.write_all(&&bytes)
+            }
             Value::GroupElement(ecp) => ecp.sigma_serialize(w),
             Value::SigmaProp(s) => s.value().sigma_serialize(w),
             Value::CBox(_) => todo!(),
@@ -79,6 +85,18 @@ impl DataSerializer {
             SShort => Value::Short(r.get_i16()?),
             SInt => Value::Int(r.get_i32()?),
             SLong => Value::Long(r.get_i64()?),
+            SBigInt => {
+                let size = r.get_u16()?;
+                if size > 32 {
+                    return Err(SerializationError::ValueOutOfBounds(format!(
+                        "serialized BigInt size {0} bytes exceeds 32",
+                        size
+                    )));
+                }
+                let mut buf = vec![0u8; size as usize];
+                r.read_exact(&mut buf)?;
+                Value::BigInt(BigInt::from_signed_bytes_be(buf.as_slice()))
+            }
             SGroupElement => Value::GroupElement(Box::new(EcPoint::sigma_parse(r)?)),
             SSigmaProp => Value::sigma_prop(SigmaProp::new(SigmaBoolean::sigma_parse(r)?)),
             SColl(elem_type) if **elem_type == SByte => {
diff --git a/ergotree-ir/src/types/stype.rs b/ergotree-ir/src/types/stype.rs
index 142fabd7f..d49c9627a 100644
--- a/ergotree-ir/src/types/stype.rs
+++ b/ergotree-ir/src/types/stype.rs
@@ -3,6 +3,7 @@
 use std::convert::TryInto;
 
 use impl_trait_for_tuples::impl_for_tuples;
+use num_bigint::BigInt;
 
 use crate::ir_ergo_box::IrBoxId;
 use crate::serialization::types::TypeCode;
@@ -84,6 +85,14 @@ impl SType {
     pub fn type_companion(&self) -> Option<Box<STypeCompanion>> {
         todo!()
     }
+
+    /// Check if type is numeric
+    pub fn is_numeric(&self) -> bool {
+        matches!(
+            self,
+            SType::SByte | SType::SShort | SType::SInt | SType::SLong | SType::SBigInt
+        )
+    }
 }
 
 impl From<STuple> for SType {
@@ -170,6 +179,12 @@ impl LiftIntoSType for EcPoint {
     }
 }
 
+impl LiftIntoSType for BigInt {
+    fn stype() -> SType {
+        SType::SBigInt
+    }
+}
+
 impl<T: LiftIntoSType> LiftIntoSType for Option<T> {
     fn stype() -> SType {
         SType::SOption(Box::new(T::stype()))


Test patch: