Skip to content

Commit

Permalink
merging i958
Browse files Browse the repository at this point in the history
  • Loading branch information
kushti committed Oct 9, 2024
2 parents 5b3721e + 0aa88be commit ae0a025
Show file tree
Hide file tree
Showing 18 changed files with 293 additions and 25 deletions.
3 changes: 3 additions & 0 deletions core/shared/src/main/scala/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,10 @@ object ReflectionData {
},
mkMethod(clazz, "none", Array[Class[_]](classOf[RType[_]])) { (obj, args) =>
obj.asInstanceOf[SigmaDslBuilder].none()(args(0).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])
}
)
)
Expand Down
10 changes: 10 additions & 0 deletions data/shared/src/main/scala/sigma/SigmaDataReflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,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])
}
)
)
Expand Down
36 changes: 36 additions & 0 deletions data/shared/src/main/scala/sigma/ast/CostKind.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package sigma.ast

import sigma.Coll

import scala.runtime.Statics

/** Cost descriptor of a single operation, usually associated with
Expand Down Expand Up @@ -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)
}
}



6 changes: 3 additions & 3 deletions data/shared/src/main/scala/sigma/ast/SMethod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = {
Expand Down
27 changes: 25 additions & 2 deletions data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -13,8 +14,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}
Expand Down Expand Up @@ -1972,6 +1974,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(20), perChunkCost = JitCost(7), chunkSize = 128)

Expand Down Expand Up @@ -2065,7 +2087,8 @@ case object SGlobalMethods extends MonoTypeMethods {
deserializeToMethod,
fromBigEndianBytesMethod,
someMethod,
noneMethod
noneMethod,
powHitMethod
)
} else {
Seq(
Expand Down
12 changes: 0 additions & 12 deletions data/shared/src/main/scala/sigma/ast/trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -415,18 +415,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
Expand Down
10 changes: 9 additions & 1 deletion data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import sigma.ast.{AtLeast, SBigInt, SubstConstants}
import scorex.utils.Longs
import sigma.Evaluation.rtypeToSType
import sigma.ast.{AtLeast, SType, SubstConstants}
import sigma.ast.SType
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.validation.SigmaValidationSettings
import sigma.{AvlTree, BigInt, Box, Coll, CollBuilder, Evaluation, GroupElement, SigmaDslBuilder, SigmaProp, VersionContext}
Expand Down Expand Up @@ -266,6 +268,12 @@ class CSigmaDslBuilder extends SigmaDslBuilder { dsl =>
override def none[T]()(implicit cT: RType[T]): Option[T] = {
None
}

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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package sigma.serialization

import scorex.utils.Ints
import sigma.VersionContext
import sigma.ast.SCollection.SByteArray
import sigma.ast.SType.tT
Expand Down Expand Up @@ -41,6 +42,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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, 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, 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")))
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -1218,6 +1219,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.deserializeToMethod.name if VersionContext.current.isV6SoftForkActivated =>
val c1 = asRep[Coll[Byte]](argsV(0))
val c2 = stypeToElem(method.stype.tRange.withSubstTypes(typeSubst))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -532,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]])
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,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]
def deserializeTo[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T]
Expand Down
Loading

0 comments on commit ae0a025

Please sign in to comment.