From fa4b4b4680cd02b93eba33c69569de6cae659e4d Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 22 Nov 2024 23:39:43 +0300 Subject: [PATCH] doc update --- .../src/main/scala/sigma/SigmaDsl.scala | 2 - docs/LangSpec.md | 277 ++++++++++++++++-- 2 files changed, 247 insertions(+), 32 deletions(-) diff --git a/core/shared/src/main/scala/sigma/SigmaDsl.scala b/core/shared/src/main/scala/sigma/SigmaDsl.scala index 821080ec0..e29570233 100644 --- a/core/shared/src/main/scala/sigma/SigmaDsl.scala +++ b/core/shared/src/main/scala/sigma/SigmaDsl.scala @@ -1,7 +1,5 @@ package sigma -import sigma.ast.SType - import java.math.BigInteger import sigma.data._ diff --git a/docs/LangSpec.md b/docs/LangSpec.md index 5cbb569a5..3f5453072 100644 --- a/docs/LangSpec.md +++ b/docs/LangSpec.md @@ -4,18 +4,18 @@ 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 +[ErgoTrees](https://ergoplatform.org/docs/ErgoTree.pdf), typed abstract 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 +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). +[Appkit](https://github.com/ergoplatform/ergo-appkit) or [Fleet](https://github.com/fleet-sdk) . 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. +from any JVM langugage and also on Android and JavaFX platforms. It is also cross-compiled to JavaScript using Scala.js. The following example shows how source code of ErgoScript contract can be used to create new transaction using @@ -56,7 +56,7 @@ The following sections describe ErgoScript and its operations. #### ErgoScript language features overview -- syntax borrowed from Scala +- syntax borrowed from Scala but simplified - standard syntax and semantics for well known constructs (operations, code blocks, if branches etc.) - high-order language with first-class lambdas which are used in collection operations - call-by-value (eager evaluation) @@ -97,6 +97,7 @@ Type Name | Description `Int` | 32 bit signed integer `Long` | 64 bit signed integer `BigInt` | 256 bit signed integer +`UnsignedBigInt` | 256 bit unsigned integer `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 @@ -119,11 +120,12 @@ types as in the following example. 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 - val bool: Boolean = true // logical literal - val arr = Coll(1, 2, 3) // constructs a collection with given items - val str = "abc" // string of characters + val unit: Unit = () // unit constant + val long: Int = 10 // interger value literal + val bool: Boolean = true // logical literal + val arr = Coll(1, 2, 3) // constructs a collection with given items + val str = "abc" // string of characters + val ubi = unsignedBigInt("508473958676860") // unsigned big integer ``` Note that many types don't have literal syntax and their values are introduced by applying operations, for example `deserialize` function can be used to introduce @@ -167,9 +169,61 @@ class Numeric { /** Convert this Numeric value to BigInt. */ def toBigInt: BigInt + + /** Convert this Numeric value to UnsignedBigInt. */ + def toUnsignedBigInt: UnsignedBigInt + + /** Returns a big-endian representation of this value in a collection of bytes. + * For example, the `Int` value `0x12131415` would yield the + * collection of bytes [0x12, 0x13, 0x14, 0x15] + */ + def toBytes(x: T): Coll[Byte] + + /** + * Returns a big-endian binary representation of this value as boolean array. + */ + def toBits(x: T): Coll[Boolean] + + /** + * @return a numeric value which is inverse of `x` (every bit, including sign, is flipped) + */ + def bitwiseInverse(x: T): T + + /** + * @return a numeric value which is `this | that` + */ + def bitwiseOr(x: T, y: T): T + + /** + * @return a numeric value which is `this && that` + */ + def bitwiseAnd(x: T, y: T): T + + /** + * @return a numeric value which is `this xor that` + */ + def bitwiseXor(x: T, y: T): T + + /** + * @return a value which is (this << n). The shift distance, n, may be negative, + * in which case this method performs a right shift. (Computes floor(this * 2n).) + */ + def shiftLeft(x: T, bits: Int): T + + /** + * @return a value which is (this >> n). Sign extension is performed. The shift distance, n, + * may be negative, in which case this method performs a left shift. (Computes floor(this / 2n).) + */ + def shiftRight(x: T, bits: Int): T + } ``` +The only exception for conversions is about BigInt to and from UnsignedBigInt. To convert from signed big int to unsigned, use +`.toUnsigned` method to convert signed big integer to unsigned, or `.toUnsignedMod(m)` to convert modulo `m` (and modulo + operation is cryptographic, ie always returns positive number modulo `m`). To convert from unsigned big int to signed, + use `.toSigned`. + All the predefined numeric types inherit Numeric class and its methods. They can be thought as being pre-defined like the following. @@ -179,8 +233,51 @@ class Short extends Numeric class Int extends Numeric class Long extends Numeric class BigInt extends Numeric +class UnsignedBigInt extends Numeric +``` + +examples: + +``` +val b = (126 + 1).toByte // max byte value +b.bitwiseInverse == (-128).toByte // min byte value +``` + +``` +val x = 4.toByte +val y = 2 +x.shiftLeft(y) == 16.toByte +``` + +``` +val l = getVar[Long](1).get +val ba = l.toBytes +Global.fromBigEndianBytes[Long](ba) == l +``` + +In addition to all the methods from above, UnsignedBigInt type has support for modular arithmetics, namely, `modInverse`, +`plusMod`, `subtractMod`, `multiplyMod`, `mod`. Examples: + +``` +val bi1 = unsignedBigInt("2") +val bi2 = unsignedBigInt("4") +val m = unsignedBigInt("575879797") +bi1.subtractMod(bi2, m) > 0 ``` +``` +val bi = unsignedBigInt("248486720836984554860790790898080606") +val m = unsignedBigInt("575879797") +bi.mod(m) < bi +``` + +``` +val bi = unsignedBigInt("248486720836984554860790790898080606") +val m = unsignedBigInt("575879797") +bi.modInverse(m) > 0 // returns multiplicative inverse of bi mod m +``` + + #### Context Data Every script is executed in a context, which is a collection of data available @@ -245,13 +342,34 @@ class Context { */ 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. + */ + def getVar[T](id: Byte): Option[T] + + /** + * A variant of `getVar` to extract a context variable by id and type from any input + */ + def getVarFromInput[T](inputIndex: Short, id: Byte): Option[T] + + /** Returns new $coll with elements in reversed order. + * + * @return A new $coll with all elements of this $coll in reversed order. + */ + def reverse: Coll[T] + + /** Builds a new $coll from this $coll without any duplicate elements. + * + * @return A new $coll which contains the first occurrence of every element of this $coll. + */ + def distinct: Coll[T] + } /** Represents data of the block headers available in scripts. */ class Header { - - /** Validate header's proof-of-work */ - def checkPow: Boolean /** Bytes representation of ModifierId of this Header */ def id: Coll[Byte] @@ -276,7 +394,7 @@ class Header { /** Current difficulty in a compressed view. * NOTE: actually it is unsigned Int*/ - def nBits: Long // actually it is unsigned Int + def nBits: Long /** Block height */ def height: Int @@ -288,24 +406,16 @@ class Header { * 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] + + /** Validate header's proof-of-work */ + def checkPow: Boolean + } /** Only header fields that can be predicted by a miner. */ @@ -458,6 +568,12 @@ class GroupElement { * @return this to the power of k. */ def exp(k: BigInt): GroupElement + + /** Exponentiate this GroupElement to the given unsigned 256 bit integer. + * @param k The power. + * @return this to the power of k. + */ + def expUnsigned(k: UnsignedBigInt): GroupElement /** Group operation. */ def multiply(that: GroupElement): GroupElement @@ -669,8 +785,7 @@ class Option[A] { /** Returns the option's value if the option is nonempty, otherwise * return the result of evaluating `default`. - * NOTE: the `default` is evaluated even if the option contains the value - * i.e. not lazily. + * NOTE: the `default` is evaluated lazily. * * @param default the default expression. */ @@ -845,6 +960,27 @@ class Coll[A] { * to `elem`, or `-1`, if none exists. */ def indexOf(elem: A, from: Int): Int + + /** The element at given index or None if there is no such element. Indices start at `0`. + * + * @param i the index + * @return the element at the given index, or None if there is no such element + */ + def get(i: Int): Option[A] + + /** + * @return true if first elements of this collection form given `ys` collection, false otherwise. + * E.g. [1,2,3] starts with [1,2] + */ + def startsWith(ys: Coll[A]): Boolean + + /** + * @return true if last elements of this collection form given `ys` collection, false otherwise. + * E.g. [1,2,3] ends with [2,3] + */ + def endsWith(ys: Coll[A]): Boolean + + } ``` @@ -854,6 +990,14 @@ val myOutput = OUTPUTS(0) val myInput = INPUTS(0) ``` +or, if you want to get None instead of exception: +``` +val elemOpt = coll.get(0) +if (elemOpt.isDefined) { + val elem = elemOpt.get +} +``` + Any collection have the `size` property which returns the number of elements in the collection. @@ -868,6 +1012,73 @@ satisfying some predicate (condition) val ok = OUTPUTS.exists { (box: Box) => box.value > 1000 } ``` +### Predefined global functions + +There are some functions which do not belong to other types, thus they put under `Global` type. Those functions are: + + + +``` +{ + /** The generator g of the group is an element of the group such that, when written + * multiplicative form, every element of the group is a power of g. + * @return the generator of this Dlog group + */ + def groupGenerator: GroupElement + + /** + * @return NBits-encoded approximate representation of given big integer, + * see (https://bitcoin.stackexchange.com/questions/57184/what-does-the-nbits-value-represent) + * for NBits format details + */ + def encodeNbits(bi: BigInt): Long + + /** + * @return big integer decoded from NBits value provided, + * see (https://bitcoin.stackexchange.com/questions/57184/what-does-the-nbits-value-represent) + * for format details + */ + def decodeNbits(l: Long): BigInt + + + /** Serializes the given `value` into bytes using the default serialization format. */ + def serialize[T](value: T)(implicit cT: RType[T]): Coll[Byte] + + /** Returns a byte-wise XOR of the two collections of bytes. */ + def xor(l: Coll[Byte], r: Coll[Byte]): Coll[Byte] + + /** Calculates value of a custom Autolykos 2 hash function + * @param k - k parameter of Autolykos 2 (number of inputs in k-sum problem) + * @param msg - message to calculate Autolykos hash 2 for + * @param nonce - used to pad the message to get Proof-of-Work hash function output with desirable properties + * @param h - PoW protocol specific padding for table uniqueness (e.g. block height in Ergo) + */ + def powHit(k: Int, msg: Coll[Byte], nonce: Coll[Byte], h: Coll[Byte], N: Int): BigInt + + /** Deserializes provided `bytes` into a value of type `T`. **/ + def deserializeTo[T](bytes: Coll[Byte])(implicit cT: RType[T]): T + + /** Returns a number decoded from provided big-endian bytes array. */ + def fromBigEndianBytes[T](bytes: Coll[Byte])(implicit cT: RType[T]): T +} +``` + +examples: + +``` +val h = getVar[Header](21).get +val n = h.nBits +val target = Global.decodeNbits(n) +``` + +``` +val src = getVar[Coll[Short]](21).get +val ba = Global.serialize(src) +val restored = Global.deserializeTo[Coll[Short]](ba) +src == restored +``` + + ### Predefined global functions @@ -1011,6 +1222,12 @@ def proveDlog(value: GroupElement): SigmaProp */ def bigInt(input: String): BigInt +/** Transforms Base16 encoded string literal into constant of type UnsignedBigInt. + * It is a compile-time operation and only string literal (constant) can be its + * argument. + */ +def unsignedBigInt(input: String): UnsignedBigInt + /** Transforms Base16 encoded string literal into constant of type Coll[Byte]. * It is a compile-time operation and only string literal (constant) can be its * argument.