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