diff --git a/core/shared/src/main/scala/sigma/SigmaDsl.scala b/core/shared/src/main/scala/sigma/SigmaDsl.scala index 16331febfd..2289ccfcbf 100644 --- a/core/shared/src/main/scala/sigma/SigmaDsl.scala +++ b/core/shared/src/main/scala/sigma/SigmaDsl.scala @@ -776,6 +776,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 + /** 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 3351c5a2c6..3e28b862f4 100644 --- a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala +++ b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala @@ -471,6 +471,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]) } ) ) diff --git a/data/shared/src/main/scala/sigma/SigmaDataReflection.scala b/data/shared/src/main/scala/sigma/SigmaDataReflection.scala index 341ee647b3..c270e43440 100644 --- a/data/shared/src/main/scala/sigma/SigmaDataReflection.scala +++ b/data/shared/src/main/scala/sigma/SigmaDataReflection.scala @@ -351,6 +351,16 @@ 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, "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..65a8d93ce3 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,39 @@ abstract class TypeBasedCost extends CostKind { * See [[EQ]], [[NEQ]]. */ case object DynamicCost extends CostKind +/** 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) + } +} + +/** + * 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 = 200 + + // 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..aac6ee330a 100644 --- a/data/shared/src/main/scala/sigma/ast/SMethod.scala +++ b/data/shared/src/main/scala/sigma/ast/SMethod.scala @@ -155,8 +155,8 @@ case class SMethod( val methodName = name + "_eval" val m = try { objType.thisRClass.getMethod(methodName, paramTypes:_*) - } - catch { case e: NoSuchMethodException => + } catch { case e: NoSuchMethodException => + println("dm: " + objType.thisRClass.getDeclaredMethods().mkString(", ")) throw new RuntimeException(s"Cannot find eval method def $methodName(${Seq(paramTypes:_*)})", e) } m @@ -339,7 +339,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 4cba72e28d..221075e203 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.ast.SCollection.{SBooleanArray, SBoxArray, SByteArray, SByteArray2, SHeaderArray} @@ -11,8 +12,9 @@ import sigma.ast.syntax.{SValue, ValueOps} import sigma.data.ExactIntegral.{ByteIsExactIntegral, IntIsExactIntegral, LongIsExactIntegral, ShortIsExactIntegral} import sigma.data.NumericOps.BigIntIsExactIntegral import sigma.data.OverloadHack.Overloaded1 -import sigma.data.{DataValueComparer, KeyValueColl, Nullable, RType, SigmaConstants} +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} @@ -1821,6 +1823,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) + } + /** Implements evaluation of Global.xor method call ErgoTree node. * Called via reflection based on naming convention. * @see SMethod.evalMethod, Xor.eval, Xor.xorWithCosting @@ -1877,7 +1899,8 @@ case object SGlobalMethods extends MonoTypeMethods { groupGeneratorMethod, xorMethod, serializeMethod, - fromBigEndianBytesMethod + fromBigEndianBytesMethod, + powHitMethod ) } else { Seq( 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 2ae4f73703..2ad7ad8385 100644 --- a/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala +++ b/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala @@ -6,12 +6,12 @@ import org.ergoplatform.validation.ValidationRules import scorex.crypto.hash.{Blake2b256, Sha256} import scorex.utils.{Ints, Longs} import sigma.ast.{AtLeast, SBigInt, SubstConstants} -import scorex.utils.Longs -import sigma.ast.{AtLeast, SType, SubstConstants} +import sigma.ast.SType import sigma.crypto.{CryptoConstants, EcPointType, Ecp} import sigma.eval.Extensions.EvalCollOps 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.validation.SigmaValidationSettings import sigma.{AvlTree, BigInt, Box, Coll, CollBuilder, Evaluation, GroupElement, SigmaDslBuilder, SigmaProp, VersionContext} @@ -245,6 +245,12 @@ class CSigmaDslBuilder extends SigmaDslBuilder { dsl => DataSerializer.serialize(value.asInstanceOf[SType#WrappedType], tpe, w) 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) + } + } /** Default singleton instance of Global object, which implements global ErgoTree functions. */ 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 793d3df959..500f802729 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._ @@ -40,6 +41,33 @@ class MethodCallSerializerSpecification extends SerializationSpecification { code } + 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[Exception] 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 e9ba273e17..3688a07863 100644 --- a/interpreter/shared/src/test/scala/sigmastate/eval/BasicOpsTests.scala +++ b/interpreter/shared/src/test/scala/sigmastate/eval/BasicOpsTests.scala @@ -2,12 +2,16 @@ package sigmastate.eval import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers +import scorex.util.encode.Base16 +import sigma.Extensions.ArrayOps +import sigma.ast.{ByteArrayConstant, ErgoTree, Global, IntConstant, JitCost, MethodCall, SGlobalMethods} import sigma.crypto.SecP256K1Group -import sigma.data.{CSigmaDslBuilder => SigmaDsl, TrivialProp} +import sigma.data.{CBigInt, TrivialProp, CSigmaDslBuilder => SigmaDsl} import sigma.util.Extensions.SigmaBooleanOps - import java.math.BigInteger -import sigma.{ContractsTestkit, SigmaProp} +import sigma.{Box, ContractsTestkit, SigmaProp, VersionContext} +import sigmastate.interpreter.CErgoTreeEvaluator.DefaultProfiler +import sigmastate.interpreter.{CErgoTreeEvaluator, CostAccumulator} import scala.language.implicitConversions @@ -63,4 +67,67 @@ 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"))) + } + } + } 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 37550e3430..e22a2051fc 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala @@ -4,6 +4,7 @@ import org.ergoplatform._ import sigma.ast.SType.tT 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} @@ -1177,6 +1178,13 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => val c1 = asRep[Coll[Byte]](argsV(0)) val c2 = asRep[Coll[Byte]](argsV(1)) g.xor(c1, c2) + 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.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 8f461e502e..3d795ac5a4 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.ast.SType import sigma.compiler.ir.primitives.Thunks import sigma.data.RType @@ -531,6 +532,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, "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]]) } 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 a90583a4a4..25bb4080fa 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 @@ -115,7 +115,8 @@ import scalan._ def decodePoint(encoded: Ref[Coll[Byte]]): Ref[GroupElement]; /** This method will be used in v6.0 to handle CreateAvlTree operation in GraphBuilding */ def avlTree(operationFlags: Ref[Byte], digest: Ref[Coll[Byte]], keyLength: Ref[Int], valueLengthOpt: Ref[WOption[Int]]): Ref[AvlTree]; - def xor(l: Ref[Coll[Byte]], r: Ref[Coll[Byte]]): Ref[Coll[Byte]] + def xor(l: Ref[Coll[Byte]], r: Ref[Coll[Byte]]): Ref[Coll[Byte]]; + 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] }; 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 0ce72d2314..5224c52a6b 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 @@ -1996,6 +1996,13 @@ 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])) + } } implicit object LiftableSigmaDslBuilder @@ -2156,6 +2163,14 @@ 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] = { + println(SigmaDslBuilderClass.getDeclaredMethods().mkString(", ")) + 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]), @@ -2188,7 +2203,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", "serialize", "fromBigEndianBytes" + "decodePoint", "avlTree", "xor", "serialize", "fromBigEndianBytes", "powHit" )) } } 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..732b1dbba1 100644 --- a/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala +++ b/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala @@ -37,7 +37,7 @@ class SigmaTyper(val builder: SigmaBuilder, private def processGlobalMethod(srcCtx: Nullable[SourceContext], method: SMethod, - args: IndexedSeq[SValue]) = { + args: IndexedSeq[SValue]): SValue = { val global = Global.withPropagatedSrcCtx(srcCtx) val node = for { pf <- method.irInfo.irBuilder if lowerMethodCalls diff --git a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala index 15ce673332..e996bc5072 100644 --- a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala +++ b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala @@ -21,6 +21,7 @@ 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 @@ -1459,6 +1460,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 1c38ca45a0..1be8c78da1 100644 --- a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala @@ -520,7 +520,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit with C MInfo(1, groupGeneratorMethod), MInfo(2, xorMethod) ) ++ (if (isV6Activated) { // id = 4 reserved for deserializeTo method - Seq(MInfo(3, serializeMethod), MInfo(5, fromBigEndianBytesMethod)) // methods added in v6.0 + Seq(MInfo(3, serializeMethod), MInfo(5, fromBigEndianBytesMethod), 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 4ba6b1a9f7..2b18c74d63 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 @@ -316,6 +317,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