diff --git a/README.md b/README.md index 078e861e80..8b95695a6d 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,123 @@ -# sigmastate-interpreter -Interpreter for a family of Sigma-State authentication languages - -## Brief Description - -Every coin in Bitcoin is protected by a program in the stack-based Script language. An interpreter for the language is -evaluating the program against a redeeming program (in the same language) as well as a context (few variables -containing information about a spending transaction and the blockchain), producing a single boolean value as a result. -While Bitcoin Script allows for some contracts to be programmed, its abilities are limited while many instructions -were removed after denial-of-service or security issues discovered. Also, to add new cryptographic primitives, for example, -ring signatures, a hard-fork is required. - -Generalizing the Bitcoin Script, we introduce a notion of an authentication language where a verifier is running an -interpreter which three inputs are a proposition defined in terms of the language, a context and also a proof (not -necessarily defined in the same language) generated by a prover for the proposition against the same context. The -interpreter is deterministically producing a boolean value and must finish evaluation for any possible inputs within -concrete constant time. - -We propose an alternative authentication language, named Σ-State. It defines guarding proposition for a coin as a logic -formula which combines predicates over a context and cryptographic statements provable via Σ-protocols with -AND, OR, k-out-of-n connectives. A prover willing to spend the coin first reduces the compound proposition to a -compound cryptographic statement by evaluating predicates over known shared context (state of the blockchain system and -a spending transaction). Then the prover is turning a corresponding (and possibly complex) Σ-protocol into a signature -with the help of a Fiat-Shamir transformation. A verifier (a full-node in a blockchain setting) then is checking the proposition -against the context and the signature. Language expressiveness is defined by a set of predicates over context and a -set of cryptographic statements. We show how the latter could be updated with a soft-fork by using a language like -ZKPDL [1], and how the former could be updated with a soft-fork by using versioning conventions. We propose a set of -context predicates for a Bitcoin-like cryptocurrency with a guarantee of constant upper-bound verification time. -We provide several examples: ring and threshold signatures, pre-issued mining rewards, crowdfunding, and demurrage currency. - -## Getting started - -Because there is currently no published version of Sigma-state interpreter, -to use it in your project you first need to: - - 1. Clone or download [sigmastate-interpreter](https://github.com/ScorexFoundation/sigmastate-interpreter) - (`git clone git@github.com:ScorexFoundation/sigmastate-interpreter.git` from command line). - 2. Run `sbt publishLocal` in the directory it was cloned to. - This will publish the artifacts in the local ivy repository (usually at `$HOME/.ivy`) - 3. In your own project add library dependency - ``` -libraryDependencies ++= Seq( - "org.scorexfoundation" %% "sigma-state" % "0.9.4" -) - ``` +# ErgoScript compiler and ErgoTree interpreter + +This repository contains implementations of ErgoScript compiler and ErgoTree +Interpreter for a family of Sigma-protocol based authentication languages (or simply +Sigma language). + +This library is used internally in [Ergo +Node](https://github.com/ergoplatform/ergo) and +[ergo-wallet](https://github.com/ergoplatform/ergo/tree/master/ergo-wallet), the +public interfaces are subject to change. + +For development of Ergo applications using JVM languages (Java/Scala/Kotlin/etc) +a better alternative is to use +[Appkit](https://github.com/ergoplatform/ergo-appkit). + +## Sigma Language Background + +Every coin in Bitcoin is protected by a program in the stack-based Script +language. An interpreter for the language is evaluating the program against a +context (few variables containing information about a spending transaction and +the blockchain), producing a single boolean value as a result. While Bitcoin +Script allows for some contracts to be programmed, its abilities are limited. +Also, to add new cryptographic primitives, for example, ring signatures, a +hard-fork is required. + +Generalizing the Bitcoin Script, ErgoScript compiler and ErgoTree interpreter +implement an _authentication language_ which allows to express coin spending +conditions. The [ErgoScript +Compiler](sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala#L48) +compiles the source code into +[ErgoTree](sigmastate/src/main/scala/sigmastate/Values.scala#L990) byte code, +which can be saved in UTXO coins to protect their spending (same as in Bitcoin). + +ErgoTree, in turn, is a bytecode language and memory representation which can be +deterministically interpreted in the given _blockchain context_. +ErgoTree defines guarding proposition for a coin as a logic formula which +combines predicates over a context and cryptographic statements provable via +[Σ-protocols](https://en.wikipedia.org/wiki/Proof_of_knowledge#Sigma_protocols) +with AND, OR, k-out-of-n connectives. + +An _interacting party_ willing to spend the coin first constructs a +[prover](sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala) +with a set of secrets it knows and then the prover is executed in two steps: + +- _Reduction_ - the prover uses the ErgoTree interpreter and deterministically +reduces the ErgoTree proposition to a compound _cryptographic statement_(aka +sigma proposition, Σ-protocol) by evaluating ErgoTree over known shared context +(state of the blockchain system and a spending transaction). This step produces +a value of the [SigmaBoolean](sigmastate/src/main/scala/sigmastate/Values.scala) +type. + +- Signing - the prover is turning the obtained (and possibly +complex) Σ-proposition into a signature with the help of a [Fiat-Shamir +transformation](https://en.wikipedia.org/wiki/Fiat-Shamir_heuristic). This step +produces a _proof_ that the party knows the secrets such that the knowledge can +be verified before the spending transaction is added to the blockchain. + +To allow valid coin spending a +[verifier](sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala) +is running the ErgoTree interpreter with the following three inputs: +- a quarding proposition given by an ErgoTree +- a blockchain _context_ of the transaction being verified +- a _proof_ (aka transaction signature) generated by a _prover_ + +The verifier is executed as part of transaction validation for each input and is +executed in tree steps: + +- _Reduction_ - same as prover, the verifier uses the ErgoTree interpreter and +deterministically produces a value of the +[SigmaBoolean](sigmastate/src/main/scala/sigmastate/Values.scala) type. +However, this step must finish evaluation for any possible inputs within +concrete fixed time limit (aka maximum cost), which is checked by the interpreter. + +- Cost estimation - the verifier estimates the complexity of cryptographic Sigma +proposition (based in the size and the concrete nodes of SigmaBoolean tree). The +spending fails if the estimated cost exceeds the maximum limit. + +- Signature verification - the signature checker takes 1) the proof, 2) the +SigmaBoolean (aka [sigma +protocol](https://en.wikipedia.org/wiki/Proof_of_knowledge#Sigma_protocols) +proposition) and 3) the signed message (e.g. transaction bytes). +The checker than verifies the proof, which means it verifies that all the +necessary secrets has been known and used to construct the proof (i.e. sign the +transaction). + +## Getting Started + +This library is +[publishied](https://mvnrepository.com/artifact/org.scorexfoundation/sigma-state) +on Maven repository and can be added to the SBT configuration of Scala project. + +```scala +libraryDependencies += "org.scorexfoundation" %% "sigma-state" % "4.0.3" +``` + +## Repository Organization + +| sub-module | description | +|---|-----| +| common | Used in all other submodules and contain basic utility classes | +| core | Implementation of graph-based intermediate representation of ErgoTree, which is used in cost estimation and interpretation | +| docs | Collection of documents | +| library | Implementation of graph IR nodes for Coll, Size and other types | +| library-api | Declarations of interfaces | +| library-impl | Implementation of interfaces | +| sigma-api | Declarations of runtime interfaces which are used in ErgoTree interpreter | +| sigma-impl | Implementation of sigma-api interfaces | +| sigma-library | Implementation of graph IR nodes for Sigma types | +| sigmastate | Implementation ErgoTree, Interpreter and cost estimation | + +## References + +- [Ergo Site](https://ergoplatform.org/en/) +- [Ergo Sources](https://github.com/ergoplatform/ergo) +- [Ergo Appkit](https://github.com/ergoplatform/ergo-appkit) +- [Ergo Appkit Examples](https://github.com/aslesarenko/ergo-appkit-examples) +- [ergo-android](https://github.com/aslesarenko/ergo-android) +- [ergo-wallet-android](https://github.com/MrStahlfelge/ergo-wallet-android) +- [ErgoTree Specification](https://ergoplatform.org/docs/ErgoTree.pdf) +- [Ergo Documents](https://ergoplatform.org/en/documents/) + + + diff --git a/build.sbt b/build.sbt index 29e0c208e8..0c8bfebbe8 100644 --- a/build.sbt +++ b/build.sbt @@ -142,7 +142,7 @@ credentials ++= (for { pgpPublicRing := file("ci/pubring.asc") pgpSecretRing := file("ci/secring.asc") pgpPassphrase := sys.env.get("PGP_PASSPHRASE").map(_.toArray) -usePgpKeyHex("28E27A67AEA38DA458C72228CA9254B5E0640FE4") +usePgpKeyHex("C1FD62B4D44BDF702CDF2B726FF59DA944B150DD") def libraryDefSettings = commonSettings ++ testSettings ++ Seq( scalacOptions ++= Seq( diff --git a/common/src/main/scala/scalan/AnyVals.scala b/common/src/main/scala/scalan/AnyVals.scala index 29b5a1ad7b..6da152e524 100644 --- a/common/src/main/scala/scalan/AnyVals.scala +++ b/common/src/main/scala/scalan/AnyVals.scala @@ -43,6 +43,7 @@ class AVHashMap[K,V](val hashMap: HashMap[K,V]) extends AnyVal { @inline final def keySet: java.util.Set[K] = hashMap.keySet() } object AVHashMap { + /** Helper method to create a new map with the given capacity. */ def apply[K,V](initialCapacity: Int) = new AVHashMap[K,V](new HashMap[K,V](initialCapacity)) } diff --git a/common/src/main/scala/scalan/util/BenchmarkUtil.scala b/common/src/main/scala/scalan/util/BenchmarkUtil.scala index 8ed69a8707..ffaed0b16b 100644 --- a/common/src/main/scala/scalan/util/BenchmarkUtil.scala +++ b/common/src/main/scala/scalan/util/BenchmarkUtil.scala @@ -31,6 +31,15 @@ object BenchmarkUtil { (res, t - t0) } + /** Execute block and measure the time of its execution in nanoseconds. */ + def measureTimeNano[T](block: => T): (T, Long) = { + val start = System.nanoTime() + val res = block + val end = System.nanoTime() + (res, end - start) + } + + def runTasks(nTasks: Int)(block: Int => Unit) = { val (_, total) = measureTime { val tasks = (1 to nTasks).map(iTask => Future(block(iTask))) diff --git a/common/src/main/scala/scalan/util/Extensions.scala b/common/src/main/scala/scalan/util/Extensions.scala index 7a820585fe..d144d6dc96 100644 --- a/common/src/main/scala/scalan/util/Extensions.scala +++ b/common/src/main/scala/scalan/util/Extensions.scala @@ -164,7 +164,14 @@ object Extensions { * @see BigInteger#longValueExact */ @inline final def to256BitValueExact: BigInteger = { - if (x.bitLength() <= 255) x // TODO HF: allow 256 bit values + // Comparing with 255 is correct because bitLength() method excludes the sign bit. + // For example, these are the boundary values: + // (new BigInteger("80" + "00" * 31, 16)).bitLength() = 256 + // (new BigInteger("7F" + "ff" * 31, 16)).bitLength() = 255 + // (new BigInteger("-7F" + "ff" * 31, 16)).bitLength() = 255 + // (new BigInteger("-80" + "00" * 31, 16)).bitLength() = 255 + // (new BigInteger("-80" + "00" * 30 + "01", 16)).bitLength() = 256 + if (x.bitLength() <= 255) x else throw new ArithmeticException("BigInteger out of 256 bit range"); } diff --git a/common/src/test/scala/scalan/TestUtils.scala b/common/src/test/scala/scalan/TestUtils.scala index 75c5568b6f..0e1645f71d 100644 --- a/common/src/test/scala/scalan/TestUtils.scala +++ b/common/src/test/scala/scalan/TestUtils.scala @@ -3,6 +3,7 @@ package scalan import scalan.util.FileUtil import org.scalactic.TripleEquals import org.scalatest.{Inside, Matchers, TestSuite} +import scalan.util.StringUtil.StringUtilExtensions /** * Created by slesarenko on 11/10/2017. @@ -35,8 +36,8 @@ trait TestUtils extends TestSuite with Matchers with Inside with TripleEquals { protected def currentTestName: String = { val testName = _currentTestName.get() - assert(testName != null, "currentTestName called outside a test") - testName + if (testName.isNullOrEmpty) "_outside_tests_" + else testName } protected def currentTestNameAsFileName: String = FileUtil.cleanFileName(currentTestName) diff --git a/docs/LangSpec.md b/docs/LangSpec.md index 5d4e5ffd11..30eb450afc 100644 --- a/docs/LangSpec.md +++ b/docs/LangSpec.md @@ -2,28 +2,81 @@ ### Introduction -#### ErgoScript language features +ErgoScript is a language to write contracts for [Ergo +blockchain](https://ergoplatform.org). ErgoScript contracts can be compiled to +[ErgoTrees](https://ergoplatform.org/docs/ErgoTree.pdf), serialized and stored +in UTXOs. + +A good starting point to writing contracts is to use [ErgoScript by +Example](https://github.com/ergoplatform/ergoscript-by-example) with [Ergo +Playgrounds](https://github.com/ergoplatform/ergo-playgrounds) or +[Appkit](https://github.com/ergoplatform/ergo-appkit). + +ErgoScript compiler is +[published](https://mvnrepository.com/artifact/org.scorexfoundation/sigma-state) +as a library which is cross compiled for Java 7 and Java 8+ and thus can be used +from any JVM lanugage and also on Android and JavaFX platforms. + +The following example shows how source code of ErgoScript contract can be used +to create new transaction using +[Appkit](https://github.com/ergoplatform/ergo-appkit), see [full +example](https://github.com/aslesarenko/ergo-appkit-examples/blob/master/java-examples/src/main/java/org/ergoplatform/appkit/examples/FreezeCoin.java) +for details. + +```java +// To create transaction we use a builder obtained from the context +// the builder keeps relationship with the context to access necessary blockchain data. +UnsignedTransactionBuilder txB = ctx.newTxBuilder(); + +// create new box using new builder obtained from the transaction builder +// in this case we compile new ErgoContract from source ErgoScript code +OutBox newBox = txB.outBoxBuilder() + .value(amountToPay) + .contract(ctx.compileContract( + ConstantsBuilder.create() + .item("freezeDeadline", ctx.getHeight() + newBoxDelay) + .item("pkOwner", prover.getP2PKAddress().pubkey()) + .build(), + "{ " + + " val deadlinePassed = sigmaProp(HEIGHT > freezeDeadline)" + + " deadlinePassed && pkOwner " + + "}")) + .build(); +``` + +The contract is given as the string literal which contains the block of `val` +declarations followed by the logical expression. The expression defines the all +possible conditions to spend the box. The contract can also contain _named +constants_ (which cannot be represented as literals in the source code). +In the example `freezeDeadline` and `pkOwner` are named constants. The concrete +values of named constants should be given to the compiler (see `compileContract` +method) + +The following sections describe ErgoScript and its operations. + +#### ErgoScript language features overview - syntax borrowed from Scala and Kotlin - standard syntax and semantics for well known constructs (operations, code blocks, if branches etc.) -- high-order with first-class lambdas that are used in collection operations +- high-order language with first-class lambdas which are used in collection operations - call-by-value (eager evaluation) - statically typed with local type inference - blocks are expressions - semicolon inference in blocks -- type constructors: Tuple, Coll, Option +- type constructors: Pair, Coll, Option -#### Operations and constructs +#### Operations and constructs overview -- Binary operations: `>, <, >=, <=, +, -, &&, ||, ==, !=, |, *, ^, ++` +- Binary operations: `>, <, >=, <=, +, -, &&, ||, ==, !=, |, &, *, /, %, ^, ++` - predefined primitives: `blake2b256`, `byteArrayToBigInt`, `proveDlog` etc. - val declarations: `val h = blake2b256(pubkey)` - if-then-else clause: `if (x > 0) 1 else 0` - collection literals: `Coll(1, 2, 3, 4)` -- generic high-order collection operations: `map`, `fold`, `exists`, `forall`, etc. -- accessing fields of any predefined structured objects: `box.value` +- generic high-order collection operations: `map`, `filter`, `fold`, `exists`, `forall`, etc. +- accessing fields of any predefined types: `box.value` +- method invocation for predefined types: `coll.map({ x => x + 1 })` - function invocations (predefined and user defined): `proveDlog(pubkey)` -- user defined functions: `def isProven(pk: GroupElement) = proveDlog(pk).isProven` +- user defined function declarations: `def isProven(pk: GroupElement) = proveDlog(pk).isProven` - lambdas and high-order methods: `OUTPUTS.exists { (out: Box) => out.value >= minToRaise }` #### Data types @@ -31,12 +84,12 @@ In ErgoScript, everything is an object in the sense that we can call member functions and properties on any variable. Some of the types can have a special internal representation - for example, numbers and booleans can be represented as primitive values at runtime - but to the user they look like ordinary classes. -NOTE: in ErgoScript we use *type* and *class* as synonyms, we prefer *type* when talking about primitive values and -*class* when talking about methods. +NOTE: in ErgoScript we use *type*, *class* and *trait* as synonyms, we prefer *type* when talking about primitive values and +*trait* or *class* when talking about methods. Type Name | Description -----------------|------------------------------ -`Any` | a supertype of any other type +`Any` | a supertype of any other type (not used directly in ErgoScript) `Unit` | a type with a single value `()` `Boolean` | a type with two logical values `true` and `false` `Byte` | 8 bit signed integer @@ -44,17 +97,27 @@ Type Name | Description `Int` | 32 bit signed integer `Long` | 64 bit signed integer `BigInt` | 256 bit signed integer -`SigmaProp` | a type which represent a logical value which can be be obtained by executing a Sigma protocol with zero-knowledge proof of knowledge -`AvlTree` | authenticated dynamic dictionary +`SigmaProp` | a type representing a _sigma proposition_ which can be verified by executing a Sigma protocol with zero-knowledge proof of knowledge. Every contract should return a value of this type. +`AvlTree` | represents a digest of authenticated dynamic dictionary and can be used to verify proofs of operations performed on the dictionary `GroupElement` | elliptic curve points `Box` | a box containing a monetary value (in NanoErgs), tokens and registers along with a guarding proposition `Option[T]` | a container which either have some value of type `T` or none. `Coll[T]` | a collection of arbitrary length with all values of type `T` -`(T1,...,Tn)` | tuples, a collection of element where T1, ..., Tn can be different types +`(T1,T2)` | a pair of values where T1, T2 can be different types + +The type constructors `Coll`, `Option`, `(_,_)` can be used to construct complex +types as in the following example. +```scala +{ + val keyValues = OUTPUTS(0).R4[Coll[(Int, Option[Int])]].get + ... +} +``` #### Literal syntax and Constants -Literals are used to introduce values of some types directly in program text like the following examples: +Literals are used to introduce values of some types directly in program text +like in the following example: ``` val unit: Unit = () // unit constant val long: Int = 10 // interger value literal @@ -71,14 +134,15 @@ a constant of any type by using Base64 encoded string (See [predefined function] #### Primitive Types -```scala -class Boolean { - /** Convert true to 1 and false to 0 - * @since 2.0 - */ - def toByte: Byte -} +Below we specify methods of pre-defined types using Scala-like declaration of +classes. Note, the `Boolean` type doesn't have pre-defined methods in addition +to the standard operations. + +Note, ErgoScript doesn't allow to define new `class` types, however it has many +pre-defined classes with methods defined below. +Every numeric type has the following methods. +```scala /** Base supertype for all numeric types. */ class Numeric { /** Convert this Numeric value to Byte. @@ -96,205 +160,204 @@ class Numeric { */ def toInt: Int - /** Convert this Numeric value to Int. + /** Convert this Numeric value to Long. * @throws ArithmeticException if overflow happens. */ def toLong: Long - /** Convert this Numeric value to Int. */ + /** Convert this Numeric value to BigInt. */ def toBigInt: BigInt - - /** Returns a big-endian representation of this numeric in a collection of bytes. - * For example, the long value {@code 0x1213141516171819L} would yield the - * byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}}. - * @since 2.0 - */ - def toBytes: Coll[Byte] - - /** Returns a big-endian representation of this numeric in a collection of Booleans. - * Each boolean corresponds to one bit. - * @since 2.0 - */ - def toBits: Coll[Boolean] - - /** Absolute value of this numeric value. - * @since 2.0 - */ - def toAbs: Numeric - - /** Compares this numeric with that numeric for order. Returns a negative integer, zero, or a positive integer as the - * `this` is less than, equal to, or greater than `that`. - */ - def compareTo(that: Numeric): Int - - /** Returns least of the two (`this` or `that`). - * @since 2.0 - */ - def min(that: Numeric): Numeric - - /** Returns max of the two (`this` or `that`). - * @since 2.0 - */ - def max(that: Numeric): Numeric } +``` + +All the predefined numeric types inherit Numeric class and its method. +Internally they are pre-defined like the following. +```scala +class Byte extends Numeric class Short extends Numeric class Int extends Numeric class Long extends Numeric - -/** -* All `modQ` operations assume that Q is a global constant (an order of the only one cryptographically strong group -* which is used for all cryptographic operations). -* So it is globally and implicitly used in all methods. -* */ -class BigInt extends Numeric { - /** Returns this `mod` Q, i.e. remainder of division by Q, where Q is an order of the cryprographic group. - * @since 2.0 - */ - def modQ: BigInt - /** Adds this number with `other` by module Q. - * @since 2.0 - */ - def plusModQ(other: BigInt): BigInt // Testnet2 - /** Subracts this number with `other` by module Q. - * @since 2.0 - */ - def minusModQ(other: BigInt): BigInt // Testnet2 - /** Multiply this number with `other` by module Q. - * @since 2.0 - */ - def multModQ(other: BigInt): BigInt // Testnet2 - /** Multiply this number with `other` by module Q. - * @since Mainnet - */ - def multInverseModQ: BigInt // ??? @kushti do we need it -} +class BigInt extends Numeric ``` #### Context Data -Every script is executed in a context, which is a collection of data available for operations in the script. -The context data is available using variable `CONTEXT` which is of class `Context` defined below. -The following shortcut variables are available in every script to simplify access to the most commonly used context -data. +Every script is executed in a context, which is a collection of data available +for operations in the script. The context data is available using the `CONTEXT` +variable which is of pre-defined class `Context` which is shown below. + +There are also shortcut variables which are available in every script to +simplify access to the most commonly used context data. Variable | Type | Shortcut for ... ------------------|---------------------|---------------------- -`HEIGHT` | `Int` | `CONTEXT.height` -`SELF` | `Box` | `CONTEXT.selfBox` -`INPUTS` | `Coll[Box]` | `CONTEXT.inputs` -`OUTPUTS` | `Coll[Box]` | `CONTEXT.outputs` +`HEIGHT` | `Int` | `CONTEXT.HEIGHT` +`SELF` | `Box` | `CONTEXT.SELF` +`INPUTS` | `Coll[Box]` | `CONTEXT.INPUTS` +`OUTPUTS` | `Coll[Box]` | `CONTEXT.OUTPUTS` + +The following listing shows the methods of pre-defined `Context`, `Header`, +`PreHeader` types. ```scala -/** - * Represents data available in ErgoScript using `CONTEXT` global variable - * @since 2.0 - */ +/** Represents data available in ErgoScript using `CONTEXT` global variable */ class Context { /** Height (block number) of the block which is currently being validated. */ - def height: Int + def HEIGHT: Int /** Box whose proposition is being currently executing */ - def selfBox: Box - - /** Zero based index in `inputs` of `selfBox`. */ - def selfBoxIndex: Int + def SELF: Box - /** A collection of inputs of the current transaction, the transaction where selfBox is one of the inputs. */ - def inputs: Coll[Box] + /** A collection of inputs of the current transaction, the transaction where + * selfBox is one of the inputs. + */ + def INPUTS: Coll[Box] - /** A collection of data inputs of the current transaction. Data inputs are not going to be spent and thus don't - * participate in transaction validation as `inputs`, but data boxes are available in guarding propositions of - * `inputs` and thus can be used in spending logic. - * @since 2.0 - */ + /** A collection of data inputs of the current transaction. Data inputs are + * not going to be spent and thus don't participate in transaction validation + * as `INPUTS`, but data boxes are available in guarding propositions of + * `INPUTS` and thus can be used in spending logic. + */ def dataInputs: Coll[Box] /** A collection of outputs of the current transaction. */ - def outputs: Coll[Box] - - /** Authenticated dynamic dictionary digest representing Utxo state before current state. */ - def lastBlockUtxoRoot: AvlTree + def OUTPUTS: Coll[Box] - /** - * @since 2.0 + /** Authenticated dynamic dictionary digest representing Utxo state before + * current state. */ - def headers: Coll[SHeader] - /** - * @since 2.0 + def LastBlockUtxoRootHash: AvlTree + + /** A fixed number of last block headers in descending order (first header is + * the newest one) */ - def preheader: SPreheader + def headers: Coll[Header] + + /** Header fields that are known before the block is mined. */ + def preHeader: PreHeader } -/** Represents data of the block headers available in scripts. - * @since 2.0 - */ +/** Represents data of the block headers available in scripts. */ class Header { + /** Bytes representation of ModifierId of this Header */ + def id: Coll[Byte] + + /** Block version, to be increased on every soft and hardfork. */ def version: Byte - /** Bytes representation of ModifierId of the previous block in the blockchain */ + /** Id of parent block (as bytes) */ def parentId: Coll[Byte] // + /** 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 */ def stateRoot: Coll[Byte] // ADDigest //33 bytes! extra byte with tree height here! + + /** Root hash (for a Merkle tree) of transactions in a block. */ def transactionsRoot: Coll[Byte] // Digest32 + + /** Block timestamp (in milliseconds since beginning of Unix Epoch) */ def timestamp: Long + + /** Current difficulty in a compressed view. + * NOTE: actually it is unsigned Int*/ def nBits: Long // actually it is unsigned Int + + /** Block height */ def height: Int - def extensionRoot: Coll[Byte] // Digest32 - def minerPk: GroupElement // pk - def powOnetimePk: GroupElement // w - def powNonce: Coll[Byte] // n - def powDistance: BigInt // d + + /** Root hash of extension section (Digest32) */ + def extensionRoot: Coll[Byte] + + /** Miner public key. Should be used to collect block rewards. + * Part of Autolykos solution (pk). + */ + def minerPk: GroupElement + + /** One-time public key. Prevents revealing of miners secret. + * Part of Autolykos solution (w). + */ + def powOnetimePk: GroupElement + + /** Nonce value found by the miner. Part of Autolykos solution (n). */ + def powNonce: Coll[Byte] + + /** 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. + * Part of Autolykos solution (d). + */ + def powDistance: BigInt + + /** Miner votes for changing system parameters. */ + def votes: Coll[Byte] } -/** Only header fields that can be predicted by a miner - * @since 2.0 - */ -class PreHeader { // Testnet2 +/** Only header fields that can be predicted by a miner. */ +class PreHeader { + /** Block version, to be increased on every soft and hardfork. */ def version: Byte + + /** Id of parent block */ def parentId: Coll[Byte] // ModifierId + + /** Block timestamp (in milliseconds since beginning of Unix Epoch) */ def timestamp: Long - def nBits: Long // actually it is unsigned Int + + /** Current difficulty in a compressed view. + * NOTE: actually it is 32-bit unsigned Int */ + def nBits: Long + + /** Block height */ def height: Int + + /** Miner public key. Should be used to collect block rewards. */ def minerPk: GroupElement -} -class AvlTree { - /** Returns digest of the state represent by this tree. - * @since 2.0 - */ - def digest: Coll[Byte] + /** Miner votes for changing system parameters. */ + def votes: Coll[Byte] } + ``` #### Box type +Box represents a unit of storage in Ergo blockchain. It contains 10 registers +(indexed 0-9). First 4 are mandatory and the others are optional. + ```scala +/** Representation of Ergo boxes used during execution of ErgoTree operations. */ class Box { /** Box monetary value in NanoErg */ def value: Long - /** Blake2b256 hash of this box's content, basically equals to `blake2b256(bytes)` */ + /** Blake2b256 hash of this box's content, basically equals to + * `blake2b256(bytes)` + */ def id: Coll[Byte] - /** Serialized bytes of guarding script, which should be evaluated to true in order to open this box. */ + /** Serialized bytes of guarding script, which should be evaluated to true in + * order to open this box. + */ def propositionBytes: Coll[Byte] /** Serialized bytes of this box's content, including proposition bytes. */ def bytes: Coll[Byte] - /** Serialized bytes of this box's content, excluding transactionId and index of output. */ + /** Serialized bytes of this box's content, excluding transactionId and index + * of output. + */ def bytesWithoutRef: Coll[Byte] - /** If `tx` is a transaction which generated this box, then `creationInfo._1` is a height of the tx's block. - * The `creationInfo._2` is a serialized transaction identifier followed by box index in the transaction outputs. - */ + /** If `tx` is a transaction which generated this box, then `creationInfo._1` + * is a height of the tx's block. The `creationInfo._2` is a serialized + * transaction identifier followed by box index in the transaction outputs. + */ def creationInfo: (Int, Coll[Byte]) - /** Synonym of R2 obligatory register - * @since 2.0 - */ + /** Synonym of R2 obligatory register */ def tokens: Coll[(Coll[Byte], Long)] /** Extracts register by id and type. @@ -353,51 +416,171 @@ class Box { * None otherwise * @throws special.sigma.InvalidType exception when the type of the register value is * different from T. - * @since 2.0 - */ - def getReg[T](i: Int): Option[T] - - /** Extracts register as Coll[Byte], deserializes it to script and then executes this script in the current context. - * The original Coll[Byte] of the script is available as getReg[Coll[Byte]](id) - * @param regId identifier of the register - * @tparam T result type of the deserialized script. - * @throws InterpreterException if the actual script type doesn't conform to T - * @return result of the script execution in the current context - * @since Mainnet */ - def executeFromRegister[T](regId: Byte): T + def Ri[T]: Option[T] } ``` Besides properties, every box can have up to 10 numbered registers. The following syntax is supported to access registers on box objects: ``` -box.getReg[Int](3).get // access R3 register, cast its value to Int and return it -box.getReg[Int](3).isDefined // check that value of R3 is defined and has type Int -box.getReg[Int](3).isEmpty // check that value of R3 is either undefined or have not Int type -box.getReg[Int](3).getOrElse(def) // access R3 value if defined, otherwise return def +box.R3[Int].get // access R3 register, check that its value of type Int and return it +box.R3[Int].isDefined // check that value of R3 is defined and has type Int +box.R3[Int].isEmpty // check that value of R3 is undefined +box.R3[Int].getOrElse(d) // access R3 value if defined, otherwise return `d` ``` #### GroupElement ```scala +/** Base class for points on elliptic curves. */ class GroupElement { - // ... - /** - * // this should replace the currently used ^ - * @since 2.0 - */ - def exp(n: BigInt): GroupElement // Testnet2 + /** Exponentiate this GroupElement to the given number. + * @param k The power. + * @return this to the power of k. + */ + def exp(k: BigInt): GroupElement + + /** Group operation. */ + def multiply(that: GroupElement): GroupElement + + /** Inverse element in the group. */ + def negate: GroupElement + + /** Get an encoding of the point value. + * + * @return the point encoding + */ + def getEncoded: Coll[Byte] } ``` #### AvlTree ```scala +/** Type of data which efficiently authenticates potentially huge dataset having key-value dictionary interface. + * Only root hash of dynamic AVL+ tree, tree height, key length, optional value length, and access flags are stored + * in an instance of the datatype. + * + * Please note that standard hash function from `scorex.crypto.hash` is used, and height is stored along with root hash of + * the tree, thus `digest` size is always CryptoConstants.hashLength + 1 bytes. + */ class AvlTree { - /** - * @since 2.0 - */ + /** Returns digest of the state represented by this tree. + * Authenticated tree digest = root hash bytes ++ tree height + */ def digest: Coll[Byte] + + /** Flags of enabled operations packed in single byte. + * isInsertAllowed == (enabledOperations & 0x01) != 0 + * isUpdateAllowed == (enabledOperations & 0x02) != 0 + * isRemoveAllowed == (enabledOperations & 0x04) != 0 + */ + def enabledOperations: Byte + + /** All the elements under the tree have the same length of the keys */ + def keyLength: Int + + /** If non-empty, all the values under the tree are of the same length. */ + def valueLengthOpt: Option[Int] + + /** Checks if Insert operation is allowed for this tree instance. */ + def isInsertAllowed: Boolean + + /** Checks if Update operation is allowed for this tree instance. */ + def isUpdateAllowed: Boolean + + /** Checks if Remove operation is allowed for this tree instance. */ + def isRemoveAllowed: Boolean + + /** Replace digest of this tree producing a new tree. + * Since AvlTree is immutable, this tree instance remains unchanged. + * @param newDigest a new digest + * @return a copy of this AvlTree instance where `this.digest` replaced by + * `newDigest` + */ + def updateDigest(newDigest: Coll[Byte]): AvlTree + + /** Enable/disable operations of this tree producing a new tree. + * Since AvlTree is immutable, `this` tree instance remains unchanged. + * @param newOperations a new flags which specify available operations on a + * new tree. + * @return a copy of this AvlTree instance where + * `this.enabledOperations` replaced by `newOperations` + */ + def updateOperations(newOperations: Byte): AvlTree + + /** Checks if an entry with key `key` exists in this tree using proof `proof`. + * Throws exception if proof is incorrect. + * + * @note CAUTION! Does not support multiple keys check, use [[getMany]] instead. + * Return `true` if a leaf with the key `key` exists + * Return `false` if leaf with provided key does not exist. + * @param key a key of an element of this authenticated dictionary. + * @param proof data to reconstruct part of the tree enough to perform the check + */ + def contains(key: Coll[Byte], proof: Coll[Byte]): Boolean + + /** Perform a lookup of key `key` in this tree using proof `proof`. + * Throws exception if proof is incorrect + * + * @note CAUTION! Does not support multiple keys check, use [[getMany]] instead. + * Return Some(bytes) of leaf with key `key` if it exists + * Return None if leaf with provided key does not exist. + * @param key a key of an element of this authenticated dictionary. + * @param proof data to reconstruct part of the tree enough to get the value + * by the key + */ + def get(key: Coll[Byte], proof: Coll[Byte]): Option[Coll[Byte]] + + /** Perform a lookup of many keys `keys` in this tree using proof `proof`. + * + * @note CAUTION! Keys must be ordered the same way they were in lookup + * before proof was generated. + * For each key return Some(bytes) of leaf if it exists and None if is doesn't. + * @param keys keys of elements of this authenticated dictionary. + * @param proof data to reconstruct part of the tree enough to get the values + * by the keys + */ + def getMany(keys: Coll[Coll[Byte]], proof: Coll[Byte]): Coll[Option[Coll[Byte]]] + + /** Perform insertions of key-value entries into this tree using proof `proof`. + * Throws exception if proof is incorrect + * + * @note CAUTION! Pairs must be ordered the same way they were in insert ops + * before proof was generated. + * Return Some(newTree) if successful + * Return None if operations were not performed. + * @param operations collection of key-value pairs to insert in this + * authenticated dictionary. + * @param proof data to reconstruct part of the tree + */ + def insert(operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree] + + /** Perform updates of key-value entries into this tree using proof `proof`. + * Throws exception if proof is incorrect + * + * @note CAUTION! Pairs must be ordered the same way they were in update ops + * before proof was generated. + * Return Some(newTree) if successful + * Return None if operations were not performed. + * @param operations collection of key-value pairs to update in this + * authenticated dictionary. + * @param proof data to reconstruct part of the tree + */ + def update(operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree] + + /** Perform removal of entries into this tree using proof `proof`. + * Throws exception if proof is incorrect + * Return Some(newTree) if successful + * Return None if operations were not performed. + * + * @note CAUTION! Keys must be ordered the same way they were in remove ops + * before proof was generated. + * @param operations collection of keys to remove from this authenticated + * dictionary. + * @param proof data to reconstruct part of the tree + */ + def remove(operations: Coll[Coll[Byte]], proof: Coll[Byte]): Option[AvlTree] } ``` @@ -408,33 +591,25 @@ class AvlTree { * are either an instance of `Some(x)` or the value `None`. */ class Option[A] { - /** Returns true if the option is None, false otherwise. - */ - def isEmpty: Boolean; - /** Returns true if the option is an instance of Some(value), false otherwise. */ def isDefined: Boolean; /** Returns the option's value if the option is nonempty, otherwise - * return the result of evaluating `default`. - * - * @param default the default expression, which is evaluated only if option is None. - */ - def getOrElse[B](default: => B): B - + * return the result of evaluating `default`. + * NOTE: the `default` is evaluated even if the option contains the value + * i.e. not lazily. + * + * @param default the default expression. + */ + def getOrElse[B](default: B): B + /** Returns the option's value. * @note The option must be nonempty. * @throws InterpreterException if the option is empty. */ def get: A - /** Returns a singleton collection containing the $option's value - * if it is nonempty, or the empty collection if the $option is empty. - * @since 2.0 - */ - def toColl: Coll[A] - /** Returns a Some containing the result of applying $f to this option's * value if this option is nonempty. * Otherwise return None. @@ -455,18 +630,6 @@ class Option[A] { * @since 2.0 */ def filter(p: A => Boolean): Option[A] - - /** Returns the result of applying $f to this option's value if - * this option is nonempty. - * Returns None if this option is empty. - * Slightly different from `map` in that $f is expected to - * return an option (which could be None). - * - * @param f the function to apply - * @see map - * @since 2.0 - */ - def flatMap[B](f: A => Option[B]): Option[B] } ``` @@ -474,13 +637,11 @@ class Option[A] { ```scala /** Indexed (zero-based) collection of elements of type `A` - * @define Coll `Coll` - * @define coll collection * @tparam A the collection element type */ class Coll[A] { - /** The length of the collection */ - def length: Int + /** The number of elements in the collection */ + def size: Int /** The element at given index. * Indices start at `0`; `xs.apply(0)` is the first element of collection `xs`. @@ -496,7 +657,6 @@ class Coll[A] { * If an index is out of bounds (`i < 0 || i >= length`) then `default` value is returned. * @param i the index * @return the element at the given index or default value if index is out or bounds - * @since 2.0 */ def getOrElse(i: Int, default: A): A @@ -511,9 +671,8 @@ class Coll[A] { /** For this collection (x0, ..., xN) and other collection (y0, ..., yM) * produces a collection ((x0, y0), ..., (xK, yK)) where K = min(N, M) - * @since 2.0 */ - def zip[B](ys: Coll[B]): PairColl[A, B] + def zip[B](ys: Coll[B]): Coll[(A, B)] /** Tests whether a predicate holds for at least one element of this collection. * @param p the predicate used to test elements. @@ -532,7 +691,6 @@ class Coll[A] { * @param p the predicate used to test elements. * @return a new collection consisting of all elements of this collection that satisfy the given * predicate `p`. The order of the elements is preserved. - * @since 2.0 */ def filter(p: A => Boolean): Coll[A] @@ -550,11 +708,9 @@ class Coll[A] { * where `x,,1,,, ..., x,,n,,` are the elements of this collection. * Returns `z` if this collection is empty. */ - def foldLeft[B](z: B)(op: (B, A) => B): B + def fold[B](z: B, op: (B, A) => B): B - /** Produces the range of all indices of this collection [0 .. size-1] - * @since 2.0 - */ + /** Produces the range of all indices of this collection [0 .. size-1] */ def indices: Coll[Int] /** @@ -568,49 +724,9 @@ class Coll[A] { * @tparam B the element type of the returned collection. * @return a new collection of type `Coll[B]` resulting from applying the given collection-valued function * `f` to each element of this collection and concatenating the results. - * @since 2.0 */ def flatMap[B](f: A => Coll[B]): Coll[B] - /** Finds the first element of the $coll satisfying a predicate, if any. - * - * @param p the predicate used to test elements. - * @return an option value containing the first element in the $coll - * that satisfies `p`, or `None` if none exists. - */ - def find(p: A => Boolean): Option[A] - - /** Finds index of the first element satisfying some predicate after or at some start index. - * - * @param p the predicate used to test elements. - * @param from the start index - * @return the index `>= from` of the first element of this collection that satisfies the predicate `p`, - * or `-1`, if none exists. - * @since 2.0 - */ - def indexWhere(p: A => Boolean, from: Int): Int - - /** Finds index of last element satisfying some predicate before or at given end index. - * - * @param p the predicate used to test elements. - * @return the index `<= end` of the last element of this collection that satisfies the predicate `p`, - * or `-1`, if none exists. - * @since 2.0 - */ - def lastIndexWhere(p: A => Boolean, end: Int): Int - - - /** Partitions this collection in two collectionss according to a predicate. - * - * @param pred the predicate on which to partition. - * @return a pair of collections: the first collection consists of all elements that - * satisfy the predicate `p` and the second collection consists of all elements - * that don't. The relative order of the elements in the resulting collections - * will BE preserved (this is different from Scala's version of this method). - * @since 2.0 - */ - def partition(pred: A => Boolean): (Coll[A], Coll[A]) - /** Produces a new collection where a slice of elements in this collection is replaced by another sequence. * * @param from the index of the first replaced element @@ -618,7 +734,6 @@ class Coll[A] { * @param replaced the number of elements to drop in the original collection * @return a new collection consisting of all elements of this collection * except that `replaced` elements starting from `from` are replaced by `patch`. - * @since 2.0 */ def patch(from: Int, patch: Coll[A], replaced: Int): Coll[A] @@ -627,95 +742,14 @@ class Coll[A] { * @param elem the replacing element * @return a new collection which is a copy of this collection with the element at position `index` replaced by `elem`. * @throws IndexOutOfBoundsException if `index` does not satisfy `0 <= index < length`. - * @since 2.0 */ def updated(index: Int, elem: A): Coll[A] - /** Returns a copy of this collection where elements at `indexes` are replaced with `values`. - * @since 2.0 - */ - def updateMany(indexes: Coll[Int], values: Coll[A]): Coll[A] - - /** Apply m for each element of this collection, group by key and reduce each group using r. - * @returns one item for each group in a new collection of (K,V) pairs. - * @since 2.0 - */ - def mapReduce[K, V](m: A => (K,V), r: (V,V) => V): Coll[(K,V)] - - /** Partitions this $coll into a map of ${coll}s according to some discriminator function. - * - * @param key the discriminator function. - * @tparam K the type of keys returned by the discriminator function. - * @return A map from keys to ${coll}s such that the following invariant holds: - * {{{ - * (xs groupBy key)(k) = xs filter (x => key(x) == k) - * }}} - * That is, every key `k` is bound to a $coll of those elements `x` - * for which `key(x)` equals `k`. + /** Returns a copy of this collection where elements at `indexes` are replaced + * with `values`. */ - def groupBy[K: RType](key: A => K): Coll[(K, Coll[A])] - - /** Partitions this $coll into a map of ${coll}s according to some discriminator function. - * Additionally projecting each element to a new value. - * - * @param key the discriminator function. - * @param proj projection function to produce new value for each element of this $coll - * @tparam K the type of keys returned by the discriminator function. - * @tparam V the type of values returned by the projection function. - * @return A map from keys to ${coll}s such that the following invariant holds: - * {{{ - * (xs groupByProjecting (key, proj))(k) = xs filter (x => key(x) == k).map(proj) - * }}} - * That is, every key `k` is bound to projections of those elements `x` - * for which `key(x)` equals `k`. - */ - def groupByProjecting[K: RType, V: RType](key: A => K, proj: A => V): Coll[(K, Coll[V])] - - /** Produces a new collection which contains all distinct elements of this collection and also all elements of - * a given collection that are not in this collection. - * This is order preserving operation considering only first occurrences of each distinct elements. - * Any collection `xs` can be transformed to a sequence with distinct elements by using xs.unionSet(Coll()). - * - * NOTE: Use append if you don't need set semantics. - * - * @param that the collection to add. - * @since 2.0 - */ - def unionSets(that: Coll[A]): Coll[A] - - /** Computes the multiset difference between this collection and another sequence. - * - * @param that the sequence of elements to remove - * @tparam B the element type of the returned collection. - * @return a new collection which contains all elements of this collection - * except some of occurrences of elements that also appear in `that`. - * If an element value `x` appears - * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form - * part of the result, but any following occurrences will. - * @since 2.0 - */ - def diff(that: Coll[A]): Coll[A] - - /** Computes the multiset intersection between this collection and another sequence. - * @param that the sequence of elements to intersect with. - * @return a new collection which contains all elements of this collection - * which also appear in `that`. - * If an element value `x` appears - * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained - * in the result, but any following occurrences will be omitted. - * @since 2.0 - */ - def intersect(that: Coll[A]): Coll[A] + def updateMany(indexes: Coll[Int], values: Coll[A]): Coll[A] - /** Folding through all elements of this $coll starting from m.zero and applying m.plus to accumulate - * resulting value. - * - * @param m monoid object to use for summation - * @return result of the following operations (m.zero `m.plus` x1 `m.plus` x2 `m.plus` ... xN) - * @since 2.0 - */ - def sum(m: Monoid[A]): A - /** Selects an interval of elements. The returned collection is made up * of all elements `x` which satisfy the invariant: * {{{ @@ -726,71 +760,19 @@ class Coll[A] { */ def slice(from: Int, until: Int): Coll[A] - /** Puts the elements of other collection after the elements of this collection (concatenation of 2 collections) - */ + /** Puts the elements of other collection after the elements of this + * collection (concatenation of 2 collections). + */ def append(other: Coll[A]): Coll[A] - /** Returns the length of the longest prefix whose elements all satisfy some predicate. - * @param p the predicate used to test elements. - * @return the length of the longest prefix of this collection - * such that every element of the segment satisfies the predicate `p`. - * @since 2.0 - */ - def prefixLength(p: A => Boolean): Int - - /** Finds index of first occurrence of some value in this collection after or at some start index. - * @param elem the element value to search for. - * @param from the start index - * @return the index `>= from` of the first element of this collection that is equal (as determined by `==`) - * to `elem`, or `-1`, if none exists. - * @since 2.0 - */ - def indexOf(elem: A, from: Int): Int - - /** Finds index of last occurrence of some value in this collection before or at a given end index. - * - * @param elem the element value to search for. - * @param end the end index. - * @return the index `<= end` of the last element of this collection that is equal (as determined by `==`) - * to `elem`, or `-1`, if none exists. - * @since 2.0 - */ - def lastIndexOf(elem: A, end: Int): Int - - /** Finds the first element of the collection satisfying a predicate, if any. - * @param p the predicate used to test elements. - * @return an option value containing the first element in the collection - * that satisfies `p`, or `None` if none exists. - * @since 2.0 - */ - def find(p: A => Boolean): Option[A] - - /** Builds a new collection from this collection without any duplicate elements. - * @return A new collection which contains the first occurrence of every element of this collection. - * @since 2.0 - */ - def distinct: Coll[A] - - /** Tests whether this collection contains the given sequence at a given index. - * - * '''Note''': If the both the receiver object `this` and the argument - * `that` are infinite sequences this method may not terminate. - * - * @param that the sequence to test - * @param offset the index where the sequence is searched. - * @return `true` if the sequence `that` is contained in this collection at - * index `offset`, otherwise `false`. - * @since 2.0 - */ - def startsWith(that: Coll[A], offset: Int): Boolean - - /** Tests whether this collection ends with the given collection. - * @param that the collection to test - * @return `true` if this collection has `that` as a suffix, `false` otherwise. - * @since 2.0 + /** Finds index of first occurrence of some value in this collection after or + * at some start index. + * @param elem the element value to search for. + * @param from the start index + * @return the index `>= from` of the first element of this collection that is equal (as determined by `==`) + * to `elem`, or `-1`, if none exists. */ - def endsWith(that: Coll[A]): Boolean - + def indexOf(elem: A, from: Int): Int } ``` @@ -800,14 +782,15 @@ val myOutput = OUTPUTS(0) val myInput = INPUTS(0) ``` -Any collection have a `length` property which returns number of elements in a collection. +Any collection have the `size` property which returns the number of elements in +the collection. ``` -val l = OUTPUTS.length +val size = OUTPUTS.size ``` -The following script check an existence of some element in the collection satisfying some -predicate (condition) +The following script check an existence of some element in the collection +satisfying some predicate (condition) ``` val ok = OUTPUTS.exists { (box: Box) => box.value > 1000 } @@ -828,9 +811,7 @@ def allOf(conditions: Coll[Boolean]): Boolean /** Returns true if at least on element of the conditions is true */ def anyOf(conditions: Coll[Boolean]): Boolean -/** Similar to allOf, but performing logical XOR operation instead of `&&` - * @since 2.0 - */ +/** Similar to allOf, but performing logical XOR operation instead of `&&` */ def xorOf(conditions: Coll[Boolean]): Boolean /** Returns SigmaProp value which can be ZK proven to be true @@ -848,13 +829,14 @@ def atLeast(k: Int, properties: Coll[SigmaProp]): SigmaProp * - no boolean operations in the body, because otherwise the result may be disclosed * - all the operations are over SigmaProp values * - * For motivation and details see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/236 - * @since 1.9 + * For motivation and details see + * https://github.com/ScorexFoundation/sigmastate-interpreter/issues/236 */ def ZKProof(block: SSigmaProp): Boolean -/** Embedding of Boolean values to SigmaProp values. As an example, this operation allows boolean experesions - * to be used as arguments of `atLeast(..., sigmaProp(myCondition), ...)` operation. +/** Embedding of Boolean values to SigmaProp values. As an example, this + * operation allows boolean experesions to be used as arguments of + * `atLeast(..., sigmaProp(myCondition), ...)` operation. */ def sigmaProp(condition: Boolean): SigmaProp @@ -870,120 +852,133 @@ def byteArrayToBigInt(input: Coll[Byte]): BigInt /** Create Long from a collection of bytes. */ def byteArrayToLong(input: Coll[Byte]): Long -/** Returns bytes representation of Long value. - * @since 2.0 - */ +/** Returns bytes representation of Long value. */ def longToByteArray(input: Long): Coll[Byte] -/** - * Convert bytes representation of group element (ECPoint) - * to a new value of GroupElement (using org.bouncycastle.math.ec.ECCurve.decodePoint()) - * @since 1.9 +/** Convert bytes representation of group element (ECPoint) + * to a new value of GroupElement (using + * org.bouncycastle.math.ec.ECCurve.decodePoint()) */ def decodePoint(bytes: Coll[Byte]): GroupElement -/** Returns value of the given type from the context by its tag.*/ +/** Extracts Context variable by id and type. + * ErgoScript is typed, so accessing a the variables is an operation which involves + * some expected type given in brackets. Thus `getVar[Int](id)` expression should + * evaluate to a valid value of the `Option[Int]` type. + * + * For example `val x = getVar[Int](10)` expects the variable, if it is present, to have + * type `Int`. At runtime the corresponding type descriptor is passed as `cT` + * parameter. + * + * There are three cases: + * 1) If the variable doesn't exist. + * Then `val x = getVar[Int](id)` succeeds and returns the None value, which conforms to + * any value of type `Option[T]` for any T. (In the example above T is equal to + * `Int`). Calling `x.get` fails when x is equal to None, but `x.isDefined` + * succeeds and returns `false`. + * 2) If the variable contains a value `v` of type `Int`. + * Then `val x = getVar[Int](id)` succeeds and returns `Some(v)`, which is a valid value + * of type `Option[Int]`. In this case, calling `x.get` succeeds and returns the + * value `v` of type `Int`. Calling `x.isDefined` returns `true`. + * 3) If the variable contains a value `v` of type T other then `Int`. + * Then `val x = getVar[Int](id)` fails, because there is no way to return a valid value + * of type `Option[Int]`. The value of variable is present, so returning it as None + * would break the typed semantics of variables collection. + * + * In some use cases one variable may have values of different types. To access such + * variable an additional variable can be used as a tag. + * + *
+  *   val tagOpt = getVar[Int](id)
+  *   val res = if (tagOpt.isDefined) {
+  *     val tag = tagOpt.get
+  *     if (tag == 1) {
+  *       val x = getVar[Int](id2).get
+  *       // compute res using value x is of type Int
+  *     } else if (tag == 2) {
+  *       val x = getVar[GroupElement](id2).get
+  *       // compute res using value x is of type GroupElement
+  *     } else if (tag == 3) {
+  *       val x = getVar[ Array[Byte] ](id2).get
+  *       // compute res using value x of type Array[Byte]
+  *     } else {
+  *       // compute `res` when `tag` is not 1, 2 or 3
+  *     }
+  *   }
+  *   else {
+  *     // compute value of res when the variable is not present
+  *   }
+  * 
+ * + * @param id zero-based identifier of the variable. + * @tparam T expected type of the variable. + * @return Some(value) if the variable is defined in the context AND has the given type. + * None otherwise + * @throws special.sigma.InvalidType exception when the type of the variable value is + * different from cT. + */ def getVar[T](tag: Int): Option[T] -/** Construct a new SigmaProp value representing public key of Diffie Hellman signature protocol. - * When executed as part of Sigma protocol allow to provide for a verifier a zero-knowledge proof - * of secret knowledge. +/** Construct a new SigmaProp value representing public key of Diffie Hellman + * signature protocol. When executed as part of Sigma protocol allow to provide + * for a verifier a zero-knowledge proof of secret knowledge. */ def proveDHTuple(g: GroupElement, h: GroupElement, u: GroupElement, v: GroupElement): SigmaProp -/** Construct a new SigmaProp value representing public key of discrete logarithm signature protocol. - * When executed as part of Sigma protocol allow to provide for a verifier a zero-knowledge proof - * of secret knowledge. +/** Construct a new SigmaProp value representing public key of discrete + * logarithm signature protocol. When executed as part of Sigma protocol allow + * to provide for a verifier a zero-knowledge proof of secret knowledge. */ def proveDlog(value: GroupElement): SigmaProp -/** Predicate function which checks whether a key is in a tree, by using a membership proof. */ -def isMember(tree: AvlTree, key: Coll[Byte], proof: Coll[Byte]): Boolean - -/** - * Perform a lookup of key `key` in a tree with root `tree` using proof `proof`. - * Throws exception if proof is incorrect - * Return Some(bytes) of leaf with key `key` if it exists - * Return None if leaf with provided key does not exist. - */ -def treeLookup(tree: AvlTree, key: Coll[Byte], proof: Coll[Byte]): Option[Coll[Byte]] - -/** Perform modification of in the tree with root `tree` using proof `proof`. - * Return Some(newTree) if successfull - * Return None if the proof was not correct - * @since 2.0 - */ -def treeModifications(tree: AvlTree, ops: Coll[Byte], proof: Coll[Byte]): Option[AvlTree] - -/** - * Transforms Base58 encoded string litereal into constant of type Coll[Byte]. - * It is a compile-time operation and only string literal (constant) can be its argument. +/** Transforms Base58 encoded string litereal into constant of type Coll[Byte]. + * It is a compile-time operation and only string literal (constant) can be its + * argument. */ def fromBase58(input: String): Coll[Byte] -/** - * Transforms Base64 encoded string litereal into constant of type Coll[Byte]. - * It is a compile-time operation and only string literal (constant) can be its argument. +/** Transforms Base64 encoded string litereal into constant of type Coll[Byte]. + * It is a compile-time operation and only string literal (constant) can be its + * argument. */ def fromBase64(input: String): Coll[Byte] -/** - * It is executed in compile time. - * The compiler takes Base58 encoding of public key as String literal and create GroupElement constant. - * Then the compiler used this constant to construct proveDlog public key out of it. - * @since 1.9 +/** It is executed in compile time. The compiler takes Base58 encoding of public + * key as String literal and create GroupElement constant. Then the compiler + * used this constant to construct proveDlog public key out of it. */ def PK(input: String): SigmaProp -/** Deserializes values from Base58 encoded binary data at compile time */ -def deserialize[T](string: String): T - -/** Extracts context variable as Coll[Byte], deserializes it to script and then executes this script in the current context. - * The original `Coll[Byte]` of the script is available as `getVar[Coll[Byte]](id)` - * @param id identifier of the context variable - * @tparam T result type of the deserialized script. - * @throws InterpreterException if the actual script type doesn't conform to T - * @return result of the script execution in the current context - * @since 2.0 +/** Deserializes values from Base58 encoded binary data at compile time into a + * value of type T. */ -def executeFromVar[T](id: Byte): T +def deserialize[T](string: String): T /** - * Transforms serialized bytes of ErgoTree with segregated constants by replacing constants - * at given positions with new values. This operation allow to use serialized scripts as - * pre-defined templates. - * The typical usage is "check that output box have proposition equal to given script bytes, - * where minerPk (constants(0)) is replaced with currentMinerPk". - * Each constant in original scriptBytes have SType serialized before actual data (see ConstantSerializer). - * During substitution each value from newValues is checked to be an instance of the corresponding type. - * This means, the constants during substitution cannot change their types. + * Transforms serialized bytes of ErgoTree with segregated constants by + * replacing constants at given positions with new values. This operation allow + * to use serialized scripts as pre-defined templates. + + * The typical usage is "check that output box have proposition equal to given + * script bytes, where minerPk (constants(0)) is replaced with currentMinerPk". + * Each constant in original scriptBytes have SType serialized before actual + * data (see ConstantSerializer). During substitution each value from newValues + * is checked to be an instance of the corresponding type. This means, the + * constants during substitution cannot change their types. * * @param scriptBytes serialized ErgoTree with ConstantSegregationFlag set to 1. - * @param positions zero based indexes in ErgoTree.constants array which should be replaced with new values - * @param newValues new values to be injected into the corresponding positions in ErgoTree.constants array - * @return original scriptBytes array where only specified constants are replaced and all other bytes remain exactly the same - * @since 1.9 + * @param positions zero based indexes in ErgoTree.constants array which should + * be replaced with new values + * @param newValues new values to be injected into the corresponding positions + * in ErgoTree.constants array + * @return original scriptBytes array where only specified constants are + * replaced and all other bytes remain exactly the same */ def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], newValues: Coll[T]): Coll[Byte] - -/** Performs outer join operation between left and right collections. - * This is a restricted version of relational join operation. - * It expects `left` and `right` collections have distinct K values in pairs (otherwise exception is thrown). - * Under this condition resulting collection has size <= left.size + right.size. - * @param l projection function executed for each element of `left` - * @param r projection function executed for each element of `right` - * @param inner projection function which is executed for matching items (K, L) and (K, R) with the same K - * @return collection of (K, O) pairs, where each key comes form either left or right collection and values are produced by projections - * @since 2.0 - */ -def outerJoin[K, L, R, O] - (left: Coll[(K, L)], right: Coll[(K, R)]) - (l: (K,L) => O, r: (K,R) => O, inner: (K,L,R) => O): Coll[(K,O)] // Mainnet - ``` ## Examples -See [white paper for example](wpaper/sigma.tex) +See [white paper for examples](https://ergoplatform.org/docs/ErgoScript.pdf) diff --git a/docs/notes.md b/docs/notes.md index 0f7a441ee7..aec9cac341 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -1,3 +1,4 @@ + ## Approximate sizes of different dependencies These dependencies can be removed with simple refactoring diff --git a/docs/wpaper/sigma.pdf b/docs/wpaper/sigma.pdf index 57ae3d40c1..7a18a94427 100644 Binary files a/docs/wpaper/sigma.pdf and b/docs/wpaper/sigma.pdf differ diff --git a/docs/wpaper/sigma.tex b/docs/wpaper/sigma.tex index 6c0896c013..5f71de5240 100644 --- a/docs/wpaper/sigma.tex +++ b/docs/wpaper/sigma.tex @@ -313,7 +313,7 @@ \subsection{Context Extension and Hashing} \subsection{Box Registers and Additional Tokens} \label{sec:box-registers} -Together with its value and protecting script, a box can contain up to 10 numbered registers, \texttt{R0} through \texttt{R9}. The first four of these have fixed meaning, as follows. For a box \texttt{b}, \texttt{b.R0} is the same as \texttt{b.value} and \texttt{b.R1} is the same as \texttt{b.propositionBytes}. +Context variables are passed at box spending time whereas registers at box creation time. Together with its value and protecting script, a box can contain up to 10 numbered registers, \texttt{R0} through \texttt{R9}. The first four of these have fixed meaning, as follows. For a box \texttt{b}, \texttt{b.R0} is the same as \texttt{b.value} and \texttt{b.R1} is the same as \texttt{b.propositionBytes}. The third register, \texttt{b.R2}, is for specifying additional, secondary tokens contained in the box (the primary token amount is specified in \texttt{b.value}). \texttt{b.R2} contains a collection of pairs, the first element of each pair specifying the token id (as a collection of 32 bytes) and the second element specifying the amount (as a long value). The maximum number of tokens in a box is set to 4. For every token id, the sum of amounts in inputs boxes must be no less than the sum of amounts in output boxes. There is one exception to this rule for the creation of new tokens. When a new token type gets created in a transaction, its id is equal to the id of the input box 0. Thus, the exception for the creation of new tokens is that if the token id in some output box is equal to the id of input box 0, then an arbitrary amount of this token can be output. Because each box has a unique id (see Section~\ref{sec:context}, this exception can be applied exactly once per token type. A newly created token can be emitted in a time-controlled fashion---see Section~\ref{sec:self-replicating}. @@ -329,7 +329,7 @@ \subsection{Box Registers and Additional Tokens} In addition to registers, scripts can access two serialized versions of the box: \texttt{b.bytes} is a serialization of the entire box including all its registers, and \texttt{b.bytesWithNoRef}, which the same but without the transaction identifier and the output index~(so that a box can be viewed independently of where it appeared. \paragraph{Example: atomic exchange on a single block chain} -These box registers provide additional capabilities to \langname. Consider, for example, Alice and Bob who want to exchange tokens: they agree that Alice will give Bob 60 tokens of type \texttt{token1} (this type is mapped to an actual token id in the environment map) in exchange for 100 Ergo tokens. Alice could create an output box with value 100 and protect it with the following script: +These box registers provide additional capabilities to \langname. Consider, for example, Alice and Bob who want to exchange tokens: they agree that Bob will give Alice 60 tokens of type \texttt{token1} (this type is mapped to an actual token id in the environment map) in exchange for 100 Ergo tokens. Alice could create an output box with value 100 and protect it with the following script: \begin{verbatim} (HEIGHT > deadline && pkA) || { @@ -360,7 +360,7 @@ \subsection{Box Registers and Additional Tokens} A transaction containing these two boxes as inputs must produce two outputs: the first giving at least 60 tokens of type1 to Alice and the second giving at least 100 tokens of type2 to Bob. Once the two boxes are on the blockchain, anyone can create such a transaction using the two boxes as inputs, and thus effect the exchange between Alice and Bob. Unlike the cross-chain trading example above using hashing (which requires one side to go first), there are no potential problems with synchronization here, because the exchange will happen in a single transaction or will not happen at all. \subsection{Self-Replicating Code} \label{sec:self-replicating} -Access to box registers allow us to create self-replicating boxes, because a script can check that an output box contains the same script as \texttt{SELF}. As shown in \cite{CKM18}, this powerful tool allows for Turing-completeness as computation evolves from box to box, even if each individual script is not Turing-complete. We will demonstrate two examples of complex behavior via self-replication. +Access to box registers allows us to create self-replicating boxes, because a script can check that an output box contains the same script as \texttt{SELF}. As shown in \cite{CKM18}, this powerful tool allows for Turing-completeness as computation evolves from box to box, even if each individual script is not Turing-complete. We will demonstrate two examples of complex behavior via self-replication. \paragraph{Example: time-controlled coin emission} In this example, we will create a self-replicating box that emits new coins at each time step in order to add to the total amount of currency available. This box appears as an output in the genesis block (at height 0) of the blockchain; all "new" coins come from this box or its descendants, thus maintaining the invariant that for every transaction after the genesis block, the combined value of all inputs is equal to the combined value of all outputs. @@ -407,7 +407,7 @@ \section{Implementation} \begin{itemize} \item We estimate the time required to process the script and, if it exceeds a certain bound, refuses to evaluate it in order to prevent a denial-of-service attack. -\item The evaluation converts the script not to a Boolean value, but to a $\Sigma$-statement. This statement is a tree, with \texttt{proveDlog} or \texttt{proveDHtuple} nodes for leaves, and $\andnode$ (\texttt{\&\&}), $\ornode$ (\texttt{||}), or $\tnode$ for non-leaves. The prover (when trying to use in a transaction the box that is protected by the script) generates a proof this $\Sigma$-statement. The verifier verifies it, obtaining a Boolean value. +\item The evaluation converts the script not to a Boolean value, but to a $\Sigma$-statement. This statement is a tree, with \texttt{proveDlog} or \texttt{proveDHtuple} nodes for leaves, and $\andnode$ (\texttt{\&\&}), $\ornode$ (\texttt{||}), or $\tnode$ for non-leaves. The prover (when trying to use in a transaction the box that is protected by the script) generates a proof for this $\Sigma$-statement. The verifier verifies it, obtaining a Boolean value. \end{itemize} We describe the latter non-standard step in Appendix \ref{app:crypto}, while the former will be described in a separate @@ -420,9 +420,8 @@ \section{Further Work} \begin{enumerate} \item{} More examples, including non-interactive and fully on-chain tumbler for mixing the coins, cold wallets, oracles, initial coin offering scenario, multi-state contract defined as a finite state machine, and so on. We - already have code for such the examples done, and writing documentation about them at the moment. - \item{} Detailed description of used type system, cost estimation procedure, safety guarantees, - and abstract syntax tree format. + already have code for such examples done, and writing documentation about them at the moment. + \item{} Detailed description of used type system, cost estimation procedure, safety guarantees and abstract syntax tree format. \end{enumerate} \bibliographystyle{alpha} \bibliography{sigma.bib} diff --git a/library-api/src/main/scala/special/collection/Colls.scala b/library-api/src/main/scala/special/collection/Colls.scala index 99827b10c7..76e74bc900 100644 --- a/library-api/src/main/scala/special/collection/Colls.scala +++ b/library-api/src/main/scala/special/collection/Colls.scala @@ -61,17 +61,6 @@ trait Coll[@specialized A] { */ def isDefinedAt(idx: Int): Boolean - /** The elements at given indexes. - * Indices start at `0` so that `xs.apply(0)` is the first element of collection `xs`. - * Note the indexing syntax `xs(i)` is a shorthand for `xs.apply(i)`. - * - * @param indexes the indexes of the elements to extract from this collection - * @return the elements at the given indexes - * @throws ArrayIndexOutOfBoundsException if `i < 0` or `length <= i` - * for any `i` in `indexes` - */ -// def applyMany(indexes: Coll[Int]): Coll[A] - /** The element of the collection or default value. * If an index is out of bounds (`i < 0 || i >= length`) then `default` value is returned. * @param i the index @@ -117,7 +106,7 @@ trait Coll[@specialized A] { /** Applies a binary operator to a start value and all elements of this collection, * going left to right. * - * @param z the start value. + * @param zero the start value. * @param op the binary operator. * @tparam B the result type of the binary operator. * @return the result of inserting `op` between consecutive elements of this collection, @@ -125,7 +114,7 @@ trait Coll[@specialized A] { * {{{ * op(...op(z, x_1), x_2, ..., x_n) * }}} - * where `x,,1,,, ..., x,,n,,` are the elements of this collection. + * where `x_1, ..., x_n` are the elements of this collection. * Returns `z` if this collection is empty. */ def foldLeft[B](zero: B, op: ((B, A)) => B): B diff --git a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala index 78488d11cf..7a5776cc22 100644 --- a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala +++ b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala @@ -3,9 +3,12 @@ package special.sigma import java.math.BigInteger import org.bouncycastle.math.ec.ECPoint - import special.collection._ import scalan._ +import scorex.crypto.authds.{ADDigest, ADValue} +import scorex.crypto.authds.avltree.batch.Operation + +import scala.util.Try @scalan.Liftable trait CostModel { @@ -487,7 +490,7 @@ trait AvlTree { * Return `true` if a leaf with the key `key` exists * Return `false` if leaf with provided key does not exist. * @param key a key of an element of this authenticated dictionary. - * @param proof + * @param proof data to reconstruct part of the tree enough to perform the check */ def contains(key: Coll[Byte], proof: Coll[Byte]): Boolean @@ -498,7 +501,8 @@ trait AvlTree { * Return Some(bytes) of leaf with key `key` if it exists * Return None if leaf with provided key does not exist. * @param key a key of an element of this authenticated dictionary. - * @param proof + * @param proof data to reconstruct part of the tree enough to get the value + * by the key */ def get(key: Coll[Byte], proof: Coll[Byte]): Option[Coll[Byte]] @@ -518,7 +522,7 @@ trait AvlTree { * Return Some(newTree) if successful * Return None if operations were not performed. * @param operations collection of key-value pairs to insert in this authenticated dictionary. - * @param proof + * @param proof data to reconstruct part of the tree */ def insert(operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree] @@ -529,7 +533,7 @@ trait AvlTree { * Return Some(newTree) if successful * Return None if operations were not performed. * @param operations collection of key-value pairs to update in this authenticated dictionary. - * @param proof + * @param proof data to reconstruct part of the tree */ def update(operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree] @@ -540,11 +544,52 @@ trait AvlTree { * * @note CAUTION! Keys must be ordered the same way they were in remove ops before proof was generated. * @param operations collection of keys to remove from this authenticated dictionary. - * @param proof + * @param proof data to reconstruct part of the tree */ def remove(operations: Coll[Coll[Byte]], proof: Coll[Byte]): Option[AvlTree] + + /** Creates a new instance of [[AvlTreeVerifier]] with the given `proof` and using + * properties of this AvlTree (digest, keyLength, valueLengthOpt) for constructor + * arguments. + * + * @param proof bytes of the serialized proof which is used to represent the tree. + */ + def createVerifier(proof: Coll[Byte]): AvlTreeVerifier +} + +/** Represents operations of AVL tree verifier in an abstract (implementation independent) + * way which allows declaration of the [[AvlTree.createVerifier()]] method. + */ +trait AvlTreeVerifier { + /** + * If operation.key exists in the tree and the operation succeeds, + * returns Success(Some(v)), where v is the value associated with operation.key + * before the operation. + * If operation.key does not exists in the tree and the operation succeeds, returns Success(None). + * Returns Failure if the operation fails or the proof does not verify. + * After one failure, all subsequent operations will fail and digest + * is None. + * + * @param operation an operation descriptor + * @return - Success(Some(old value)), Success(None), or Failure + */ + def performOneOperation(operation: Operation): Try[Option[ADValue]] + + /** Returns the max height of the tree extracted from the root digest. */ + def treeHeight: Int + + /** + * Returns Some[the current digest of the authenticated data structure], + * where the digest contains the root hash and the root height + * Returns None if the proof verification failed at construction + * or during any of the operations. + * + * @return - Some[digest] or None + */ + def digest: Option[ADDigest] } + /** Only header fields that can be predicted by a miner. * @since 2.0 */ @@ -569,6 +614,7 @@ trait PreHeader { // Testnet2 /** Miner public key. Should be used to collect block rewards. */ def minerPk: GroupElement + /** Miner votes for changing system parameters. */ def votes: Coll[Byte] } @@ -622,6 +668,7 @@ trait Header { * corresponding to `minerPk`. The lower `powDistance` is, the harder it was to find this solution. */ def powDistance: BigInt + /** Miner votes for changing system parameters. */ def votes: Coll[Byte] //3 bytes } @@ -665,8 +712,70 @@ trait Context { def preHeader: PreHeader def minerPubKey: Coll[Byte] + + /** Extracts Context variable by id and type. + * ErgoScript is typed, so accessing a the variables is an operation which involves + * some expected type given in brackets. Thus `getVar[Int](id)` expression should + * evaluate to a valid value of the `Option[Int]` type. + * + * For example `val x = getVar[Int](10)` expects the variable, if it is present, to have + * type `Int`. At runtime the corresponding type descriptor is passed as `cT` + * parameter. + * + * There are three cases: + * 1) If the variable doesn't exist. + * Then `val x = getVar[Int](id)` succeeds and returns the None value, which conforms to + * any value of type `Option[T]` for any T. (In the example above T is equal to + * `Int`). Calling `x.get` fails when x is equal to None, but `x.isDefined` + * succeeds and returns `false`. + * 2) If the variable contains a value `v` of type `Int`. + * Then `val x = getVar[Int](id)` succeeds and returns `Some(v)`, which is a valid value + * of type `Option[Int]`. In this case, calling `x.get` succeeds and returns the + * value `v` of type `Int`. Calling `x.isDefined` returns `true`. + * 3) If the variable contains a value `v` of type T other then `Int`. + * Then `val x = getVar[Int](id)` fails, because there is no way to return a valid value + * of type `Option[Int]`. The value of variable is present, so returning it as None + * would break the typed semantics of variables collection. + * + * In some use cases one variable may have values of different types. To access such + * variable an additional variable can be used as a tag. + * + *
+    *   val tagOpt = getVar[Int](id)
+    *   val res = if (tagOpt.isDefined) {
+    *     val tag = tagOpt.get
+    *     if (tag == 1) {
+    *       val x = getVar[Int](id2).get
+    *       // compute res using value x is of type Int
+    *     } else if (tag == 2) {
+    *       val x = getVar[GroupElement](id2).get
+    *       // compute res using value x is of type GroupElement
+    *     } else if (tag == 3) {
+    *       val x = getVar[ Array[Byte] ](id2).get
+    *       // compute res using value x of type Array[Byte]
+    *     } else {
+    *       // compute `res` when `tag` is not 1, 2 or 3
+    *     }
+    *   }
+    *   else {
+    *     // compute value of res when the variable is not present
+    *   }
+    * 
+ * + * @param id zero-based identifier of the variable. + * @tparam T expected type of the variable. + * @return Some(value) if the variable is defined in the context AND has the given type. + * None otherwise + * @throws special.sigma.InvalidType exception when the type of the variable value is + * different from cT. + */ def getVar[T](id: Byte)(implicit cT: RType[T]): Option[T] + def vars: Coll[AnyValue] + + /** Maximum version of ErgoTree currently activated on the network. + * See [[ErgoLikeContext]] class for details. */ + def activatedScriptVersion: Byte } @scalan.Liftable @@ -710,8 +819,7 @@ trait SigmaContract { @Reified("T") def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], - newValues: Coll[T]) - (implicit cT: RType[T]): Coll[Byte] = this.builder.substConstants(scriptBytes, positions, newValues) + newValues: Coll[T]): Coll[Byte] = this.builder.substConstants(scriptBytes, positions, newValues) } /** Runtime representation of SGlobal ErgoTree type. @@ -828,7 +936,7 @@ trait SigmaDslBuilder { * @return original scriptBytes array where only specified constants are replaced and all other bytes remain exactly the same */ @Reified("T") - def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], newValues: Coll[T])(implicit cT: RType[T]): Coll[Byte] + def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], newValues: Coll[T]): Coll[Byte] /** Decodes the given bytes to the corresponding GroupElement using default serialization. * @param encoded serialized bytes of some GroupElement value diff --git a/sigma-impl/src/main/scala/special/sigma/Mocks.scala b/sigma-impl/src/main/scala/special/sigma/Mocks.scala index 700f4a7825..6069b29b4d 100644 --- a/sigma-impl/src/main/scala/special/sigma/Mocks.scala +++ b/sigma-impl/src/main/scala/special/sigma/Mocks.scala @@ -5,9 +5,10 @@ import scalan.{NeverInline, OverloadId} import special.collection.Builder.DefaultCollBuilder import special.collection.{Coll, Builder} -/**NOTE: this should extend SigmaProp because semantically it subclass of SigmaProp - * and DefaultSigma is used just to mixin implementations. */ - +// TODO refactor: remove this class +/** NOTE: this should extend SigmaProp because semantically it subclass of SigmaProp + * and DefaultSigma is used just to mixin implementations. + */ case class MockSigma(val _isValid: Boolean) extends SigmaProp { def propBytes: Coll[Byte] = DefaultCollBuilder.fromItems(if(isValid) 1 else 0) @@ -25,7 +26,6 @@ case class MockSigma(val _isValid: Boolean) extends SigmaProp { @NeverInline @OverloadId("or_bool") def ||(other: Boolean): SigmaProp = MockSigma(isValid || other) - } case class MockProveDlog(var isValid: Boolean, val propBytes: Coll[Byte]) extends SigmaProp { diff --git a/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala b/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala index abd72e5236..d1180960a7 100644 --- a/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala +++ b/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala @@ -80,8 +80,7 @@ class TestSigmaDslBuilder extends SigmaDslBuilder { @NeverInline override def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], - newValues: Coll[T]) - (implicit cT: RType[T]): Coll[Byte] = ??? + newValues: Coll[T]): Coll[Byte] = ??? @NeverInline override def decodePoint(encoded: Coll[Byte]): GroupElement = diff --git a/sigma-library/src/main/scala/special/sigma/SigmaDslUnit.scala b/sigma-library/src/main/scala/special/sigma/SigmaDslUnit.scala index 9d47cf1cc2..8dad6b3a9f 100644 --- a/sigma-library/src/main/scala/special/sigma/SigmaDslUnit.scala +++ b/sigma-library/src/main/scala/special/sigma/SigmaDslUnit.scala @@ -179,7 +179,7 @@ package special.sigma { def proveDHTuple(g: Ref[GroupElement], h: Ref[GroupElement], u: Ref[GroupElement], v: Ref[GroupElement]): Ref[SigmaProp] = this.builder.proveDHTuple(g, h, u, v); def groupGenerator: Ref[GroupElement] = this.builder.groupGenerator; def decodePoint(encoded: Ref[Coll[Byte]]): Ref[GroupElement] = this.builder.decodePoint(encoded); - @Reified(value = "T") def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]])(implicit cT: Elem[T]): Ref[Coll[Byte]] = this.builder.substConstants[T](scriptBytes, positions, newValues) + @Reified(value = "T") def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]]): Ref[Coll[Byte]] = this.builder.substConstants[T](scriptBytes, positions, newValues) }; @Liftable @WithMethodCallRecognizers trait SigmaDslBuilder extends Def[SigmaDslBuilder] { def Colls: Ref[CollBuilder]; @@ -202,7 +202,7 @@ package special.sigma { def proveDlog(g: Ref[GroupElement]): Ref[SigmaProp]; def proveDHTuple(g: Ref[GroupElement], h: Ref[GroupElement], u: Ref[GroupElement], v: Ref[GroupElement]): Ref[SigmaProp]; def groupGenerator: Ref[GroupElement]; - @Reified(value = "T") def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]])(implicit cT: Elem[T]): Ref[Coll[Byte]]; + @Reified(value = "T") def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]]): Ref[Coll[Byte]]; def decodePoint(encoded: Ref[Coll[Byte]]): Ref[GroupElement]; def avlTree(operationFlags: Ref[Byte], digest: Ref[Coll[Byte]], keyLength: Ref[Int], valueLengthOpt: Ref[WOption[Int]]): Ref[AvlTree]; def xor(l: Ref[Coll[Byte]], r: Ref[Coll[Byte]]): Ref[Coll[Byte]] diff --git a/sigma-library/src/main/scala/special/sigma/impl/SigmaDslImpl.scala b/sigma-library/src/main/scala/special/sigma/impl/SigmaDslImpl.scala index 47a377b03e..67230ff5ae 100644 --- a/sigma-library/src/main/scala/special/sigma/impl/SigmaDslImpl.scala +++ b/sigma-library/src/main/scala/special/sigma/impl/SigmaDslImpl.scala @@ -3332,11 +3332,11 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { true, false, element[GroupElement])) } - override def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]])(implicit cT: Elem[T]): Ref[Coll[Byte]] = { + override def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]]): Ref[Coll[Byte]] = { implicit val eT = newValues.eA asRep[Coll[Byte]](mkMethodCall(self, - SigmaDslBuilderClass.getMethod("substConstants", classOf[Sym], classOf[Sym], classOf[Sym], classOf[Elem[_]]), - Array[AnyRef](scriptBytes, positions, newValues, cT), + SigmaDslBuilderClass.getMethod("substConstants", classOf[Sym], classOf[Sym], classOf[Sym]), + Array[AnyRef](scriptBytes, positions, newValues), true, false, element[Coll[Byte]])) } @@ -3525,11 +3525,11 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { true, true, element[GroupElement])) } - def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]])(implicit cT: Elem[T]): Ref[Coll[Byte]] = { + def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]]): Ref[Coll[Byte]] = { implicit val eT = newValues.eA asRep[Coll[Byte]](mkMethodCall(source, - SigmaDslBuilderClass.getMethod("substConstants", classOf[Sym], classOf[Sym], classOf[Sym], classOf[Elem[_]]), - Array[AnyRef](scriptBytes, positions, newValues, cT), + SigmaDslBuilderClass.getMethod("substConstants", classOf[Sym], classOf[Sym], classOf[Sym]), + Array[AnyRef](scriptBytes, positions, newValues), true, true, element[Coll[Byte]])) } @@ -3793,13 +3793,13 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { } object substConstants { - def unapply(d: Def[_]): Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]], Elem[T]) forSome {type T}] = d match { + def unapply(d: Def[_]): Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]]) forSome {type T}] = d match { case MethodCall(receiver, method, args, _) if method.getName == "substConstants" && receiver.elem.isInstanceOf[SigmaDslBuilderElem[_]] => - val res = (receiver, args(0), args(1), args(2), args(3)) - Nullable(res).asInstanceOf[Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]], Elem[T]) forSome {type T}]] + val res = (receiver, args(0), args(1), args(2)) + Nullable(res).asInstanceOf[Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]]) forSome {type T}]] case _ => Nullable.None } - def unapply(exp: Sym): Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]], Elem[T]) forSome {type T}] = unapply(exp.node) + def unapply(exp: Sym): Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]]) forSome {type T}] = unapply(exp.node) } object decodePoint { diff --git a/sigmastate/src/main/java/gf2t/GF2_192.java b/sigmastate/src/main/java/gf2t/GF2_192.java index ac40b9b071..dc0c166bcf 100644 --- a/sigmastate/src/main/java/gf2t/GF2_192.java +++ b/sigmastate/src/main/java/gf2t/GF2_192.java @@ -28,16 +28,29 @@ */ package gf2t; +import java.util.Arrays; + public class GF2_192 { private final long [] word = new long[3]; /** * - * @param that the field element with which to compare + * @param obj the field element with which to compare * @return true if and only if this and that represent the same field element */ - public boolean equals(GF2_192 that) { - return this.word[0]==that.word[0] && this.word[1]==that.word[1] && this.word[2]==that.word[2]; + @Override + public boolean equals(Object obj) { + if (this == obj) return true; // equal references + if (obj instanceof GF2_192) { + GF2_192 that = (GF2_192)obj; + return this.word[0]==that.word[0] && this.word[1]==that.word[1] && this.word[2]==that.word[2]; + } + return false; // different types + } + + @Override + public int hashCode() { + return Arrays.hashCode(word); } // using irreducible polynomial x^192+x^7+x^2+x+1 diff --git a/sigmastate/src/main/java/gf2t/GF2_192_Poly.java b/sigmastate/src/main/java/gf2t/GF2_192_Poly.java index 2e1e2cafc1..d3ccd61fc3 100644 --- a/sigmastate/src/main/java/gf2t/GF2_192_Poly.java +++ b/sigmastate/src/main/java/gf2t/GF2_192_Poly.java @@ -29,6 +29,8 @@ package gf2t; +import java.util.Arrays; + public class GF2_192_Poly { private final GF2_192 [] c; // must be not null and of length at least 1 @@ -227,4 +229,19 @@ public byte[] toByteArray(Boolean coeff0) { public byte[] coeff0Bytes() { return c[0].toByteArray(); } + + @Override + public int hashCode() { + return 31 * Arrays.deepHashCode(c) + deg; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj instanceof GF2_192_Poly) { + GF2_192_Poly that = (GF2_192_Poly)obj; + return Arrays.deepEquals(c, that.c) && deg == that.deg; + } + return false; + } } diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala index 79fbb8c436..72c2736841 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala @@ -120,8 +120,7 @@ object ErgoBox { trait RegisterId { val number: Byte def asIndex: Int = number.toInt - - override def toString: Idn = "R" + number + override def toString: String = "R" + number } abstract class MandatoryRegisterId(override val number: Byte, val purpose: String) extends RegisterId diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 82f6f5f877..19211b6aa0 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -50,6 +50,7 @@ class ErgoBoxCandidate(val value: Long, lazy val propositionBytes: Array[Byte] = ergoTree.bytes /** Serialized bytes of this Box without transaction reference data (transactionId and boxIndex). */ + // TODO v5.0: re-implement extracting directly from `ErgoBox.bytes` array lazy val bytesWithNoRef: Array[Byte] = ErgoBoxCandidate.serializer.toBytes(this) /** Creates a new [[ErgoBox]] based on this candidate using the given transaction reference data. @@ -93,7 +94,7 @@ class ErgoBoxCandidate(val value: Long, ScalaRunTime._hashCode((value, ergoTree, additionalTokens, additionalRegisters, creationHeight)) } - override def toString: Idn = s"ErgoBoxCandidate($value, $ergoTree," + + override def toString: String = s"ErgoBoxCandidate($value, $ergoTree," + s"tokens: (${additionalTokens.map(t => ErgoAlgos.encode(t._1) + ":" + t._2).toArray.mkString(", ")}), " + s"$additionalRegisters, creationHeight: $creationHeight)" diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala index 8467a24ee7..22edda7ab0 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala @@ -152,9 +152,10 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData, } val vars = contextVars(varMap ++ extensions) val avlTree = CAvlTree(lastBlockUtxoRoot) + val selfBox = boxesToSpend(selfIndex).toTestBox(isCost) CostingDataContext( - dataInputs, headers, preHeader, inputs, outputs, preHeader.height, boxesToSpend(selfIndex).toTestBox(isCost), avlTree, - preHeader.minerPk.getEncoded, vars, isCost) + dataInputs, headers, preHeader, inputs, outputs, preHeader.height, selfBox, avlTree, + preHeader.minerPk.getEncoded, vars, activatedScriptVersion, isCost) } diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala index 1ce5bede46..57fc66853e 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala @@ -40,4 +40,5 @@ class ErgoLikeInterpreter(implicit val IR: IRContext) extends Interpreter { }.orElse(d.default) case _ => super.substDeserialize(context, updateContext, node) } + } \ No newline at end of file diff --git a/sigmastate/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala b/sigmastate/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala index d4266bb1c6..5798762db8 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala @@ -11,7 +11,7 @@ import special.sigma.{SigmaProp, SigmaContract, Context, DslSyntaxExtensions, Si import scala.language.implicitConversions trait ContractSyntax { contract: SigmaContract => - override def builder: SigmaDslBuilder = new CostingSigmaDslBuilder + override def builder: SigmaDslBuilder = CostingSigmaDslBuilder val spec: ContractSpec val syntax = new DslSyntaxExtensions(builder) def contractEnv: ScriptEnv diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala index 40fa3d8ad6..cbcb93f0a9 100644 --- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala @@ -1,150 +1,224 @@ package sigmastate +import com.typesafe.scalalogging.LazyLogging +import gf2t.GF2_192_Poly import org.bouncycastle.util.BigIntegers -import sigmastate.basics.DLogProtocol.{SecondDLogProverMessage, ProveDlog} -import sigmastate.basics.VerifierMessage.Challenge +import scorex.util.encode.Base16 +import sigmastate.SigSerializer.{logger, readBytesChecked} import sigmastate.Values.SigmaBoolean +import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} +import sigmastate.basics.VerifierMessage.Challenge +import sigmastate.basics.{ProveDHTuple, SecondDiffieHellmanTupleProverMessage} import sigmastate.interpreter.CryptoConstants -import sigmastate.utils.Helpers -import Helpers.xor -import gf2t.GF2_192_Poly -import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple} +import sigmastate.lang.exceptions.SerializerException +import sigmastate.serialization.SigmaSerializer +import sigmastate.utils.{Helpers, SigmaByteReader, SigmaByteWriter} +import spire.syntax.all.cfor -object SigSerializer { +object SigSerializer extends LazyLogging { + /** Log warning message using this class's logger. */ + def warn(msg: String) = logger.warn(msg) val hashSize = CryptoConstants.soundnessBits / 8 val order = CryptoConstants.groupSize - def toBytes(tree: UncheckedTree): Array[Byte] = { - - def traverseNode(node: UncheckedSigmaTree, - acc: Array[Byte], - writeChallenge: Boolean = true): Array[Byte] = { - val parentChal = if (writeChallenge) node.challenge else Array.emptyByteArray - node match { - case dl: UncheckedSchnorr => - acc ++ - parentChal ++ - BigIntegers.asUnsignedByteArray(order, dl.secondMessage.z.bigInteger) - case dh: UncheckedDiffieHellmanTuple => - acc ++ - parentChal ++ - BigIntegers.asUnsignedByteArray(order, dh.secondMessage.z) - case and: CAndUncheckedNode => - // don't write children's challenges -- they are equal to the challenge of this node - and.children.foldLeft(acc ++ parentChal) { case (ba, child) => - traverseNode(child, ba, writeChallenge = false) - } - case or: COrUncheckedNode => - // don't write last child's challenge -- it's computed by the verifier via XOR - val res = or.children.init.foldLeft(acc ++ parentChal) { case (ba, child) => - traverseNode(child, ba, writeChallenge = true) - } - traverseNode(or.children.last, res, writeChallenge = false) - - case t: CThresholdUncheckedNode => - // write the polynomial, except the zero coefficient - val poly = t.polynomialOpt.get.toByteArray(false) - - // don't write children's challenges - t.children.foldLeft(acc ++ parentChal ++ poly) { case (ba, child) => - traverseNode(child, ba, writeChallenge = false) - } - - case _ => ??? - } + /** Recursively traverses the given node and serializes challenges and prover messages + * to the given writer. + * Note, sigma propositions and commitments are not serialized. + * + * @param tree tree to traverse and serialize + * @return the proof bytes containing all the serialized challenges and prover messages + * (aka `z` values) + */ + def toProofBytes(tree: UncheckedTree): Array[Byte] = { + tree match { + case NoProof => + Array.emptyByteArray + case t: UncheckedSigmaTree => + val w = SigmaSerializer.startWriter() + toProofBytes(t, w, writeChallenge = true) + val res = w.toBytes + res } + } - tree match { - case NoProof => Array.emptyByteArray - case t: UncheckedSigmaTree => traverseNode(t, Array[Byte](), writeChallenge = true) // always write the root challenge + /** Recursively traverses the given node and serializes challenges and prover messages + * to the given writer. + * Note, sigma propositions and commitments are not serialized. + * + * @param node subtree to traverse + * @param w writer to put the bytes + * @param writeChallenge if true, than node.challenge is serialized, and omitted + * otherwise. + */ + def toProofBytes(node: UncheckedSigmaTree, + w: SigmaByteWriter, + writeChallenge: Boolean): Unit = { + if (writeChallenge) { + w.putBytes(node.challenge) + } + node match { + case dl: UncheckedSchnorr => + val z = BigIntegers.asUnsignedByteArray(order, dl.secondMessage.z.bigInteger) + w.putBytes(z) + + case dh: UncheckedDiffieHellmanTuple => + val z = BigIntegers.asUnsignedByteArray(order, dh.secondMessage.z) + w.putBytes(z) + + case and: CAndUncheckedNode => + // don't write children's challenges -- they are equal to the challenge of this node + val cs = and.children.toArray + cfor(0)(_ < cs.length, _ + 1) { i => + val child = cs(i) + toProofBytes(child, w, writeChallenge = false) + } + + case or: COrUncheckedNode => + // don't write last child's challenge -- it's computed by the verifier via XOR + val cs = or.children.toArray + val iLastChild = cs.length - 1 + cfor(0)(_ < iLastChild, _ + 1) { i => + val child = cs(i) + toProofBytes(child, w, writeChallenge = true) + } + toProofBytes(cs(iLastChild), w, writeChallenge = false) + + case t: CThresholdUncheckedNode => + // write the polynomial, except the zero coefficient + val poly = t.polynomialOpt.get.toByteArray(false) + w.putBytes(poly) + + // don't write children's challenges + val cs = t.children.toArray + cfor(0)(_ < cs.length, _ + 1) { i => + val child = cs(i) + toProofBytes(child, w, writeChallenge = false) + } + + case _ => + throw new SerializerException(s"Don't know how to execute toBytes($node)") } } + /** Verifier Step 2: In a top-down traversal of the tree, obtain the challenges for the + * children of every non-leaf node by reading them from the proof or computing them. + * Verifier Step 3: For every leaf node, read the response z provided in the proof. + * + * @param exp sigma proposition which defines the structure of bytes from the reader + * @param proof proof to extract challenges from + * @return An instance of [[UncheckedTree]] i.e. either [[NoProof]] or [[UncheckedSigmaTree]] + */ + def parseAndComputeChallenges(exp: SigmaBoolean, proof: Array[Byte]): UncheckedTree = { + if (proof.isEmpty) + NoProof + else { + // Verifier step 1: Read the root challenge from the proof. + val r = SigmaSerializer.startReader(proof) + val res = parseAndComputeChallenges(exp, r, null) + res + } + } - def parseAndComputeChallenges(exp: SigmaBoolean, bytes: Array[Byte]): UncheckedTree = { - - /** - * Verifier Step 2: In a top-down traversal of the tree, obtain the challenges for the children of every - * non-leaf node by reading them from the proof or computing them. - * Verifier Step 3: For every leaf node, read the response z provided in the proof. - * - * @param exp - * @param bytes - * @param pos - * @param challengeOpt if non-empty, then the challenge has been computed for this node by its parent; - * else it needs to be read from the proof - * @return - */ - def traverseNode(exp: SigmaBoolean, - bytes: Array[Byte], - pos: Int, - challengeOpt: Option[Challenge] = None): (UncheckedSigmaTree, Int) = { - // Verifier Step 2: Let e_0 be the challenge in the node here (e_0 is called "challenge" in the code) - val (challenge, chalLen) = if (challengeOpt.isEmpty) { - (Challenge @@ bytes.slice(pos, pos + hashSize)) -> hashSize - } else { - challengeOpt.get -> 0 - } - exp match { - case dl: ProveDlog => - // Verifier Step 3: For every leaf node, read the response z provided in the proof. - val z = BigIntegers.fromUnsignedByteArray(bytes.slice(pos + chalLen, pos + chalLen + order)) - UncheckedSchnorr(dl, None, challenge, SecondDLogProverMessage(z)) -> (chalLen + order) - - case dh: ProveDHTuple => - // Verifier Step 3: For every leaf node, read the response z provided in the proof. - val z = BigIntegers.fromUnsignedByteArray(bytes.slice(pos + chalLen, pos + chalLen + order)) - UncheckedDiffieHellmanTuple(dh, None, challenge, SecondDiffieHellmanTupleProverMessage(z)) -> (chalLen + order) - - case and: CAND => - // Verifier Step 2: If the node is AND, then all of its children get e_0 as the challenge - val (seq, finalPos) = and.children.foldLeft(Seq[UncheckedSigmaTree]() -> (pos + chalLen)) { case ((s, p), child) => - val (rewrittenChild, consumed) = traverseNode(child, bytes, p, Some(challenge)) - (s :+ rewrittenChild, p + consumed) - } - CAndUncheckedNode(challenge, seq) -> (finalPos - pos) - - case or: COR => - // Verifier Step 2: If the node is OR, then each of its children except rightmost - // one gets the challenge given in the proof for that node. - // The rightmost child gets a challenge computed as an XOR of the challenges of all the other children and e_0. - - // Read all the children but the last and compute the XOR of all the challenges including e_0 - val (seq, lastPos, lastChallenge) = or.children.init.foldLeft((Seq[UncheckedSigmaTree](), pos + chalLen, challenge)) { - case ((s, p, challengeXOR), child) => - val (rewrittenChild, consumed) = traverseNode(child, bytes, p, challengeOpt = None) - val ch = Challenge @@ xor(challengeXOR, rewrittenChild.challenge) - (s :+ rewrittenChild, p + consumed, ch) - } - // use the computed XOR for last child's challenge - val (lastChild, numRightChildBytes) = traverseNode(or.children.last, bytes, lastPos, Some(lastChallenge)) - COrUncheckedNode(challenge, seq :+ lastChild) -> (lastPos + numRightChildBytes - pos) - - case t: CTHRESHOLD => - // Verifier Step 2: If the node is THRESHOLD, - // evaluate the polynomial Q(x) at points 1, 2, ..., n to get challenges for child 1, 2, ..., n, respectively. - - // Read the polynomial -- it has n-k coefficients - val endPolyPos = pos + chalLen + hashSize * (t.children.length - t.k) - val polynomial = GF2_192_Poly.fromByteArray(challenge, bytes.slice(pos + chalLen, endPolyPos)) - - - val (seq, finalPos, _) = t.children.foldLeft((Seq[UncheckedSigmaTree](), endPolyPos, 1)) { - case ((s, p, childIndex), child) => - val (rewrittenChild, consumed) = traverseNode(child, bytes, p, Some(Challenge @@ polynomial.evaluate(childIndex.toByte).toByteArray)) - (s :+ rewrittenChild, p + consumed, childIndex + 1) - } - - // Verifier doesn't need the polynomial anymore -- hence pass in None - CThresholdUncheckedNode(challenge, seq, t.k, None) -> (finalPos - pos) - } + /** Helper method to read requested or remaining bytes from the reader. */ + def readBytesChecked(r: SigmaByteReader, numRequestedBytes: Int, onError: String => Unit): Array[Byte] = { + val bytes = r.getBytesUnsafe(numRequestedBytes) + if (bytes.length != numRequestedBytes) { + val hex = Base16.encode(r.getAllBufferBytes) + onError(hex) } + bytes + } - if (bytes.isEmpty) - NoProof - else - // Verifier step 1: Read the root challenge from the proof. - traverseNode(exp, bytes, 0, challengeOpt = None)._1 // get the root hash, then call + /** Verifier Step 2: In a top-down traversal of the tree, obtain the challenges for the + * children of every non-leaf node by reading them from the proof or computing them. + * Verifier Step 3: For every leaf node, read the response z provided in the proof. + * + * @param exp sigma proposition which defines the structure of bytes from the reader + * @param r reader to extract challenges from + * @param challengeOpt if non-empty, then the challenge has been computed for this node + * by its parent; else it needs to be read from the proof (via reader) + * @return An instance of [[UncheckedSigmaTree]] + * + * HOTSPOT: don't beautify the code + * Note, `null` is used instead of Option to avoid allocations. + */ + def parseAndComputeChallenges( + exp: SigmaBoolean, + r: SigmaByteReader, + challengeOpt: Challenge = null): UncheckedSigmaTree = { + // 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")) + } else { + challengeOpt + } + + exp match { + case dl: ProveDlog => + // Verifier Step 3: For every leaf node, read the response z provided in the proof. + val z_bytes = readBytesChecked(r, order, hex => warn(s"Invalid z bytes for $dl: $hex")) + val z = BigIntegers.fromUnsignedByteArray(z_bytes) + UncheckedSchnorr(dl, None, challenge, SecondDLogProverMessage(z)) + + case dh: ProveDHTuple => + // Verifier Step 3: For every leaf node, read the response z provided in the proof. + 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)) + + case and: CAND => + // Verifier Step 2: If the node is AND, then all of its children get e_0 as the challenge + val nChildren = and.children.length + val children = new Array[UncheckedSigmaTree](nChildren) + cfor(0)(_ < nChildren, _ + 1) { i => + children(i) = parseAndComputeChallenges(and.children(i), r, challenge) + } + CAndUncheckedNode(challenge, children) + + case or: COR => + // Verifier Step 2: If the node is OR, then each of its children except rightmost + // one gets the challenge given in the proof for that node. + // The rightmost child gets a challenge computed as an XOR of the challenges of all the other children and e_0. + + // 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 = new Array[UncheckedSigmaTree](nChildren) + val xorBuf = challenge.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 + } + val lastChild = or.children(iLastChild) + + // use the computed XOR for last child's challenge + children(iLastChild) = parseAndComputeChallenges( + lastChild, r, challengeOpt = Challenge @@ xorBuf) + + COrUncheckedNode(challenge, children) + + case th: CTHRESHOLD => + // Verifier Step 2: If the node is THRESHOLD, + // evaluate the polynomial Q(x) at points 1, 2, ..., n to get challenges for child 1, 2, ..., n, respectively. + + // Read the polynomial -- it has n-k coefficients + val nChildren = th.children.length + val nCoefs = nChildren - th.k + val coeffBytes = readBytesChecked(r, hashSize * nCoefs, + hex => warn(s"Invalid coeffBytes for $th: $hex")) + val polynomial = GF2_192_Poly.fromByteArray(challenge, coeffBytes) + + val children = new Array[UncheckedSigmaTree](nChildren) + cfor(0)(_ < nChildren, _ + 1) { i => + val c = Challenge @@ polynomial.evaluate((i + 1).toByte).toByteArray + children(i) = parseAndComputeChallenges(th.children(i), r, c) + } + + CThresholdUncheckedNode(challenge, children, th.k, Some(polynomial)) + } } + } \ No newline at end of file diff --git a/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala b/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala index be1df762fd..b8bd59dc66 100644 --- a/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala +++ b/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala @@ -2,11 +2,11 @@ package sigmastate import java.util -import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, ProveDlog, SecondDLogProverMessage} +import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, SecondDLogProverMessage, ProveDlog} import sigmastate.basics.VerifierMessage.Challenge import sigmastate.Values.SigmaBoolean import gf2t.GF2_192_Poly -import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, ProveDHTuple, SecondDiffieHellmanTupleProverMessage} +import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple, FirstDiffieHellmanTupleProverMessage} sealed trait UncheckedTree extends ProofTree @@ -18,10 +18,14 @@ sealed trait UncheckedSigmaTree extends UncheckedTree { trait UncheckedConjecture extends UncheckedSigmaTree with ProofTreeConjecture { - override def equals(obj: Any): Boolean = obj match { + override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { case x: UncheckedConjecture => util.Arrays.equals(challenge, x.challenge) && children == x.children - } + case _ => false + }) + + override def hashCode(): Int = + 31 * util.Arrays.hashCode(challenge) + children.hashCode() } trait UncheckedLeaf[SP <: SigmaBoolean] extends UncheckedSigmaTree with ProofTreeLeaf { @@ -34,12 +38,21 @@ case class UncheckedSchnorr(override val proposition: ProveDlog, secondMessage: SecondDLogProverMessage) extends UncheckedLeaf[ProveDlog] { - override def equals(obj: Any): Boolean = obj match { + override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { case x: UncheckedSchnorr => - util.Arrays.equals(challenge, x.challenge) && - commitmentOpt == x.commitmentOpt && + // NOTE, proposition is not compared because it is included into challenge + // like `challenge = hash(prop ++ msg)` + commitmentOpt == x.commitmentOpt && + util.Arrays.equals(challenge, x.challenge) && secondMessage == x.secondMessage case _ => false + }) + + override def hashCode(): Int = { + var h = commitmentOpt.hashCode() + h = 31 * h + util.Arrays.hashCode(challenge) + h = 31 * h + secondMessage.hashCode() + h } } @@ -50,12 +63,21 @@ case class UncheckedDiffieHellmanTuple(override val proposition: ProveDHTuple, secondMessage: SecondDiffieHellmanTupleProverMessage) extends UncheckedLeaf[ProveDHTuple] { - override def equals(obj: Any): Boolean = obj match { + override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { case x: UncheckedDiffieHellmanTuple => - proposition == x.proposition && - commitmentOpt == x.commitmentOpt && + // NOTE, proposition is not compared because it is included into challenge + // like `challenge = hash(prop ++ msg)` + commitmentOpt == x.commitmentOpt && util.Arrays.equals(challenge, x.challenge) && secondMessage == x.secondMessage + case _ => false + }) + + override def hashCode(): Int = { + var h = commitmentOpt.hashCode() + h = 31 * h + util.Arrays.hashCode(challenge) + h = 31 * h + secondMessage.hashCode() + h } } @@ -84,6 +106,7 @@ case class CThresholdUncheckedNode(override val challenge: Challenge, 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 => util.Arrays.equals(challenge, other.challenge) && @@ -92,4 +115,12 @@ case class CThresholdUncheckedNode(override val challenge: Challenge, polynomialOpt == other.polynomialOpt case _ => false }) + + override def hashCode(): Int = { + var h = util.Arrays.hashCode(challenge) + h = 31 * h + children.hashCode + h = 31 * h + k.hashCode() + h = 31 * h + polynomialOpt.hashCode() + h + } } diff --git a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala index 0cf1887ca1..7cbe19f3d6 100644 --- a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala +++ b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala @@ -2,13 +2,15 @@ package sigmastate import java.math.BigInteger -import com.google.common.primitives.Shorts import gf2t.GF2_192_Poly +import sigmastate.Values.{ErgoTree, SigmaBoolean, SigmaPropConstant} import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, ProveDlog} import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.Values.{ErgoTree, SigmaBoolean, SigmaPropConstant} import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, FirstProverMessage, ProveDHTuple} import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer +import sigmastate.serialization.SigmaSerializer +import sigmastate.utils.SigmaByteWriter +import spire.syntax.all.cfor import scala.language.existentials @@ -197,49 +199,64 @@ case class UnprovenDiffieHellmanTuple(override val proposition: ProveDHTuple, override def withPosition(updatedPosition: NodePosition) = this.copy(position = updatedPosition) } - -/** - * Prover Step 7: Convert the tree to a string s for input to the Fiat-Shamir hash function. - * The conversion should be such that the tree can be unambiguously parsed and restored given the string. - * For each non-leaf node, the string should contain its type (OR or AND). - * For each leaf node, the string should contain the Sigma-protocol statement being proven and the commitment. - * The string should not contain information on whether a node is marked "real" or "simulated", - * and should not contain challenges, responses, or the real/simulated flag for any node. - * - */ // TODO coverage (8h): write a test that restores the tree from this string and check that the result is equal, // in order to make sure this conversion is unambiguous object FiatShamirTree { - val internalNodePrefix = 0: Byte - val leafPrefix = 1: Byte + val internalNodePrefix: Byte = 0 + val leafPrefix: Byte = 1 + /** Prover Step 7: Convert the tree to a byte array `s` for input to the Fiat-Shamir hash + * function. + * See the other overload for details. */ def toBytes(tree: ProofTree): Array[Byte] = { + val w = SigmaSerializer.startWriter() + toBytes(tree, w) + w.toBytes + } - def traverseNode(node: ProofTree): Array[Byte] = node match { - case l: ProofTreeLeaf => - val propTree = ErgoTree.withSegregation(SigmaPropConstant(l.proposition)) - val propBytes = DefaultSerializer.serializeErgoTree(propTree) - val commitmentBytes = l.commitmentOpt.get.bytes - leafPrefix +: - ((Shorts.toByteArray(propBytes.length.toShort) ++ propBytes) ++ - (Shorts.toByteArray(commitmentBytes.length.toShort) ++ commitmentBytes)) - - case c: ProofTreeConjecture => - val childrenCountBytes = Shorts.toByteArray(c.children.length.toShort) - val conjBytes = Array(internalNodePrefix, c.conjectureType.id.toByte) - val thresholdByte = c match { - case unproven: CThresholdUnproven => - Array(unproven.k.toByte) - case unchecked: CThresholdUncheckedNode => - Array(unchecked.k.toByte) - case _ => Array() - } - - c.children.foldLeft(conjBytes ++ thresholdByte ++ childrenCountBytes) { case (acc, ch) => - acc ++ traverseNode(ch) - } - } - - traverseNode(tree) + /** Prover Step 7: Convert the tree to a byte array `s` for input to the Fiat-Shamir hash + * function. The conversion should be such that the tree can be unambiguously parsed and + * restored given the array. + * For each non-leaf node, the string should contain its type (OR or AND). + * For each leaf node, the string should contain the Sigma-protocol statement being + * proven and the commitment. + * The string should not contain information on whether a node is marked "real" or + * "simulated", and should not contain challenges, responses and/or the real/simulated + * flag for any node. + * + * @param tree the tree to take commitments from + * @param w writer which is used for serialization + * + * HOTSPOT: don't beautify the code + */ + def toBytes(tree: ProofTree, w: SigmaByteWriter): Unit = tree match { + case l: ProofTreeLeaf => + val propTree = ErgoTree.withSegregation(SigmaPropConstant(l.proposition)) + val propBytes = DefaultSerializer.serializeErgoTree(propTree) + val commitmentBytes = l.commitmentOpt.get.bytes + w.put(leafPrefix) + w.putShortBytes(propBytes.length.toShort) + w.putBytes(propBytes) + w.putShortBytes(commitmentBytes.length.toShort) + w.putBytes(commitmentBytes) + + case c: ProofTreeConjecture => + w.put(internalNodePrefix) + w.put(c.conjectureType.id.toByte) + c match { + case unproven: CThresholdUnproven => + w.put(unproven.k.toByte) + case unchecked: CThresholdUncheckedNode => + w.put(unchecked.k.toByte) + case _ => + } + val childrenCount = c.children.length.toShort + w.putShortBytes(childrenCount) + + val cs = c.children.toArray + cfor(0)(_ < cs.length, _ + 1) { i => + val child = cs(i) + toBytes(child, w) + } } } diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 63c6deda93..86512a5e13 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -28,7 +28,7 @@ import spire.syntax.all.cfor import scala.language.implicitConversions import scala.reflect.ClassTag -import sigmastate.lang.DefaultSigmaBuilder._ +import sigmastate.lang.CheckingSigmaBuilder._ import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.serialization.transformers.ProveDHTupleSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} @@ -173,7 +173,7 @@ object Values { trait Constant[+S <: SType] extends EvaluatedValue[S] {} case class ConstantNode[S <: SType](value: S#WrappedType, tpe: S) extends Constant[S] { - assert(Constant.isCorrectType(value, tpe), s"Invalid type of constant value $value, expected type $tpe") + require(Constant.isCorrectType(value, tpe), s"Invalid type of constant value $value, expected type $tpe") override def companion: ValueCompanion = Constant override def opCode: OpCode = companion.opCode override def opName: String = s"Const" @@ -585,36 +585,27 @@ object Values { case _: ProveDlog => CostTable.proveDlogEvalCost case _: ProveDHTuple => CostTable.proveDHTupleEvalCost case and: CAND => - val children = and.children.toArray - val nChildren = children.length - var sum = 0 - cfor(0)(_ < nChildren, _ + 1) { i => - val c = estimateCost(children(i)) - sum = Math.addExact(sum, c) - } - sum - case or: COR => - val children = or.children.toArray - val nChildren = children.length - var sum = 0 - cfor(0)(_ < nChildren, _ + 1) { i => - val c = estimateCost(children(i)) - sum = Math.addExact(sum, c) - } - sum + childrenCost(and.children) + case or: COR => + childrenCost(or.children) case th: CTHRESHOLD => - val children = th.children.toArray - val nChildren = children.length - var sum = 0 - cfor(0)(_ < nChildren, _ + 1) { i => - val c = estimateCost(children(i)) - sum = Math.addExact(sum, c) - } - sum + childrenCost(th.children) case _ => CostTable.MinimalCost } + /** Compute the total cost of the given children. */ + private def childrenCost(children: Seq[SigmaBoolean]): Int = { + val childrenArr = children.toArray + val nChildren = childrenArr.length + var sum = 0 + cfor(0)(_ < nChildren, _ + 1) { i => + val c = estimateCost(childrenArr(i)) + sum = Math.addExact(sum, c) + } + sum + } + /** HOTSPOT: don't beautify this code */ object serializer extends SigmaSerializer[SigmaBoolean, SigmaBoolean] { val dhtSerializer = ProveDHTupleSerializer(ProveDHTuple.apply) diff --git a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala index 256290ef60..9b7ad8a88c 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala @@ -21,6 +21,7 @@ import spire.syntax.all.cfor import scala.util.{Success, Failure} import scalan.{Nullable, RType} import scorex.crypto.hash.{Digest32, Sha256, Blake2b256} +import sigmastate.Values.ErgoTree.EmptyConstants import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.basics.ProveDHTuple import sigmastate.lang.Terms.OperationId @@ -70,7 +71,8 @@ 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 - val ergoTree = ErgoTree.fromSigmaBoolean(sigmaTree) + val root = sigmaTree.toSigmaProp + val ergoTree = new ErgoTree(ErgoTree.DefaultHeader, EmptyConstants, Right(root), 0, null, None) val bytes = DefaultSerializer.serializeErgoTree(ergoTree) Colls.fromArray(bytes) } @@ -96,6 +98,24 @@ case class CSigmaProp(sigmaTree: SigmaBoolean) extends SigmaProp with WrapperOf[ override def toString: String = s"SigmaProp(${wrappedValue.showToString})" } +/** Implementation of the [[special.sigma.AvlTreeVerifier]] trait based on + * [[scorex.crypto.authds.avltree.batch.BatchAVLVerifier]]. + * + * @see BatchAVLVerifier, AvlTreeVerifier + */ +class CAvlTreeVerifier(startingDigest: ADDigest, + proof: SerializedAdProof, + override val keyLength: Int, + override val valueLengthOpt: Option[Int]) + extends BatchAVLVerifier[Digest32, Blake2b256.type]( + startingDigest, proof, keyLength, valueLengthOpt) + with AvlTreeVerifier { + override def treeHeight: Int = rootNodeHeight + + /** Override default logging which outputs stack trace to the console. */ + override protected def logError(t: Throwable): Unit = {} +} + /** A default implementation of [[AvlTree]] interface. * @see [[AvlTree]] for detailed descriptions */ @@ -129,14 +149,10 @@ case class CAvlTree(treeData: AvlTreeData) extends AvlTree with WrapperOf[AvlTre this.copy(treeData = td) } - private def createVerifier(proof: Coll[Byte]): BatchAVLVerifier[Digest32, Blake2b256.type] = { + override def createVerifier(proof: Coll[Byte]): AvlTreeVerifier = { val adProof = SerializedAdProof @@ proof.toArray - val bv = new BatchAVLVerifier[Digest32, Blake2b256.type]( - treeData.digest, adProof, - treeData.keyLength, treeData.valueLengthOpt) { - /** Override default logging which outputs stack trace to the console. */ - override protected def logError(t: Throwable): Unit = {} - } + val bv = new CAvlTreeVerifier( + treeData.digest, adProof, treeData.keyLength, treeData.valueLengthOpt) bv } @@ -334,6 +350,7 @@ case class CostingBox(isCost: Boolean, val ebox: ErgoBox) extends Box with Wrapp override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj != null && ( obj match { case obj: Box => util.Arrays.equals(id.toArray, obj.id.toArray) + // TODO v5.0 HF: return false when obj in not Box instead of throwing an exception })) } @@ -656,9 +673,8 @@ class CostingSigmaDslBuilder extends TestSigmaDslBuilder { dsl => CSigmaProp(dht) } - override def groupGenerator: GroupElement = { - this.GroupElement(CryptoConstants.dlogGroup.generator) - } + private lazy val _generatorElement = this.GroupElement(CryptoConstants.dlogGroup.generator) + override def groupGenerator: GroupElement = _generatorElement /** * @return the identity of the Dlog group used in ErgoTree @@ -669,8 +685,7 @@ class CostingSigmaDslBuilder extends TestSigmaDslBuilder { dsl => override def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], - newValues: Coll[T]) - (implicit cT: RType[T]): Coll[Byte] = { + newValues: Coll[T]): Coll[Byte] = { val typedNewVals = newValues.toArray.map(v => TransformingSigmaBuilder.liftAny(v) match { case Nullable(v) => v case _ => sys.error(s"Cannot evaluate substConstants($scriptBytes, $positions, $newValues): cannot lift value $v") @@ -705,6 +720,7 @@ case class CostingDataContext( lastBlockUtxoRootHash: AvlTree, _minerPubKey: Coll[Byte], vars: Coll[AnyValue], + override val activatedScriptVersion: Byte, var isCost: Boolean) extends Context { @inline override def builder: SigmaDslBuilder = CostingSigmaDslBuilder @@ -800,3 +816,4 @@ case class CostingDataContext( this.copy(vars = newVars) } } + diff --git a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala index 0aaab8b302..ce7107476e 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala @@ -197,7 +197,7 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => case OM.getOrElse(_, _) => OptionGetOrElseCode case OM.fold(_, _, _) => MethodCallCode case OM.isDefined(_) => OptionIsDefinedCode - case SDBM.substConstants(_, _, _, _, _) => SubstConstantsCode + case SDBM.substConstants(_, _, _, _) => SubstConstantsCode case SDBM.longToByteArray(_, _) => LongToByteArrayCode case SDBM.byteArrayToBigInt(_, _) => ByteArrayToBigIntCode case SDBM.byteArrayToLong(_, _) => ByteArrayToLongCode @@ -572,14 +572,9 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => case SDBM.substConstants(_, In(input: special.collection.Coll[Byte]@unchecked), In(positions: special.collection.Coll[Int]@unchecked), - In(newVals: special.collection.Coll[Any]@unchecked), _) => - // TODO refactor: call sigmaDslBuilderValue.substConstants - val typedNewVals = newVals.toArray.map(v => builder.liftAny(v) match { - case Nullable(v) => v - case _ => sys.error(s"Cannot evaluate substConstants($input, $positions, $newVals): cannot lift value $v") - }) - val byteArray = SubstConstants.eval(input.toArray, positions.toArray, typedNewVals)(sigmaDslBuilderValue.validationSettings) - out(sigmaDslBuilderValue.Colls.fromArray(byteArray)) + In(newVals: special.collection.Coll[Any]@unchecked)) => + val res = sigmaDslBuilderValue.substConstants(input, positions, newVals) + out(res) case CBM.replicate(In(b: special.collection.CollBuilder), In(n: Int), xSym @ In(x)) => out(b.replicate(n, x)(asType[Any](xSym.elem.sourceType))) diff --git a/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala b/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala index 69b8f11f7a..9f0848ecd6 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala @@ -3,12 +3,12 @@ package sigmastate.eval import java.math.BigInteger import scalan.RType -import sigmastate.{SCollection, SType, SCollectionType} +import sigmastate.{SCollectionType, SCollection, SType} import sigmastate.Values.{Constant, ConstantNode} -import sigmastate.lang.DefaultSigmaBuilder +import sigmastate.lang.CheckingSigmaBuilder import special.collection.Coll import special.sigma._ -import SType.AnyOps +import sigmastate.SType.AnyOps import org.ergoplatform.ErgoBox import spire.syntax.all._ @@ -68,7 +68,7 @@ object Extensions { implicit class DslDataOps[A](data: A)(implicit tA: RType[A]) { def toTreeData: Constant[SType] = { - DefaultSigmaBuilder.mkConstant(data.asWrappedType, Evaluation.rtypeToSType(tA)) + CheckingSigmaBuilder.mkConstant(data.asWrappedType, Evaluation.rtypeToSType(tA)) } } diff --git a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala index 3f5a6e41e5..f5b59538c2 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala @@ -97,7 +97,7 @@ trait IRContext extends Evaluation with TreeBuilding { estimatedCost } - /** TODO soft-fork: Version Based Costing + /* TODO soft-fork: Version Based Costing * The following is based on ErgoTree.header checks performed during deserialization and * described in `ErgoTreeSerializer` * The next version should ensure that v2 node contained both old and new version of casting @@ -115,6 +115,14 @@ trait IRContext extends Evaluation with TreeBuilding { * And taking into account the cleaning of the garbage and cutting the blockchain history, * the old scripts at some point will die out of the blockchain. */ + /** Increase the cost using the given `costF` graph starting from `initCost`. + * The total cost is limited by `maxCost` + * @param ctx script execution context + * @param costF graph of cost formula which predicts script execution cost in the given ctx + * @param maxCost limit on the total cost + * @param initCost initial cost, which is increased by the additional cost computed by costF + * @return new total cost if Success, or exception wrapped in Failure + */ def checkCostWithContext(ctx: SContext, costF: Ref[((Context, (Int, Size[Context]))) => Int], maxCost: Long, initCost: Long): Try[Int] = Try { val costFun = compile[(SContext, (Int, SSize[SContext])), Int, (Context, (Int, Size[Context])), Int]( @@ -127,10 +135,7 @@ trait IRContext extends Evaluation with TreeBuilding { } val scaledCost = JMath.multiplyExact(estimatedCost.toLong, CostTable.costFactorIncrease.toLong) / CostTable.costFactorDecrease - val totalCost = JMath.addExact(initCost, scaledCost) - if (totalCost > maxCost) { - throw new CostLimitException(totalCost, Evaluation.msgCostLimitError(totalCost, maxCost), None) - } + val totalCost = Evaluation.addCostChecked(initCost, scaledCost, maxCost) totalCost.toInt } diff --git a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala index d6457d0fe5..acb955264a 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala @@ -1819,7 +1819,7 @@ trait RuntimeCosting extends CostingRules { IR: IRContext => RCCostedColl(values, costs, sizes, cost) case SubstConstants(InCollByte(bytes), InCollInt(positions), InCollAny(newValues)) => - val values = sigmaDslBuilder.substConstants(bytes.values, positions.values, newValues.values)(AnyElement) + val values = sigmaDslBuilder.substConstants(bytes.values, positions.values, newValues.values) val len = bytes.size.dataSize + newValues.size.dataSize val cost = opCost(values, Array(bytes.cost, positions.cost, newValues.cost), perKbCostOf(node, len)) mkCostedColl(values, len.toInt, cost) diff --git a/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala b/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala index ed34043c1d..32c9c303a8 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala @@ -348,7 +348,7 @@ trait TreeBuilding extends RuntimeCosting { IR: IRContext => mkByteArrayToLong(recurse(colSym)) case SDBM.decodePoint(_, colSym) => mkDecodePoint(recurse(colSym)) - case SDBM.substConstants(_, In(scriptBytes), In(positions), In(newValues), _) => + case SDBM.substConstants(_, In(scriptBytes), In(positions), In(newValues)) => mkSubstConst(scriptBytes.asByteArray, positions.asIntArray, newValues.asCollection[SType]) case Def(IfThenElseLazy(condSym, thenPSym, elsePSym)) => diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index d94a1bb6e2..bdb379f72f 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -1,27 +1,27 @@ package sigmastate.interpreter -import java.util import java.lang.{Math => JMath} +import java.util -import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, rule, everywherebu} +import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{everywherebu, rule, strategy} import org.bitbucket.inkytonik.kiama.rewriting.Strategy import org.ergoplatform.validation.SigmaValidationSettings -import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, DLogInteractiveProver} +import org.ergoplatform.validation.ValidationRules._ +import scalan.util.BenchmarkUtil import scorex.util.ScorexLogging import sigmastate.SCollection.SByteArray import sigmastate.Values._ -import sigmastate.eval.{IRContext, Evaluation} -import sigmastate.lang.Terms.ValueOps +import sigmastate.basics.DLogProtocol.{DLogInteractiveProver, FirstDLogProverMessage} import sigmastate.basics._ -import sigmastate.interpreter.Interpreter.{VerificationResult, ScriptEnv, WhenSoftForkReductionResult} -import sigmastate.lang.exceptions.{InterpreterException, CostLimitException} -import sigmastate.serialization.{ValueSerializer, SigmaSerializer} +import sigmastate.eval.{Evaluation, IRContext} +import sigmastate.interpreter.Interpreter.{ScriptEnv, VerificationResult, WhenSoftForkReductionResult} +import sigmastate.lang.Terms.ValueOps +import sigmastate.lang.exceptions.{CostLimitException, InterpreterException} +import sigmastate.serialization.{SigmaSerializer, ValueSerializer} +import sigmastate.utils.Helpers +import sigmastate.utils.Helpers._ import sigmastate.utxo.DeserializeContext import sigmastate.{SType, _} -import org.ergoplatform.validation.ValidationRules._ -import scalan.{MutableLazy, Nullable} -import scalan.util.BenchmarkUtil -import sigmastate.utils.Helpers._ import scala.util.{Success, Try} @@ -57,11 +57,7 @@ trait Interpreter extends ScorexLogging { val script = ValueSerializer.deserialize(r) // Why ValueSerializer? read NOTE above val scriptComplexity = r.complexity - val currCost = JMath.addExact(context.initCost, scriptComplexity) - val remainingLimit = context.costLimit - currCost - if (remainingLimit <= 0) { - throw new CostLimitException(currCost, Evaluation.msgCostLimitError(currCost, context.costLimit), None) - } + val currCost = Evaluation.addCostChecked(context.initCost, scriptComplexity, context.costLimit) val ctx1 = context.withInitCost(currCost).asInstanceOf[CTX] (ctx1, script) } @@ -133,7 +129,7 @@ trait Interpreter extends ScorexLogging { implicit val vs = context.validationSettings val maxCost = context.costLimit val initCost = context.initCost - trySoftForkable[ReductionResult](whenSoftFork = WhenSoftForkReductionResult) { + trySoftForkable[ReductionResult](whenSoftFork = WhenSoftForkReductionResult(initCost)) { val costingRes = doCostingEx(env, exp, true) val costF = costingRes.costF IR.onCostingResult(env, exp, costingRes) @@ -150,7 +146,7 @@ trait Interpreter extends ScorexLogging { CheckCalcFunc(IR)(calcF) val calcCtx = context.toSigmaContext(isCost = false) val res = Interpreter.calcResult(IR)(calcCtx, calcF) - SigmaDsl.toSigmaBoolean(res) -> estimatedCost + ReductionResult(SigmaDsl.toSigmaBoolean(res), estimatedCost) } } @@ -175,14 +171,14 @@ trait Interpreter extends ScorexLogging { */ def fullReduction(ergoTree: ErgoTree, context: CTX, - env: ScriptEnv): (SigmaBoolean, Long) = { + env: ScriptEnv): ReductionResult = { val prop = propositionFromErgoTree(ergoTree, context) prop match { case SigmaPropConstant(p) => val sb = SigmaDsl.toSigmaBoolean(p) val cost = SigmaBoolean.estimateCost(sb) val resCost = Evaluation.addCostChecked(context.initCost, cost, context.costLimit) - (sb, resCost) + ReductionResult(sb, resCost) case _ if !ergoTree.hasDeserialize => val r = precompiledScriptProcessor.getReducer(ergoTree, context.validationSettings) r.reduce(context) @@ -251,22 +247,18 @@ trait Interpreter extends ScorexLogging { // else proceed normally } - val initCost = JMath.addExact(ergoTree.complexity.toLong, context.initCost) - val remainingLimit = context.costLimit - initCost - if (remainingLimit <= 0) { - // TODO cover with tests (2h) - throw new CostLimitException(initCost, Evaluation.msgCostLimitError(initCost, context.costLimit), None) - } + val complexityCost = ergoTree.complexity.toLong + val initCost = Evaluation.addCostChecked(context.initCost, complexityCost, context.costLimit) val contextWithCost = context.withInitCost(initCost).asInstanceOf[CTX] - val (cProp, cost) = fullReduction(ergoTree, contextWithCost, env) + val res = fullReduction(ergoTree, contextWithCost, env) - val checkingResult = cProp match { + val checkingResult = res.value match { case TrivialProp.TrueProp => true case TrivialProp.FalseProp => false - case _ => verifySignature(cProp, message, proof) + case cProp => verifySignature(cProp, message, proof) } - checkingResult -> cost + checkingResult -> res.cost }) if (outputComputedResults) { res.foreach { case (_, cost) => @@ -290,14 +282,14 @@ trait Interpreter extends ScorexLogging { private def checkCommitments(sp: UncheckedSigmaTree, message: Array[Byte]): Boolean = { // Perform Verifier Step 4 val newRoot = computeCommitments(sp).get.asInstanceOf[UncheckedSigmaTree] - + val bytes = Helpers.concatArrays(FiatShamirTree.toBytes(newRoot), message) /** * Verifier Steps 5-6: Convert the tree to a string `s` for input to the Fiat-Shamir hash function, * using the same conversion as the prover in 7 * Accept the proof if the challenge at the root of the tree is equal to the Fiat-Shamir hash of `s` * (and, if applicable, the associated data). Reject otherwise. */ - val expectedChallenge = CryptoFunctions.hashFn(FiatShamirTree.toBytes(newRoot) ++ message) + val expectedChallenge = CryptoFunctions.hashFn(bytes) util.Arrays.equals(newRoot.challenge, expectedChallenge) } @@ -342,7 +334,7 @@ trait Interpreter extends ScorexLogging { context: CTX, proof: ProofT, message: Array[Byte]): Try[VerificationResult] = { - verify(Interpreter.emptyEnv, ergoTree, context, SigSerializer.toBytes(proof), message) + verify(Interpreter.emptyEnv, ergoTree, context, SigSerializer.toProofBytes(proof), message) } /** @@ -365,7 +357,12 @@ trait Interpreter extends ScorexLogging { checkCommitments(sp, message) } } catch { - case e: Exception => log.warn("Improper signature: ", e); false + case t: Throwable => + // TODO coverage: property("handle improper signature") doesn't lead to exception + // because the current implementation of parseAndComputeChallenges doesn't check + // signature format + log.warn("Improper signature: ", t); + false } } @@ -379,13 +376,19 @@ object Interpreter { type VerificationResult = (Boolean, Long) /** Result of ErgoTree reduction procedure (see `reduceToCrypto` and friends). - * The first component is the value of SigmaProp type which represents a statement - * verifiable via sigma protocol. - * The second component is the estimated cost of consumed by the contract execution. */ - type ReductionResult = (SigmaBoolean, Long) + * + * @param value the value of SigmaProp type which represents a logical statement + * verifiable via sigma protocol. + * @param cost the estimated cost of the contract execution. */ + case class ReductionResult(value: SigmaBoolean, cost: Long) + /** Represents properties of interpreter invocation. */ type ScriptEnv = Map[String, Any] + + /** Empty interpreter properties. */ val emptyEnv: ScriptEnv = Map.empty[String, Any] + + /** Property name used to store script name. */ val ScriptNameProp = "ScriptName" /** Maximum version of ErgoTree supported by this interpreter release. @@ -402,7 +405,7 @@ object Interpreter { /** The result of script reduction when soft-fork condition is detected by the old node, * in which case the script is reduced to the trivial true proposition and takes up 0 cost. */ - val WhenSoftForkReductionResult: ReductionResult = TrivialProp.TrueProp -> 0 + def WhenSoftForkReductionResult(cost: Long): ReductionResult = ReductionResult(TrivialProp.TrueProp, cost) /** Executes the given `calcF` graph in the given context. * @param IR container of the graph (see [[IRContext]]) @@ -445,6 +448,7 @@ object Interpreter { throw new Error(s"Context-dependent pre-processing should produce tree of type Boolean or SigmaProp but was $x") } + /** Helper method to throw errors from Interpreter. */ def error(msg: String) = throw new InterpreterException(msg) } \ No newline at end of file diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index d5124b5e43..4efa94ff54 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -33,8 +33,8 @@ trait ScriptReducer { /** Used as a fallback reducer when precompilation failed due to soft-fork condition. */ case object WhenSoftForkReducer extends ScriptReducer { - override def reduce(context: InterpreterContext): (Values.SigmaBoolean, Long) = { - WhenSoftForkReductionResult + override def reduce(context: InterpreterContext): ReductionResult = { + WhenSoftForkReductionResult(context.initCost) } } @@ -78,7 +78,7 @@ case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRC implicit val vs = context.validationSettings val maxCost = context.costLimit val initCost = context.initCost - trySoftForkable[ReductionResult](whenSoftFork = WhenSoftForkReductionResult) { + trySoftForkable[ReductionResult](whenSoftFork = WhenSoftForkReductionResult(initCost)) { val costF = costingRes.costF val costingCtx = context.toSigmaContext(isCost = true) val estimatedCost = IR.checkCostWithContext(costingCtx, costF, maxCost, initCost).getOrThrow @@ -87,7 +87,7 @@ case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRC val calcF = costingRes.calcF val calcCtx = context.toSigmaContext(isCost = false) val res = Interpreter.calcResult(IR)(calcCtx, calcF) - SigmaDsl.toSigmaBoolean(res) -> estimatedCost + ReductionResult(SigmaDsl.toSigmaBoolean(res), estimatedCost) } } } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala index da0f26dac6..a7b174ed6c 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala @@ -7,6 +7,7 @@ import org.bitbucket.inkytonik.kiama.attribution.AttributionCore import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{everywherebu, everywheretd, rule} import org.bitbucket.inkytonik.kiama.rewriting.Strategy import scalan.util.CollectionUtil._ +import sigmastate.TrivialProp.{FalseProp, TrueProp} import sigmastate.Values._ import sigmastate._ import sigmastate.basics.DLogProtocol._ @@ -90,7 +91,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor // 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(propBytes ++ message) + val rootChallenge = Challenge @@ CryptoFunctions.hashFn(Helpers.concatArrays(propBytes, message)) val step8 = step6.withChallenge(rootChallenge) // Prover Step 9: complete the proof by computing challenges at real nodes and additionally responses at real leaves @@ -116,7 +117,6 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor context: CTX, message: Array[Byte], hintsBag: HintsBag = HintsBag.empty): Try[CostedProverResult] = Try { - import TrivialProp._ val initCost = ergoTree.complexity + context.initCost val remainingLimit = context.costLimit - initCost @@ -126,9 +126,16 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor val ctxUpdInitCost = context.withInitCost(initCost).asInstanceOf[CTX] - val (reducedProp, cost) = fullReduction(ergoTree, ctxUpdInitCost, env) + val res = fullReduction(ergoTree, ctxUpdInitCost, env) + val proof = generateProof(res.value, message, hintsBag) - val proofTree = reducedProp match { + CostedProverResult(proof, ctxUpdInitCost.extension, res.cost) + } + + def generateProof(sb: SigmaBoolean, + message: Array[Byte], + hintsBag: HintsBag): Array[Byte] = { + val proofTree = sb match { case TrueProp => NoProof case FalseProp => error("Script reduced to false") case sigmaTree => @@ -136,10 +143,9 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor prove(unprovenTree, message, hintsBag) } // Prover Step 10: output the right information into the proof - val proof = SigSerializer.toBytes(proofTree) - CostedProverResult(proof, ctxUpdInitCost.extension, cost) + val proof = SigSerializer.toProofBytes(proofTree) + proof } - /** * Prover Step 1: This step will mark as "real" every node for which the prover can produce a real proof. * This step may mark as "real" more nodes than necessary if the prover has more than the minimal @@ -593,7 +599,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor hintsBag: HintsBag): Try[Array[Byte]] = Try { val unprovenTree = convertToUnproven(sigmaTree) val proofTree = prove(unprovenTree, message, hintsBag) - SigSerializer.toBytes(proofTree) + SigSerializer.toProofBytes(proofTree) } } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverUtils.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverUtils.scala index 25f8be0a65..6c9c0cf279 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverUtils.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverUtils.scala @@ -17,7 +17,7 @@ trait ProverUtils extends Interpreter { def generateCommitmentsFor(ergoTree: ErgoTree, context: CTX, generateFor: Seq[SigmaBoolean]): HintsBag = { - val reducedTree = fullReduction(ergoTree, context, Interpreter.emptyEnv)._1 + val reducedTree = fullReduction(ergoTree, context, Interpreter.emptyEnv).value generateCommitmentsFor(reducedTree, generateFor) } @@ -79,7 +79,7 @@ trait ProverUtils extends Interpreter { proof: Array[Byte], realSecretsToExtract: Seq[SigmaBoolean], simulatedSecretsToExtract: Seq[SigmaBoolean] = Seq.empty): HintsBag = { - val reducedTree = fullReduction(ergoTree, context, Interpreter.emptyEnv)._1 + val reducedTree = fullReduction(ergoTree, context, Interpreter.emptyEnv).value bagForMultisig(context, reducedTree, proof, realSecretsToExtract, simulatedSecretsToExtract) } diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala index a322718a17..5b220109f8 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala @@ -4,10 +4,10 @@ import java.math.BigInteger import org.ergoplatform.ErgoBox import org.ergoplatform.ErgoBox.RegisterId -import sigmastate.SCollection.{SByteArray, SIntArray} +import sigmastate.SCollection.{SIntArray, SByteArray} import sigmastate.Values._ import sigmastate._ -import sigmastate.lang.Constraints.{TypeConstraint2, onlyNumeric2, sameType2} +import sigmastate.lang.Constraints._ import sigmastate.lang.Terms._ import sigmastate.lang.exceptions.ConstraintFailed import sigmastate.serialization.OpCodes @@ -19,13 +19,20 @@ import sigmastate.interpreter.CryptoConstants.EcPointType import special.collection.Coll import sigmastate.lang.SigmaTyper.STypeSubst import sigmastate.serialization.OpCodes.OpCode -import special.sigma.{AvlTree, GroupElement, SigmaProp} +import special.sigma.{AvlTree, SigmaProp, GroupElement} import spire.syntax.all.cfor import scala.util.DynamicVariable -trait SigmaBuilder { +/** Abstract interface of ErgoTree node builders. + * Each method of the interface creates the corresponding ErgoTree node. + * The signatures of the methods reflect the constructors of the nodes. + * See the corresponding node classes for details. + */ +abstract class SigmaBuilder { + /** Dynamic variable used to pass SourceContext to the constructors of the node. + * Used in concrete implementations of this interface. */ val currentSrcCtx = new DynamicVariable[Nullable[SourceContext]](Nullable.None) def mkEQ[T <: SType](left: Value[T], right: Value[T]): Value[SBoolean.type] @@ -259,27 +266,69 @@ trait SigmaBuilder { case _ => Nullable.None } - - def unliftAny(value: SValue): Nullable[Any] = value match { - case Constant(v, _) => Nullable(v) - case _ => Nullable.None - } } +/** Standard implementation of [[SigmaBuilder]] interface in which most of the operations + * delegate common logic to [[equalityOp]], [[comparisonOp]] and [[arithOp]] with default + * implementation. + * Note, each method of this class uses current value of `currentSrcCtx` dynamic variable + * to attach SourceContext to the created node. Thus, it is a responsibility of the + * caller to provide valid value of the `currentSrcCtx` variable. (See for example how + * this variable is used in [[SigmaParser]].) + */ class StdSigmaBuilder extends SigmaBuilder { + /** Create equality operation using given operation arguments and the given node + * constructor. + * @param left operand of the operation (left sub-expression) + * @param right operand of the operation (right sub-expression) + * @param cons constructor of the node + */ protected def equalityOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = cons(left, right) + /** Create comparison operation using given operation arguments and the given node + * constructor. + * @param left operand of the operation (left sub-expression) + * @param right operand of the operation (right sub-expression) + * @param cons constructor of the node + */ protected def comparisonOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = cons(left, right) + /** Create arithmetic operation using given operation arguments and the given node + * constructor. + * @param left operand of the operation (left sub-expression) + * @param right operand of the operation (right sub-expression) + * @param cons constructor of the node + */ protected def arithOp[T <: SNumericType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = cons(left, right) + /** Helper method to check constraints on the arguments of the binary operation. + * + * @param left operand of the operation (left sub-expression) + * @param right operand of the operation (right sub-expression) + * @param constraints an array of constraints (should be WrappedArray (not List) for + * performance) + * + * HOTSPOT: called during script deserialization (don't beautify this code) + * @consensus + */ + final def check2[T <: SType](left: Value[T], + right: Value[T], + constraints: Seq[TypeConstraint2]): Unit = { + val n = constraints.length + cfor(0)(_ < n, _ + 1) { i => + val c = constraints(i) // to be efficient constraints should be WrappedArray (not List) + if (!c(left.tpe, right.tpe)) + throw new ConstraintFailed(s"Failed constraint $c for binary operation parameters ($left(tpe: ${left.tpe}), $right(tpe: ${right.tpe}))") + } + } + override def mkEQ[T <: SType](left: Value[T], right: Value[T]): Value[SBoolean.type] = equalityOp(left, right, EQ.apply[T]).withSrcCtx(currentSrcCtx.value) @@ -649,25 +698,18 @@ class StdSigmaBuilder extends SigmaBuilder { override def mkUnitConstant: Value[SUnit.type] = UnitConstant().withSrcCtx(currentSrcCtx.value) } -trait TypeConstraintCheck { - - /** HOTSPOT: called during script deserialization (don't beautify this code) - * @consensus +/** Builder which does automatic upcast of numeric arguments when necessary. + * The upcast is implemented by inserting additional Upcast nodes. + * It also performs checking of constrains. + * */ +class TransformingSigmaBuilder extends StdSigmaBuilder { + + /** Checks the types of left anf right arguments and if necessary inserts the upcast + * node. + * @param left operand of the operation (left sub-expression) + * @param right operand of the operation (right sub-expression) + * @return a pair (l,r) of the arguments appropriately upcasted. */ - def check2[T <: SType](left: Value[T], - right: Value[T], - constraints: Seq[TypeConstraint2]): Unit = { - val n = constraints.length - cfor(0)(_ < n, _ + 1) { i => - val c = constraints(i) // to be efficient constraints should be WrappedArray (not List) - if (!c(left.tpe, right.tpe)) - throw new ConstraintFailed(s"Failed constraint $c for binary operation parameters ($left(tpe: ${left.tpe}), $right(tpe: ${right.tpe}))") - } - } -} - -trait TransformingSigmaBuilder extends StdSigmaBuilder with TypeConstraintCheck { - private def applyUpcast[T <: SType](left: Value[T], right: Value[T]): (Value[T], Value[T]) = (left.tpe, right.tpe) match { case (t1: SNumericType, t2: SNumericType) if t1 != t2 => @@ -679,76 +721,98 @@ trait TransformingSigmaBuilder extends StdSigmaBuilder with TypeConstraintCheck (left, right) } + /** HOTSPOT: don't beautify the code. */ override protected def equalityOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - val (l, r) = applyUpcast(left, right) - check2(l, r, Array(sameType2)) + val t = applyUpcast(left, right) + val l = t._1; val r = t._2 + check2(l, r, SameTypeConstrain) cons(l, r) } + /** HOTSPOT: don't beautify the code. */ override protected def comparisonOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - check2(left, right, Array(onlyNumeric2)) - val (l, r) = applyUpcast(left, right) - check2(l, r, Array(sameType2)) + check2(left, right, OnlyNumericConstrain) + val t = applyUpcast(left, right) + val l = t._1; val r = t._2 + check2(l, r, SameTypeConstrain) cons(l, r) } + /** HOTSPOT: don't beautify the code. */ override protected def arithOp[T <: SNumericType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - val (l, r) = applyUpcast(left, right) - cons(l, r) + val t = applyUpcast(left, right) + cons(t._1, t._2) } } -trait CheckingSigmaBuilder extends StdSigmaBuilder with TypeConstraintCheck { +/** Builder which does checking of constraints on the numeric arguments of binary operations. */ +class CheckingSigmaBuilder extends StdSigmaBuilder { override protected def equalityOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - check2(left, right, Array(sameType2)) + check2(left, right, SameTypeConstrain) cons(left, right) } override protected def comparisonOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - check2(left, right, Array(onlyNumeric2, sameType2)) + check2(left, right, OnlyNumericAndSameTypeConstrain) cons(left, right) } override protected def arithOp[T <: SNumericType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - check2(left, right, Array(sameType2)) + check2(left, right, SameTypeConstrain) cons(left, right) } } +/** Standard builder which don't perform any additional transformations and checking. */ case object StdSigmaBuilder extends StdSigmaBuilder -case object CheckingSigmaBuilder extends StdSigmaBuilder with CheckingSigmaBuilder +/** Builder which performs checking of constraints on numeric operations. */ +case object CheckingSigmaBuilder extends CheckingSigmaBuilder + +/** Builder of ErgoTree nodes which is used in SigmaCompiler. */ +case object TransformingSigmaBuilder extends TransformingSigmaBuilder -case object DefaultSigmaBuilder extends StdSigmaBuilder with CheckingSigmaBuilder -case object TransformingSigmaBuilder extends StdSigmaBuilder with TransformingSigmaBuilder -case object DeserializationSigmaBuilder extends StdSigmaBuilder with TransformingSigmaBuilder +/** Builder of ErgoTree nodes which is used in deserializers. */ +case object DeserializationSigmaBuilder extends TransformingSigmaBuilder object Constraints { - type Constraint2 = (SType.TypeCode, SType.TypeCode) => Boolean - type TypeConstraint2 = (SType, SType) => Boolean - type ConstraintN = Seq[SType.TypeCode] => Boolean + /** Represents a constraint on arguments of binary operation. */ + abstract class TypeConstraint2 { + /** Returns true if the constraints is satisfied. */ + def apply(t1: SType, t2: SType): Boolean + } - def onlyNumeric2: TypeConstraint2 = { - case (_: SNumericType, _: SNumericType) => true - case _ => false + /** Checks that both arguments are numeric types. */ + object OnlyNumeric2 extends TypeConstraint2 { + override def apply(t1: SType, t2: SType): Boolean = t1 match { + case _: SNumericType => t2 match { + case _: SNumericType => true + case _ => false + } + case _ => false + } } - def sameType2: TypeConstraint2 = { - case (v1, v2) => v1.tpe == v2.tpe + /** Checks that both arguments have the same type. */ + object SameType2 extends TypeConstraint2 { + override def apply(t1: SType, t2: SType): Boolean = t1 == t2 } - def sameTypeN: ConstraintN = { tcs => tcs.tail.forall(_ == tcs.head) } + /** These constraints sets are allocated once here and reused in different places. */ + val SameTypeConstrain: Seq[TypeConstraint2] = Array(SameType2) + val OnlyNumericConstrain: Seq[TypeConstraint2] = Array(OnlyNumeric2) + val OnlyNumericAndSameTypeConstrain: Seq[TypeConstraint2] = Array(OnlyNumeric2, SameType2) } diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaParser.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaParser.scala index 83810260eb..9bd73d934f 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaParser.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaParser.scala @@ -6,7 +6,6 @@ import sigmastate._ import Values._ import scalan.Nullable import sigmastate.lang.Terms._ -import sigmastate.SCollection.SByteArray import sigmastate.lang.syntax.Basic._ import sigmastate.lang.syntax.{Core, Exprs} diff --git a/sigmastate/src/main/scala/sigmastate/sigmastate.scala b/sigmastate/src/main/scala/sigmastate/sigmastate.scala index f7b7003289..28b817a651 100644 --- a/sigmastate/src/main/scala/sigmastate/sigmastate.scala +++ b/sigmastate/src/main/scala/sigmastate/sigmastate.scala @@ -1,8 +1,8 @@ import sigmastate.Values.Value -import sigmastate.lang.DefaultSigmaBuilder +import sigmastate.lang.CheckingSigmaBuilder package object sigmastate { - import DefaultSigmaBuilder._ + import CheckingSigmaBuilder._ /** * SInt addition diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index a65cab2988..4f9f604d20 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -9,6 +9,7 @@ import scalan.RType.GeneralType import sigmastate.SType.{TypeCode, AnyOps} import sigmastate.interpreter.CryptoConstants import sigmastate.utils.Overloading.Overload1 +import sigmastate.utils.SparseArrayContainer import scalan.util.Extensions._ import sigmastate.Values._ import sigmastate.lang.Terms._ @@ -111,7 +112,8 @@ object SType { implicit val typeSigmaProp = SSigmaProp implicit val typeBox = SBox - implicit def typeCollection[V <: SType](implicit tV: V): SCollection[V] = SCollection[V] + /** Costructs a collection type with the given type of elements. */ + implicit def typeCollection[V <: SType](implicit tV: V): SCollection[V] = SCollection[V](tV) implicit val SigmaBooleanRType: RType[SigmaBoolean] = RType.fromClassTag(classTag[SigmaBoolean]) implicit val ErgoBoxRType: RType[ErgoBox] = RType.fromClassTag(classTag[ErgoBox]) @@ -165,8 +167,6 @@ object SType { SGlobal, SHeader, SPreHeader, SAvlTree, SGroupElement, SSigmaProp, SString, SBox, SUnit, SAny) - val typeCodeToType = allPredefTypes.map(t => t.typeCode -> t).toMap - /** A mapping of object types supporting MethodCall operations. For each serialized * typeId this map contains a companion object which can be used to access the list of * corresponding methods. @@ -185,7 +185,7 @@ object SType { * should be changed and SGlobal.typeId should be preserved. The regression tests in * `property("MethodCall Codes")` should pass. */ - // TODO v5.0 (h4): should contain all numeric types (including also SNumericType) + // TODO v6.0 (h4): 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( @@ -193,6 +193,47 @@ object SType { SAvlTree, SBox, SOption, SCollection, SBigInt ).map { t => (t.typeId, t) }.toMap + /** Checks that the type of the value corresponds to the descriptor `tpe`. + * If the value has complex structure only root type constructor is checked. + * NOTE, this method is used in ErgoTree evaluation to systematically check that each + * tree node evaluates to a value of the expected type. + * Shallow runtime checks are enough if: + * 1) ErgoTree is well-typed, i.e. each sub-expression has correct types (agree with + * the argument type). + * 2) `isValueOfType == true` for each tree leaf + * 3) `isValueOfType == true` for each sub-expression + * + * @param value value to check type + * @param tpe type descriptor to check value against + * @return true if the given `value` is of type tpe` + */ + def isValueOfType[T <: SType](x: Any, tpe: T): Boolean = tpe match { + case SBoolean => x.isInstanceOf[Boolean] + case SByte => x.isInstanceOf[Byte] + case SShort => x.isInstanceOf[Short] + case SInt => x.isInstanceOf[Int] + case SLong => x.isInstanceOf[Long] + case SBigInt => x.isInstanceOf[BigInt] + case SGroupElement => x.isInstanceOf[GroupElement] + case SSigmaProp => x.isInstanceOf[SigmaProp] + case SBox => x.isInstanceOf[Box] + case _: SCollectionType[_] => x.isInstanceOf[Coll[_]] + case _: SOption[_] => x.isInstanceOf[Option[_]] + case t: STuple => + if (t.items.length == 2) x.isInstanceOf[Tuple2[_,_]] + else sys.error(s"Unsupported tuple type $t") + case tF: SFunc => + if (tF.tDom.length == 1) x.isInstanceOf[Function1[_,_]] + else sys.error(s"Unsupported function type $tF") + case SContext => x.isInstanceOf[Context] + case SAvlTree => x.isInstanceOf[AvlTree] + case SGlobal => x.isInstanceOf[SigmaDslBuilder] + case SHeader => x.isInstanceOf[Header] + case SPreHeader => x.isInstanceOf[PreHeader] + case SUnit => x.isInstanceOf[Unit] + case _ => sys.error(s"Unknown type $tpe") + } + implicit class STypeOps(val tpe: SType) extends AnyVal { def isCollectionLike: Boolean = tpe.isInstanceOf[SCollection[_]] def isCollection: Boolean = tpe.isInstanceOf[SCollectionType[_]] @@ -203,15 +244,6 @@ object SType { def isAvlTree: Boolean = tpe.isInstanceOf[SAvlTree.type] def isFunc : Boolean = tpe.isInstanceOf[SFunc] def isTuple: Boolean = tpe.isInstanceOf[STuple] - def canBeTypedAs(expected: SType): Boolean = (tpe, expected) match { - case (NoType, _) => true - case (t1, t2) if t1 == t2 => true - case (f1: SFunc, f2: SFunc) => - val okDom = f1.tDom.size == f2.tDom.size && - f1.tDom.zip(f2.tDom).forall { case (d1, d2) => d1.canBeTypedAs(d2) } - val okRange = f1.tRange.canBeTypedAs(f2.tRange) - okDom && okRange - } /** Returns true if this type is numeric (Byte, Short, etc.) * @see [[sigmastate.SNumericType]] @@ -563,13 +595,15 @@ trait SNumericType extends SProduct { def isCastMethod (name: String): Boolean = castMethods.contains(name) /** Upcasts the given value of a smaller type to this larger type. + * Corresponds to section 5.1.2 Widening Primitive Conversion of Java Language Spec. * @param n numeric value to be converted * @return a value of WrappedType of this type descriptor's instance. - * @throw exception if `i` has actual type which is larger than this type. + * @throw exception if `n` has actual type which is larger than this type. */ def upcast(n: AnyVal): WrappedType /** Downcasts the given value of a larger type to this smaller type. + * Corresponds to section 5.1.3 Narrowing Primitive Conversion of Java Language Spec. * @param n numeric value to be converted * @return a value of WrappedType of this type descriptor's instance. * @throw exception if the actual value of `i` cannot fit into this type. @@ -578,15 +612,18 @@ trait SNumericType extends SProduct { /** Returns a type which is larger. */ @inline def max(that: SNumericType): SNumericType = - if (this.typeIndex > that.typeIndex) this else that + if (this.numericTypeIndex > that.numericTypeIndex) this else that - /** Number of bytes to store values of this type. */ - @inline private def typeIndex: Int = allNumericTypes.indexOf(this) + /** Numeric types are ordered by the number of bytes to store the numeric values. + * @return index in the array of all numeric types. */ + @inline protected def numericTypeIndex: Int override def toString: Idn = this.getClass.getSimpleName } 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 HF (4h): this typeId is now shadowed by SGlobal.typeId // see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/667 def typeId: TypeCode = 106: Byte @@ -672,6 +709,7 @@ case object SByte extends SPrimType with SEmbeddable with SNumericType with SMon override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 1 override def isConstantSize = true + override protected def numericTypeIndex: Int = 0 override def upcast(v: AnyVal): Byte = v match { case b: Byte => b case _ => sys.error(s"Cannot upcast value $v to the type $this") @@ -691,6 +729,7 @@ case object SShort extends SPrimType with SEmbeddable with SNumericType with SMo override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 2 override def isConstantSize = true + override protected def numericTypeIndex: Int = 1 override def upcast(v: AnyVal): Short = v match { case x: Byte => x.toShort case x: Short => x @@ -710,6 +749,7 @@ case object SInt extends SPrimType with SEmbeddable with SNumericType with SMono override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 4 override def isConstantSize = true + override protected def numericTypeIndex: Int = 2 override def upcast(v: AnyVal): Int = v match { case x: Byte => x.toInt case x: Short => x.toInt @@ -731,6 +771,7 @@ case object SLong extends SPrimType with SEmbeddable with SNumericType with SMon override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 8 override def isConstantSize = true + override protected def numericTypeIndex: Int = 3 override def upcast(v: AnyVal): Long = v match { case x: Byte => x.toLong case x: Short => x.toLong @@ -767,6 +808,8 @@ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SM val Max: BigInt = SigmaDsl.BigInt(CryptoConstants.dlogGroup.order) + override protected def numericTypeIndex: Int = 4 + override def upcast(v: AnyVal): BigInt = { val bi = v match { case x: Byte => BigInteger.valueOf(x.toLong) diff --git a/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala b/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala index cb79246333..4417b4412c 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala @@ -54,11 +54,16 @@ object Helpers { result } + /** Concatenates two arrays into a new resulting array. + * All items of both arrays are copied to the result using System.arraycopy. + */ def concatArrays[T:ClassTag](arr1: Array[T], arr2: Array[T]): Array[T] = { - val length: Int = arr1.length + arr2.length + val l1 = arr1.length + val l2 = arr2.length + val length: Int = l1 + l2 val result: Array[T] = new Array[T](length) - System.arraycopy(arr1, 0, result, 0, arr1.length) - System.arraycopy(arr2, 0, result, arr1.length, arr2.length) + System.arraycopy(arr1, 0, result, 0, l1) + System.arraycopy(arr2, 0, result, l1, l2) result } diff --git a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala index ee55690b3a..c3d1826742 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala @@ -96,6 +96,25 @@ class SigmaByteReader(val r: Reader, r.getBytes(size) } + /** Reads either the given of remaining number of bytes from this reader. + * This method is `unsafe` because it may return less than requested number of bytes. + * @param numRequestedBytes + */ + @inline final def getBytesUnsafe(numRequestedBytes: Int): Array[Byte] = { + checkPositionLimit() + val bytesToRead = Math.min(numRequestedBytes, remaining) + r.getBytes(bytesToRead) + } + + /** Returns all bytes of the underlying ByteBuffer. */ + private[sigmastate] def getAllBufferBytes: Array[Byte] = { + val savedPos = position + position = 0 + val res = getBytesUnsafe(remaining) + position = savedPos + res + } + @inline override def getBits(size: Int): Array[Boolean] = { checkPositionLimit() r.getBits(size) diff --git a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala index 733b4c348b..4c3ef03610 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala @@ -87,6 +87,14 @@ class SigmaByteWriter(val w: Writer, w.putBytes(xs); this } + /** Put the two bytes of the big-endian representation of the Short value into the + * writer. */ + @inline def putShortBytes(value: Short): this.type = { + w.put((value >> 8).toByte) + w.put(value.toByte) + this + } + @inline def putBits(xs: Array[Boolean]): this.type = { w.putBits(xs); this } @inline def putBits(xs: Array[Boolean], info: DataInfo[Bits]): this.type = { ValueSerializer.addArgInfo(info) diff --git a/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala b/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala index 771a8f21cb..743f95e90d 100644 --- a/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala +++ b/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala @@ -2,24 +2,26 @@ package org.ergoplatform import java.math.BigInteger -import org.ergoplatform.ErgoAddressEncoder.{hash256, MainnetNetworkPrefix, TestnetNetworkPrefix} +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 import sigmastate.basics.DLogProtocol -import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer -import sigmastate.serialization.ValueSerializer +import sigmastate.serialization.{GroupElementSerializer, ValueSerializer} import scorex.util.encode.Base58 -import sigmastate.{SigmaAnd, SType, CrossVersionProps} -import sigmastate.Values.{UnparsedErgoTree, Constant, EvaluatedValue, ByteArrayConstant, IntConstant, ErgoTree} +import sigmastate.{CrossVersionProps, SType, SigmaAnd} +import sigmastate.Values.{ByteArrayConstant, Constant, ErgoTree, EvaluatedValue, IntConstant, UnparsedErgoTree} import sigmastate.eval.IRContext import sigmastate.helpers._ import sigmastate.helpers.TestingHelpers._ +import sigmastate.interpreter.CryptoConstants.dlogGroup import sigmastate.interpreter.{ContextExtension, CostedProverResult} -import sigmastate.interpreter.Interpreter.{ScriptNameProp, ScriptEnv} +import sigmastate.interpreter.Interpreter.{ScriptEnv, ScriptNameProp} import sigmastate.lang.Terms.ValueOps -import sigmastate.lang.exceptions.{CosterException, CostLimitException} +import sigmastate.lang.exceptions.{CostLimitException, CosterException} import sigmastate.utils.Helpers._ import special.sigma.SigmaDslTesting @@ -117,6 +119,25 @@ class ErgoAddressSpecification extends SigmaDslTesting assertResult(true)(from_tree == p2pk) } + property("derivation from private key") { + val w = new BigInteger("bb2e6f44a38052b3f564fafcd477c4eb8cda1a8a553a4a5f38f1e1084d6a69f0", 16) + val g = dlogGroup.generator + val pk = dlogGroup.exponentiate(g, w) + val pkBytes = GroupElementSerializer.toBytes(pk) + val encoder = new ErgoAddressEncoder(MainnetNetworkPrefix) + val p2pk = new P2PKAddress(ProveDlog(pk), pkBytes)(encoder) + val addrStr = p2pk.toString + + val prefix = (encoder.networkPrefix + P2PKAddress.addressTypePrefix).toByte + val bytes = prefix +: pkBytes + + val checksum = Blake2b256(bytes).take(ErgoAddressEncoder.ChecksumLength) + val expectedAddrStr = Base58.encode(bytes ++ checksum) + + addrStr shouldBe expectedAddrStr + addrStr shouldBe "9iJd9drp1KR3R7HLi7YmQbB5sJ5HFKZoPb5MxGepamggJs5vDHm" + } + property("fromProposition() should properly distinct all types of addresses from script AST") { testFromProposition(scriptVersion = 0, expectedP2S = "JryiCXrZf5VDetH1PM7rKDX3q4sLF34AdErWJFqG87Hf5ikTDf636b35Nr7goWMdRUKA3ZPxdeqFNbQzBjhnDR9SUMYwDX1tdV8ZuGgXwQPaRVcB9", diff --git a/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala b/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala index 7062c0153a..28a28c9d91 100644 --- a/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala +++ b/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala @@ -33,7 +33,7 @@ class CalcSha256Specification extends SigmaTestingCommons forAll(objects) { (in, result) => val expectedResult = decodeString(result) val calcSha256 = EQ(CalcSha256(stringToByteConstant(in)), expectedResult) - val res = int.reduceToCrypto(ctx, calcSha256).get._1 + val res = int.reduceToCrypto(ctx, calcSha256).get.value res shouldBe TrivialProp.TrueProp } } diff --git a/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala b/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala index 8b0e4552b7..562b5bec71 100644 --- a/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala @@ -84,7 +84,7 @@ class CostingSpecification extends SigmaTestingData with CrossVersionProps { def cost(script: String)(expCost: Int): Unit = { val ergoTree = compiler.compile(env, script) - val res = interpreter.reduceToCrypto(context, env, ergoTree).get._2 + val res = interpreter.reduceToCrypto(context, env, ergoTree).get.cost if (printCosts) println(script + s" --> cost $res") res shouldBe ((expCost * CostTable.costFactorIncrease / CostTable.costFactorDecrease) + CostTable.interpreterInitCost).toLong diff --git a/sigmastate/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala b/sigmastate/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala index 5155e1d117..d22d6aa4cf 100644 --- a/sigmastate/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala +++ b/sigmastate/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala @@ -2,7 +2,7 @@ package sigmastate import special.collections.BenchmarkGens import org.scalameter.api.Bench -import sigmastate.Values.{SValue, IntConstant} +import sigmastate.Values.{SValue, IntConstant, Constant} import sigmastate.serialization.OpCodes.PlusCode import spire.syntax.all.cfor @@ -43,4 +43,23 @@ object ErgoTreeBenchmarks extends Bench.LocalTime with BenchmarkGens { suite: Be } } } + + performance of "SType" in { + measure method "isValueOfType" in { + using(sizes) in { size => + cfor(0)(_ < size, _ + 1) { i => + SType.isValueOfType(i, SType.allPredefTypes(i % 10)) + } + } + } + } + performance of "Constant" in { + measure method "isCorrectType" in { + using(sizes) in { size => + cfor(0)(_ < size, _ + 1) { i => + Constant.isCorrectType(i, SType.allPredefTypes(i % 10)) + } + } + } + } } diff --git a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala index 2574a009be..27efc5a695 100644 --- a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala @@ -391,7 +391,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA r shouldBe WhenSoftForkReducer val ctx = createContext(blockHeight, txV2, v2vs) - r.reduce(ctx) shouldBe WhenSoftForkReductionResult + r.reduce(ctx) shouldBe WhenSoftForkReductionResult(0) } override protected def afterAll(): Unit = { diff --git a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index 47f9fc457d..e50723f5f0 100644 --- a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -51,27 +51,27 @@ class TestingInterpreterSpecification extends SigmaTestingCommons val dk1 = SigmaPropConstant(DLogProverInput.random().publicImage).isProven val ctx = testingContext(h) - prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h - 1)), dk1)).get._1 should( + prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h - 1)), dk1)).get.value should( matchPattern { case _: SigmaBoolean => }) - prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h)), dk1)).get._1 should ( + prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h)), dk1)).get.value should ( matchPattern { case _: SigmaBoolean => }) { - val res = prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h + 1)), dk1)).get._1 + val res = prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h + 1)), dk1)).get.value res should matchPattern { case TrivialProp.FalseProp => } } { - val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h - 1)), dk1)).get._1 + val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h - 1)), dk1)).get.value res should matchPattern { case TrivialProp.TrueProp => } } { - val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h)), dk1)).get._1 + val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h)), dk1)).get.value res should matchPattern { case TrivialProp.TrueProp => } } { - val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h + 1)), dk1)).get._1 + val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h + 1)), dk1)).get.value res should matchPattern { case _: SigmaBoolean => } } } @@ -91,18 +91,18 @@ class TestingInterpreterSpecification extends SigmaTestingCommons assert(prover.reduceToCrypto(ctx, OR( AND(LE(Height, IntConstant(h + 1)), AND(dk1, dk2)), AND(GT(Height, IntConstant(h + 1)), dk1) - )).get._1.isInstanceOf[CAND]) + )).get.value.isInstanceOf[CAND]) assert(prover.reduceToCrypto(ctx, OR( AND(LE(Height, IntConstant(h - 1)), AND(dk1, dk2)), AND(GT(Height, IntConstant(h - 1)), dk1) - )).get._1.isInstanceOf[ProveDlog]) + )).get.value.isInstanceOf[ProveDlog]) prover.reduceToCrypto(ctx, OR( AND(LE(Height, IntConstant(h - 1)), AND(dk1, dk2)), AND(GT(Height, IntConstant(h + 1)), dk1) - )).get._1 shouldBe TrivialProp.FalseProp + )).get.value shouldBe TrivialProp.FalseProp prover.reduceToCrypto(ctx, OR( @@ -112,7 +112,7 @@ class TestingInterpreterSpecification extends SigmaTestingCommons ), AND(GT(Height, IntConstant(h - 1)), LE(Height, IntConstant(h + 1))) ) - ).get._1 shouldBe TrivialProp.TrueProp + ).get.value shouldBe TrivialProp.TrueProp } } diff --git a/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala b/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala index afc3a75eb4..c7fcb9d6b0 100644 --- a/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala @@ -9,17 +9,15 @@ import scorex.crypto.hash.Digest32 import sigmastate.basics.DLogProtocol import sigmastate.interpreter.CryptoConstants import special.sigma.SigmaTestingData -import sigmastate.SType.AnyOps +import sigmastate.SType.{isValueOfType, AnyOps} import sigmastate.Values.{ShortConstant, LongConstant, BigIntConstant, AvlTreeConstant, IntConstant, ByteConstant} -import sigmastate.eval.SigmaDsl +import sigmastate.eval.{CSigmaProp, SigmaDsl, CostingSigmaDslBuilder} import special.collection.Coll import special.sigma.BigInt import sigmastate.helpers.TestingHelpers._ class TypesSpecification extends SigmaTestingData { - def Coll[T](items: T*)(implicit cT: RType[T]) = SigmaDsl.Colls.fromItems(items:_*) - def check(tpe: SType, v: Any, exp: Long) = tpe.dataSize(v.asWrappedType) shouldBe exp @@ -81,4 +79,101 @@ class TypesSpecification extends SigmaTestingData { assertExceptionThrown(check(SContext, null, 0), assertError) } + property("SType.isValueOfType") { + forAll { t: SPredefType => + implicit val tWrapped = wrappedTypeGen(t) + forAll { x: SPredefType#WrappedType => + isValueOfType(x, t) shouldBe true + // since forall t. SHeader != t + isValueOfType(x, SHeader) shouldBe false + } + } + } + + import TestData._ + + property("SType.isValueOfType test vectors") { + def assertTrue(x: Any, t: SType) = { + isValueOfType(x, t) shouldBe true + } + def assertFalse(x: Any, t: SType) = { + isValueOfType(x, t) shouldBe false + } + + assertTrue(true, SBoolean) + assertTrue(false, SBoolean) + assertFalse(true, SByte) + + assertTrue(0.toByte, SByte) + assertFalse(0.toByte, SShort) + + assertTrue(0.toShort, SShort) + assertFalse(0.toShort, SInt) + + assertTrue(0, SInt) + assertFalse(0, SShort) + + assertTrue(0L, SLong) + assertFalse(0L, SShort) + + assertTrue(BigIntZero, SBigInt) + assertFalse(BigIntZero, SShort) + + assertTrue(ge1, SGroupElement) + assertFalse(ge1, SShort) + + assertTrue(CSigmaProp(create_dlog()), SSigmaProp) + assertFalse(CSigmaProp(create_dlog()), SShort) + + assertTrue(b1, SBox) + assertFalse(b1, SShort) + + val coll = Coll[Int](1, 2) + assertTrue(coll, SCollection(SInt)) + assertFalse(coll, SShort) + assertTrue(Coll[Long](1L), SCollection(SInt)) // because type check is shallow + + assertTrue(None, SOption(SInt)) + assertTrue(Some(10), SOption(SInt)) + assertTrue(Some(10), SOption(SLong)) // because type check is shallow + assertFalse(None, SShort) + assertFalse(Some(10), SShort) + + val ctx = fakeContext.toSigmaContext(false) + assertTrue(ctx, SContext) + assertFalse(ctx, SShort) + + assertTrue(t1, SAvlTree) + assertFalse(t1, SShort) + + assertTrue(CostingSigmaDslBuilder, SGlobal) + assertFalse(CostingSigmaDslBuilder, SShort) + + assertTrue(h1, SHeader) + assertFalse(h1, SShort) + + assertTrue(preH1, SPreHeader) + assertFalse(preH1, SShort) + + assertTrue((1, 1L), STuple(SInt, SLong)) + assertTrue((1, 1L), STuple(SInt, SInt)) // because type check is shallow + assertTrue((1, Some(1)), STuple(SInt, SOption(SLong))) // because type check is shallow + assertExceptionThrown( + assertTrue((1, 1L, Some(1)), STuple(SInt, SLong, SOption(SInt))), + exceptionLike[RuntimeException](s"Unsupported tuple type") + ) + + assertTrue((x: Any) => x, SFunc(SInt, SLong)) // note, arg and result types not checked + assertFalse((x: Any) => x, STuple(SInt, SLong)) + assertFalse(1, SFunc(SInt, SLong)) + assertExceptionThrown( + assertTrue((x: Any) => x, SFunc(Array(SInt, SLong), SOption(SInt))), + exceptionLike[RuntimeException](s"Unsupported function type") + ) + + assertExceptionThrown( + assertTrue("", SString), + exceptionLike[RuntimeException](s"Unknown type") + ) + } } diff --git a/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala b/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala index 5a81fcdfa4..9390e1b4b3 100644 --- a/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala @@ -2,7 +2,7 @@ package sigmastate.crypto import org.scalacheck.Gen import scorex.util.encode.Base16 -import sigmastate.{AtLeast, CAND} +import sigmastate.{AtLeast, CAND, COR} import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.helpers.{ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter, SigmaTestingCommons} @@ -29,6 +29,65 @@ class SigningSpecification extends SigmaTestingCommons { printSimpleSignature(msg: Array[Byte]) } + property("handle improper signature") { + val pi = new ErgoLikeTestProvingInterpreter() + val sigmaTree: SigmaBoolean = pi.publicKeys.head + val verifier = new ErgoLikeTestInterpreter + val msg = "any message".getBytes("UTF-8") + val sig = "invalid signature".getBytes("UTF-8") + verifier.verifySignature(sigmaTree, msg, sig) shouldBe false + } + + property("AND signature test vector") { + val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get + val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger) + val sk2 = DLogProverInput(BigInt("50415569076448343263191022044468203756975150511337537963383000142821297891310").bigInteger) + val signature = Base16.decode("9b2ebb226be42df67817e9c56541de061997c3ea84e7e72dbb69edb7318d7bb525f9c16ccb1adc0ede4700a046d0a4ab1e239245460c1ba45e5637f7a2d4cc4cc460e5895125be73a2ca16091db2dcf51d3028043c2b9340").get + // check that signature is correct + val verifier = new ErgoLikeTestInterpreter + val proverResult = ProverResult(signature, ContextExtension.empty) + val sigmaTree: SigmaBoolean = CAND(Seq(sk1.publicImage, sk2.publicImage)) + verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true + } + + property("OR signature test vector") { + val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get + val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger) + val sk2 = DLogProverInput(BigInt("50415569076448343263191022044468203756975150511337537963383000142821297891310").bigInteger) + val signature = Base16.decode("ec94d2d5ef0e1e638237f53fd883c339f9771941f70020742a7dc85130aaee535c61321aa1e1367befb500256567b3e6f9c7a3720baa75ba6056305d7595748a93f23f9fc0eb9c1aaabc24acc4197030834d76d3c95ede60c5b59b4b306cd787d010e8217f34677d046646778877c669").get + // check that signature is correct + val verifier = new ErgoLikeTestInterpreter + val proverResult = ProverResult(signature, ContextExtension.empty) + val sigmaTree: SigmaBoolean = COR(Seq(sk1.publicImage, sk2.publicImage)) + verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true + } + + property("AND with OR signature test vector") { + val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get + val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger) + val sk2 = DLogProverInput(BigInt("50415569076448343263191022044468203756975150511337537963383000142821297891310").bigInteger) + val sk3 = DLogProverInput(BigInt("34648336872573478681093104997365775365807654884817677358848426648354905397359").bigInteger) + val signature = Base16.decode("397e005d85c161990d0e44853fbf14951ff76e393fe1939bb48f68e852cd5af028f6c7eaaed587f6d5435891a564d8f9a77288773ce5b526a670ab0278aa4278891db53a9842df6fba69f95f6d55cfe77dd7b4bdccc1a3378ac4524b51598cb813258f64c94e98c3ef891a6eb8cbfd2e527a9038ca50b5bb50058de55a859a169628e6ae5ba4cb0332c694e450782d6f").get + // check that signature is correct + val verifier = new ErgoLikeTestInterpreter + val proverResult = ProverResult(signature, ContextExtension.empty) + val sigmaTree: SigmaBoolean = CAND(Seq(sk1.publicImage, COR(Seq(sk2.publicImage, sk3.publicImage)))) + verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true + } + + property("OR with AND signature test vector") { + val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get + val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger) + val sk2 = DLogProverInput(BigInt("50415569076448343263191022044468203756975150511337537963383000142821297891310").bigInteger) + val sk3 = DLogProverInput(BigInt("34648336872573478681093104997365775365807654884817677358848426648354905397359").bigInteger) + val signature = Base16.decode("a58b251be319a9656c21876b1136a59f42b18835dec6076c92f7a925ba28d2030218c177ab07563003eff5250cfafeb631ef610f4d710ab8e821bf632203adf23f4376580eaa17ddb36c0138f73a88551f45d92cde2b66dfbb5906c02e4d48106ff08be4a2fc29ec242f495468692f9ddeeb029dc5d8f38e2649cf09c44b67cbcfb3de4202026fb84d23ce2b4ff0f69b").get + // check that signature is correct + val verifier = new ErgoLikeTestInterpreter + val proverResult = ProverResult(signature, ContextExtension.empty) + val sigmaTree: SigmaBoolean = COR(Seq(sk1.publicImage, CAND(Seq(sk2.publicImage, sk3.publicImage)))) + verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true + } + property("threshold signature test vector") { val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get diff --git a/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala index 582c31f9ae..5e6df2a349 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala @@ -2,25 +2,25 @@ package sigmastate.helpers import java.math.BigInteger +import gf2t.GF2_192_Poly import org.ergoplatform.ErgoBox import org.ergoplatform.ErgoBox.RegisterId import org.ergoplatform.settings.ErgoAlgos - -import scala.collection.mutable -import pprint.{Tree, PPrinter} +import pprint.{PPrinter, Tree} import scalan.RType import scalan.RType.PrimitiveType import sigmastate.SCollection._ -import sigmastate.Values.{ValueCompanion, ConstantNode, ErgoTree} +import sigmastate.Values.{ConstantNode, ErgoTree, ValueCompanion} +import sigmastate._ import sigmastate.interpreter.CryptoConstants.EcPointType import sigmastate.lang.SigmaTyper import sigmastate.lang.Terms.MethodCall import sigmastate.serialization.GroupElementSerializer import sigmastate.utxo.SelectField -import sigmastate._ import special.collection.Coll import special.sigma.GroupElement +import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.reflect.ClassTag @@ -103,6 +103,14 @@ object SigmaPPrint extends PPrinter { case v: BigInteger => Tree.Apply("new BigInteger", treeifyMany(v.toString(16), 16)) + case v: BigInt => + Tree.Apply("BigInt", treeifyMany(v.toString(16), 16)) + + case poly: GF2_192_Poly => + val c0 = poly.coeff0Bytes() + val others = poly.toByteArray(false) // don't output + Tree.Apply("GF2_192_Poly.fromByteArray", treeifyMany(c0, others)) + case wa: mutable.WrappedArray[Byte @unchecked] if wa.elemTag == ClassTag.Byte => treeifyByteArray(wa.array) diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index fa9ebab9e2..9cb3f8ee26 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -1,15 +1,21 @@ package sigmastate.serialization +import java.math.BigInteger import java.util +import gf2t.GF2_192_Poly +import org.ergoplatform.settings.ErgoAlgos import org.scalacheck.{Arbitrary, Gen} import org.scalatest.Assertion import sigmastate.Values.SigmaBoolean import sigmastate._ -import sigmastate.basics.DLogProtocol.ProveDlog -import sigmastate.basics.ProveDHTuple -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons, ErgoLikeTransactionTesting} +import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} +import sigmastate.basics.VerifierMessage.Challenge +import sigmastate.basics.{ProveDHTuple, SecondDiffieHellmanTupleProverMessage} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTransactionTesting, SigmaTestingCommons} +import sigmastate.interpreter.Interpreter import sigmastate.serialization.generators.ObjectGenerators +import sigmastate.utils.Helpers import scala.util.Random @@ -21,21 +27,21 @@ class SigSerializerSpecification extends SigmaTestingCommons private lazy val prover = new ContextEnrichingTestProvingInterpreter() private lazy val interpreterProveDlogGen: Gen[ProveDlog] = - Gen.oneOf(prover.dlogSecrets.map(secret => ProveDlog(secret.publicImage.h))) + Gen.oneOf(prover.dlogSecrets.map(_.publicImage)) private lazy val interpreterProveDHTGen = - Gen.oneOf( - prover.dhSecrets - .map(_.commonInput) - .map(ci => ProveDHTuple(ci.g, ci.h, ci.u, ci.v))) + Gen.oneOf(prover.dhSecrets.map(_.publicImage)) private def exprTreeNodeGen: Gen[SigmaBoolean] = for { left <- exprTreeGen right <- exprTreeGen + third <- exprTreeGen node <- Gen.oneOf( - COR(Seq(left, right)), - CAND(Seq(left, right)) - ) + COR(Array(left, right)), + CAND(Array(left, right)), + CTHRESHOLD(2, Array(left, right, third)) + ) if SigmaBoolean.estimateCost(node) <= 100000 // limit size of the generated tree + // to fit into Box size limit } yield node private def exprTreeGen: Gen[SigmaBoolean] = @@ -57,8 +63,8 @@ class SigSerializerSpecification extends SigmaTestingCommons private def roundTrip(uncheckedTree: UncheckedTree, exp: SigmaBoolean): Assertion = { - val bytes = SigSerializer.toBytes(uncheckedTree) - val parsedUncheckedTree = SigSerializer.parseAndComputeChallenges(exp, bytes) + val proof = SigSerializer.toProofBytes(uncheckedTree) + val parsedUncheckedTree = SigSerializer.parseAndComputeChallenges(exp, proof) isEquivalent(uncheckedTree, parsedUncheckedTree) shouldBe true } @@ -82,11 +88,22 @@ class SigSerializerSpecification extends SigmaTestingCommons try { // get sigma conjectures out of transformers - val prop = prover.reduceToCrypto(ctx, expr).get._1 + val tree = mkTestErgoTree(expr) + val prop = prover.fullReduction(tree, ctx, Interpreter.emptyEnv).value + + val proof = prover.prove(tree, ctx, challenge).get.proof + val uncheckedTree = SigSerializer.parseAndComputeChallenges(prop, proof) + roundTrip(uncheckedTree, prop) + +// uncomment the following lines to print test cases with test vectors +// they can be added to property("SigSerializer test vectors") +// val fiatShamirHex = getFiatShamirHex(uncheckedTree) +// val testCase = ProofTestCase(prop, proof, uncheckedTree, fiatShamirHex) +// println( +// s"""------------------------------- +// |${sigmastate.helpers.SigmaPPrint(testCase, width = 150, height = 150)} +// |""".stripMargin) - val proof = prover.prove(mkTestErgoTree(expr), ctx, challenge).get.proof - val proofTree = SigSerializer.parseAndComputeChallenges(prop, proof) - roundTrip(proofTree, prop) } catch { case t: Throwable => t.printStackTrace() @@ -95,4 +112,417 @@ class SigSerializerSpecification extends SigmaTestingCommons } } + /** This is used to represent related test vectors to test: + * - SigSerializer.toBytes + * - SigSerializer.parseAndComputeChallenges + * - FiatShamirTree.toBytes + */ + case class ProofTestCase( + prop: SigmaBoolean, + proof: Array[Byte], + uncheckedTree: UncheckedTree, + fiatShamirHex: String = "" + ) + + /** This steps are performed in Interpreter.checkCommitments and should be in sync with + * that code. */ + def getFiatShamirHex(uncheckedTree: UncheckedTree): String = { + val newRoot = prover.computeCommitments(uncheckedTree).get.asInstanceOf[UncheckedSigmaTree] + val fiatShamirBytes = FiatShamirTree.toBytes(newRoot) + val hex = ErgoAlgos.encode(fiatShamirBytes) + hex + } + + property("SigSerializer test vectors") { + // this list of test cases contains test vectors generated by v4.0.2 + // as such they can be used to catch changes in behaviour of later versions + val cases = Seq( + ProofTestCase( + prop = ProveDlog( + Helpers.decodeECPoint("02e8e77123e300f8324e7b5c4cbe0f7ac616e0b78fc45f28f54fa6696231fc8ec3") + ), + proof = ErgoAlgos.decodeUnsafe( + "c6429b70f4926a3ba1454f1aec116075f9e9fbe8a8f72114b277b8462a8b9098f5d4c934ab2876eb1b5707f3119e209bdbbad831e7cc4a41" + ), + uncheckedTree = UncheckedSchnorr( + ProveDlog( + Helpers.decodeECPoint("02e8e77123e300f8324e7b5c4cbe0f7ac616e0b78fc45f28f54fa6696231fc8ec3") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("c6429b70f4926a3ba1454f1aec116075f9e9fbe8a8f72114"), + SecondDLogProverMessage( + BigInt("b277b8462a8b9098f5d4c934ab2876eb1b5707f3119e209bdbbad831e7cc4a41", 16) + ) + ), + fiatShamirHex = "010027100108cd02e8e77123e300f8324e7b5c4cbe0f7ac616e0b78fc45f28f54fa6696231fc8ec373000021021d30cef8084f8659e9734099bf8e6faa89d81f908c3a62e7638da7b2a33822fc" + ), + ProofTestCase( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("024ebfeb5a2b6ad997e40efb4888b3f091a611df8298cf7fb24315b4d112ad7c3c"), + Helpers.decodeECPoint("03d41afc8c5875a8d52439d088b66ed63a5d64f16e1efd7f17c6036a923c637e5c"), + Helpers.decodeECPoint("034132d4c7eb387f12ef40ba3ec03723bda0ee5707f7471185aafc316167e85137") + ), + ErgoAlgos.decodeUnsafe( + "9ec740b57353cb2f6035bb1a481b0066b2fdc0406a6fa67ebb2e6f44a38052b3f564fafcd477c4eb8cda1a8a553a4a5f38f1e1084d6a69f0" + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("024ebfeb5a2b6ad997e40efb4888b3f091a611df8298cf7fb24315b4d112ad7c3c"), + Helpers.decodeECPoint("03d41afc8c5875a8d52439d088b66ed63a5d64f16e1efd7f17c6036a923c637e5c"), + Helpers.decodeECPoint("034132d4c7eb387f12ef40ba3ec03723bda0ee5707f7471185aafc316167e85137") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("9ec740b57353cb2f6035bb1a481b0066b2fdc0406a6fa67e"), + SecondDiffieHellmanTupleProverMessage( + new BigInteger("bb2e6f44a38052b3f564fafcd477c4eb8cda1a8a553a4a5f38f1e1084d6a69f0", 16) + ) + ), + "01008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798024ebfeb5a2b6ad997e40efb4888b3f091a611df8298cf7fb24315b4d112ad7c3c03d41afc8c5875a8d52439d088b66ed63a5d64f16e1efd7f17c6036a923c637e5c034132d4c7eb387f12ef40ba3ec03723bda0ee5707f7471185aafc316167e851377300004203724c887a207569c4648a81bc2d66bddeefe14b3ed259ee320773ec0c7df714490338360ba3350c16c42839878fc13b641c0a67372c4217e2ceafe1d40405f03708" + ), + ProofTestCase( + CAND( + List( + ProveDlog( + Helpers.decodeECPoint("03670a10fcf68531423e3aa8bdad2d755eb5363ac53068e80d44578861f80abef3") + ), + ProveDlog( + Helpers.decodeECPoint("0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb87") + ) + ) + ), + ErgoAlgos.decodeUnsafe( + "a00b476899e583aefc18b237a7a70e73baace72aa533271a561d3432c347dcaec8975fdefb36389abe21656aadcfda0a0259681ce17bc47c9539ae1e7068292bb9646a9ffe4e11653495bd67588cfd6454d82cc455036e5b" + ), + CAndUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a"), + List( + UncheckedSchnorr( + ProveDlog( + Helpers.decodeECPoint( + "03670a10fcf68531423e3aa8bdad2d755eb5363ac53068e80d44578861f80abef3" + ) + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a"), + SecondDLogProverMessage( + BigInt("561d3432c347dcaec8975fdefb36389abe21656aadcfda0a0259681ce17bc47c", 16) + ) + ), + UncheckedSchnorr( + ProveDlog( + Helpers.decodeECPoint( + "0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb87" + ) + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a"), + SecondDLogProverMessage( + BigInt("9539ae1e7068292bb9646a9ffe4e11653495bd67588cfd6454d82cc455036e5b", 16) + ) + ) + ) + ), + "00000002010027100108cd03670a10fcf68531423e3aa8bdad2d755eb5363ac53068e80d44578861f80abef373000021024fc32f5fc7dad49005dc86b8ad95975d62ee4336cdddd4de8868414211370320010027100108cd0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb877300002103f084eb45540454909d3e793d876262fa184f90412c33a046a0b1c4d7c8933f67" + ), + ProofTestCase( + COR( + List( + ProveDlog( + Helpers.decodeECPoint("0344789e3a797e713103f2a8edd673fac35e56d414c584e575aaa750f3e8728b5b") + ), + ProveDlog( + Helpers.decodeECPoint("0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb87") + ) + ) + ), + ErgoAlgos.decodeUnsafe( + "c617e65a2ca62ac97bc33a33b76cb669622129ba0e094ad96287d97c2c6d6c8e48790d7c44961f7d958d59222ab4d7c814808a466a3e66e6f98e02d421757baa2842288b8d02787b5111db2e8924623790175e5bf27a2e4513e8eb196c22c8cf26a9d7b51cd7e386508db9c12b070d84" + ), + COrUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("c617e65a2ca62ac97bc33a33b76cb669622129ba0e094ad9"), + List( + UncheckedSchnorr( + ProveDlog( + Helpers.decodeECPoint( + "0344789e3a797e713103f2a8edd673fac35e56d414c584e575aaa750f3e8728b5b" + ) + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("6287d97c2c6d6c8e48790d7c44961f7d958d59222ab4d7c8"), + SecondDLogProverMessage( + BigInt("14808a466a3e66e6f98e02d421757baa2842288b8d02787b5111db2e89246237", 16) + ) + ), + UncheckedSchnorr( + ProveDlog( + Helpers.decodeECPoint( + "0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb87" + ) + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("a4903f2600cb464733ba374ff3faa914f7ac709824bd9d11"), + SecondDLogProverMessage( + BigInt("90175e5bf27a2e4513e8eb196c22c8cf26a9d7b51cd7e386508db9c12b070d84", 16) + ) + ) + ) + ), + "00010002010027100108cd0344789e3a797e713103f2a8edd673fac35e56d414c584e575aaa750f3e8728b5b730000210207700723f7cf3a94782a56d9366f3916c548616edf9bcbaecb892f8a52b28836010027100108cd0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb8773000021039072557976001866ac8a1a6a8bd921e5b18171b195e6dabffff667f9a88ab9a2" + ), + ProofTestCase( + COR( + Array( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("03a5f4c3b8217557514df3df8537ca13f991b11538935b2ea407e8b24afcabe509"), + Helpers.decodeECPoint("029837d12c86c29c92e74229dfd3fcb10933b696685209b14baa74dbabacb2dee5"), + Helpers.decodeECPoint("03f17cefec3911966dc9952090325267a5cf7f9b0be76b02623021989d7f0007a2") + ), + COR( + Array( + ProveDlog(Helpers.decodeECPoint("03f997167c03aa234732e3a68126b371dffa1e409f62ca8fa18cea6acd1dbe54d5")), + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("03f5921dde02233135665d006838fcb783deca634ee333c5541cc05a9012e684ee"), + Helpers.decodeECPoint("039b65625db7aad6d86599355b7cac785e6b5ac85b8a32e0d6927b324704d0a261"), + Helpers.decodeECPoint("02fc58b939b105231da101540c87e56f5703460c179935aaee47137f3c367904f1") + ) + ) + ) + ) + ), + ErgoAlgos.decodeUnsafe( + "96addfddcc197bdbacf5c0142fb16c39384b3699fa47da7dffd3149193b042fda134c0e208fefcb791379959ac6fc731adf47e32000fc75e2923dba482c843c7f6b684cbf2ceec5bfdf5fe6d13cabe5d15f8295ca4e8094fba3c4716bfdfc3c462417a79a61fcc487d6997a42739d533eebffa3b420a6e2e44616a1341e5baa1165c6c22e91a81addd97c3bd2fe40ecdbbda6f43bf71240da8dac878c044c16d42a4b34c536bbb1b" + ), + COrUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("96addfddcc197bdbacf5c0142fb16c39384b3699fa47da7d"), + List( + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("03a5f4c3b8217557514df3df8537ca13f991b11538935b2ea407e8b24afcabe509"), + Helpers.decodeECPoint("029837d12c86c29c92e74229dfd3fcb10933b696685209b14baa74dbabacb2dee5"), + Helpers.decodeECPoint("03f17cefec3911966dc9952090325267a5cf7f9b0be76b02623021989d7f0007a2") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("ffd3149193b042fda134c0e208fefcb791379959ac6fc731"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("adf47e32000fc75e2923dba482c843c7f6b684cbf2ceec5bfdf5fe6d13cabe5d", 16)) + ), + COrUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("697ecb4c5fa939260dc100f6274f908ea97cafc056281d4c"), + List( + UncheckedSchnorr( + ProveDlog(Helpers.decodeECPoint("03f997167c03aa234732e3a68126b371dffa1e409f62ca8fa18cea6acd1dbe54d5")), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("15f8295ca4e8094fba3c4716bfdfc3c462417a79a61fcc48"), + SecondDLogProverMessage(BigInt("7d6997a42739d533eebffa3b420a6e2e44616a1341e5baa1165c6c22e91a81ad", 16)) + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("03f5921dde02233135665d006838fcb783deca634ee333c5541cc05a9012e684ee"), + Helpers.decodeECPoint("039b65625db7aad6d86599355b7cac785e6b5ac85b8a32e0d6927b324704d0a261"), + Helpers.decodeECPoint("02fc58b939b105231da101540c87e56f5703460c179935aaee47137f3c367904f1") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("7c86e210fb413069b7fd47e09890534acb3dd5b9f037d104"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("dd97c3bd2fe40ecdbbda6f43bf71240da8dac878c044c16d42a4b34c536bbb1b", 16)) + ) + ) + ) + ) + ), + "0001000201008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179803a5f4c3b8217557514df3df8537ca13f991b11538935b2ea407e8b24afcabe509029837d12c86c29c92e74229dfd3fcb10933b696685209b14baa74dbabacb2dee503f17cefec3911966dc9952090325267a5cf7f9b0be76b02623021989d7f0007a273000042035e192266f309bebe0a3e50f96f8161ad4dbd6136771619b7560b8ea4271ff2be036ebbb7e8e91546c16a41d79971d81513813416c06ef4ee92825065c484f43c6f00010002010027100108cd03f997167c03aa234732e3a68126b371dffa1e409f62ca8fa18cea6acd1dbe54d573000021031a93fccb6536b097682276ec047138f95ad05369b8bd24d73ecdc46571b5a7e601008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179803f5921dde02233135665d006838fcb783deca634ee333c5541cc05a9012e684ee039b65625db7aad6d86599355b7cac785e6b5ac85b8a32e0d6927b324704d0a26102fc58b939b105231da101540c87e56f5703460c179935aaee47137f3c367904f1730000420359bc0180bf8e1df00dd5021dd43cb52acd5612fa5baa3d517222a514ed7e4d1903de533bce02969892436e113cad7270de0b3d051035629abd645d3470928f3700" + ), + ProofTestCase( + COR( + Array( + CAND( + Array( + ProveDlog(Helpers.decodeECPoint("0368c0d88d9eb2972bbfc23c961de6307f6a944352cbfe316f262401feabdaa87d")), + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02badd523c2f5c12f4a3d4d667efb6ce8e95c8ad007cc697c34e91884335b55249"), + Helpers.decodeECPoint("02c455e55dc7bc731c5a487778e84814080fb70cc59957bc2a40c45373fe1ce14c"), + Helpers.decodeECPoint("029d4ec275379f9212a53e15994aef203dcec43a177c0b1f40afcf592e5753ce67") + ) + ) + ), + COR( + Array( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa7"), + Helpers.decodeECPoint("03a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e2"), + Helpers.decodeECPoint("0315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a") + ), + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa7"), + Helpers.decodeECPoint("03a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e2"), + Helpers.decodeECPoint("0315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a") + ) + ) + ) + ) + ), + ErgoAlgos.decodeUnsafe( + "4fdc76711fd844de0831d8e90ebaf9c622117a062b2f8b63ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2e0a3e44384f23eee260931d88e1f5241a2600a7c98545ada675fd5e627e8e84f140fc95e28775cde52e71bb4d7b5ee2564553fac5b52202530fcbcdf205b7cca145202fb2a5bb181a890eb15536b08b747ea163f6b5d32a116fa9e1eb6b348fd82d3ebc11c125e5bc3f09c499aa0a8db14dc1780b4181f9bae5ed0f743f71b82b18784380814507d810cbef61ebc0b30e7f324083e2d3d08" + ), + COrUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("4fdc76711fd844de0831d8e90ebaf9c622117a062b2f8b63"), + List( + CAndUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2"), + List( + UncheckedSchnorr( + ProveDlog(Helpers.decodeECPoint("0368c0d88d9eb2972bbfc23c961de6307f6a944352cbfe316f262401feabdaa87d")), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2"), + SecondDLogProverMessage(BigInt("e0a3e44384f23eee260931d88e1f5241a2600a7c98545ada675fd5e627e8e84f", 16)) + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02badd523c2f5c12f4a3d4d667efb6ce8e95c8ad007cc697c34e91884335b55249"), + Helpers.decodeECPoint("02c455e55dc7bc731c5a487778e84814080fb70cc59957bc2a40c45373fe1ce14c"), + Helpers.decodeECPoint("029d4ec275379f9212a53e15994aef203dcec43a177c0b1f40afcf592e5753ce67") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("140fc95e28775cde52e71bb4d7b5ee2564553fac5b52202530fcbcdf205b7cca", 16)) + ) + ) + ), + COrUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("b057ea5b5135708419f74f1f8bb60a65a572ad3e78b559b1"), + List( + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa7"), + Helpers.decodeECPoint("03a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e2"), + Helpers.decodeECPoint("0315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("145202fb2a5bb181a890eb15536b08b747ea163f6b5d32a1"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("16fa9e1eb6b348fd82d3ebc11c125e5bc3f09c499aa0a8db14dc1780b4181f9b", 16)) + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa7"), + Helpers.decodeECPoint("03a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e2"), + Helpers.decodeECPoint("0315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("a405e8a07b6ec105b167a40ad8dd02d2e298bb0113e86b10"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("ae5ed0f743f71b82b18784380814507d810cbef61ebc0b30e7f324083e2d3d08", 16)) + ) + ) + ) + ) + ), + "0001000200000002010027100108cd0368c0d88d9eb2972bbfc23c961de6307f6a944352cbfe316f262401feabdaa87d7300002102628c0e33748b2e120536945d77e69b3fb5fb3878c5697c20bf70b8459f476e4b01008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802badd523c2f5c12f4a3d4d667efb6ce8e95c8ad007cc697c34e91884335b5524902c455e55dc7bc731c5a487778e84814080fb70cc59957bc2a40c45373fe1ce14c029d4ec275379f9212a53e15994aef203dcec43a177c0b1f40afcf592e5753ce6773000042030064fb366dce2d424a14308d2c3d562f3558b042250cc28eab47a594efdec9c603787ca34604619468b3c677e2887a0de382a093264729a15ace38f1e3be20fd0f0001000201008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa703a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e20315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a7300004202f718c7ac18b4d942056b7237809f843811a04314044d6dd517022f6d15198c4e0329ecb543ea406fda96d3088196f4490cacdea49286c954ea2924523a57847a7d01008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa703a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e20315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a73000042021cc93df3de5b405ee306f5c7003b6abbfb7b4d0ab356c36d0c454a4c6b3403f603608a98d8aa0781ec96970353b4e22ae05454833528ae8f8468d0a8fc3f92675a" + ), + ProofTestCase( + CTHRESHOLD( + 2, + Array( + ProveDlog(Helpers.decodeECPoint("03a5a5234701fff48be4ed1b3e1fab446657eeddb52e2573c52b9c4021f2403866")), + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("036b52166c82e61d0954d521a8a8a20af0eb1adb7f23a9c3ee1ebac1242e35ac18"), + Helpers.decodeECPoint("0339a5debbb2bb67aa560e98dbfc4050e8ca0643683314cd1bc911f11c5477a312"), + Helpers.decodeECPoint("02730455ebb8c01a89dced09c5253c9bfa4b1471d1068ba30ab226104a6551c461") + ), + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("029775461de9886800dac39ef14f535e38dc3b2719770d69627007aee9f638e918"), + Helpers.decodeECPoint("02fdc07913da5615db53a0351a47131832d77e29c6520248e82a568d02b09d4604"), + Helpers.decodeECPoint("03cefefa1511430ca2a873759107085f269f6fbcd4e836db7760749f52b7f7923a") + ) + ) + ), + ErgoAlgos.decodeUnsafe( + "c94696c3e3089d9fd1174c18e6dd22f1be8003bbea08011fcf39310e7c9049c1c9966198b8d63a2f19e98843b81b74399f662dba4e764cd548406dd180453dd1bc0e24562f0184d189ca25a41ca8b54ada857dd649d3228a8c359ac499d430ecada3f92d5206cddeffb16248068c1003477d717e04afbf206c87a59ce5263ee7cc4020b5772d91b1df00bd72b15347fd" + ), + CThresholdUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("c94696c3e3089d9fd1174c18e6dd22f1be8003bbea08011f"), + List( + UncheckedSchnorr( + ProveDlog(Helpers.decodeECPoint("03a5a5234701fff48be4ed1b3e1fab446657eeddb52e2573c52b9c4021f2403866")), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("067fa7cd9f98d45e18812d805e0b18dea7698bf852137526"), + SecondDLogProverMessage(BigInt("9f662dba4e764cd548406dd180453dd1bc0e24562f0184d189ca25a41ca8b54a", 16)) + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("036b52166c82e61d0954d521a8a8a20af0eb1adb7f23a9c3ee1ebac1242e35ac18"), + Helpers.decodeECPoint("0339a5debbb2bb67aa560e98dbfc4050e8ca0643683314cd1bc911f11c5477a312"), + Helpers.decodeECPoint("02730455ebb8c01a89dced09c5253c9bfa4b1471d1068ba30ab226104a6551c461") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("5735f4df1b280e1d423a8f28977057af8c52123c9a3fe96d"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("da857dd649d3228a8c359ac499d430ecada3f92d5206cddeffb16248068c1003", 16)) + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("029775461de9886800dac39ef14f535e38dc3b2719770d69627007aee9f638e918"), + Helpers.decodeECPoint("02fdc07913da5615db53a0351a47131832d77e29c6520248e82a568d02b09d4604"), + Helpers.decodeECPoint("03cefefa1511430ca2a873759107085f269f6fbcd4e836db7760749f52b7f7923a") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("980cc5d167b847dc8baceeb02fa66d8095bb9a7f22249d54"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("477d717e04afbf206c87a59ce5263ee7cc4020b5772d91b1df00bd72b15347fd", 16)) + ) + ), + 2, + Some( + GF2_192_Poly.fromByteArray( + ErgoAlgos.decodeUnsafe("c94696c3e3089d9fd1174c18e6dd22f1be8003bbea08011f"), + ErgoAlgos.decodeUnsafe("cf39310e7c9049c1c9966198b8d63a2f19e98843b81b7439") + ) + ) + ), + "0002020003010027100108cd03a5a5234701fff48be4ed1b3e1fab446657eeddb52e2573c52b9c4021f2403866730000210337af2d4066efa710279bcf8e1e53bb0cc7a164d04b5b1734f182fc33517a1aa701008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798036b52166c82e61d0954d521a8a8a20af0eb1adb7f23a9c3ee1ebac1242e35ac180339a5debbb2bb67aa560e98dbfc4050e8ca0643683314cd1bc911f11c5477a31202730455ebb8c01a89dced09c5253c9bfa4b1471d1068ba30ab226104a6551c46173000042033569a1a0dc21c510fb415b8cf11bc79d40ba0c9344ac4f6d031e0cefb0c5a86a039e35b6fde9f8443e18527807007619874abcc5a6d28d8ad9324bf89749d2f72901008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798029775461de9886800dac39ef14f535e38dc3b2719770d69627007aee9f638e91802fdc07913da5615db53a0351a47131832d77e29c6520248e82a568d02b09d460403cefefa1511430ca2a873759107085f269f6fbcd4e836db7760749f52b7f7923a7300004203e4ffc0fd026f9b9ab6d80d13e453f14caf09b1157ffca8d3da899b52f6b10d1603e119655077c1fda14ab47138496b50f4e7305edbfd0a6925952a4650d4f2729f" + ) + ) + + cases.zipWithIndex.foreach { case (c, iCase) => + val sigBytes = SigSerializer.toProofBytes(c.uncheckedTree) + sigBytes shouldBe c.proof + val uncheckedTree = SigSerializer.parseAndComputeChallenges(c.prop, c.proof) + uncheckedTree shouldBe c.uncheckedTree + + val hex = getFiatShamirHex(c.uncheckedTree) + + if (c.fiatShamirHex.isEmpty) { + // output test vector + val vector = sigmastate.helpers.SigmaPPrint(hex, width = 150, height = 150) + println( + s"""case $iCase: ------------------------- + |hex: $vector + |""".stripMargin) + } + + hex shouldBe c.fiatShamirHex + } + } + + property("Invalid signature parsing") { + forAll { bytes: Array[Byte] => + val r = SigmaSerializer.startReader(bytes) + val readBytes = r.getBytesUnsafe(bytes.length + 1) // request more than present + readBytes shouldBe bytes + + // we now at the limit position of reader, and still can get all buffer bytes + val allBytes = r.getAllBufferBytes + allBytes shouldBe bytes + } + } } diff --git a/sigmastate/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala index 3009df8ba5..de54b62295 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala @@ -2,12 +2,12 @@ package sigmastate.serialization import org.ergoplatform.Outputs import sigmastate.Values.{ByteConstant, IntConstant, LongConstant} -import sigmastate.lang.DefaultSigmaBuilder +import sigmastate.lang.CheckingSigmaBuilder import sigmastate.utxo.ByIndex import sigmastate.{SInt, SLong, Upcast} class UpcastOnDeserializationSpecification extends SerializationSpecification { - import DefaultSigmaBuilder._ + import CheckingSigmaBuilder._ property("Upcast deserialization round trip") { forAll(comparisonExprTreeNodeGen, minSuccessful(500)) { tree => diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala index c53345bfd0..39d82cf304 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala @@ -45,12 +45,12 @@ class ErgoLikeInterpreterSpecification extends SigmaTestingCommons val exp = TrueLeaf e shouldBe exp - val res = verifier.reduceToCrypto(ctx, exp).get._1 + val res = verifier.reduceToCrypto(ctx, exp).get.value res shouldBe TrivialProp.TrueProp val res2 = verifier.reduceToCrypto(ctx, EQ(ByteArrayConstant(h1.treeWithSegregation(ergoTreeHeaderInTests).bytes), - ByteArrayConstant(h2.treeWithSegregation(ergoTreeHeaderInTests).bytes))).get._1 + ByteArrayConstant(h2.treeWithSegregation(ergoTreeHeaderInTests).bytes))).get.value res2 shouldBe TrivialProp.FalseProp } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala index 695e579e98..3439a7df4c 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala @@ -1,10 +1,14 @@ package sigmastate.utxo -import sigmastate.{CAND, CAndUnproven, COR, COrUnproven, CTHRESHOLD, CThresholdUnproven, NodePosition, UnprovenSchnorr} +import org.ergoplatform.ErgoLikeInterpreter +import scorex.crypto.hash.Blake2b256 import sigmastate.Values.SigmaBoolean +import sigmastate._ import sigmastate.basics.DLogProtocol.FirstDLogProverMessage import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, SecP256K1} import sigmastate.helpers.{ErgoLikeTestProvingInterpreter, SigmaTestingCommons} +import sigmastate.interpreter.{HintsBag, ProverInterpreter} +import sigmastate.lang.exceptions.InterpreterException class ProverSpecification extends SigmaTestingCommons { @@ -104,4 +108,84 @@ class ProverSpecification extends SigmaTestingCommons { c1.children(1).asInstanceOf[UnprovenSchnorr].position shouldBe NodePosition(Seq(0, 0, 1)) } + val message = Blake2b256("some message based on tx content") + + def checkProof(sb: SigmaBoolean)(implicit prover: ProverInterpreter) = { + val verifier = new ErgoLikeInterpreter() + val proof = prover.generateProof(sb, message, HintsBag.empty) + val ok = verifier.verifySignature(sb, message, proof) + ok shouldBe true + } + + def checkUnrealRoot(sb: SigmaBoolean)(implicit prover: ProverInterpreter) = { + assertExceptionThrown( + checkProof(sb), + { case e: AssertionError => e.getMessage.contains("Tree root should be real but was") + case _ => false }) + } + + property("proof/verify completeness") { + implicit val prover = new ErgoLikeTestProvingInterpreter + val pk0 = prover.dlogSecrets(0).publicImage + val pk1 = prover.dlogSecrets(1).publicImage + val dht0 = prover.dhSecrets(0).publicImage + + val otherProver = new ErgoLikeTestProvingInterpreter + val pkUnknown0 = otherProver.dlogSecrets(0).publicImage + val pkUnknown1 = otherProver.dlogSecrets(1).publicImage + val pkUnknown2 = otherProver.dlogSecrets(2).publicImage + + assertExceptionThrown( + checkProof(TrivialProp.FalseProp), + { case e: InterpreterException => + e.getMessage.contains("Script reduced to false") + case _ => false }) + + checkProof(pk0) + checkProof(dht0) + + checkProof(CAND(Array(pk0))) + checkUnrealRoot(CAND(Array(pkUnknown0))) + + checkProof(CAND(Array(pk0, pk1))) + checkUnrealRoot(CAND(Array(pk0, pkUnknown0))) + checkUnrealRoot(CAND(Array(pkUnknown0, pk0))) + checkUnrealRoot(CAND(Array(pkUnknown0, pkUnknown1))) + + checkProof(COR(Array(pk0))) + checkUnrealRoot(COR(Array(pkUnknown0))) + + checkProof(COR(Array(pk0, pk1))) + checkProof(COR(Array(pk0, pkUnknown0))) + checkProof(COR(Array(pkUnknown0, pk0))) + checkUnrealRoot(COR(Array(pkUnknown0, pkUnknown1))) + + checkProof(CTHRESHOLD(1, Array(pk0))) + checkUnrealRoot(CTHRESHOLD(1, Array(pkUnknown0))) + + checkProof(CTHRESHOLD(1, Array(pk0, pk1))) + checkProof(CTHRESHOLD(1, Array(pk0, pkUnknown1))) + checkProof(CTHRESHOLD(1, Array(pkUnknown1, pk0))) + checkUnrealRoot(CTHRESHOLD(1, Array(pkUnknown0, pkUnknown1))) + + checkProof(CTHRESHOLD(1, Array(pk0, pk1, dht0))) + checkProof(CTHRESHOLD(1, Array(pk0, pkUnknown0, dht0))) + checkProof(CTHRESHOLD(1, Array(pkUnknown0, pkUnknown1, dht0))) + checkProof(CTHRESHOLD(1, Array(pkUnknown0, dht0, pkUnknown1))) + checkProof(CTHRESHOLD(1, Array(dht0, pkUnknown0, pkUnknown1))) + checkUnrealRoot(CTHRESHOLD(1, Array(pkUnknown0, pkUnknown1, pkUnknown2))) + + checkProof(CTHRESHOLD(2, Array(pk0, pk1, dht0))) + checkProof(CTHRESHOLD(2, Array(pkUnknown0, pk0, dht0))) + checkProof(CTHRESHOLD(2, Array(pk0, pkUnknown0, dht0))) + checkProof(CTHRESHOLD(2, Array(pk0, dht0, pkUnknown0))) + checkUnrealRoot(CTHRESHOLD(2, Array(pk0, pkUnknown0, pkUnknown1))) + checkUnrealRoot(CTHRESHOLD(2, Array(pkUnknown0, pk0, pkUnknown1))) + checkUnrealRoot(CTHRESHOLD(2, Array(pkUnknown0, pkUnknown1, pk0))) + + checkProof(CTHRESHOLD(3, Array(pk0, pk1, dht0))) + checkUnrealRoot(CTHRESHOLD(3, Array(pkUnknown0, pk0, dht0))) + checkUnrealRoot(CTHRESHOLD(3, Array(pk0, pkUnknown0, dht0))) + checkUnrealRoot(CTHRESHOLD(3, Array(pk0, dht0, pkUnknown0))) + } } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala index 6443263507..8fba6f7f47 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala @@ -67,7 +67,7 @@ class ThresholdSpecification extends SigmaTestingCommons } val prop2And = CAND(Seq(pubkeyA, pubkeyB, pubkeyC)).toSigmaProp - proverA.reduceToCrypto(ctx, compiledProp2).get._1 shouldBe proverA.reduceToCrypto(ctx, prop2And).get._1 + proverA.reduceToCrypto(ctx, compiledProp2).get.value shouldBe proverA.reduceToCrypto(ctx, prop2And).get.value // this example is from the white paper val (compiledTree3, compiledProp3) = compileAndCheck(env, @@ -85,7 +85,7 @@ class ThresholdSpecification extends SigmaTestingCommons proverD.prove(compiledTree3, ctx, fakeMessage).isFailure shouldBe true val prop3Or = COR(Seq(pubkeyA, pubkeyB, pubkeyC)).toSigmaProp - proverA.reduceToCrypto(ctx, compiledProp3).get._1 shouldBe proverA.reduceToCrypto(ctx, prop3Or).get._1 + proverA.reduceToCrypto(ctx, compiledProp3).get.value shouldBe proverA.reduceToCrypto(ctx, prop3Or).get.value val (compiledTree4, _) = compileAndCheck(env, """{ @@ -118,8 +118,8 @@ class ThresholdSpecification extends SigmaTestingCommons case class TestCase(numTrue: Int, vector: Seq[SigmaPropValue], dlogOnlyVector: DlogOnlyVector) case class DlogOnlyVector(v: Seq[SigmaPropValue]) { - lazy val orVersion = prover.reduceToCrypto(ctx, SigmaOr(v)).get._1 - lazy val andVersion = prover.reduceToCrypto(ctx, SigmaAnd(v)).get._1 + lazy val orVersion = prover.reduceToCrypto(ctx, SigmaOr(v)).get.value + lazy val andVersion = prover.reduceToCrypto(ctx, SigmaAnd(v)).get.value } // Sequence of three secrets, in order to build test cases with 0, 1, 2, or 3 ProveDlogs inputs @@ -167,57 +167,57 @@ class ThresholdSpecification extends SigmaTestingCommons pReduced.mapOrThrow(_ => true) shouldBe true if (t.dlogOnlyVector.v.isEmpty) { // Case 0: no ProveDlogs in the test vector -- just booleans if (t.numTrue >= bound) { - pReduced.get._1 shouldBe TrivialProp.TrueProp + pReduced.get.value shouldBe TrivialProp.TrueProp case0TrueHit = true } else { - pReduced.get._1 shouldBe TrivialProp.FalseProp + pReduced.get.value shouldBe TrivialProp.FalseProp case0FalseHit = true } } else if (t.dlogOnlyVector.v.length == 1) { // Case 1: 1 ProveDlog in the test vector // Should be just true if numTrue>=bound if (t.numTrue >= bound) { - pReduced.get._1 shouldBe TrivialProp.TrueProp + pReduced.get.value shouldBe TrivialProp.TrueProp case1TrueHit = true } // Should be false if bound>numTrue + 1 else if (bound > t.numTrue + 1) { - pReduced.get._1 shouldBe TrivialProp.FalseProp + pReduced.get.value shouldBe TrivialProp.FalseProp case1FalseHit = true } // if bound is exactly numTrue+1, should be just dlog else if (bound == t.numTrue + 1) { - SigmaPropConstant(pReduced.get._1.asInstanceOf[ProveDlog]) shouldBe t.dlogOnlyVector.v.head + SigmaPropConstant(pReduced.get.value.asInstanceOf[ProveDlog]) shouldBe t.dlogOnlyVector.v.head case1DLogHit = true } } else { // Case 2: more than 1 ProveDlogs in the test vector // Should be just true if numTrue>=bound if (t.numTrue >= bound) { - pReduced.get._1 shouldBe TrivialProp.TrueProp + pReduced.get.value shouldBe TrivialProp.TrueProp case2TrueHit = true } // Should be false if bound>numTrue + dlogOnlyVector.length else if (bound > t.numTrue + t.dlogOnlyVector.v.length) { - pReduced.get._1 shouldBe TrivialProp.FalseProp + pReduced.get.value shouldBe TrivialProp.FalseProp case2FalseHit = true } // if bound is exactly numTrue+dlogOnlyVector, should be just AND of all dlogs else if (bound == t.numTrue + t.dlogOnlyVector.v.length) { - pReduced.get._1 shouldBe t.dlogOnlyVector.andVersion + pReduced.get.value shouldBe t.dlogOnlyVector.andVersion case2AndHit = true } // if bound is exactly numTrue+1, should be just OR of all dlogs else if (bound == t.numTrue + 1) { - pReduced.get._1 shouldBe t.dlogOnlyVector.orVersion + pReduced.get.value shouldBe t.dlogOnlyVector.orVersion case2OrHit = true } // else should be AtLeast else { val atLeastReduced = prover.reduceToCrypto(ctx, AtLeast(bound - t.numTrue, t.dlogOnlyVector.v)) - pReduced.get._1 shouldBe atLeastReduced.get._1 + pReduced.get.value shouldBe atLeastReduced.get.value case2AtLeastHit = true } } diff --git a/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala b/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala index 884e8523be..ad260af0c7 100644 --- a/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala +++ b/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala @@ -46,7 +46,7 @@ trait ContractsTestkit { } def contextVars(m: Map[Byte, AnyValue]): Coll[AnyValue] = { - val maxKey = if (m.keys.isEmpty) 0 else m.keys.max + val maxKey = if (m.keys.isEmpty) 0 else m.keys.max // TODO optimize: max takes 90% of this method val res = new Array[AnyValue](maxKey) for ((id, v) <- m) { val i = id - 1 @@ -66,13 +66,16 @@ trait ContractsTestkit { def testContext(inputs: Array[Box], outputs: Array[Box], height: Int, self: Box, - tree: AvlTree, minerPk: Array[Byte], vars: Array[AnyValue]) = + tree: AvlTree, minerPk: Array[Byte], activatedScriptVersion: Byte, vars: Array[AnyValue]) = new CostingDataContext( noInputs.toColl, noHeaders, dummyPreHeader, - inputs.toColl, outputs.toColl, height, self, tree, minerPk.toColl, vars.toColl, false) + inputs.toColl, outputs.toColl, height, self, tree, + minerPk.toColl, vars.toColl, activatedScriptVersion, false) - def newContext(height: Int, self: Box, vars: AnyValue*): CostingDataContext = { - testContext(noInputs, noOutputs, height, self, emptyAvlTree, dummyPubkey, vars.toArray) + def newContext(height: Int, self: Box, activatedScriptVersion: Byte, vars: AnyValue*): CostingDataContext = { + testContext( + noInputs, noOutputs, height, self, emptyAvlTree, dummyPubkey, + activatedScriptVersion, vars.toArray) } implicit class TestContextOps(ctx: CostingDataContext) { diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala index 2ba7adb6f1..383f062371 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala @@ -48,161 +48,6 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui implicit def IR = createIR() - object TestData { - val BigIntZero: BigInt = CBigInt(new BigInteger("0", 16)) - val BigIntOne: BigInt = CBigInt(new BigInteger("1", 16)) - val BigIntMinusOne: BigInt = CBigInt(new BigInteger("-1", 16)) - val BigInt10: BigInt = CBigInt(new BigInteger("a", 16)) - val BigInt11: BigInt = CBigInt(new BigInteger("b", 16)) - - val ge1str = "03358d53f01276211f92d0aefbd278805121d4ff6eb534b777af1ee8abae5b2056" - val ge2str = "02dba7b94b111f3894e2f9120b577da595ec7d58d488485adf73bf4e153af63575" - val ge3str = "0290449814f5671172dd696a61b8aa49aaa4c87013f56165e27d49944e98bc414d" - - val ge1 = Helpers.decodeGroupElement(ge1str) - val ge2 = Helpers.decodeGroupElement(ge2str) - val ge3 = Helpers.decodeGroupElement(ge3str) - - val t1: AvlTree = CAvlTree( - AvlTreeData( - ADDigest @@ ErgoAlgos.decodeUnsafe("000183807f66b301530120ff7fc6bd6601ff01ff7f7d2bedbbffff00187fe89094"), - AvlTreeFlags(false, true, true), - 1, - Some(1) - ) - ) - val t2: AvlTree = CAvlTree( - AvlTreeData( - ADDigest @@ ErgoAlgos.decodeUnsafe("ff000d937f80ffd731ed802d24358001ff8080ff71007f00ad37e0a7ae43fff95b"), - AvlTreeFlags(false, false, false), - 32, - Some(64) - ) - ) - val t3: AvlTree = CAvlTree( - AvlTreeData( - ADDigest @@ ErgoAlgos.decodeUnsafe("3100d2e101ff01fc047c7f6f00ff80129df69a5090012f01ffca99f5bfff0c8036"), - AvlTreeFlags(true, false, false), - 128, - None - ) - ) - - val b1: Box = CostingBox( - false, - new ErgoBox( - 9223372036854775807L, - new ErgoTree( - 16.toByte, - Array( - SigmaPropConstant( - CSigmaProp( - ProveDlog( - Helpers.decodeECPoint( - "0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e" - ) - ) - ) - ) - ), - Right(ConstantPlaceholder(0, SSigmaProp)) - ), - Coll( - (Digest32 @@ (ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001")), - 10000000L), - (Digest32 @@ (ErgoAlgos.decodeUnsafe("a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600")), - 500L) - ), - Map( - ErgoBox.R5 -> ByteArrayConstant(Helpers.decodeBytes("7fc87f7f01ff")), - ErgoBox.R4 -> FalseLeaf - ), - ModifierId @@ ("218301ae8000018008637f0021fb9e00018055486f0b514121016a00ff718080"), - 22588.toShort, - 677407 - ) - ) - - val b2: Box = CostingBox( - false, - new ErgoBox( - 12345L, - new ErgoTree( - 0.toByte, - Vector(), - Right( - BoolToSigmaProp( - AND( - ConcreteCollection( - Array( - FalseLeaf, - XorOf( - ConcreteCollection(Array(EQ(IntConstant(1), IntConstant(1)), FalseLeaf), SBoolean) - ) - ), - SBoolean - ) - ) - ) - ) - ), - Coll(), - Map( - ErgoBox.R5 -> ByteArrayConstant( - Helpers.decodeBytes( - "297000800b80f1d56c809a8c6affbed864b87f007f6f007f00ac00018c01c4fdff011088807f0100657f00f9ab0101ff6d65" - ) - ), - ErgoBox.R4 -> TrueLeaf, - ErgoBox.R7 -> LongConstant(9223372036854775807L), - ErgoBox.R6 -> LongConstant(2115927197107005906L) - ), - ModifierId @@ ("003bd5c630803cfff6c1ff7f7fb980ff136afc011f8080b8b04ad4dbda2d7f4e"), - 1.toShort, - 1000000 - ) - ) - - val preH1: PreHeader = CPreHeader( - 0.toByte, - Helpers.decodeBytes("7fff7fdd6f62018bae0001006d9ca888ff7f56ff8006573700a167f17f2c9f40"), - 6306290372572472443L, - -3683306095029417063L, - 1, - Helpers.decodeGroupElement("026930cb9972e01534918a6f6d6b8e35bc398f57140d13eb3623ea31fbd069939b"), - Helpers.decodeBytes("ff8087") - ) - - val preH2: PreHeader = preH1.asInstanceOf[CPreHeader].copy(height = 2) - - val treeData = AvlTreeData( - ADDigest @@ ( - ErgoAlgos.decodeUnsafe("010180017f7f7b7f720c00007f7f7f0f01e857a626f37f1483d06af8077a008080") - ), - AvlTreeFlags(false, true, false), - 728138553, - Some(2147483647) - ) - val h1: Header = CHeader( - Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a"), - 0.toByte, - Helpers.decodeBytes("0180dd805b0000ff5400b997fd7f0b9b00de00fb03c47e37806a8186b94f07ff"), - Helpers.decodeBytes("01f07f60d100ffb970c3007f60ff7f24d4070bb8fffa7fca7f34c10001ffe39d"), - CAvlTree(treeData), - Helpers.decodeBytes("804101ff01000080a3ffbd006ac080098df132a7017f00649311ec0e00000100"), - 1L, - -1L, - 1, - Helpers.decodeBytes("e57f80885601b8ff348e01808000bcfc767f2dd37f0d01015030ec018080bc62"), - Helpers.decodeGroupElement("039bdbfa0b49cc6bef58297a85feff45f7bbeb500a9d2283004c74fcedd4bd2904"), - Helpers.decodeGroupElement("0361299207fa392231e23666f6945ae3e867b978e021d8d702872bde454e9abe9c"), - Helpers.decodeBytes("7f4f09012a807f01"), - CBigInt(new BigInteger("-e24990c47e15ed4d0178c44f1790cc72155d516c43c3e8684e75db3800a288", 16)), - Helpers.decodeBytes("7f0180") - ) - val h2: Header = h1.asInstanceOf[CHeader].copy(height = 2) - - } import TestData._ prepareSamples[BigInt] @@ -3581,7 +3426,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui existingPropTest("ADProofsRoot", { (x: Header) => x.ADProofsRoot})) verifyCases( - Seq((h1, Expected(Success(CAvlTree(treeData)), cost = 36092))), + Seq((h1, Expected(Success(CAvlTree(createAvlTreeData())), cost = 36092))), existingPropTest("stateRoot", { (x: Header) => x.stateRoot })) verifyCases( @@ -3809,6 +3654,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui .append(Coll[AnyValue]( TestValue(Helpers.decodeBytes("00"), CollType(RType.ByteType)), TestValue(true, RType.BooleanType))), + activatedScriptVersion = activatedVersionInTests, false ) val ctx2 = ctx.copy(vars = Coll[AnyValue](null, null, null)) @@ -6431,7 +6277,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui }, existingFeature( { (x: (Coll[Byte], Int)) => - SigmaDsl.substConstants(x._1, Coll[Int](x._2), Coll[Any](SigmaDsl.sigmaProp(false))(RType.AnyType))(RType.AnyType) + SigmaDsl.substConstants(x._1, Coll[Int](x._2), Coll[Any](SigmaDsl.sigmaProp(false))(RType.AnyType)) }, "{ (x: (Coll[Byte], Int)) => substConstants[Any](x._1, Coll[Int](x._2), Coll[Any](sigmaProp(false))) }", FuncValue( diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslStaginTests.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslStaginTests.scala index 8ba908b7b4..a996617525 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslStaginTests.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslStaginTests.scala @@ -6,6 +6,7 @@ import scala.language.reflectiveCalls import scalan.{BaseLiftableTests, BaseCtxTests} import sigmastate.eval.Extensions._ import sigmastate.eval.{IRContext, ErgoScriptTestkit} +import sigmastate.interpreter.Interpreter class SigmaDslStaginTests extends BaseCtxTests with ErgoScriptTestkit with BaseLiftableTests { class Ctx extends TestContext with IRContext with LiftableTestKit { @@ -29,7 +30,7 @@ class SigmaDslStaginTests extends BaseCtxTests with ErgoScriptTestkit with BaseL type RSigmaProp = cake.SigmaProp val boxA1 = newAliceBox(1, 100) val boxA2 = newAliceBox(2, 200) - val ctx: SContext = newContext(10, boxA1) + val ctx: SContext = newContext(10, boxA1, Interpreter.MaxSupportedScriptVersion) .withInputs(boxA2) .withVariables(Map(1 -> toAnyValue(30), 2 -> toAnyValue(40))) val p1: SSigmaProp = new special.sigma.MockSigma(true) diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala index ea5eab5e14..89ec0ab7e0 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala @@ -43,6 +43,7 @@ class SigmaDslTesting extends PropSpec with Matchers with SigmaTestingData with SigmaContractSyntax with ObjectGenerators { suite => + override def Coll[T](items: T*)(implicit cT: RType[T]): Coll[T] = super.Coll(items:_*) lazy val spec: ContractSpec = TestContractSpec(suite)(new TestingIRContext) diff --git a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala index 1ce73cdbe2..30751521ba 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala @@ -1,8 +1,11 @@ package special.sigma +import java.math.BigInteger + +import org.ergoplatform.ErgoBox import org.ergoplatform.settings.ErgoAlgos import org.scalacheck.Gen.containerOfN -import sigmastate.AvlTreeFlags +import sigmastate._ import org.scalacheck.{Arbitrary, Gen} import sigmastate.helpers.SigmaTestingCommons import sigmastate.eval._ @@ -10,13 +13,22 @@ import sigmastate.eval.Extensions._ import org.scalacheck.util.Buildable import scalan.RType import scorex.crypto.hash.{Digest32, Blake2b256} -import scorex.crypto.authds.{ADKey, ADValue} -import sigmastate.Values.ErgoTree +import scorex.crypto.authds.{ADDigest, ADKey, ADValue} +import scorex.util.ModifierId +import sigmastate.Values._ +import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.basics.ProveDHTuple +import sigmastate.interpreter.CryptoConstants.EcPointType import sigmastate.serialization.ErgoTreeSerializer import sigmastate.serialization.generators.ObjectGenerators +import sigmastate.utils.Helpers import special.collection.Coll +import scala.reflect.ClassTag + trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { + def Coll[T](items: T*)(implicit cT: RType[T]) = CostingSigmaDslBuilder.Colls.fromItems(items:_*) + def collOfN[T: RType: Arbitrary](n: Int)(implicit b: Buildable[T, Array[T]]): Gen[Coll[T]] = { implicit val g: Gen[T] = Arbitrary.arbitrary[T] containerOfN[Array, T](n, g).map(Colls.fromArray(_)) @@ -102,6 +114,233 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { votes = Colls.emptyColl[Byte] ) + object TestData { + val BigIntZero: BigInt = CBigInt(new BigInteger("0", 16)) + val BigIntOne: BigInt = CBigInt(new BigInteger("1", 16)) + val BigIntMinusOne: BigInt = CBigInt(new BigInteger("-1", 16)) + val BigInt10: BigInt = CBigInt(new BigInteger("a", 16)) + val BigInt11: BigInt = CBigInt(new BigInteger("b", 16)) + + val BigIntMaxValueStr = "7F" + "ff" * 31 + val BigIntMaxValue_instances = new CloneSet(1000, + CBigInt(new BigInteger(BigIntMaxValueStr, 16))) + + def createBigIntMaxValue(): BigInt = BigIntMaxValue_instances.getNext + + // TODO HF: this values have bitCount == 255 (see to256BitValueExact) + val BigIntMinValue = CBigInt(new BigInteger("-7F" + "ff" * 31, 16)) + val BigIntMaxValue = createBigIntMaxValue() + val BigIntOverlimit = CBigInt(new BigInteger("7F" + "ff" * 33, 16)) + + val ge1str = "03358d53f01276211f92d0aefbd278805121d4ff6eb534b777af1ee8abae5b2056" + val ge2str = "02dba7b94b111f3894e2f9120b577da595ec7d58d488485adf73bf4e153af63575" + val ge3str = "0290449814f5671172dd696a61b8aa49aaa4c87013f56165e27d49944e98bc414d" + + val ge1_bytes = ErgoAlgos.decodeUnsafe(ge1str) + + class CloneSet[T: ClassTag](val size: Int, generator: => T) { + val instances = Array.fill(size)(generator) + var currentInst: Int = 0 + + /** Selects next instance (round-robin). */ + def getNext: T = { + val res = instances(currentInst) + currentInst = (currentInst + 1) % size + res + } + } + + val ge1_instances = new CloneSet(1000, SigmaDsl.decodePoint(Colls.fromArray(ge1_bytes))) + + /** Selects next ge1 instance (round-robin). */ + def create_ge1(): GroupElement = ge1_instances.getNext + + val ge1 = create_ge1() + + val ge2_bytes = ErgoAlgos.decodeUnsafe(ge2str) + val ge2_instances = new CloneSet(1000, SigmaDsl.decodePoint(Colls.fromArray(ge2_bytes))) + def create_ge2(): GroupElement = ge2_instances.getNext + val ge2 = create_ge2() + + val ge3 = Helpers.decodeGroupElement(ge3str) + + val t1_instances = new CloneSet(1000, CAvlTree( + AvlTreeData( + ADDigest @@ ErgoAlgos.decodeUnsafe("000183807f66b301530120ff7fc6bd6601ff01ff7f7d2bedbbffff00187fe89094"), + AvlTreeFlags(false, true, true), + 1, + Some(1) + ) + )) + + def create_t1(): AvlTree = t1_instances.getNext + + val t1: AvlTree = create_t1() + val t2: AvlTree = CAvlTree( + AvlTreeData( + ADDigest @@ ErgoAlgos.decodeUnsafe("ff000d937f80ffd731ed802d24358001ff8080ff71007f00ad37e0a7ae43fff95b"), + AvlTreeFlags(false, false, false), + 32, + Some(64) + ) + ) + val t3: AvlTree = CAvlTree( + AvlTreeData( + ADDigest @@ ErgoAlgos.decodeUnsafe("3100d2e101ff01fc047c7f6f00ff80129df69a5090012f01ffca99f5bfff0c8036"), + AvlTreeFlags(true, false, false), + 128, + None + ) + ) + + val b1_instances = new CloneSet(1000, CostingBox( + false, + new ErgoBox( + 9223372036854775807L, + new ErgoTree( + 16.toByte, + Array( + SigmaPropConstant( + CSigmaProp( + ProveDlog( + Helpers.decodeECPoint( + "0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e" + ) + ) + ) + ) + ), + Right(ConstantPlaceholder(0, SSigmaProp)) + ), + Coll( + (Digest32 @@ (ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001")), + 10000000L), + (Digest32 @@ (ErgoAlgos.decodeUnsafe("a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600")), + 500L) + ), + Map( + ErgoBox.R5 -> ByteArrayConstant(Helpers.decodeBytes("7fc87f7f01ff")), + ErgoBox.R4 -> FalseLeaf + ), + ModifierId @@ ("218301ae8000018008637f0021fb9e00018055486f0b514121016a00ff718080"), + 22588.toShort, + 677407 + ) + )) + + def create_b1(): Box = b1_instances.getNext + + val b1: Box = create_b1() + + val b2: Box = CostingBox( + false, + new ErgoBox( + 12345L, + new ErgoTree( + 0.toByte, + Vector(), + Right( + BoolToSigmaProp( + AND( + ConcreteCollection( + Array( + FalseLeaf, + XorOf( + ConcreteCollection(Array(EQ(IntConstant(1), IntConstant(1)), FalseLeaf), SBoolean) + ) + ), + SBoolean + ) + ) + ) + ) + ), + Coll(), + Map( + ErgoBox.R5 -> ByteArrayConstant( + Helpers.decodeBytes( + "297000800b80f1d56c809a8c6affbed864b87f007f6f007f00ac00018c01c4fdff011088807f0100657f00f9ab0101ff6d65" + ) + ), + ErgoBox.R4 -> TrueLeaf, + ErgoBox.R7 -> LongConstant(9223372036854775807L), + ErgoBox.R6 -> LongConstant(2115927197107005906L) + ), + ModifierId @@ ("003bd5c630803cfff6c1ff7f7fb980ff136afc011f8080b8b04ad4dbda2d7f4e"), + 1.toShort, + 1000000 + ) + ) + + val preH1_instances = new CloneSet(1000, CPreHeader( + 0.toByte, + Helpers.decodeBytes("7fff7fdd6f62018bae0001006d9ca888ff7f56ff8006573700a167f17f2c9f40"), + 6306290372572472443L, + -3683306095029417063L, + 1, + Helpers.decodeGroupElement("026930cb9972e01534918a6f6d6b8e35bc398f57140d13eb3623ea31fbd069939b"), + Helpers.decodeBytes("ff8087") + )) + + def create_preH1(): PreHeader = preH1_instances.getNext + + val preH1: PreHeader = create_preH1() + + val preH2: PreHeader = create_preH1().asInstanceOf[CPreHeader].copy(height = 2) + + def createAvlTreeData() = AvlTreeData( + ADDigest @@ ( + ErgoAlgos.decodeUnsafe("010180017f7f7b7f720c00007f7f7f0f01e857a626f37f1483d06af8077a008080") + ), + AvlTreeFlags(false, true, false), + 728138553, + Some(2147483647) + ) + + val h1_instances = new CloneSet(1000, CHeader( + Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a"), + 0.toByte, + Helpers.decodeBytes("0180dd805b0000ff5400b997fd7f0b9b00de00fb03c47e37806a8186b94f07ff"), + Helpers.decodeBytes("01f07f60d100ffb970c3007f60ff7f24d4070bb8fffa7fca7f34c10001ffe39d"), + CAvlTree(createAvlTreeData()), + Helpers.decodeBytes("804101ff01000080a3ffbd006ac080098df132a7017f00649311ec0e00000100"), + 1L, + -1L, + 1, + Helpers.decodeBytes("e57f80885601b8ff348e01808000bcfc767f2dd37f0d01015030ec018080bc62"), + Helpers.decodeGroupElement("039bdbfa0b49cc6bef58297a85feff45f7bbeb500a9d2283004c74fcedd4bd2904"), + Helpers.decodeGroupElement("0361299207fa392231e23666f6945ae3e867b978e021d8d702872bde454e9abe9c"), + Helpers.decodeBytes("7f4f09012a807f01"), + CBigInt(new BigInteger("-e24990c47e15ed4d0178c44f1790cc72155d516c43c3e8684e75db3800a288", 16)), + Helpers.decodeBytes("7f0180") + )) + + def create_h1(): Header = h1_instances.getNext + + val h1: Header = create_h1() + + val h2: Header = create_h1().asInstanceOf[CHeader].copy(height = 2) + + val dlog_instances = new CloneSet(1000, ProveDlog( + SigmaDsl.toECPoint(create_ge1()).asInstanceOf[EcPointType] + )) + + def create_dlog(): ProveDlog = dlog_instances.getNext + + val dht_instances = new CloneSet(1000, ProveDHTuple( + create_dlog().value, + SigmaDsl.toECPoint(create_ge2()).asInstanceOf[EcPointType], + create_dlog().value, + SigmaDsl.toECPoint(create_ge2()).asInstanceOf[EcPointType] + )) + + def create_dht(): ProveDHTuple = dht_instances.getNext + + def create_and() = CAND(Array(create_dlog(), create_dht())) + + def create_or() = COR(Array(create_dlog(), create_dht())) + } + /** A list of ErgoTree hexes which are the most often used in Ergo mainnet. */ val predefScriptHexes = Seq( "0008cd03a40b2249d6a9cc7eedf21188842acef44f3df110a58a687ba3e28cbc2e97ead2",