From 3a712722756f97095eb99ae3745981801c5ea8e9 Mon Sep 17 00:00:00 2001 From: Kamal Ahmad Date: Tue, 12 Nov 2024 16:26:35 +0500 Subject: [PATCH] Replace BigInt256 internals with bnum num256 has no no_std support and uses BigInt internally, so it'll be less efficient for fixed-size ops compared to bnum --- Cargo.toml | 16 +- bindings/ergo-lib-wasm/src/ast.rs | 5 +- ergo-chain-generation/src/chain_generation.rs | 8 +- ergo-chain-generation/src/fake_pow_scheme.rs | 7 +- ergo-nipopow/src/autolykos_pow_scheme.rs | 8 +- ergo-nipopow/src/nipopow_algos.rs | 4 +- ergotree-interpreter/src/eval/bin_op.rs | 36 +- .../src/eval/byte_array_to_bigint.rs | 9 +- ergotree-interpreter/src/eval/downcast.rs | 18 +- ergotree-interpreter/src/eval/exponentiate.rs | 4 +- ergotree-interpreter/src/eval/upcast.rs | 2 +- ergotree-ir/Cargo.toml | 2 +- ergotree-ir/src/bigint256.rs | 331 +++++++++--------- ergotree-ir/src/mir/bin_op.rs | 1 - ergotree-ir/src/mir/expr.rs | 1 - ergotree-ir/src/mir/value.rs | 2 +- ergotree-ir/src/pretty_printer.rs | 4 +- ergotree-ir/src/serialization/data.rs | 21 +- ergotree-ir/src/sigma_protocol/dlog_group.rs | 122 +++---- .../src/sigma_protocol/sigma_boolean.rs | 1 - 20 files changed, 277 insertions(+), 325 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4390e2663..ee934a1f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,16 +35,26 @@ ergotree-interpreter = { version = "^0.28.0", path = "./ergotree-interpreter" } ergo-nipopow = { version = "^0.15", path = "./ergo-nipopow" } ergo-merkle-tree = { version = "^0.15.0", path = "./ergo-merkle-tree" } ergo-rest = { version = "^0.13.0", path = "./ergo-rest" } -ergo-lib = { version = "^0.28.0", path = "./ergo-lib"} +ergo-lib = { version = "^0.28.0", path = "./ergo-lib" } k256 = { version = "0.13.1", features = ["arithmetic", "ecdsa"] } elliptic-curve = { version = "0.12", features = ["ff"] } thiserror = "1" bounded-vec = { version = "^0.7.0" } bitvec = { version = "1.0.1" } -derive_more = "0.99" +derive_more = { version = "0.99", features = [ + "add", + "add_assign", + "mul", + "not", + "from", + "into", + "try_into", + "from_str", + "display", +] } blake2 = "0.10" sha2 = "0.10" -num-derive = "0.3.3" +num-derive = "0.4.2" num-traits = "0.2.14" num-integer = "0.1.44" num-bigint = "0.4.0" diff --git a/bindings/ergo-lib-wasm/src/ast.rs b/bindings/ergo-lib-wasm/src/ast.rs index 4968fc528..a7759f66f 100644 --- a/bindings/ergo-lib-wasm/src/ast.rs +++ b/bindings/ergo-lib-wasm/src/ast.rs @@ -95,7 +95,10 @@ impl Constant { /// Create BigInt constant from byte array (signed bytes bit-endian) pub fn from_bigint_signed_bytes_be(num: &[u8]) -> Result { Ok(Constant( - ergo_lib::ergotree_ir::mir::constant::Constant::from(BigInt256::try_from(num)?), + ergo_lib::ergotree_ir::mir::constant::Constant::from( + BigInt256::from_be_slice(num) + .ok_or_else(|| String::from("BigInt256: out of bounds"))?, + ), )) } diff --git a/ergo-chain-generation/src/chain_generation.rs b/ergo-chain-generation/src/chain_generation.rs index eb00e088c..9367cb013 100644 --- a/ergo-chain-generation/src/chain_generation.rs +++ b/ergo-chain-generation/src/chain_generation.rs @@ -10,6 +10,7 @@ use ergo_lib::{ transaction::{prover_result::ProverResult, Input, Transaction, TxIoVec}, }, ergo_chain_types::{BlockId, Digest32}, + ergotree_ir::sigma_protocol::dlog_group::order_bigint, }; use ergo_lib::{ ergo_chain_types::ADDigest, @@ -21,7 +22,6 @@ use ergo_lib::{ chain::ergo_box::{box_value::BoxValue, BoxId}, ergo_tree::ErgoTree, serialization::{sigma_byte_writer::SigmaByteWriter, SigmaSerializable}, - sigma_protocol::dlog_group::order, }, }; use ergo_merkle_tree::{MerkleNode, MerkleTree}; @@ -196,7 +196,7 @@ fn prove_block( .0 .to_vec(); // Order of the secp256k1 elliptic curve - let order = order(); + let order = order_bigint(); let target_b = order.clone() / ergo_nipopow::decode_compact_bits(header.n_bits); let x = DlogProverInput::random(); @@ -293,8 +293,8 @@ fn generate_element( concat.extend(pk); concat.extend(msg); concat.extend(w); - let valid_range = (BigInt::from(2_u8).pow(256) / order()) * order(); - numeric_hash(&concat, valid_range, order()) + let valid_range = (BigInt::from(2_u8).pow(256) / order_bigint()) * order_bigint(); + numeric_hash(&concat, valid_range, order_bigint()) } else { // Autolykos v. 2: H(j|h|M) (line 5 from the Algo 2 of the spec) let mut concat = vec![]; diff --git a/ergo-chain-generation/src/fake_pow_scheme.rs b/ergo-chain-generation/src/fake_pow_scheme.rs index 03b6cfeef..bf464bf47 100644 --- a/ergo-chain-generation/src/fake_pow_scheme.rs +++ b/ergo-chain-generation/src/fake_pow_scheme.rs @@ -8,13 +8,12 @@ #[cfg(test)] mod tests { use ergo_lib::ergo_chain_types::{blake2b256_hash, ADDigest, BlockId, Digest32}; + use ergo_lib::ergotree_ir::sigma_protocol::dlog_group::order_bigint; use ergo_nipopow::{NipopowAlgos, NipopowProof}; use ergo_chain_types::{AutolykosSolution, Header, Votes}; use ergo_lib::ergotree_interpreter::sigma_protocol::private_input::DlogProverInput; - use ergo_lib::ergotree_ir::{ - serialization::sigma_byte_writer::SigmaByteWriter, sigma_protocol::dlog_group::order, - }; + use ergo_lib::ergotree_ir::serialization::sigma_byte_writer::SigmaByteWriter; use ergo_nipopow::PoPowHeader; use num_bigint::BigInt; use rand::{thread_rng, Rng}; @@ -149,7 +148,7 @@ mod tests { let (sk, _) = default_miner_secret(); let nonce: Vec = std::iter::repeat(0_u8).take(8).collect(); - let d = order() / (height + 1); + let d = order_bigint() / (height + 1); let autolykos_solution = AutolykosSolution { miner_pk: sk.public_key().unwrap().public_key.into(), pow_onetime_pk: Some(x.public_image().h), diff --git a/ergo-nipopow/src/autolykos_pow_scheme.rs b/ergo-nipopow/src/autolykos_pow_scheme.rs index bcaeeb0f5..b596288f8 100644 --- a/ergo-nipopow/src/autolykos_pow_scheme.rs +++ b/ergo-nipopow/src/autolykos_pow_scheme.rs @@ -203,7 +203,7 @@ pub enum AutolykosPowSchemeError { #[allow(clippy::unwrap_used)] #[cfg(test)] mod tests { - use ergotree_ir::{serialization::SigmaSerializable, sigma_protocol::dlog_group::order}; + use ergotree_ir::{serialization::SigmaSerializable, sigma_protocol::dlog_group::order_bigint}; use crate::nipopow_algos::decode_compact_bits; @@ -256,7 +256,7 @@ mod tests { "adProofsId" : "dec129290a763f4de41f04e87e2b661dd59758af6bdd00dd51f5d97c3a8cb9b5", "transactionsId" : "eba1dd82cf51147232e09c1f72b37c554c30f63274d5093bff36849a83472a42", "parentId" : "ac2101807f0000ca01ff0119db227f202201007f62000177a080005d440896d0" - } + } "#; let header: Header = serde_json::from_str(json).unwrap(); @@ -285,7 +285,7 @@ mod tests { let decoded = decode_compact_bits(header.n_bits); // Target `b` from encoded difficulty `nBits` - let target_b = order() / decoded; + let target_b = order_bigint() / decoded; assert_eq!( target_b, BigInt::parse_bytes( @@ -324,7 +324,7 @@ mod tests { let decoded = decode_compact_bits(header.n_bits); // Target `b` from encoded difficulty `nBits` - let target_b = order() / decoded; + let target_b = order_bigint() / decoded; let hit = pow.pow_hit(&header).unwrap(); assert!(hit >= target_b); diff --git a/ergo-nipopow/src/nipopow_algos.rs b/ergo-nipopow/src/nipopow_algos.rs index fdeb7f916..417d57b9e 100644 --- a/ergo-nipopow/src/nipopow_algos.rs +++ b/ergo-nipopow/src/nipopow_algos.rs @@ -1,5 +1,5 @@ use ergo_chain_types::Header; -use ergotree_ir::sigma_protocol::dlog_group::order; +use ergotree_ir::sigma_protocol::dlog_group::order_bigint; use num_bigint::BigInt; use num_traits::ToPrimitive; use std::convert::TryInto; @@ -93,7 +93,7 @@ impl NipopowAlgos { let genesis_header = header.height == 1; if !genesis_header { // Order of the secp256k1 elliptic curve - let order = order(); + let order = order_bigint(); #[allow(clippy::unwrap_used)] let required_target = (order / decode_compact_bits(header.n_bits)) .to_f64() diff --git a/ergotree-interpreter/src/eval/bin_op.rs b/ergotree-interpreter/src/eval/bin_op.rs index 58de9d1bb..e77ab254b 100644 --- a/ergotree-interpreter/src/eval/bin_op.rs +++ b/ergotree-interpreter/src/eval/bin_op.rs @@ -627,24 +627,24 @@ mod tests { fn test_num_bigint(l_long in any::(), r_long in any::()) { let l = BigInt256::from(l_long); let r = BigInt256::from(r_long); - prop_assert_eq!(eval_arith_op(ArithOp::Plus, l.clone(), r.clone()).ok(), l.checked_add(&r)); - prop_assert_eq!(eval_arith_op(ArithOp::Minus, l.clone(), r.clone()).ok(), l.checked_sub(&r)); - prop_assert_eq!(eval_arith_op(ArithOp::Multiply, l.clone(), r.clone()).ok(), l.checked_mul(&r)); - prop_assert_eq!(eval_arith_op(ArithOp::Divide, l.clone(), r.clone()).ok(), l.checked_div(&r)); - prop_assert_eq!(eval_arith_op(ArithOp::Modulo, l.clone(), r.clone()).ok(), l.checked_rem(&r)); - prop_assert_eq!(eval_arith_op::(ArithOp::Max, l.clone(), - r.clone()).unwrap(), l.clone().max(r.clone())); - prop_assert_eq!(eval_arith_op::(ArithOp::Min, l.clone(), - r.clone()).unwrap(), l.clone().min(r.clone())); - - prop_assert_eq!(eval_bit_op(BitOp::BitAnd, l.clone(), r.clone()), Ok(&l & &r)); - prop_assert_eq!(eval_bit_op(BitOp::BitOr, l.clone(), r.clone()), Ok(&l | &r)); - prop_assert_eq!(eval_bit_op(BitOp::BitXor, l.clone(), r.clone()), Ok(&l ^ &r)); - - prop_assert_eq!(eval_relation_op(RelationOp::Gt, l.clone(), r.clone()), l > r); - prop_assert_eq!(eval_relation_op(RelationOp::Lt, l.clone(), r.clone()), l < r); - prop_assert_eq!(eval_relation_op(RelationOp::Ge, l.clone(), r.clone()), l >= r); - prop_assert_eq!(eval_relation_op(RelationOp::Le, l.clone(), r.clone()), l <= r); + prop_assert_eq!(eval_arith_op(ArithOp::Plus, l, r).ok(), l.checked_add(&r)); + prop_assert_eq!(eval_arith_op(ArithOp::Minus, l, r).ok(), l.checked_sub(&r)); + prop_assert_eq!(eval_arith_op(ArithOp::Multiply, l, r).ok(), l.checked_mul(&r)); + prop_assert_eq!(eval_arith_op(ArithOp::Divide, l, r).ok(), l.checked_div(&r)); + prop_assert_eq!(eval_arith_op(ArithOp::Modulo, l, r).ok(), l.checked_rem(&r)); + prop_assert_eq!(eval_arith_op::(ArithOp::Max, l, + r).unwrap(), l.max(r)); + prop_assert_eq!(eval_arith_op::(ArithOp::Min, l, + r).unwrap(), l.min(r)); + + prop_assert_eq!(eval_bit_op(BitOp::BitAnd, l, r), Ok(l & r)); + prop_assert_eq!(eval_bit_op(BitOp::BitOr, l, r), Ok(l | r)); + prop_assert_eq!(eval_bit_op(BitOp::BitXor, l, r), Ok(l ^ r)); + + prop_assert_eq!(eval_relation_op(RelationOp::Gt, l, r), l > r); + prop_assert_eq!(eval_relation_op(RelationOp::Lt, l, r), l < r); + prop_assert_eq!(eval_relation_op(RelationOp::Ge, l, r), l >= r); + prop_assert_eq!(eval_relation_op(RelationOp::Le, l, r), l <= r); } #[test] diff --git a/ergotree-interpreter/src/eval/byte_array_to_bigint.rs b/ergotree-interpreter/src/eval/byte_array_to_bigint.rs index ef6d65ee8..2be8f4110 100644 --- a/ergotree-interpreter/src/eval/byte_array_to_bigint.rs +++ b/ergotree-interpreter/src/eval/byte_array_to_bigint.rs @@ -3,7 +3,6 @@ use ergotree_ir::bigint256::BigInt256; use ergotree_ir::mir::byte_array_to_bigint::ByteArrayToBigInt; use ergotree_ir::mir::constant::TryExtractInto; use ergotree_ir::mir::value::Value; -use std::convert::TryFrom; use crate::eval::env::Env; use crate::eval::Context; @@ -23,9 +22,11 @@ impl Evaluable for ByteArrayToBigInt { "ByteArrayToBigInt: byte array is empty".into(), )); } - match BigInt256::try_from(&input[..]) { - Ok(n) => Ok(Value::BigInt(n)), - Err(e) => Err(UnexpectedValue(e)), + match BigInt256::from_be_slice(&input[..]) { + Some(n) => Ok(Value::BigInt(n)), + None => Err(UnexpectedValue( + "ByteArrayToBigInt: input array out of bounds".into(), + )), } } } diff --git a/ergotree-interpreter/src/eval/downcast.rs b/ergotree-interpreter/src/eval/downcast.rs index cf469c3af..ed34051ca 100644 --- a/ergotree-interpreter/src/eval/downcast.rs +++ b/ergotree-interpreter/src/eval/downcast.rs @@ -214,12 +214,12 @@ mod tests { ); let ctx = force_any_val::(); (0..ErgoTreeVersion::V6_SOFT_FORK_VERSION) - .for_each(|version| assert!(try_eval_out_with_version::(&downcast(v_bigint.clone(), SType::SBigInt), &ctx, version).is_err())); + .for_each(|version| assert!(try_eval_out_with_version::(&downcast(v_bigint, SType::SBigInt), &ctx, version).is_err())); (ErgoTreeVersion::V6_SOFT_FORK_VERSION..=ErgoTreeVersion::MAX_SCRIPT_VERSION).for_each( |version| { assert_eq!( try_eval_out_with_version::( - &downcast(v_bigint.clone(), SType::SBigInt), + &downcast(v_bigint, SType::SBigInt), &ctx, version ).unwrap(), @@ -234,7 +234,7 @@ mod tests { let c_short: Constant = v_short.into(); let c_int: Constant = v_int.into(); let c_long: Constant = v_long.into(); - let c_bigint: Constant = v_bigint.clone().into(); + let c_bigint: Constant = v_bigint.into(); assert_eq!( eval_out_wo_ctx::(&downcast(c_byte, SType::SLong)), @@ -307,11 +307,11 @@ mod tests { .is_err()); let ctx = force_any_val::(); (0..ErgoTreeVersion::V6_SOFT_FORK_VERSION) - .for_each(|version| assert!(try_eval_out_with_version::(&downcast(v_bigint.clone(), SType::SInt), &ctx, version).is_err())); + .for_each(|version| assert!(try_eval_out_with_version::(&downcast(v_bigint, SType::SInt), &ctx, version).is_err())); (ErgoTreeVersion::V6_SOFT_FORK_VERSION..=ErgoTreeVersion::MAX_SCRIPT_VERSION).for_each( |version| { let res = try_eval_out_with_version::( - &downcast(v_bigint.clone(), SType::SInt), + &downcast(v_bigint, SType::SInt), &ctx, version ); @@ -361,11 +361,11 @@ mod tests { assert!(try_eval_out_wo_ctx::(&downcast(c_long_oob, SType::SShort)).is_err()); let ctx = force_any_val::(); (0..ErgoTreeVersion::V6_SOFT_FORK_VERSION) - .for_each(|version| assert!(try_eval_out_with_version::(&downcast(v_bigint.clone(), SType::SShort), &ctx, version).is_err())); + .for_each(|version| assert!(try_eval_out_with_version::(&downcast(v_bigint, SType::SShort), &ctx, version).is_err())); (ErgoTreeVersion::V6_SOFT_FORK_VERSION..=ErgoTreeVersion::MAX_SCRIPT_VERSION).for_each( |version| { let res = try_eval_out_with_version::( - &downcast(v_bigint.clone(), SType::SShort), + &downcast(v_bigint, SType::SShort), &ctx, version ); @@ -426,11 +426,11 @@ mod tests { assert!(try_eval_out_wo_ctx::(&downcast(c_long_oob, SType::SByte)).is_err()); let ctx = force_any_val::(); (0..ErgoTreeVersion::V6_SOFT_FORK_VERSION) - .for_each(|version| assert!(try_eval_out_with_version::(&downcast(v_bigint.clone(), SType::SByte), &ctx, version).is_err())); + .for_each(|version| assert!(try_eval_out_with_version::(&downcast(v_bigint, SType::SByte), &ctx, version).is_err())); (ErgoTreeVersion::V6_SOFT_FORK_VERSION..=ErgoTreeVersion::MAX_SCRIPT_VERSION).for_each( |version| { let res = try_eval_out_with_version::( - &downcast(v_bigint.clone(), SType::SByte), + &downcast(v_bigint, SType::SByte), &ctx, version ); diff --git a/ergotree-interpreter/src/eval/exponentiate.rs b/ergotree-interpreter/src/eval/exponentiate.rs index c63462ccd..39350de7c 100644 --- a/ergotree-interpreter/src/eval/exponentiate.rs +++ b/ergotree-interpreter/src/eval/exponentiate.rs @@ -61,7 +61,7 @@ mod tests { let expected_exp = ergo_chain_types::ec_point::exponentiate( &left, - &dlog_group::bigint256_to_scalar(right.clone()).unwrap() + &dlog_group::bigint256_to_scalar(right).unwrap() ); let expr: Expr = Exponentiate { @@ -82,7 +82,7 @@ mod tests { let expected_exp = ergo_chain_types::ec_point::exponentiate( &left, - &dlog_group::bigint256_to_scalar(right.clone()).unwrap(), + &dlog_group::bigint256_to_scalar(right).unwrap(), ); let expr: Expr = Exponentiate { diff --git a/ergotree-interpreter/src/eval/upcast.rs b/ergotree-interpreter/src/eval/upcast.rs index a04c442b2..b0c384fb1 100644 --- a/ergotree-interpreter/src/eval/upcast.rs +++ b/ergotree-interpreter/src/eval/upcast.rs @@ -184,7 +184,7 @@ mod tests { #[test] fn from_bigint(v in any::()) { - let c: Constant = v.clone().into(); + let c: Constant = v.into(); let ctx = force_any_val::(); (0..ErgoTreeVersion::V6_SOFT_FORK_VERSION).for_each(|version| { assert!(try_eval_out_with_version::(&Upcast::new(c.clone().into(), SType::SBigInt).unwrap().into(), &ctx, version).is_err()); diff --git a/ergotree-ir/Cargo.toml b/ergotree-ir/Cargo.toml index 078c41f32..ef185305d 100644 --- a/ergotree-ir/Cargo.toml +++ b/ergotree-ir/Cargo.toml @@ -33,7 +33,7 @@ indexmap = { workspace = true } serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } serde_with = { workspace = true, optional = true } -num256 = "0.3.1" +bnum = { version = "0.12.0", features = ["numtraits"] } impl-trait-for-tuples = "0.2.0" strum = "0.21" strum_macros = "0.21" diff --git a/ergotree-ir/src/bigint256.rs b/ergotree-ir/src/bigint256.rs index 986e52396..cd1848068 100644 --- a/ergotree-ir/src/bigint256.rs +++ b/ergotree-ir/src/bigint256.rs @@ -1,141 +1,144 @@ //! 256-bit signed integer type -use std::convert::TryFrom; -use std::fmt; -use std::ops::{Add, BitAnd, BitOr, BitXor, Deref, Div, Mul, Neg, Not, Rem, Sub}; +use std::ops::{Div, Mul, Neg, Rem}; -use num256::int256::Int256; +use bnum::cast::As; +use bnum::types::I256; +use bnum::BUintD8; +use derive_more::From; +use derive_more::{Add, AddAssign, BitAnd, BitOr, BitXor, Display, Div, FromStr, Mul, Not, Sub}; use num_bigint::BigInt; -use num_bigint::BigUint; -use num_bigint::ToBigInt; -use num_derive::{One, Zero}; -use num_integer::Integer; +use num_derive::{Num, One, Signed, Zero}; use num_traits::{ - Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedSub, Num, - ToPrimitive, Zero, + Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedSub, Signed, + ToPrimitive, }; -/// 256-bit signed integer type -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Zero, One)] -pub struct BigInt256(Int256); - -impl TryFrom for BigInt256 { - type Error = String; +use crate::serialization::{SigmaParsingError, SigmaSerializable}; - fn try_from(value: BigInt) -> Result { - if value < Self::min_value().0 .0 { - Err(format!("BigInt256: Value {} is smaller than -2^255", value)) - } else if value > Self::max_value().0 .0 { - Err(format!( - "BigInt256: Value {} is larger than 2^255 - 1", - value - )) - } else { - Ok(Self(Int256(value))) +/// 256-bit signed integer type +#[derive( + PartialEq, + Eq, + PartialOrd, + Ord, + Debug, + Display, + From, + FromStr, + Copy, + Clone, + Zero, + One, + Num, + Not, + Add, + AddAssign, + Sub, + Mul, + Div, + BitAnd, + BitOr, + BitXor, + Signed, +)] +pub struct BigInt256(pub(crate) bnum::types::I256); + +impl BigInt256 { + /// Create a BigInt256 from a slice of bytes in big-endian format. Returns None if slice.len() > 32 || slice.len() == 0 + pub fn from_be_slice(slice: &[u8]) -> Option { + // match scala implementation which returns exception with empty byte array, whereas bnum returns 0 + if slice.is_empty() { + return None; + } + I256::from_be_slice(slice).map(Self) + } + + /// Return bytes of integer in big-endian order + pub fn to_be_bytes(&self) -> [u8; 32] { + *self + .0 + .as_::>() + .to_be() + .digits() + } + + /// Convert BigInt256 to minimum number of bytes to represent it + /// # Example + /// ``` + /// # use ergotree_ir::bigint256::BigInt256; + /// use num_traits::Num; + /// + /// let num = BigInt256::from_str_radix("ff", 16).unwrap(); + /// let num_bytes = num.to_be_vec(); + /// assert_eq!(num_bytes, vec![0x00, 0xff]); + /// assert_eq!(num, BigInt256::from_be_slice(&num_bytes).unwrap()); + /// + /// let neg = BigInt256::from_str_radix("-1", 16).unwrap(); + /// let neg_bytes = neg.to_be_vec(); + /// assert_eq!(neg_bytes, vec![0xff]); + /// assert_eq!(neg, BigInt256::from_be_slice(&neg_bytes).unwrap()); + /// ``` + pub fn to_be_vec(&self) -> Vec { + let mut bytes = self.0.to_radix_be(256); + if self.0.is_negative() { + // drain leading ones + let leading_bytes = (self.0.leading_ones().saturating_sub(1)) / 8; + bytes.drain(0..leading_bytes as usize); + } else if bytes[0] & 0x80 != 0 { + // If number has a leading 1, pad it with zeroes to avoid it being misinterpreted as negative by from_be_slice + bytes.insert(0, 0); } + bytes } } -impl TryFrom for BigInt256 { +impl TryFrom for BigInt256 { type Error = String; - fn try_from(value: BigUint) -> Result { - #[allow(clippy::unwrap_used)] - if value > Self::max_value().0 .0.to_biguint().unwrap() { - Err(format!( - "BigInt256: Value {} is larger than 2^255 - 1", - value - )) - } else { - Ok(Self(Int256(value.to_bigint().unwrap()))) - } + fn try_from(value: BigInt) -> Result { + let bytes = value.to_signed_bytes_be(); + Self::from_be_slice(&bytes).ok_or_else(|| "BigInt256 value: {value} out of bounds".into()) } } -impl TryFrom<&[u8]> for BigInt256 { - type Error = String; - - fn try_from(value: &[u8]) -> Result { - let n = BigInt::from_signed_bytes_be(value); - Self::try_from(n) +impl From for BigInt { + fn from(value: BigInt256) -> Self { + BigInt::from_signed_bytes_be(&value.to_be_bytes()) } } impl From for BigInt256 { fn from(value: i8) -> Self { - Self(Int256::from(value)) + Self(I256::from(value)) } } impl From for BigInt256 { fn from(value: i16) -> Self { - Self(Int256::from(value)) + Self(I256::from(value)) } } impl From for BigInt256 { fn from(value: i32) -> Self { - Self(Int256::from(value)) + Self(I256::from(value)) } } impl From for BigInt256 { fn from(value: i64) -> Self { - Self(Int256::from(value)) - } -} - -impl From for BigInt { - fn from(value: BigInt256) -> Self { - let Int256(bi) = value.0; - bi - } -} - -impl Deref for BigInt256 { - type Target = Int256; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Num for BigInt256 { - type FromStrRadixErr = String; - - // Don't use Int256::from_str_radix because of this issue: - // https://github.com/althea-net/num256_rs/issues/16 - fn from_str_radix(s: &str, radix: u32) -> Result { - match BigInt::from_str_radix(s, radix) { - Ok(n) => Self::try_from(n), - Err(e) => Err(e.to_string()), - } + Self(I256::from(value)) } } impl Bounded for BigInt256 { fn min_value() -> Self { - Self(Int256::min_value()) + Self(I256::min_value()) } fn max_value() -> Self { - Self(Int256::max_value()) - } -} - -impl Add for BigInt256 { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - BigInt256(self.0 + rhs.0) - } -} - -impl Sub for BigInt256 { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - BigInt256(self.0 - rhs.0) + Self(I256::max_value()) } } @@ -171,45 +174,37 @@ impl Neg for BigInt256 { } } -impl Not for BigInt256 { - type Output = Self; - - fn not(self) -> Self::Output { - // Int256 currently doesn't have an impl for Not - BigInt256(Int256(!self.0 .0)) - } -} - impl CheckedAdd for BigInt256 { fn checked_add(&self, v: &Self) -> Option { - Some(BigInt256(self.0.checked_add(&v.0)?)) + Some(BigInt256(self.0.checked_add(v.0)?)) } } impl CheckedSub for BigInt256 { fn checked_sub(&self, v: &Self) -> Option { - Some(BigInt256(self.0.checked_sub(&v.0)?)) + Some(BigInt256(self.0.checked_sub(v.0)?)) } } impl CheckedMul for BigInt256 { fn checked_mul(&self, v: &Self) -> Option { - Some(BigInt256(self.0.checked_mul(&v.0)?)) + Some(BigInt256(self.0.checked_mul(v.0)?)) } } impl CheckedDiv for BigInt256 { fn checked_div(&self, v: &Self) -> Option { - Some(BigInt256(self.0.checked_div(&v.0)?)) + Some(BigInt256(self.0.checked_div(v.0)?)) } } impl CheckedRem for BigInt256 { fn checked_rem(&self, v: &Self) -> Option { - if v.0 .0 <= BigInt::zero() { + // Scala BigInt does not allow modulo operations with negative divisors + if v.is_negative() { return None; } - Some(BigInt256(Int256(self.0 .0.mod_floor(&v.0 .0)))) + self.0.checked_rem(v.0).map(Self) } } @@ -218,59 +213,11 @@ impl CheckedNeg for BigInt256 { if self == &BigInt256::min_value() { None } else { - Some(-self.clone()) + Some(-*self) } } } -impl BitAnd for BigInt256 { - type Output = Self; - - fn bitand(self, rhs: Self) -> Self::Output { - BigInt256(Int256(self.0 .0 & rhs.0 .0)) - } -} - -impl<'a> BitAnd<&'a BigInt256> for &'a BigInt256 { - type Output = BigInt256; - - fn bitand(self, rhs: &BigInt256) -> Self::Output { - BigInt256(Int256(&self.0 .0 & &rhs.0 .0)) - } -} - -impl BitOr for BigInt256 { - type Output = Self; - - fn bitor(self, rhs: Self) -> Self::Output { - BigInt256(Int256(self.0 .0 | rhs.0 .0)) - } -} - -impl<'a> BitOr<&'a BigInt256> for &'a BigInt256 { - type Output = BigInt256; - - fn bitor(self, rhs: &BigInt256) -> Self::Output { - BigInt256(Int256(&self.0 .0 | &rhs.0 .0)) - } -} - -impl BitXor for BigInt256 { - type Output = Self; - - fn bitxor(self, rhs: Self) -> Self::Output { - BigInt256(Int256(self.0 .0 ^ rhs.0 .0)) - } -} - -impl<'a> BitXor<&'a BigInt256> for &'a BigInt256 { - type Output = BigInt256; - - fn bitxor(self, rhs: &BigInt256) -> Self::Output { - BigInt256(Int256(&self.0 .0 ^ &rhs.0 .0)) - } -} - impl ToPrimitive for BigInt256 { fn to_i64(&self) -> Option { self.0.to_i64() @@ -281,9 +228,33 @@ impl ToPrimitive for BigInt256 { } } -impl fmt::Display for BigInt256 { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", &self.to_str_radix(10)) +impl SigmaSerializable for BigInt256 { + fn sigma_serialize( + &self, + w: &mut W, + ) -> crate::serialization::SigmaSerializeResult { + let bytes = self.to_be_vec(); + w.put_u16(bytes.len() as u16)?; + w.write_all(&bytes)?; + Ok(()) + } + + fn sigma_parse( + r: &mut R, + ) -> Result { + let size = r.get_u16()?; + if size > 32 { + return Err(SigmaParsingError::ValueOutOfBounds(format!( + "serialized BigInt size {0} bytes exceeds 32", + size + ))); + } + let mut buf = vec![0u8; size as usize]; + r.read_exact(&mut buf)?; + match BigInt256::from_be_slice(&buf) { + Some(x) => Ok(x), + None => Err(SigmaParsingError::ValueOutOfBounds(String::new())), + } } } @@ -303,7 +274,7 @@ mod arbitrary { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { #[allow(clippy::unwrap_used)] any::<[u8; 32]>() - .prop_map(|bytes| Self::try_from(&bytes[..]).unwrap()) + .prop_map(|bytes| Self::from_be_slice(&bytes[..]).unwrap()) .boxed() } } @@ -313,6 +284,28 @@ mod arbitrary { #[cfg(test)] mod tests { use super::*; + use num_traits::Num; + use proptest::{prelude::*, proptest}; + + #[cfg(feature = "arbitrary")] + proptest! { + #[test] + fn roundtrip(b in any::()) { + let serialized = b.to_be_vec(); + assert_eq!(b, BigInt256::from_be_slice(&serialized).unwrap()); + } + #[test] + fn bigint_roundtrip(b in any::()) { + let bigint: BigInt = b.into(); + assert_eq!(b, bigint.try_into().unwrap()); + } + #[test] + fn upcast(l in any::()) { + let bytes = l.to_be_bytes(); + let upcast = BigInt256::from(l); + assert_eq!(upcast, BigInt256::from_be_slice(&bytes).unwrap()); + } + } #[test] fn min_value() { @@ -324,13 +317,13 @@ mod tests { let mut bytes = [0x00_u8; 32]; bytes[0] = 0x80; - let bigint_from_bytes = BigInt256::try_from(&bytes[..]); + let bigint_from_bytes = BigInt256::from_be_slice(&bytes[..]); assert_eq!(BigInt256::min_value(), bigint_from_bytes.unwrap()); let mut bytes = [0x00_u8; 33]; bytes[0] = 0xff; bytes[1] = 0x80; - let bigint_from_bytes = BigInt256::try_from(&bytes[..]); + let bigint_from_bytes = BigInt256::from_be_slice(&bytes[..]); assert_eq!(BigInt256::min_value(), bigint_from_bytes.unwrap()); } @@ -344,13 +337,13 @@ mod tests { let mut bytes = [0xff_u8; 32]; bytes[0] = 0x7f; - let bigint_from_bytes = BigInt256::try_from(&bytes[..]); + let bigint_from_bytes = BigInt256::from_be_slice(&bytes[..]); assert_eq!(BigInt256::max_value(), bigint_from_bytes.unwrap()); let mut bytes = [0xff_u8; 33]; bytes[0] = 0x00; bytes[1] = 0x7f; - let bigint_from_bytes = BigInt256::try_from(&bytes[..]); + let bigint_from_bytes = BigInt256::from_be_slice(&bytes[..]); assert_eq!(BigInt256::max_value(), bigint_from_bytes.unwrap()); } @@ -366,8 +359,8 @@ mod tests { let mut bytes = [0xff_u8; 33]; bytes[0] = 0xff; bytes[1] = 0x7f; - let bigint_from_bytes = BigInt256::try_from(&bytes[..]); - assert!(bigint_from_bytes.is_err()); + let bigint_from_bytes = BigInt256::from_be_slice(&bytes[..]); + assert!(bigint_from_bytes.is_none()); // Upper bound let bigint_from_str = BigInt256::from_str_radix( @@ -379,7 +372,7 @@ mod tests { let mut bytes = [0x00_u8; 33]; bytes[0] = 0x00; bytes[1] = 0x80; - let bigint_from_bytes = BigInt256::try_from(&bytes[..]); - assert!(bigint_from_bytes.is_err()); + let bigint_from_bytes = BigInt256::from_be_slice(&bytes[..]); + assert!(bigint_from_bytes.is_none()); } } diff --git a/ergotree-ir/src/mir/bin_op.rs b/ergotree-ir/src/mir/bin_op.rs index 3df3054a7..70cc7307c 100644 --- a/ergotree-ir/src/mir/bin_op.rs +++ b/ergotree-ir/src/mir/bin_op.rs @@ -8,7 +8,6 @@ use crate::serialization::op_code::OpCode; use crate::traversable::impl_traversable_expr; use crate::types::stype::SType; -extern crate derive_more; use derive_more::From; #[cfg(feature = "arbitrary")] diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index 6e8a5f62b..9f48d128e 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -2,7 +2,6 @@ use std::convert::Infallible; use std::convert::TryFrom; -use std::convert::TryInto; use crate::chain::context::Context; use crate::chain::ergo_box::RegisterId; diff --git a/ergotree-ir/src/mir/value.rs b/ergotree-ir/src/mir/value.rs index 3b3b33b75..9f01c9b69 100644 --- a/ergotree-ir/src/mir/value.rs +++ b/ergotree-ir/src/mir/value.rs @@ -217,7 +217,7 @@ impl<'ctx> Value<'ctx> { Value::Int(b) => Value::Int(*b), Value::Long(b) => Value::Long(*b), Value::Unit => Value::Unit, - Value::BigInt(b) => Value::BigInt(b.clone()), + Value::BigInt(b) => Value::BigInt(*b), Value::GroupElement(b) => Value::GroupElement(b.to_static()), Value::SigmaProp(p) => Value::SigmaProp(p.clone()), Value::AvlTree(t) => Value::AvlTree(t.clone()), diff --git a/ergotree-ir/src/pretty_printer.rs b/ergotree-ir/src/pretty_printer.rs index 234eb8358..345ce1b01 100644 --- a/ergotree-ir/src/pretty_printer.rs +++ b/ergotree-ir/src/pretty_printer.rs @@ -529,7 +529,7 @@ mod tests { val v20 = upcast(v18) val v21 = upcast(v10) val v22 = upcast(v11) / v21 - sigmaProp(v1.propBytes == SELF.propBytes && v1.value >= SELF.value && v2(1) == v3(1) && v4._1 == v5._1 && v6._1 == v7._1 && v8._1 == v9._1 && if (v11 == 0) if (v14) v16 * v17 * BigInt256(Int256(997)) >= upcast(-v18) * v19 * BigInt256(Int256(1000)) + upcast(v13 * 997) else v19 * v20 * BigInt256(Int256(997)) >= upcast(-v13) * v16 * BigInt256(Int256(1000)) + upcast(v18 * 997) else if (v14 && v18 > 0) upcast(-v11) <= v17 * v21 / v19 min v20 * v21 / v16 else v17 >= v22 * v19 && v20 >= v22 * v16) + sigmaProp(v1.propBytes == SELF.propBytes && v1.value >= SELF.value && v2(1) == v3(1) && v4._1 == v5._1 && v6._1 == v7._1 && v8._1 == v9._1 && if (v11 == 0) if (v14) v16 * v17 * BigInt256(997) >= upcast(-v18) * v19 * BigInt256(1000) + upcast(v13 * 997) else v19 * v20 * BigInt256(997) >= upcast(-v13) * v16 * BigInt256(1000) + upcast(v18 * 997) else if (v14 && v18 > 0) upcast(-v11) <= v17 * v21 / v19 min v20 * v21 / v16 else v17 >= v22 * v19 && v20 >= v22 * v16) } "#]], ) @@ -556,7 +556,7 @@ mod tests { (v7: Box) => { val v9 = v7.tokens(0)._2 - v9 >= upcast(1000) && upcast(v5._2) * upcast(v6) * BigInt256(Int256(997)) <= upcast(v9) * upcast(v2._2) * BigInt256(Int256(1000)) + upcast(v6 * 997) + v9 >= upcast(1000) && upcast(v5._2) * upcast(v6) * BigInt256(997) <= upcast(v9) * upcast(v2._2) * BigInt256(1000) + upcast(v6 * 997) } } diff --git a/ergotree-ir/src/serialization/data.rs b/ergotree-ir/src/serialization/data.rs index 190658b5e..49a1ef5e1 100644 --- a/ergotree-ir/src/serialization/data.rs +++ b/ergotree-ir/src/serialization/data.rs @@ -1,5 +1,6 @@ use sigma_util::AsVecU8; +use crate::bigint256::BigInt256; use crate::chain::ergo_box::ErgoBox; use crate::mir::avl_tree_data::AvlTreeData; use crate::mir::constant::Literal; @@ -35,9 +36,7 @@ impl DataSerializer { Literal::Int(v) => w.put_i32(*v)?, Literal::Long(v) => w.put_i64(*v)?, Literal::BigInt(v) => { - let bytes = v.to_signed_bytes_be(); - w.put_u16(bytes.len() as u16)?; - w.write_all(&bytes)? + v.sigma_serialize(w)?; } Literal::GroupElement(ecp) => ecp.sigma_serialize(w)?, Literal::SigmaProp(s) => s.value().sigma_serialize(w)?, @@ -95,21 +94,7 @@ impl DataSerializer { SShort => Literal::Short(r.get_i16()?), SInt => Literal::Int(r.get_i32()?), SLong => Literal::Long(r.get_i64()?), - SBigInt => { - let size = r.get_u16()?; - if size > 32 { - return Err(SigmaParsingError::ValueOutOfBounds(format!( - "serialized BigInt size {0} bytes exceeds 32", - size - ))); - } - let mut buf = vec![0u8; size as usize]; - r.read_exact(&mut buf)?; - match buf.as_slice().try_into() { - Ok(x) => Literal::BigInt(x), - Err(e) => return Err(SigmaParsingError::ValueOutOfBounds(e)), - } - } + SBigInt => Literal::BigInt(BigInt256::sigma_parse(r)?), SUnit => Literal::Unit, SGroupElement => Literal::GroupElement(Arc::new(EcPoint::sigma_parse(r)?)), SSigmaProp => { diff --git a/ergotree-ir/src/sigma_protocol/dlog_group.rs b/ergotree-ir/src/sigma_protocol/dlog_group.rs index 9b92ccc45..f8a3ff7c1 100644 --- a/ergotree-ir/src/sigma_protocol/dlog_group.rs +++ b/ergotree-ir/src/sigma_protocol/dlog_group.rs @@ -21,15 +21,15 @@ use crate::serialization::SigmaSerializeResult; use crate::serialization::{ sigma_byte_reader::SigmaByteRead, SigmaParsingError, SigmaSerializable, }; +use bnum::cast::CastFrom; +use bnum::types::{I256, U256}; +use bnum::BTryFrom; use elliptic_curve::rand_core::RngCore; use k256::elliptic_curve::PrimeField; use k256::Scalar; -use num_bigint::Sign; -use num_bigint::ToBigUint; -use num_bigint::{BigInt, BigUint}; -use num_traits::ToPrimitive; +use num_bigint::BigInt; +use num_traits::Num; use sigma_ser::ScorexSerializable; -use std::convert::TryFrom; // /// Creates a random member of this Dlog group // pub fn random_element() -> EcPoint { @@ -51,38 +51,28 @@ pub fn random_scalar_in_group_range(mut rng: impl RngCore) -> Scalar { pub fn scalar_to_bigint256(s: Scalar) -> Option { // from https://github.com/RustCrypto/elliptic-curves/blob/fe737c56add103e4e8ff270d0c05ffdb6107b8d6/k256/src/arithmetic/scalar.rs#L598-L602 let bytes = s.to_bytes(); - #[allow(clippy::unwrap_used)] - let bu: BigUint = bytes - .iter() - .enumerate() - .map(|(i, w)| w.to_biguint().unwrap() << ((31 - i) * 8)) - .sum(); - BigInt256::try_from(bu).ok() -} - -fn biguint_to_bytes(x: &BigUint) -> [u8; 32] { - // from https://github.com/RustCrypto/elliptic-curves/blob/fe737c56add103e4e8ff270d0c05ffdb6107b8d6/k256/src/arithmetic/scalar.rs#L587-L588 - let mask = BigUint::from(u8::MAX); - let mut bytes = [0u8; 32]; - #[allow(clippy::needless_range_loop)] - #[allow(clippy::unwrap_used)] - for i in 0..32 { - bytes[i] = ((x >> ((31 - i) * 8)) as BigUint & &mask).to_u8().unwrap(); - } - bytes + #[allow(clippy::unwrap_used)] // Scalar always fits in 256-bit unsigned integer + let uint = U256::from_be_slice(&bytes).unwrap(); + >::try_from(uint) + .ok() + .map(Into::into) } /// Attempts to create Scalar from BigInt256 pub fn bigint256_to_scalar(bi: BigInt256) -> Option { - // To convert BigInt bi to Scalar calculate (bi mod order) - let order = order(); - let mut bi = &**bi % ℴ - if Sign::Minus == bi.sign() { + type I257 = bnum::BIntD8<33>; + use num_traits::identities::Zero; + // To convert BigInt bi to Scalar calculate (bi mod order). Widen signed calculations to 257 bits since secp256k1 order doesn't fit in 256 bits signed + let order: I257 = I257::cast_from(order()); + let mut bi: I257 = I257::cast_from(bi.0) % order; + if bi < I257::zero() { bi += order; } - #[allow(clippy::unwrap_used)] // since it's 256-bit BigInt it should always fit into BigUint - let bu = bi.to_biguint().unwrap(); - let bytes = biguint_to_bytes(&bu); + #[allow(clippy::unwrap_used)] // bi is positive, and bi < order() < U256::MAX + let bytes = * as BTryFrom>>::try_from(bi) + .unwrap() + .to_be() + .digits(); Scalar::from_repr(bytes.into()).into() } @@ -98,14 +88,18 @@ impl SigmaSerializable for ergo_chain_types::EcPoint { } } +const ORDER: &str = "+FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"; /// Order of the secp256k1 elliptic curve -pub fn order() -> BigInt { +// Since secp256k1 order doesn't fit in a signed 256 bit integer, this returns an unsigned 256-bit integer instead +pub fn order() -> U256 { #[allow(clippy::unwrap_used)] - BigInt::parse_bytes( - b"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", - 16, - ) - .unwrap() + U256::from_str_radix(ORDER, 16).unwrap() +} + +/// Order of the secp256k1 elliptic curve as BigInt +pub fn order_bigint() -> BigInt { + #[allow(clippy::unwrap_used)] + BigInt::from_str_radix(ORDER, 16).unwrap() } #[allow(clippy::unwrap_used)] @@ -113,55 +107,25 @@ pub fn order() -> BigInt { #[allow(clippy::panic)] mod tests { use super::*; - use num_bigint::BigUint; - use num_bigint::ToBigUint; use proptest::prelude::*; - // the following Scalar <-> BigUint helpers are from k256::arithmetic::scalar - - /// Converts a byte array (big-endian) to BigUint. - fn bytes_to_biguint(bytes: &[u8; 32]) -> BigUint { - bytes - .iter() - .enumerate() - .map(|(i, w)| w.to_biguint().unwrap() << ((31 - i) * 8)) - .sum() - } - - fn scalar_to_biguint(scalar: &Scalar) -> Option { - Some(bytes_to_biguint(scalar.to_bytes().as_ref())) - } - - fn biguint_to_scalar(x: &BigUint) -> Scalar { - debug_assert!(x < &modulus_as_biguint()); - let bytes = biguint_to_bytes(x); - Scalar::from_repr(bytes.into()).unwrap() - } - - /// Returns the scalar modulus as a `BigUint` object. - fn modulus_as_biguint() -> BigUint { - scalar_to_biguint(&Scalar::ONE.negate()).unwrap() + 1.to_biguint().unwrap() - } - - prop_compose! { - fn scalar()(bytes in any::<[u8; 32]>()) -> Scalar { - let mut res = bytes_to_biguint(&bytes); - let m = modulus_as_biguint(); - // Modulus is 256 bit long, same as the maximum `res`, - // so this is guaranteed to land us in the correct range. - if res >= m { - res -= m; - } - biguint_to_scalar(&res) - } + fn scalar() -> impl Strategy { + any::<[u8; 32]>().prop_filter_map( + format!("Scalars must be 0 <= n < {0}", order()), + |bytes| { + if bytes[0] & 0x80 != 0 { + return None; + } + Scalar::from_repr(bytes.into()).into() + }, + ) } proptest! { - #[test] fn scalar_biguint_roundtrip(scalar in scalar()) { - let bu = scalar_to_biguint(&scalar).unwrap(); - let to_scalar = biguint_to_scalar(&bu); + let bu = scalar_to_bigint256(scalar).unwrap(); + let to_scalar = bigint256_to_scalar(bu).unwrap(); prop_assert_eq!(scalar, to_scalar); } diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs index 377d0966d..2f67f3fd4 100644 --- a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs +++ b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs @@ -12,7 +12,6 @@ use crate::serialization::op_code::OpCode; use crate::serialization::SigmaSerializable; use ergo_chain_types::EcPoint; use std::convert::TryFrom; -use std::convert::TryInto; use std::fmt::Formatter; extern crate derive_more;