diff --git a/build.sbt b/build.sbt index 06bf2e6f14..31d769e1e2 100644 --- a/build.sbt +++ b/build.sbt @@ -17,7 +17,7 @@ lazy val commonSettings = Seq( scalacOptions ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, 13)) => - Seq("-Ywarn-unused:_,imports", "-Ywarn-unused:imports", "-release", "8") + Seq("-Ywarn-unused:_,imports", "-Ywarn-unused:imports", "-Wconf:src=src_managed/.*:silent", "-release", "8") case Some((2, 12)) => Seq("-Ywarn-unused:_,imports", "-Ywarn-unused:imports", "-release", "8") case Some((2, 11)) => @@ -282,7 +282,7 @@ lazy val interpreterJS = interpreter.js }, Compile / npmDependencies ++= Seq( "sigmajs-crypto-facade" -> sigmajsCryptoFacadeVersion, - "@fleet-sdk/common" -> "0.1.0-alpha.14" + "@fleet-sdk/common" -> "0.1.3" ) ) diff --git a/common/shared/src/main/scala/scalan/TypeDesc.scala b/common/shared/src/main/scala/scalan/TypeDesc.scala index c7578a2ea8..98ecb1ec4c 100644 --- a/common/shared/src/main/scala/scalan/TypeDesc.scala +++ b/common/shared/src/main/scala/scalan/TypeDesc.scala @@ -35,9 +35,6 @@ object RType { case ClassTag.Short => ShortType case ClassTag.Int => IntType case ClassTag.Long => LongType - case ClassTag.Char => CharType - case ClassTag.Float => FloatType - case ClassTag.Double => DoubleType case ClassTag.Unit => UnitType case _ => GeneralType[A](ctA) }).asInstanceOf[RType[A]] @@ -72,10 +69,6 @@ object RType { implicit val ShortType : RType[Short] = PrimitiveType[Short] (ClassTag.Short, Array.emptyShortArray) implicit val IntType : RType[Int] = PrimitiveType[Int] (ClassTag.Int, Array.emptyIntArray) implicit val LongType : RType[Long] = PrimitiveType[Long] (ClassTag.Long, Array.emptyLongArray) - // TODO v5.x: optimize: remove Char, Float, Double types, they are not supported and will never be - implicit val CharType : RType[Char] = PrimitiveType[Char] (ClassTag.Char, Array.emptyCharArray) - implicit val FloatType : RType[Float] = PrimitiveType[Float] (ClassTag.Float, Array.emptyFloatArray) - implicit val DoubleType : RType[Double] = PrimitiveType[Double] (ClassTag.Double, Array.emptyDoubleArray) implicit val UnitType : RType[Unit] = PrimitiveType[Unit] (ClassTag.Unit, Array[Unit]()(ClassTag.Unit)) implicit case object StringType extends RType[String] { diff --git a/common/shared/src/main/scala/scalan/util/CollectionUtil.scala b/common/shared/src/main/scala/scalan/util/CollectionUtil.scala index b9b79dd65c..0ee4030e2c 100644 --- a/common/shared/src/main/scala/scalan/util/CollectionUtil.scala +++ b/common/shared/src/main/scala/scalan/util/CollectionUtil.scala @@ -128,6 +128,9 @@ object CollectionUtil { } implicit class AnyOps[A](val x: A) extends AnyVal { + /** Performs a specified action on the source value and returns the result. */ + def perform(action: A => A): A = action(x) + /** Traverses the tree structure in a depth-first manner using the provided function to generate child nodes. * * @param f a function that takes a node of type A and returns a list of its children diff --git a/common/shared/src/test/scala/scalan/BaseTests.scala b/common/shared/src/test/scala/scalan/BaseTests.scala index 64a8c7f615..e015ff948e 100644 --- a/common/shared/src/test/scala/scalan/BaseTests.scala +++ b/common/shared/src/test/scala/scalan/BaseTests.scala @@ -34,7 +34,8 @@ abstract class BaseShouldTests extends AnyFlatSpec with TestUtils { } } - protected implicit def convertToInAndIgnoreMethods2(resultOfStringPassedToVerb: ResultOfStringPassedToVerb) = + protected implicit def convertToInAndIgnoreMethods2( + resultOfStringPassedToVerb: ResultOfStringPassedToVerb): InAndIgnoreMethods2 = new InAndIgnoreMethods2(resultOfStringPassedToVerb) } diff --git a/core-lib/shared/src/main/scala/special/collection/package.scala b/core-lib/shared/src/main/scala/special/collection/package.scala index 826c1bd3b5..b8be71be23 100644 --- a/core-lib/shared/src/main/scala/special/collection/package.scala +++ b/core-lib/shared/src/main/scala/special/collection/package.scala @@ -20,5 +20,14 @@ package object collection { /** Implicit resolution of `Coll[A]` type descriptor, given a descriptor of `A`. */ implicit def collRType[A](implicit tA: RType[A]): RType[Coll[A]] = CollType[A](tA) + /** Conversion to underlying descriptor class. + * Allows syntax like + * + * ```val tColl: RType[Coll[A]] = ...; tColl.tItem``` + * + * where `tItem` is a method of `CollType`, but is not defined on `RType`. + */ + implicit def downcastCollType[A](ct: RType[Coll[A]]): CollType[A] = ct.asInstanceOf[CollType[A]] + implicit val collBuilderRType: RType[CollBuilder] = RType.fromClassTag(classTag[CollBuilder]) } diff --git a/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala b/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala index 186aa59bd7..b1f27f0899 100644 --- a/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala +++ b/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala @@ -192,8 +192,7 @@ trait BigInt { def |(that: BigInt): BigInt = or(that) } -/** Base class for points on elliptic curves. - */ +/** Base class for points on elliptic curves. */ trait GroupElement { /** Checks if the provided element is an identity element. */ def isIdentity: Boolean @@ -381,7 +380,6 @@ trait Box { trait AvlTree { /** Returns digest of the state represented by this tree. * Authenticated tree digest = root hash bytes ++ tree height - * @since 2.0 */ def digest: Coll[Byte] @@ -529,10 +527,8 @@ trait AvlTreeVerifier { } -/** Only header fields that can be predicted by a miner. - * @since 2.0 - */ -trait PreHeader { // Testnet2 +/** Only header fields that can be predicted by a miner. */ +trait PreHeader { /** Block version, to be increased on every soft and hardfork. */ def version: Byte @@ -556,9 +552,7 @@ trait PreHeader { // Testnet2 def votes: Coll[Byte] } -/** Represents data of the block header available in Sigma propositions. - * @since 2.0 - */ +/** Represents data of the block header available in Sigma propositions. */ trait Header { /** Bytes representation of ModifierId of this Header */ def id: Coll[Byte] @@ -572,7 +566,7 @@ trait Header { /** Hash of ADProofs for transactions in a block */ def ADProofsRoot: Coll[Byte] // Digest32. Can we build AvlTree out of it? - /** AvlTree) of a state after block application */ + /** AvlTree of a state after block application */ def stateRoot: AvlTree /** Root hash (for a Merkle tree) of transactions in a block. */ diff --git a/core-lib/shared/src/test/scala/special/TypesTests.scala b/core-lib/shared/src/test/scala/special/TypesTests.scala index 13b562d84c..b0c6ce5808 100644 --- a/core-lib/shared/src/test/scala/special/TypesTests.scala +++ b/core-lib/shared/src/test/scala/special/TypesTests.scala @@ -10,8 +10,8 @@ class TypesTests extends BaseTests { def test[A](t: RType[A], n: String) = { t.name shouldBe n } - test(tupleRType(Array(IntType, LongType, RType[(String, Double)], RType[Option[Boolean]])), - "(Int, Long, (String, Double), Option[Boolean])") + test(tupleRType(Array(IntType, LongType, RType[(Byte, special.sigma.BigInt)], RType[Option[Boolean]])), + "(Int, Long, (Byte, BigInt), Option[Boolean])") } test("RType implements equality") { diff --git a/core-lib/shared/src/test/scala/special/collections/CollsTests.scala b/core-lib/shared/src/test/scala/special/collections/CollsTests.scala index 6e2271fd9e..f1fe708a7f 100644 --- a/core-lib/shared/src/test/scala/special/collections/CollsTests.scala +++ b/core-lib/shared/src/test/scala/special/collections/CollsTests.scala @@ -433,24 +433,6 @@ class CollsTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers checkColls(repl, coll) } - forAll(charGen, indexGen, minSuccess) { (x, n) => - val repl = builder.replicate(n, x) - val coll = builder.fromArray(Array.fill(n)(x)) - - checkColls(repl, coll) - } - forAll(floatGen, indexGen, minSuccess) { (x, n) => - val repl = builder.replicate(n, x) - val coll = builder.fromArray(Array.fill(n)(x)) - - checkColls(repl, coll) - } - forAll (doubleGen, indexGen, minSuccess) { (x, n) => - val repl = builder.replicate(n, x) - val coll = builder.fromArray(Array.fill(n)(x)) - - checkColls(repl, coll) - } forAll (indexGen, minSuccess) { (n) => val replTrue = builder.replicate(n, true) val collTrue = builder.fromArray(Array.fill(n)(true)) @@ -479,13 +461,13 @@ class CollsTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers checkColls(repl, coll) } - forAll (byteGen, doubleGen, intGen, indexGen, minSuccess) { (b, d, i, n) => + forAll (byteGen, longGen, intGen, indexGen, minSuccess) { (b, d, i, n) => val repl = builder.replicate(n, (b, (i, (d, b)))) - val coll = builder.fromArray(Array.fill[(Byte, (Int, (Double, Byte)))](n)((b, (i, (d, b))))) + val coll = builder.fromArray(Array.fill[(Byte, (Int, (Long, Byte)))](n)((b, (i, (d, b))))) checkColls(repl, coll) } - forAll (byteGen, doubleGen, intGen, indexGen, indexGen, minSuccess) { (b, d, i, n, m) => + forAll (byteGen, longGen, intGen, indexGen, indexGen, minSuccess) { (b, d, i, n, m) => val repl = builder.replicate(n, (b, ((i, (("string", builder.replicate(m, n)), Array(1, 2, 3, 4))), (d, b)))) val coll = builder.fromArray(Array.fill(n)((b, ((i, (("string", builder.fromArray(Array.fill(m)(n))), Array(1, 2, 3, 4))), (d, b))))) diff --git a/docs/Costing.md b/docs/Costing.md deleted file mode 100644 index a48b6d9b84..0000000000 --- a/docs/Costing.md +++ /dev/null @@ -1,446 +0,0 @@ - -## Estimation of ErgoTree computational complexity - -### Background - -To prevent DDoS attacks every script in a blockchain have to be checked for complexity limits. -This estimation happens during block/transaction validation for every guarding script of every input box. -Script can be executed iff its estimated complexity in a given `Context` is less than a `limit` value. - -### Contract execution context - -Transaction `tx` is validated as part of the block. -Every input box `ib` in `tx` contains a property `propBytes` with serialized ErgoTree of the contract. -During validation `propBytes` property is deserialized to ErgoTree `tree` which is executed. -The box `ib` itself is accessed via `SELF` property of the `Context` data structure. -Besides `Context` execution of a contract depends on votable `ProtocolParameters` data, which contains -global parameters which can be set up by miners following a voting protocol. - -### Costing Rules - -The following constants are used in cost and size calculations. - -Constant Name | Description ---------------|------------ -GroupSize | Number of bytes to represent any group element as byte array - -The following table shows the rules for calculating cost and size for a result of each operation -based on costs and sizes of the operations arguments. -The operations names are given by the node classes of ErgoTree. - - -Operation | Cost in time units, Size in bytes ----------------------|----------------------------------- -`ProveDLog` | CT("ProveDlogEval"), GroupSize -`ProveDHTuple` | CT("ProveDHTupleEval"), GroupSize * 4 -x,y: BigInt; x op y where op in ("+", "-") | cost(x) + cost(y) + CT("op"), MaxSizeInBytes - - -### Asymptotic complexity of the costing algorithm - -For a given input box `ib` the algorithm consists of the following steps (details of each -step are given in later sections): - -`#` | Step | Complexity -----|-------------------------------------------------------------------|----------- -1 | Check that `val len = propBytes.length; len < MaxPropBytesSize` | `O(1)` -2 | Deserialize `propBytes` to ErgoTree `tree` with `N` nodes | `O(len) and N = O(len)` -3 | Recursively traverse `tree` and build costed graph `graphC` with `M <= N` nodes | `O(N)` -4 | Split `graphC` into calculation function `calcF` and cost estimation function `costF` | `O(M)` -5 | Topologically sort nodes of `costF` for execution (Tarjan algorithm) | `O(M)` -6 | Iterate over sorted nodes of `costF` and execute primitive ops | `O(M)` - -#### Overview of Costing Process - - -Deserialized ErgoTree have to be translated into two related functions: -1) `calcF: Context => SigmaBoolean` - script calculation function, which produces Sigma tree for -further proof generation (when new `tx` is created) or proof verification (when `tx` is verified) -2) `costF: Context => Int` - cost estimation function, which by construction is closely connected -with `calcF` and allows to compute execution complexity of `calcF` in a given context. - -_Costing Process_ or simply _costing_ is the process of obtaining two functions `calcF` and `costF` -for a given deserialized ErgoTree. - -The key feature of the costing algorithm is that in many cases the functions `calcF` and `costF` can be -constructed for a given ErgoTree once and for all Context. This is statically verifiable property -which can be ensured by the compiler of ErgoTree. - -If context independent costing is not possible, the corresponding bit should be setup in script header. -In this case _context-dependent costing_ should be performed for the script during transaction validation. -Context-dependent costing can use data in the `Context` to construct `calcF` and `costF` functions. -This is necessary to achieve better cost approximations in complex scripts. - -Costing process is divided into two steps: -1) Building of _Costed Graph_ `graphC` (see [below](#BuildingCostedGraph)) -2) Splitting of the `graphC` into `calcF` and `costF` functions (see [below](#SplittingCostedGraph)) - -### Deserialization (Steps 1, 2 of costing algorithm) - -Deserializer should check that serialized byte array is of limited size, otherwise -out-of-memory attack is possible. `MaxPropBytesSize` is a protocol parameter (see `ProtocolParameters`). -During deserialization another parameter `MaxTreeDepth` is checked to limit depth of ErgoTree and thus -avoid stack-overflow attack. - -### Building Costed Graph (Step 3) - - -Costed Graph is a graph-based intermediate representatin (IR) which is created from the deserialized -ErgoTree. Implementation of Costed Graph is based on Scalan/Special framework. -See [Scalan idioms](https://github.com/scalan/scalan.github.io/blob/master/idioms.md) for details. - -#### Costed Values - - -The nodes of the costed graph `graphC` are _costed values_ of type `Costed[T]`. -```scala -trait Costed[Val] { - def value: Val // the value which is costed - def cost: Int // the cost estimation to obtain value - def dataSize: Long // the size estimation of the value in bytes -} -``` -Every costed value `valC: Costed[T]` is a container of the value along with additional data to -represent cost and size (costing information, costing properties). - -Note, that `cost` and `dataSize` are independent parameters because some _costed_ values may have -very small `dataSize`, but at the same time very high `cost`, e.g. result of contract may be `true` -boolean value whose `dataSize` is 1 byte, but its `cost` is the cost of executing the whole contract. -The opposite is also possible. For example a context variable of `Coll[Byte]` type have `cost` equal 0, -but may have very big `dataSize`. - -From this perspective Costed Graph `graphC` is a data flow graph between costed values. -Costed graph represents both execution of values and simultaneous execution of `cost` and `dataSize` -estimations for all intermediate values. - -The `cost` and `dataSize` computations depend on operations in the contract. -Consider the following contract fragment where we multiply two big integers: -``` -val x: BigInt = ... -val y: BigInt = ... -val z = x * y -``` -In the costed graph it corresponds to the following fragment: - -```scala -val xC: Costed[BigInt] = ... -val yC: Costed[BigInt]= ... -val zC = new Costed { - val value = xC.value * yC.value - val dataSize = xC.dataSize + yC.dataSize + 1L - val cost = xC.cost + yC.cost + costOf("*_per_item") * this.dataSize -} -``` -For the example above note the following properties: -1) both `cost` and `dataSize` depend on costing information from arguments -2) resulting `zC.cost` depend on `dataSize` of arguments -3) neigher `cost` nor `dataSize` depend on argument values, i.e. costing properties of result -can be approximated using costing properties of arguments along. - -The property 3) turns out to be very important, because many operations have this property -which leads to the possibility to do context-independent costing. - -Depending on type `T` costed values have specialized representations, given by descendants of -the type `Costed[T]`. The following subsections describe possible specialization in detail. - -##### Costed Values of Primitive Type - -The simplest case is when `T` is primitive. -In this case cost information is represented by class `CCostedPrim[T]` derived from trait `CostedPrim[T]`. -Separation into class and closely related trait is technical implementation detail, we will omit traits -in the following sections for brevity. The trait always have the same public methods as class. -```scala -trait CostedPrim[Val] extends Costed[Val] -class CCostedPrim[Val](val value: Val, val cost: Int, val dataSize: Long) extends CostedPrim[Val] -``` -This representation of costs is used for intermediate values of type `Boolean`, `Byte`, `Short`, `Int`, -`Long`, `BigInt`, `GroupElement` and `SigmaProp` types. -These types represent atomic values of constant or limited size. -The following table summarizes this - -Type | Data Size, bytes ----------------|----------------- -`Boolean` | == 1 -`Byte` | == 1 -`Short` | == 2 -`Int` | == 4 -`Long` | == 8 -`BigInt` | <= 32 -`GroupElement` | <= 32 -`SigmaProp` | <= 32 - -Constants, context variables and registers of primitive types are represented in costed graph `graphC` -using `CCostedPrim` class. For these cases costed properties as computed from actual data values. -For example having Int literal in the contract - -`val x: Int = 10` - -costing algorithm constructs the following graph - -`val xC: Costed[Int] = CCostedPrim(10, costOf("Const:() => Int"), sizeOf[Int])` - -Here `costOf("Const:() => Int")` is an operation which requests `CostModel` for the cost of using -a constant value in a script. - -##### Costed Values of Tuple type - -If `L` and `R` types, then costed value of type `(L,R)` is represented by the following -specializations of `Costed[(L,R)]` type -```scala -class CCostedPair[L,R](val l: Costed[L], val r: Costed[R]) extends CostedPair[L,R] { - def value: (L,R) = (l.value, r.value) - def cost: Int = l.cost + r.cost + ConstructTupleCost - def dataSize: Long = l.dataSize + r.dataSize -} -``` -Thus, for every intermediate value of type `(L,R)` we assume it has two components -which are both costed values and accessible via properties `l` and `r` of -`CostedPair` trait. - -For each constant, context variable and register access the corresponding `CCostedPair` -is computed from actual data values by recursively deconstructing them down to primitive -types. -For example for the constant `(10, (10L, true))` the following costed value will be created -in costed graph -```scala -CCostedPair( - CCostedPrim(10, costOf("Const:() => Int"), sizeOf[Int]), - CCostedPair( - CCostedPrim(10L, costOf("Const:() => Long"), sizeOf[Long]), - CCostedPrim(true, costOf("Const:() => Boolean"), sizeOf[Boolean]))) -``` - -##### Costed Values of Coll Type - -If `Item` is a type of array element, then costed value of type `Coll[Item]` is -represented by the following specializations of `Costed[Coll[Item]]` type -```scala -class CCostedColl[Item]( - val values: Coll[Item], val costs: Coll[Int], - val sizes: Coll[Long], val valuesCost: Int) extends CostedColl[Item] { - def value: Coll[Item] = values - def cost: Int = valuesCost + costs.sum - def dataSize: Long = sizes.sum - def mapCosted[Res](f: Costed[Item] => Costed[Res]): CostedColl[Res] = rewritableMethod - def foldCosted[B](zero: Costed[B], op: Costed[(B, Item)] => Costed[B]): Costed[B] = rewritableMethod -} -``` -For constant, context variables and registers values of `Coll` type the costing information -of `CCostedColl` is computed from actual data. - -Note methods `mapCosted` and `foldCosted`, these methods represent costed version of original -collection methods. Note the methods are defined as rewritable, meaning their implementation -require special rewriting rule. See section [Rewrite Rules](#RewriteRules) - -#### Building Costed Graph - -Given an environment `envVals` and ErgoTree `tree` a Costed Graph can be obtained as -[reified lambda](https://github.com/scalan/scalan.github.io/blob/master/idioms.md#Idiom4) of type -`Ref[Context => Costed[T#WrappedType]]` -This transformation is implemented as shown in the following `buildCostedGraph` method, which can -be found in `RuntimeCosting.scala` file. -```scala -def buildCostedGraph[T <: SType](envVals: Map[Any, SValue], tree: Value[T]): Ref[Context => Costed[T#WrappedType]] = - fun { ctx: Ref[Context] => // here ctx represents data context - val ctxC = RCCostedContext(ctx) // data context is wrapped into Costed value container - val env = envVals.mapValues(v => evalNode(ctxC, Map(), v)) // do costing of environment - val res = evalNode(ctxC, env, tree) // traverse tree recursively applying costing rules - res - } -``` -Note that function `evalNode` applies the [costing rules](#CostingRules) recursively for tree nodes -(of `sigmastate.Values.Value[T]` type). Those rules are executed in the `fun` block and -as result all the created graph nodes belong to the resulting costed graph - -#### Costing Rules - - -In order to build costed graph, the algorithm have to recursively traverse ErgoTree. -For each node of ErgoTree, separate _costing rule_ is applied using `evalNode` method -whose structure is show below. -```scala -type RCosted[A] = Ref[Costed[A]] -type CostingEnv = Map[Any, RCosted[_]] -def evalNode[T <: SType](ctx: Ref[CostedContext], env: CostingEnv, node: Value[T]): RCosted[T#WrappedType] = { - def eval[T <: SType](node: Value[T]) = evalNode(ctx, env, node) - object In { def unapply(v: SValue): Nullable[RCosted[Any]] = Nullable(evalNode(ctx, env, v)) } - ... - node match { - case Node1(In(arg1),...,In(argK)) => rhs1(arg1,...,argK) - ... - case NodeN(In(arg1),...,In(argK)) => rhsN(arg1,...,argK) - } -} -``` -Here `In` is a helper extractor which recursively apply `evalNode` for each argument so that variables -`arg1,...,argK` represent results of costing of the corresponding subtree. -The right hand side of each rule (`rhs1,...rhsN`) contains operations with -[Ref types](https://github.com/scalan/scalan.github.io/blob/master/idioms.md#Idiom3), the effect of their -execution is creation of new graph nodes which become part of resulting costed graph. - -Following is an example of a simple costing rule to introduce basic concepts -(it can be found in RuntimeCosting.scala file). -```scala - case sigmastate.MultiplyGroup(In(_l), In(_r)) => - val l = asRep[Costed[GroupElement]](_l) // type cast to an expected Ref type - val r = asRep[Costed[GroupElement]](_r) - val value = l.value.add(r.value) // value sub-rule - val cost = l.cost + r.cost + costOf(node) // cost sub-rule - val size = CryptoConstants.groupSize.toLong // size sub-rule - RCCostedPrim(value, cost, size) -``` -The rule first recognizes specific ErgoTree node, then recursively each argument is costed -so that the result is bound with variables `_l` and `_r`. -Right hand side starts with typecasting costed subgraphs to expected types. This operation is safe -because input ErgoTree is type checked. These typecasts also make the rest of the rules typesafe, -which is ensured by Scala compiler checking correctness of the operations. -Using costed arguments `l` and `r` the rule contains three sub-rules. -_Value rule_ implements calculation of the resulting value and essentially translates -MultiplyGroup operation into operation in the costed graph. -_Cost rule_ defines formula of the cost for `MultiplyGroup` operation (by adding to the costed graph). -_Size rule_ defines formula of the size for `MultiplyGroup` operation -And finally `value`, `cost` and `size` are packed into costed value which represent current tree node -in the costed graph. - -The rule above has the simples form and applicable to most simple operations. However some operations -require rules which don't fall into this default patterns. -Following is an example rule for `MapCollection` tree node, which makes recursive costing of arguments -explicit by using `eval` helper and also employ other idioms of staged evaluation. -```scala - case MapCollection(input, id, mapper) => - val eIn = stypeToElem(input.tpe.elemType) // translate sigma type to Special type descriptor - val xs = asRep[CostedColl[Any]](eval(input)) // recursively build subgraph for input argument - implicit val eAny = xs.elem.asInstanceOf[CostedElem[Coll[Any],_]].eVal.eA - assert(eIn == eAny, s"Types should be equal: but $eIn != $eAny") - val mapperC = fun { x: Ref[Costed[Any]] => // x argument is already costed - evalNode(ctx, env + (id -> x), mapper) // associate id in the tree with x node of the graph - } - val res = xs.mapCosted(mapperC) // call costed method of costed collection - res -``` -Observe that this rule basically translates mapping operation over collection into invocation of -the method `mapCosted` on costed collection value `xs` with appropriately prepared argument `mapperC`. -Because `xs` has [Ref type](https://github.com/scalan/scalan.github.io/blob/master/idioms.md#Idiom3) -this invocation has an effect of adding new `MethodCall(xs, "mapCosted", Seq(mapperC))` node to the graph. -This new node is immediately catched by the rewriting rule (see [Rewrite Rules](#RewriteRules) section) which further transforms it into final -nodes of resulting costed graph. Such separation makes the whole algorithm more modular. - -### Spliting Costed Graph (Step 4) - - -Costed Graph represents simultaneous calculation of original contract and cost information along contract's data flow. -However in order to perform cost estimation before contract calculation we need to separate contract calculation -operations of the Costed Graph from cost and data size computing operations. - -After building a Costed Graph -```scala -val graphC: Ref[Context => SType#WrappedValue] = buildCostedGraph(env, tree) -``` -we can perform _splitting_ by using the function `split` to obtain `calcF` and `costF` functions -```scala -val Pair(calcF: Ref[Context => SType#WrappedValue], costF: Ref[Context => Int]) = split(graphC) -``` -Both `calcF` and `costF` take `Context` as its argument and both represented as -[reified lambdas](https://github.com/scalan/scalan.github.io/blob/master/idioms.md#Idiom4) of -Scalan/Special IR. - -This _splitting_ function is generic and defined as shown below -```scala - def split[T,R](f: Ref[T => Costed[R]]): Ref[(T => R, T => Int)] = { - val calc = fun { x: Ref[T] => val y = f(x); val res = y.value; res } - val cost = fun { x: Ref[T] => f(x).cost } - Pair(calc, cost) - } -``` -In order to understand how this function works first observe, that this function is from -reified lambda to a pair of reified lambdas, thus it performs transformation of one graph to a pair of -new graphs. -Second, consider the `fun` block in the definition of `calc` and the application `f(x)` inside the block. -Because `f` is a reified lambda then according to staged evaluation semantics its application to `x` -has an effect of inlining, i.e. unfolding the graph of `f` with `x` substituted for `f`'s argument into -the block of `fun`. -Third, remember that Costed Graph have nodes of types derived from `Costed` depending of the type of value, -e.g. for primitive type `CCostedPrim(v, c, s)` node is added to the graph. -This is described in [Costed Values](#CostedValues) section. -And forth, recall that typical costing rule have the following formulas for calculation of costed values. -```scala - val value = l.value.add(r.value) // value sub-rule - val cost = l.cost + r.cost + costOf(node) // cost sub-rule - val size = CryptoConstants.groupSize.toLong // size sub-rule - RCCostedPrim(value, cost, size) -``` -Combined with third point and assuming -```scala - l = new CCostedPrim(v1, c1, s1) - r = new CCostedPrim(v2, c2, s2) -``` -we can conclude that `l.value.add(r.value)` will evaluate to `v1.add(v2)` and similarly -`l.cost + r.cost + costOf(node)` will evaluate to `c1 + c2 + costOf(node)`. - -```scala - val value = v1.add(v2) // value sub-rule - val cost = c1 + c2 + costOf(node) // cost sub-rule - val size = CryptoConstants.groupSize.toLong // size sub-rule - RCCostedPrim(value, cost, size) -``` -In other words, resuting node of the cosing rule doesn't depend on intermediate costed nodes. -Such intermediate nodes (like `CCostedPrim` above) become dead and are not used in values calculation. -This is the key insight in the implementation of function `split`. - -Now, keeping above in mind, after the graph of `f` is unfolded `y` represents resulting node. -Thus, `value` property is called on the costed node `y` which has type `Costed`. -The resulting symbol obtained by execution of `y.value` becomes a resulting node of the -`calc` graph. After the block of `fun` operator is executed, dead-code-elimination is performed by `fun` -using simple collection of reachable nodes of the graph starting from resulting node `res`. - -Thus, by construction, the body of `calc` contains only operations necessary to execute contract, and have no -vestiges of costing and sizing operations. - -### Real World Complications - -Here we discuss some complications in the algorithm caused by the diversity of real world examples. -The main motivation here is to keep main algorithm generic and simple, and encapsulate all complexity -of the specific cases into reusable modules. - - -#### Rewrite Rules - -[Rewrite rules](https://github.com/scalan/scalan.github.io/blob/master/idioms.md#Idiom6) is the mechanism -to hook into graph building process and perform on the fly substitution of -specific sub-graphs with equivalent but different sub-graphs. -The following rule uses auto-generated extractor `mapCosted` which recognizes invocations of method -`CostedColl.mapCosted` (Remember, this method was used in costing rule for `MapCollection` tree node). - -```scala -override def rewriteDef[T](d: Def[T]): Ref[_] = { - val CCM = CostedCollMethods - d match { - case CCM.mapCosted(xs: RCostedColl[a], _f: RCostedFunc[_, b]) => - val f = asRep[Costed[a] => Costed[b]](_f) - val (calcF, costF, sizeF) = splitCostedFunc[a, b](f) - val vals = xs.values.map(calcF) - val mRes = AllMarking(element[Int]) - val mCostF = sliceAnalyzer.analyzeFunc(costF, mRes) - implicit val eA = xs.elem.eItem - implicit val eB = f.elem.eRange.eVal - - val costs = mCostF.mDom match { - case PairMarking(markA,_) if markA.isEmpty => - val slicedCostF = fun { in: Ref[(Int, Long)] => costF(Pair(variable[a], in)) } - xs.costs.zip(xs.sizes).map(slicedCostF) - case _ => - xs.values.zip(xs.costs.zip(xs.sizes)).map(costF) - } - val tpeB = elemToSType(eB) - val sizes = if (tpeB.isConstantSize) { - colBuilder.replicate(xs.sizes.length, typeSize(tpeB)) - } else - xs.sizes.map(sizeF) - RCCostedColl(vals, costs, sizes, xs.valuesCost) - case _ => super.rewriteDef(d) - } -} -``` -Method `rewriteDef` is defined in `Scalan` cake trait and can be overridden following _stackable overrides_ -pattern (calling super.rewriteDef for the default case). This specific rule is defined in `RuntimeCosting` -trait. diff --git a/docs/ergoscript-compiler.md b/docs/ergoscript-compiler.md new file mode 100644 index 0000000000..e50f7d6a00 --- /dev/null +++ b/docs/ergoscript-compiler.md @@ -0,0 +1,56 @@ + +# ErgoScript Compiler + +Sigma frontend implements the following pipeline: + +`SourceCode` --> `parse` --> `bind` -> `typecheck` -> `buildGraph` -> `buildTree` -> `ErgoTree` + +Here: +- `SourceCode` - a string of unicode characters +- `parse` - method `SigmaCompiler.parse` +- `bind` - method `SigmaBinder.bind` +- `typecheck` - method `SigmaTyper.typecheck` +- `buildGraph` - method `IRContext.buildGraph` +- `buildTree` - method `IRContext.buildTree` +- `ErgoTree` - an intermediate representation which can be processed by Sigma [Interpreter](https://github.com/ScorexFoundation/sigmastate-interpreter/blob/master/interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala#L46) + +## Parser +`parse` takes a string and produces abstract syntax tree, AST, of a Sigma expression represented by `Value` type in Scala. + +In case of any errors it throws `ParserException` + +## Binder +`SigmaBinder` takes an AST of successfully parsed Sigma expression and resolves +global variables and predefined functions that are looked up in the provided environment. +Binder transforms environment values of predefined Scala types (such as Int, Boolean, Box, etc.) +into constant nodes (IntConstant, BoxConstant, etc) of the corresponding type. (See also `Constant` class) + +In case of any error it throws `BinderException` + +## Typer +`SigmaTyper` takes an AST from the output of `SigmaBinder` and assigns types +to all tree nodes. Since AST is immutable data structure the typer produces a new tree. + +Type assignment is performed by `assignType` tree transformation which assign correct types for all +tree nodes. + +In case of any error it throws `TyperException` + +## Graph Building + +`IRContext.buildGraph` takes an AST from the output of `SigmaTyper` and builds a graph where nodes are operations and edges are dependencies between operations. +During graph building the following optimizations are performed: +- constant propagation +- common subexpression elimination +- dead code elimination + +## Tree Building + +`IRContext.buildTree` takes a graph from the output of `IRContext.buildGraph` and builds the resulting ErgoTree. + +## IR contexts + +- `IRContext` - the main interface of graph IR which mixes in both GraphBuilding and TreeBuilding traits. + Since v5.0 it is not used by Interpreter and thus not part of consensus. + +- `CompiletimeIRContext` - the main implementation of IRContext diff --git a/docs/notes.md b/docs/notes.md index cc75f1065d..7bea09d887 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -3,13 +3,13 @@ These dependencies can be removed with refactoring -| Jar | Size, Kb | -|---------------|---------------| -| - jline-2.14.3.jar | 268 | -| cats-core_2.12-1.4.0.jar | 4400 | -| - cats-kernel_2.12-1.4.0.jar | 3200 | -| - algebra_2.12-0.7.0.jar | 1100 | -| - spire-macros_2.12-0.14.1.jar | 73 | +| Jar | Size, Kb | +|--------------------------------|----------| +| - jline-3.10.0.jar | 715 | +| cats-core_2.12-2.1.0.jar | 4900 | +| - cats-kernel_2.12-1.4.0.jar | 3500 | +| - algebra_2.12-2.0.0-M2.jar | 1400 | +| - spire-macros_2.12-0.14.1.jar | 79 | diff --git a/docs/releasenotes.md b/docs/releasenotes.md deleted file mode 100644 index e34cfa8562..0000000000 --- a/docs/releasenotes.md +++ /dev/null @@ -1,21 +0,0 @@ -# v2.2 - -- soft-forkability for cost estimation (#503) -- optimization of cost estimation rules (#523) -- new Context parameters (initCost, costLimit) -- implemented fast complexity measure of box propositions - (to fail-fast on too complex scripts #537) -- implemented test cases to check fast script rejections of - of either oversized of too costly scripts - -# v2.1 -- soft-forkability for language evolution (add types, operations) #500 -- ErgoTree language specification (abstract syntax, typing rules, semantics, serialization format) #495 -- ErgoTree IR extended with metadata to generate specification appendixes. -- more operations implemented (zip #498, element-wise xor for collections #494, flatMap #493, - plusModQ, minusModQ #488, logical xor #478, filter #471, Option.map #469, groupGenerator #440) -- security improvements (like limiting ErgoTree depth during deserialization #459 #482) -- ICO and LETS examples #450 -- final version of ErgoScript white paper (#410) -- improvements in scorex-util serialization -- various fixes code cleanup and better test coverage (#504, #508, #515) \ No newline at end of file diff --git a/docs/sigma-dsl.md b/docs/sigma-dsl.md index ee9691186b..e9ca0ba0ff 100644 --- a/docs/sigma-dsl.md +++ b/docs/sigma-dsl.md @@ -6,10 +6,8 @@ code directly in Scala IDE (e.g. IntelliJ IDEA) and copy-paste code snippets between SigmaDsl and SigmaScript. Special Scala macros can also be used to automatically translate SigmaDsl to - Sigma byte code. - -SigmaDsl is implemented as Scala library using [Special](https://github.com/scalan/special) -framework. + Sigma byte code. Some prototype has been implemented [here](https://github.com/ergoplatform/ergo-scala-compiler) ## See also -[Special](https://github.com/scalan/special) + +[ergo-scala-compiler](https://github.com/ergoplatform/ergo-scala-compiler) diff --git a/docs/sigma-front-end.md b/docs/sigma-front-end.md deleted file mode 100644 index 693ca98089..0000000000 --- a/docs/sigma-front-end.md +++ /dev/null @@ -1,55 +0,0 @@ - -# Sigma language front-end - -Sigma frontend implements the following pipeline: - -SourceCode --> `Parser` --> `Binder` -> `Typer` -> `CompiletimeCosting` -> `TreeBuilding` -> ErgoTree - -Here: -- SourceCode is a string of unicode characters -- ErgoTree is an intermediate representation which can be processed by Sigma [Interpreter](https://github.com/ScorexFoundation/sigmastate-interpreter/blob/master/src/main/scala/sigmastate/interpreter/Interpreter.scala) - -## Parser -`SigmaParser` takes a string and produces abstract syntax tree, AST, of a Sigma expression represented by `Value` type in Scala. - -In case of any errors it throws `ParserException` - -## Binder -`SigmaBinder` takes an AST of successfully parsed Sigma expression and resolves -global variables and predefined functions that are looked up in the provided environment.. -Binder transforms environment values of predefined Scala types (such as Int, Boolean, Box, etc.) -into constant nodes (IntConstant, BoxConstant, etc) of the corresponding type. (See also `Constant` class) - -In case of any error it throws `BinderException` - -## Typer -`SigmaTyper` takes an AST from the output of `SigmaBinder` and assigns types -to all tree nodes. Since AST is immutable data structure the typer produces a new tree. - -Type assignment is performed by `assignType` tree transformation which assign correct types for all -tree nodes. - -In case of any error it throws `TyperException` - -## Costing - -See Costing.md for detailed description of costing process. - -## TreeBuilding - - -## IR contexts -There are following IR contexts. - -Context class | Description -----------------------|------------ - IRContext | Generic context which includes extends Evaluation, RuntimeCosting and TreeBuilding - RuntimeIRContext | context which should be used during transaction validation - CompiletimeIRContext | context which should be used during ErgoScript compilation - -The reason to have different contexts is to limit RuntimeIRContext to support only those nodes which can be serialized as part of ErgoTree. -This doesn't include all ErgoScript nodes which CompiletimeIRContext can handle. -For example, compileWithCosting should use IR context with CompiletimeCosting mixed in. -However, Interpreter takes as input compiled or deserialized ErgoTree, so it can work without CompiletimeCosting mixed in into IR context. - - diff --git a/docs/soft-fork-log.md b/docs/soft-fork-log.md deleted file mode 100644 index 072e177827..0000000000 --- a/docs/soft-fork-log.md +++ /dev/null @@ -1,38 +0,0 @@ - -## A log of changes leading to soft-fork - -This list should be updated every time something soft-forkable is added. - -### Changes in v2.1 - - - new type (SGlobal.typeCode = 106) - - new method (SGlobal.groupGenerator.methodId = 1) - - new method (SAvlTree.updateDigest.methodId = 15) - - removed GroupElement.nonce (changed codes of getEncoded, exp, multiply, negate) - - change in Coll.filter serialization format (removed tagged variable id, changed condition type) - -### Changes in v2.2 - -#### Changes in ErgoConstants - MaxTokens 4 -> 255 - MaxPropositionBytes 64K -> 4K - SizeBoxBytesWithoutRefsMax 64K -> 4K - MaxSigmaPropSizeInBytes 1K (added because SigmaProp.isConstantSize == true) - MaxLoopLevelInCostFunction 1 (added and checked) - -#### ComplexityTable added - -#### Changes in CostTable - -MinimalCost = 10 (1) -interpreterInitCost = 10000 (added) -perGraphNodeCost = 200 (added) -val costFactor: Double = 2d (added) -constCost = 10 (1) -lambdaCost = 10 (1) -plusMinus = 10 (2) -comparisonCost = 10 (3) -lambdaInvoke = 30 (added) -concreteCollectionItemCost = 10 (added) // since each item is a separate graph node -logicCost = 10 (2) -castOp = 10 (5) \ No newline at end of file diff --git a/graph-ir/shared/src/main/scala/scalan/Base.scala b/graph-ir/shared/src/main/scala/scalan/Base.scala index b105665f14..22e01da17f 100644 --- a/graph-ir/shared/src/main/scala/scalan/Base.scala +++ b/graph-ir/shared/src/main/scala/scalan/Base.scala @@ -301,16 +301,13 @@ abstract class Base { scalan: Scalan => def lift(srcF: SA => SB): Ref[A => B] = FuncConst[SA,SB,A,B](srcF) } - implicit lazy val BooleanIsLiftable = asLiftable[Boolean,Boolean](BooleanElement.liftable) - implicit lazy val ByteIsLiftable = asLiftable[Byte,Byte](ByteElement.liftable) - implicit lazy val ShortIsLiftable = asLiftable[Short,Short](ShortElement.liftable) - implicit lazy val IntIsLiftable = asLiftable[Int,Int](IntElement.liftable) - implicit lazy val LongIsLiftable = asLiftable[Long,Long](LongElement.liftable) - implicit lazy val StringIsLiftable = asLiftable[String,String](StringElement.liftable) - implicit lazy val FloatIsLiftable = asLiftable[Float,Float](FloatElement.liftable) - implicit lazy val DoubleIsLiftable = asLiftable[Double,Double](DoubleElement.liftable) - implicit lazy val UnitIsLiftable = asLiftable[Unit,Unit](UnitElement.liftable) - implicit lazy val CharIsLiftable = asLiftable[Char,Char](CharElement.liftable) + implicit lazy val BooleanIsLiftable: Liftable[Boolean, Boolean] = asLiftable[Boolean,Boolean](BooleanElement.liftable) + implicit lazy val ByteIsLiftable: Liftable[Byte, Byte] = asLiftable[Byte,Byte](ByteElement.liftable) + implicit lazy val ShortIsLiftable: Liftable[Short, Short] = asLiftable[Short,Short](ShortElement.liftable) + implicit lazy val IntIsLiftable: Liftable[Int, Int] = asLiftable[Int,Int](IntElement.liftable) + implicit lazy val LongIsLiftable: Liftable[Long, Long] = asLiftable[Long,Long](LongElement.liftable) + implicit lazy val StringIsLiftable: Liftable[String, String] = asLiftable[String,String](StringElement.liftable) + implicit lazy val UnitIsLiftable: Liftable[Unit, Unit] = asLiftable[Unit,Unit](UnitElement.liftable) implicit def PairIsLiftable[SA,SB,A,B] (implicit lA: Liftable[SA, A], lB: Liftable[SB, B]): Liftable[(SA, SB), (A, B)] = diff --git a/graph-ir/shared/src/main/scala/scalan/TypeDescs.scala b/graph-ir/shared/src/main/scala/scalan/TypeDescs.scala index 48cb0a0dc9..57fc2a1a52 100644 --- a/graph-ir/shared/src/main/scala/scalan/TypeDescs.scala +++ b/graph-ir/shared/src/main/scala/scalan/TypeDescs.scala @@ -367,11 +367,8 @@ abstract class TypeDescs extends Base { self: Scalan => implicit val ShortElement: Elem[Short] = new BaseElemLiftable(0.toShort, ShortType) implicit val IntElement: Elem[Int] = new BaseElemLiftable(0, IntType) implicit val LongElement: Elem[Long] = new BaseElemLiftable(0L, LongType) - implicit val FloatElement: Elem[Float] = new BaseElemLiftable(0.0F, FloatType) - implicit val DoubleElement: Elem[Double] = new BaseElemLiftable(0.0, DoubleType) implicit val UnitElement: Elem[Unit] = new BaseElemLiftable((), UnitType) implicit val StringElement: Elem[String] = new BaseElemLiftable("", StringType) - implicit val CharElement: Elem[Char] = new BaseElemLiftable('\u0000', CharType) /** Implicitly defines element type for pairs. */ implicit final def pairElement[A, B](implicit ea: Elem[A], eb: Elem[B]): Elem[(A, B)] = diff --git a/graph-ir/shared/src/main/scala/wrappers/scala/impl/WOptionsImpl.scala b/graph-ir/shared/src/main/scala/wrappers/scala/impl/WOptionsImpl.scala index 4b69cfe957..e5e7f333e6 100644 --- a/graph-ir/shared/src/main/scala/wrappers/scala/impl/WOptionsImpl.scala +++ b/graph-ir/shared/src/main/scala/wrappers/scala/impl/WOptionsImpl.scala @@ -90,7 +90,7 @@ class WOptionCls extends EntityObject("WOption") { case class WOptionAdapter[A](source: Ref[WOption[A]]) extends Node with WOption[A] with Def[WOption[A]] { - implicit lazy val eA = source.elem.typeArgs("A")._1.asInstanceOf[Elem[A]] + implicit lazy val eA: Elem[A] = source.elem.typeArgs("A")._1.asInstanceOf[Elem[A]] val resultType: Elem[WOption[A]] = element[WOption[A]] override def transform(t: Transformer) = WOptionAdapter[A](t(source)) diff --git a/graph-ir/shared/src/main/scala/wrappers/scalan/impl/WRTypesImpl.scala b/graph-ir/shared/src/main/scala/wrappers/scalan/impl/WRTypesImpl.scala index dfbd0f3111..66bbf9ed81 100644 --- a/graph-ir/shared/src/main/scala/wrappers/scalan/impl/WRTypesImpl.scala +++ b/graph-ir/shared/src/main/scala/wrappers/scalan/impl/WRTypesImpl.scala @@ -67,7 +67,7 @@ class WRTypeCls extends EntityObject("WRType") { case class WRTypeAdapter[A](source: Ref[WRType[A]]) extends Node with WRType[A] with Def[WRType[A]] { - implicit lazy val eA = source.elem.typeArgs("A")._1.asInstanceOf[Elem[A]] + implicit lazy val eA: Elem[A] = source.elem.typeArgs("A")._1.asInstanceOf[Elem[A]] val resultType: Elem[WRType[A]] = element[WRType[A]] override def transform(t: Transformer) = WRTypeAdapter[A](t(source)) diff --git a/interpreter/js/src/main/scala/sigmastate/crypto/Platform.scala b/interpreter/js/src/main/scala/sigmastate/crypto/Platform.scala index d23407e7d7..ba5460e517 100644 --- a/interpreter/js/src/main/scala/sigmastate/crypto/Platform.scala +++ b/interpreter/js/src/main/scala/sigmastate/crypto/Platform.scala @@ -199,8 +199,12 @@ object Platform { override def infinity(): crypto.Ecp = new Ecp(ctx.getInfinity()) - override def decodePoint(encoded: Array[Byte]): crypto.Ecp = + override def decodePoint(encoded: Array[Byte]): crypto.Ecp = { + if (encoded(0) == 0) { + return infinity() + } new Ecp(ctx.decodePoint(Base16.encode(encoded))) + } override def generator: crypto.Ecp = new Ecp(ctx.getGenerator()) diff --git a/interpreter/js/src/test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala b/interpreter/js/src/test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala index 9fc4e43d86..63839e67ef 100644 --- a/interpreter/js/src/test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala +++ b/interpreter/js/src/test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala @@ -2,7 +2,6 @@ package sigmastate.crypto import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec -import scorex.util.encode.Base16 import scala.scalajs.js import scala.scalajs.js.typedarray.Uint8Array diff --git a/interpreter/jvm/src/main/scala/sigmastate/crypto/Platform.scala b/interpreter/jvm/src/main/scala/sigmastate/crypto/Platform.scala index dcd5deabde..3b74ba4714 100644 --- a/interpreter/jvm/src/main/scala/sigmastate/crypto/Platform.scala +++ b/interpreter/jvm/src/main/scala/sigmastate/crypto/Platform.scala @@ -187,7 +187,7 @@ object Platform { case _: Int => tpe == SInt case _: Long => tpe == SLong case _: BigInt => tpe == SBigInt - case _: String => tpe == SString // TODO v6.0: remove this case + case _: String => tpe == SString // TODO v6.0: remove this case (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/905) case _: GroupElement => tpe.isGroupElement case _: SigmaProp => tpe.isSigmaProp case _: AvlTree => tpe.isAvlTree diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala index 37a6e96193..dadd0d5c7a 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala @@ -1,11 +1,11 @@ package org.ergoplatform -import scorex.utils.{Ints, Shorts} -import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, Token} +import org.ergoplatform.ErgoBox.{AdditionalRegisters, Token} import org.ergoplatform.settings.ErgoAlgos import scorex.crypto.authds.ADKey -import scorex.crypto.hash.{Blake2b256, Digest32} +import scorex.crypto.hash.Blake2b256 import scorex.util._ +import scorex.utils.{Ints, Shorts} import sigmastate.SCollection.SByteArray import sigmastate.SType.AnyOps import sigmastate.Values._ @@ -13,12 +13,10 @@ import sigmastate._ import sigmastate.eval.Extensions._ import sigmastate.eval._ import sigmastate.serialization.SigmaSerializer -import sigmastate.utils.{SigmaByteReader, SigmaByteWriter, Helpers} +import sigmastate.utils.{Helpers, SigmaByteReader, SigmaByteWriter} import sigmastate.utxo.ExtractCreationInfo import special.collection._ -import scala.runtime.ScalaRunTime - /** * Box (aka coin, or an unspent output) is a basic concept of a UTXO-based cryptocurrency. In Bitcoin, such an object * is associated with some monetary value (arbitrary, but with predefined precision, so we use integer arithmetic to @@ -37,6 +35,8 @@ import scala.runtime.ScalaRunTime * A transaction is unsealing a box. As a box can not be open twice, any further valid transaction can not be linked * to the same box. * + * Note, private constructor can only be used from within the ErgoBox companion object, e.g. by deserializer. + * * @param value - amount of money associated with the box * @param ergoTree - guarding script, which should be evaluated to true in order to open this box * @param additionalTokens - secondary tokens the box contains @@ -46,17 +46,28 @@ import scala.runtime.ScalaRunTime * @param creationHeight - height when a transaction containing the box was created. * This height is declared by user and should not exceed height of the block, * containing the transaction with this box. + * @param _bytes - serialized bytes of the box when not `null` * HOTSPOT: don't beautify the code of this class */ -class ErgoBox( +class ErgoBox private ( override val value: Long, override val ergoTree: ErgoTree, - override val additionalTokens: Coll[Token] = Colls.emptyColl[Token], - override val additionalRegisters: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] = Map.empty, + override val additionalTokens: Coll[Token], + override val additionalRegisters: AdditionalRegisters, val transactionId: ModifierId, val index: Short, - override val creationHeight: Int + override val creationHeight: Int, + _bytes: Array[Byte] ) extends ErgoBoxCandidate(value, ergoTree, creationHeight, additionalTokens, additionalRegisters) { + /** This is public constructor has the same parameters as the private primary constructor, except bytes. */ + def this(value: Long, + ergoTree: ErgoTree, + additionalTokens: Coll[Token] = Colls.emptyColl[Token], + additionalRegisters: AdditionalRegisters = Map.empty, + transactionId: ModifierId, + index: Short, + creationHeight: Int) = + this(value, ergoTree, additionalTokens, additionalRegisters, transactionId, index, creationHeight, null) import ErgoBox._ @@ -72,11 +83,15 @@ class ErgoBox( } } - // TODO optimize: avoid serialization by implementing lazy box deserialization /** Serialized content of this box. * @see [[ErgoBox.sigmaSerializer]] */ - lazy val bytes: Array[Byte] = ErgoBox.sigmaSerializer.toBytes(this) + lazy val bytes: Array[Byte] = { + if (_bytes != null) + _bytes // bytes provided by deserializer + else + ErgoBox.sigmaSerializer.toBytes(this) + } override def equals(arg: Any): Boolean = arg match { case x: ErgoBox => java.util.Arrays.equals(id, x.id) @@ -136,7 +151,7 @@ object ErgoBox { /** Represents id of optional registers of a box. */ sealed abstract class NonMandatoryRegisterId(override val number: Byte) extends RegisterId - type AdditionalRegisters = Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] + type AdditionalRegisters = scala.collection.Map[NonMandatoryRegisterId, EvaluatedValue[_ <: SType]] object R0 extends MandatoryRegisterId(0, "Monetary value, in Ergo tokens") object R1 extends MandatoryRegisterId(1, "Guarding script") @@ -199,10 +214,16 @@ object ErgoBox { } override def parse(r: SigmaByteReader): ErgoBox = { - val ergoBoxCandidate = ErgoBoxCandidate.serializer.parse(r) + val start = r.position + val c = ErgoBoxCandidate.serializer.parse(r) val transactionId = r.getBytes(ErgoLikeTransaction.TransactionIdBytesSize).toModifierId val index = r.getUShort() - ergoBoxCandidate.toBox(transactionId, index.toShort) + val end = r.position + val len = end - start + r.position = start + val boxBytes = r.getBytes(len) // also moves position back to end + new ErgoBox(c.value, c.ergoTree, c.additionalTokens, c.additionalRegisters, + transactionId, index.toShort, c.creationHeight, boxBytes) } } } diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 8d5765caf5..da1fdc94fd 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -38,7 +38,7 @@ class ErgoBoxCandidate(val value: Long, val ergoTree: ErgoTree, val creationHeight: Int, val additionalTokens: Coll[Token] = Colls.emptyColl, - val additionalRegisters: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] = Map()) + val additionalRegisters: AdditionalRegisters = Map()) extends ErgoBoxAssets { /** Transforms this tree to a proposition, substituting the constants if the constant diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala index 3bf455ae6f..3b356fd4ec 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala @@ -200,11 +200,11 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData, lastBlockUtxoRoot, headers, preHeader, dataBoxes, boxesToSpend, spendingTransaction, selfIndex, extension, validationSettings, costLimit, initCost, activatedScriptVersion) - var hashCode = 0 + var h = 0 cfor(0)(_ < state.length, _ + 1) { i => - hashCode = 31 * hashCode + state(i).hashCode + h = 31 * h + state(i).hashCode } - hashCode + h } override def toString = s"ErgoLikeContext(lastBlockUtxoRoot=$lastBlockUtxoRoot, headers=$headers, preHeader=$preHeader, dataBoxes=$dataBoxes, boxesToSpend=$boxesToSpend, spendingTransaction=$spendingTransaction, selfIndex=$selfIndex, extension=$extension, validationSettings=$validationSettings, costLimit=$costLimit, initCost=$initCost, activatedScriptVersion=$activatedScriptVersion)" diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index 5ad3e08207..f5b8a2c15f 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -50,7 +50,6 @@ trait ErgoLikeTransactionTemplate[IT <: UnsignedInput] { lazy val inputIds: IndexedSeq[ADKey] = inputs.map(_.boxId) - override def toString = s"ErgoLikeTransactionTemplate(dataInputs=$dataInputs, inputs=$inputs, outputCandidates=$outputCandidates)" } @@ -76,6 +75,8 @@ class UnsignedErgoLikeTransaction(override val inputs: IndexedSeq[UnsignedInput] } override def hashCode(): Int = id.hashCode() + + override def toString = s"UnsignedErgoLikeTransaction(dataInputs=$dataInputs, inputs=$inputs, outputCandidates=$outputCandidates)" } object UnsignedErgoLikeTransaction { @@ -105,6 +106,7 @@ class ErgoLikeTransaction(override val inputs: IndexedSeq[Input], } override def hashCode(): Int = id.hashCode() + override def toString = s"ErgoLikeTransaction(dataInputs=$dataInputs, inputs=$inputs, outputCandidates=$outputCandidates)" } object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction, ErgoLikeTransaction] { diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/SigmaConstants.scala b/interpreter/shared/src/main/scala/org/ergoplatform/SigmaConstants.scala index ff1251ae10..be745d8d7d 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/SigmaConstants.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/SigmaConstants.scala @@ -66,10 +66,6 @@ object SigmaConstants { "Max children count should not be greater than provided value") { } - object ScriptCostLimit extends SizeConstant[Int](1000000, 12, - "Maximum execution cost of a script") { - } - object MaxLoopLevelInCostFunction extends SizeConstant[Int](1, 13, "Maximum allowed loop level in a cost function") { } @@ -96,7 +92,6 @@ object SigmaConstants { MaxTupleLength, MaxHeaders, MaxChildrenCountForAtLeastOp, - ScriptCostLimit, MaxLoopLevelInCostFunction, VotesArraySize, AutolykosPowSolutionNonceArraySize diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala index 6314ea0b20..e59575a272 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala @@ -48,7 +48,7 @@ abstract class SigmaValidationSettings extends Iterable[(Short, (ValidationRule, def isSoftFork(ruleId: Short, ve: ValidationException): Boolean = { val infoOpt = get(ruleId) infoOpt match { - case Some((_, ReplacedRule(newRuleId))) => true + case Some((_, ReplacedRule(_))) => true case Some((rule, status)) => rule.isSoftFork(this, rule.id, status, ve.args) case None => false } diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala index 1e2b8b352a..667d8f4989 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala @@ -2,9 +2,9 @@ package org.ergoplatform.validation import sigmastate.serialization.SigmaSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} -import scalan.util.Extensions.{IntOps,LongOps} +import scalan.util.Extensions.{IntOps, LongOps} +import sigmastate.exceptions.SerializerException -// TODO v5.x: remove unused class and related json encoders /** The rules are serialized ordered by ruleId. * This serializer preserves roundtrip identity `deserialize(serialize(_)) = identity` * however it may not preserve `serialize(deserialize(_)) = identity` */ @@ -13,9 +13,9 @@ object SigmaValidationSettingsSerializer extends SigmaSerializer[SigmaValidation override def serialize(settings: SigmaValidationSettings, w: SigmaByteWriter): Unit = { val rules = settings.toArray.sortBy(_._1) w.putUInt(rules.length) - rules.foreach { r => - w.putUShort(r._1) - RuleStatusSerializer.serialize(r._2._2, w) + rules.foreach { case (id, (_, status)) => + w.putUShort(id) + RuleStatusSerializer.serialize(status, w) } } @@ -27,10 +27,12 @@ object SigmaValidationSettingsSerializer extends SigmaSerializer[SigmaValidation val status = RuleStatusSerializer.parse(r) ruleId -> status } - val initVs = ValidationRules.currentSettings - val res = parsed - .filter(pair => initVs.get(pair._1).isDefined) - .foldLeft(initVs) { (vs, rule) => vs.updated(rule._1, rule._2) } + val map = parsed.map { case (id, status) => + val (rule, _) = ValidationRules.currentSettings.get(id) + .getOrElse(throw SerializerException(s"Rule with id $id is not registered")) + id -> (rule, status) + }.toMap + val res = new MapSigmaValidationSettings(map) res } } diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/validation/ValidationRules.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/ValidationRules.scala index 1110916e03..2573d3f592 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/validation/ValidationRules.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/validation/ValidationRules.scala @@ -1,9 +1,9 @@ package org.ergoplatform.validation import scalan.util.Extensions.toUByte -import sigmastate.Values.{SValue, ErgoTree} +import sigmastate.Values.{ErgoTree, SValue} import sigmastate._ -import sigmastate.exceptions.{InvalidOpCode, SerializerException, ReaderPositionLimitExceeded, SigmaException, InterpreterException} +import sigmastate.exceptions.{InterpreterException, InvalidOpCode, ReaderPositionLimitExceeded, SerializerException, SigmaException} import sigmastate.serialization.OpCodes.OpCode import sigmastate.serialization.TypeSerializer.embeddableIdToType import sigmastate.serialization.{OpCodes, ValueSerializer} diff --git a/interpreter/shared/src/main/scala/sigmastate/SigSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/SigSerializer.scala index 07ff33c5bc..f003586d85 100644 --- a/interpreter/shared/src/main/scala/sigmastate/SigSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/SigSerializer.scala @@ -5,13 +5,14 @@ import scorex.util.encode.Base16 import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple, CryptoConstants} +import sigmastate.basics.{CryptoConstants, ProveDHTuple, SecondDHTupleProverMessage} import sigmastate.interpreter.ErgoTreeEvaluator.{fixedCostOp, perItemCostOp} import sigmastate.interpreter.{ErgoTreeEvaluator, NamedDesc, OperationCostInfo} import sigmastate.serialization.SigmaSerializer import sigmastate.util.safeNewArray import sigmastate.utils.{Helpers, SigmaByteReader, SigmaByteWriter} import debox.cfor +import sigmastate.eval.Extensions.ArrayOps import sigmastate.exceptions.SerializerException /** Contains implementation of signature (aka proof) serialization. @@ -61,7 +62,7 @@ class SigSerializer { w: SigmaByteWriter, writeChallenge: Boolean): Unit = { if (writeChallenge) { - w.putBytes(node.challenge) + w.putBytes(node.challenge.toArray) } node match { case dl: UncheckedSchnorr => @@ -184,7 +185,7 @@ class SigSerializer { // Verifier Step 2: Let e_0 be the challenge in the node here (e_0 is called "challenge" in the code) val challenge = if (challengeOpt == null) { Challenge @@ readBytesChecked(r, hashSize, - hex => warn(s"Invalid challenge in: $hex")) + hex => warn(s"Invalid challenge in: $hex")).toColl } else { challengeOpt } @@ -203,7 +204,7 @@ class SigSerializer { fixedCostOp(ParseChallenge_ProveDHT) { val z_bytes = readBytesChecked(r, order, hex => warn(s"Invalid z bytes for $dh: $hex")) val z = BigIntegers.fromUnsignedByteArray(z_bytes) - UncheckedDiffieHellmanTuple(dh, None, challenge, SecondDiffieHellmanTupleProverMessage(z)) + UncheckedDiffieHellmanTuple(dh, None, challenge, SecondDHTupleProverMessage(z)) } case and: CAND => @@ -223,18 +224,18 @@ class SigSerializer { // Read all the children but the last and compute the XOR of all the challenges including e_0 val nChildren = or.children.length val children = safeNewArray[UncheckedSigmaTree](nChildren) - val xorBuf = challenge.clone() + val xorBuf = challenge.toArray.clone() val iLastChild = nChildren - 1 cfor(0)(_ < iLastChild, _ + 1) { i => val parsedChild = parseAndComputeChallenges(or.children(i), r, null) children(i) = parsedChild - Helpers.xorU(xorBuf, parsedChild.challenge) // xor it into buffer + Helpers.xorU(xorBuf, parsedChild.challenge.toArray) // xor it into buffer } val lastChild = or.children(iLastChild) // use the computed XOR for last child's challenge children(iLastChild) = parseAndComputeChallenges( - lastChild, r, challengeOpt = Challenge @@ xorBuf) + lastChild, r, challengeOpt = Challenge @@ xorBuf.toColl) COrUncheckedNode(challenge, children) @@ -248,13 +249,13 @@ class SigSerializer { val polynomial = perItemCostOp(ParsePolynomial, nCoefs) { () => val coeffBytes = readBytesChecked(r, hashSize * nCoefs, hex => warn(s"Invalid coeffBytes for $th: $hex")) - GF2_192_Poly.fromByteArray(challenge, coeffBytes) + GF2_192_Poly.fromByteArray(challenge.toArray, coeffBytes) } val children = safeNewArray[UncheckedSigmaTree](nChildren) cfor(0)(_ < nChildren, _ + 1) { i => val c = perItemCostOp(EvaluatePolynomial, nCoefs) { () => - Challenge @@ polynomial.evaluate((i + 1).toByte).toByteArray + Challenge @@ polynomial.evaluate((i + 1).toByte).toByteArray.toColl } children(i) = parseAndComputeChallenges(th.children(i), r, c) } diff --git a/interpreter/shared/src/main/scala/sigmastate/UncheckedTree.scala b/interpreter/shared/src/main/scala/sigmastate/UncheckedTree.scala index 365a1f85a7..4b4b28fa0d 100644 --- a/interpreter/shared/src/main/scala/sigmastate/UncheckedTree.scala +++ b/interpreter/shared/src/main/scala/sigmastate/UncheckedTree.scala @@ -1,10 +1,9 @@ package sigmastate -import java.util.Arrays import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, ProveDlog, SecondDLogProverMessage} import sigmastate.basics.VerifierMessage.Challenge import sigmastate.Values.SigmaBoolean -import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, ProveDHTuple, SecondDiffieHellmanTupleProverMessage} +import sigmastate.basics.{FirstDHTupleProverMessage, ProveDHTuple, SecondDHTupleProverMessage} import sigmastate.crypto.GF2_192_Poly sealed trait UncheckedTree extends ProofTree @@ -12,114 +11,46 @@ sealed trait UncheckedTree extends ProofTree case object NoProof extends UncheckedTree sealed trait UncheckedSigmaTree extends UncheckedTree { - val challenge: Array[Byte] + val challenge: Challenge } -trait UncheckedConjecture extends UncheckedSigmaTree with ProofTreeConjecture { +trait UncheckedConjecture extends UncheckedSigmaTree with ProofTreeConjecture - override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { - case x: UncheckedConjecture => - Arrays.equals(challenge, x.challenge) && children == x.children - case _ => false - }) +trait UncheckedLeaf extends UncheckedSigmaTree with ProofTreeLeaf - override def hashCode(): Int = - 31 * Arrays.hashCode(challenge) + children.hashCode() -} - -trait UncheckedLeaf[SP <: SigmaBoolean] extends UncheckedSigmaTree with ProofTreeLeaf { - val proposition: SigmaBoolean -} - -case class UncheckedSchnorr(override val proposition: ProveDlog, - override val commitmentOpt: Option[FirstDLogProverMessage], - override val challenge: Challenge, - secondMessage: SecondDLogProverMessage) - extends UncheckedLeaf[ProveDlog] { - - override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { - case x: UncheckedSchnorr => - // NOTE, proposition is not compared because it is included into challenge - // like `challenge = hash(prop ++ msg)` - commitmentOpt == x.commitmentOpt && - Arrays.equals(challenge, x.challenge) && - secondMessage == x.secondMessage - case _ => false - }) - - override def hashCode(): Int = { - var h = commitmentOpt.hashCode() - h = 31 * h + Arrays.hashCode(challenge) - h = 31 * h + secondMessage.hashCode() - h - } -} - - -case class UncheckedDiffieHellmanTuple(override val proposition: ProveDHTuple, - override val commitmentOpt: Option[FirstDiffieHellmanTupleProverMessage], - override val challenge: Challenge, - secondMessage: SecondDiffieHellmanTupleProverMessage) - extends UncheckedLeaf[ProveDHTuple] { +case class UncheckedSchnorr( + override val proposition: ProveDlog, + override val commitmentOpt: Option[FirstDLogProverMessage], + override val challenge: Challenge, + secondMessage: SecondDLogProverMessage +) extends UncheckedLeaf - override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { - case x: UncheckedDiffieHellmanTuple => - // NOTE, proposition is not compared because it is included into challenge - // like `challenge = hash(prop ++ msg)` - commitmentOpt == x.commitmentOpt && - Arrays.equals(challenge, x.challenge) && - secondMessage == x.secondMessage - case _ => false - }) - - override def hashCode(): Int = { - var h = commitmentOpt.hashCode() - h = 31 * h + Arrays.hashCode(challenge) - h = 31 * h + secondMessage.hashCode() - h - } -} - -case class CAndUncheckedNode(override val challenge: Challenge, - override val children: Seq[UncheckedSigmaTree]) - extends UncheckedConjecture { +case class UncheckedDiffieHellmanTuple( + override val proposition: ProveDHTuple, + override val commitmentOpt: Option[FirstDHTupleProverMessage], + override val challenge: Challenge, + secondMessage: SecondDHTupleProverMessage +) extends UncheckedLeaf +case class CAndUncheckedNode( + override val challenge: Challenge, + override val children: Seq[UncheckedSigmaTree]) extends UncheckedConjecture { override val conjectureType = ConjectureType.AndConjecture } - -case class COrUncheckedNode(override val challenge: Challenge, - override val children: Seq[UncheckedSigmaTree]) extends UncheckedConjecture { - +case class COrUncheckedNode( + override val challenge: Challenge, + override val children: Seq[UncheckedSigmaTree]) extends UncheckedConjecture { override val conjectureType = ConjectureType.OrConjecture - } -case class CThresholdUncheckedNode(override val challenge: Challenge, - override val children: Seq[UncheckedSigmaTree], - k: Integer, - polynomialOpt: Option[GF2_192_Poly]) extends UncheckedConjecture { +case class CThresholdUncheckedNode( + override val challenge: Challenge, + override val children: Seq[UncheckedSigmaTree], + k: Integer, + polynomialOpt: Option[GF2_192_Poly]) extends UncheckedConjecture { require(children.length <= 255) // Our polynomial arithmetic can take only byte inputs require(k >= 0 && k <= children.length) override val conjectureType = ConjectureType.ThresholdConjecture - - override def canEqual(other: Any) = other.isInstanceOf[CThresholdUncheckedNode] - - override def equals(other: Any) = (this eq other.asInstanceOf[AnyRef]) || (other match { - case other: CThresholdUncheckedNode => - Arrays.equals(challenge, other.challenge) && - children == other.children && - k == other.k && - polynomialOpt == other.polynomialOpt - case _ => false - }) - - override def hashCode(): Int = { - var h = Arrays.hashCode(challenge) - h = 31 * h + children.hashCode - h = 31 * h + k.hashCode() - h = 31 * h + polynomialOpt.hashCode() - h - } } diff --git a/interpreter/shared/src/main/scala/sigmastate/UnprovenTree.scala b/interpreter/shared/src/main/scala/sigmastate/UnprovenTree.scala index 6e600b927b..a01c937a1f 100644 --- a/interpreter/shared/src/main/scala/sigmastate/UnprovenTree.scala +++ b/interpreter/shared/src/main/scala/sigmastate/UnprovenTree.scala @@ -4,7 +4,7 @@ import java.math.BigInteger import sigmastate.Values.{ErgoTree, SigmaBoolean, SigmaPropConstant} import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, ProveDlog} import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, FirstProverMessage, ProveDHTuple} +import sigmastate.basics.{FirstDHTupleProverMessage, FirstProverMessage, ProveDHTuple} import sigmastate.interpreter.{ErgoTreeEvaluator, NamedDesc, OperationCostInfo} import sigmastate.interpreter.ErgoTreeEvaluator.fixedCostOp import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer @@ -25,7 +25,7 @@ object ConjectureType extends Enumeration { trait ProofTree extends Product trait ProofTreeLeaf extends ProofTree { - val proposition: SigmaBoolean + val proposition: SigmaLeaf val commitmentOpt: Option[FirstProverMessage] } @@ -102,7 +102,7 @@ sealed trait UnprovenTree extends ProofTree { /** * Challenge used by the prover. */ - val challengeOpt: Option[Array[Byte]] + val challengeOpt: Option[Challenge] def withChallenge(challenge: Challenge): UnprovenTree @@ -187,7 +187,7 @@ case class UnprovenSchnorr(override val proposition: ProveDlog, } case class UnprovenDiffieHellmanTuple(override val proposition: ProveDHTuple, - override val commitmentOpt: Option[FirstDiffieHellmanTupleProverMessage], + override val commitmentOpt: Option[FirstDHTupleProverMessage], randomnessOpt: Option[BigInteger], override val challengeOpt: Option[Challenge] = None, override val simulated: Boolean, diff --git a/interpreter/shared/src/main/scala/sigmastate/Values.scala b/interpreter/shared/src/main/scala/sigmastate/Values.scala index 1e9114a782..840df3ab56 100644 --- a/interpreter/shared/src/main/scala/sigmastate/Values.scala +++ b/interpreter/shared/src/main/scala/sigmastate/Values.scala @@ -755,7 +755,6 @@ object Values { val dhtSerializer = ProveDHTupleSerializer(ProveDHTuple.apply) val dlogSerializer = ProveDlogSerializer(ProveDlog.apply) - // TODO v5.x: control maxTreeDepth same as in deserialize override def serialize(data: SigmaBoolean, w: SigmaByteWriter): Unit = { w.put(data.opCode) data match { @@ -878,31 +877,6 @@ object Values { def apply(items: Value[SType]*): Tuple = Tuple(items.toIndexedSeq) } - trait OptionValue[T <: SType] extends Value[SOption[T]] { - } - - // TODO v6.0 (4h): SomeValue and NoneValue are not used in ErgoTree and can be - // either removed or implemented in v6.0 - case class SomeValue[T <: SType](x: Value[T]) extends OptionValue[T] { - override def companion = SomeValue - val tpe = SOption(x.tpe) - def opType = SFunc(x.tpe, tpe) - } - object SomeValue extends ValueCompanion { - override val opCode = SomeValueCode - override def costKind: CostKind = Constant.costKind - } - - case class NoneValue[T <: SType](elemType: T) extends OptionValue[T] { - override def companion = NoneValue - val tpe = SOption(elemType) - def opType = SFunc(elemType, tpe) - } - object NoneValue extends ValueCompanion { - override val opCode = NoneValueCode - override def costKind: CostKind = Constant.costKind - } - /** ErgoTree node which converts a collection of expressions into a collection of data * values. Each data value of the resulting collection is obtained by evaluating the * corresponding expression in `items`. All items must have the same type. diff --git a/interpreter/shared/src/main/scala/sigmastate/basics/DLogProtocol.scala b/interpreter/shared/src/main/scala/sigmastate/basics/DLogProtocol.scala index 492b054b0e..9b801d7ad1 100644 --- a/interpreter/shared/src/main/scala/sigmastate/basics/DLogProtocol.scala +++ b/interpreter/shared/src/main/scala/sigmastate/basics/DLogProtocol.scala @@ -22,8 +22,7 @@ object DLogProtocol { } /** Construct a new SigmaBoolean value representing public key of discrete logarithm signature protocol. */ - case class ProveDlog(value: EcPointType) - extends SigmaProofOfKnowledgeLeaf[DLogSigmaProtocol, DLogProverInput] { + case class ProveDlog(value: EcPointType) extends SigmaLeaf { override def size: Int = 1 override val opCode: OpCode = OpCodes.ProveDlogCode /** Serialized bytes of the elliptic curve point (using GroupElementSerializer). */ @@ -43,7 +42,7 @@ object DLogProtocol { } case class DLogProverInput(w: BigInteger) - extends SigmaProtocolPrivateInput[DLogSigmaProtocol, ProveDlog] { + extends SigmaProtocolPrivateInput[ProveDlog] { import CryptoConstants.dlogGroup @@ -79,7 +78,7 @@ object DLogProtocol { } - object DLogInteractiveProver { + object DLogInteractiveProver extends SigmaProtocolProver { import CryptoConstants.secureRandom def firstMessage(): (BigInteger, FirstDLogProverMessage) = { @@ -92,12 +91,7 @@ object DLogProtocol { } def secondMessage(privateInput: DLogProverInput, rnd: BigInteger, challenge: Challenge): SecondDLogProverMessage = { - import CryptoConstants.dlogGroup - - val q: BigInteger = dlogGroup.order - val e: BigInteger = new BigInteger(1, challenge) - val ew: BigInteger = e.multiply(privateInput.w).mod(q) - val z: BigInteger = rnd.add(ew).mod(q) + val z = responseToChallenge(privateInput, rnd, challenge) SecondDLogProverMessage(z) } @@ -109,7 +103,7 @@ object DLogProtocol { val z = BigIntegers.createRandomInRange(BigInteger.ZERO, qMinusOne, secureRandom) //COMPUTE a = g^z*h^(-e) (where -e here means -e mod q) - val e: BigInteger = new BigInteger(1, challenge) + val e: BigInteger = new BigInteger(1, challenge.toArray) val minusE = dlogGroup.order.subtract(e) val hToE = dlogGroup.exponentiate(publicInput.value, minusE) val gToZ = dlogGroup.exponentiate(dlogGroup.generator, z) @@ -137,7 +131,7 @@ object DLogProtocol { dlogGroup.multiplyGroupElements( dlogGroup.exponentiate(g, secondMessage.z.underlying()), - dlogGroup.inverseOf(dlogGroup.exponentiate(h, new BigInteger(1, challenge)))) + dlogGroup.inverseOf(dlogGroup.exponentiate(h, new BigInteger(1, challenge.toArray)))) } } diff --git a/interpreter/shared/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala b/interpreter/shared/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala index 08e9dd2ab7..e39508bf4f 100644 --- a/interpreter/shared/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala +++ b/interpreter/shared/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala @@ -14,12 +14,12 @@ import special.sigma.SigmaProp trait DiffieHellmanTupleProtocol extends SigmaProtocol[DiffieHellmanTupleProtocol] { - override type A = FirstDiffieHellmanTupleProverMessage - override type Z = SecondDiffieHellmanTupleProverMessage + override type A = FirstDHTupleProverMessage + override type Z = SecondDHTupleProverMessage } case class DiffieHellmanTupleProverInput(w: BigInteger, commonInput: ProveDHTuple) - extends SigmaProtocolPrivateInput[DiffieHellmanTupleProtocol, ProveDHTuple] { + extends SigmaProtocolPrivateInput[ProveDHTuple] { override lazy val publicImage: ProveDHTuple = commonInput } @@ -41,8 +41,12 @@ object DiffieHellmanTupleProverInput { } } -//a = g^r, b = h^r -case class FirstDiffieHellmanTupleProverMessage(a: CryptoConstants.EcPointType, b: CryptoConstants.EcPointType) +/** First message of Diffie Hellman tuple sigma protocol. + * @param a commitment to secret randomness `a = g^r`, where `g` is default generator of the group + * @param b commitment to secret randomness `b = h^r`, where `h` is another random generator of the group + * @see createRandomGenerator in [[sigmastate.basics.CryptoConstants.dlogGroup]] + */ +case class FirstDHTupleProverMessage(a: EcPointType, b: EcPointType) extends FirstProverMessage { override type SP = DiffieHellmanTupleProtocol @@ -52,17 +56,21 @@ case class FirstDiffieHellmanTupleProverMessage(a: CryptoConstants.EcPointType, } } -//z = r + ew mod q -case class SecondDiffieHellmanTupleProverMessage(z: BigInteger) extends SecondProverMessage { - +/** Represents a response for challenge in Diffie Hellman tuple sigma protocol. + * @param z responce to the challenge computed as `(r + ew) mod q`, where + * `r` is the prover's randomness, + * `e` challenge from the verifier (also computed by the prover in non-interactive case), + * `w` is the prover's secret. + * `q` is the group order + */ +case class SecondDHTupleProverMessage(z: BigInteger) extends SecondProverMessage { override type SP = DiffieHellmanTupleProtocol - } /** Construct a new SigmaProp value representing public key of Diffie Hellman signature protocol. * Common input: (g,h,u,v) */ case class ProveDHTuple(gv: EcPointType, hv: EcPointType, uv: EcPointType, vv: EcPointType) - extends SigmaProofOfKnowledgeLeaf[DiffieHellmanTupleProtocol, DiffieHellmanTupleProverInput] { + extends SigmaLeaf { override val opCode: OpCode = OpCodes.ProveDiffieHellmanTupleCode override def size: Int = 4 // one node for each EcPoint lazy val g = gv @@ -83,38 +91,47 @@ object ProveDHTupleProp { } } -object DiffieHellmanTupleInteractiveProver { +object DiffieHellmanTupleInteractiveProver extends SigmaProtocolProver { import CryptoConstants.dlogGroup - def firstMessage(publicInput: ProveDHTuple): (BigInteger, FirstDiffieHellmanTupleProverMessage) = { + /** Create a commitment to randomness r and a first message of sigma protocol. */ + def firstMessage(publicInput: ProveDHTuple): (BigInteger, FirstDHTupleProverMessage) = { val qMinusOne = dlogGroup.order.subtract(BigInteger.ONE) val r = BigIntegers.createRandomInRange(BigInteger.ZERO, qMinusOne, dlogGroup.secureRandom) val a = dlogGroup.exponentiate(publicInput.g, r) val b = dlogGroup.exponentiate(publicInput.h, r) - r -> FirstDiffieHellmanTupleProverMessage(a, b) + r -> FirstDHTupleProverMessage(a, b) } + /** Creates second message of sigma protocol, which is a response for the challenge from the verifier. + * + * @param privateInput private input of the prover (secret) + * @param rnd random number generated by the prover (secret random number used to + * compute commitment) + * @param challenge challenge from the verifier (also computed by the prover in non-interactive case) + * @return second message from the prover + */ def secondMessage(privateInput: DiffieHellmanTupleProverInput, rnd: BigInteger, - challenge: Challenge): SecondDiffieHellmanTupleProverMessage = { - val q: BigInteger = dlogGroup.order - val e: BigInteger = new BigInteger(1, challenge) - val ew: BigInteger = e.multiply(privateInput.w).mod(q) - val z: BigInteger = rnd.add(ew).mod(q) - SecondDiffieHellmanTupleProverMessage(z) + challenge: Challenge): SecondDHTupleProverMessage = { + val z = responseToChallenge(privateInput, rnd, challenge) + SecondDHTupleProverMessage(z) } - def simulate(publicInput: ProveDHTuple, challenge: Challenge): - (FirstDiffieHellmanTupleProverMessage, SecondDiffieHellmanTupleProverMessage) = { + /** Simulates messages of the sigma protocol which are indistinquishable from generated + * by the real prover. */ + def simulate(publicInput: ProveDHTuple, challenge: Challenge): (FirstDHTupleProverMessage, SecondDHTupleProverMessage) = { val qMinusOne = dlogGroup.order.subtract(BigInteger.ONE) - //SAMPLE a random z <- Zq + //SAMPLE a random z <- Zq, this will be the simulated response to the challenge val z = BigIntegers.createRandomInRange(BigInteger.ZERO, qMinusOne, dlogGroup.secureRandom) // COMPUTE a = g^z*u^(-e) and b = h^z*v^{-e} (where -e here means -e mod q) - val e: BigInteger = new BigInteger(1, challenge) + // in real prover we compute commitments from random number and them compute response to the challenge, + // but here we do in opposite direction, use random response and compute commitments from it + val e: BigInteger = new BigInteger(1, challenge.toArray) val minusE = dlogGroup.order.subtract(e) val hToZ = dlogGroup.exponentiate(publicInput.h, z) val gToZ = dlogGroup.exponentiate(publicInput.g, z) @@ -122,7 +139,7 @@ object DiffieHellmanTupleInteractiveProver { val vToMinusE = dlogGroup.exponentiate(publicInput.v, minusE) val a = dlogGroup.multiplyGroupElements(gToZ, uToMinusE) val b = dlogGroup.multiplyGroupElements(hToZ, vToMinusE) - FirstDiffieHellmanTupleProverMessage(a, b) -> SecondDiffieHellmanTupleProverMessage(z) + FirstDHTupleProverMessage(a, b) -> SecondDHTupleProverMessage(z) } /** @@ -133,14 +150,14 @@ object DiffieHellmanTupleInteractiveProver { * * g^z = a*u^e, h^z = b*v^e => a = g^z/u^e, b = h^z/v^e * - * @param proposition - * @param challenge - * @param secondMessage + * @param proposition proposition "I know DH tuple" + * @param challenge challenge from verifier + * @param secondMessage prover's response to the challenge * @return */ def computeCommitment(proposition: ProveDHTuple, challenge: Challenge, - secondMessage: SecondDiffieHellmanTupleProverMessage): (EcPointType, EcPointType) = { + secondMessage: SecondDHTupleProverMessage): (EcPointType, EcPointType) = { val g = proposition.g val h = proposition.h @@ -149,7 +166,7 @@ object DiffieHellmanTupleInteractiveProver { val z = secondMessage.z - val e = new BigInteger(1, challenge) + val e = new BigInteger(1, challenge.toArray) val gToZ = dlogGroup.exponentiate(g, z) val hToZ = dlogGroup.exponentiate(h, z) diff --git a/interpreter/shared/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala b/interpreter/shared/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala index 3327d94ebf..2350c3f1d2 100644 --- a/interpreter/shared/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala +++ b/interpreter/shared/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala @@ -1,7 +1,14 @@ package sigmastate.basics +import sigmastate.SigmaLeaf +import sigmastate.basics.CryptoConstants.dlogGroup +import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} +import sigmastate.basics.VerifierMessage.Challenge +import special.collection.Coll import supertagged.TaggedType +import java.math.BigInteger + /* Abstracting Sigma protocols Functionality to get: @@ -25,7 +32,7 @@ trait VerifierMessage extends TranscriptMessage object VerifierMessage { /** A challenge from the verifier (message `e` of `SigmaProtocol`)*/ - object Challenge extends TaggedType[Array[Byte]] + object Challenge extends TaggedType[Coll[Byte]] type Challenge = Challenge.Type } @@ -50,11 +57,35 @@ trait SigmaProtocol[SP <: SigmaProtocol[SP]] { } -trait SigmaProtocolCommonInput[SP <: SigmaProtocol[SP]] { +trait SigmaProtocolPrivateInput[CI <: SigmaLeaf] { + /** Public image generated from the secret. + * Represents proof of knowledge proposition. + */ + def publicImage: CI + + /** Secret random number known to the prover. */ + def w: BigInteger } -trait SigmaProtocolPrivateInput[SP <: SigmaProtocol[SP], CI <: SigmaProtocolCommonInput[SP]] { - def publicImage: CI +trait SigmaProtocolProver { + /** Computes response for the challenge in non-interactive sigma protocol. + * + * @param privateInput private input of the prover (secret) + * @param rnd random number generated by the prover (secret random number used to + * compute commitment) + * @param challenge challenge from the verifier (also computed by the prover in non-interactive case) + * @return response computed by the prover + */ + protected def responseToChallenge( + privateInput: SigmaProtocolPrivateInput[_ <: SigmaLeaf], + rnd: BigInteger, + challenge: Challenge): BigInteger = { + val q: BigInteger = dlogGroup.order + val e: BigInteger = new BigInteger(1, challenge.toArray) + val ew: BigInteger = e.multiply(privateInput.w).mod(q) + val z: BigInteger = rnd.add(ew).mod(q) + z + } } diff --git a/interpreter/shared/src/main/scala/sigmastate/crypto/BigIntegers.scala b/interpreter/shared/src/main/scala/sigmastate/crypto/BigIntegers.scala index 7bab41df79..43bded8e09 100644 --- a/interpreter/shared/src/main/scala/sigmastate/crypto/BigIntegers.scala +++ b/interpreter/shared/src/main/scala/sigmastate/crypto/BigIntegers.scala @@ -65,7 +65,7 @@ object BigIntegers { if (min.bitLength > max.bitLength / 2) return createRandomInRange(ZERO, max.subtract(min), random).add(min) - for ( i <- 0 until MAX_ITERATIONS ) { + for ( _ <- 0 until MAX_ITERATIONS ) { val x = createRandomBigInteger(max.bitLength, random) if (x.compareTo(min) >= 0 && x.compareTo(max) <= 0) return x } diff --git a/interpreter/shared/src/main/scala/sigmastate/crypto/GF2_192_Poly.scala b/interpreter/shared/src/main/scala/sigmastate/crypto/GF2_192_Poly.scala index dc0617ea29..344305a157 100644 --- a/interpreter/shared/src/main/scala/sigmastate/crypto/GF2_192_Poly.scala +++ b/interpreter/shared/src/main/scala/sigmastate/crypto/GF2_192_Poly.scala @@ -32,7 +32,6 @@ package sigmastate.crypto import debox.cfor import java.util -import java.util.Arrays class GF2_192_Poly { final private var c: Array[GF2_192] = null // must be not null and of length at least 1 diff --git a/interpreter/shared/src/main/scala/sigmastate/eval/CostingDataContext.scala b/interpreter/shared/src/main/scala/sigmastate/eval/CostingDataContext.scala index f7679573d6..72cd34b725 100644 --- a/interpreter/shared/src/main/scala/sigmastate/eval/CostingDataContext.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/CostingDataContext.scala @@ -129,7 +129,7 @@ case class CSigmaProp(sigmaTree: SigmaBoolean) extends SigmaProp with WrapperOf[ override def propBytes: Coll[Byte] = { // in order to have comparisons like `box.propositionBytes == pk.propBytes` we need to make sure // the same serialization method is used in both cases - // TODO v6.0: add `pk.propBytes(version)` + // TODO v6.0: add `pk.propBytes(version)` (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/903) val root = sigmaTree.toSigmaProp val ergoTree = new ErgoTree(ErgoTree.DefaultHeader, EmptyConstants, Right(root), 0, null, None) val bytes = DefaultSerializer.serializeErgoTree(ergoTree) diff --git a/interpreter/shared/src/main/scala/sigmastate/eval/Evaluation.scala b/interpreter/shared/src/main/scala/sigmastate/eval/Evaluation.scala index 818e604dd2..fa8b50a6c9 100644 --- a/interpreter/shared/src/main/scala/sigmastate/eval/Evaluation.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/Evaluation.scala @@ -141,9 +141,6 @@ object Evaluation { case _: Short => ShortType case _: Int => IntType case _: Long => LongType - case _: Char => CharType - case _: Float => FloatType - case _: Double => DoubleType case _: String => StringType case _: Unit => UnitType diff --git a/interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala b/interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala index d765e01c41..b4efc58348 100644 --- a/interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala @@ -4,6 +4,7 @@ import debox.{cfor, Buffer => DBuffer} import org.ergoplatform.ErgoBox import org.ergoplatform.ErgoBox.TokenId import scalan.{Nullable, RType} +import scorex.util.encode.Base16 import sigmastate.SType.AnyOps import sigmastate.Values.{Constant, ConstantNode} import sigmastate.crypto.{CryptoFacade, Ecp} @@ -41,6 +42,8 @@ object Extensions { implicit class ArrayByteOps(val arr: Array[Byte]) extends AnyVal { /** Wraps array into TokenId instance. The source array in not cloned. */ @inline def toTokenId: TokenId = Digest32Coll @@ Colls.fromArray(arr) + /** Encodes array into hex string */ + @inline def toHex: String = Base16.encode(arr) } implicit class EvalIterableOps[T: RType](seq: Iterable[T]) { diff --git a/interpreter/shared/src/main/scala/sigmastate/eval/Profiler.scala b/interpreter/shared/src/main/scala/sigmastate/eval/Profiler.scala index 2f0e7f3e0c..74e187b809 100644 --- a/interpreter/shared/src/main/scala/sigmastate/eval/Profiler.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/Profiler.scala @@ -169,12 +169,6 @@ class Profiler { /** Timings of method calls */ private val mcStat = new StatCollection[Int, Long]() - /** Update time measurement stats for a given method. */ - @inline private final def addMcTime(typeId: Byte, methodId: Byte, time: Long) = { - val key = typeId << 8 | methodId - mcStat.addPoint(key, time) - } - /** Wrapper class which implements special equality between CostItem instances, * suitable for collecting of the statistics. */ class CostItemKey(val costItem: CostItem) { diff --git a/interpreter/shared/src/main/scala/sigmastate/exceptions/CompilerExceptions.scala b/interpreter/shared/src/main/scala/sigmastate/exceptions/CompilerExceptions.scala index b652a4091d..8c331766af 100644 --- a/interpreter/shared/src/main/scala/sigmastate/exceptions/CompilerExceptions.scala +++ b/interpreter/shared/src/main/scala/sigmastate/exceptions/CompilerExceptions.scala @@ -42,8 +42,7 @@ class TyperException(message: String, source: Option[SourceContext] = None) class BuilderException(message: String, source: Option[SourceContext] = None) extends CompilerException(message, source) -// TODO v5.x: remove this exception -class CosterException(message: String, source: Option[SourceContext], cause: Option[Throwable] = None) +class GraphBuildingException(message: String, source: Option[SourceContext], cause: Option[Throwable] = None) extends CompilerException(message, source, cause) diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala index bdd59eb850..de6ad39789 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala @@ -1,7 +1,6 @@ package sigmastate.interpreter import org.ergoplatform.ErgoLikeContext -import org.ergoplatform.SigmaConstants.ScriptCostLimit import sigmastate.{PerItemCost, VersionContext, TypeBasedCost, FixedCost, SType, JitCost} import sigmastate.Values._ import sigmastate.eval.Profiler @@ -42,7 +41,12 @@ case class EvalSettings( * The default value is None, which means the version is defined by ErgoTree.version * and Context.activatedScriptVersion. */ - evaluationMode: Option[EvaluationMode] = None) + evaluationMode: Option[EvaluationMode] = None, + /** Maximum execution cost of a script used by profiler. + * @see ErgoTreeEvaluator + */ + scriptCostLimitInEvaluator: Int = 1000000 +) object EvalSettings { /** Enumeration type of evaluation modes of [[Interpreter]]. @@ -123,7 +127,7 @@ class ErgoTreeEvaluator( protected val coster: CostAccumulator, val profiler: Profiler, val settings: EvalSettings) { - + /** Evaluates the given expression in the given data environment. */ def eval(env: DataEnv, exp: SValue): Any = { VersionContext.checkVersions(context.activatedScriptVersion, context.currentErgoTreeVersion) @@ -392,7 +396,7 @@ object ErgoTreeEvaluator { def forProfiling(profiler: Profiler, evalSettings: EvalSettings): ErgoTreeEvaluator = { val acc = new CostAccumulator( initialCost = JitCost(0), - costLimit = Some(JitCost.fromBlockCost(ScriptCostLimit.value))) + costLimit = Some(JitCost.fromBlockCost(evalSettings.scriptCostLimitInEvaluator))) new ErgoTreeEvaluator( context = null, constants = ArraySeq.empty, diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/Hint.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/Hint.scala index 13570e846c..ab2530f653 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/Hint.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/Hint.scala @@ -1,8 +1,7 @@ package sigmastate.interpreter import java.math.BigInteger - -import sigmastate.{NodePosition, UncheckedTree} +import sigmastate.{NodePosition, SigmaLeaf, UncheckedTree} import sigmastate.Values.SigmaBoolean import sigmastate.basics.FirstProverMessage import sigmastate.basics.VerifierMessage.Challenge @@ -13,6 +12,10 @@ import sigmastate.basics.VerifierMessage.Challenge * prover knows that pk2 is known to another party, the prover may prove the statement (with an empty proof for "pk2"). */ trait Hint { + /** + * Public image of a secret + */ + def image: SigmaLeaf /** * A hint is related to a subtree (or a leaf) of a tree. This field encodes a position in the tree. @@ -25,12 +28,6 @@ trait Hint { * A hint which is indicating that a secret associated with its public image "image" is already proven. */ abstract class SecretProven extends Hint { - - /** - * Public image of a secret which is proven - */ - def image: SigmaBoolean - /** * Challenge used for a proof */ @@ -46,16 +43,16 @@ abstract class SecretProven extends Hint { * A hint which contains a proof-of-knowledge for a secret associated with its public image "image", * with also the mark that the proof is real. */ -case class RealSecretProof(image: SigmaBoolean, +case class RealSecretProof(image: SigmaLeaf, challenge: Challenge, uncheckedTree: UncheckedTree, override val position: NodePosition) extends SecretProven /** * A hint which contains a proof-of-knowledge for a secret associated with its public image "image", - * with also the mark that the proof is real. + * with also the mark that the proof is simulated (not real). */ -case class SimulatedSecretProof(image: SigmaBoolean, +case class SimulatedSecretProof(image: SigmaLeaf, challenge: Challenge, uncheckedTree: UncheckedTree, override val position: NodePosition) extends SecretProven @@ -66,7 +63,7 @@ case class SimulatedSecretProof(image: SigmaBoolean, * to randomness ("a" in a sigma protocol). */ abstract class CommitmentHint extends Hint { - def image: SigmaBoolean + /** Commitment to randomness (first message in a sigma protocol) */ def commitment: FirstProverMessage } @@ -78,7 +75,7 @@ abstract class CommitmentHint extends Hint { * @param secretRandomness - randomness * @param commitment - commitment to randomness used while proving knowledge of the secret */ -case class OwnCommitment(override val image: SigmaBoolean, +case class OwnCommitment(override val image: SigmaLeaf, secretRandomness: BigInteger, commitment: FirstProverMessage, override val position: NodePosition) extends CommitmentHint @@ -89,7 +86,7 @@ case class OwnCommitment(override val image: SigmaBoolean, * @param image - image of a secret * @param commitment - commitment to randomness used while proving knowledge of the secret */ -case class RealCommitment(override val image: SigmaBoolean, +case class RealCommitment(override val image: SigmaLeaf, commitment: FirstProverMessage, override val position: NodePosition) extends CommitmentHint @@ -99,7 +96,7 @@ case class RealCommitment(override val image: SigmaBoolean, * @param image - image of a secret * @param commitment - commitment to randomness used while proving knowledge of the secret */ -case class SimulatedCommitment(override val image: SigmaBoolean, +case class SimulatedCommitment(override val image: SigmaLeaf, commitment: FirstProverMessage, override val position: NodePosition) extends CommitmentHint diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala index 1559fcc775..e606e72525 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -282,7 +282,7 @@ trait Interpreter { * @throws InterpreterException when cannot proceed and no activation yet. */ protected def checkSoftForkCondition(ergoTree: ErgoTree, context: CTX): Option[VerificationResult] = { - // TODO v6.0: the condition below should be revised if necessary + // TODO v6.0: the condition below should be revised if necessary (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/904) // The following conditions define behavior which depend on the version of ergoTree // This works in addition to more fine-grained soft-forkability mechanism implemented // using ValidationRules (see trySoftForkable method call here and in reduceToCrypto). @@ -383,7 +383,7 @@ trait Interpreter { * (and, if applicable, the associated data). Reject otherwise. */ val expectedChallenge = CryptoFunctions.hashFn(bytes) - util.Arrays.equals(newRoot.challenge, expectedChallenge) + util.Arrays.equals(newRoot.challenge.toArray, expectedChallenge) } /** @@ -405,7 +405,7 @@ trait Interpreter { implicit val E = ErgoTreeEvaluator.getCurrentEvaluator fixedCostOp(ComputeCommitments_DHT) { val (a, b) = DiffieHellmanTupleInteractiveProver.computeCommitment(dh.proposition, dh.challenge, dh.secondMessage) - dh.copy(commitmentOpt = Some(FirstDiffieHellmanTupleProverMessage(a, b))) + dh.copy(commitmentOpt = Some(FirstDHTupleProverMessage(a, b))) } case _: UncheckedSigmaTree => ??? diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala index 73e0a96370..937aa37fda 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala @@ -9,6 +9,8 @@ import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import special.sigma import special.sigma.AnyValue +import scala.collection.mutable + /** * User-defined variables to be put into context. * Each variable is identified by `id: Byte` and can be accessed from a script @@ -20,7 +22,7 @@ import special.sigma.AnyValue * * @param values internal container of the key-value pairs */ -case class ContextExtension(values: Map[Byte, EvaluatedValue[_ <: SType]]) { +case class ContextExtension(values: scala.collection.Map[Byte, EvaluatedValue[_ <: SType]]) { def add(bindings: VarBinding*): ContextExtension = ContextExtension(values ++ bindings) } @@ -49,8 +51,7 @@ object ContextExtension { error(s"Negative amount of context extension values: $extSize") val ext = (0 until extSize) .map(_ => (r.getByte(), r.getValue().asInstanceOf[EvaluatedValue[_ <: SType]])) - .toMap[Byte, EvaluatedValue[_ <: SType]] - ContextExtension(ext) + ContextExtension(mutable.LinkedHashMap(ext:_*)) } } diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala index ff91db6285..e9dfa6b052 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala @@ -11,8 +11,10 @@ import sigmastate.basics.DLogProtocol._ import sigmastate.basics.VerifierMessage.Challenge import sigmastate.basics._ import sigmastate.crypto.{GF2_192, GF2_192_Poly} +import sigmastate.eval.Extensions.ArrayOps import sigmastate.exceptions.InterpreterException import sigmastate.utils.Helpers +import special.collection.Coll import java.math.BigInteger import scala.util.Try @@ -29,7 +31,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils { override type ProofT = UncheckedTree /** All secrets available for this prover. */ - def secrets: Seq[SigmaProtocolPrivateInput[_, _]] + def secrets: Seq[SigmaProtocolPrivateInput[_]] /** * Public keys of prover's secrets. This operation can be costly if there are many @@ -91,7 +93,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils { // Prover Step 8: compute the challenge for the root of the tree as the Fiat-Shamir hash of propBytes // and the message being signed. - val rootChallenge = Challenge @@ CryptoFunctions.hashFn(Helpers.concatArrays(propBytes, message)) + val rootChallenge = Challenge @@ CryptoFunctions.hashFn(Helpers.concatArrays(propBytes, message)).toColl val step8 = step6.withChallenge(rootChallenge) // Prover Step 9: complete the proof by computing challenges at real nodes and additionally responses at real leaves @@ -181,7 +183,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils { // is known to an external participant in multi-signing; // else mark it "simulated" val isReal = hintsBag.realImages.contains(ul.proposition) || secrets.exists { - case in: SigmaProtocolPrivateInput[_, _] => in.publicImage == ul.proposition + case in: SigmaProtocolPrivateInput[_] => in.publicImage == ul.proposition } ul.withSimulated(!isReal) case t: UnprovenTree => @@ -287,7 +289,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils { // take challenge from previously done proof stored in the hints bag, // or generate random challenge for simulated child val newChallenge = hintsBag.proofs.find(_.position == c.position).map(_.challenge).getOrElse( - Challenge @@ secureRandomBytes(CryptoFunctions.soundnessBytes) + Challenge @@ secureRandomBytes(CryptoFunctions.soundnessBytes).toColl ) c.withChallenge(newChallenge) } @@ -314,8 +316,10 @@ trait ProverInterpreter extends Interpreter with ProverUtils { // the other children and e_0. assert(or.challengeOpt.isDefined) val unprovenChildren = or.children.cast[UnprovenTree] - val t = unprovenChildren.tail.map(_.withChallenge(Challenge @@ secureRandomBytes(CryptoFunctions.soundnessBytes))) - val toXor: Seq[Array[Byte]] = or.challengeOpt.get +: t.map(_.challengeOpt.get) + val t = unprovenChildren.tail.map( + _.withChallenge(Challenge @@ secureRandomBytes(CryptoFunctions.soundnessBytes).toColl) + ) + val toXor: Seq[Coll[Byte]] = or.challengeOpt.get +: t.map(_.challengeOpt.get) val xoredChallenge = Challenge @@ Helpers.xor(toXor: _*) val h = unprovenChildren.head.withChallenge(xoredChallenge) or.copy(children = h +: t) @@ -329,11 +333,11 @@ trait ProverInterpreter extends Interpreter with ProverUtils { assert(t.challengeOpt.isDefined) val n = t.children.length val unprovenChildren = t.children.cast[UnprovenTree] - val q = GF2_192_Poly.fromByteArray(t.challengeOpt.get, secureRandomBytes(CryptoFunctions.soundnessBytes * (n - t.k))) + val q = GF2_192_Poly.fromByteArray(t.challengeOpt.get.toArray, secureRandomBytes(CryptoFunctions.soundnessBytes * (n - t.k))) val newChildren = unprovenChildren.foldLeft((Seq[UnprovenTree](), 1)) { case ((childSeq, childIndex), child) => - (childSeq :+ child.withChallenge(Challenge @@ q.evaluate(childIndex.toByte).toByteArray), childIndex + 1) + (childSeq :+ child.withChallenge(Challenge @@ q.evaluate(childIndex.toByte).toByteArray.toColl), childIndex + 1) }._1 t.withPolynomial(q).copy(children = newChildren) @@ -395,7 +399,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils { // Step 6 (real leaf -- compute the commitment a or take it from the hints bag) hintsBag.commitments.find(_.position == dhu.position).map { cmtHint => - dhu.copy(commitmentOpt = Some(cmtHint.commitment.asInstanceOf[FirstDiffieHellmanTupleProverMessage])) + dhu.copy(commitmentOpt = Some(cmtHint.commitment.asInstanceOf[FirstDHTupleProverMessage])) }.getOrElse { if (dhu.simulated) { // Step 5 (simulated leaf -- complete the simulation) @@ -412,7 +416,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils { case t: ProofTree => error(s"Don't know how to challengeSimulated($t)") }) - private def extractChallenge(pt: ProofTree): Option[Array[Byte]] = pt match { + private def extractChallenge(pt: ProofTree): Option[Challenge] = pt match { case upt: UnprovenTree => upt.challengeOpt case sn: UncheckedSchnorr => Some(sn.challenge) case dh: UncheckedDiffieHellmanTuple => Some(dh.challenge) @@ -461,16 +465,16 @@ trait ProverInterpreter extends Interpreter with ProverUtils { // has a challenge. Other ways are more of a pain because the children can be of different types val challengeOpt = extractChallenge(child) if (challengeOpt.isEmpty) (p, v) - else (p :+ count.toByte, v :+ new GF2_192(challengeOpt.get)) + else (p :+ count.toByte, v :+ new GF2_192(challengeOpt.get.toArray)) } (newPoints, newValues, count + 1) } - val q = GF2_192_Poly.interpolate(points, values, new GF2_192(t.challengeOpt.get)) + val q = GF2_192_Poly.interpolate(points, values, new GF2_192(t.challengeOpt.get.toArray)) val newChildren = t.children.foldLeft(Seq[ProofTree](), 1) { case ((s, count), child) => val newChild = child match { - case r: UnprovenTree if r.real => r.withChallenge(Challenge @@ q.evaluate(count.toByte).toByteArray) + case r: UnprovenTree if r.real => r.withChallenge(Challenge @@ q.evaluate(count.toByte).toByteArray.toColl) case p: ProofTree => p } (s :+ newChild, count + 1) @@ -538,7 +542,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils { provenSchnorr.secondMessage }.getOrElse { val bs = secureRandomBytes(32) - SecondDiffieHellmanTupleProverMessage(new BigInteger(1, bs).mod(CryptoConstants.groupOrder)) + SecondDHTupleProverMessage(new BigInteger(1, bs).mod(CryptoConstants.groupOrder)) } } UncheckedDiffieHellmanTuple(dhu.proposition, None, dhu.challengeOpt.get, z) diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverUtils.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverUtils.scala index 7a3ce291db..e2b0fe8f5a 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverUtils.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverUtils.scala @@ -41,7 +41,7 @@ trait ProverUtils extends Interpreter { sc.children.zipWithIndex.foldLeft(bag) { case (b, (child, idx)) => traverseNode(child, b, position.child(idx)) } - case leaf: SigmaProofOfKnowledgeLeaf[_, _] => + case leaf: SigmaLeaf => if (generateFor.contains(leaf)) { val (r, a) = leaf match { case _: ProveDlog => @@ -115,19 +115,19 @@ trait ProverUtils extends Interpreter { inner.children.zipWithIndex.foldLeft(hintsBag) { case (hb, (c, idx)) => traverseNode(c, realPropositions, simulatedPropositions, hb, position.child(idx)) } - case leaf: UncheckedLeaf[_] => + case leaf: UncheckedLeaf => val realFound = realPropositions.contains(leaf.proposition) val simulatedFound = simulatedPropositions.contains(leaf.proposition) if (realFound || simulatedFound) { val hints = if (realFound) { Seq( RealCommitment(leaf.proposition, leaf.commitmentOpt.get, position), - RealSecretProof(leaf.proposition, Challenge @@ leaf.challenge, leaf, position) + RealSecretProof(leaf.proposition, leaf.challenge, leaf, position) ) } else { Seq( SimulatedCommitment(leaf.proposition, leaf.commitmentOpt.get, position), - SimulatedSecretProof(leaf.proposition, Challenge @@ leaf.challenge, leaf, position) + SimulatedSecretProof(leaf.proposition, leaf.challenge, leaf, position) ) } hintsBag.addHints(hints: _*) diff --git a/interpreter/shared/src/main/scala/sigmastate/lang/SigmaBuilder.scala b/interpreter/shared/src/main/scala/sigmastate/lang/SigmaBuilder.scala index 813664931d..55d1575ff9 100644 --- a/interpreter/shared/src/main/scala/sigmastate/lang/SigmaBuilder.scala +++ b/interpreter/shared/src/main/scala/sigmastate/lang/SigmaBuilder.scala @@ -158,9 +158,6 @@ abstract class SigmaBuilder { def mkTaggedVariable[T <: SType](varId: Byte, tpe: T): TaggedVariable[T] - def mkSomeValue[T <: SType](x: Value[T]): Value[SOption[T]] - def mkNoneValue[T <: SType](elemType: T): Value[SOption[T]] - def mkBlock(bindings: Seq[Val], result: Value[SType]): Value[SType] def mkBlockValue(items: IndexedSeq[BlockItem], result: Value[SType]): Value[SType] def mkValUse(valId: Int, tpe: SType): Value[SType] @@ -527,11 +524,6 @@ class StdSigmaBuilder extends SigmaBuilder { override def mkTaggedVariable[T <: SType](varId: Byte, tpe: T): TaggedVariable[T] = TaggedVariableNode(varId, tpe).withSrcCtx(currentSrcCtx.value).asInstanceOf[TaggedVariable[T]] - override def mkSomeValue[T <: SType](x: Value[T]): Value[SOption[T]] = - SomeValue(x).withSrcCtx(currentSrcCtx.value) - override def mkNoneValue[T <: SType](elemType: T): Value[SOption[T]] = - NoneValue(elemType).withSrcCtx(currentSrcCtx.value) - override def mkBlock(bindings: Seq[Val], result: Value[SType]): Value[SType] = Block(bindings, result).withSrcCtx(currentSrcCtx.value) diff --git a/interpreter/shared/src/main/scala/sigmastate/serialization/DataSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/DataSerializer.scala index d659dbf81b..f38fd97309 100644 --- a/interpreter/shared/src/main/scala/sigmastate/serialization/DataSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/DataSerializer.scala @@ -19,7 +19,6 @@ import scala.collection.mutable /** This works in tandem with ConstantSerializer, if you change one make sure to check the other.*/ object DataSerializer { - // TODO v5.x: control maxTreeDepth same as in deserialize /** Use type descriptor `tpe` to deconstruct type structure and recursively serialize subcomponents. * Primitive types are leaves of the type tree, and they are served as basis of recursion. * The data value `v` is expected to conform to the type described by `tpe`. diff --git a/interpreter/shared/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala index b8d8d07093..765aaeb7b4 100644 --- a/interpreter/shared/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala @@ -6,7 +6,7 @@ import sigmastate.utils.SigmaByteWriter.DataInfo import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import sigmastate.{ModQArithOpCompanion, SType, ModQArithOp} -// TODO v6.0 (2h): make sure it is covered with tests +// TODO v6.0: make sure it is covered with tests (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/327) case class ModQArithOpSerializer(override val opDesc: ModQArithOpCompanion, cons: (BigIntValue, BigIntValue) => BigIntValue) extends ValueSerializer[ModQArithOp] { val leftInfo: DataInfo[SValue] = opDesc.argInfos(0) diff --git a/interpreter/shared/src/main/scala/sigmastate/serialization/ModQSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ModQSerializer.scala index f48a6e6388..335c12c1af 100644 --- a/interpreter/shared/src/main/scala/sigmastate/serialization/ModQSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/ModQSerializer.scala @@ -5,7 +5,7 @@ import sigmastate.lang.Terms._ import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import sigmastate.{ModQ, SType} -// TODO v6.0 (2h): make sure it is covered with tests +// TODO v6.0: make sure it is covered with tests (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/327) object ModQSerializer extends ValueSerializer[ModQ] { override def opDesc = ModQ diff --git a/interpreter/shared/src/main/scala/sigmastate/serialization/ValueSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ValueSerializer.scala index abb0cbe964..ca5a60926c 100644 --- a/interpreter/shared/src/main/scala/sigmastate/serialization/ValueSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/ValueSerializer.scala @@ -21,7 +21,7 @@ import scala.collection.mutable.{HashMap, Map} abstract class ValueSerializer[V <: Value[SType]] extends SigmaSerializer[Value[SType], V] { import scala.language.implicitConversions - val companion = ValueSerializer + private val companion = ValueSerializer def getComplexity: Int = OpCodeComplexity.getOrElse(opCode, MinimalComplexity) lazy val complexity: Int = getComplexity @@ -49,7 +49,8 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] { private val constantSerializer = ConstantSerializer(builder) private val constantPlaceholderSerializer = ConstantPlaceholderSerializer(mkConstantPlaceholder) - val serializers = SparseArrayContainer.buildForSerializers(Seq[ValueSerializer[_ <: Value[SType]]]( + val serializers: SparseArrayContainer[ValueSerializer[_ <: Value[SType]]] = + SparseArrayContainer.buildForSerializers(Seq[ValueSerializer[_ <: Value[SType]]]( constantSerializer, constantPlaceholderSerializer, TupleSerializer(mkTuple), @@ -144,7 +145,7 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] { SigmaTransformerSerializer(SigmaOr, mkSigmaOr), BoolToSigmaPropSerializer(mkBoolToSigmaProp), - // TODO hard-fork: this ModQ serializers should be removed only as part of hard-fork + // NOTE: these ModQ serializers can be removed only as part of hard-fork // because their removal may break deserialization of transaction, when for example // ModQ operation happen to be in one of the outputs (i.e. script is not executed // during validation, however deserializer is still used) @@ -161,7 +162,7 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] { )) private def serializable(v: Value[SType]): Value[SType] = v match { - case upcast: Upcast[SType, _]@unchecked => + case upcast: Upcast[SNumericType, _]@unchecked => upcast.input case _ => v } @@ -171,10 +172,10 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] { CheckValidOpCode(serializer, opCode) serializer } - def addSerializer(opCode: OpCode, ser: ValueSerializer[_ <: Value[SType]]) = { + def addSerializer(opCode: OpCode, ser: ValueSerializer[_ <: Value[SType]]): Unit = { serializers.add(opCode, ser) } - def removeSerializer(opCode: OpCode) = { + def removeSerializer(opCode: OpCode): Unit = { serializers.remove(opCode) } @@ -184,13 +185,13 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] { def parent: Scope def children: ChildrenMap def get(name: String): Option[Scope] = children.find(_._1 == name).map(_._2) - def add(name: String, s: Scope) = { + def add(name: String, s: Scope): ChildrenMap = { assert(get(name).isEmpty, s"Error while adding scope $s: name $name already exists in $this") children += (name -> s) } def showInScope(v: String): String - def provideScope(n: String, createNewScope: => Scope) = { + def provideScope(n: String, createNewScope: => Scope): Scope = { val scope = get(n) match { case Some(saved) => saved case None => @@ -203,7 +204,7 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] { } case class SerScope(opCode: OpCode, children: ChildrenMap) extends Scope { - def serializer = getSerializer(opCode) + private def serializer = getSerializer(opCode) def name = s"Serializer of ${serializer.opDesc}" override def parent: Scope = null override def showInScope(v: String): String = name + "/" + v @@ -211,8 +212,8 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] { } case class DataScope(parent: Scope, data: DataInfo[_]) extends Scope { - def name = data.info.name - override def children = mutable.ArrayBuffer.empty + override def name: String = data.info.name + override def children: ChildrenMap = mutable.ArrayBuffer.empty override def showInScope(v: String): String = parent.showInScope(s"DataInfo($data)") override def toString = s"DataScope($data)" } @@ -250,7 +251,7 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] { } val collectSerInfo: Boolean = false - val serializerInfo: Map[OpCode, SerScope] = HashMap.empty + val serializerInfo: mutable.Map[OpCode, SerScope] = mutable.HashMap.empty private var scopeStack: List[Scope] = Nil def printSerInfo(): String = { @@ -347,7 +348,7 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] { scope.get(prop.info.name) match { case None => scope.add(prop.info.name, DataScope(scope, prop)) - println(s"Added $prop to ${scope}") + println(s"Added $prop to $scope") case Some(saved) => saved match { case DataScope(_, data) => assert(data == prop, s"Saved property $data is different from being added $prop: scope $scope") @@ -357,7 +358,6 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] { } } - // TODO v5.x: control maxTreeDepth same as in deserialize (see Reader.level property and SigmaSerializer.MaxTreeDepth) override def serialize(v: Value[SType], w: SigmaByteWriter): Unit = serializable(v) match { case c: Constant[SType] => w.constantExtractionStore match { diff --git a/interpreter/shared/src/main/scala/sigmastate/trees.scala b/interpreter/shared/src/main/scala/sigmastate/trees.scala index 0d60fa1b74..254aa7c317 100644 --- a/interpreter/shared/src/main/scala/sigmastate/trees.scala +++ b/interpreter/shared/src/main/scala/sigmastate/trees.scala @@ -1,34 +1,31 @@ package sigmastate +import debox.{cfor, Map => DMap} import org.ergoplatform.SigmaConstants import org.ergoplatform.validation.SigmaValidationSettings -import scalan.{ExactIntegral, ExactNumeric, ExactOrdering, Nullable} +import scalan.ExactIntegral._ +import scalan.ExactOrdering._ import scalan.OverloadHack.Overloaded1 +import scalan.{ExactIntegral, ExactOrdering} import scorex.crypto.hash.{Blake2b256, CryptographicHash32, Sha256} +import sigmastate.ArithOp.OperationImpl import sigmastate.Operations._ import sigmastate.SCollection.{SByteArray, SIntArray} import sigmastate.SOption.SIntOption import sigmastate.Values._ -import sigmastate.basics.{SigmaProtocol, SigmaProtocolCommonInput, SigmaProtocolPrivateInput} +import sigmastate.eval.Extensions.EvalCollOps +import sigmastate.eval.NumericOps.{BigIntIsExactIntegral, BigIntIsExactOrdering} +import sigmastate.eval.{Colls, SigmaDsl} import sigmastate.interpreter.ErgoTreeEvaluator import sigmastate.interpreter.ErgoTreeEvaluator.DataEnv import sigmastate.serialization.OpCodes._ import sigmastate.serialization._ import sigmastate.utxo.{SimpleTransformerCompanion, Transformer} -import debox.{Map => DMap} -import scalan.ExactIntegral._ -import scalan.ExactOrdering._ -import sigmastate.ArithOp.OperationImpl -import sigmastate.eval.NumericOps.{BigIntIsExactIntegral, BigIntIsExactOrdering} -import sigmastate.eval.{Colls, SigmaDsl} -import sigmastate.lang.TransformingSigmaBuilder import special.collection.Coll import special.sigma.{GroupElement, SigmaProp} import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -import debox.cfor -import sigmastate.eval.Extensions.EvalCollOps /** * Basic trait for inner nodes of crypto-trees, so AND/OR/THRESHOLD sigma-protocol connectives @@ -38,10 +35,13 @@ trait SigmaConjecture extends SigmaBoolean { } /** - * Basic trait for leafs of crypto-trees, such as ProveDlog and ProveDiffieHellman instances + * Basic trait for leafs of crypto-trees, such as + * [[sigmastate.basics.DLogProtocol.ProveDlog]] and [[sigmastate.basics.ProveDHTuple]] + * instances. + * It plays the same role as [[SigmaConjecture]]. It used in prover to distinguish leafs from + * other nodes and have logic common to leaves regardless of the concrete leaf type. */ -trait SigmaProofOfKnowledgeLeaf[SP <: SigmaProtocol[SP], S <: SigmaProtocolPrivateInput[SP, _]] - extends SigmaBoolean with SigmaProtocolCommonInput[SP] +trait SigmaLeaf extends SigmaBoolean /** @@ -204,7 +204,7 @@ object CreateProveDlog extends FixedCostValueCompanion { val OpType = SFunc(SGroupElement, SSigmaProp) } -// TODO v6.0: implement `eval` method and add support in GraphBuilding +// TODO v6.0: implement `eval` method and add support in GraphBuilding (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/907) /** Construct a new authenticated dictionary with given parameters and tree root digest.*/ case class CreateAvlTree(operationFlags: ByteValue, digest: Value[SByteArray], @@ -1092,7 +1092,7 @@ object BitOp { } } -// TODO v6.0 (24h): implement modular operations +// TODO v6.0: implement modular operations (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/327) case class ModQ(input: Value[SBigInt.type]) extends NotReadyValue[SBigInt.type] { override def companion = ModQ @@ -1122,12 +1122,10 @@ trait OpGroup[C <: ValueCompanion] { object ModQArithOp extends OpGroup[ModQArithOpCompanion] { import OpCodes._ object PlusModQ extends ModQArithOpCompanion(PlusModQCode, "PlusModQ") { - // TODO soft-fork: // override def argInfos: Seq[ArgInfo] = PlusModQInfo.argInfos override def argInfos: Seq[ArgInfo] = Seq(ArgInfo("this", ""), ArgInfo("other", "")) } object MinusModQ extends ModQArithOpCompanion(MinusModQCode, "MinusModQ") { - // TODO soft-fork: // override def argInfos: Seq[ArgInfo] = MinusModQInfo.argInfos override def argInfos: Seq[ArgInfo] = Seq(ArgInfo("this", ""), ArgInfo("other", "")) } diff --git a/interpreter/shared/src/main/scala/sigmastate/types.scala b/interpreter/shared/src/main/scala/sigmastate/types.scala index fe7630eb3e..e4fd826304 100644 --- a/interpreter/shared/src/main/scala/sigmastate/types.scala +++ b/interpreter/shared/src/main/scala/sigmastate/types.scala @@ -188,7 +188,7 @@ object SType { * should be changed and SGlobal.typeId should be preserved. The regression tests in * `property("MethodCall Codes")` should pass. */ - // TODO v6.0 (h4): should contain all numeric types (including also SNumericType) + // TODO v6.0: should contain all numeric types (including also SNumericType) // to support method calls like 10.toByte which encoded as MethodCall with typeId = 4, methodId = 1 // see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/667 lazy val types: Map[Byte, STypeCompanion] = Seq( @@ -793,7 +793,7 @@ object SNumericType extends STypeCompanion { /** Array of all numeric types ordered by number of bytes in the representation. */ final val allNumericTypes = Array(SByte, SShort, SInt, SLong, SBigInt) - // TODO v6.0 (4h): this typeId is now shadowed by SGlobal.typeId + // TODO v6.0: this typeId is now shadowed by SGlobal.typeId // see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/667 override def typeId: TypeCode = 106: Byte @@ -2326,7 +2326,7 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { val insert = Insert(ADKey @@ key.toArray, ADValue @@ value.toArray) val insertRes = bv.performOneOperation(insert) // TODO v6.0: throwing exception is not consistent with update semantics - // however it preserves v4.0 semantics + // however it preserves v4.0 semantics (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/908) if (insertRes.isFailure) { Interpreter.error(s"Incorrect insert for $tree (key: $key, value: $value, digest: ${tree.digest}): ${insertRes.failed.get}}") } diff --git a/interpreter/shared/src/main/scala/sigmastate/utils/Helpers.scala b/interpreter/shared/src/main/scala/sigmastate/utils/Helpers.scala index df775cbecf..1b16ef05f9 100644 --- a/interpreter/shared/src/main/scala/sigmastate/utils/Helpers.scala +++ b/interpreter/shared/src/main/scala/sigmastate/utils/Helpers.scala @@ -1,5 +1,6 @@ package sigmastate.utils +import debox.cfor import io.circe.Decoder import org.ergoplatform.settings.ErgoAlgos import scalan.{OverloadHack, RType} @@ -37,6 +38,18 @@ object Helpers { def xor(bas: Array[Byte]*): Array[Byte] = bas.reduce({case (ba, ba1) => xor(ba, ba1)}: ((Array[Byte], Array[Byte]) => Array[Byte])) + def xor(bas: Coll[Byte]*): Coll[Byte] = { + require(bas.nonEmpty, "at least one argument is required") + if (bas.length == 1) bas(0) + else { + val res = bas(0).toArray.clone() + cfor(1)(_ < bas.length, _ + 1) { i => + xorU(res, bas(i).toArray) + } + Colls.fromArray(res) + } + } + /** Same as `xor` but makes in-place update of the first argument (hence suffix `U`) * This is boxing-free version. * @return reference to the updated first argument to easy chaining of calls. */ diff --git a/interpreter/shared/src/main/scala/sigmastate/utxo/transformers.scala b/interpreter/shared/src/main/scala/sigmastate/utxo/transformers.scala index f78552197e..8a434afc2b 100644 --- a/interpreter/shared/src/main/scala/sigmastate/utxo/transformers.scala +++ b/interpreter/shared/src/main/scala/sigmastate/utxo/transformers.scala @@ -412,7 +412,6 @@ object ExtractScriptBytes extends SimpleTransformerCompanion with FixedCostValue val OpType = SFunc(SBox, SByteArray) override def opCode: OpCode = OpCodes.ExtractScriptBytesCode - // TODO v5.x: ensure the following is true /** The cost is fixed and doesn't include serialization of ErgoTree because * the ErgoTree is expected to be constructed with non-null propositionBytes. * This is (and must be) guaranteed by ErgoTree deserializer. @@ -437,7 +436,7 @@ object ExtractBytes extends SimpleTransformerCompanion { override def opCode: OpCode = OpCodes.ExtractBytesCode /** The cost is fixed and doesn't include serialization of ErgoBox because * the ErgoBox is expected to be constructed with non-null `bytes`. - * TODO v5.x: This is not currently, but must be guaranteed by lazy ErgoBox deserializer. */ + */ override val costKind = FixedCost(JitCost(12)) override def argInfos: Seq[ArgInfo] = ExtractBytesInfo.argInfos } @@ -618,7 +617,7 @@ case class OptionGetOrElse[V <: SType](input: Value[SOption[V]], default: Value[ override def tpe: V = input.tpe.elemType protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { val inputV = input.evalTo[Option[V#WrappedType]](env) - val dV = default.evalTo[V#WrappedType](env) // TODO v6.0: execute lazily + val dV = default.evalTo[V#WrappedType](env) // TODO v6.0: execute lazily (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/906) Value.checkType(default, dV) // necessary because cast to V#WrappedType is erased addCost(OptionGetOrElse.costKind) inputV.getOrElse(dV) diff --git a/interpreter/shared/src/test/scala/sigmastate/CryptoFacadeSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/CryptoFacadeSpecification.scala index 9d5a8c116f..49fa4534bd 100644 --- a/interpreter/shared/src/test/scala/sigmastate/CryptoFacadeSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/CryptoFacadeSpecification.scala @@ -11,6 +11,8 @@ import java.math.BigInteger class CryptoFacadeSpecification extends AnyPropSpec with Matchers with ScalaCheckPropertyChecks { + val G_hex = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + property("CryptoFacade.HashHmacSHA512") { val cases = Table( ("string", "hash"), @@ -54,7 +56,7 @@ class CryptoFacadeSpecification extends AnyPropSpec with Matchers with ScalaChec } } - property("CryptoFacade.encodePoint") { + property("CryptoFacade.getASN1Encoding") { val ctx = CryptoFacade.createCryptoContext() val G = ctx.generator val Q = ctx.order @@ -62,8 +64,8 @@ class CryptoFacadeSpecification extends AnyPropSpec with Matchers with ScalaChec ("point", "expectedHex"), (ctx.infinity(), "00"), (CryptoFacade.exponentiatePoint(G, Q), "00"), - (G, "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), - (CryptoFacade.exponentiatePoint(G, BigInteger.ONE), "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + (G, G_hex), + (CryptoFacade.exponentiatePoint(G, BigInteger.ONE), G_hex), (CryptoFacade.exponentiatePoint(G, Q.subtract(BigInteger.ONE)), "0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") ) forAll (vectors) { (point, expectedHex) => @@ -71,4 +73,20 @@ class CryptoFacadeSpecification extends AnyPropSpec with Matchers with ScalaChec res shouldBe expectedHex } } + + property("CryptoContext.decodePoint") { + val ctx = CryptoFacade.createCryptoContext() + + val inf = ctx.decodePoint(Array[Byte](0)) + CryptoFacade.isInfinityPoint(inf) shouldBe true + + val G = ctx.generator + ctx.decodePoint(ErgoAlgos.decode(G_hex).get) shouldBe G + + val Q = ctx.order + val Q_minus_1 = Q.subtract(BigInteger.ONE) + val maxExp = CryptoFacade.exponentiatePoint(G, Q_minus_1) + val maxExpBytes = ErgoAlgos.decode("0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798").get + ctx.decodePoint(maxExpBytes) shouldBe maxExp + } } \ No newline at end of file diff --git a/interpreter/shared/src/test/scala/sigmastate/SigmaProtocolSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/SigmaProtocolSpecification.scala index 96931f5e84..669cb09da5 100644 --- a/interpreter/shared/src/test/scala/sigmastate/SigmaProtocolSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/SigmaProtocolSpecification.scala @@ -7,8 +7,8 @@ import special.sigma.SigmaTestingData class SigmaProtocolSpecification extends SigmaTestingData { property("CThresholdUncheckedNode equality") { - val c1 = Challenge @@ Array[Byte](1) - val c2 = Challenge @@ Array[Byte](2) + val c1 = Challenge @@ Coll[Byte](1) + val c2 = Challenge @@ Coll[Byte](2) val n0 = CThresholdUncheckedNode(c1, Seq(), 0, None) val n1 = CThresholdUncheckedNode(c1, Seq(), 0, None) val n2 = CThresholdUncheckedNode(c2, Seq(), 0, None) diff --git a/interpreter/shared/src/test/scala/sigmastate/TestsBase.scala b/interpreter/shared/src/test/scala/sigmastate/TestsBase.scala index 2ac254da5b..4e40d91c23 100644 --- a/interpreter/shared/src/test/scala/sigmastate/TestsBase.scala +++ b/interpreter/shared/src/test/scala/sigmastate/TestsBase.scala @@ -31,4 +31,7 @@ trait TestsBase extends Matchers with VersionTesting { /** Transform sigma proposition into [[ErgoTree]] using current ergoTreeHeaderInTests. */ def mkTestErgoTree(prop: SigmaBoolean): ErgoTree = ErgoTree.fromSigmaBoolean(ergoTreeHeaderInTests, prop) + + /** Max cost of script execution in tests. */ + val scriptCostLimitInTests: Int = 1000000 } diff --git a/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeContextTesting.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeContextTesting.scala index 2945192700..702d890147 100644 --- a/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeContextTesting.scala +++ b/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeContextTesting.scala @@ -1,16 +1,16 @@ package sigmastate.helpers -import org.ergoplatform.SigmaConstants.ScriptCostLimit import org.ergoplatform.ErgoLikeContext.Height import org.ergoplatform._ -import org.ergoplatform.validation.{ValidationRules, SigmaValidationSettings} +import org.ergoplatform.validation.{SigmaValidationSettings, ValidationRules} import sigmastate.AvlTreeData import sigmastate.basics.CryptoConstants import sigmastate.eval._ import sigmastate.interpreter.ContextExtension -import sigmastate.serialization.{SigmaSerializer, GroupElementSerializer} +import sigmastate.interpreter.ErgoTreeEvaluator.DefaultEvalSettings +import sigmastate.serialization.{GroupElementSerializer, SigmaSerializer} import special.collection.Coll -import special.sigma.{Box, PreHeader, Header} +import special.sigma.{Box, Header, PreHeader} object ErgoLikeContextTesting { /* NO HF PROOF: @@ -46,7 +46,8 @@ object ErgoLikeContextTesting { new ErgoLikeContext( lastBlockUtxoRoot, noHeaders, dummyPreHeader(currentHeight, minerPubkey), noBoxes, boxesToSpend, spendingTransaction, boxesToSpend.indexOf(self), extension, vs, - ScriptCostLimit.value, initCost = 0L, activatedVersion) + DefaultEvalSettings.scriptCostLimitInEvaluator, + initCost = 0L, activatedVersion) def apply(currentHeight: Height, lastBlockUtxoRoot: AvlTreeData, @@ -59,7 +60,7 @@ object ErgoLikeContextTesting { new ErgoLikeContext( lastBlockUtxoRoot, noHeaders, dummyPreHeader(currentHeight, minerPubkey), dataBoxes, boxesToSpend, spendingTransaction, selfIndex, ContextExtension.empty, - ValidationRules.currentSettings, ScriptCostLimit.value, + ValidationRules.currentSettings, DefaultEvalSettings.scriptCostLimitInEvaluator, initCost = 0L, activatedVersion) diff --git a/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeTestProvingInterpreter.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeTestProvingInterpreter.scala index aec7d4a723..2a3609a3fc 100644 --- a/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeTestProvingInterpreter.scala +++ b/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeTestProvingInterpreter.scala @@ -8,7 +8,7 @@ import sigmastate.interpreter.ProverInterpreter class ErgoLikeTestProvingInterpreter extends ErgoLikeTestInterpreter with ProverInterpreter { - override lazy val secrets: Seq[SigmaProtocolPrivateInput[_, _]] = { + override lazy val secrets: Seq[SigmaProtocolPrivateInput[_]] = { (1 to 4).map(_ => DLogProverInput.random()) ++ (1 to 4).map(_ => DiffieHellmanTupleProverInput.random()) } diff --git a/interpreter/shared/src/test/scala/sigmastate/helpers/NegativeTesting.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/NegativeTesting.scala index 158dc2b1bf..fb004c2302 100644 --- a/interpreter/shared/src/test/scala/sigmastate/helpers/NegativeTesting.scala +++ b/interpreter/shared/src/test/scala/sigmastate/helpers/NegativeTesting.scala @@ -114,7 +114,7 @@ trait NegativeTesting extends Matchers { def repeatAndReturnLast[A](nIters: Int)(block: => A): A = { require(nIters > 0) var res = block - cfor(1)(_ < nIters, _ + 1) { i => + cfor(1)(_ < nIters, _ + 1) { _ => res = block } res diff --git a/interpreter/shared/src/test/scala/sigmastate/helpers/TestingHelpers.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/TestingHelpers.scala index 0ebac010ae..fa1b9f163c 100644 --- a/interpreter/shared/src/test/scala/sigmastate/helpers/TestingHelpers.scala +++ b/interpreter/shared/src/test/scala/sigmastate/helpers/TestingHelpers.scala @@ -1,20 +1,17 @@ package sigmastate.helpers -import scorex.crypto.hash.Digest32 -import special.collection.{Coll, CollOverArray, PairOfCols} -import scorex.util.ModifierId -import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, ErgoLikeContext, ErgoLikeTransaction, ErgoLikeTransactionTemplate, Input, UnsignedInput} -import sigmastate.Values.ErgoTree import org.ergoplatform.ErgoBox.{AdditionalRegisters, Token, allZerosModifierId} import org.ergoplatform.validation.SigmaValidationSettings +import org.ergoplatform._ +import scorex.util.ModifierId import sigmastate.AvlTreeData -import sigmastate.eval.CostingSigmaDslBuilder -import sigmastate.eval._ +import sigmastate.Values.ErgoTree +import sigmastate.eval.{CostingSigmaDslBuilder, _} import sigmastate.interpreter.ContextExtension +import special.collection.{Coll, CollOverArray, PairOfCols} import special.sigma.{Header, PreHeader} import scala.collection.compat.immutable.ArraySeq -import scala.collection.mutable.WrappedArray // TODO refactor: unification is required between two hierarchies of tests // and as part of it, more methods can be moved to TestingHelpers diff --git a/interpreter/shared/src/test/scala/sigmastate/lang/SigmaBuilderTest.scala b/interpreter/shared/src/test/scala/sigmastate/lang/SigmaBuilderTest.scala index 7015587e34..8fa3ad0304 100644 --- a/interpreter/shared/src/test/scala/sigmastate/lang/SigmaBuilderTest.scala +++ b/interpreter/shared/src/test/scala/sigmastate/lang/SigmaBuilderTest.scala @@ -135,7 +135,7 @@ class SigmaBuilderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Ma val v = true val c = BooleanConstant(v) test[SBoolean.type](v, c) - testArray[SBoolean.type](v, c) // TODO v6.0: arrays should not be liftable directly + testArray[SBoolean.type](v, c) // TODO v6.0: arrays should not be liftable directly (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/905) testColl[SBoolean.type](v, c) } @@ -144,7 +144,7 @@ class SigmaBuilderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Ma val c = ByteConstant(v) testNumeric[SByte.type](v, c) testLiftingOfCAnyValue[SByte.type](v, c) - testArray[SByte.type](v, c) // TODO v6.0: arrays should not be liftable directly + testArray[SByte.type](v, c) // TODO v6.0: arrays should not be liftable directly (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/905) testColl[SByte.type](v, c) } @@ -153,7 +153,7 @@ class SigmaBuilderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Ma val c = ShortConstant(v) testNumeric[SShort.type](v, c) testLiftingOfCAnyValue[SShort.type](v, c) - testArray[SShort.type](v, c) // TODO v6.0: arrays should not be liftable directly + testArray[SShort.type](v, c) // TODO v6.0: arrays should not be liftable directly (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/905) testColl[SShort.type](v, c) } @@ -161,7 +161,7 @@ class SigmaBuilderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Ma val v = 1 val c = IntConstant(v) test[SInt.type](v, c) - testArray[SInt.type](v, c) // TODO v6.0: arrays should not be liftable directly + testArray[SInt.type](v, c) // TODO v6.0: arrays should not be liftable directly (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/905) testColl[SInt.type](v, c) } @@ -169,7 +169,7 @@ class SigmaBuilderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Ma val v = 1L val c = LongConstant(v) test[SLong.type](v, c) - testArray[SLong.type](v, c) // TODO v6.0: arrays should not be liftable directly + testArray[SLong.type](v, c) // TODO v6.0: arrays should not be liftable directly (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/905) testColl[SLong.type](v, c) } @@ -177,14 +177,14 @@ class SigmaBuilderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Ma val v = "abc" val c = StringConstant(v) test[SString.type](v, c) - testArray[SString.type](v, c) // TODO v6.0: String should be liftable at all (not supported in ErgoTree) + testArray[SString.type](v, c) // TODO v6.0: String should be liftable at all (not supported in ErgoTree) (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/905) testColl[SString.type](v, c) } property("liftToConstant BigInteger") { val v = BigInteger.valueOf(1L) val c = BigIntConstant(v) - testSuccess(v, c) // TODO v6.0: both BigInteger and arrays should not be liftable directly + testSuccess(v, c) // TODO v6.0: both BigInteger and arrays should not be liftable directly (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/905) val arr = Array.fill(10)(v) testSuccess(arr, TransformingSigmaBuilder.mkCollectionConstant[SBigInt.type](arr.map(SigmaDsl.BigInt), c.tpe)) } @@ -208,7 +208,7 @@ class SigmaBuilderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Ma property("liftToConstant ErgoBox") { val v = TestData.b2.asInstanceOf[CostingBox].wrappedValue val c = BoxConstant(TestData.b2) - testSuccess(v, c) // TODO v6.0: ErgoBox should not be liftable directly + testSuccess(v, c) // TODO v6.0: ErgoBox should not be liftable directly (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/905) testFailure(Array.fill(10)(v)) } @@ -235,7 +235,7 @@ class SigmaBuilderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Ma property("liftToConstant AvlTreeData") { val v = TestData.t1.asInstanceOf[CAvlTree].wrappedValue val c = AvlTreeConstant(SigmaDsl.avlTree(v)) - testSuccess(v, c) // TODO v6.0: AvlTreeData should not be liftable directly + testSuccess(v, c) // TODO v6.0: AvlTreeData should not be liftable directly (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/905) testFailure(Array.fill(10)(v)) } diff --git a/interpreter/shared/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala index 8b261df880..42df295d53 100644 --- a/interpreter/shared/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala @@ -1,12 +1,11 @@ package sigmastate.serialization import java.math.BigInteger - import org.ergoplatform._ import org.scalacheck.Arbitrary._ import scalan.RType import sigmastate.SCollection.SByteArray -import sigmastate.Values.{LongConstant, FalseLeaf, Constant, SValue, TrueLeaf, BigIntConstant, GroupGenerator, ByteArrayConstant} +import sigmastate.Values.{BigIntConstant, ByteArrayConstant, Constant, FalseLeaf, GroupGenerator, LongConstant, SValue, TrueLeaf} import sigmastate.basics.CryptoConstants.EcPointType import sigmastate._ import sigmastate.eval._ @@ -19,6 +18,8 @@ import scorex.util.encode.Base16 import sigmastate.exceptions.SerializerException import sigmastate.lang.DeserializationSigmaBuilder +import scala.annotation.nowarn + class ConstantSerializerSpecification extends TableSerializationSpecification { private def testCollection[T <: SType](tpe: T) = { @@ -47,8 +48,8 @@ class ConstantSerializerSpecification extends TableSerializationSpecification { def testTuples[T <: SType](tpe: T) = { implicit val wWrapped = wrappedTypeGen(tpe) implicit val tT = Evaluation.stypeToRType(tpe) - implicit val tag = tT.classTag - implicit val tAny = RType.AnyType + @nowarn implicit val tag = tT.classTag + implicit val tAny: RType[Any] = RType.AnyType forAll { in: (T#WrappedType, T#WrappedType) => val (x,y) = (in._1, in._2) roundTripTest(Constant[SType]((x, y).asWrappedType, STuple(tpe, tpe))) diff --git a/interpreter/shared/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala index 61f6fc85e3..d50f943d7a 100644 --- a/interpreter/shared/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala @@ -1,12 +1,11 @@ package sigmastate.serialization import java.math.BigInteger - import org.ergoplatform.ErgoBox import org.scalacheck.Arbitrary._ import scalan.RType import sigmastate.SCollection.SByteArray -import sigmastate.Values.{SigmaBoolean, ErgoTree} +import sigmastate.Values.{ErgoTree, SigmaBoolean} import sigmastate._ import sigmastate.eval.Evaluation import sigmastate.eval._ @@ -14,12 +13,13 @@ import sigmastate.eval.Extensions._ import sigmastate.basics.CryptoConstants.EcPointType import special.sigma.AvlTree import SType.AnyOps -import org.ergoplatform.SigmaConstants.ScriptCostLimit import sigmastate.exceptions.SerializerException import sigmastate.interpreter.{CostAccumulator, ErgoTreeEvaluator} import sigmastate.interpreter.ErgoTreeEvaluator.DefaultProfiler import sigmastate.utils.Helpers +import scala.annotation.nowarn + class DataSerializerSpecification extends SerializationSpecification { def roundtrip[T <: SType](obj: T#WrappedType, tpe: T) = { @@ -30,13 +30,14 @@ class DataSerializerSpecification extends SerializationSpecification { val res = DataSerializer.deserialize(tpe, r) res shouldBe obj + val es = ErgoTreeEvaluator.DefaultEvalSettings val accumulator = new CostAccumulator( initialCost = JitCost(0), - costLimit = Some(JitCost.fromBlockCost(ScriptCostLimit.value))) + costLimit = Some(JitCost.fromBlockCost(es.scriptCostLimitInEvaluator))) val evaluator = new ErgoTreeEvaluator( context = null, constants = ErgoTree.EmptyConstants, - coster = accumulator, DefaultProfiler, ErgoTreeEvaluator.DefaultEvalSettings) + coster = accumulator, DefaultProfiler, es) val ok = DataValueComparer.equalDataValues(res, obj)(evaluator) ok shouldBe true @@ -76,8 +77,8 @@ class DataSerializerSpecification extends SerializationSpecification { def testTuples[T <: SType](tpe: T) = { implicit val wWrapped = wrappedTypeGen(tpe) - implicit val tag = tpe.classTag[T#WrappedType] - implicit val tAny = RType.AnyType + @nowarn implicit val tag = tpe.classTag[T#WrappedType] + implicit val tAny: RType[Any] = RType.AnyType forAll { in: (T#WrappedType, T#WrappedType) => val (x,y) = (in._1, in._2) roundtrip[SType]((x, y).asWrappedType, STuple(tpe, tpe)) diff --git a/interpreter/shared/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index 6df3cc89d0..a66c56f2f2 100644 --- a/interpreter/shared/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -1,17 +1,17 @@ package sigmastate.serialization import java.math.BigInteger -import java.util import org.ergoplatform.settings.ErgoAlgos -import org.scalacheck.{Gen, Arbitrary} +import org.scalacheck.{Arbitrary, Gen} import org.scalatest.Assertion import sigmastate.Values.SigmaBoolean import sigmastate._ import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple} +import sigmastate.basics.{ProveDHTuple, SecondDHTupleProverMessage} import sigmastate.crypto.GF2_192_Poly -import sigmastate.helpers.{ErgoLikeTransactionTesting, ErgoLikeContextTesting, ContextEnrichingTestProvingInterpreter, TestingCommons} +import sigmastate.eval.Extensions.ArrayOps +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTransactionTesting, TestingCommons} import sigmastate.interpreter.Interpreter import sigmastate.serialization.generators.ObjectGenerators import sigmastate.utils.Helpers @@ -54,7 +54,7 @@ class SigSerializerSpecification extends TestingCommons // `firstMessageOpt` is not serialized sch1.copy(commitmentOpt = None) == sch2 case (conj1: UncheckedConjecture, conj2: UncheckedConjecture) => - util.Arrays.equals(conj1.challenge, conj2.challenge) && + conj1.challenge == conj2.challenge && conj1.children.zip(conj2.children).forall(t => isEquivalent(t._1, t._2)) case _ => false } @@ -147,7 +147,7 @@ class SigSerializerSpecification extends TestingCommons Helpers.decodeECPoint("02e8e77123e300f8324e7b5c4cbe0f7ac616e0b78fc45f28f54fa6696231fc8ec3") ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("c6429b70f4926a3ba1454f1aec116075f9e9fbe8a8f72114"), + Challenge @@ ErgoAlgos.decodeUnsafe("c6429b70f4926a3ba1454f1aec116075f9e9fbe8a8f72114").toColl, SecondDLogProverMessage( BigInt("b277b8462a8b9098f5d4c934ab2876eb1b5707f3119e209bdbbad831e7cc4a41", 16) ) @@ -172,8 +172,8 @@ class SigSerializerSpecification extends TestingCommons Helpers.decodeECPoint("034132d4c7eb387f12ef40ba3ec03723bda0ee5707f7471185aafc316167e85137") ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("9ec740b57353cb2f6035bb1a481b0066b2fdc0406a6fa67e"), - SecondDiffieHellmanTupleProverMessage( + Challenge @@ ErgoAlgos.decodeUnsafe("9ec740b57353cb2f6035bb1a481b0066b2fdc0406a6fa67e").toColl, + SecondDHTupleProverMessage( new BigInteger("bb2e6f44a38052b3f564fafcd477c4eb8cda1a8a553a4a5f38f1e1084d6a69f0", 16) ) ), @@ -194,7 +194,7 @@ class SigSerializerSpecification extends TestingCommons "a00b476899e583aefc18b237a7a70e73baace72aa533271a561d3432c347dcaec8975fdefb36389abe21656aadcfda0a0259681ce17bc47c9539ae1e7068292bb9646a9ffe4e11653495bd67588cfd6454d82cc455036e5b" ), CAndUncheckedNode( - Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a"), + Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a").toColl, List( UncheckedSchnorr( ProveDlog( @@ -203,7 +203,7 @@ class SigSerializerSpecification extends TestingCommons ) ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a"), + Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a").toColl, SecondDLogProverMessage( BigInt("561d3432c347dcaec8975fdefb36389abe21656aadcfda0a0259681ce17bc47c", 16) ) @@ -215,7 +215,7 @@ class SigSerializerSpecification extends TestingCommons ) ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a"), + Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a").toColl, SecondDLogProverMessage( BigInt("9539ae1e7068292bb9646a9ffe4e11653495bd67588cfd6454d82cc455036e5b", 16) ) @@ -239,7 +239,7 @@ class SigSerializerSpecification extends TestingCommons "c617e65a2ca62ac97bc33a33b76cb669622129ba0e094ad96287d97c2c6d6c8e48790d7c44961f7d958d59222ab4d7c814808a466a3e66e6f98e02d421757baa2842288b8d02787b5111db2e8924623790175e5bf27a2e4513e8eb196c22c8cf26a9d7b51cd7e386508db9c12b070d84" ), COrUncheckedNode( - Challenge @@ ErgoAlgos.decodeUnsafe("c617e65a2ca62ac97bc33a33b76cb669622129ba0e094ad9"), + Challenge @@ ErgoAlgos.decodeUnsafe("c617e65a2ca62ac97bc33a33b76cb669622129ba0e094ad9").toColl, List( UncheckedSchnorr( ProveDlog( @@ -248,7 +248,7 @@ class SigSerializerSpecification extends TestingCommons ) ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("6287d97c2c6d6c8e48790d7c44961f7d958d59222ab4d7c8"), + Challenge @@ ErgoAlgos.decodeUnsafe("6287d97c2c6d6c8e48790d7c44961f7d958d59222ab4d7c8").toColl, SecondDLogProverMessage( BigInt("14808a466a3e66e6f98e02d421757baa2842288b8d02787b5111db2e89246237", 16) ) @@ -260,7 +260,7 @@ class SigSerializerSpecification extends TestingCommons ) ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("a4903f2600cb464733ba374ff3faa914f7ac709824bd9d11"), + Challenge @@ ErgoAlgos.decodeUnsafe("a4903f2600cb464733ba374ff3faa914f7ac709824bd9d11").toColl, SecondDLogProverMessage( BigInt("90175e5bf27a2e4513e8eb196c22c8cf26a9d7b51cd7e386508db9c12b070d84", 16) ) @@ -295,7 +295,7 @@ class SigSerializerSpecification extends TestingCommons "96addfddcc197bdbacf5c0142fb16c39384b3699fa47da7dffd3149193b042fda134c0e208fefcb791379959ac6fc731adf47e32000fc75e2923dba482c843c7f6b684cbf2ceec5bfdf5fe6d13cabe5d15f8295ca4e8094fba3c4716bfdfc3c462417a79a61fcc487d6997a42739d533eebffa3b420a6e2e44616a1341e5baa1165c6c22e91a81addd97c3bd2fe40ecdbbda6f43bf71240da8dac878c044c16d42a4b34c536bbb1b" ), COrUncheckedNode( - Challenge @@ ErgoAlgos.decodeUnsafe("96addfddcc197bdbacf5c0142fb16c39384b3699fa47da7d"), + Challenge @@ ErgoAlgos.decodeUnsafe("96addfddcc197bdbacf5c0142fb16c39384b3699fa47da7d").toColl, List( UncheckedDiffieHellmanTuple( ProveDHTuple( @@ -305,16 +305,16 @@ class SigSerializerSpecification extends TestingCommons Helpers.decodeECPoint("03f17cefec3911966dc9952090325267a5cf7f9b0be76b02623021989d7f0007a2") ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("ffd3149193b042fda134c0e208fefcb791379959ac6fc731"), - SecondDiffieHellmanTupleProverMessage(new BigInteger("adf47e32000fc75e2923dba482c843c7f6b684cbf2ceec5bfdf5fe6d13cabe5d", 16)) + Challenge @@ ErgoAlgos.decodeUnsafe("ffd3149193b042fda134c0e208fefcb791379959ac6fc731").toColl, + SecondDHTupleProverMessage(new BigInteger("adf47e32000fc75e2923dba482c843c7f6b684cbf2ceec5bfdf5fe6d13cabe5d", 16)) ), COrUncheckedNode( - Challenge @@ ErgoAlgos.decodeUnsafe("697ecb4c5fa939260dc100f6274f908ea97cafc056281d4c"), + Challenge @@ ErgoAlgos.decodeUnsafe("697ecb4c5fa939260dc100f6274f908ea97cafc056281d4c").toColl, List( UncheckedSchnorr( ProveDlog(Helpers.decodeECPoint("03f997167c03aa234732e3a68126b371dffa1e409f62ca8fa18cea6acd1dbe54d5")), None, - Challenge @@ ErgoAlgos.decodeUnsafe("15f8295ca4e8094fba3c4716bfdfc3c462417a79a61fcc48"), + Challenge @@ ErgoAlgos.decodeUnsafe("15f8295ca4e8094fba3c4716bfdfc3c462417a79a61fcc48").toColl, SecondDLogProverMessage(BigInt("7d6997a42739d533eebffa3b420a6e2e44616a1341e5baa1165c6c22e91a81ad", 16)) ), UncheckedDiffieHellmanTuple( @@ -325,8 +325,8 @@ class SigSerializerSpecification extends TestingCommons Helpers.decodeECPoint("02fc58b939b105231da101540c87e56f5703460c179935aaee47137f3c367904f1") ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("7c86e210fb413069b7fd47e09890534acb3dd5b9f037d104"), - SecondDiffieHellmanTupleProverMessage(new BigInteger("dd97c3bd2fe40ecdbbda6f43bf71240da8dac878c044c16d42a4b34c536bbb1b", 16)) + Challenge @@ ErgoAlgos.decodeUnsafe("7c86e210fb413069b7fd47e09890534acb3dd5b9f037d104").toColl, + SecondDHTupleProverMessage(new BigInteger("dd97c3bd2fe40ecdbbda6f43bf71240da8dac878c044c16d42a4b34c536bbb1b", 16)) ) ) ) @@ -370,15 +370,15 @@ class SigSerializerSpecification extends TestingCommons "4fdc76711fd844de0831d8e90ebaf9c622117a062b2f8b63ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2e0a3e44384f23eee260931d88e1f5241a2600a7c98545ada675fd5e627e8e84f140fc95e28775cde52e71bb4d7b5ee2564553fac5b52202530fcbcdf205b7cca145202fb2a5bb181a890eb15536b08b747ea163f6b5d32a116fa9e1eb6b348fd82d3ebc11c125e5bc3f09c499aa0a8db14dc1780b4181f9bae5ed0f743f71b82b18784380814507d810cbef61ebc0b30e7f324083e2d3d08" ), COrUncheckedNode( - Challenge @@ ErgoAlgos.decodeUnsafe("4fdc76711fd844de0831d8e90ebaf9c622117a062b2f8b63"), + Challenge @@ ErgoAlgos.decodeUnsafe("4fdc76711fd844de0831d8e90ebaf9c622117a062b2f8b63").toColl, List( CAndUncheckedNode( - Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2"), + Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2").toColl, List( UncheckedSchnorr( ProveDlog(Helpers.decodeECPoint("0368c0d88d9eb2972bbfc23c961de6307f6a944352cbfe316f262401feabdaa87d")), None, - Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2"), + Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2").toColl, SecondDLogProverMessage(BigInt("e0a3e44384f23eee260931d88e1f5241a2600a7c98545ada675fd5e627e8e84f", 16)) ), UncheckedDiffieHellmanTuple( @@ -389,13 +389,13 @@ class SigSerializerSpecification extends TestingCommons Helpers.decodeECPoint("029d4ec275379f9212a53e15994aef203dcec43a177c0b1f40afcf592e5753ce67") ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2"), - SecondDiffieHellmanTupleProverMessage(new BigInteger("140fc95e28775cde52e71bb4d7b5ee2564553fac5b52202530fcbcdf205b7cca", 16)) + Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2").toColl, + SecondDHTupleProverMessage(new BigInteger("140fc95e28775cde52e71bb4d7b5ee2564553fac5b52202530fcbcdf205b7cca", 16)) ) ) ), COrUncheckedNode( - Challenge @@ ErgoAlgos.decodeUnsafe("b057ea5b5135708419f74f1f8bb60a65a572ad3e78b559b1"), + Challenge @@ ErgoAlgos.decodeUnsafe("b057ea5b5135708419f74f1f8bb60a65a572ad3e78b559b1").toColl, List( UncheckedDiffieHellmanTuple( ProveDHTuple( @@ -405,8 +405,8 @@ class SigSerializerSpecification extends TestingCommons Helpers.decodeECPoint("0315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a") ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("145202fb2a5bb181a890eb15536b08b747ea163f6b5d32a1"), - SecondDiffieHellmanTupleProverMessage(new BigInteger("16fa9e1eb6b348fd82d3ebc11c125e5bc3f09c499aa0a8db14dc1780b4181f9b", 16)) + Challenge @@ ErgoAlgos.decodeUnsafe("145202fb2a5bb181a890eb15536b08b747ea163f6b5d32a1").toColl, + SecondDHTupleProverMessage(new BigInteger("16fa9e1eb6b348fd82d3ebc11c125e5bc3f09c499aa0a8db14dc1780b4181f9b", 16)) ), UncheckedDiffieHellmanTuple( ProveDHTuple( @@ -416,8 +416,8 @@ class SigSerializerSpecification extends TestingCommons Helpers.decodeECPoint("0315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a") ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("a405e8a07b6ec105b167a40ad8dd02d2e298bb0113e86b10"), - SecondDiffieHellmanTupleProverMessage(new BigInteger("ae5ed0f743f71b82b18784380814507d810cbef61ebc0b30e7f324083e2d3d08", 16)) + Challenge @@ ErgoAlgos.decodeUnsafe("a405e8a07b6ec105b167a40ad8dd02d2e298bb0113e86b10").toColl, + SecondDHTupleProverMessage(new BigInteger("ae5ed0f743f71b82b18784380814507d810cbef61ebc0b30e7f324083e2d3d08", 16)) ) ) ) @@ -448,12 +448,12 @@ class SigSerializerSpecification extends TestingCommons "c94696c3e3089d9fd1174c18e6dd22f1be8003bbea08011fcf39310e7c9049c1c9966198b8d63a2f19e98843b81b74399f662dba4e764cd548406dd180453dd1bc0e24562f0184d189ca25a41ca8b54ada857dd649d3228a8c359ac499d430ecada3f92d5206cddeffb16248068c1003477d717e04afbf206c87a59ce5263ee7cc4020b5772d91b1df00bd72b15347fd" ), CThresholdUncheckedNode( - Challenge @@ ErgoAlgos.decodeUnsafe("c94696c3e3089d9fd1174c18e6dd22f1be8003bbea08011f"), + Challenge @@ ErgoAlgos.decodeUnsafe("c94696c3e3089d9fd1174c18e6dd22f1be8003bbea08011f").toColl, List( UncheckedSchnorr( ProveDlog(Helpers.decodeECPoint("03a5a5234701fff48be4ed1b3e1fab446657eeddb52e2573c52b9c4021f2403866")), None, - Challenge @@ ErgoAlgos.decodeUnsafe("067fa7cd9f98d45e18812d805e0b18dea7698bf852137526"), + Challenge @@ ErgoAlgos.decodeUnsafe("067fa7cd9f98d45e18812d805e0b18dea7698bf852137526").toColl, SecondDLogProverMessage(BigInt("9f662dba4e764cd548406dd180453dd1bc0e24562f0184d189ca25a41ca8b54a", 16)) ), UncheckedDiffieHellmanTuple( @@ -464,8 +464,8 @@ class SigSerializerSpecification extends TestingCommons Helpers.decodeECPoint("02730455ebb8c01a89dced09c5253c9bfa4b1471d1068ba30ab226104a6551c461") ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("5735f4df1b280e1d423a8f28977057af8c52123c9a3fe96d"), - SecondDiffieHellmanTupleProverMessage(new BigInteger("da857dd649d3228a8c359ac499d430ecada3f92d5206cddeffb16248068c1003", 16)) + Challenge @@ ErgoAlgos.decodeUnsafe("5735f4df1b280e1d423a8f28977057af8c52123c9a3fe96d").toColl, + SecondDHTupleProverMessage(new BigInteger("da857dd649d3228a8c359ac499d430ecada3f92d5206cddeffb16248068c1003", 16)) ), UncheckedDiffieHellmanTuple( ProveDHTuple( @@ -475,8 +475,8 @@ class SigSerializerSpecification extends TestingCommons Helpers.decodeECPoint("03cefefa1511430ca2a873759107085f269f6fbcd4e836db7760749f52b7f7923a") ), None, - Challenge @@ ErgoAlgos.decodeUnsafe("980cc5d167b847dc8baceeb02fa66d8095bb9a7f22249d54"), - SecondDiffieHellmanTupleProverMessage(new BigInteger("477d717e04afbf206c87a59ce5263ee7cc4020b5772d91b1df00bd72b15347fd", 16)) + Challenge @@ ErgoAlgos.decodeUnsafe("980cc5d167b847dc8baceeb02fa66d8095bb9a7f22249d54").toColl, + SecondDHTupleProverMessage(new BigInteger("477d717e04afbf206c87a59ce5263ee7cc4020b5772d91b1df00bd72b15347fd", 16)) ) ), 2, @@ -491,7 +491,7 @@ class SigSerializerSpecification extends TestingCommons ) ) - cases.zipWithIndex.foreach { case (c, iCase) => + cases.zipWithIndex.foreach { case (c, _) => val sigBytes = SigSerializer.toProofBytes(c.uncheckedTree) sigBytes shouldBe c.proof val uncheckedTree = SigSerializer.parseAndComputeChallenges(c.prop, c.proof)(null) @@ -525,7 +525,9 @@ class SigSerializerSpecification extends TestingCommons r.position = 0 var reported = false - val res = SigSerializer.readBytesChecked(r, nRequested, msg => reported = true) + val res = SigSerializer.readBytesChecked(r, + numRequestedBytes = nRequested, + onError = _ => reported = true) res shouldBe bytes reported shouldBe true } diff --git a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala index 0e15cd525e..d566e416ba 100644 --- a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala @@ -27,6 +27,7 @@ import special.sigma._ import java.math.BigInteger import scala.collection.compat.immutable.ArraySeq +import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.reflect.ClassTag @@ -209,7 +210,7 @@ trait ObjectGenerators extends TypeGenerators lazy val evaluatedValueGen: Gen[EvaluatedValue[SType]] = Gen.oneOf(booleanConstGen.asInstanceOf[Gen[EvaluatedValue[SType]]], byteArrayConstGen, longConstGen) - def additionalRegistersGen(cnt: Byte): Seq[Gen[(NonMandatoryRegisterId, EvaluatedValue[SType])]] = { + def additionalRegistersGen(cnt: Byte): Seq[Gen[(NonMandatoryRegisterId, EvaluatedValue[_ <: SType])]] = { scala.util.Random.shuffle((0 until cnt).toList) .map(_ + ErgoBox.startingNonMandatoryIndex) .map(rI => ErgoBox.registerByIndex(rI).asInstanceOf[NonMandatoryRegisterId]) @@ -237,8 +238,8 @@ trait ObjectGenerators extends TypeGenerators val unsignedShortGen: Gen[Short] = Gen.chooseNum(0, Short.MaxValue).map(_.toShort) lazy val contextExtensionGen: Gen[ContextExtension] = for { - values <- Gen.sequence(contextExtensionValuesGen(0, 5))(Buildable.buildableSeq) - } yield ContextExtension(values.toMap) + values: collection.Seq[(Byte, EvaluatedValue[SType])] <- Gen.sequence(contextExtensionValuesGen(0, 5))(Buildable.buildableSeq) + } yield ContextExtension(mutable.LinkedHashMap[Byte, EvaluatedValue[SType]](values.sortBy(_._1).toSeq:_*)) lazy val serializedProverResultGen: Gen[ProverResult] = for { bytes <- arrayOfRange(1, 100, arbByte.arbitrary) @@ -347,7 +348,8 @@ trait ObjectGenerators extends TypeGenerators lazy val additionalRegistersGen: Gen[AdditionalRegisters] = for { regNum <- Gen.chooseNum[Byte](0, ErgoBox.nonMandatoryRegistersCount) regs <- Gen.sequence(additionalRegistersGen(regNum))(Buildable.buildableSeq) - } yield regs.toMap + } yield + Map(regs.toIndexedSeq:_*) def ergoBoxTokens(availableTokens: Seq[TokenId]): Gen[Coll[Token]] = for { tokens <- diff --git a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala index c4c964cc74..2582d1305c 100644 --- a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala @@ -5,24 +5,24 @@ import org.scalacheck.Arbitrary.arbString import sigmastate._ trait TypeGenerators { - implicit val booleanTypeGen = Gen.const(SBoolean) - implicit val byteTypeGen = Gen.const(SByte) - implicit val shortTypeGen = Gen.const(SShort) - implicit val intTypeGen = Gen.const(SInt) - implicit val longTypeGen = Gen.const(SLong) - implicit val bigIntTypeGen = Gen.const(SBigInt) - implicit val groupElementTypeGen = Gen.const(SGroupElement) - implicit val sigmaPropTypeGen = Gen.const(SSigmaProp) - implicit val boxTypeGen = Gen.const(SBox) - implicit val avlTreeTypeGen = Gen.const(SAvlTree) - implicit val optionSigmaPropTypeGen = Gen.const(SOption(SSigmaProp)) + implicit val booleanTypeGen: Gen[SBoolean.type] = Gen.const(SBoolean) + implicit val byteTypeGen: Gen[SByte.type] = Gen.const(SByte) + implicit val shortTypeGen: Gen[SShort.type] = Gen.const(SShort) + implicit val intTypeGen: Gen[SInt.type] = Gen.const(SInt) + implicit val longTypeGen: Gen[SLong.type] = Gen.const(SLong) + implicit val bigIntTypeGen: Gen[SBigInt.type] = Gen.const(SBigInt) + implicit val groupElementTypeGen: Gen[SGroupElement.type] = Gen.const(SGroupElement) + implicit val sigmaPropTypeGen: Gen[SSigmaProp.type] = Gen.const(SSigmaProp) + implicit val boxTypeGen: Gen[SBox.type] = Gen.const(SBox) + implicit val avlTreeTypeGen: Gen[SAvlTree.type] = Gen.const(SAvlTree) + implicit val optionSigmaPropTypeGen: Gen[SOption[SSigmaProp.type]] = Gen.const(SOption(SSigmaProp)) implicit val primTypeGen: Gen[SPrimType] = Gen.oneOf[SPrimType](SBoolean, SByte, SShort, SInt, SLong, SBigInt, SGroupElement, SSigmaProp, SUnit) - implicit val arbPrimType = Arbitrary(primTypeGen) + implicit val arbPrimType: Arbitrary[SPrimType] = Arbitrary(primTypeGen) implicit val predefTypeGen: Gen[SPredefType] = Gen.oneOf[SPredefType](SBoolean, SByte, SShort, SInt, SLong, SBigInt, SGroupElement, SSigmaProp, SUnit, SBox, SAvlTree) - implicit val arbPredefType = Arbitrary(predefTypeGen) + implicit val arbPredefType: Arbitrary[SPredefType] = Arbitrary(predefTypeGen) implicit def genToArbitrary[T: Gen]: Arbitrary[T] = Arbitrary(implicitly[Gen[T]]) diff --git a/interpreter/shared/src/test/scala/sigmastate/utils/HelpersTests.scala b/interpreter/shared/src/test/scala/sigmastate/utils/HelpersTests.scala index c1accad043..3325035bbd 100644 --- a/interpreter/shared/src/test/scala/sigmastate/utils/HelpersTests.scala +++ b/interpreter/shared/src/test/scala/sigmastate/utils/HelpersTests.scala @@ -5,12 +5,16 @@ import Helpers._ import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import sigmastate.eval.Extensions.ArrayOps class HelpersTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers with ObjectGenerators { property("xorU") { forAll(arrayGen[Byte]) { arr => val x = xor(arr, arr) + val xColl = xor(arr.toColl, arr.toColl) + x shouldBe xColl.toArray + val cloned = arr.clone() xorU(cloned, arr) cloned shouldBe x @@ -18,7 +22,13 @@ class HelpersTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matche val arr1 = x val arr2 = cloned val arr3 = xor(arr1, arr2) + val arr3Coll = xor(arr1.toColl, arr2.toColl) + arr3 shouldBe arr3Coll.toArray + val res1 = xor(cloned, arr1, arr2, arr3) + val res1Coll = xor(cloned.toColl, arr1.toColl, arr2.toColl, arr3.toColl) + res1 shouldBe res1Coll.toArray + val res2 = cloned xorU(res2, Seq(arr1, arr2, arr3)) diff --git a/interpreter/shared/src/test/scala/sigmastate/utxo/ProverSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/utxo/ProverSpecification.scala index 1ca5bb96a2..360754db6f 100644 --- a/interpreter/shared/src/test/scala/sigmastate/utxo/ProverSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/utxo/ProverSpecification.scala @@ -5,7 +5,7 @@ import scorex.crypto.hash.Blake2b256 import sigmastate.Values.SigmaBoolean import sigmastate._ import sigmastate.basics.DLogProtocol.FirstDLogProverMessage -import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, SecP256K1Group} +import sigmastate.basics.{FirstDHTupleProverMessage, SecP256K1Group} import sigmastate.exceptions.InterpreterException import sigmastate.helpers.{ErgoLikeTestProvingInterpreter, TestingCommons} import sigmastate.interpreter.{HintsBag, ProverInterpreter} @@ -52,7 +52,7 @@ class ProverSpecification extends TestingCommons { h3.realCommitments.head.commitment shouldBe h3.ownCommitments.head.commitment - h3.realCommitments.head.commitment.isInstanceOf[FirstDiffieHellmanTupleProverMessage] shouldBe true + h3.realCommitments.head.commitment.isInstanceOf[FirstDHTupleProverMessage] shouldBe true } property("setPositions - and") { diff --git a/interpreter/shared/src/test/scala/special/sigma/ContractsTestkit.scala b/interpreter/shared/src/test/scala/special/sigma/ContractsTestkit.scala index 4c073d3dc9..a544361d98 100644 --- a/interpreter/shared/src/test/scala/special/sigma/ContractsTestkit.scala +++ b/interpreter/shared/src/test/scala/special/sigma/ContractsTestkit.scala @@ -7,7 +7,9 @@ import sigmastate.{AvlTreeData, Values} import sigmastate.eval._ import sigmastate.eval.Extensions._ import sigmastate.helpers.TestingHelpers._ -import scalan._ // imports implicit ClassTag +import scalan._ + +import scala.annotation.nowarn // imports implicit ClassTag trait ContractsTestkit { val R0 = 0.toByte; @@ -59,7 +61,7 @@ trait ContractsTestkit { val AliceId = Array[Byte](1) // 0x0001 - def newAliceBox(id: Byte, value: Long): Box = { + def newAliceBox(@nowarn id: Byte, value: Long): Box = { val ergoBox = testBox(value, ErgoTree.fromProposition(Values.TrueSigmaProp), creationHeight = 0, additionalTokens = Seq(), additionalRegisters = Map()) diff --git a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala index 3838ac87d2..d2b2fa819e 100644 --- a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala @@ -7,7 +7,7 @@ import org.scalacheck.Gen.containerOfN import org.scalacheck.util.Buildable import org.scalacheck.{Arbitrary, Gen} import scalan.RType -import scorex.crypto.authds.{ADDigest, ADKey, ADValue} +import scorex.crypto.authds.{ADKey, ADValue} import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.ModifierId import sigmastate.Values.{ByteArrayConstant, ConcreteCollection, ConstantPlaceholder, ErgoTree, FalseLeaf, IntConstant, LongConstant, SigmaPropConstant, TrueLeaf} @@ -43,11 +43,11 @@ trait SigmaTestingData extends TestingCommons with ObjectGenerators { len <- Gen.choose(0, 100) arr <- containerOfN[Array, Byte](len, Arbitrary.arbByte.arbitrary) } yield arr - val bytesCollGen = bytesGen.map(Colls.fromArray(_)) - val intsCollGen = arrayGen[Int].map(Colls.fromArray(_)) - implicit val arbBytes = Arbitrary(bytesCollGen) - implicit val arbInts = Arbitrary(intsCollGen) - val keyCollGen = collOfN[Byte](32, arbitrary[Byte]) + val bytesCollGen: Gen[Coll[Byte]] = bytesGen.map(Colls.fromArray(_)) + val intsCollGen: Gen[Coll[Int]] = arrayGen[Int].map(Colls.fromArray(_)) + implicit val arbBytes: Arbitrary[Coll[Byte]] = Arbitrary(bytesCollGen) + implicit val arbInts: Arbitrary[Coll[Int]] = Arbitrary(intsCollGen) + val keyCollGen: Gen[Coll[Byte]] = collOfN[Byte](32, arbitrary[Byte]) import org.ergoplatform.dsl.AvlTreeHelpers._ def createAvlTreeAndProver(entries: (Coll[Byte], Coll[Byte])*) = { @@ -132,7 +132,7 @@ trait SigmaTestingData extends TestingCommons with ObjectGenerators { def createBigIntMaxValue(): BigInt = BigIntMaxValue_instances.getNext - // TODO v6.0: this values have bitCount == 255 (see to256BitValueExact) + // TODO v6.0: this values have bitCount == 255 (see to256BitValueExact) (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/554) val BigIntMinValue = CBigInt(new BigInteger("-7F" + "ff" * 31, 16)) val BigIntMaxValue = createBigIntMaxValue() diff --git a/parsers/shared/src/main/scala/sigmastate/lang/Types.scala b/parsers/shared/src/main/scala/sigmastate/lang/Types.scala index 8a45d4d66b..e4869e4190 100644 --- a/parsers/shared/src/main/scala/sigmastate/lang/Types.scala +++ b/parsers/shared/src/main/scala/sigmastate/lang/Types.scala @@ -3,7 +3,6 @@ package sigmastate.lang import fastparse._ import ScalaWhitespace._ import sigmastate._ -import sigmastate.SCollection.SByteArray import Values._ import sigmastate.lang.Terms.Ident import sigmastate.lang.syntax.Core diff --git a/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala b/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala index e28cd71473..42d02fefb5 100644 --- a/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala +++ b/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala @@ -1,7 +1,8 @@ package sigmastate.lang import fastparse.Parsed -import org.ergoplatform.{ErgoAddressEncoder, ErgoBox} +import fastparse.Parsed.Failure +import org.ergoplatform.ErgoBox import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks @@ -25,9 +26,9 @@ class SigmaParserTest extends AnyPropSpec with ScalaCheckPropertyChecks with Mat v.sourceContext.isDefined shouldBe true assertSrcCtxForAllNodes(v) v - case f@Parsed.Failure(_, _, extra) => - val traced = extra.traced - println(s"\nTRACE: ${traced.trace}") + case f: Failure => + val traced = f.extra.trace() + println(s"\nTRACE: ${traced.msg}") f.get // force show error diagnostics } } diff --git a/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala b/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala index 085ad468f9..a992dbf0d7 100644 --- a/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala +++ b/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala @@ -1,7 +1,6 @@ package sigmastate.eval import org.ergoplatform._ -import org.ergoplatform.validation.ValidationRules.CheckTupleType import scalan.ExactIntegral.{ByteIsExactIntegral, IntIsExactIntegral, LongIsExactIntegral, ShortIsExactIntegral} import scalan.ExactOrdering.{ByteIsExactOrdering, IntIsExactOrdering, LongIsExactOrdering, ShortIsExactOrdering} import scalan.util.Extensions.ByteOps @@ -15,7 +14,7 @@ import sigmastate.serialization.OpCodes import sigmastate.utxo._ import sigmastate._ import sigmastate.basics.CryptoConstants.EcPointType -import sigmastate.exceptions.{SigmaException, CosterException} +import sigmastate.exceptions.{SigmaException, GraphBuildingException} import scala.collection.mutable.ArrayBuffer /** Perform translation of typed expression given by [[Value]] to a graph in IRContext. @@ -396,8 +395,8 @@ trait GraphBuilding extends SigmaLibrary { IR: IRContext => protected implicit def groupElementToECPoint(g: special.sigma.GroupElement): EcPointType = CostingSigmaDslBuilder.toECPoint(g).asInstanceOf[EcPointType] - def error(msg: String) = throw new CosterException(msg, None) - def error(msg: String, srcCtx: Option[SourceContext]) = throw new CosterException(msg, srcCtx) + def error(msg: String) = throw new GraphBuildingException(msg, None) + def error(msg: String, srcCtx: Option[SourceContext]) = throw new GraphBuildingException(msg, srcCtx) /** Translates the given typed expression to IR graph representing a function from * Context to some type T. diff --git a/sc/shared/src/main/scala/sigmastate/lang/SigmaBinder.scala b/sc/shared/src/main/scala/sigmastate/lang/SigmaBinder.scala index 07616af0c4..f30b25b44c 100644 --- a/sc/shared/src/main/scala/sigmastate/lang/SigmaBinder.scala +++ b/sc/shared/src/main/scala/sigmastate/lang/SigmaBinder.scala @@ -48,7 +48,6 @@ class SigmaBinder(env: ScriptEnv, builder: SigmaBuilder, case "SELF" => Some(Self) case "CONTEXT" => Some(Context) case "Global" => Some(Global) - case "None" => Some(mkNoneValue(NoType)) case _ => None } } @@ -62,12 +61,6 @@ class SigmaBinder(env: ScriptEnv, builder: SigmaBuilder, val tpe = if (args.isEmpty) NoType else args(0).tpe Some(mkConcreteCollection(args, tpe)) - // Rule: Some(x) --> - case Apply(i @ Ident("Some", _), args) => args match { - case Seq(arg) => Some(mkSomeValue(arg)) - case _ => error(s"Invalid arguments of Some: expected one argument but found $args", i.sourceContext) - } - // Rule: min(x, y) --> case Apply(i @ Ident("min", _), args) => args match { case Seq(l: SValue, r: SValue) => diff --git a/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala b/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala index ad4dddcf51..ccc0784abe 100644 --- a/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala +++ b/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala @@ -497,9 +497,6 @@ class SigmaTyper(val builder: SigmaBuilder, case Negation(i) => unmap[SNumericType](env, "-", i.asNumValue)(mkNegation)(tT) case BitInversion(i) => unmap[SNumericType](env, "~", i.asNumValue)(mkBitInversion)(tT) - case SomeValue(x) => SomeValue(assignType(env, x)) - case v: NoneValue[_] => v - case Global => Global case Context => Context case Height => Height diff --git a/sc/shared/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala b/sc/shared/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala index 6b3c219aaa..22359c1606 100644 --- a/sc/shared/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala +++ b/sc/shared/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala @@ -1,7 +1,6 @@ package org.ergoplatform import org.ergoplatform.ErgoAddressEncoder.{MainnetNetworkPrefix, TestnetNetworkPrefix, hash256} -import org.ergoplatform.SigmaConstants.ScriptCostLimit import org.ergoplatform.validation.{ValidationException, ValidationRules} import org.scalatest.{Assertion, TryValues} import scorex.crypto.hash.Blake2b256 @@ -260,7 +259,7 @@ class ErgoAddressSpecification extends SigmaDslTesting property("negative cases: deserialized script + costing exceptions") { implicit lazy val IR = new TestingIRContext - def testPay2SHAddress(address: Pay2SHAddress, script: VarBinding, costLimit: Int = ScriptCostLimit.value): CostedProverResult = { + def testPay2SHAddress(address: Pay2SHAddress, script: VarBinding, costLimit: Int = scriptCostLimitInTests): CostedProverResult = { val boxToSpend = testBox(10, address.script, creationHeight = 5) val ctx = copyContext(ErgoLikeContextTesting.dummy(boxToSpend, activatedVersionInTests) .withExtension(ContextExtension(Seq( diff --git a/sc/shared/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala b/sc/shared/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala index c0c926c9a2..25c01af43d 100644 --- a/sc/shared/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala +++ b/sc/shared/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala @@ -100,6 +100,7 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting { ).map(identity).toConstant // TODO v6.0 (16h): fix collections equality and remove map(identity) // (PairOfColl should be equal CollOverArray but now it is not) + // see (https://github.com/ScorexFoundation/sigmastate-interpreter/issues/909) res shouldBe exp } diff --git a/sc/shared/src/test/scala/org/ergoplatform/ErgoTreePredefSpec.scala b/sc/shared/src/test/scala/org/ergoplatform/ErgoTreePredefSpec.scala index 36c5e78067..89247a968a 100644 --- a/sc/shared/src/test/scala/org/ergoplatform/ErgoTreePredefSpec.scala +++ b/sc/shared/src/test/scala/org/ergoplatform/ErgoTreePredefSpec.scala @@ -226,7 +226,7 @@ class ErgoTreePredefSpec extends CompilerTestingCommons with CompilerCrossVersio boxesToSpend = inputBoxes, spendingTransaction, self = inputBoxes.head, - activatedVersionInTests).withCostLimit(SigmaConstants.ScriptCostLimit.value * 10) + activatedVersionInTests).withCostLimit(scriptCostLimitInTests * 10) val pr = prover.prove(emptyEnv + (ScriptNameProp -> "tokenThresholdScript_prove"), prop, ctx, fakeMessage).getOrThrow verifier.verify(emptyEnv + (ScriptNameProp -> "tokenThresholdScript_verify"), prop, ctx, pr, fakeMessage).getOrThrow._1 shouldBe true diff --git a/sc/shared/src/test/scala/org/ergoplatform/validation/RuleStatusSerializerSpec.scala b/sc/shared/src/test/scala/org/ergoplatform/validation/RuleStatusSerializerSpec.scala index 711827fe1e..9909f485b5 100644 --- a/sc/shared/src/test/scala/org/ergoplatform/validation/RuleStatusSerializerSpec.scala +++ b/sc/shared/src/test/scala/org/ergoplatform/validation/RuleStatusSerializerSpec.scala @@ -1,13 +1,12 @@ package org.ergoplatform.validation -import org.scalatest.Assertion import sigmastate.helpers.CompilerTestingCommons import sigmastate.serialization.{SigmaSerializer, SerializationSpecification} class RuleStatusSerializerSpec extends SerializationSpecification with CompilerTestingCommons { - private def roundtrip(status: RuleStatus): Assertion = { - implicit val ser = RuleStatusSerializer + private def roundtrip(status: RuleStatus) = { + implicit val ser: RuleStatusSerializer.type = RuleStatusSerializer roundTripTest(status) roundTripTestWithPos(status) } diff --git a/sc/shared/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala b/sc/shared/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala index b74d4014f2..c4162103c6 100644 --- a/sc/shared/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala +++ b/sc/shared/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala @@ -1,26 +1,34 @@ package org.ergoplatform.validation +import org.ergoplatform.validation.ValidationRules.{FirstRuleId, currentSettings} import org.scalatest.Assertion import sigmastate.helpers.CompilerTestingCommons import sigmastate.serialization.SerializationSpecification class SigmaValidationSettingsSerializerSpec extends SerializationSpecification with CompilerTestingCommons { - private def roundtrip(settings: SigmaValidationSettings): Assertion = { - implicit val set = SigmaValidationSettingsSerializer + private def roundtrip(settings: SigmaValidationSettings) = { + implicit val set: SigmaValidationSettingsSerializer.type = SigmaValidationSettingsSerializer roundTripTest(settings) roundTripTestWithPos(settings) } property("ValidationRules.currentSettings round trip") { - roundtrip(ValidationRules.currentSettings) + roundtrip(currentSettings) } property("SigmaValidationSettings round trip") { forAll(ruleIdGen, statusGen, MinSuccessful(100)) { (ruleId, status) => - val vs = ValidationRules.currentSettings.updated(ruleId, status) + val vs = currentSettings.updated(ruleId, status) roundtrip(vs) } } + property("SigmaValidationSettings equality") { + val vs = currentSettings + val vs_copy = currentSettings.updated(FirstRuleId, currentSettings.getStatus(FirstRuleId).get) + val vs2 = currentSettings.updated(FirstRuleId, DisabledRule) + vs.equals(vs2) shouldBe false + vs.equals(vs_copy) shouldBe true + } } \ No newline at end of file diff --git a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala index 20aabb1b00..7bffa895f8 100644 --- a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala @@ -256,7 +256,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { { import SSigmaProp._ (SSigmaProp.typeId, Seq( MInfo(1, PropBytesMethod), - MInfo(2, IsProvenMethod) // TODO v5.x (3h): this method must be removed + MInfo(2, IsProvenMethod) // TODO v5.x (3h): this method must be removed (see https://github.com/ScorexFoundation/sigmastate-interpreter/pull/800) ), true) }, { import SBox._ diff --git a/sc/shared/src/test/scala/sigmastate/helpers/CompilerTestingCommons.scala b/sc/shared/src/test/scala/sigmastate/helpers/CompilerTestingCommons.scala index 8981ace5df..588dfe4f96 100644 --- a/sc/shared/src/test/scala/sigmastate/helpers/CompilerTestingCommons.scala +++ b/sc/shared/src/test/scala/sigmastate/helpers/CompilerTestingCommons.scala @@ -1,6 +1,5 @@ package sigmastate.helpers -import org.ergoplatform.SigmaConstants.ScriptCostLimit import org.ergoplatform._ import org.ergoplatform.validation.ValidationRules.CheckSerializableTypeCode import org.ergoplatform.validation.{ValidationException, ValidationSpecification} @@ -137,7 +136,7 @@ trait CompilerTestingCommons extends TestingCommons val sigmaCtx = createContexts(in, bindings) val accumulator = new CostAccumulator( initialCost = JitCost(0), - costLimit = Some(JitCost.fromBlockCost(ScriptCostLimit.value))) + costLimit = Some(JitCost.fromBlockCost(evalSettings.scriptCostLimitInEvaluator))) val evaluator = new ErgoTreeEvaluator( context = sigmaCtx, constants = ErgoTree.EmptyConstants, @@ -175,22 +174,26 @@ trait CompilerTestingCommons extends TestingCommons funcJitFromExpr(funcScript, compiledTree, bindings:_*) } - protected def roundTripTest[T](v: T)(implicit serializer: SigmaSerializer[T, T]): Assertion = { + protected def roundTripTest[T](v: T)(implicit serializer: SigmaSerializer[T, T]): T = { // using default sigma reader/writer val bytes = serializer.toBytes(v) bytes.nonEmpty shouldBe true val r = SigmaSerializer.startReader(bytes) val positionLimitBefore = r.positionLimit - serializer.parse(r) shouldBe v + val parsed = serializer.parse(r) + parsed shouldBe v r.positionLimit shouldBe positionLimitBefore + parsed } - protected def roundTripTestWithPos[T](v: T)(implicit serializer: SigmaSerializer[T, T]): Assertion = { + protected def roundTripTestWithPos[T](v: T)(implicit serializer: SigmaSerializer[T, T]): T = { val randomBytesCount = Gen.chooseNum(1, 20).sample.get val randomBytes = Gen.listOfN(randomBytesCount, arbByte.arbitrary).sample.get.toArray val bytes = serializer.toBytes(v) - serializer.parse(SigmaSerializer.startReader(bytes)) shouldBe v + val parsed = serializer.parse(SigmaSerializer.startReader(bytes)) + parsed shouldBe v serializer.parse(SigmaSerializer.startReader(randomBytes ++ bytes, randomBytesCount)) shouldBe v + parsed } def testReduce(I: Interpreter)(ctx: I.CTX, prop: SigmaPropValue): SigmaBoolean = { diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala index b8aefc1041..397c5c5e5d 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala @@ -143,19 +143,6 @@ class SigmaBinderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Mat If(EQ(IntConstant(10), IntConstant(11)), IntConstant(2), IntConstant(3))) } - // TODO v6.0 (4h): SomeValue and NoneValue are not used in ErgoTree and can be - // either removed or implemented in v4.x - property("Option constructors") { - bind(env, "None") shouldBe NoneValue(NoType) - bind(env, "Some(None)") shouldBe SomeValue(NoneValue(NoType)) - bind(env, "Some(10)") shouldBe SomeValue(IntConstant(10)) - bind(env, "Some(X)") shouldBe SomeValue(Ident("X")) - bind(env, "Some(Some(X - 1))") shouldBe - SomeValue(SomeValue(mkMinus(Ident("X").asValue[SInt.type], IntConstant(1)))) - bind(env, "Some(Some(X + 1))") shouldBe - SomeValue(SomeValue(plus(Ident("X").asValue[SInt.type], IntConstant(1)))) - } - property("lambdas") { bind(env, "{ (a: Int) => a - 1 }") shouldBe Lambda(IndexedSeq("a" -> SInt), NoType, mkMinus(IntIdent("a"), 1)) @@ -214,8 +201,4 @@ class SigmaBinderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Mat e.source shouldBe Some(SourceContext(2, 5, "val x = 10")) } - property("fail Some (invalid arguments)") { - fail(env, "Some(1, 2)", 1, 1) - fail(env, "Some()", 1, 1) - } } diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala index 96a430a51d..f6266cf285 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala @@ -8,7 +8,7 @@ import sigmastate._ import sigmastate.helpers.CompilerTestingCommons import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.lang.Terms.{Apply, Ident, Lambda, MethodCall, ZKProofBlock} -import sigmastate.exceptions.{CosterException, InvalidArguments, TyperException} +import sigmastate.exceptions.{GraphBuildingException, InvalidArguments, TyperException} import sigmastate.serialization.ValueSerializer import sigmastate.serialization.generators.ObjectGenerators import sigmastate.utxo.{ByIndex, ExtractAmount, GetVar, SelectField} @@ -23,15 +23,15 @@ class SigmaCompilerTest extends CompilerTestingCommons with LangTests with Objec private def comp(x: String): Value[SType] = compile(env, x) private def testMissingCosting(script: String, expected: SValue): Unit = { - an [CosterException] should be thrownBy comp(env, script) + an [GraphBuildingException] should be thrownBy comp(env, script) } private def testMissingCostingWOSerialization(script: String, expected: SValue): Unit = { - an [CosterException] should be thrownBy comp(env, script) + an [GraphBuildingException] should be thrownBy comp(env, script) } private def costerFail(env: ScriptEnv, x: String, expectedLine: Int, expectedCol: Int): Unit = { - val exception = the[CosterException] thrownBy comp(env, x) + val exception = the[GraphBuildingException] thrownBy comp(env, x) withClue(s"Exception: $exception, is missing source context:") { exception.source shouldBe defined } val sourceContext = exception.source.get sourceContext.line shouldBe expectedLine @@ -320,11 +320,6 @@ class SigmaCompilerTest extends CompilerTestingCommons with LangTests with Objec ) } - property("failed option constructors (not supported)") { - costerFail("None", 1, 1) - costerFail("Some(10)", 1, 1) - } - property("byteArrayToLong") { comp("byteArrayToLong(longToByteArray(1L))") shouldBe ByteArrayToLong(LongToByteArray(LongConstant(1))) } diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala index 22c2a1fd7a..85476c3c6d 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala @@ -214,13 +214,6 @@ class SigmaTyperTest extends AnyPropSpec typefail(env, "Coll(1, false)", 1, 1) } - property("Option constructors") { - typecheck(env, "Some(10)") shouldBe SOption(SInt) - typecheck(env, "Some(x)") shouldBe SOption(SInt) - typecheck(env, "Some(x + 1)") shouldBe SOption(SInt) - typecheck(env, "Some(Some(10))") shouldBe SOption(SOption(SInt)) - } - property("methods returning Option") { typecheck(env, "getVar[Int](10)") shouldBe SOption(SInt) typecheck(env, "{ val v = getVar[Int](1); v.get }") shouldBe SInt diff --git a/sc/shared/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala b/sc/shared/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala index 54ee4f4857..bfba524baf 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala @@ -55,8 +55,14 @@ class SerializationRoundTripSpec extends AnyPropSpec } property("ErgoBox: Serializer round trip") { - forAll { t: ErgoBox => roundTripTest(t)(ErgoBox.sigmaSerializer) } - forAll { t: ErgoBox => roundTripTestWithPos(t)(ErgoBox.sigmaSerializer) } + forAll { t: ErgoBox => + val parsed = roundTripTest(t)(ErgoBox.sigmaSerializer) + parsed.bytes shouldBe t.bytes + } + forAll { t: ErgoBox => + val parsed = roundTripTestWithPos(t)(ErgoBox.sigmaSerializer) + parsed.bytes shouldBe t.bytes + } } property("ContextExtension: Serializer round trip") { diff --git a/sc/shared/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala b/sc/shared/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala index f2f797a832..ce442ecb50 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala @@ -4,7 +4,7 @@ import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} import sigmastate.Values.{ConcreteCollection, FalseLeaf, IntConstant, SigmaPropConstant, SigmaPropValue, TrueLeaf} import sigmastate._ import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTransactionTesting, CompilerTestingCommons} -import sigmastate.exceptions.CosterException +import sigmastate.exceptions.GraphBuildingException class ThresholdSpecification extends CompilerTestingCommons with CompilerCrossVersionProps { @@ -407,8 +407,8 @@ class ThresholdSpecification extends CompilerTestingCommons val keyName = "pubkeyA" val env = Map(keyName -> pubkeyA) val pubKeysStrExceeding = Array.fill[String](AtLeast.MaxChildrenCount + 1)(keyName).mkString(",") - an[CosterException] should be thrownBy compile(env, s"""atLeast(2, Coll($pubKeysStrExceeding))""") - an[CosterException] should be thrownBy + an[GraphBuildingException] should be thrownBy compile(env, s"""atLeast(2, Coll($pubKeysStrExceeding))""") + an[GraphBuildingException] should be thrownBy compile(env, s"""{ val arr = Coll($pubKeysStrExceeding); atLeast(2, arr) }""") // max children should work fine diff --git a/sc/shared/src/test/scala/special/sigma/DataValueComparerSpecification.scala b/sc/shared/src/test/scala/special/sigma/DataValueComparerSpecification.scala index b264d52144..eaca756182 100644 --- a/sc/shared/src/test/scala/special/sigma/DataValueComparerSpecification.scala +++ b/sc/shared/src/test/scala/special/sigma/DataValueComparerSpecification.scala @@ -1,6 +1,5 @@ package special.sigma -import org.ergoplatform.SigmaConstants.ScriptCostLimit import org.scalatest.BeforeAndAfterAll import scalan.RType import scalan.util.BenchmarkUtil @@ -23,7 +22,8 @@ class DataValueComparerSpecification extends SigmaDslTesting isMeasureOperationTime = true, isMeasureScriptTime = true, isLogEnabled = false, // don't commit the `true` value (CI log is too high) - costTracingEnabled = true // should always be enabled in tests (and false by default) + costTracingEnabled = true, // should always be enabled in tests (and false by default) + scriptCostLimitInEvaluator = scriptCostLimitInTests ) override val nBenchmarkIters = 10 @@ -36,7 +36,7 @@ class DataValueComparerSpecification extends SigmaDslTesting def createEvaluator(settings: EvalSettings, profiler: Profiler): ErgoTreeEvaluator = { val accumulator = new CostAccumulator( initialCost = JitCost(0), - costLimit = Some(JitCost.fromBlockCost(ScriptCostLimit.value))) + costLimit = Some(JitCost.fromBlockCost(settings.scriptCostLimitInEvaluator))) val evaluator = new ErgoTreeEvaluator( context = null, constants = ErgoTree.EmptyConstants, diff --git a/sc/shared/src/test/scala/special/sigma/SigmaDslSpecification.scala b/sc/shared/src/test/scala/special/sigma/SigmaDslSpecification.scala index 07984e81f4..ead1af1873 100644 --- a/sc/shared/src/test/scala/special/sigma/SigmaDslSpecification.scala +++ b/sc/shared/src/test/scala/special/sigma/SigmaDslSpecification.scala @@ -39,7 +39,7 @@ import sigmastate.basics.ProveDHTuple import sigmastate.interpreter._ import org.scalactic.source.Position import sigmastate.helpers.SigmaPPrint -import sigmastate.exceptions.CosterException +import sigmastate.exceptions.GraphBuildingException import scala.collection.compat.immutable.ArraySeq /** This suite tests every method of every SigmaDsl type to be equivalent to @@ -2202,7 +2202,7 @@ class SigmaDslSpecification extends SigmaDslTesting (BigIntMaxValue, BigIntMinValue) -> expect(false), (BigIntMaxValue, -47.toBigInt) -> expect(false), (BigIntMaxValue, BigIntMaxValue) -> expect(false), - (BigIntMaxValue, BigIntOverlimit) -> expect(true), // TODO v6.0: reject this overlimit cases + (BigIntMaxValue, BigIntOverlimit) -> expect(true), // TODO v6.0: reject this overlimit cases (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/554) (BigIntOverlimit, BigIntOverlimit) -> expect(false) ) @@ -2219,7 +2219,7 @@ class SigmaDslSpecification extends SigmaDslTesting property("BigInt LE, GE") { val o = NumericOps.BigIntIsExactOrdering - // TODO v6.0: this values have bitCount == 255 (see to256BitValueExact) + // TODO v6.0: this values have bitCount == 255 (see to256BitValueExact) (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/554) val BigIntMinValue = CBigInt(new BigInteger("-7F" + "ff" * 31, 16)) val BigIntMaxValue = CBigInt(new BigInteger("7F" + "ff" * 31, 16)) val BigIntOverlimit = CBigInt(new BigInteger("7F" + "ff" * 33, 16)) @@ -2262,7 +2262,7 @@ class SigmaDslSpecification extends SigmaDslTesting (BigIntMaxValue, BigIntMinValue) -> expect(false), (BigIntMaxValue, -47.toBigInt) -> expect(false), (BigIntMaxValue, BigIntMaxValue) -> expect(true), - (BigIntMaxValue, BigIntOverlimit) -> expect(true), // TODO v6.0: reject this overlimit cases + (BigIntMaxValue, BigIntOverlimit) -> expect(true), // TODO v6.0: reject this overlimit cases (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/554) (BigIntOverlimit, BigIntOverlimit) -> expect(true) ) @@ -2274,7 +2274,7 @@ class SigmaDslSpecification extends SigmaDslTesting } property("BigInt methods equivalence (new features)") { - // TODO v6.0 (2h): the behavior of `upcast` for BigInt is different from all other Numeric types + // TODO v6.0: the behavior of `upcast` for BigInt is different from all other Numeric types (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/877) // The `Upcast(bigInt, SBigInt)` node is never produced by ErgoScript compiler, but is still valid ErgoTree. // It makes sense to fix this inconsistency as part of upcoming forks assertExceptionThrown( @@ -2282,7 +2282,7 @@ class SigmaDslSpecification extends SigmaDslTesting _.getMessage.contains("Cannot upcast value") ) - // TODO v6.0 (2h): the behavior of `downcast` for BigInt is different from all other Numeric types + // TODO v6.0: the behavior of `downcast` for BigInt is different from all other Numeric types (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/877) // The `Downcast(bigInt, SBigInt)` node is never produced by ErgoScript compiler, but is still valid ErgoTree. // It makes sense to fix this inconsistency as part of HF assertExceptionThrown( @@ -2919,12 +2919,6 @@ class SigmaDslSpecification extends SigmaDslTesting )) } - // TODO v6.0 (3h): related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - // property("GroupElement.isIdentity equivalence") { - // // val isIdentity = existingFeature({ (x: GroupElement) => x.isIdentity }, - // // "{ (x: GroupElement) => x.isIdentity }") - // } - property("AvlTree properties equivalence") { def expectedExprFor(propName: String) = { FuncValue( @@ -3555,7 +3549,7 @@ class SigmaDslSpecification extends SigmaDslTesting val invalidKvs = Colls.fromItems((invalidKey -> value)) // NOTE, insertProof is based on `key` val input = (tree, (invalidKvs, insertProof)) val (res, _) = insert.checkEquality(input).getOrThrow - res.isDefined shouldBe true // TODO v6.0: should it really be true? (looks like a bug) + res.isDefined shouldBe true // TODO v6.0: should it really be true? (looks like a bug) (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/908) insert.checkExpected(input, Expected(Success(res), 1796, costDetails2, 1796)) } @@ -3717,7 +3711,7 @@ class SigmaDslSpecification extends SigmaDslTesting val invalidKvs = Colls.fromItems((key -> invalidValue)) val input = (tree, (invalidKvs, updateProof)) val (res, _) = update.checkEquality(input).getOrThrow - res.isDefined shouldBe true // TODO v6.0: should it really be true? (looks like a bug) + res.isDefined shouldBe true // TODO v6.0: should it really be true? (looks like a bug) (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/908) update.checkExpected(input, Expected(Success(res), 1805, costDetails2, 1805)) } @@ -4044,7 +4038,7 @@ class SigmaDslSpecification extends SigmaDslTesting "{ (x: Box) => x.creationInfo }", FuncValue(Vector((1, SBox)), ExtractCreationInfo(ValUse(1, SBox))))) - // TODO v6.0 (2h): fix collections equality and remove map(identity) + // TODO v6.0: fix collections equality and remove map(identity)(see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/909) // (PairOfColl should be equal CollOverArray) verifyCases( Seq( @@ -4068,7 +4062,7 @@ class SigmaDslSpecification extends SigmaDslTesting } property("Box properties equivalence (new features)") { - // TODO v6.0 (4h): related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416 + // TODO v6.0: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416 val getReg = newFeature((x: Box) => x.getReg[Int](1).get, "{ (x: Box) => x.getReg[Int](1).get }") @@ -4344,7 +4338,7 @@ class SigmaDslSpecification extends SigmaDslTesting val box3 = SigmaDsl.Box(testBox(20, TrueTree, 0, Seq(), Map( ErgoBox.R4 -> Constant((10, 20L).asInstanceOf[SType#WrappedType], STuple(SInt, SLong)) - // TODO v6.0 (1h): uncomment after DataSerializer support of Option type + // TODO v6.0: uncomment after DataSerializer support of Option type (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/659) // ErgoBox.R5 -> Constant((10, Some(20L)).asInstanceOf[SType#WrappedType], STuple(SInt, SOption(SLong))) // ErgoBox.R6 -> Constant[SOption[SInt.type]](Option(10), SOption(SInt)), ))) @@ -4448,7 +4442,7 @@ class SigmaDslSpecification extends SigmaDslTesting ))) - // TODO v6.0 (1h): uncomment after DataSerializer support of Option type + // TODO v6.0: uncomment after DataSerializer support of Option type (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/659) // verifyCases( // Seq( // (box3, Expected(Success(10), cost = 36468))//, expCostDetails, 1790)) @@ -5164,7 +5158,7 @@ class SigmaDslSpecification extends SigmaDslTesting existingPropTest("minerPubKey", { (x: Context) => x.minerPubKey }), preGeneratedSamples = Some(samples)) -// TODO v6.0 (2h): implement support of Option[T] in DataSerializer +// TODO v6.0: implement support of Option[T] in DataSerializer (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/659) // this will allow passing optional values in registers and also in constants // testCases2( // Seq( @@ -9095,7 +9089,7 @@ class SigmaDslSpecification extends SigmaDslTesting ) )) } - // TODO v6.0 (3h): implement Option.fold + // TODO v6.0: implement Option.fold (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479) property("Option new methods") { val n = ExactNumeric.LongIsExactNumeric val fold = newFeature({ (x: Option[Long]) => x.fold(5.toLong)( (v: Long) => n.plus(v, 1) ) }, @@ -10032,7 +10026,7 @@ class SigmaDslSpecification extends SigmaDslTesting |""".stripMargin ) - // TODO v6.0: Add support of SFunc in TypeSerializer + // TODO v6.0: Add support of SFunc in TypeSerializer (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/847) assertExceptionThrown( f.verifyCase(Coll[Int](), Expected(Success(Coll[Int]()), 0)), exceptionLike[MatchError]("(SInt$) => SInt$ (of class sigmastate.SFunc)") diff --git a/sc/shared/src/test/scala/special/sigma/SigmaDslTesting.scala b/sc/shared/src/test/scala/special/sigma/SigmaDslTesting.scala index e7781a289c..1630e7f8a9 100644 --- a/sc/shared/src/test/scala/special/sigma/SigmaDslTesting.scala +++ b/sc/shared/src/test/scala/special/sigma/SigmaDslTesting.scala @@ -1,6 +1,6 @@ package special.sigma -import org.ergoplatform.SigmaConstants.ScriptCostLimit +import debox.cfor import org.ergoplatform._ import org.ergoplatform.dsl.{ContractSpec, SigmaContractSyntax, TestContractSpec} import org.ergoplatform.validation.ValidationRules.CheckSerializableTypeCode @@ -9,6 +9,10 @@ import org.scalacheck.Arbitrary._ import org.scalacheck.Gen.frequency import org.scalacheck.{Arbitrary, Gen} import org.scalatest.exceptions.TestFailedException +import org.scalatest.matchers.should.Matchers +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import scalan.Platform.threadSleepOrNoOp import scalan.RType import scalan.RType._ import scalan.util.BenchmarkUtil @@ -18,7 +22,7 @@ import scalan.util.StringUtil.StringUtilExtensions import sigmastate.SType.AnyOps import sigmastate.Values.{ByteArrayConstant, Constant, ConstantNode, ErgoTree, IntConstant, SValue} import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} -import sigmastate.basics.{SigmaProtocol, SigmaProtocolCommonInput, SigmaProtocolPrivateInput} +import sigmastate.basics.SigmaProtocolPrivateInput import sigmastate.eval.Extensions._ import sigmastate.eval.{CompiletimeIRContext, CostingBox, CostingDataContext, Evaluation, IRContext, SigmaDsl} import sigmastate.helpers.TestingHelpers._ @@ -30,13 +34,8 @@ import sigmastate.serialization.ValueSerializer import sigmastate.serialization.generators.ObjectGenerators import sigmastate.utils.Helpers._ import sigmastate.utxo.{DeserializeContext, DeserializeRegister, GetVar, OptionGet} -import sigmastate.{SOption, SSigmaProp, SType, VersionContext, eval} +import sigmastate.{SOption, SSigmaProp, SType, SigmaLeaf, VersionContext, eval} import special.collection.{Coll, CollType} -import debox.cfor -import org.scalatest.matchers.should.Matchers -import org.scalatest.propspec.AnyPropSpec -import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -import scalan.Platform.threadSleepOrNoOp import java.util import scala.collection.mutable @@ -85,7 +84,7 @@ class SigmaDslTesting extends AnyPropSpec val sk2: DLogProverInput = decodeSecretInput("34648336872573478681093104997365775365807654884817677358848426648354905397359") val sk3: DLogProverInput = decodeSecretInput("50415569076448343263191022044468203756975150511337537963383000142821297891310") - val secrets: Seq[SigmaProtocolPrivateInput[_ <: SigmaProtocol[_], _ <: SigmaProtocolCommonInput[_]]] = { + val secrets: Seq[SigmaProtocolPrivateInput[_ <: SigmaLeaf]] = { // Note, not all secrets are used, which is required by checkVerify // This is to make AtLeast to be unproved and thus the verify is successfull // because of the other condition in SigmaOr (see checkVerify) @@ -366,7 +365,7 @@ class SigmaDslTesting extends AnyPropSpec createErgoLikeContext( newCtx, ValidationRules.currentSettings, - ScriptCostLimit.value, + evalSettings.scriptCostLimitInEvaluator, initCost = initialCostInTests.value ) @@ -374,7 +373,10 @@ class SigmaDslTesting extends AnyPropSpec val box = createBox(0, compiledTree, additionalRegisters = newRegisters) // make sure we are doing tests with the box with is actually serializable - try roundTripTest(box)(ErgoBox.sigmaSerializer) + try { + val parsed = roundTripTest(box)(ErgoBox.sigmaSerializer) + parsed.bytes shouldBe box.bytes + } catch { case ValidationException(_, r: CheckSerializableTypeCode.type, Seq(SOption.OptionTypeCode), _) => // ignore the problem with Option serialization, but test all the other cases @@ -845,7 +847,7 @@ class SigmaDslTesting extends AnyPropSpec /** in v5.x the old and the new interpreters are the same */ val oldImpl = () => funcJit[A, B](script) - val newImpl = oldImpl // funcJit[A, B](script) // TODO v6.0 (16h): use actual new implementation here + val newImpl = oldImpl // funcJit[A, B](script) // TODO v6.0: use actual new implementation here (https://github.com/ScorexFoundation/sigmastate-interpreter/issues/910) /** In v5.x this method just checks the old implementations fails on the new feature. */ override def checkEquality(input: A, logInputOutput: Boolean = false): Try[(B, CostDetails)] = { diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala index 4f4cc87010..def671c2d4 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala @@ -1,10 +1,29 @@ package org.ergoplatform.sdk.js -import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters +import org.ergoplatform.sdk import scala.scalajs.js.UndefOr import scala.scalajs.js.annotation.JSExportTopLevel +import scala.scalajs.js + +/** JS exported version of the [[sdk.BlockchainParameters]] class with the same fields. + * Blockchain parameters re-adjustable via miners voting and voting-related data. + * All these fields are included into extension section of a first block of a voting epoch. + * + * @param storageFeeFactor cost of storing 1 byte in UTXO for four years, in nanoErgs + * @param minValuePerByte cost of a transaction output, in computation unit + * @param maxBlockSize max block size, in bytes + * @param tokenAccessCost cost of a token contained in a transaction, in computation unit + * @param inputCost cost of a transaction input, in computation unit + * @param dataInputCost cost of a transaction data input, in computation unit + * @param outputCost cost of a transaction output, in computation unit + * @param maxBlockCost computation units limit per block + * @param softForkStartingHeight height when voting for a soft-fork had been started + * @param softForkVotesCollected votes for soft-fork collected in previous epochs + * @param blockVersion Protocol version activated on the network + * @see sdk.BlockchainParameters + */ @JSExportTopLevel("BlockchainParameters") class BlockchainParameters( val storageFeeFactor: Int, @@ -15,20 +34,7 @@ class BlockchainParameters( val dataInputCost: Int, val outputCost: Int, val maxBlockCost: Int, - val _softForkStartingHeight: UndefOr[Int], - val _softForkVotesCollected: UndefOr[Int], + val softForkStartingHeight: UndefOr[Int], + val softForkVotesCollected: UndefOr[Int], val blockVersion: Byte -) extends ErgoLikeParameters { - import org.ergoplatform.sdk.Iso._ - /** - * @return height when voting for a soft-fork had been started - */ - override def softForkStartingHeight: Option[Int] = - Isos.isoUndefOr[Int, Int](identityIso).to(_softForkStartingHeight) - - /** - * @return votes for soft-fork collected in previous epochs - */ - override def softForkVotesCollected: Option[Int] = - Isos.isoUndefOr[Int, Int](identityIso).to(_softForkVotesCollected) -} +) extends js.Object diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala index 0b91cbae57..425d4c7e88 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala @@ -3,10 +3,14 @@ package org.ergoplatform.sdk.js import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel -/** Equivalent of [[org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeStateContext]] available from JS. */ +/** Equivalent of [[org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext]] available from JS. + * @param sigmaLastHeaders fixed number (10 in Ergo) of last block headers + * @param previousStateDigest hex of UTXO set digest from a last header (of sigmaLastHeaders) + * @param sigmaPreHeader returns pre-header (header without certain fields) of the current block + */ @JSExportTopLevel("BlockchainStateContext") class BlockchainStateContext( val sigmaLastHeaders: js.Array[Header], val previousStateDigest: String, val sigmaPreHeader: PreHeader -) +) extends js.Object diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ErgoTree.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ErgoTree.scala index 810cc1ea81..8469545d95 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ErgoTree.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ErgoTree.scala @@ -1,8 +1,6 @@ package org.ergoplatform.sdk.js -import scorex.util.encode.Base16 import sigmastate.Values -import sigmastate.serialization.ErgoTreeSerializer import scala.scalajs.js import scala.scalajs.js.JSConverters.JSRichIterableOnce diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala new file mode 100644 index 0000000000..cc20e704bb --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala @@ -0,0 +1,29 @@ +package org.ergoplatform.sdk.js + +import sigmastate.crypto.{CryptoFacade, CryptoFacadeJs, Ecp, Platform} +import sigmastate.eval.Extensions.ArrayByteOps + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExportTopLevel + +/** Equivalent of [[special.sigma.GroupElement]] available from JS. */ +@JSExportTopLevel("GroupElement") +class GroupElement(val point: Ecp) extends js.Object { + /** Returns the point encoded as hex string (ASN.1 encoding). + * @see CryptoFacade.getASN1Encoding + */ + def toPointHex(): String = { + CryptoFacade.getASN1Encoding(point, true).toHex + } +} + +@JSExportTopLevel("GroupElementObj") +object GroupElement extends js.Object { + /** Creates a new [[GroupElement]] from the given hex string (ASN.1 encoding) + * representation of the underlying [[sigmastate.crypto.Platform.Point]]. + */ + def fromPointHex(pointHex: String): GroupElement = { + val point = CryptoFacadeJs.createCryptoContext().decodePoint(pointHex) + new GroupElement(new Platform.Ecp(point)) + } +} \ No newline at end of file diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala index fecd4ef67c..f95b8eb0e1 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala @@ -3,22 +3,48 @@ package org.ergoplatform.sdk.js import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel -/** Equivalent of [[special.sigma.Header]] available from JS. */ +/** Equivalent of [[special.sigma.Header]] available from JS. + * Represents data of the block header available in Sigma propositions. + */ @JSExportTopLevel("Header") class Header( + /** Hex representation of ModifierId of this Header */ val id: String, + /** Block version, to be increased on every soft and hardfork. */ val version: Byte, + /** Hex representation of ModifierId of the parent block */ val parentId: String, + /** Hex hash of ADProofs for transactions in a block */ val ADProofsRoot: String, + /** AvlTree of a state after block application */ val stateRoot: AvlTree, + /** Hex of root hash (for a Merkle tree) of transactions in a block. */ val transactionsRoot: String, + /** Block timestamp (in milliseconds since beginning of Unix Epoch) */ val timestamp: js.BigInt, + /** Current difficulty in a compressed view. + * NOTE: actually it is unsigned Int */ val nBits: js.BigInt, + /** Block height */ val height: Int, + /** Hex of root hash of extension section */ val extensionRoot: String, - val minerPk: String, - val powOnetimePk: String, + + /** Miner public key (hex of EC Point). Should be used to collect block rewards. + * Part of Autolykos solution. + */ + val minerPk: GroupElement, + + /** One-time public key (hex of EC Point). Prevents revealing of miners secret. */ + val powOnetimePk: GroupElement, + + /** Hex of nonce bytes */ val powNonce: String, + + /** Distance between pseudo-random number, corresponding to nonce `powNonce` and a secret, + * corresponding to `minerPk`. The lower `powDistance` is, the harder it was to find this solution. */ val powDistance: js.BigInt, + + /** Miner votes for changing system parameters. */ val votes: String ) extends js.Object diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index c00f72bdff..a317e3ab29 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -1,30 +1,29 @@ package org.ergoplatform.sdk.js import org.ergoplatform.ErgoBox._ -import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, UnsignedErgoLikeTransaction, UnsignedInput} -import org.ergoplatform.sdk.{ExtendedInputBox, Iso} import org.ergoplatform.sdk.JavaHelpers.UniversalConverter -import org.ergoplatform.sdk.wallet.protocol.context.{CErgoLikeStateContext, ErgoLikeStateContext} +import org.ergoplatform.sdk.{Iso, ExtendedInputBox} +import org.ergoplatform.sdk.wallet.protocol.context +import org.ergoplatform._ import scalan.RType -import scorex.crypto.authds.{ADDigest, ADKey} +import scorex.crypto.authds.ADKey import scorex.util.ModifierId import scorex.util.encode.Base16 -import sigmastate.{AvlTreeData, AvlTreeFlags, SType} import sigmastate.Values.{Constant, GroupElementConstant} import sigmastate.eval.Extensions.ArrayOps -import sigmastate.eval.{CAvlTree, CBigInt, CHeader, CPreHeader, Colls, Digest32Coll, Evaluation} -import sigmastate.fleetSdkCommon.{distEsmTypesBoxesMod => boxesMod, distEsmTypesCommonMod => commonMod, distEsmTypesContextExtensionMod => contextExtensionMod, distEsmTypesInputsMod => inputsMod, distEsmTypesRegistersMod => registersMod, distEsmTypesTokenMod => tokenMod} -import sigmastate.interpreter.ContextExtension -import sigmastate.serialization.{ErgoTreeSerializer, ValueSerializer} -import special.collection.Coll -import special.collection.Extensions.CollBytesOps -import special.sigma -import special.sigma.GroupElement +import sigmastate.eval.{CBigInt, Digest32Coll, Evaluation, CAvlTree, Colls, CGroupElement, CPreHeader, CHeader} import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box import sigmastate.fleetSdkCommon.distEsmTypesCommonMod.HexString import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters import sigmastate.fleetSdkCommon.distEsmTypesTokenMod.TokenAmount -import sigmastate.fleetSdkCommon.distEsmTypesTransactionsMod.UnsignedTransaction +import sigmastate.fleetSdkCommon.distEsmTypesTransactionsMod.{UnsignedTransaction, SignedTransaction} +import sigmastate.fleetSdkCommon.{distEsmTypesProverResultMod => proverResultMod, distEsmTypesContextExtensionMod => contextExtensionMod, distEsmTypesInputsMod => inputsMod, distEsmTypesBoxesMod => boxesMod, distEsmTypesCommonMod => commonMod, distEsmTypesRegistersMod => registersMod, distEsmTypesTokenMod => tokenMod} +import sigmastate.interpreter.{ContextExtension, ProverResult} +import sigmastate.serialization.{ErgoTreeSerializer, ValueSerializer} +import sigmastate.{AvlTreeData, SType, AvlTreeFlags} +import special.collection.Coll +import special.collection.Extensions.CollBytesOps +import special.sigma import java.math.BigInteger import scala.collection.immutable.ListMap @@ -57,17 +56,26 @@ object Isos { override def from(x: Coll[Byte]): String = x.toHex } - val isoStringToGroupElement: Iso[String, GroupElement] = new Iso[String, GroupElement] { - override def to(x: String): GroupElement = { + val isoStringToGroupElement: Iso[String, sigma.GroupElement] = new Iso[String, sigma.GroupElement] { + override def to(x: String): sigma.GroupElement = { val bytes = Base16.decode(x).get ValueSerializer.deserialize(bytes).asInstanceOf[GroupElementConstant].value } - override def from(x: GroupElement): String = { + override def from(x: sigma.GroupElement): String = { val bytes = ValueSerializer.serialize(GroupElementConstant(x)) Base16.encode(bytes) } } + val isoGroupElement: Iso[GroupElement, special.sigma.GroupElement] = new Iso[GroupElement, special.sigma.GroupElement] { + override def to(x: GroupElement): sigma.GroupElement = { + CGroupElement(x.point) + } + override def from(x: sigma.GroupElement): GroupElement = { + new GroupElement(x.asInstanceOf[CGroupElement].wrappedValue) + } + } + implicit val isoBoxId: Iso[boxesMod.BoxId, ErgoBox.BoxId] = new Iso[boxesMod.BoxId, ErgoBox.BoxId] { override def to(x: boxesMod.BoxId): ErgoBox.BoxId = ADKey @@@ isoStringToArray.to(x) @@ -124,8 +132,8 @@ object Isos { nBits = isoBigIntToLong.to(a.nBits), height = a.height, extensionRoot = isoStringToColl.to(a.extensionRoot), - minerPk = isoStringToGroupElement.to(a.minerPk), - powOnetimePk = isoStringToGroupElement.to(a.powOnetimePk), + minerPk = isoGroupElement.to(a.minerPk), + powOnetimePk = isoGroupElement.to(a.powOnetimePk), powNonce = isoStringToColl.to(a.powNonce), powDistance = isoBigInt.to(a.powDistance), votes = isoStringToColl.to(a.votes) @@ -144,8 +152,8 @@ object Isos { nBits = isoBigIntToLong.from(header.nBits), height = header.height, extensionRoot = isoStringToColl.from(header.extensionRoot), - minerPk = isoStringToGroupElement.from(header.minerPk), - powOnetimePk = isoStringToGroupElement.from(header.powOnetimePk), + minerPk = isoGroupElement.from(header.minerPk), + powOnetimePk = isoGroupElement.from(header.powOnetimePk), powNonce = isoStringToColl.from(header.powNonce), powDistance = isoBigInt.from(header.powDistance), votes = isoStringToColl.from(header.votes) @@ -161,7 +169,7 @@ object Isos { timestamp = isoBigIntToLong.to(a.timestamp), nBits = isoBigIntToLong.to(a.nBits), height = a.height, - minerPk = isoStringToGroupElement.to(a.minerPk), + minerPk = isoGroupElement.to(a.minerPk), votes = isoStringToColl.to(a.votes) ) } @@ -173,22 +181,55 @@ object Isos { timestamp = isoBigIntToLong.from(header.timestamp), nBits = isoBigIntToLong.from(header.nBits), height = header.height, - minerPk = isoStringToGroupElement.from(header.minerPk), + minerPk = isoGroupElement.from(header.minerPk), votes = isoStringToColl.from(header.votes) ) } } - implicit val isoBlockchainStateContext: Iso[BlockchainStateContext, ErgoLikeStateContext] = new Iso[BlockchainStateContext, ErgoLikeStateContext] { - override def to(a: BlockchainStateContext): ErgoLikeStateContext = { - CErgoLikeStateContext( + val isoBlockchainParameters: Iso[BlockchainParameters, sdk.BlockchainParameters] = new Iso[BlockchainParameters, sdk.BlockchainParameters] { + override def to(a: BlockchainParameters): sdk.BlockchainParameters = { + sdk.BlockchainParameters( + storageFeeFactor = a.storageFeeFactor, + minValuePerByte = a.minValuePerByte, + maxBlockSize = a.maxBlockSize, + tokenAccessCost = a.tokenAccessCost, + inputCost = a.inputCost, + dataInputCost = a.dataInputCost, + outputCost = a.outputCost, + maxBlockCost = a.maxBlockCost, + softForkStartingHeight = Isos.isoUndefOr[Int, Int](Iso.identityIso).to(a.softForkStartingHeight), + softForkVotesCollected = Isos.isoUndefOr[Int, Int](Iso.identityIso).to(a.softForkVotesCollected), + blockVersion = a.blockVersion + ) + } + override def from(b: sdk.BlockchainParameters): BlockchainParameters = { + new BlockchainParameters( + storageFeeFactor = b.storageFeeFactor, + minValuePerByte = b.minValuePerByte, + maxBlockSize = b.maxBlockSize, + tokenAccessCost = b.tokenAccessCost, + inputCost = b.inputCost, + dataInputCost = b.dataInputCost, + outputCost = b.outputCost, + maxBlockCost = b.maxBlockCost, + softForkStartingHeight = Isos.isoUndefOr[Int, Int](Iso.identityIso).from(b.softForkStartingHeight), + softForkVotesCollected = Isos.isoUndefOr[Int, Int](Iso.identityIso).from(b.softForkVotesCollected), + blockVersion = b.blockVersion + ) + } + } + + implicit val isoBlockchainStateContext: Iso[BlockchainStateContext, context.BlockchainStateContext] = new Iso[BlockchainStateContext, context.BlockchainStateContext] { + override def to(a: BlockchainStateContext): context.BlockchainStateContext = { + context.BlockchainStateContext( sigmaLastHeaders = isoArrayToColl(isoHeader).to(a.sigmaLastHeaders), previousStateDigest = isoStringToColl.to(a.previousStateDigest), sigmaPreHeader = isoPreHeader.to(a.sigmaPreHeader) ) } - override def from(b: ErgoLikeStateContext): BlockchainStateContext = { + override def from(b: context.BlockchainStateContext): BlockchainStateContext = { new BlockchainStateContext( sigmaLastHeaders = isoArrayToColl(isoHeader).from(b.sigmaLastHeaders), previousStateDigest = isoStringToColl.from(b.previousStateDigest), @@ -200,7 +241,7 @@ object Isos { implicit val isoContextExtension: Iso[contextExtensionMod.ContextExtension, ContextExtension] = new Iso[contextExtensionMod.ContextExtension, ContextExtension] { override def to(x: contextExtensionMod.ContextExtension): ContextExtension = { var map = new ListMap[Byte, Constant[SType]]() - val keys = js.Object.keys(x) + val keys = js.Object.keys(x).sorted for ( k <- keys ) { val id = k.toInt.toByte val c = isoHexStringToConstant.to(x.apply(id).get.get) @@ -227,6 +268,28 @@ object Isos { inputsMod.UnsignedInput(x.boxId.convertTo[boxesMod.BoxId], isoContextExtension.from(x.extension)) } + implicit val isoProverResult: Iso[proverResultMod.ProverResult, ProverResult] = new Iso[proverResultMod.ProverResult, ProverResult] { + override def to(a: proverResultMod.ProverResult): ProverResult = { + ProverResult( + proof = isoStringToArray.to(a.proofBytes), + extension = isoContextExtension.to(a.extension) + ) + } + override def from(b: ProverResult): proverResultMod.ProverResult = { + proverResultMod.ProverResult( + isoContextExtension.from(b.extension), + isoStringToArray.from(b.proof) + ) + } + } + + implicit val isoSignedInput: Iso[inputsMod.SignedInput, Input] = new Iso[inputsMod.SignedInput, Input] { + override def to(x: inputsMod.SignedInput): Input = + Input(x.boxId.convertTo[ErgoBox.BoxId], isoProverResult.to(x.spendingProof)) + override def from(x: Input): inputsMod.SignedInput = + inputsMod.SignedInput(x.boxId.convertTo[boxesMod.BoxId], isoProverResult.from(x.spendingProof)) + } + implicit val isoDataInput: Iso[inputsMod.DataInput, DataInput] = new Iso[inputsMod.DataInput, DataInput] { override def to(x: inputsMod.DataInput): DataInput = DataInput(x.boxId.convertTo[ErgoBox.BoxId]) @@ -321,8 +384,8 @@ object Isos { } } - implicit val isoBoxCandidate: Iso[boxesMod.BoxCandidate[commonMod.Amount], ErgoBoxCandidate] = new Iso[boxesMod.BoxCandidate[commonMod.Amount], ErgoBoxCandidate] { - override def to(x: boxesMod.BoxCandidate[commonMod.Amount]): ErgoBoxCandidate = { + implicit val isoBoxCandidate: Iso[boxesMod.BoxCandidate[commonMod.Amount, NonMandatoryRegisters], ErgoBoxCandidate] = new Iso[boxesMod.BoxCandidate[commonMod.Amount, NonMandatoryRegisters], ErgoBoxCandidate] { + override def to(x: boxesMod.BoxCandidate[commonMod.Amount, NonMandatoryRegisters]): ErgoBoxCandidate = { val ergoBoxCandidate = new ErgoBoxCandidate( value = isoAmount.to(x.value), ergoTree = { @@ -336,11 +399,11 @@ object Isos { ergoBoxCandidate } - override def from(x: ErgoBoxCandidate): boxesMod.BoxCandidate[commonMod.Amount] = { + override def from(x: ErgoBoxCandidate): boxesMod.BoxCandidate[commonMod.Amount, NonMandatoryRegisters] = { val ergoTree = ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(x.ergoTree) val ergoTreeStr = Base16.encode(ergoTree) val assets = isoTokenArray.from(x.additionalTokens) - boxesMod.BoxCandidate[commonMod.Amount]( + boxesMod.BoxCandidate[commonMod.Amount, NonMandatoryRegisters]( ergoTree = ergoTreeStr, value = isoAmount.from(x.value), assets = assets, @@ -350,27 +413,8 @@ object Isos { } } - // Implements Iso between UnsignedTransaction and UnsignedErgoLikeTransaction - val isoUnsignedTransaction: Iso[UnsignedTransaction, UnsignedErgoLikeTransaction] = - new Iso[UnsignedTransaction, UnsignedErgoLikeTransaction] { - override def to(a: UnsignedTransaction): UnsignedErgoLikeTransaction = { - new UnsignedErgoLikeTransaction( - inputs = isoArrayToIndexed(isoUnsignedInput).to(a.inputs), - dataInputs = isoArrayToIndexed(isoDataInput).to(a.dataInputs), - outputCandidates = isoArrayToIndexed(isoBoxCandidate).to(a.outputs), - ) - } - override def from(b: UnsignedErgoLikeTransaction): UnsignedTransaction = { - UnsignedTransaction( - inputs = isoArrayToIndexed(isoUnsignedInput).from(b.inputs), - dataInputs = isoArrayToIndexed(isoDataInput).from(b.dataInputs), - outputs = isoArrayToIndexed(isoBoxCandidate).from(b.outputCandidates) - ) - } - } - - val isoBox: Iso[Box[commonMod.Amount], ErgoBox] = new Iso[Box[commonMod.Amount], ErgoBox] { - override def to(x: Box[commonMod.Amount]): ErgoBox = { + val isoBox: Iso[Box[commonMod.Amount, NonMandatoryRegisters], ErgoBox] = new Iso[Box[commonMod.Amount, NonMandatoryRegisters], ErgoBox] { + override def to(x: Box[commonMod.Amount, NonMandatoryRegisters]): ErgoBox = { val ergoBox = new ErgoBox( value = isoAmount.to(x.value), ergoTree = { @@ -386,11 +430,11 @@ object Isos { ergoBox } - override def from(x: ErgoBox): Box[commonMod.Amount] = { + override def from(x: ErgoBox): Box[commonMod.Amount, NonMandatoryRegisters] = { val ergoTree = ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(x.ergoTree) val ergoTreeStr = Base16.encode(ergoTree) val assets = isoTokenArray.from(x.additionalTokens) - Box[commonMod.Amount]( + Box[commonMod.Amount, NonMandatoryRegisters]( boxId = Base16.encode(x.id), ergoTree = ergoTreeStr, value = isoAmount.from(x.value), @@ -406,7 +450,7 @@ object Isos { val isoEIP12UnsignedInput: Iso[inputsMod.EIP12UnsignedInput, ExtendedInputBox] = new Iso[inputsMod.EIP12UnsignedInput, ExtendedInputBox] { override def to(x: inputsMod.EIP12UnsignedInput): ExtendedInputBox = { - val box = Box[commonMod.Amount]( + val box = Box[commonMod.Amount, NonMandatoryRegisters]( boxId = x.boxId, ergoTree = x.ergoTree, value = x.value, @@ -436,4 +480,42 @@ object Isos { ) } } + + val isoUnsignedTransaction: Iso[UnsignedTransaction, UnsignedErgoLikeTransaction] = + new Iso[UnsignedTransaction, UnsignedErgoLikeTransaction] { + override def to(a: UnsignedTransaction): UnsignedErgoLikeTransaction = { + new UnsignedErgoLikeTransaction( + inputs = isoArrayToIndexed(isoUnsignedInput).to(a.inputs), + dataInputs = isoArrayToIndexed(isoDataInput).to(a.dataInputs), + outputCandidates = isoArrayToIndexed(isoBoxCandidate).to(a.outputs), + ) + } + + override def from(b: UnsignedErgoLikeTransaction): UnsignedTransaction = { + UnsignedTransaction( + inputs = isoArrayToIndexed(isoUnsignedInput).from(b.inputs), + dataInputs = isoArrayToIndexed(isoDataInput).from(b.dataInputs), + outputs = isoArrayToIndexed(isoBoxCandidate).from(b.outputCandidates) + ) + } + } + + val isoSignedTransaction: Iso[SignedTransaction, ErgoLikeTransaction] = + new Iso[SignedTransaction, ErgoLikeTransaction] { + override def to(a: SignedTransaction): ErgoLikeTransaction = { + new ErgoLikeTransaction( + inputs = isoArrayToIndexed(isoSignedInput).to(a.inputs), + dataInputs = isoArrayToIndexed(isoDataInput).to(a.dataInputs), + outputCandidates = isoArrayToIndexed(isoBox).to(a.outputs), + ) + } + + override def from(tx: ErgoLikeTransaction): SignedTransaction = { + val inputs = isoArrayToIndexed(isoSignedInput).from(tx.inputs) + val dataInputs = isoArrayToIndexed(isoDataInput).from(tx.dataInputs) + val outputs = isoArrayToIndexed(isoBox).from(tx.outputs) + SignedTransaction(dataInputs, tx.id, inputs, outputs) + } + } + } \ No newline at end of file diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/PreHeader.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/PreHeader.scala index 7e38032446..ae75b67ece 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/PreHeader.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/PreHeader.scala @@ -3,14 +3,25 @@ package org.ergoplatform.sdk.js import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel -/** Equivalent of [[special.sigma.PreHeader]] available from JS. */ +/** Equivalent of [[special.sigma.PreHeader]] available from JS. + * Only header fields that can be predicted by a miner. + */ @JSExportTopLevel("PreHeader") class PreHeader( + /** Block version, to be increased on every soft and hardfork. */ val version: Byte, + /** Hex of id of parent block */ val parentId: String, + /** Block timestamp (in milliseconds since beginning of Unix Epoch) */ val timestamp: js.BigInt, + + /** Current difficulty in a compressed view. + * NOTE: actually it is unsigned integer */ val nBits: js.BigInt, + /** Block height */ val height: Int, - val minerPk: String, + /** Miner public key (hex of EC Point). Should be used to collect block rewards. */ + val minerPk: GroupElement, + /** Hex of miner votes bytes for changing system parameters. */ val votes: String ) extends js.Object diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala index 4dfce27d44..8d3b3cd2e3 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala @@ -1,7 +1,5 @@ package org.ergoplatform.sdk.js -import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix -import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters import org.ergoplatform.sdk import org.ergoplatform.sdk.SecretString @@ -10,10 +8,16 @@ import scala.scalajs.js.annotation.JSExportTopLevel import Isos._ import sigmastate.eval.SigmaDsl -/** Equivalent of [[sdk.ProverBuilder]] available from JS. */ +/** Equivalent of [[sdk.ProverBuilder]] available from JS. + * + * @param parameters Blockchain parameters re-adjustable via miners voting and + * voting-related data. All of them are included into extension + * section of a first block of a voting epoch. + * @param network Network prefix to use for addresses. + */ @JSExportTopLevel("ProverBuilder") -class ProverBuilder(parameters: ErgoLikeParameters, networkPrefix: NetworkPrefix) extends js.Object { - val _builder = new sdk.ProverBuilder(parameters, networkPrefix) +class ProverBuilder(parameters: BlockchainParameters, network: Byte) extends js.Object { + val _builder = new sdk.ProverBuilder(Isos.isoBlockchainParameters.to(parameters), network) /** Configure this builder to use the given seed when building a new prover. * diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ReducedTransaction.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ReducedTransaction.scala new file mode 100644 index 0000000000..66c5ce7f38 --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ReducedTransaction.scala @@ -0,0 +1,22 @@ +package org.ergoplatform.sdk.js + +import org.ergoplatform.sdk + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExportTopLevel + +/** Equivalent of [[sdk.ReducedTransaction]] available from JS. */ +@JSExportTopLevel("ReducedTransaction") +class ReducedTransaction(val _tx: sdk.ReducedTransaction) extends js.Object { + /** Serialized bytes of this transaction in hex format. */ + def toHex: String = _tx.toHex +} + +@JSExportTopLevel("ReducedTransactionObj") +object ReducedTransaction extends js.Object { + /** Creates a [[ReducedTransaction]] from serialized bytes in hex format. */ + def fromHex(hex: String): ReducedTransaction = { + val tx = sdk.ReducedTransaction.fromHex(hex) + new ReducedTransaction(tx) + } +} \ No newline at end of file diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProp.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProp.scala index 0722b68e5a..bc2da23561 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProp.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProp.scala @@ -1,9 +1,24 @@ package org.ergoplatform.sdk.js import sigmastate.Values.SigmaBoolean +import sigmastate.basics.DLogProtocol.ProveDlog + import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel /** Equivalent of [[special.sigma.SigmaProp]] available from JS. */ @JSExportTopLevel("SigmaProp") -class SigmaProp(val sigmaBoolean: SigmaBoolean) extends js.Object +class SigmaProp(val sigmaBoolean: SigmaBoolean) extends js.Object { +} + +@JSExportTopLevel("SigmaPropObj") +object SigmaProp extends js.Object { + /** Creates a new [[SigmaProp]] from the given hex string of public key. + * @param pointHex hex representation of elliptic curve point (ASN.1 encoded) + * @see CryptoFacade.getASN1Encoding, GroupElement.fromPointHex, Point + */ + def fromPointHex(pointHex: String): SigmaProp = { + val point = GroupElement.fromPointHex(pointHex).point + new SigmaProp(ProveDlog(point)) + } +} diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala index 41bf634473..b7ca33b1de 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala @@ -2,6 +2,7 @@ package org.ergoplatform.sdk.js import org.ergoplatform.sdk import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box +import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters import sigmastate.fleetSdkCommon.{distEsmTypesCommonMod => commonMod, distEsmTypesInputsMod => inputsMod, distEsmTypesTokenMod => tokenMod, distEsmTypesTransactionsMod => transactionsMod} import scala.scalajs.js @@ -12,47 +13,58 @@ import scala.scalajs.js.annotation.JSExportTopLevel class SigmaProver(_prover: sdk.SigmaProver) extends js.Object { import Isos._ - //TODO finish implementation + /** Returns the Pay-to-Public-Key (P2PK) address associated with the prover's public key. + * The returned address corresponds to the master secret derived from the mnemonic + * phrase configured in the [[ProverBuilder]]. + */ + def getP2PKAddress: String = { + val addr = _prover.getP2PKAddress + addr.toString + } + + /** Returns the prover's secret key. */ + def getSecretKey: js.BigInt = + isoBigInt.from(_prover.getSecretKey) + + /** Returns a sequence of EIP-3 addresses associated with the prover's secret keys. */ + def getEip3Addresses: js.Array[String] = { + val addresses = _prover.getEip3Addresses + js.Array(addresses.map(_.toString): _*) + } + + /** Reduces the transaction to the reduced form, which is ready to be signed. + * @param stateCtx blockchain state context + * @param unsignedTx unsigned transaction to be reduced (created by Fleet builders) + * @param boxesToSpend boxes to be spent by the transaction + * @param dataInputs data inputs to be used by the transaction + * @param tokensToBurn tokens to be burned by the transaction + * @param baseCost base cost of the transaction + * @return reduced transaction + */ def reduce( stateCtx: BlockchainStateContext, unsignedTx: transactionsMod.UnsignedTransaction, boxesToSpend: js.Array[inputsMod.EIP12UnsignedInput], + dataInputs: js.Array[Box[commonMod.Amount, NonMandatoryRegisters]], + tokensToBurn: js.Array[tokenMod.TokenAmount[commonMod.Amount]], baseCost: Int): ReducedTransaction = { - val tx = sdk.UnreducedTransaction( + val unreducedTx = sdk.UnreducedTransaction( unsignedTx = isoUnsignedTransaction.to(unsignedTx), boxesToSpend = isoArrayToIndexed(isoEIP12UnsignedInput).to(boxesToSpend), - dataInputs = IndexedSeq.empty, - tokensToBurn = IndexedSeq.empty - ) - _prover.reduce( - isoBlockchainStateContext.to(stateCtx), - tx, - baseCost + dataInputs = isoArrayToIndexed(isoBox).to(dataInputs), + tokensToBurn = isoArrayToIndexed(isoToken.andThen(sdk.Iso.isoErgoTokenToPair.inverse)).to(tokensToBurn) ) - new ReducedTransaction + val ctx = isoBlockchainStateContext.to(stateCtx) + val reducedTx = _prover.reduce(ctx, unreducedTx, baseCost) + new ReducedTransaction(reducedTx) } - def reduceTransaction( - unsignedTx: transactionsMod.UnsignedTransaction, - boxesToSpend: js.Array[inputsMod.EIP12UnsignedInput], - dataBoxes: js.Array[Box[commonMod.Amount]], - stateDigest: String, - baseCost: Int, - tokensToBurn: js.Array[tokenMod.TokenAmount[commonMod.Amount]] - ): (ReducedTransaction, Int) = { - val tx = Isos.isoUnsignedTransaction.to(unsignedTx) -// val inputs: = boxesToSpend.map(isoEIP12UnsignedInput.to).toArray - - (new ReducedTransaction, 0) + /** Signs the reduced transaction. + * @param reducedTx reduced transaction to be signed + * @return signed transaction containting all the required proofs (signatures) + */ + def signReduced(reducedTx: ReducedTransaction): transactionsMod.SignedTransaction = { + val signed = _prover.signReduced(reducedTx._tx) + isoSignedTransaction.from(signed.ergoTx) } - -} - -//TODO finish implementation -@JSExportTopLevel("ReducedTransaction") -class ReducedTransaction - -//TODO finish implementation -@JSExportTopLevel("SigmaProverObj") -object SigmaProver { } diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala index b926cc54aa..0c30216dbc 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala @@ -10,6 +10,7 @@ import sigmastate.crypto.Platform import sigmastate.eval.{CAvlTree, CGroupElement, CSigmaProp, Colls, CostingBox, Evaluation, SigmaDsl} import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box import sigmastate.fleetSdkCommon.distEsmTypesCommonMod +import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters import sigmastate.lang.DeserializationSigmaBuilder import sigmastate.serialization.{ConstantSerializer, DataSerializer, SigmaSerializer} import special.collection.{Coll, CollType} @@ -88,8 +89,8 @@ object Value extends js.Object { val v = data.asInstanceOf[js.BigInt] SigmaDsl.BigInt(new BigInteger(v.toString(16), 16)) case special.sigma.GroupElementRType => - val point = data.asInstanceOf[Platform.Point] - SigmaDsl.GroupElement(new Platform.Ecp(point)) + val ge = data.asInstanceOf[GroupElement] + SigmaDsl.GroupElement(ge.point) case special.sigma.SigmaPropRType => val p = data.asInstanceOf[SigmaProp] SigmaDsl.SigmaProp(p.sigmaBoolean) @@ -97,7 +98,7 @@ object Value extends js.Object { val t = data.asInstanceOf[AvlTree] Isos.isoAvlTree.to(t) case special.sigma.BoxRType => - val t = data.asInstanceOf[Box[distEsmTypesCommonMod.Amount]] + val t = data.asInstanceOf[Box[distEsmTypesCommonMod.Amount, NonMandatoryRegisters]] SigmaDsl.Box(Isos.isoBox.to(t)) case ct: CollType[a] => val xs = data.asInstanceOf[js.Array[Any]] @@ -128,8 +129,8 @@ object Value extends js.Object { val hex = SigmaDsl.toBigInteger(value.asInstanceOf[special.sigma.BigInt]).toString(10) js.BigInt(hex) case special.sigma.GroupElementRType => - val point: Platform.Point = value.asInstanceOf[CGroupElement].wrappedValue.asInstanceOf[Platform.Ecp].point - point + val point = value.asInstanceOf[CGroupElement].wrappedValue.asInstanceOf[Platform.Ecp] + new GroupElement(point) case special.sigma.SigmaPropRType => new SigmaProp(value.asInstanceOf[CSigmaProp].wrappedValue) case special.sigma.AvlTreeRType => @@ -163,6 +164,10 @@ object Value extends js.Object { n case special.sigma.BigIntRType => data.asInstanceOf[js.BigInt] + case special.sigma.GroupElementRType => + data.asInstanceOf[GroupElement] + case special.sigma.SigmaPropRType => + data.asInstanceOf[SigmaProp] case PairType(l, r) => data match { case arr: js.Array[Any @unchecked] => checkJsData(arr(0), l) @@ -212,6 +217,22 @@ object Value extends js.Object { new Value(n, Type.BigInt) } + /** Creates a Value of GroupElement type from [[sigmastate.crypto.Platform.Point]] hex. + * @param pointHex hex of ASN representation of [[sigmastate.crypto.Platform.Point]] + */ + def ofGroupElement(pointHex: String): Value = { + val ge = GroupElement.fromPointHex(pointHex) + new Value(ge, Type.GroupElement) + } + + /** Creates a Value of SigmaProp type from [[sigmastate.crypto.Platform.Point]] hex. + * @param pointHex hex of ASN representation of [[sigmastate.crypto.Platform.Point]] + */ + def ofSigmaProp(pointHex: String): Value = { + val sp = SigmaProp.fromPointHex(pointHex) + new Value(sp, Type.SigmaProp) + } + /** Create Pair value from two values. */ def pairOf(l: Value, r: Value): Value = { val data = js.Array(l.data, r.data) // the l and r data have been validated @@ -237,7 +258,9 @@ object Value extends js.Object { * descriptor. * @param hex the string is obtained as hex encoding of serialized ConstantNode. * (The bytes obtained by ConstantSerializer in sigma) - * @return new deserialized ErgoValue instance + * @return new deserialized Value instance containing: + * - suitable JS value in its `data` field + * - and [[Type]] descriptor in its `tpe` field */ def fromHex(hex: String): Value = { val bytes = Base16.decode(hex).fold(t => throw t, identity) diff --git a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala index f8e7962055..47eedf2706 100644 --- a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala +++ b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala @@ -1,18 +1,17 @@ package org.ergoplatform.sdk.js import org.ergoplatform.ErgoBox.{AdditionalRegisters, BoxId, TokenId} -import org.ergoplatform.sdk.{ExtendedInputBox, Iso} import org.ergoplatform._ -import org.ergoplatform.sdk.wallet.protocol.context.{CErgoLikeStateContext, ErgoLikeStateContext} +import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext +import org.ergoplatform.sdk.{ExtendedInputBox, Iso} import org.scalacheck.{Arbitrary, Gen} import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -import scorex.crypto.authds.ADDigest import sigmastate.SType import sigmastate.Values.Constant import sigmastate.eval.Colls -import sigmastate.interpreter.ContextExtension +import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.serialization.generators.ObjectGenerators import special.collection.Coll import special.sigma @@ -27,19 +26,18 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca extension <- contextExtensionGen } yield ExtendedInputBox(box, extension) - lazy val ergoLikeStateContextGen: Gen[ErgoLikeStateContext] = for { + lazy val blockchainStateContextGen: Gen[BlockchainStateContext] = for { stateRoot <- avlTreeGen headers <- headersGen(stateRoot) preHeader <- preHeaderGen(headers.headOption.map(_.id).getOrElse(modifierIdBytesGen.sample.get)) - } yield CErgoLikeStateContext( + } yield BlockchainStateContext( sigmaLastHeaders = Colls.fromItems(headers:_*), previousStateDigest = stateRoot.digest, sigmaPreHeader = preHeader ) def roundtrip[A,B](iso: Iso[A,B])(b: B): Unit = { - val invIso = iso.inverse - invIso.from(invIso.to(b)) shouldBe b + iso.to(iso.from(b)) shouldBe b } override implicit val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration(minSuccessful = 30) @@ -93,7 +91,7 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca } property("Iso.isoBlockchainStateContext") { - forAll(ergoLikeStateContextGen) { (c: ErgoLikeStateContext) => + forAll(blockchainStateContextGen) { (c: BlockchainStateContext) => roundtrip(Isos.isoBlockchainStateContext)(c) } } @@ -104,12 +102,24 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca } } + property("Iso.isoProverResult") { + forAll { (c: ProverResult) => + roundtrip(Isos.isoProverResult)(c) + } + } + property("Iso.isoUnsignedInput") { forAll { (c: UnsignedInput) => roundtrip(Isos.isoUnsignedInput)(c) } } + property("Iso.isoSignedInput") { + forAll { (c: Input) => + roundtrip(Isos.isoSignedInput)(c) + } + } + property("Iso.isoDataInput") { forAll { (c: DataInput) => roundtrip(Isos.isoDataInput)(c) @@ -165,12 +175,6 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca } } - ignore("Iso.isoUnsignedTransaction") { - forAll { (tx: UnsignedErgoLikeTransaction) => - roundtrip(Isos.isoUnsignedTransaction)(tx) - } - } - property("Iso.isoBox") { forAll { (b: ErgoBox) => roundtrip(Isos.isoBox)(b) @@ -183,4 +187,15 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca } } + property("Iso.isoUnsignedTransaction") { + forAll { (tx: UnsignedErgoLikeTransaction) => + roundtrip(Isos.isoUnsignedTransaction)(tx) + } + } + + property("Iso.isoSignedTransaction") { + forAll { (tx: ErgoLikeTransaction) => + roundtrip(Isos.isoSignedTransaction)(tx) + } + } } diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala index 91e684d9ac..dc289ba4e6 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala @@ -5,7 +5,7 @@ import org.ergoplatform._ import org.ergoplatform.sdk.Extensions.{CollOps, PairCollOps} import org.ergoplatform.sdk.JavaHelpers.{TokenColl, UniversalConverter} import org.ergoplatform.sdk.utils.ArithUtils -import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeParameters, ErgoLikeStateContext, TransactionContext} +import org.ergoplatform.sdk.wallet.protocol.context.{BlockchainStateContext, TransactionContext} import org.ergoplatform.sdk.wallet.secrets.ExtendedSecretKey import org.ergoplatform.validation.ValidationRules import scalan.util.Extensions.LongOps @@ -35,7 +35,7 @@ class AppkitProvingInterpreter( val secretKeys: IndexedSeq[ExtendedSecretKey], val dLogInputs: IndexedSeq[DLogProverInput], val dhtInputs: IndexedSeq[DiffieHellmanTupleProverInput], - params: ErgoLikeParameters) + params: BlockchainParameters) extends ReducingInterpreter(params) with ProverInterpreter { override type CTX = ErgoLikeContext @@ -44,7 +44,7 @@ class AppkitProvingInterpreter( /** All secrets available to this interpreter including [[ExtendedSecretKey]], dlog and * dht secrets. */ - override val secrets: Seq[SigmaProtocolPrivateInput[_, _]] = { + override val secrets: Seq[SigmaProtocolPrivateInput[_]] = { val dlogs: IndexedSeq[DLogProverInput] = secretKeys.map(_.privateInput) dlogs ++ dLogInputs ++ dhtInputs } @@ -79,7 +79,7 @@ class AppkitProvingInterpreter( * The returned cost doesn't include `baseCost`. */ def sign(unreducedTx: UnreducedTransaction, - stateContext: ErgoLikeStateContext, + stateContext: BlockchainStateContext, baseCost: Int): Try[SignedTransaction] = Try { val maxCost = params.maxBlockCost var currentCost: Long = baseCost @@ -112,7 +112,7 @@ class AppkitProvingInterpreter( unsignedTx: UnsignedErgoLikeTransaction, boxesToSpend: IndexedSeq[ExtendedInputBox], dataBoxes: IndexedSeq[ErgoBox], - stateContext: ErgoLikeStateContext, + stateContext: BlockchainStateContext, baseCost: Int, tokensToBurn: IndexedSeq[ErgoToken]): ReducedErgoLikeTransaction = { if (unsignedTx.inputs.length != boxesToSpend.length) throw new Exception("Not enough boxes to spend") diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainContext.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainContext.scala new file mode 100644 index 0000000000..d5b6edaf69 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainContext.scala @@ -0,0 +1,22 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext +import special.collection.Coll +import special.sigma.Header + +/** Represents a specific context of blockchain for execution + * of transaction building scenario. + * It contains methods for accessing blockchain data, current blockchain state, + * node information etc. + * An instance of this class can also be used to create new builders + * for creating new transactions and provers (used for transaction signing). + */ +case class BlockchainContext( + networkType: NetworkType, + parameters: BlockchainParameters, + stateContext: BlockchainStateContext +) { + def headers: Coll[Header] = stateContext.sigmaLastHeaders + + def height: Int = headers(0).height +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainParameters.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainParameters.scala new file mode 100644 index 0000000000..4a1014b112 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainParameters.scala @@ -0,0 +1,52 @@ +package org.ergoplatform.sdk + +/** Blockchain parameters re-adjustable via miners voting and voting-related data. + * All these fields are included into extension section of a first block of a voting epoch. + * + * @param storageFeeFactor cost of storing 1 byte in UTXO for four years, in nanoErgs + * @param minValuePerByte cost of a transaction output, in computation unit + * @param maxBlockSize max block size, in bytes + * @param tokenAccessCost cost of a token contained in a transaction, in computation unit + * @param inputCost cost of a transaction input, in computation unit + * @param dataInputCost cost of a transaction data input, in computation unit + * @param outputCost cost of a transaction output, in computation unit + * @param maxBlockCost computation units limit per block + * @param softForkStartingHeight height when voting for a soft-fork had been started + * @param softForkVotesCollected votes for soft-fork collected in previous epochs + * @param blockVersion Protocol version activated on the network + */ +case class BlockchainParameters( + storageFeeFactor: Int, + minValuePerByte: Int, + maxBlockSize: Int, + tokenAccessCost: Int, + inputCost: Int, + dataInputCost: Int, + outputCost: Int, + maxBlockCost: Int, + softForkStartingHeight: Option[Int], + softForkVotesCollected: Option[Int], + blockVersion: Byte +) + +/** Global parameters used by SDK */ +object BlockchainParameters { + /** A number of blocks a miner should wait before he/she can spend block reward. + * This is part of Ergo protocol and cannot be changed. + */ + val MinerRewardDelay_Mainnet = 720 + + val MinerRewardDelay_Testnet = 720 + + /** One Erg is 10^9 NanoErg */ + val OneErg: Long = 1000 * 1000 * 1000 + + /** Minimum transaction fee in NanoErgs as it is defined in Ergo protocol. */ + val MinFee: Long = 1000 * 1000 + + /** Minimum value for a change. It can be used to compute change output value. + * If computed change is less than this value, it is added to the fee + * and `change` output in not added to the transaction. + */ + val MinChangeValue: Long = 1000 * 1000 +} \ No newline at end of file diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/BoxSelectionResult.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BoxSelectionResult.scala new file mode 100644 index 0000000000..d63ae82a44 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BoxSelectionResult.scala @@ -0,0 +1,15 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.ErgoBoxAssets + +/** + * Containter for box selector output + * + * @param inputBoxes - transaction inputs chosen by a selector + * @param changeBoxes - change outputs + * @param payToReemissionBox - pay-to-reemission output mde according to EIP-27, if needed + */ +class BoxSelectionResult[T <: ErgoBoxAssets]( + val inputBoxes: Seq[T], + val changeBoxes: Seq[ErgoBoxAssets], + val payToReemissionBox: Option[ErgoBoxAssets]) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala index 311475c134..05cd83daea 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala @@ -1,6 +1,7 @@ package org.ergoplatform.sdk -import org.ergoplatform.{ErgoBox, UnsignedInput} +import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, UnsignedInput} +import scorex.util.ModifierId import sigmastate.interpreter.ContextExtension /** Input ErgoBox paired with context variables (aka ContextExtensions). @@ -16,4 +17,25 @@ case class ExtendedInputBox( extension: ContextExtension ) { def toUnsignedInput: UnsignedInput = new UnsignedInput(box.id, extension) + def value: Long = box.value } + +case class OutBox(candidate: ErgoBoxCandidate) { + /** + * Converts this box candidate into a new instance of {@link ExtendedInputBox} by + * associating it with the given transaction and output position. + * This method can be used to create input boxed from scratch, without + * retrieving them from the UTXOs. Thus created boxes can be indistinguishable from those + * loaded from blockchain node, and as result can be used to create new transactions. + * This method can also be used to create chains of transactions in advance + * + * @param txId the id of the transaction of which created the box which will be returned + * @param outputIndex zero-based position (index) of the box in the outputs of the transaction. + * @return a new {@link ExtendedInputBox} representing UTXO box as an input of a next transaction. + */ + def convertToInputWith(txId: String, boxIndex: Short): ExtendedInputBox = { + val box = candidate.toBox(ModifierId @@ txId, boxIndex) + ExtendedInputBox(box, ContextExtension.empty) + } +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala index 840ec21576..426090b125 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala @@ -2,8 +2,10 @@ package org.ergoplatform.sdk import debox.cfor import scalan.RType -import scalan.rtypeToClassTag // actually required +import scalan.rtypeToClassTag // actually used +import sigmastate.eval.CPreHeader import special.collection.{Coll, CollBuilder, PairColl} +import special.sigma.{Header, PreHeader} import scala.collection.compat.BuildFrom import scala.collection.{GenIterable, immutable} @@ -196,4 +198,14 @@ object Extensions { builder.pairCollFromArrays(ks, vs) } } + + implicit class HeaderOps(val h: Header) extends AnyVal { + def toPreHeader: PreHeader = { + CPreHeader(h.version, h.parentId, h.timestamp, h.nBits, h.height, h.minerPk, h.votes) + } + } + + implicit class DoubleOps(val i: Double) extends AnyVal { + def erg: Long = (i * 1000000000L).toLong + } } diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/InputBoxesValidator.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/InputBoxesValidator.scala new file mode 100644 index 0000000000..7e3e24a8b7 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/InputBoxesValidator.scala @@ -0,0 +1,127 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.SigmaConstants.MaxBoxSize +import org.ergoplatform.sdk.wallet.Constants.MaxAssetsPerBox +import org.ergoplatform.sdk.wallet.{AssetUtils, TokensMap} +import org.ergoplatform.{ErgoBoxAssets, ErgoBoxAssetsHolder} +import scorex.util.ModifierId + +import scala.collection.mutable + +object BoxSelection { + // from https://github.com/ergoplatform/ergo/blob/2ce78a0380977b8ca354518edca93a5269ac9f53/src/main/scala/org/ergoplatform/settings/Parameters.scala#L258-L258 + private val MinValuePerByteDefault = 30 * 12 + + val MinBoxValue: Long = (MaxBoxSize.value / 2L) * MinValuePerByteDefault + + trait Error { + def message: String + } + + final case class NotEnoughErgsError( + message: String, + balanceFound: Long) extends Error + + final case class NotEnoughTokensError( + message: String, + tokensFound: Map[ModifierId, Long]) extends Error + + final case class NotEnoughCoinsForChangeBoxesError(message: String) extends Error + + /** + * Pass through implementation of the box selector. Unlike DefaultBoxSelector from ergo-wallet, + * it does not select input boxes. We do this in SDK ourselves and do not need the selector + * to interfere with how we built our transaction. Instead, this selector performs validation + * and calculates the necessary change box + */ + class InputBoxesValidator { + + def select[T <: ErgoBoxAssets](inputBoxes: Iterator[T], + externalFilter: T => Boolean, + targetBalance: Long, + targetAssets: TokensMap): Either[Error, BoxSelectionResult[T]] = { + //mutable structures to collect results + val res = mutable.Buffer[T]() + var currentBalance = 0L + val currentAssets = mutable.Map[ModifierId, Long]() + + // select all input boxes - we only validate here + inputBoxes.foreach { box: T => + currentBalance = currentBalance + box.value + AssetUtils.mergeAssetsMut(currentAssets, box.tokens) + res += box + } + + if (currentBalance - targetBalance >= 0) { + //now check if we found all tokens + if (targetAssets.forall { + case (id, targetAmt) => currentAssets.getOrElse(id, 0L) >= targetAmt + }) { + formChangeBoxes(currentBalance, targetBalance, currentAssets, targetAssets) match { + case Right(changeBoxes) => Right(new BoxSelectionResult(res.toSeq, changeBoxes, None)) + case Left(error) => Left(error) + } + } else { + Left(NotEnoughTokensError( + s"Not enough tokens in input boxes to send $targetAssets (found only $currentAssets)", currentAssets.toMap) + ) + } + } else { + Left(NotEnoughErgsError( + s"not enough boxes to meet ERG needs $targetBalance (found only $currentBalance)", currentBalance) + ) + } + } + + /** + * Helper method to construct change outputs + * + * @param foundBalance - ERG balance of boxes collected + * (spendable only, so after possibly deducting re-emission tokens) + * @param targetBalance - ERG amount to be transferred to recipients + * @param foundBoxAssets - assets balances of boxes + * @param targetBoxAssets - assets amounts to be transferred to recipients + * @return + */ + def formChangeBoxes(foundBalance: Long, + targetBalance: Long, + foundBoxAssets: mutable.Map[ModifierId, Long], + targetBoxAssets: TokensMap): Either[Error, Seq[ErgoBoxAssets]] = { + AssetUtils.subtractAssetsMut(foundBoxAssets, targetBoxAssets) + val changeBoxesAssets: Seq[mutable.Map[ModifierId, Long]] = foundBoxAssets.grouped(MaxAssetsPerBox).toSeq + val changeBalance = foundBalance - targetBalance + //at least a minimum amount of ERG should be assigned per a created box + if (changeBoxesAssets.size * MinBoxValue > changeBalance) { + Left(NotEnoughCoinsForChangeBoxesError( + s"Not enough nanoERGs ($changeBalance nanoERG) to create ${changeBoxesAssets.size} change boxes, \nfor $changeBoxesAssets" + )) + } else { + val changeBoxes = if (changeBoxesAssets.nonEmpty) { + val baseChangeBalance = changeBalance / changeBoxesAssets.size + + val changeBoxesNoBalanceAdjusted = changeBoxesAssets.map { a => + ErgoBoxAssetsHolder(baseChangeBalance, a.toMap) + } + + val modifiedBoxOpt = changeBoxesNoBalanceAdjusted.headOption.map { firstBox => + ErgoBoxAssetsHolder( + changeBalance - baseChangeBalance * (changeBoxesAssets.size - 1), + firstBox.tokens + ) + } + + modifiedBoxOpt.toSeq ++ changeBoxesNoBalanceAdjusted.tail + } else if (changeBalance > 0) { + Seq(ErgoBoxAssetsHolder(changeBalance)) + } else { + Seq.empty + } + + Right(changeBoxes) + } + } + + } + +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala index 4a49e23283..52c653e9db 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala @@ -109,21 +109,25 @@ object Iso extends LowPriorityIsos { override def from(t: Token): ErgoToken = new ErgoToken(t._1.toArray, t._2) } - implicit val isoJListErgoTokenToMapPair: Iso[JList[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] = - new Iso[JList[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] { - override def to(a: JList[ErgoToken]): mutable.LinkedHashMap[ModifierId, Long] = { - import JavaHelpers._ + implicit val isoErgoTokenSeqToLinkedMap: Iso[IndexedSeq[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] = + new Iso[IndexedSeq[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] { + override def to(a: IndexedSeq[ErgoToken]): mutable.LinkedHashMap[ModifierId, Long] = { val lhm = new mutable.LinkedHashMap[ModifierId, Long]() - a.convertTo[IndexedSeq[Token]] - .map(t => bytesToId(t._1.toArray) -> t._2) - .foldLeft(lhm)(_ += _) + a.foreach { et => + val t = isoErgoTokenToPair.to(et) + lhm += bytesToId(t._1.toArray) -> t._2 + } + lhm } - override def from(t: mutable.LinkedHashMap[ModifierId, Long]): JList[ErgoToken] = { - import JavaHelpers._ - val pairs: IndexedSeq[Token] = t.toIndexedSeq - .map(t => (Digest32Coll @@ Colls.fromArray(idToBytes(t._1))) -> t._2) - pairs.convertTo[JList[ErgoToken]] + override def from(t: mutable.LinkedHashMap[ModifierId, Long]): IndexedSeq[ErgoToken] = { + val pairs = t.toIndexedSeq + .map { t => + val id = Digest32Coll @@ Colls.fromArray(idToBytes(t._1)) + val value = t._2 + isoErgoTokenToPair.from((id, value)) + } + pairs } } @@ -294,9 +298,6 @@ object JavaHelpers { ErgoAlgos.encode(ErgoAlgos.hash(s)) } - def toPreHeader(h: Header): special.sigma.PreHeader = { - CPreHeader(h.version, h.parentId, h.timestamp, h.nBits, h.height, h.minerPk, h.votes) - } def toSigmaBoolean(ergoTree: ErgoTree): SigmaBoolean = { val prop = ergoTree.toProposition(ergoTree.isConstantSegregation) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala index 42d25e09d4..52e1565ca3 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -1,7 +1,6 @@ package org.ergoplatform.sdk import java.math.BigInteger - import cats.syntax.either._ import io.circe._ import io.circe.syntax._ @@ -19,6 +18,7 @@ import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.{AvlTreeData, AvlTreeFlags, SType} import special.collection.Coll import special.sigma.{AnyValue, Header, PreHeader} + import scala.util.Try import sigmastate.utils.Helpers._ // required for Scala 2.11 import org.ergoplatform.ErgoBox @@ -33,6 +33,8 @@ import org.ergoplatform.ErgoLikeTransactionTemplate import org.ergoplatform.ErgoBoxCandidate import org.ergoplatform.ErgoLikeContext +import scala.collection.mutable + trait JsonCodecs { def fromTry[T](tryResult: Try[T])(implicit cursor: ACursor): Either[DecodingFailure, T] = { @@ -237,14 +239,14 @@ trait JsonCodecs { }) implicit val contextExtensionEncoder: Encoder[ContextExtension] = Encoder.instance({ extension => - extension.values.map { case (key, value) => - key -> evaluatedValueEncoder(value) - }.asJson + Json.obj(extension.values.toSeq.map { case (key, value) => + key.toString -> evaluatedValueEncoder(value) + }: _*) }) implicit val contextExtensionDecoder: Decoder[ContextExtension] = Decoder.instance({ cursor => for { - values <- cursor.as[Map[Byte, EvaluatedValue[SType]]] + values <- cursor.as[mutable.LinkedHashMap[Byte, EvaluatedValue[SType]]] } yield ContextExtension(values) }) @@ -258,8 +260,8 @@ trait JsonCodecs { implicit val proverResultDecoder: Decoder[ProverResult] = Decoder.instance({ cursor => for { proofBytes <- cursor.downField("proofBytes").as[Array[Byte]] - extMap <- cursor.downField("extension").as[Map[Byte, EvaluatedValue[SType]]] - } yield ProverResult(proofBytes, ContextExtension(extMap)) + ext <- cursor.downField("extension").as[ContextExtension] + } yield ProverResult(proofBytes, ext) }) @@ -295,13 +297,17 @@ trait JsonCodecs { decodeErgoTree(_.asInstanceOf[ErgoTree]) } - implicit def registersEncoder[T <: EvaluatedValue[_ <: SType]]: Encoder[Map[NonMandatoryRegisterId, T]] = Encoder.instance({ m => + implicit def registersEncoder[T <: EvaluatedValue[_ <: SType]]: Encoder[scala.collection.Map[NonMandatoryRegisterId, T]] = Encoder.instance({ m => Json.obj( m.toSeq .sortBy(_._1.number) .map { case (k, v) => registerIdEncoder(k) -> evaluatedValueEncoder(v) }: _*) }) + implicit def registersDecoder[T <: EvaluatedValue[_ <: SType]]: Decoder[scala.collection.Map[NonMandatoryRegisterId, T]] = Decoder.instance({ implicit m => + m.as[mutable.LinkedHashMap[NonMandatoryRegisterId, EvaluatedValue[SType]]].asInstanceOf[Decoder.Result[scala.collection.Map[NonMandatoryRegisterId, T]]] + }) + implicit val ergoBoxEncoder: Encoder[ErgoBox] = Encoder.instance({ box => Json.obj( "boxId" -> box.id.asJson, @@ -321,7 +327,7 @@ trait JsonCodecs { ergoTreeBytes <- cursor.downField("ergoTree").as[Array[Byte]] additionalTokens <- cursor.downField("assets").as(Decoder.decodeSeq(assetDecoder)) creationHeight <- cursor.downField("creationHeight").as[Int] - additionalRegisters <- cursor.downField("additionalRegisters").as[Map[NonMandatoryRegisterId, EvaluatedValue[SType]]] + additionalRegisters <- cursor.downField("additionalRegisters").as(registersDecoder) transactionId <- cursor.downField("transactionId").as[ModifierId] index <- cursor.downField("index").as[Short] } yield new ErgoBox( @@ -335,62 +341,86 @@ trait JsonCodecs { ) }) + implicit val ergoBoxCandidateEncoder: Encoder[ErgoBoxCandidate] = Encoder.instance({ box => + Json.obj( + "value" -> box.value.asJson, + "ergoTree" -> ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(box.ergoTree).asJson, + "assets" -> box.additionalTokens.toArray.toSeq.asJson, + "creationHeight" -> box.creationHeight.asJson, + "additionalRegisters" -> box.additionalRegisters.asJson + ) + }) + + implicit val ergoBoxCandidateDecoder: Decoder[ErgoBoxCandidate] = Decoder.instance({ cursor => + for { + value <- cursor.downField("value").as[Long] + ergoTreeBytes <- cursor.downField("ergoTree").as[Array[Byte]] + additionalTokens <- cursor.downField("assets").as(Decoder.decodeSeq(assetDecoder)) + creationHeight <- cursor.downField("creationHeight").as[Int] + additionalRegisters <- cursor.downField("additionalRegisters").as(registersDecoder) + } yield new ErgoBoxCandidate( + value = value, + ergoTree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(ergoTreeBytes), + creationHeight = creationHeight, + additionalTokens = additionalTokens.toColl, + additionalRegisters = additionalRegisters + ) + }) + implicit val ergoLikeTransactionEncoder: Encoder[ErgoLikeTransaction] = Encoder.instance({ tx => Json.obj( + "type" -> "ELT".asJson, // ErgoLikeTransaction "id" -> tx.id.asJson, "inputs" -> tx.inputs.asJson, "dataInputs" -> tx.dataInputs.asJson, - "outputs" -> tx.outputs.asJson + "outputs" -> tx.outputCandidates.asJson ) }) + implicit val ergoLikeTransactionDecoder: Decoder[ErgoLikeTransaction] = Decoder.instance({ implicit cursor => + for { + t <- cursor.downField("type").as[String] + inputs <- {require(t == "ELT"); cursor.downField("inputs").as[IndexedSeq[Input]] } + dataInputs <- cursor.downField("dataInputs").as[IndexedSeq[DataInput]] + outputs <- cursor.downField("outputs").as[IndexedSeq[ErgoBoxCandidate]] + } yield new ErgoLikeTransaction(inputs, dataInputs, outputs) + }) + implicit val unsignedErgoLikeTransactionEncoder: Encoder[UnsignedErgoLikeTransaction] = Encoder.instance({ tx => Json.obj( + "type" -> "UELT".asJson, // UnsignedErgoLikeTransaction "id" -> tx.id.asJson, "inputs" -> tx.inputs.asJson, "dataInputs" -> tx.dataInputs.asJson, - "outputs" -> tx.outputs.asJson + "outputs" -> tx.outputCandidates.asJson ) }) + implicit val unsignedErgoLikeTransactionDecoder: Decoder[UnsignedErgoLikeTransaction] = Decoder.instance({ implicit cursor => + for { + t <- cursor.downField("type").as[String] + inputs <- {require(t == "UELT"); cursor.downField("inputs").as[IndexedSeq[UnsignedInput]] } + dataInputs <- cursor.downField("dataInputs").as[IndexedSeq[DataInput]] + outputs <- cursor.downField("outputs").as[IndexedSeq[ErgoBoxCandidate]] + } yield new UnsignedErgoLikeTransaction(inputs, dataInputs, outputs) + }) + implicit def ergoLikeTransactionTemplateEncoder[T <: UnsignedInput]: Encoder[ErgoLikeTransactionTemplate[T]] = Encoder.instance({ case transaction: ErgoLikeTransaction => ergoLikeTransactionEncoder(transaction) case transaction: UnsignedErgoLikeTransaction => unsignedErgoLikeTransactionEncoder(transaction) case t => throw new SigmaException(s"Don't know how to encode transaction $t") }) - implicit val transactionOutputsDecoder: Decoder[(ErgoBoxCandidate, Option[BoxId])] = Decoder.instance({ cursor => - for { - maybeId <- cursor.downField("boxId").as[Option[BoxId]] - value <- cursor.downField("value").as[Long] - creationHeight <- cursor.downField("creationHeight").as[Int] - ergoTree <- cursor.downField("ergoTree").as[ErgoTree] - assets <- cursor.downField("assets").as[Seq[(ErgoBox.TokenId, Long)]] // TODO optimize: encode directly into Coll avoiding allocation of Tuple2 for each element - registers <- cursor.downField("additionalRegisters").as[Map[NonMandatoryRegisterId, EvaluatedValue[SType]]] - } yield (new ErgoBoxCandidate(value, ergoTree, creationHeight, assets.toColl, registers), maybeId) - }) - - implicit val ergoLikeTransactionDecoder: Decoder[ErgoLikeTransaction] = Decoder.instance({ implicit cursor => - for { - inputs <- cursor.downField("inputs").as[IndexedSeq[Input]] - dataInputs <- cursor.downField("dataInputs").as[IndexedSeq[DataInput]] - outputsWithIndex <- cursor.downField("outputs").as[IndexedSeq[(ErgoBoxCandidate, Option[BoxId])]] - } yield new ErgoLikeTransaction(inputs, dataInputs, outputsWithIndex.map(_._1)) - }) - - implicit val unsignedErgoLikeTransactionDecoder: Decoder[UnsignedErgoLikeTransaction] = Decoder.instance({ implicit cursor => + implicit val ergoLikeTransactionTemplateDecoder: Decoder[ErgoLikeTransactionTemplate[_ <: UnsignedInput]] = Decoder.instance({ implicit cursor => for { - inputs <- cursor.downField("inputs").as[IndexedSeq[UnsignedInput]] - dataInputs <- cursor.downField("dataInputs").as[IndexedSeq[DataInput]] - outputsWithIndex <- cursor.downField("outputs").as[IndexedSeq[(ErgoBoxCandidate, Option[BoxId])]] - } yield new UnsignedErgoLikeTransaction(inputs, dataInputs, outputsWithIndex.map(_._1)) + t <- cursor.downField("type").as[String] + tx <- t match { + case "ELT" => ergoLikeTransactionDecoder(cursor) + case "UELT" => unsignedErgoLikeTransactionDecoder(cursor) + } + } yield tx }) - implicit val ergoLikeTransactionTemplateDecoder: Decoder[ErgoLikeTransactionTemplate[_ <: UnsignedInput]] = { - ergoLikeTransactionDecoder.asInstanceOf[Decoder[ErgoLikeTransactionTemplate[_ <: UnsignedInput]]] or - unsignedErgoLikeTransactionDecoder.asInstanceOf[Decoder[ErgoLikeTransactionTemplate[_ <: UnsignedInput]]] - } - implicit val sigmaValidationSettingsEncoder: Encoder[SigmaValidationSettings] = Encoder.instance({ v => SigmaValidationSettingsSerializer.toBytes(v).asJson }) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/NetworkType.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/NetworkType.scala new file mode 100644 index 0000000000..806ce6b014 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/NetworkType.scala @@ -0,0 +1,49 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.ErgoAddressEncoder +import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix + +/** + * Enumeration of network types as they are defined by Ergo specification of {@link ErgoAddress}. + */ +abstract class NetworkType { + + /** + * The network prefix code used in Ergo addresses + */ + val networkPrefix: NetworkPrefix + + /** + * verbose name for network type as reported by Node API + */ + val verboseName: String +} + +object NetworkType { + /** Mainnet network type. + * + * @see ErgoAddressEncoder#MainnetNetworkPrefix() + */ + case object Mainnet extends NetworkType { + override val networkPrefix: NetworkPrefix = ErgoAddressEncoder.MainnetNetworkPrefix + override val verboseName = "mainnet" + } + + /** Testnet network type. + * + * @see ErgoAddressEncoder#TestnetNetworkPrefix() + */ + case object Testnet extends NetworkType { + override val networkPrefix: NetworkPrefix = ErgoAddressEncoder.TestnetNetworkPrefix + override val verboseName = "testnet" + } + + /** @return network type for given verbose name */ + def fromName(name: String): Option[NetworkType] = name match { + case "mainnet" => Some(Mainnet) + case "testnet" => Some(Testnet) + case _ => None + } + +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/OutBoxBuilder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/OutBoxBuilder.scala new file mode 100644 index 0000000000..5c2f531588 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/OutBoxBuilder.scala @@ -0,0 +1,80 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.ErgoBox.TokenId +import org.ergoplatform.sdk.JavaHelpers.collRType +import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, SigmaConstants} +import scalan.RType +import sigmastate.SType +import sigmastate.Values.{Constant, ErgoTree, EvaluatedValue} +import sigmastate.eval.Colls + +import scala.collection.mutable.ArrayBuffer + +class OutBoxBuilder(val _txB: UnsignedTransactionBuilder) { + private val _ctx = _txB.ctx + private var _value: Long = 0 + private var _contract: ErgoTree = _ + private val _tokens = ArrayBuffer.empty[ErgoToken] + private val _registers = ArrayBuffer.empty[Constant[_]] + private var _creationHeightOpt: Option[Int] = None + + def value(value: Long): this.type = { + _value = value + this + } + + def contract(contract: ErgoTree): this.type = { + _contract = contract + this + } + + def tokens(tokens: ErgoToken*): this.type = { + require(tokens.nonEmpty, "At least one token should be specified") + val maxTokens = SigmaConstants.MaxTokens.value + require(tokens.size <= maxTokens, SigmaConstants.MaxTokens.description + s": $maxTokens") + _tokens ++= tokens + this + } + + def registers(registers: Constant[_]*): this.type = { + require(registers.nonEmpty, "At least one register should be specified") + _registers.clear() + _registers ++= registers + this + } + + def creationHeight(height: Int): OutBoxBuilder = { + _creationHeightOpt = Some(height) + this + } + + def build(): OutBox = { + require(_contract != null, "Contract is not defined") + val ergoBoxCandidate = OutBoxBuilder.createBoxCandidate( + _value, _contract, _tokens.toSeq, _registers.toSeq, + creationHeight = _creationHeightOpt.getOrElse(_txB.ctx.height)) + OutBox(ergoBoxCandidate) + } +} + +object OutBoxBuilder { + def apply(txB: UnsignedTransactionBuilder): OutBoxBuilder = new OutBoxBuilder(txB) + + private[sdk] def createBoxCandidate( + value: Long, tree: ErgoTree, + tokens: Seq[ErgoToken], + registers: Seq[Constant[_]], creationHeight: Int): ErgoBoxCandidate = { + import org.ergoplatform.ErgoBox.nonMandatoryRegisters + val nRegs = registers.length + require(nRegs <= nonMandatoryRegisters.length, + s"Too many additional registers $nRegs. Max allowed ${nonMandatoryRegisters.length}") + implicit val TokenIdRType: RType[TokenId] = collRType(RType.ByteType).asInstanceOf[RType[TokenId]] + val ts = Colls.fromItems(tokens.map(Iso.isoErgoTokenToPair.to(_)): _*) + val rs = registers.zipWithIndex.map { case (c, i) => + val id = ErgoBox.nonMandatoryRegisters(i) + id -> c.asInstanceOf[EvaluatedValue[_ <: SType]] + }.toMap + new ErgoBoxCandidate(value, tree, creationHeight, ts, rs) + } +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala index 2bdfe48fb1..0704cbe8d6 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala @@ -1,7 +1,6 @@ package org.ergoplatform.sdk -import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix -import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters +import org.ergoplatform.ErgoAddressEncoder.{MainnetNetworkPrefix, NetworkPrefix} import org.ergoplatform.sdk.wallet.secrets.ExtendedSecretKey import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.basics.{DLogProtocol, DiffieHellmanTupleProverInput} @@ -10,8 +9,14 @@ import special.sigma.GroupElement import java.math.BigInteger import scala.collection.mutable.ArrayBuffer -/** A builder class for constructing a `Prover` with specified secrets. */ -class ProverBuilder(parameters: ErgoLikeParameters, networkPrefix: NetworkPrefix) { +/** A builder class for constructing a `Prover` with specified secrets. + * + * @param parameters Blockchain parameters re-adjustable via miners voting and + * voting-related data. All of them are included into extension + * section of a first block of a voting epoch. + * @param networkPrefix Network prefix to use for addresses. + */ +class ProverBuilder(parameters: BlockchainParameters, networkPrefix: NetworkPrefix) { private var _masterKey: Option[ExtendedSecretKey] = None /** Generated EIP-3 secret keys paired with their derivation path index. */ @@ -27,7 +32,7 @@ class ProverBuilder(parameters: ErgoLikeParameters, networkPrefix: NetworkPrefix def withMnemonic( mnemonicPhrase: SecretString, mnemonicPass: SecretString, - usePre1627KeyDerivation: Boolean + usePre1627KeyDerivation: Boolean = false ): ProverBuilder = { _masterKey = Some(JavaHelpers.seedToMasterKey(mnemonicPhrase, mnemonicPass, usePre1627KeyDerivation)) this @@ -89,3 +94,8 @@ class ProverBuilder(parameters: ErgoLikeParameters, networkPrefix: NetworkPrefix } } +object ProverBuilder { + def forMainnet(parameters: BlockchainParameters): ProverBuilder = + new ProverBuilder(parameters, MainnetNetworkPrefix) +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala index b7d8547e32..f06c37fd39 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala @@ -3,7 +3,7 @@ package org.ergoplatform.sdk import org.ergoplatform.sdk.Extensions.{CollOps, PairCollOps} import org.ergoplatform.sdk.JavaHelpers.UniversalConverter import org.ergoplatform.sdk.utils.ArithUtils -import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeParameters, ErgoLikeStateContext, TransactionContext} +import org.ergoplatform.sdk.wallet.protocol.context.{BlockchainStateContext, TransactionContext} import org.ergoplatform.validation.ValidationRules import org.ergoplatform.{ErgoLikeContext, ErgoLikeInterpreter} import scalan.util.Extensions.LongOps @@ -20,7 +20,7 @@ import java.util.{Objects, List => JList} import scala.collection.mutable /** Interpreter that can reduce transactions with given chain parameters. */ -class ReducingInterpreter(params: ErgoLikeParameters) extends ErgoLikeInterpreter { +class ReducingInterpreter(params: BlockchainParameters) extends ErgoLikeInterpreter { override type CTX = ErgoLikeContext import org.ergoplatform.sdk.Iso._ @@ -56,7 +56,7 @@ class ReducingInterpreter(params: ErgoLikeParameters) extends ErgoLikeInterprete */ def reduceTransaction( unreducedTx: UnreducedTransaction, - stateContext: ErgoLikeStateContext, + stateContext: BlockchainStateContext, baseCost: Int ): ReducedTransaction = { val unsignedTx = unreducedTx.unsignedTx diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala index 255534e14b..8090627083 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala @@ -2,7 +2,7 @@ package org.ergoplatform.sdk import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix import org.ergoplatform._ -import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeStateContext +import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext import sigmastate.eval.{CostingSigmaDslBuilder, SigmaDsl} import sigmastate.interpreter.HintsBag import sigmastate.utils.Helpers.TryOps @@ -16,7 +16,10 @@ import special.sigma.{BigInt, SigmaProp} class SigmaProver(_prover: AppkitProvingInterpreter, networkPrefix: NetworkPrefix) { implicit val ergoAddressEncoder: ErgoAddressEncoder = ErgoAddressEncoder(networkPrefix) - /** Returns the Pay-to-Public-Key (P2PK) address associated with the prover's public key. */ + /** Returns the Pay-to-Public-Key (P2PK) address associated with the prover's public key. + * The returned address corresponds to the master secret derived from the mnemonic + * phrase configured in the [[ProverBuilder]]. + */ def getP2PKAddress: P2PKAddress = { val pk = _prover.pubKeys(0) P2PKAddress(pk) @@ -37,16 +40,16 @@ class SigmaProver(_prover: AppkitProvingInterpreter, networkPrefix: NetworkPrefi addresses } - /** Signs a given `UnreducedTransaction` using the prover's secret keys and the provided `ErgoLikeStateContext`. + /** Signs a given `UnreducedTransaction` using the prover's secret keys and the provided [[BlockchainStateContext]]. * Uses baseCost == 0. */ - def sign(stateCtx: ErgoLikeStateContext, tx: UnreducedTransaction): SignedTransaction = + def sign(stateCtx: BlockchainStateContext, tx: UnreducedTransaction): SignedTransaction = sign(stateCtx, tx, baseCost = 0) - /** Signs a given `UnreducedTransaction` using the prover's secret keys and the provided `ErgoLikeStateContext`. + /** Signs a given `UnreducedTransaction` using the prover's secret keys and the provided [[BlockchainStateContext]]. * Uses the given baseCost. */ - def sign(stateCtx: ErgoLikeStateContext, tx: UnreducedTransaction, baseCost: Int): SignedTransaction = { + def sign(stateCtx: BlockchainStateContext, tx: UnreducedTransaction, baseCost: Int): SignedTransaction = { val signed = _prover .sign(tx, stateContext = stateCtx, baseCost = baseCost) .getOrThrow @@ -65,9 +68,9 @@ class SigmaProver(_prover: AppkitProvingInterpreter, networkPrefix: NetworkPrefi } /** Reduces a given `UnreducedTransaction` using the prover's secret keys and the - * provided `ErgoLikeStateContext` with a base cost. + * provided [[BlockchainStateContext]] with a base cost. */ - def reduce(stateCtx: ErgoLikeStateContext, tx: UnreducedTransaction, baseCost: Int): ReducedTransaction = { + def reduce(stateCtx: BlockchainStateContext, tx: UnreducedTransaction, baseCost: Int): ReducedTransaction = { val reduced = _prover.reduceTransaction( unreducedTx = tx, stateContext = stateCtx, baseCost = baseCost) reduced diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Transactions.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Transactions.scala index 8073b77c9e..275814a983 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Transactions.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Transactions.scala @@ -1,6 +1,9 @@ package org.ergoplatform.sdk +import org.ergoplatform.sdk.JavaHelpers.StringExtensions import org.ergoplatform.{ErgoBox, ErgoLikeTransaction, UnsignedErgoLikeTransaction} +import sigmastate.eval.Extensions.ArrayByteOps +import sigmastate.serialization.SigmaSerializer import java.util @@ -45,7 +48,23 @@ case class UnreducedTransaction( } /** Represents results for transaction reduction by [[ReducingInterpreter]]. */ -case class ReducedTransaction(ergoTx: ReducedErgoLikeTransaction) +case class ReducedTransaction(ergoTx: ReducedErgoLikeTransaction) { + /** Serialized bytes of this transaction in hex format. */ + def toHex: String = { + val w = SigmaSerializer.startWriter() + ReducedErgoLikeTransactionSerializer.serialize(ergoTx, w) + w.toBytes.toHex + } +} + +object ReducedTransaction { + /** Creates a [[ReducedTransaction]] from serialized bytes in hex format. */ + def fromHex(hex: String): ReducedTransaction = { + val r = SigmaSerializer.startReader(hex.toBytes) + val tx = ReducedErgoLikeTransactionSerializer.parse(r) + ReducedTransaction(tx) + } +} /** Represents results for transaction signing by a prover like [[SigmaProver]]. */ case class SignedTransaction(ergoTx: ErgoLikeTransaction, cost: Int) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/UnsignedTransactionBuilder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/UnsignedTransactionBuilder.scala new file mode 100644 index 0000000000..e48f26fd6f --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/UnsignedTransactionBuilder.scala @@ -0,0 +1,240 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.ErgoBox.TokenId +import org.ergoplatform._ +import org.ergoplatform.sdk.BlockchainParameters.MinChangeValue +import org.ergoplatform.sdk.BoxSelection.InputBoxesValidator +import org.ergoplatform.sdk.Extensions.HeaderOps +import org.ergoplatform.sdk.wallet.{AssetUtils, TokensMap} +import scorex.util.{ModifierId, bytesToId} +import sigmastate.eval.Extensions.ArrayOps +import sigmastate.utils.Extensions.ModifierIdOps +import special.collection.Coll +import special.collection.Extensions.CollBytesOps +import special.sigma.PreHeader + +import scala.collection.mutable.ArrayBuffer +import scala.util.Try + +class UnsignedTransactionBuilder(val ctx: BlockchainContext) { + private[sdk] val _inputs: ArrayBuffer[ExtendedInputBox] = ArrayBuffer.empty[ExtendedInputBox] + private[sdk] val _outputs: ArrayBuffer[OutBox] = ArrayBuffer.empty[OutBox] + private[sdk] val _dataInputs: ArrayBuffer[ErgoBox] = ArrayBuffer.empty[ErgoBox] + + private var _tokensToBurn: Option[ArrayBuffer[ErgoToken]] = None + private var _feeAmount: Option[Long] = None + private var _changeAddress: Option[ErgoAddress] = None + private var _ph: Option[PreHeader] = None + + def preHeader(ph: PreHeader): this.type = { + require(_ph.isEmpty, "PreHeader is already specified") + _ph = Some(ph) + this + } + + def addInputs(boxes: ExtendedInputBox*): this.type = { + _inputs ++= boxes + this + } + + def addDataInputs(boxes: ErgoBox*): this.type = { + _dataInputs ++= boxes + this + } + + def addOutputs(outBoxes: OutBox*): this.type = { + _outputs ++= outBoxes + this + } + + def fee(feeAmount: Long): this.type = { + require(_feeAmount.isEmpty, "Fee already defined") + _feeAmount = Some(feeAmount) + this + } + + def addTokensToBurn(tokens: ErgoToken*): this.type = { + if (_tokensToBurn.isEmpty) + _tokensToBurn = Some(ArrayBuffer.empty[ErgoToken]) + + _tokensToBurn.get ++= tokens + this + } + + def sendChangeTo(changeAddress: ErgoAddress): this.type = { + require(_changeAddress.isEmpty, "Change address is already specified") + _changeAddress = Some(changeAddress) + this + } + + private def getDefined[T](opt: Option[T], msg: => String): T = { + opt match { + case Some(x) => x + case _ => + throw new IllegalArgumentException("requirement failed: " + msg) + } + } + + def build(): UnreducedTransaction = { + val boxesToSpend = _inputs.toIndexedSeq + val outputCandidates = _outputs.map(c => c.candidate).toIndexedSeq + require(!outputCandidates.isEmpty, "Output boxes are not specified") + + val dataInputBoxes = _dataInputs.toIndexedSeq + val dataInputs = _dataInputs.map(box => DataInput(box.id)).toIndexedSeq + require(_feeAmount.isEmpty || _feeAmount.get >= BlockchainParameters.MinFee, + s"When fee amount is defined it should be >= ${BlockchainParameters.MinFee}, got ${_feeAmount.get}") + val changeAddress = getDefined(_changeAddress, "Change address is not defined") + val inputBoxesSeq = boxesToSpend.map(eb => eb.box) + val requestedToBurn = _tokensToBurn.fold(IndexedSeq.empty[ErgoToken])(_.toIndexedSeq) + val burnTokens = Iso.isoErgoTokenSeqToLinkedMap.to(requestedToBurn).toMap + val rewardDelay = ctx.networkType match { + case NetworkType.Mainnet => BlockchainParameters.MinerRewardDelay_Mainnet + case NetworkType.Testnet => BlockchainParameters.MinerRewardDelay_Testnet + } + val tx = UnsignedTransactionBuilder.buildUnsignedTx( + inputs = inputBoxesSeq, dataInputs = dataInputs, outputCandidates = outputCandidates, + currentHeight = ctx.height, createFeeOutput = _feeAmount, + changeAddress = changeAddress, minChangeValue = MinChangeValue, + minerRewardDelay = rewardDelay, + burnTokens = burnTokens).get + + // the method above don't accept ContextExtension along with inputs, thus, after the + // transaction has been built we need to zip with the extensions that have been + // attached to the inputBoxes + val txWithExtensions = new UnsignedErgoLikeTransaction( + inputs = boxesToSpend.map(_.toUnsignedInput), + tx.dataInputs, tx.outputCandidates + ) + UnreducedTransaction(txWithExtensions, boxesToSpend, dataInputBoxes, requestedToBurn) + } + + def preHeader: PreHeader = _ph.getOrElse(ctx.headers(0).toPreHeader) + + def outBoxBuilder: OutBoxBuilder = OutBoxBuilder(this) + + def networkType: NetworkType = ctx.networkType + + def inputBoxes: IndexedSeq[ExtendedInputBox] = _inputs.toIndexedSeq + + def outputBoxes: IndexedSeq[OutBox] = _outputs.toIndexedSeq +} + +object UnsignedTransactionBuilder { + def apply(ctx: BlockchainContext): UnsignedTransactionBuilder = new UnsignedTransactionBuilder(ctx) + + private def validateStatelessChecks( + inputs: IndexedSeq[ErgoBox], dataInputs: IndexedSeq[DataInput], + outputCandidates: Seq[ErgoBoxCandidate]): Unit = { + // checks from ErgoTransaction.validateStateless + require(inputs.nonEmpty, "inputs cannot be empty") + require(outputCandidates.nonEmpty, "outputCandidates cannot be empty") + require(inputs.size <= Short.MaxValue, s"too many inputs - ${inputs.size} (max ${Short.MaxValue})") + require(dataInputs.size <= Short.MaxValue, s"too many dataInputs - ${dataInputs.size} (max ${Short.MaxValue})") + require(outputCandidates.size <= Short.MaxValue, + s"too many outputCandidates - ${outputCandidates.size} (max ${Short.MaxValue})") + require(outputCandidates.forall(_.value >= 0), s"outputCandidate.value must be >= 0") + val outputSumTry = Try(outputCandidates.map(_.value).reduce(java7.compat.Math.addExact(_, _))) + require(outputSumTry.isSuccess, s"Sum of transaction output values should not exceed ${Long.MaxValue}") + require(inputs.distinct.size == inputs.size, s"There should be no duplicate inputs") + } + + def collectOutputTokens(outputCandidates: Seq[ErgoBoxCandidate]): TokensMap = { + AssetUtils.mergeAssets( + initialMap = Map.empty[ModifierId, Long], + maps = outputCandidates.map(b => collTokensToMap(b.additionalTokens)): _*) + } + + def collTokensToMap(tokens: Coll[(TokenId, Long)]): TokensMap = + tokens.toArray.map(t => t._1.toModifierId -> t._2).toMap + + def tokensMapToColl(tokens: TokensMap): Coll[(TokenId, Long)] = + tokens.toArray.map { t => t._1.toTokenId -> t._2 }.toColl + + /** Creates unsigned transaction from given inputs and outputs adding outputs with miner's fee and change + * Runs required checks ensuring that resulted transaction will be successfully validated by a node. + * + * @param inputs - input boxes + * @param dataInputs - data inputs + * @param outputCandidates - output candidate boxes + * @param currentHeight - current height (used in miner's fee box and change box) + * @param createFeeOutput - optional fee amount to put in a new miner's fee box, which will be + * created by this method. If None, then feeOut is not created. + * @param changeAddress - address where to send change from the input boxes + * @param minChangeValue - minimum change value to send, otherwise add to miner's fee + * @param minerRewardDelay - reward delay to encode in miner's fee box + * @return unsigned transaction + */ + def buildUnsignedTx( + inputs: IndexedSeq[ErgoBox], + dataInputs: IndexedSeq[DataInput], + outputCandidates: Seq[ErgoBoxCandidate], + currentHeight: Int, + createFeeOutput: Option[Long], + changeAddress: ErgoAddress, + minChangeValue: Long, + minerRewardDelay: Int, + burnTokens: TokensMap = Map.empty + ): Try[UnsignedErgoLikeTransaction] = Try { + validateStatelessChecks(inputs, dataInputs, outputCandidates) + + // TODO: implement all appropriate checks from ErgoTransaction.validateStatefull + val feeAmount = createFeeOutput.getOrElse(0L) + require(createFeeOutput.fold(true)(_ > 0), s"expected fee amount > 0, got $feeAmount") + val inputTotal = inputs.map(_.value).sum + val outputSum = outputCandidates.map(_.value).sum + val outputTotal = outputSum + feeAmount + val changeAmt = inputTotal - outputTotal + require(changeAmt >= 0, s"total inputs $inputTotal is less then total outputs $outputTotal") + val firstInputBoxId = bytesToId(inputs(0).id) + val tokensOut = collectOutputTokens(outputCandidates) + // remove minted tokens if any + val tokensOutNoMinted = tokensOut.filterKeys(_ != firstInputBoxId) + val mintedTokensNum = tokensOut.size - tokensOutNoMinted.size + require(mintedTokensNum <= 1, s"Only one token can be minted, but found $mintedTokensNum") + require(burnTokens.values.forall(_ > 0), + s"Incorrect burnTokens specification, positive values are expected: $burnTokens") + // add burnTokens to target assets so that they are excluded from the change outputs + // thus total outputs assets will be reduced which is interpreted as _token burning_ + val tokensOutWithBurned = AssetUtils.mergeAssets(tokensOutNoMinted.toMap, burnTokens) + val boxSelector = new InputBoxesValidator + val selection = boxSelector.select[ErgoBox](inputs.iterator, _ => true, outputTotal, tokensOutWithBurned) match { + case Left(err) => throw new IllegalArgumentException( + s"failed to calculate change for outputTotal: $outputTotal, \ntokens: $tokensOut, \nburnTokens: $burnTokens, \ninputs: $inputs, \nreason: $err") + case Right(v) => v + } + // although we're only interested in change boxes, make sure selection contains exact inputs + assert(selection.inputBoxes == inputs, s"unexpected selected boxes, expected: $inputs, got ${selection.inputBoxes}") + val changeBoxes = selection.changeBoxes + val changeBoxesHaveTokens = changeBoxes.exists(_.tokens.nonEmpty) + val changeGoesToFee = changeAmt < minChangeValue && !changeBoxesHaveTokens + require(!changeGoesToFee || (changeAmt == 0 || createFeeOutput.isDefined), + s"""When change=$changeAmt < minChangeValue=$minChangeValue it is added to miner's fee, + |in this case createFeeOutput should be defined + |""".stripMargin) + val feeOutOpt = createFeeOutput.map { fee => + // if computed changeAmt is too small give it to miner as tips + val actualFee = if (changeGoesToFee) fee + changeAmt else fee + new ErgoBoxCandidate( + actualFee, + ErgoTreePredef.feeProposition(minerRewardDelay), + currentHeight + ) + } + val addedChangeOut = if (!changeGoesToFee) { + val script = changeAddress.script + changeBoxes.map { cb => + new ErgoBoxCandidate(cb.value, script, currentHeight, tokensMapToColl(cb.tokens)) + } + } else { + Seq() + } + val finalOutputCandidates = outputCandidates ++ feeOutOpt ++ addedChangeOut + new UnsignedErgoLikeTransaction( + inputs.map(b => new UnsignedInput(b.id)), + dataInputs, + finalOutputCandidates.toIndexedSeq + ) + } +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/BlockchainStateContext.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/BlockchainStateContext.scala new file mode 100644 index 0000000000..5c8d97df4f --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/BlockchainStateContext.scala @@ -0,0 +1,15 @@ +package org.ergoplatform.sdk.wallet.protocol.context + +import special.collection.Coll + +/** Blockchain context used in tx signing. + * + * @param sigmaLastHeaders fixed number (10 in Ergo) of last block headers + * @param previousStateDigest UTXO set digest from a last header (of sigmaLastHeaders) + * @param sigmaPreHeader returns pre-header (header without certain fields) of the current block + */ +case class BlockchainStateContext( + sigmaLastHeaders: Coll[special.sigma.Header], + previousStateDigest: Coll[Byte], + sigmaPreHeader: special.sigma.PreHeader +) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeParameters.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeParameters.scala deleted file mode 100644 index b84387e2b3..0000000000 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeParameters.scala +++ /dev/null @@ -1,63 +0,0 @@ -package org.ergoplatform.sdk.wallet.protocol.context - -/** - * Blockchain parameters readjustable via miners voting and voting-related data. - * All these fields are included into extension section of a first block of a voting epoch. - */ -trait ErgoLikeParameters { - - /** - * @return cost of storing 1 byte in UTXO for four years, in nanoErgs - */ - def storageFeeFactor: Int - - /** - * @return cost of a transaction output, in computation unit - */ - def minValuePerByte: Int - - /** - * @return max block size, in bytes - */ - def maxBlockSize: Int - - /** - * @return cost of a token contained in a transaction, in computation unit - */ - def tokenAccessCost: Int - - /** - * @return cost of a transaction input, in computation unit - */ - def inputCost: Int - - /** - * @return cost of a transaction data input, in computation unit - */ - def dataInputCost: Int - - /** - * @return cost of a transaction output, in computation unit - */ - def outputCost: Int - - /** - * @return computation units limit per block - */ - def maxBlockCost: Int - - /** - * @return height when voting for a soft-fork had been started - */ - def softForkStartingHeight: Option[Int] - - /** - * @return votes for soft-fork collected in previous epochs - */ - def softForkVotesCollected: Option[Int] - - /** - * @return Protocol version - */ - def blockVersion: Byte -} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeStateContext.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeStateContext.scala deleted file mode 100644 index b150c83218..0000000000 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeStateContext.scala +++ /dev/null @@ -1,41 +0,0 @@ -package org.ergoplatform.sdk.wallet.protocol.context - -import scorex.crypto.authds.ADDigest -import special.collection.Coll - -import java.util - -/** - * Blockchain context used in transaction validation. - */ -trait ErgoLikeStateContext { - - /** - * @return fixed number (10 in Ergo) of last block headers - */ - def sigmaLastHeaders: Coll[special.sigma.Header] - - // todo remove from ErgoLikeContext and from ErgoStateContext - /** - * @return UTXO set digest from a last header (of sigmaLastHeaders) - */ - def previousStateDigest: Coll[Byte] - - /** - * @return returns pre-header (header without certain fields) of the current block - */ - def sigmaPreHeader: special.sigma.PreHeader -} - -/** Representis the Ergo-like state context for tx signing. - * - * @param sigmaLastHeaders the last headers of the Sigma blockchain - * @param previousStateDigest the bytes representing the previous state digest - * @param sigmaPreHeader the pre-header object - */ -case class CErgoLikeStateContext( - sigmaLastHeaders: Coll[special.sigma.Header], - previousStateDigest: Coll[Byte], - sigmaPreHeader: special.sigma.PreHeader -) extends ErgoLikeStateContext { -} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/SecretKey.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/SecretKey.scala index 0d6d54d719..73ce7d39ea 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/SecretKey.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/SecretKey.scala @@ -10,7 +10,7 @@ trait SecretKey { /** * Private (secret) input of a sigma protocol */ - def privateInput: SigmaProtocolPrivateInput[_, _] + def privateInput: SigmaProtocolPrivateInput[_] } /** @@ -19,7 +19,7 @@ trait SecretKey { sealed trait PrimitiveSecretKey extends SecretKey object PrimitiveSecretKey { - def apply(sigmaPrivateInput: SigmaProtocolPrivateInput[_, _]): PrimitiveSecretKey = sigmaPrivateInput match { + def apply(sigmaPrivateInput: SigmaProtocolPrivateInput[_]): PrimitiveSecretKey = sigmaPrivateInput match { case dls: DLogProverInput => DlogSecretKey(dls) case dhts: DiffieHellmanTupleProverInput => DhtSecretKey(dhts) } diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala index 3dfb5c4157..72c75ace73 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala @@ -91,7 +91,7 @@ class ExtensionsSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matc val left = builder.pairColl(leftKeys, leftValues) val right = builder.pairColl(rightKeys, rightValues) val res = builder.outerJoin(left, right)(l => l._2 - 2, r => r._2 - 3, i => i._2._1 + 5) - val (ks, vs) = builder.unzip(res) + val (_, vs) = builder.unzip(res) vs.sum shouldBe (col.sum * 2 + col.map(_ + 5).sum) } // test(builder.fromItems(0)) diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala index e651a542b8..e85757985e 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala @@ -16,17 +16,12 @@ import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.eval.Digest32Coll import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.serialization.SerializationSpecification -import sigmastate.utils.Helpers._ +import sigmastate.utils.Helpers.DecoderResultOps // required for Scala 2.11 (extension method toTry) import special.collection.Coll import special.sigma.{Header, PreHeader} -import org.ergoplatform.ErgoLikeContext -import org.ergoplatform.DataInput -import org.ergoplatform.Input -import org.ergoplatform.UnsignedInput -import org.ergoplatform.ErgoBox -import org.ergoplatform.ErgoLikeTransaction -import org.ergoplatform.UnsignedErgoLikeTransaction -import org.ergoplatform.ErgoLikeTransactionTemplate +import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, ErgoLikeContext, ErgoLikeTransaction, ErgoLikeTransactionTemplate, Input, UnsignedErgoLikeTransaction, UnsignedInput} + +import scala.collection.mutable class JsonSerializationSpec extends SerializationSpecification with JsonCodecs { @@ -36,7 +31,7 @@ class JsonSerializationSpec extends SerializationSpecification with JsonCodecs { } property("ErgoLikeContext should be encoded into JSON and decoded back correctly") { - forAll(ergoLikeContextGen) { v: ErgoLikeContext => jsonRoundTrip(v) } + forAll(ergoLikeContextGen, MinSuccessful(50)) { v: ErgoLikeContext => jsonRoundTrip(v) } } property("sigma.BigInt should be encoded into JSON and decoded back correctly") { @@ -84,43 +79,49 @@ class JsonSerializationSpec extends SerializationSpecification with JsonCodecs { } property("Input should be encoded into JSON and decoded back correctly") { - forAll(inputGen) { v: Input => jsonRoundTrip(v) } + forAll(inputGen, MinSuccessful(50)) { v: Input => jsonRoundTrip(v) } } property("UnsignedInput should be encoded into JSON and decoded back correctly") { - forAll(unsignedInputGen) { v: UnsignedInput => jsonRoundTrip(v) } + forAll(unsignedInputGen, MinSuccessful(50)) { v: UnsignedInput => jsonRoundTrip(v) } } property("ContextExtension should be encoded into JSON and decoded back correctly") { - forAll(contextExtensionGen) { v: ContextExtension => jsonRoundTrip(v) } + forAll(contextExtensionGen, MinSuccessful(500)) { v: ContextExtension => jsonRoundTrip(v) } + } + + property("AdditionalRegisters should be encoded into JSON and decoded back correctly") { + forAll(additionalRegistersGen, MinSuccessful(500)) { regs => + jsonRoundTrip(regs)(registersEncoder, registersDecoder) + } } property("ProverResult should be encoded into JSON and decoded back correctly") { - forAll(serializedProverResultGen) { v: ProverResult => jsonRoundTrip(v) } + forAll(serializedProverResultGen, MinSuccessful(500)) { v: ProverResult => jsonRoundTrip(v) } } property("AvlTreeData should be encoded into JSON and decoded back correctly") { - forAll(avlTreeDataGen) { v: AvlTreeData => jsonRoundTrip(v) } + forAll(avlTreeDataGen, MinSuccessful(500)) { v: AvlTreeData => jsonRoundTrip(v) } } property("ErgoTree should be encoded into JSON and decoded back correctly") { - forAll(ergoTreeGen) { v: ErgoTree => jsonRoundTrip(v) } + forAll(ergoTreeGen, MinSuccessful(500)) { v: ErgoTree => jsonRoundTrip(v) } } property("ErgoBox should be encoded into JSON and decoded back correctly") { - forAll(ergoBoxGen) { v: ErgoBox => jsonRoundTrip(v) } + forAll(ergoBoxGen, MinSuccessful(500)) { v: ErgoBox => jsonRoundTrip(v) } } property("ErgoLikeTransaction should be encoded into JSON and decoded back correctly") { - forAll(ergoLikeTransactionGen) { v: ErgoLikeTransaction => jsonRoundTrip(v) } + forAll(ergoLikeTransactionGen, MinSuccessful(50)) { v: ErgoLikeTransaction => jsonRoundTrip(v) } } property("UnsignedErgoLikeTransaction should be encoded into JSON and decoded back correctly") { - forAll(unsignedErgoLikeTransactionGen) { v: UnsignedErgoLikeTransaction => jsonRoundTrip(v) } + forAll(unsignedErgoLikeTransactionGen, MinSuccessful(50)) { v: UnsignedErgoLikeTransaction => jsonRoundTrip(v) } } property("ErgoLikeTransactionTemplate should be encoded into JSON and decoded back correctly") { - forAll(ergoLikeTransactionTemplateGen) { v: ErgoLikeTransactionTemplate[_ <: UnsignedInput] => + forAll(ergoLikeTransactionTemplateGen, MinSuccessful(50)) { v: ErgoLikeTransactionTemplate[_ <: UnsignedInput] => v.asJson.as(ergoLikeTransactionTemplateDecoder).toTry.get shouldEqual v } } @@ -136,7 +137,7 @@ class JsonSerializationSpec extends SerializationSpecification with JsonCodecs { CryptoConstants.dlogGroup.ctx.decodePoint(point).asInstanceOf[CryptoConstants.EcPointType] ) }.get - val regs = Map( + val regs = scala.collection.Map( R7 -> LongArrayConstant(Array(1L, 2L, 1234123L)), R4 -> ByteConstant(1), R6 -> IntConstant(10), diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/utils/Generators.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/utils/Generators.scala index e0672d4a3d..e4cc09c4a2 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/utils/Generators.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/utils/Generators.scala @@ -17,7 +17,6 @@ import sigmastate.eval.Extensions._ import scorex.util.{ModifierId, bytesToId} import sigmastate.eval._ import sigmastate.helpers.TestingHelpers._ -import scorex.crypto.hash.Digest32 import sigmastate.crypto.CryptoFacade trait Generators { diff --git a/sigma-js/package-lock.json b/sigma-js/package-lock.json index 48475c6a2a..153254dd3e 100644 --- a/sigma-js/package-lock.json +++ b/sigma-js/package-lock.json @@ -9,9 +9,9 @@ "version": "0.2.2", "license": "MIT", "dependencies": { - "@fleet-sdk/common": "0.1.0-alpha.14", + "@fleet-sdk/common": "0.1.3", "@noble/hashes": "1.1.4", - "sigmajs-crypto-facade": "0.0.6" + "sigmajs-crypto-facade": "0.0.7" }, "devDependencies": { "jest": "^29.0.3", @@ -592,11 +592,11 @@ "dev": true }, "node_modules/@fleet-sdk/common": { - "version": "0.1.0-alpha.14", - "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.0-alpha.14.tgz", - "integrity": "sha512-w6AMHe77FaSb759e3EwcOVRQ/lEsCdr1pXq376B+T80do5pUcWjrlrIEKKNrEJPCyqW8nNClxIqBVqkFuPbbMw==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.3.tgz", + "integrity": "sha512-gYEkHhgGpgIcmCL3nCw8E9zHkT2WLmR+mPdxFlUE6fwcwISURbJrP6W9mF7D5Y0ShAP5Is2w3edh7AyIc7ctIQ==", "engines": { - "node": ">=10" + "node": ">=14" } }, "node_modules/@istanbuljs/load-nyc-config": { @@ -3163,9 +3163,9 @@ } }, "node_modules/sigmajs-crypto-facade": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/sigmajs-crypto-facade/-/sigmajs-crypto-facade-0.0.6.tgz", - "integrity": "sha512-Nbz+CZ0rgMvDN76C3bQjrFHO30qSHE9Fti+Co4WvNzL9UTaxSOlfSFJy89u0QX+IkVNvXd35RqiyiekLpTUSsA==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/sigmajs-crypto-facade/-/sigmajs-crypto-facade-0.0.7.tgz", + "integrity": "sha512-4XK8ZS9NKAbo8aGnU6o5GkBW6Upl8+OK8A1KreVDMAamfvZ0iq4LoVH8rHaeEPf9moVtaC4QZY5RYI+0OwiydA==", "dependencies": { "@noble/hashes": "^1.1.4" }, @@ -3994,9 +3994,9 @@ "dev": true }, "@fleet-sdk/common": { - "version": "0.1.0-alpha.14", - "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.0-alpha.14.tgz", - "integrity": "sha512-w6AMHe77FaSb759e3EwcOVRQ/lEsCdr1pXq376B+T80do5pUcWjrlrIEKKNrEJPCyqW8nNClxIqBVqkFuPbbMw==" + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.3.tgz", + "integrity": "sha512-gYEkHhgGpgIcmCL3nCw8E9zHkT2WLmR+mPdxFlUE6fwcwISURbJrP6W9mF7D5Y0ShAP5Is2w3edh7AyIc7ctIQ==" }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -5935,9 +5935,9 @@ } }, "sigmajs-crypto-facade": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/sigmajs-crypto-facade/-/sigmajs-crypto-facade-0.0.6.tgz", - "integrity": "sha512-Nbz+CZ0rgMvDN76C3bQjrFHO30qSHE9Fti+Co4WvNzL9UTaxSOlfSFJy89u0QX+IkVNvXd35RqiyiekLpTUSsA==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/sigmajs-crypto-facade/-/sigmajs-crypto-facade-0.0.7.tgz", + "integrity": "sha512-4XK8ZS9NKAbo8aGnU6o5GkBW6Upl8+OK8A1KreVDMAamfvZ0iq4LoVH8rHaeEPf9moVtaC4QZY5RYI+0OwiydA==", "requires": { "@noble/hashes": "^1.1.4" } diff --git a/sigma-js/package.json b/sigma-js/package.json index 4263896038..57d5b171cf 100644 --- a/sigma-js/package.json +++ b/sigma-js/package.json @@ -36,8 +36,8 @@ }, "dependencies": { "@noble/hashes": "1.1.4", - "@fleet-sdk/common": "0.1.0-alpha.14", - "sigmajs-crypto-facade": "0.0.6" + "@fleet-sdk/common": "0.1.3", + "sigmajs-crypto-facade": "0.0.7" }, "devDependencies": { "jest": "^29.0.3", diff --git a/sigma-js/sigmastate-js.d.ts b/sigma-js/sigmastate-js.d.ts index 04bc1c7f49..34eef4df12 100644 --- a/sigma-js/sigmastate-js.d.ts +++ b/sigma-js/sigmastate-js.d.ts @@ -20,6 +20,21 @@ declare module "sigmastate-js/main" { static fromHex(value: HexString): ErgoTree; } + export declare class GroupElement { + toPointHex(): HexString; + } + + export declare class GroupElementObj { + static fromPointHex(value: HexString): GroupElement; + } + + export declare class SigmaProp { + } + + export declare class SigmaPropObj { + static fromPointHex(value: HexString): SigmaProp; + } + export declare class Type { name: string; toString(): string; @@ -55,8 +70,10 @@ declare module "sigmastate-js/main" { static ofInt(value: number): Value; static ofLong(value: bigint): Value; static ofBigInt(value: bigint): Value; + static ofGroupElement(pointHex: string): Value; + static ofSigmaProp(pointHex: string): Value; static pairOf(left: Value, right: Value): Value<[R, L]>; - static collOf(items: T[], type: Type): Value; + static collOf(items: T[], elemType: Type): Value; static fromHex(hex: HexString): Value; } diff --git a/sigma-js/tests/js/GroupElement.spec.js b/sigma-js/tests/js/GroupElement.spec.js new file mode 100644 index 0000000000..6d860691be --- /dev/null +++ b/sigma-js/tests/js/GroupElement.spec.js @@ -0,0 +1,13 @@ +const { GroupElementObj, ValueObj } = require("sigmastate-js/main"); + +let pointAsn1Hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"; + +describe("GroupElement", () => { + it("should implement toPointHex/fromPointHex", () => { + let ge = GroupElementObj.fromPointHex(pointAsn1Hex) + expect(ge.toPointHex()).toEqual(pointAsn1Hex) + + let v = ValueObj.ofGroupElement(pointAsn1Hex) + expect(v.toHex()).toEqual("07"/* GroupElement type id */ + pointAsn1Hex) + }); +}); \ No newline at end of file diff --git a/sigma-js/tests/js/SigmaProp.spec.js b/sigma-js/tests/js/SigmaProp.spec.js new file mode 100644 index 0000000000..89568120c7 --- /dev/null +++ b/sigma-js/tests/js/SigmaProp.spec.js @@ -0,0 +1,14 @@ +const { SigmaPropObj, ValueObj } = require("sigmastate-js/main"); + +let pointAsn1Hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"; + +describe("SigmaProp", () => { + it("should implement fromPointHex", () => { + let ge = SigmaPropObj.fromPointHex(pointAsn1Hex) + expect(ge).not.toBeUndefined() + + let v = ValueObj.ofSigmaProp(pointAsn1Hex) + expect(v.toHex()) + .toEqual("08"/* SigmaProp type id */ + "cd"/* ProveDlog.opCode */ + pointAsn1Hex) + }); +}); \ No newline at end of file diff --git a/sigma-js/tests/js/Value.spec.js b/sigma-js/tests/js/Value.spec.js index bac3251d4c..e687f8aecb 100644 --- a/sigma-js/tests/js/Value.spec.js +++ b/sigma-js/tests/js/Value.spec.js @@ -1,4 +1,4 @@ -const { TypeObj, ValueObj } = require("sigmastate-js/main"); +const { TypeObj, ValueObj, SigmaPropObj, SigmaProp} = require("sigmastate-js/main"); function testRange(factory, min, max) { expect(factory(max).data).toEqual(max); @@ -105,6 +105,14 @@ describe("Smoke tests for Values", () => { expect(collV.toHex()).toEqual(collHex) }); + it("Value of type Coll[SigmaProp]", () => { + let sp1 = SigmaPropObj.fromPointHex(groupElementHex.substring(2)) + let sp2 = SigmaPropObj.fromPointHex(sigmaPropHex.substring(4)) + let collV = ValueObj.collOf([sp1, sp2], TypeObj.SigmaProp) + + expect(collV.tpe.name).toEqual("Coll[SigmaProp]"); + }); + it("Pair Value.toHex", () => { let fst = ValueObj.ofByte(10) let snd = ValueObj.ofLong(20)