From f195072c03c32fad7032f064594a9616fbb122d2 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 22 Nov 2024 12:48:03 +0300 Subject: [PATCH 1/2] Global.deserializeTo[] method --- .../src/main/scala/sigma/SigmaDsl.scala | 4 +- .../sigma/reflection/ReflectionData.scala | 3 + .../serialization/CoreDataSerializer.scala | 2 +- .../scala/sigma/SigmaDataReflection.scala | 5 + .../main/scala/sigma/ast/SigmaPredef.scala | 20 ++ .../src/main/scala/sigma/ast/methods.scala | 20 ++ .../src/main/scala/sigma/data/CHeader.scala | 6 +- .../scala/sigma/data/CSigmaDslBuilder.scala | 13 +- docs/LangSpec.md | 6 +- .../sigmastate/interpreter/Interpreter.scala | 2 +- .../MethodCallSerializerSpecification.scala | 27 +- .../special/sigma/SigmaTestingData.scala | 7 +- .../main/scala/sigma/compiler/ir/Base.scala | 2 +- .../sigma/compiler/ir/GraphBuilding.scala | 7 +- .../sigma/compiler/ir/GraphIRReflection.scala | 4 + .../sigma/compiler/ir/TreeBuilding.scala | 11 +- .../ir/wrappers/sigma/SigmaDslUnit.scala | 1 + .../ir/wrappers/sigma/impl/SigmaDslImpl.scala | 26 +- .../sigma/compiler/phases/SigmaTyper.scala | 7 +- .../scala/sigma/LanguageSpecificationV6.scala | 128 ++++++- .../sigmastate/ErgoTreeSpecification.scala | 3 +- .../TestingInterpreterSpecification.scala | 1 + .../utxo/BasicOpsSpecification.scala | 337 +++++++++++++++++- .../ExecuteFromExamplesSpecification.scala | 2 - 24 files changed, 617 insertions(+), 27 deletions(-) diff --git a/core/shared/src/main/scala/sigma/SigmaDsl.scala b/core/shared/src/main/scala/sigma/SigmaDsl.scala index c969969504..d5e4d5e6c7 100644 --- a/core/shared/src/main/scala/sigma/SigmaDsl.scala +++ b/core/shared/src/main/scala/sigma/SigmaDsl.scala @@ -490,7 +490,6 @@ trait Header { * @return result of header's proof-of-work validation */ def checkPow: Boolean - } /** Runtime representation of Context ErgoTree type. @@ -790,6 +789,9 @@ trait SigmaDslBuilder { /** Returns a byte-wise XOR of the two collections of bytes. */ def xor(l: Coll[Byte], r: Coll[Byte]): Coll[Byte] + /** 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 } diff --git a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala index 702f836fde..6935328d3c 100644 --- a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala +++ b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala @@ -469,6 +469,9 @@ object ReflectionData { mkMethod(clazz, "decodePoint", Array[Class[_]](cColl)) { (obj, args) => obj.asInstanceOf[SigmaDslBuilder].decodePoint(args(0).asInstanceOf[Coll[Byte]]) }, + mkMethod(clazz, "deserializeTo", Array[Class[_]](cColl, classOf[RType[_]])) { (obj, args) => + obj.asInstanceOf[SigmaDslBuilder].deserializeTo(args(0).asInstanceOf[Coll[Byte]])(args(1).asInstanceOf[RType[_]]) + }, mkMethod(clazz, "fromBigEndianBytes", Array[Class[_]](cColl, classOf[RType[_]])) { (obj, args) => obj.asInstanceOf[SigmaDslBuilder].fromBigEndianBytes(args(0).asInstanceOf[Coll[Byte]])(args(1).asInstanceOf[RType[_]]) }, diff --git a/core/shared/src/main/scala/sigma/serialization/CoreDataSerializer.scala b/core/shared/src/main/scala/sigma/serialization/CoreDataSerializer.scala index 233494392a..d33b340284 100644 --- a/core/shared/src/main/scala/sigma/serialization/CoreDataSerializer.scala +++ b/core/shared/src/main/scala/sigma/serialization/CoreDataSerializer.scala @@ -135,7 +135,7 @@ class CoreDataSerializer { res } - def deserializeColl[T <: SType](len: Int, tpeElem: T, r: CoreByteReader): Coll[T#WrappedType] = + private def deserializeColl[T <: SType](len: Int, tpeElem: T, r: CoreByteReader): Coll[T#WrappedType] = tpeElem match { case SBoolean => Colls.fromArray(r.getBits(len)).asInstanceOf[Coll[T#WrappedType]] diff --git a/data/shared/src/main/scala/sigma/SigmaDataReflection.scala b/data/shared/src/main/scala/sigma/SigmaDataReflection.scala index 0f7cb59150..0aae2210ce 100644 --- a/data/shared/src/main/scala/sigma/SigmaDataReflection.scala +++ b/data/shared/src/main/scala/sigma/SigmaDataReflection.scala @@ -359,6 +359,11 @@ object SigmaDataReflection { obj.asInstanceOf[SGlobalMethods.type].serialize_eval(args(0).asInstanceOf[MethodCall], args(1).asInstanceOf[SigmaDslBuilder], args(2).asInstanceOf[SType#WrappedType])(args(3).asInstanceOf[ErgoTreeEvaluator]) + }, + mkMethod(clazz, "deserializeTo_eval", Array[Class[_]](classOf[MethodCall], classOf[SigmaDslBuilder], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) => + obj.asInstanceOf[SGlobalMethods.type].deserializeTo_eval(args(0).asInstanceOf[MethodCall], + args(1).asInstanceOf[SigmaDslBuilder], + args(2).asInstanceOf[Coll[Byte]])(args(3).asInstanceOf[ErgoTreeEvaluator]) } ) ) diff --git a/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala b/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala index 4f18c170fd..5226d645ce 100644 --- a/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala +++ b/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala @@ -432,6 +432,25 @@ object SigmaPredef { ) ) + val DeserializeToFunc = PredefinedFunc("deserializeTo", + Lambda(Seq(paramT), Array("bytes" -> SByteArray), tT, None), + irInfo = PredefFuncInfo( + irBuilder = { case (u, args) => + val resType = u.opType.tRange.asInstanceOf[SFunc].tRange + MethodCall( + Global, + SGlobalMethods.deserializeToMethod.withConcreteTypes(Map(tT -> resType)), + args.toIndexedSeq, + Map(tT -> resType) + ) + }), + docInfo = OperationInfo(MethodCall, + """Deserializes provided bytes into a value of given type using the default serialization format. + """.stripMargin, + Seq(ArgInfo("bytes", "bytes to deserialize")) + ) + ) + val FromBigEndianBytesFunc = PredefinedFunc("fromBigEndianBytes", Lambda(Seq(paramT), Array("bytes" -> SByteArray), tT, None), irInfo = PredefFuncInfo( @@ -480,6 +499,7 @@ object SigmaPredef { ExecuteFromVarFunc, ExecuteFromSelfRegFunc, SerializeFunc, + DeserializeToFunc, GetVarFromInputFunc, FromBigEndianBytesFunc ).map(f => f.name -> f).toMap diff --git a/data/shared/src/main/scala/sigma/ast/methods.scala b/data/shared/src/main/scala/sigma/ast/methods.scala index a8f674d995..84bb4f0943 100644 --- a/data/shared/src/main/scala/sigma/ast/methods.scala +++ b/data/shared/src/main/scala/sigma/ast/methods.scala @@ -1754,6 +1754,7 @@ case object SHeaderMethods extends MonoTypeMethods { lazy val powDistanceMethod = propertyCall("powDistance", SBigInt, 14, FixedCost(JitCost(10))) lazy val votesMethod = propertyCall("votes", SByteArray, 15, FixedCost(JitCost(10))) + // methods added in 6.0 below // cost of checkPoW is 700 as about 2*32 hashes required, and 1 hash (id) over short data costs 10 lazy val checkPowMethod = SMethod( this, "checkPow", SFunc(Array(SHeader), SBoolean), 16, FixedCost(JitCost(700))) @@ -1824,6 +1825,15 @@ case object SGlobalMethods extends MonoTypeMethods { .withInfo(Xor, "Byte-wise XOR of two collections of bytes", ArgInfo("left", "left operand"), ArgInfo("right", "right operand")) + private val deserializeCostKind = PerItemCost(baseCost = JitCost(30), perChunkCost = JitCost(20), chunkSize = 32) + + lazy val deserializeToMethod = SMethod( + this, "deserializeTo", SFunc(Array(SGlobal, SByteArray), tT, Array(paramT)), 4, deserializeCostKind, Seq(tT)) + .withIRInfo(MethodCallIrBuilder, + javaMethodOf[SigmaDslBuilder, Coll[Byte], RType[_]]("deserializeTo")) + .withInfo(MethodCall, "Deserialize provided bytes into an object of requested type", + ArgInfo("first", "Bytes to deserialize")) + /** Implements evaluation of Global.xor method call ErgoTree node. * Called via reflection based on naming convention. * @see SMethod.evalMethod, Xor.eval, Xor.xorWithCosting @@ -1859,6 +1869,15 @@ case object SGlobalMethods extends MonoTypeMethods { .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, "Decode nbits-encoded big integer number", ArgInfo("nbits", "NBits-encoded argument")) + def deserializeTo_eval(mc: MethodCall, G: SigmaDslBuilder, bytes: Coll[Byte]) + (implicit E: ErgoTreeEvaluator): Any = { + val tpe = mc.tpe + val cT = stypeToRType(tpe) + E.addSeqCost(deserializeCostKind, bytes.length, deserializeToMethod.opDesc) { () => + G.deserializeTo(bytes)(cT) + } + } + lazy val serializeMethod = SMethod(this, "serialize", SFunc(Array(SGlobal, tT), SByteArray, Array(paramT)), 3, DynamicCost) .withIRInfo(MethodCallIrBuilder) @@ -1894,6 +1913,7 @@ case object SGlobalMethods extends MonoTypeMethods { groupGeneratorMethod, xorMethod, serializeMethod, + deserializeToMethod, encodeNBitsMethod, decodeNBitsMethod, fromBigEndianBytesMethod diff --git a/data/shared/src/main/scala/sigma/data/CHeader.scala b/data/shared/src/main/scala/sigma/data/CHeader.scala index aa5a5756d2..3acb295835 100644 --- a/data/shared/src/main/scala/sigma/data/CHeader.scala +++ b/data/shared/src/main/scala/sigma/data/CHeader.scala @@ -71,7 +71,11 @@ class CHeader(val ergoHeader: ErgoHeader) extends Header with WrapperOf[ErgoHead } override def checkPow: Boolean = { - Autolykos2PowValidation.checkPoWForVersion2(this) + if (version == 1) { + throw new Exception("Autolykos v1 is not supported") //todo: more specific exception? + } else { + Autolykos2PowValidation.checkPoWForVersion2(this) + } } override def toString: String = diff --git a/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala b/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala index 9211837812..f540df55c2 100644 --- a/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala +++ b/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala @@ -1,15 +1,18 @@ package sigma.data import debox.cfor -import org.ergoplatform.ErgoBox +import org.ergoplatform.{ErgoBox, ErgoHeader} import org.ergoplatform.validation.ValidationRules import scorex.crypto.hash.{Blake2b256, Sha256} +import scorex.util.serialization.VLQByteBufferReader import scorex.utils.{Ints, Longs} import sigma.ast.{AtLeast, SBigInt, SubstConstants} import scorex.utils.Longs +import sigma.Evaluation.rtypeToSType import sigma.ast.{AtLeast, SType, SubstConstants} import sigma.crypto.{CryptoConstants, EcPointType, Ecp} import sigma.eval.Extensions.EvalCollOps +import sigma.serialization.{ConstantStore, DataSerializer, GroupElementSerializer, SigmaByteReader, SigmaSerializer} import sigma.serialization.{DataSerializer, GroupElementSerializer, SigmaSerializer} import sigma.serialization.{GroupElementSerializer, SerializerException, SigmaSerializer} import sigma.util.Extensions.BigIntegerOps @@ -18,6 +21,7 @@ import sigma.validation.SigmaValidationSettings import sigma.{AvlTree, BigInt, Box, Coll, CollBuilder, Evaluation, GroupElement, SigmaDslBuilder, SigmaProp, VersionContext} import java.math.BigInteger +import java.nio.ByteBuffer /** A default implementation of [[SigmaDslBuilder]] interface. * @@ -254,6 +258,13 @@ class CSigmaDslBuilder extends SigmaDslBuilder { dsl => DataSerializer.serialize(value.asInstanceOf[SType#WrappedType], tpe, w) Colls.fromArray(w.toBytes) } + + def deserializeTo[T](bytes: Coll[Byte])(implicit cT: RType[T]): T = { + val tpe = rtypeToSType(cT) + val reader = new SigmaByteReader(new VLQByteBufferReader(ByteBuffer.wrap(bytes.toArray)), new ConstantStore(), false) + val res = DataSerializer.deserialize(tpe, reader) + res.asInstanceOf[T] + } } /** Default singleton instance of Global object, which implements global ErgoTree functions. */ diff --git a/docs/LangSpec.md b/docs/LangSpec.md index 16defff5ff..5cbb569a56 100644 --- a/docs/LangSpec.md +++ b/docs/LangSpec.md @@ -68,7 +68,7 @@ The following sections describe ErgoScript and its operations. #### Operations and constructs overview - Binary operations: `>, <, >=, <=, +, -, &&, ||, ==, !=, |, &, *, /, %, ^, ++` -- predefined primitives: `serialize`, `blake2b256`, `byteArrayToBigInt`, `proveDlog` etc. +- predefined primitives: `deserializeTo`, `serialize`, `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)` @@ -903,6 +903,10 @@ def blake2b256(input: Coll[Byte]): Coll[Byte] /** Cryptographic hash function Sha256 (See scorex.crypto.hash.Sha256) */ def sha256(input: Coll[Byte]): Coll[Byte] +/** Create an instance of type T from bytes of its wrapped type. +See https://github.com/ScorexFoundation/sigmastate-interpreter/pull/979 for more details */ +def deserializeTo[T](input: Coll[Byte]): T + /** Create BigInt from a collection of bytes. */ def byteArrayToBigInt(input: Coll[Byte]): BigInt diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala index 6bdf1656dc..88d7be4324 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -126,7 +126,7 @@ trait Interpreter { case _ => None } - /** Extracts proposition for ErgoTree handing soft-fork condition. + /** Extracts proposition for ErgoTree handling soft-fork condition. * @note soft-fork handler */ protected def propositionFromErgoTree(ergoTree: ErgoTree, context: CTX): SigmaPropValue = { val validationSettings = context.validationSettings diff --git a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala index 5ea171ba1f..d27b5cf22d 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala @@ -2,6 +2,7 @@ package sigma.serialization import sigma.VersionContext import sigma.ast.SCollection.SByteArray +import sigma.ast.SType.tT import sigma.ast._ import sigma.validation.ValidationException @@ -51,7 +52,7 @@ class MethodCallSerializerSpecification extends SerializationSpecification { def code = { val b = ByteArrayConstant(Array(1.toByte, 2.toByte, 3.toByte)) val expr = MethodCall(Global, - SGlobalMethods.serializeMethod.withConcreteTypes(Map(STypeVar("T") -> SByteArray)), + SGlobalMethods.serializeMethod.withConcreteTypes(Map(tT -> SByteArray)), Vector(b), Map() ) @@ -62,6 +63,30 @@ class MethodCallSerializerSpecification extends SerializationSpecification { code } + an[ValidationException] should be thrownBy ( + VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) { + code + } + ) + } + + property("MethodCall deserialization round trip for Global.deserializeTo[]") { + def code = { + val h = HeaderConstant(headerGen.sample.get) + val expr = MethodCall(h, + SGlobalMethods.deserializeToMethod.withConcreteTypes(Map(tT -> SHeader)), + Array(ByteArrayConstant(Array(1.toByte, 2.toByte, 3.toByte))), // wrong header bytes but ok for test + Map(tT -> SHeader) + ) + roundTripTest(expr) + } + + println(SGlobalMethods.deserializeToMethod.hasExplicitTypeArgs) + + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + code + } + an[Exception] should be thrownBy ( VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) { code diff --git a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala index b8bf76cacf..77d87a31f8 100644 --- a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala @@ -1,6 +1,6 @@ package sigma -import org.ergoplatform.ErgoBox +import org.ergoplatform.{ErgoBox, ErgoHeader} import org.ergoplatform.settings.ErgoAlgos import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Gen.containerOfN @@ -267,7 +267,10 @@ trait SigmaTestingData extends TestingCommons with ObjectGenerators { val h1: Header = create_h1() - val h2: Header = new CHeader(h1.asInstanceOf[CHeader].wrappedValue.copy(height = 2)) + val eh1 = h1.asInstanceOf[CHeader].ergoHeader + val h2: Header = new CHeader(new ErgoHeader(eh1.version, eh1.parentId, eh1.ADProofsRoot, eh1.stateRoot, + eh1.transactionsRoot, eh1.timestamp, eh1.nBits, 2, eh1.extensionRoot, + eh1.powSolution, eh1.votes, eh1.unparsedBytes, null)) val dlog_instances = new CloneSet(1000, ProveDlog( SigmaDsl.toECPoint(create_ge1()).asInstanceOf[EcPointType] diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/Base.scala b/sc/shared/src/main/scala/sigma/compiler/ir/Base.scala index 48e3e5d09e..d6f682abd5 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/Base.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/Base.scala @@ -169,7 +169,7 @@ abstract class Base { thisIR: IRContext => /** Create a copy of this definition applying the given transformer to all `syms`. */ def transform(t: Transformer): Def[T] = - !!!(s"Cannot transfrom definition using transform($this)", self) + !!!(s"Cannot transform definition using transform($this)", self) /** Clone this definition transforming all symbols using `t`. * If new Def[A] is created, it is added to the graph with collapsing and rewriting. diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala index 9b2fcc1baa..7d91b975b2 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala @@ -2,6 +2,7 @@ package sigma.compiler.ir import org.ergoplatform._ import sigma.ast.SType.tT +import sigma.{SigmaException, VersionContext, ast} import sigma.Evaluation.stypeToRType import sigma.ast.SType.tT import sigma.ast.TypeCodes.LastConstantCode @@ -17,11 +18,11 @@ import sigma.data.{CSigmaDslBuilder, ExactIntegral, ExactNumeric, ExactOrdering, import sigma.exceptions.GraphBuildingException import sigma.serialization.OpCodes import sigma.util.Extensions.ByteOps -import sigma.{SigmaException, VersionContext, ast} import sigmastate.interpreter.Interpreter.ScriptEnv import scala.collection.mutable.ArrayBuffer + /** Perform translation of typed expression given by [[Value]] to a graph in IRContext. * Which be than be translated to [[ErgoTree]] by using [[TreeBuilding]]. * @@ -1184,6 +1185,10 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => case SGlobalMethods.decodeNBitsMethod.name if VersionContext.current.isV6SoftForkActivated => val c1 = asRep[Long](argsV(0)) g.decodeNbits(c1) + case SGlobalMethods.deserializeToMethod.name if VersionContext.current.isV6SoftForkActivated => + val c1 = asRep[Coll[Byte]](argsV(0)) + val c2 = stypeToElem(method.stype.tRange.withSubstTypes(typeSubst)) + g.deserializeTo(c1)(c2) case SGlobalMethods.serializeMethod.name => val value = asRep[Any](argsV(0)) g.serialize(value) diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala b/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala index ff64c3c699..6b83bc10c9 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala @@ -1,5 +1,6 @@ package sigma.compiler.ir +import sigma.Coll import sigma.{BigInt, SigmaDslBuilder} import sigma.ast.SType import sigma.compiler.ir.primitives.Thunks @@ -540,6 +541,9 @@ object GraphIRReflection { }, mkMethod(clazz, "fromBigEndianBytes", Array[Class[_]](classOf[Base#Ref[_]], classOf[TypeDescs#Elem[_]])) { (obj, args) => obj.asInstanceOf[ctx.SigmaDslBuilder].fromBigEndianBytes(args(0).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]])(args(1).asInstanceOf[ctx.Elem[SType]]) + }, + mkMethod(clazz, "deserializeTo", Array[Class[_]](classOf[Base#Ref[_]], classOf[TypeDescs#Elem[_]])) { (obj, args) => + obj.asInstanceOf[ctx.SigmaDslBuilder].deserializeTo(args(0).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]])(args(1).asInstanceOf[ctx.Elem[SType]]) } ) ) diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/TreeBuilding.scala b/sc/shared/src/main/scala/sigma/compiler/ir/TreeBuilding.scala index 37ec47f2dc..652aeb4fe0 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/TreeBuilding.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/TreeBuilding.scala @@ -6,9 +6,9 @@ import sigma.Evaluation.{rtypeToSType, stypeToRType} import sigma.ast.SType.tT import sigma.ast._ import sigma.ast.syntax.{ValueOps, _} -import sigma.data.{ProveDHTuple, ProveDlog} -import sigma.serialization.ConstantStore import sigma.serialization.OpCodes._ +import sigma.serialization.ConstantStore +import sigma.data.{ProveDHTuple, ProveDlog} import sigma.serialization.ValueCodes.OpCode import scala.collection.mutable.ArrayBuffer @@ -279,6 +279,13 @@ trait TreeBuilding extends Base { IR: IRContext => val tpe = elemToSType(eVar) mkGetVar(id, tpe) + case SDBM.deserializeTo(g, bytes, eVar) => + val tpe = elemToSType(eVar) + val typeSubst = Map(tT -> tpe): STypeSubst + // method specialization done to avoid serialization roundtrip issues + val method = SGlobalMethods.deserializeToMethod.withConcreteTypes(typeSubst) + builder.mkMethodCall(recurse(g), method, IndexedSeq(recurse(bytes)), typeSubst) + case BIM.subtract(In(x), In(y)) => mkArith(x.asNumValue, y.asNumValue, MinusCode) case BIM.add(In(x), In(y)) => diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala index 76ad93cac3..a8608651a2 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala @@ -120,6 +120,7 @@ import scalan._ def decodeNbits(l: Ref[Long]): Ref[BigInt] def serialize[T](value: Ref[T]): Ref[Coll[Byte]] def fromBigEndianBytes[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] + def deserializeTo[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] }; trait CostModelCompanion; trait BigIntCompanion; diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala index 5ce0255533..336e4ed9f2 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala @@ -1379,6 +1379,7 @@ object Header extends EntityObject("Header") { ArraySeq.empty, true, false, element[Boolean])) } + } implicit object LiftableHeader @@ -1989,6 +1990,12 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { true, false, element[Coll[Byte]])) } + override def deserializeTo[T](l: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] = { + asRep[T](mkMethodCall(self, + SigmaDslBuilderClass.getMethod("deserializeTo", classOf[Sym], classOf[Elem[T]]), + Array[AnyRef](l, cT), + true, false, element[T](cT), Map(tT -> Evaluation.rtypeToSType(cT.sourceType)))) + } override def fromBigEndianBytes[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] = { asRep[T](mkMethodCall(self, SigmaDslBuilderClass.getMethod("fromBigEndianBytes", classOf[Sym], classOf[Elem[T]]), @@ -2176,6 +2183,13 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { true, true, element[Coll[Byte]])) } + def deserializeTo[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] = { + asRep[T](mkMethodCall(source, + SigmaDslBuilderClass.getMethod("deserializeTo", classOf[Sym], classOf[Elem[_]]), + Array[AnyRef](bytes, cT), + true, true, element[T](cT), Map(tT -> Evaluation.rtypeToSType(cT.sourceType)))) + } + def fromBigEndianBytes[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] = { asRep[T](mkMethodCall(source, SigmaDslBuilderClass.getMethod("fromBigEndianBytes", classOf[Sym], classOf[Elem[T]]), @@ -2215,7 +2229,7 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { Elem.declaredMethods(RClass(classOf[SigmaDslBuilder]), RClass(classOf[SSigmaDslBuilder]), Set( "Colls", "verifyZK", "atLeast", "allOf", "allZK", "anyOf", "anyZK", "xorOf", "sigmaProp", "blake2b256", "sha256", "byteArrayToBigInt", "longToByteArray", "byteArrayToLong", "proveDlog", "proveDHTuple", "groupGenerator", - "substConstants", "decodePoint", "avlTree", "xor", "encodeNBits", "decodeNBits", "serialize", "fromBigEndianBytes" + "substConstants", "decodePoint", "avlTree", "xor", "encodeNBits", "decodeNBits", "serialize", "fromBigEndianBytes", "deserializeTo" )) } } @@ -2394,6 +2408,16 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { def unapply(exp: Sym): Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]])] = unapply(exp.node) } + object deserializeTo { + def unapply(d: Def[_]): Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Elem[T]) forSome {type T}] = d match { + case MethodCall(receiver, method, args, _) if method.getName == "deserializeTo" && receiver.elem.isInstanceOf[SigmaDslBuilderElem[_]] => + val res = (receiver, args(0), args(1)) + Nullable(res).asInstanceOf[Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Elem[T]) forSome {type T}]] + case _ => Nullable.None + } + def unapply(exp: Sym): Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Elem[T]) forSome {type T}] = unapply(exp.node) + } + /** This is necessary to handle CreateAvlTree in GraphBuilding (v6.0) */ object avlTree { def unapply(d: Def[_]): Nullable[(Ref[SigmaDslBuilder], Ref[Byte], Ref[Coll[Byte]], Ref[Int], Ref[WOption[Int]])] = d match { diff --git a/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala b/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala index 833bd413b9..1bbf1fc4f5 100644 --- a/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala +++ b/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala @@ -37,13 +37,14 @@ class SigmaTyper(val builder: SigmaBuilder, private def processGlobalMethod(srcCtx: Nullable[SourceContext], method: SMethod, - args: IndexedSeq[SValue]) = { + args: IndexedSeq[SValue], + subst: Map[STypeVar, SType] = EmptySubst): SValue = { val global = Global.withPropagatedSrcCtx(srcCtx) val node = for { pf <- method.irInfo.irBuilder if lowerMethodCalls - res <- pf.lift((builder, global, method, args, EmptySubst)) + res <- pf.lift((builder, global, method, args, subst)) } yield res - node.getOrElse(mkMethodCall(global, method, args, EmptySubst).withPropagatedSrcCtx(srcCtx)) + node.getOrElse(mkMethodCall(global, method, args, subst).withPropagatedSrcCtx(srcCtx)) } /** * Rewrite tree to typed tree. Checks constituent names and types. Uses diff --git a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala index c9858d02ca..c315292b77 100644 --- a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala +++ b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala @@ -9,10 +9,14 @@ import scorex.util.ModifierId import scorex.utils.{Ints, Longs, Shorts} import sigma.ast.ErgoTree.{HeaderType, ZeroHeader} import sigma.ast.SCollection.SByteArray +import sigma.ast.SType.tT import sigma.ast.syntax.TrueSigmaProp import sigma.ast.{SInt, _} import sigma.data.{AvlTreeData, AvlTreeFlags, CAnyValue, CAvlTree, CBigInt, CBox, CHeader, CSigmaProp, ExactNumeric, ProveDHTuple, RType} import sigma.data.CSigmaDslBuilder +import sigma.crypto.SecP256K1Group +import sigma.data.{CBigInt, CBox, CGroupElement, CHeader, CSigmaDslBuilder, ExactNumeric, RType} +import sigma.data.{CBigInt, CBox, CHeader, CSigmaDslBuilder, ExactNumeric, PairOfCols, RType} import sigma.eval.{CostDetails, SigmaDsl, TracedCost} import sigma.serialization.ValueCodes.OpCode import sigma.util.Extensions.{BooleanOps, IntOps} @@ -104,8 +108,6 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => verifyCases(cases, serializeShort, preGeneratedSamples = None) } - // TODO v6.0: implement serialization roundtrip tests after merge with deserializeTo - property("Boolean.toByte") { val toByte = newFeature((x: Boolean) => x.toByte, "{ (x: Boolean) => x.toByte }", @@ -1526,6 +1528,128 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => ) } + property("Global.deserializeTo - group element") { + def deserializeTo: Feature[GroupElement, Boolean] = { + newFeature( + { (x: GroupElement) => CSigmaDslBuilder.deserializeTo[GroupElement](x.getEncoded) == x}, + "{ (x: GroupElement) => Global.deserializeTo[GroupElement](x.getEncoded) == x }", + FuncValue( + Array((1, SGroupElement)), + EQ( + MethodCall.typed[Value[SGroupElement.type]]( + Global, + SGlobalMethods.deserializeToMethod.withConcreteTypes(Map(tT -> SGroupElement)), + Vector( + MethodCall.typed[Value[SCollection[SByte.type]]]( + ValUse(1, SGroupElement), + SGroupElementMethods.getMethodByName("getEncoded"), + IndexedSeq(), + Map() + ) + ), + Map(STypeVar("T") -> SGroupElement) + ), + ValUse(1, SGroupElement) + ) + ), + sinceVersion = VersionContext.V6SoftForkVersion + ) + } + + verifyCases( + Seq( + CGroupElement(SecP256K1Group.generator) -> new Expected(ExpectedResult(Success(true), None)) + ), + deserializeTo + ) + } + + property("Global.deserializeTo - header") { + val headerBytes = "02ac2101807f0000ca01ff0119db227f202201007f62000177a080005d440896d05d3f80dcff7f5e7f59007294c180808d0158d1ff6ba10000f901c7f0ef87dcfff17fffacb6ff7f7f1180d2ff7f1e24ffffe1ff937f807f0797b9ff6ebdae007e5c8c00b8403d3701557181c8df800001b6d5009e2201c6ff807d71808c00019780f087adb3fcdbc0b3441480887f80007f4b01cf7f013ff1ffff564a0000b9a54f00770e807f41ff88c00240000080c0250000000003bedaee069ff4829500b3c07c4d5fe6b3ea3d3bf76c5c28c1d4dcdb1bed0ade0c0000000000003105" + val header1 = new CHeader(ErgoHeader.sigmaSerializer.fromBytes(Base16.decode(headerBytes).get)) + + // v1 header below + val header2Bytes = "010000000000000000000000000000000000000000000000000000000000000000766ab7a313cd2fb66d135b0be6662aa02dfa8e5b17342c05a04396268df0bfbb93fb06aa44413ff57ac878fda9377207d5db0e78833556b331b4d9727b3153ba18b7a08878f2a7ee4389c5a1cece1e2724abe8b8adc8916240dd1bcac069177303f1f6cee9ba2d0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8060117650100000003be7ad70c74f691345cbedba19f4844e7fc514e1188a7929f5ae261d5bb00bb6602da9385ac99014ddcffe88d2ac5f28ce817cd615f270a0a5eae58acfb9fd9f6a0000000030151dc631b7207d4420062aeb54e82b0cfb160ff6ace90ab7754f942c4c3266b" + val header2 = new CHeader(ErgoHeader.sigmaSerializer.fromBytes(Base16.decode(header2Bytes).get)) + + def deserializeTo: Feature[Header, Boolean] = { + newFeature( + { (x: Header) => CSigmaDslBuilder.deserializeTo[Header](CSigmaDslBuilder.serialize(x)) == x}, + "{ (x: Header) => Global.deserializeTo[Header](serialize(x)) == x }", + FuncValue( + Array((1, SHeader)), + EQ( + MethodCall.typed[Value[SHeader.type]]( + Global, + SGlobalMethods.deserializeToMethod.withConcreteTypes(Map(tT -> SHeader)), + Vector( + MethodCall.typed[Value[SCollection[SByte.type]]]( + Global, + SGlobalMethods.serializeMethod.withConcreteTypes( + Map(STypeVar("T") -> SHeader) + ), + Array(ValUse(1, SHeader)), + Map() + ) + ), + Map(STypeVar("T") -> SHeader) + ), + ValUse(1, SHeader) + ) + ), + sinceVersion = VersionContext.V6SoftForkVersion + ) + } + + verifyCases( + Seq( + header1 -> new Expected(ExpectedResult(Success(true), None)), + header2 -> new Expected(ExpectedResult(Success(true), None)) + ), + deserializeTo + ) + } + + property("Global.serialize & deserialize roundtrip - BigInt") { + import sigma.data.OrderingOps.BigIntOrdering + + def deserializeTo: Feature[BigInt, Boolean] = { + newFeature( + { (x: BigInt) => CSigmaDslBuilder.deserializeTo[BigInt](CSigmaDslBuilder.serialize(x)) == x}, + "{ (x: BigInt) => Global.deserializeTo[BigInt](serialize(x)) == x }", + FuncValue( + Array((1, SBigInt)), + EQ( + MethodCall.typed[Value[SBigInt.type]]( + Global, + SGlobalMethods.deserializeToMethod.withConcreteTypes(Map(tT -> SBigInt)), + Vector( + MethodCall.typed[Value[SCollection[SByte.type]]]( + Global, + SGlobalMethods.serializeMethod.withConcreteTypes( + Map(STypeVar("T") -> SBigInt) + ), + Array(ValUse(1, SBigInt)), + Map() + ) + ), + Map(STypeVar("T") -> SBigInt) + ), + ValUse(1, SBigInt) + ) + ), + sinceVersion = VersionContext.V6SoftForkVersion + ) + } + + val cases = Seq( + (CBigInt(BigInteger.ONE), new Expected(ExpectedResult(Success(true), None))), + (CBigInt(sigma.crypto.SecP256K1Group.q.divide(new BigInteger("2"))), new Expected(ExpectedResult(Success(true), None))), + (CBigInt(sigma.crypto.SecP256K1Group.p.divide(new BigInteger("2"))), new Expected(ExpectedResult(Success(true), None))) + ) + verifyCases(cases, deserializeTo) + } + private def contextData() = { val input = CBox( new ErgoBox( diff --git a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala index fb86b3ab2b..381d21c6c1 100644 --- a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala @@ -519,8 +519,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit with C (SGlobal.typeId, Seq( MInfo(1, groupGeneratorMethod), MInfo(2, xorMethod) ) ++ (if (isV6Activated) { - // id = 4 reserved for deserializeTo method - Seq(MInfo(3, serializeMethod), MInfo(5, fromBigEndianBytesMethod), MInfo(6, encodeNBitsMethod), MInfo(7, decodeNBitsMethod)) // methods added in v6.0 + Seq(MInfo(3, serializeMethod), MInfo(4, deserializeToMethod), MInfo(5, fromBigEndianBytesMethod), MInfo(6, encodeNBitsMethod), MInfo(7, decodeNBitsMethod)) // methods added in v6.0 } else { Seq.empty[MInfo] }), true) diff --git a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index f13a70206a..1af15c9ad0 100644 --- a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -16,6 +16,7 @@ import sigma.data.{CAND, CAvlTree, CHeader, ProveDlog, SigmaBoolean, TrivialProp import sigma.interpreter.ContextExtension import sigma.data.{AvlTreeData, CAND, ProveDlog, SigmaBoolean, TrivialProp} import sigma.VersionContext.V6SoftForkVersion +import sigma.VersionContext import sigma.util.Extensions.IntOps import sigmastate.helpers.{CompilerTestingCommons, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter} import sigmastate.helpers.TestingHelpers._ diff --git a/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala b/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala index 2d465cff30..26c1866e57 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala @@ -3,20 +3,28 @@ package sigmastate.utxo import org.ergoplatform.ErgoBox.{AdditionalRegisters, R6, R8} import org.ergoplatform._ import org.scalatest.Assertion +import scorex.crypto.authds.{ADKey, ADValue} +import scorex.crypto.authds.avltree.batch.{BatchAVLProver, Insert} +import scorex.crypto.hash.{Blake2b256, Digest32} +import scorex.util.ByteArrayBuilder import scorex.util.encode.Base16 import org.scalatest.Assertion import scorex.util.encode.Base16 import scorex.utils.Ints +import scorex.util.serialization.VLQByteBufferWriter +import scorex.utils.Longs +import sigma.{Colls, SigmaTestingData} import sigma.Extensions.ArrayOps import sigma.{SigmaTestingData, VersionContext} import sigma.VersionContext.{V6SoftForkVersion, withVersions} import sigma.ast.SCollection.SByteArray import sigma.ast.SType.AnyOps import sigma.data.{AvlTreeData, CAnyValue, CHeader, CSigmaDslBuilder} +import sigma.data.{AvlTreeData, AvlTreeFlags, CAND, CAnyValue, CHeader, CSigmaDslBuilder, CSigmaProp} import sigma.util.StringUtil._ import sigma.ast._ import sigma.ast.syntax._ -import sigma.crypto.CryptoConstants +import sigma.crypto.{CryptoConstants, SecP256K1Group} import sigmastate._ import sigmastate.helpers.TestingHelpers._ import sigmastate.helpers.{CompilerTestingCommons, ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter} @@ -29,6 +37,8 @@ import sigma.exceptions.InvalidType import sigma.serialization.ErgoTreeSerializer import sigma.interpreter.{ContextExtension, ProverResult} import sigma.util.NBitsUtils +import sigma.serialization.{DataSerializer, ErgoTreeSerializer, SigmaByteWriter} +import sigma.util.Extensions import sigmastate.utils.Helpers import sigmastate.utils.Helpers._ @@ -54,7 +64,9 @@ class BasicOpsSpecification extends CompilerTestingCommons val booleanVar = 9.toByte val propVar1 = 10.toByte val propVar2 = 11.toByte - val lastExtVar = propVar2 + val propVar3 = 12.toByte + val propBytesVar1 = 13.toByte + val lastExtVar = propBytesVar1 val ext: Seq[VarBinding] = Seq( (intVar1, IntConstant(1)), (intVar2, IntConstant(2)), @@ -71,7 +83,8 @@ class BasicOpsSpecification extends CompilerTestingCommons "proofVar2" -> CAnyValue(propVar2) ) - def test(name: String, env: ScriptEnv, + def test(name: String, + env: ScriptEnv, ext: Seq[VarBinding], script: String, propExp: SValue, @@ -82,7 +95,14 @@ class BasicOpsSpecification extends CompilerTestingCommons override lazy val contextExtenders: Map[Byte, EvaluatedValue[_ <: SType]] = { val p1 = dlogSecrets(0).publicImage val p2 = dlogSecrets(1).publicImage - (ext ++ Seq(propVar1 -> SigmaPropConstant(p1), propVar2 -> SigmaPropConstant(p2))).toMap + val d1 = dhSecrets(0).publicImage + + (ext ++ Seq( + propVar1 -> SigmaPropConstant(p1), + propVar2 -> SigmaPropConstant(p2), + propVar3 -> SigmaPropConstant(CSigmaProp(CAND(Seq(p1, d1)))), + propBytesVar1 -> ByteArrayConstant(CSigmaProp(CAND(Seq(p1, d1))).propBytes) + )).toMap } override val evalSettings: EvalSettings = DefaultEvalSettings.copy( isMeasureOperationTime = true, @@ -1270,6 +1290,315 @@ class BasicOpsSpecification extends CompilerTestingCommons } } + property("serialize - deserialize roundtrip") { + val customExt = Seq(21.toByte -> ShortArrayConstant((1 to 10).map(_.toShort).toArray)) + def deserTest() = test("serialize", env, customExt, + s"""{ + val src = getVar[Coll[Short]](21).get + val ba = serialize(src) + val restored = deserializeTo[Coll[Short]](ba) + src == restored + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an[Exception] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("deserializeTo - int") { + val value = -109253 + val w = new VLQByteBufferWriter(new ByteArrayBuilder()).putInt(value) + val bytes = Base16.encode(w.toBytes) + def deserTest() = {test("deserializeTo", env, ext, + s"""{ val ba = fromBase16("$bytes"); Global.deserializeTo[Int](ba) == $value }""", + null, + true + )} + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("deserializeTo - coll[int]") { + val writer = new SigmaByteWriter(new VLQByteBufferWriter(new ByteArrayBuilder()), None, None, None) + DataSerializer.serialize[SCollection[SInt.type]](Colls.fromArray(Array(IntConstant(5).value)), SCollection(SInt), writer) + val bytes = Base16.encode(writer.toBytes) + + def deserTest() = { + test("deserializeTo", env, ext, + s"""{val ba = fromBase16("$bytes"); val coll = Global.deserializeTo[Coll[Int]](ba); coll(0) == 5 }""", + null, + true + ) + } + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("deserializeTo - long") { + val value = -10009253L + + val w = new VLQByteBufferWriter(new ByteArrayBuilder()).putLong(value) + val bytes = Base16.encode(w.toBytes) + + def deserTest() = test("deserializeTo", env, ext, + s"""{ + val ba = fromBase16("$bytes"); + Global.deserializeTo[Long](ba) == ${value}L + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("deserializeTo - box rountrip") { + def deserTest() = test("deserializeTo", env, ext, + s"""{ + val b = INPUTS(0); + val ba = b.bytes; + Global.deserializeTo[Box](ba) == b + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("deserializeTo - bigint") { + + val bigInt = SecP256K1Group.q.divide(new BigInteger("512")) + val biBytes = bigInt.toByteArray + + val w = new VLQByteBufferWriter(new ByteArrayBuilder()).putUShort(biBytes.length) + val lengthBytes = w.toBytes + + val bytes = Base16.encode(lengthBytes ++ biBytes) + + def deserTest() = test("deserializeTo", env, ext, + s"""{ + val ba = fromBase16("$bytes"); + val b = Global.deserializeTo[BigInt](ba) + b == bigInt("${bigInt.toString}") + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("deserializeTo - short") { + val s = (-1925).toShort + val w = new VLQByteBufferWriter(new ByteArrayBuilder()).putShort(s) + val bytes = Base16.encode(w.toBytes) + def deserTest() = test("deserializeTo", env, ext, + s"""{ + val ba = fromBase16("$bytes"); + Global.deserializeTo[Short](ba) == -1925 + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("deserializeTo - group element") { + val ge = Helpers.decodeGroupElement("026930cb9972e01534918a6f6d6b8e35bc398f57140d13eb3623ea31fbd069939b") + val ba = Base16.encode(ge.getEncoded.toArray) + def deserTest() = test("deserializeTo", env, Seq(21.toByte -> GroupElementConstant(ge)), + s"""{ + val ge = getVar[GroupElement](21).get + val ba = fromBase16("$ba"); + val ge2 = Global.deserializeTo[GroupElement](ba) + ba == ge2.getEncoded && ge == ge2 + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("deserializeTo - sigmaprop roundtrip") { + + def deserTest() = test("deserializeTo", env, ext, + s"""{ + val bytes = getVar[Coll[Byte]]($propBytesVar1).get + val ba = bytes.slice(2, bytes.size) + val prop = Global.deserializeTo[SigmaProp](ba) + prop == getVar[SigmaProp]($propVar3).get && prop + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("deserializeTo - .propBytes") { + def deserTest() = test("deserializeTo", env, ext, + s"""{ + val p1 = getVar[SigmaProp]($propVar1).get + val bytes = p1.propBytes + val ba = bytes.slice(2, bytes.size) + val prop = Global.deserializeTo[SigmaProp](ba) + prop == p1 && prop + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("deserializeTo - sigmaprop roundtrip - non evaluated") { + + val script = GT(Height, IntConstant(-1)).toSigmaProp + val scriptBytes = ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(ErgoTree.fromProposition(script)) + val customExt = Seq(21.toByte -> ByteArrayConstant(scriptBytes)) + + def deserTest() = test("deserializeTo", env, customExt, + s"""{ + val ba = getVar[Coll[Byte]](21).get + val prop = Global.deserializeTo[SigmaProp](ba) + prop + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + an [Exception] should be thrownBy deserTest() + } + } + + property("deserializeTo - avltree") { + val elements = Seq(123, 22) + val treeElements = elements.map(i => Longs.toByteArray(i)).map(s => (ADKey @@@ Blake2b256(s), ADValue @@ s)) + val avlProver = new BatchAVLProver[Digest32, Blake2b256.type](keyLength = 32, None) + treeElements.foreach(s => avlProver.performOneOperation(Insert(s._1, s._2))) + avlProver.generateProof() + val treeData = new AvlTreeData(avlProver.digest.toColl, AvlTreeFlags.ReadOnly, 32, None) + val treeBytes = AvlTreeData.serializer.toBytes(treeData) + + val customExt = Seq(21.toByte -> ByteArrayConstant(treeBytes)) + + def deserTest() = test("deserializeTo", env, customExt, + s"""{ + val ba = getVar[Coll[Byte]](21).get + val tree = Global.deserializeTo[AvlTree](ba) + tree.digest == fromBase16(${Base16.encode(treeData.digest.toArray)}) + && tree.enabledOperations == 0 + && tree.keyLength == 32 + && tree.valueLengthOpt.isEmpty + }""", + null, + true + ) + + an [Exception] should be thrownBy deserTest() + } + + property("deserializeTo - header") { + val td = new SigmaTestingData {} + val h1 = td.TestData.h1 + val headerBytes = h1.asInstanceOf[CHeader].ergoHeader.bytes + + val headerStateBytes = AvlTreeData.serializer.toBytes(Extensions.CoreAvlTreeOps(h1.stateRoot).toAvlTreeData) + val customExt = Seq(21.toByte -> ByteArrayConstant(headerBytes), 22.toByte -> ByteArrayConstant(headerStateBytes)) + + def deserTest() = test("deserializeTo", env, customExt, + s"""{ + val ba = getVar[Coll[Byte]](21).get + val header = Global.deserializeTo[Header](ba) + val ba2 = getVar[Coll[Byte]](22).get + val tree = Global.deserializeTo[AvlTree](ba2) + val id = fromBase16("${Base16.encode(h1.id.toArray)}") + header.height == ${h1.height} && header.stateRoot == tree && header.id == id + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an[sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("deserializeTo - header option") { + val td = new SigmaTestingData {} + val h1 = td.TestData.h1.asInstanceOf[CHeader].ergoHeader + val headerBytes = Colls.fromArray(Array(1.toByte) ++ h1.bytes) + + val customExt = Seq(21.toByte -> ByteArrayConstant(headerBytes)) + + def deserTest() = test("deserializeTo", env, customExt, + s"""{ + val ba = getVar[Coll[Byte]](21).get + val headerOpt = Global.deserializeTo[Option[Header]](ba) + val header = headerOpt.get + val id = fromBase16("${Base16.encode(h1.id.toArray)}") + header.height == ${h1.height} && header.id == id + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an[sigma.validation.ValidationException] should be thrownBy deserTest() + } else { + deserTest() + } + } + property("Relation operations") { test("R1", env, ext, "{ allOf(Coll(getVar[Boolean](trueVar).get, true, true)) }", diff --git a/sc/shared/src/test/scala/sigmastate/utxo/examples/ExecuteFromExamplesSpecification.scala b/sc/shared/src/test/scala/sigmastate/utxo/examples/ExecuteFromExamplesSpecification.scala index b75b404aed..8090d87803 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/examples/ExecuteFromExamplesSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/examples/ExecuteFromExamplesSpecification.scala @@ -9,8 +9,6 @@ import sigma.ast.ByteArrayConstant class ExecuteFromExamplesSpecification extends CompilerTestingCommons { suite => implicit lazy val IR = new TestingIRContext - private val reg1 = ErgoBox.nonMandatoryRegisters(0) - case class OracleContract[Spec <: ContractSpec] (alice: Spec#ProvingParty) (implicit val spec: Spec) extends SigmaContractSyntax with StdContracts From c0ea4b0cdd034be84a2d1c4b1950944adefbf9f3 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 22 Nov 2024 13:33:30 +0300 Subject: [PATCH 2/2] Autolykos 2 validation for custom message --- .../src/main/scala/sigma/SigmaDsl.scala | 3 + .../sigma/reflection/ReflectionData.scala | 4 ++ .../scala/sigma/SigmaDataReflection.scala | 10 +++ .../src/main/scala/sigma/ast/CostKind.scala | 34 +++++++++ .../src/main/scala/sigma/ast/SMethod.scala | 5 +- .../src/main/scala/sigma/ast/methods.scala | 23 ++++++ .../src/main/scala/sigma/ast/trees.scala | 12 ---- .../scala/sigma/data/CSigmaDslBuilder.scala | 9 ++- .../sigma/pow/Autolykos2PowValidation.scala | 8 ++- .../MethodCallSerializerSpecification.scala | 30 +++++++- .../scala/sigmastate/eval/BasicOpsTests.scala | 70 ++++++++++++++++++- .../sigma/compiler/ir/GraphBuilding.scala | 14 ++++ .../sigma/compiler/ir/GraphIRReflection.scala | 6 ++ .../ir/wrappers/sigma/SigmaDslUnit.scala | 1 + .../ir/wrappers/sigma/impl/SigmaDslImpl.scala | 17 ++++- .../scala/sigma/LanguageSpecificationV6.scala | 47 +++++++++++++ .../sigmastate/ErgoTreeSpecification.scala | 2 +- .../TestingInterpreterSpecification.scala | 22 ++++++ 18 files changed, 295 insertions(+), 22 deletions(-) diff --git a/core/shared/src/main/scala/sigma/SigmaDsl.scala b/core/shared/src/main/scala/sigma/SigmaDsl.scala index d5e4d5e6c7..821080ec00 100644 --- a/core/shared/src/main/scala/sigma/SigmaDsl.scala +++ b/core/shared/src/main/scala/sigma/SigmaDsl.scala @@ -789,6 +789,9 @@ trait SigmaDslBuilder { /** 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 */ + 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 diff --git a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala index 6935328d3c..daf8e48e81 100644 --- a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala +++ b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala @@ -475,6 +475,10 @@ object ReflectionData { mkMethod(clazz, "fromBigEndianBytes", Array[Class[_]](cColl, classOf[RType[_]])) { (obj, args) => obj.asInstanceOf[SigmaDslBuilder].fromBigEndianBytes(args(0).asInstanceOf[Coll[Byte]])(args(1).asInstanceOf[RType[_]]) }, + mkMethod(clazz, "powHit", Array[Class[_]](classOf[Int], cColl, cColl, cColl, classOf[Int])) { (obj, args) => + obj.asInstanceOf[SigmaDslBuilder].powHit(args(0).asInstanceOf[Int], args(1).asInstanceOf[Coll[Byte]], + args(2).asInstanceOf[Coll[Byte]], args(3).asInstanceOf[Coll[Byte]], args(4).asInstanceOf[Int]) + }, mkMethod(clazz, "encodeNbits", Array[Class[_]](classOf[BigInt])) { (obj, args) => obj.asInstanceOf[SigmaDslBuilder].encodeNbits(args(0).asInstanceOf[BigInt]) }, diff --git a/data/shared/src/main/scala/sigma/SigmaDataReflection.scala b/data/shared/src/main/scala/sigma/SigmaDataReflection.scala index 0aae2210ce..c79473afe1 100644 --- a/data/shared/src/main/scala/sigma/SigmaDataReflection.scala +++ b/data/shared/src/main/scala/sigma/SigmaDataReflection.scala @@ -364,6 +364,16 @@ object SigmaDataReflection { obj.asInstanceOf[SGlobalMethods.type].deserializeTo_eval(args(0).asInstanceOf[MethodCall], args(1).asInstanceOf[SigmaDslBuilder], args(2).asInstanceOf[Coll[Byte]])(args(3).asInstanceOf[ErgoTreeEvaluator]) + }, + mkMethod(clazz, "powHit_eval", Array[Class[_]](classOf[MethodCall], classOf[SigmaDslBuilder], classOf[Int], classOf[Coll[_]], classOf[Coll[_]], classOf[Coll[_]], classOf[Int], classOf[ErgoTreeEvaluator])) { (obj, args) => + obj.asInstanceOf[SGlobalMethods.type].powHit_eval(args(0).asInstanceOf[MethodCall], + args(1).asInstanceOf[SigmaDslBuilder], + args(2).asInstanceOf[Int], + args(3).asInstanceOf[Coll[Byte]], + args(4).asInstanceOf[Coll[Byte]], + args(5).asInstanceOf[Coll[Byte]], + args(6).asInstanceOf[Int] + )(args(7).asInstanceOf[ErgoTreeEvaluator]) } ) ) diff --git a/data/shared/src/main/scala/sigma/ast/CostKind.scala b/data/shared/src/main/scala/sigma/ast/CostKind.scala index 1fda6018ec..6116da2eed 100644 --- a/data/shared/src/main/scala/sigma/ast/CostKind.scala +++ b/data/shared/src/main/scala/sigma/ast/CostKind.scala @@ -1,5 +1,7 @@ package sigma.ast +import sigma.Coll + import scala.runtime.Statics /** Cost descriptor of a single operation, usually associated with @@ -52,5 +54,37 @@ abstract class TypeBasedCost extends CostKind { * See [[EQ]], [[NEQ]]. */ case object DynamicCost extends CostKind +/** + * Cost of converting numeric value to the numeric value of the given type, i.e. Byte -> Int + */ +object NumericCastCostKind extends TypeBasedCost { + override def costFunc(targetTpe: SType): JitCost = targetTpe match { + case SBigInt => JitCost(30) + case _ => JitCost(10) + } +} + +/** + * Cost of Global.powHit method, which is dependent on few parameters, see cost() function description + */ +object PowHitCostKind extends CostKind { + /** + * @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) + * @return cost of custom Autolykos2 hash function invocation + */ + def cost(k: Int, msg: Coll[Byte], nonce: Coll[Byte], h: Coll[Byte]): JitCost = { + val chunkSize = CalcBlake2b256.costKind.chunkSize + val perChunkCost = CalcBlake2b256.costKind.perChunkCost + val baseCost = 300 + + // the heaviest part inside is k + 1 Blake2b256 invocations + val c = baseCost + (k + 1) * ((msg.length + nonce.length + h.length) / chunkSize + 1) * perChunkCost.value + JitCost(c) + } +} + diff --git a/data/shared/src/main/scala/sigma/ast/SMethod.scala b/data/shared/src/main/scala/sigma/ast/SMethod.scala index e5481cee5b..35b452fef0 100644 --- a/data/shared/src/main/scala/sigma/ast/SMethod.scala +++ b/data/shared/src/main/scala/sigma/ast/SMethod.scala @@ -155,8 +155,7 @@ case class SMethod( val methodName = name + "_eval" val m = try { objType.thisRClass.getMethod(methodName, paramTypes:_*) - } - catch { case e: NoSuchMethodException => + } catch { case e: NoSuchMethodException => throw new RuntimeException(s"Cannot find eval method def $methodName(${Seq(paramTypes:_*)})", e) } m @@ -339,7 +338,7 @@ object SMethod { * @return an instance of [[SMethod]] which may contain generic type variables in the * signature (see SMethod.stype). As a result `specializeFor` is called by * deserializer to obtain monomorphic method descriptor. - * @consensus this is method is used in [[sigmastate.serialization.MethodCallSerializer]] + * @consensus this is method is used in [[sigma.serialization.MethodCallSerializer]] * `parse` method and hence it is part of consensus protocol */ def fromIds(typeId: Byte, methodId: Byte): SMethod = { diff --git a/data/shared/src/main/scala/sigma/ast/methods.scala b/data/shared/src/main/scala/sigma/ast/methods.scala index 84bb4f0943..1e3a6a80eb 100644 --- a/data/shared/src/main/scala/sigma/ast/methods.scala +++ b/data/shared/src/main/scala/sigma/ast/methods.scala @@ -2,6 +2,7 @@ package sigma.ast import org.ergoplatform._ import org.ergoplatform.validation._ +import sigma.{Coll, VersionContext, _} import sigma.Evaluation.stypeToRType import sigma._ import sigma.{VersionContext, _} @@ -15,6 +16,7 @@ import sigma.data.NumericOps.BigIntIsExactIntegral import sigma.data.OverloadHack.Overloaded1 import sigma.data.{CBigInt, DataValueComparer, KeyValueColl, Nullable, RType, SigmaConstants} import sigma.eval.{CostDetails, ErgoTreeEvaluator, TracedCost} +import sigma.pow.Autolykos2PowValidation import sigma.reflection.RClass import sigma.serialization.CoreByteWriter.ArgInfo import sigma.serialization.{DataSerializer, SigmaByteWriter, SigmaSerializer} @@ -1825,6 +1827,26 @@ case object SGlobalMethods extends MonoTypeMethods { .withInfo(Xor, "Byte-wise XOR of two collections of bytes", ArgInfo("left", "left operand"), ArgInfo("right", "right operand")) + lazy val powHitMethod = SMethod( + this, "powHit", SFunc(Array(SGlobal, SInt, SByteArray, SByteArray, SByteArray, SInt), SBigInt), methodId = 8, + PowHitCostKind) + .withIRInfo(MethodCallIrBuilder) + .withInfo(MethodCall, + "Calculating Proof-of-Work hit (Autolykos 2 hash value) for custom Autolykos 2 function", + ArgInfo("k", "k parameter of Autolykos 2 (number of inputs in k-sum problem)"), + ArgInfo("msg", "Message to calculate Autolykos hash 2 for"), + ArgInfo("nonce", "Nonce used to pad the message to get Proof-of-Work hash function output with desirable properties"), + ArgInfo("h", "PoW protocol specific padding for table uniqueness (e.g. block height in Ergo)"), + ArgInfo("N", "Size of table filled with pseudo-random data to find k elements in") + ) + + def powHit_eval(mc: MethodCall, G: SigmaDslBuilder, k: Int, msg: Coll[Byte], nonce: Coll[Byte], h: Coll[Byte], N: Int) + (implicit E: ErgoTreeEvaluator): BigInt = { + val cost = PowHitCostKind.cost(k, msg, nonce, h) + E.addCost(FixedCost(cost), powHitMethod.opDesc) + CBigInt(Autolykos2PowValidation.hitForVersion2ForMessageWithChecks(k, msg.toArray, nonce.toArray, h.toArray, N).bigInteger) + } + private val deserializeCostKind = PerItemCost(baseCost = JitCost(30), perChunkCost = JitCost(20), chunkSize = 32) lazy val deserializeToMethod = SMethod( @@ -1913,6 +1935,7 @@ case object SGlobalMethods extends MonoTypeMethods { groupGeneratorMethod, xorMethod, serializeMethod, + powHitMethod, deserializeToMethod, encodeNBitsMethod, decodeNBitsMethod, diff --git a/data/shared/src/main/scala/sigma/ast/trees.scala b/data/shared/src/main/scala/sigma/ast/trees.scala index 39e666a389..fb9f84288e 100644 --- a/data/shared/src/main/scala/sigma/ast/trees.scala +++ b/data/shared/src/main/scala/sigma/ast/trees.scala @@ -414,18 +414,6 @@ trait NumericCastCompanion extends ValueCompanion { def costKind: TypeBasedCost = NumericCastCostKind } -/** Cost of: - * 1) converting numeric value to the numeric value of the given type, i.e. Byte -> Int - * NOTE: the cost of BigInt casting is the same in JITC (comparing to AOTC) to simplify - * implementation. - */ -object NumericCastCostKind extends TypeBasedCost { - override def costFunc(targetTpe: SType): JitCost = targetTpe match { - case SBigInt => JitCost(30) - case _ => JitCost(10) - } -} - object Upcast extends NumericCastCompanion { override def opCode: OpCode = OpCodes.UpcastCode override def argInfos: Seq[ArgInfo] = UpcastInfo.argInfos diff --git a/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala b/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala index f540df55c2..0346c919b2 100644 --- a/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala +++ b/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala @@ -14,7 +14,8 @@ import sigma.crypto.{CryptoConstants, EcPointType, Ecp} import sigma.eval.Extensions.EvalCollOps import sigma.serialization.{ConstantStore, DataSerializer, GroupElementSerializer, SigmaByteReader, SigmaSerializer} import sigma.serialization.{DataSerializer, GroupElementSerializer, SigmaSerializer} -import sigma.serialization.{GroupElementSerializer, SerializerException, SigmaSerializer} +import sigma.serialization.SerializerException +import sigma.pow.Autolykos2PowValidation import sigma.util.Extensions.BigIntegerOps import sigma.util.NBitsUtils import sigma.validation.SigmaValidationSettings @@ -259,6 +260,12 @@ class CSigmaDslBuilder extends SigmaDslBuilder { dsl => Colls.fromArray(w.toBytes) } + override def powHit(k: Int, msg: Coll[Byte], nonce: Coll[Byte], h: Coll[Byte], N: Int): BigInt = { + val bi = Autolykos2PowValidation.hitForVersion2ForMessageWithChecks(k, msg.toArray, nonce.toArray, h.toArray, N) + this.BigInt(bi.bigInteger) + } + + def deserializeTo[T](bytes: Coll[Byte])(implicit cT: RType[T]): T = { val tpe = rtypeToSType(cT) val reader = new SigmaByteReader(new VLQByteBufferReader(ByteBuffer.wrap(bytes.toArray)), new ConstantStore(), false) diff --git a/data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala b/data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala index 23cf722194..f51a625825 100644 --- a/data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala +++ b/data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala @@ -112,8 +112,14 @@ object Autolykos2PowValidation { toBigInt(hash(Bytes.concat(indexBytes, heightBytes, M)).drop(1)) } - def hitForVersion2ForMessage(k: Int, msg: Array[Byte], nonce: Array[Byte], h: Array[Byte], N: Int): BigInt = { + def hitForVersion2ForMessageWithChecks(k: Int, msg: Array[Byte], nonce: Array[Byte], h: Array[Byte], N: Int): BigInt = { + require(k >= 2) // at least 2 elements needed for sum + require(k <= 32) // genIndexes function of Autolykos2 not supporting k > 32 + require(N >= 16) // min table size + hitForVersion2ForMessage(k, msg, nonce, h, N) + } + private def hitForVersion2ForMessage(k: Int, msg: Array[Byte], nonce: Array[Byte], h: Array[Byte], N: Int): BigInt = { val prei8 = BigIntegers.fromUnsignedByteArray(hash(Bytes.concat(msg, nonce)).takeRight(8)) val i = BigIntegers.asUnsignedByteArray(4, prei8.mod(BigInt(N).underlying())) val f = Blake2b256(Bytes.concat(i, h, M)).drop(1) // .drop(1) is the same as takeRight(31) diff --git a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala index d27b5cf22d..4bfe3c6a25 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala @@ -1,5 +1,6 @@ package sigma.serialization +import scorex.utils.Ints import sigma.VersionContext import sigma.ast.SCollection.SByteArray import sigma.ast.SType.tT @@ -41,7 +42,34 @@ class MethodCallSerializerSpecification extends SerializationSpecification { code } - an[SerializerException] should be thrownBy ( + a[SerializerException] should be thrownBy ( + VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) { + code + } + ) + } + + property("MethodCall deserialization round trip for Global.powHit") { + val k = IntConstant(32) + val msg = ByteArrayConstant(Array.fill(5)(1.toByte)) + val nonce = ByteArrayConstant(Array.fill(8)(2.toByte)) + val h = ByteArrayConstant(Ints.toByteArray(5)) + val N = IntConstant(1024 * 1024) + + def code = { + val expr = MethodCall(Global, + SGlobalMethods.powHitMethod, + Vector(k, msg, nonce, h, N), + Map() + ) + roundTripTest(expr) + } + + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + code + } + + an[ValidationException] should be thrownBy ( VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) { code } diff --git a/interpreter/shared/src/test/scala/sigmastate/eval/BasicOpsTests.scala b/interpreter/shared/src/test/scala/sigmastate/eval/BasicOpsTests.scala index c4cd58a4ef..52cc6af66c 100644 --- a/interpreter/shared/src/test/scala/sigmastate/eval/BasicOpsTests.scala +++ b/interpreter/shared/src/test/scala/sigmastate/eval/BasicOpsTests.scala @@ -2,7 +2,10 @@ package sigmastate.eval import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers -import sigma.ast.{BigIntConstant, ErgoTree, Global, JitCost, MethodCall, SBigIntMethods, SGlobalMethods} +import sigma.ast.{BigIntConstant, ErgoTree, Global, JitCost, MethodCall, SGlobalMethods} +import scorex.util.encode.Base16 +import sigma.Extensions.ArrayOps +import sigma.ast.{ByteArrayConstant, IntConstant} import sigma.crypto.SecP256K1Group import sigma.data.{CBigInt, TrivialProp} import sigma.eval.SigmaDsl @@ -13,6 +16,7 @@ import java.math.BigInteger import sigma.{ContractsTestkit, SigmaProp} import sigmastate.interpreter.{CErgoTreeEvaluator, CostAccumulator} import sigmastate.interpreter.CErgoTreeEvaluator.DefaultProfiler +import sigma.{Box, VersionContext} import scala.language.implicitConversions @@ -68,6 +72,69 @@ class BasicOpsTests extends AnyFunSuite with ContractsTestkit with Matchers { box.creationInfo._1 shouldBe a [Integer] } + test("xor evaluation") { + val es = CErgoTreeEvaluator.DefaultEvalSettings + val accumulator = new CostAccumulator( + initialCost = JitCost(0), + costLimit = Some(JitCost.fromBlockCost(es.scriptCostLimitInEvaluator))) + + val context = new CContext( + noInputs.toColl, noHeaders, dummyPreHeader, + Array[Box]().toColl, Array[Box]().toColl, 0, null, 0, null, + dummyPubkey.toColl, Colls.emptyColl, null, VersionContext.V6SoftForkVersion, VersionContext.V6SoftForkVersion) + + val evaluator = new CErgoTreeEvaluator( + context = context, + constants = ErgoTree.EmptyConstants, + coster = accumulator, DefaultProfiler, es) + + val msg = Colls.fromArray(Base16.decode("0a101b8c6a4f2e").get) + VersionContext.withVersions(VersionContext.V6SoftForkVersion, VersionContext.V6SoftForkVersion) { + val res = MethodCall(Global, SGlobalMethods.xorMethod, + IndexedSeq(ByteArrayConstant(msg), ByteArrayConstant(msg)), Map.empty) + .evalTo[sigma.Coll[Byte]](Map.empty)(evaluator) + + res should be(Colls.fromArray(Base16.decode("00000000000000").get)) + } + } + + /** + * Checks BigInt.nbits evaluation for SigmaDSL as well as AST interpreter (MethodCall) layers + */ + test("powHit evaluation") { + val k = 32 + val msg = Colls.fromArray(Base16.decode("0a101b8c6a4f2e").get) + val nonce = Colls.fromArray(Base16.decode("000000000000002c").get) + val hbs = Colls.fromArray(Base16.decode("00000000").get) + val N = 1024 * 1024 + + SigmaDsl.powHit(k, msg, nonce, hbs, N) shouldBe CBigInt(new BigInteger("326674862673836209462483453386286740270338859283019276168539876024851191344")) + + val es = CErgoTreeEvaluator.DefaultEvalSettings + val accumulator = new CostAccumulator( + initialCost = JitCost(0), + costLimit = Some(JitCost.fromBlockCost(es.scriptCostLimitInEvaluator))) + + val context = new CContext( + noInputs.toColl, noHeaders, dummyPreHeader, + Array[Box]().toColl, Array[Box]().toColl, 0, null, 0, null, + dummyPubkey.toColl, Colls.emptyColl, null, VersionContext.V6SoftForkVersion, VersionContext.V6SoftForkVersion) + + val evaluator = new CErgoTreeEvaluator( + context = context, + constants = ErgoTree.EmptyConstants, + coster = accumulator, DefaultProfiler, es) + + VersionContext.withVersions(VersionContext.V6SoftForkVersion, VersionContext.V6SoftForkVersion) { + val res = MethodCall(Global, SGlobalMethods.powHitMethod, + IndexedSeq(IntConstant(k), ByteArrayConstant(msg), ByteArrayConstant(nonce), + ByteArrayConstant(hbs), IntConstant(N)), Map.empty) + .evalTo[sigma.BigInt](Map.empty)(evaluator) + + res should be(CBigInt(new BigInteger("326674862673836209462483453386286740270338859283019276168539876024851191344"))) + } + } + /** * Checks BigInt.nbits evaluation for SigmaDSL as well as AST interpreter (MethodCall) layers */ @@ -88,7 +155,6 @@ class BasicOpsTests extends AnyFunSuite with ContractsTestkit with Matchers { .evalTo[Long](Map.empty)(evaluator) res should be (NBitsUtils.encodeCompactBits(0)) - } } diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala index 7d91b975b2..8f392eedac 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala @@ -5,6 +5,7 @@ import sigma.ast.SType.tT import sigma.{SigmaException, VersionContext, ast} import sigma.Evaluation.stypeToRType import sigma.ast.SType.tT +import sigma.{SigmaException, VersionContext, ast} import sigma.ast.TypeCodes.LastConstantCode import sigma.ast.Value.Typed import sigma.ast.syntax.{SValue, ValueOps} @@ -1185,6 +1186,19 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => case SGlobalMethods.decodeNBitsMethod.name if VersionContext.current.isV6SoftForkActivated => val c1 = asRep[Long](argsV(0)) g.decodeNbits(c1) + case SGlobalMethods.powHitMethod.name if VersionContext.current.isV6SoftForkActivated => + val k = asRep[Int](argsV(0)) + val msg = asRep[Coll[Byte]](argsV(1)) + val nonce = asRep[Coll[Byte]](argsV(2)) + val h = asRep[Coll[Byte]](argsV(3)) + val N = asRep[Int](argsV(4)) + g.powHit(k, msg, nonce, h, N) + case SGlobalMethods.encodeNBitsMethod.name if VersionContext.current.isV6SoftForkActivated => + val c1 = asRep[BigInt](argsV(0)) + g.encodeNbits(c1) + case SGlobalMethods.decodeNBitsMethod.name if VersionContext.current.isV6SoftForkActivated => + val c1 = asRep[Long](argsV(0)) + g.decodeNbits(c1) case SGlobalMethods.deserializeToMethod.name if VersionContext.current.isV6SoftForkActivated => val c1 = asRep[Coll[Byte]](argsV(0)) val c2 = stypeToElem(method.stype.tRange.withSubstTypes(typeSubst)) diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala b/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala index 6b83bc10c9..b415c460a7 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala @@ -533,6 +533,12 @@ object GraphIRReflection { mkMethod(clazz, "serialize", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) => obj.asInstanceOf[ctx.SigmaDslBuilder].serialize(args(0).asInstanceOf[ctx.Ref[Any]]) }, + mkMethod(clazz, "powHit", Array[Class[_]](classOf[Base#Ref[_]], classOf[Base#Ref[_]], + classOf[Base#Ref[_]], classOf[Base#Ref[_]], classOf[Base#Ref[_]])) { (obj, args) => + obj.asInstanceOf[ctx.SigmaDslBuilder].powHit(args(0).asInstanceOf[ctx.Ref[Int]], + args(1).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]], args(2).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]], + args(3).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]], args(4).asInstanceOf[ctx.Ref[Int]]) + }, mkMethod(clazz, "encodeNbits", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) => obj.asInstanceOf[ctx.SigmaDslBuilder].encodeNbits(args(0).asInstanceOf[ctx.Ref[ctx.BigInt]]) }, diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala index a8608651a2..1b6ac41077 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala @@ -118,6 +118,7 @@ import scalan._ def xor(l: Ref[Coll[Byte]], r: Ref[Coll[Byte]]): Ref[Coll[Byte]] def encodeNbits(bi: Ref[BigInt]): Ref[Long] def decodeNbits(l: Ref[Long]): Ref[BigInt] + def powHit(k: Ref[Int], msg: Ref[Coll[Byte]], nonce: Ref[Coll[Byte]], h: Ref[Coll[Byte]], N: Ref[Int]): Ref[BigInt]; def serialize[T](value: Ref[T]): Ref[Coll[Byte]] def fromBigEndianBytes[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] def deserializeTo[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala index 336e4ed9f2..7a3e5dd3b1 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala @@ -2003,6 +2003,14 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { true, false, cT)) } + + override def powHit(k: Ref[Int], msg: Ref[Coll[Byte]], nonce: Ref[Coll[Byte]], h: Ref[Coll[Byte]], N: Ref[Int]): Ref[BigInt] = { + asRep[BigInt](mkMethodCall(self, + SigmaDslBuilderClass.getMethod("powHit", classOf[Sym], classOf[Sym], classOf[Sym], classOf[Sym], classOf[Sym]), + Array[AnyRef](k, msg, nonce, h, N), + true, false, element[BigInt])) + } + override def encodeNbits(bi: Ref[BigInt]): Ref[Long] = { asRep[Long](mkMethodCall(self, SigmaDslBuilderClass.getMethod("encodeNbits", classOf[Sym]), @@ -2176,6 +2184,13 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { true, true, element[Coll[Byte]])) } + def powHit(k: Ref[Int], msg: Ref[Coll[Byte]], nonce: Ref[Coll[Byte]], h: Ref[Coll[Byte]], N: Ref[Int]): Ref[BigInt] = { + asRep[BigInt](mkMethodCall(source, + SigmaDslBuilderClass.getMethod("powHit", classOf[Sym], classOf[Sym], classOf[Sym], classOf[Sym], classOf[Sym]), + Array[AnyRef](k, msg, nonce, h, N), + true, true, element[BigInt])) + } + def serialize[T](value: Ref[T]): Ref[Coll[Byte]] = { asRep[Coll[Byte]](mkMethodCall(source, SigmaDslBuilderClass.getMethod("serialize", classOf[Sym]), @@ -2229,7 +2244,7 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { Elem.declaredMethods(RClass(classOf[SigmaDslBuilder]), RClass(classOf[SSigmaDslBuilder]), Set( "Colls", "verifyZK", "atLeast", "allOf", "allZK", "anyOf", "anyZK", "xorOf", "sigmaProp", "blake2b256", "sha256", "byteArrayToBigInt", "longToByteArray", "byteArrayToLong", "proveDlog", "proveDHTuple", "groupGenerator", - "substConstants", "decodePoint", "avlTree", "xor", "encodeNBits", "decodeNBits", "serialize", "fromBigEndianBytes", "deserializeTo" + "substConstants", "decodePoint", "avlTree", "xor", "encodeNBits", "decodeNBits", "serialize", "fromBigEndianBytes", "powHit", "deserializeTo" )) } } diff --git a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala index c315292b77..c45fc327e7 100644 --- a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala +++ b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala @@ -21,6 +21,11 @@ import sigma.eval.{CostDetails, SigmaDsl, TracedCost} import sigma.serialization.ValueCodes.OpCode import sigma.util.Extensions.{BooleanOps, IntOps} import sigmastate.eval.{CContext, CPreHeader} +import sigma.util.Extensions.{BooleanOps, IntOps} +import sigma.data.RType +import sigma.serialization.ValueCodes.OpCode +import sigma.util.Extensions.{BooleanOps, ByteOps, IntOps, LongOps} +import sigma.pow.Autolykos2PowValidation import sigmastate.exceptions.MethodNotFound import sigmastate.utils.Extensions.ByteOpsForSigma import sigmastate.utils.Helpers @@ -1457,6 +1462,48 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => ) } + property("Global.powHit") { + def powHit: Feature[Coll[Byte], sigma.BigInt] = newFeature( + { (x: Coll[Byte]) => + val msg = x.slice(0, 7).toArray + val nonce = x.slice(7, 15).toArray + val h = x.slice(15, 19).toArray + CBigInt(Autolykos2PowValidation.hitForVersion2ForMessageWithChecks(32, msg, nonce, h, 1024 * 1024).bigInteger) + }, + "{ (x: Coll[Byte]) => val msg = x.slice(0,7); val nonce = x.slice(7,15); val h = x.slice(15,19); " + + "Global.powHit(32, msg, nonce, h, 1024 * 1024) }", + FuncValue( + Array((1, SByteArray)), + MethodCall.typed[Value[SBigInt.type]]( + Global, + SGlobalMethods.powHitMethod, + Array( + IntConstant(32), + Slice(ValUse(1, SByteArray), IntConstant(0), IntConstant(7)), + Slice(ValUse(1, SByteArray), IntConstant(7), IntConstant(15)), + Slice(ValUse(1, SByteArray), IntConstant(15), IntConstant(19)), + IntConstant(1048576) + ), + Map() + ) + ), + sinceVersion = VersionContext.V6SoftForkVersion) + + // bytes of real mainnet block header at height 614,440 + val msg = Base16.decode("0a101b8c6a4f2e").get + val nonce = Base16.decode("000000000000002c").get + val h = Base16.decode("00000000").get + val x = Colls.fromArray(msg ++ nonce ++ h) + val hit = CBigInt(new BigInteger("326674862673836209462483453386286740270338859283019276168539876024851191344")) + + verifyCases( + Seq( + x -> new Expected(ExpectedResult(Success(hit), None)) + ), + powHit + ) + } + property("higher order lambdas") { val f = newFeature[Coll[Int], Coll[Int]]( { (xs: Coll[Int]) => diff --git a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala index 381d21c6c1..2e8b7ecc17 100644 --- a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala @@ -519,7 +519,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit with C (SGlobal.typeId, Seq( MInfo(1, groupGeneratorMethod), MInfo(2, xorMethod) ) ++ (if (isV6Activated) { - Seq(MInfo(3, serializeMethod), MInfo(4, deserializeToMethod), MInfo(5, fromBigEndianBytesMethod), MInfo(6, encodeNBitsMethod), MInfo(7, decodeNBitsMethod)) // methods added in v6.0 + Seq(MInfo(3, serializeMethod), MInfo(4, deserializeToMethod), MInfo(5, fromBigEndianBytesMethod), MInfo(6, encodeNBitsMethod), MInfo(7, decodeNBitsMethod), MInfo(8, powHitMethod)) // methods added in v6.0 } else { Seq.empty[MInfo] }), true) diff --git a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index 1af15c9ad0..52dec1a657 100644 --- a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -11,6 +11,7 @@ import org.scalatest.BeforeAndAfterAll import scorex.util.encode.{Base16, Base58} import sigma.Colls import sigma.VersionContext.V6SoftForkVersion +import sigma.VersionContext.V6SoftForkVersion import sigma.VersionContext import sigma.data.{CAND, CAvlTree, CHeader, ProveDlog, SigmaBoolean, TrivialProp} import sigma.interpreter.ContextExtension @@ -334,6 +335,27 @@ class TestingInterpreterSpecification extends CompilerTestingCommons testEval("Coll(1, 1).getOrElse(3, 1 + 1) == 2") } + property("Evaluate powHit") { + val source = + """ + |{ + | val b: BigInt = bigInt("1157920892373161954235709850086879078528375642790749043826051631415181614943") + | val k = 32 + | val N = 1024 * 1024 + | val msg = fromBase16("0a101b8c6a4f2e") + | val nonce = fromBase16("000000000000002c") + | val h = fromBase16("00000000") + | + | Global.powHit(k, msg, nonce, h, N) <= b // hit == b in this example + |} + |""".stripMargin + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigmastate.exceptions.MethodNotFound] should be thrownBy testEval(source) + } else { + testEval(source) + } + } + property("Evaluation example #1") { val dk1 = prover.dlogSecrets(0).publicImage val dk2 = prover.dlogSecrets(1).publicImage