Skip to content

Commit

Permalink
Autolykos 2 validation for custom message
Browse files Browse the repository at this point in the history
  • Loading branch information
kushti committed Nov 22, 2024
1 parent 494221a commit 707fa08
Show file tree
Hide file tree
Showing 29 changed files with 910 additions and 47 deletions.
7 changes: 6 additions & 1 deletion core/shared/src/main/scala/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,6 @@ trait Header {
* @return result of header's proof-of-work validation
*/
def checkPow: Boolean

}

/** Runtime representation of Context ErgoTree type.
Expand Down Expand Up @@ -790,6 +789,12 @@ 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

/** Returns a number decoded from provided big-endian bytes array. */
def fromBigEndianBytes[T](bytes: Coll[Byte])(implicit cT: RType[T]): T
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,9 +469,16 @@ 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[_]])
},
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])
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down
15 changes: 15 additions & 0 deletions data/shared/src/main/scala/sigma/SigmaDataReflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,21 @@ 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])
},
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
34 changes: 34 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,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)
}
}



5 changes: 2 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,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
Expand Down Expand Up @@ -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 = {
Expand Down
20 changes: 20 additions & 0 deletions data/shared/src/main/scala/sigma/ast/SigmaPredef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -480,6 +499,7 @@ object SigmaPredef {
ExecuteFromVarFunc,
ExecuteFromSelfRegFunc,
SerializeFunc,
DeserializeToFunc,
GetVarFromInputFunc,
FromBigEndianBytesFunc
).map(f => f.name -> f).toMap
Expand Down
43 changes: 43 additions & 0 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.{VersionContext, _}
Expand All @@ -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}
Expand Down Expand Up @@ -1754,6 +1756,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)))
Expand Down Expand Up @@ -1824,6 +1827,35 @@ 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(
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
Expand Down Expand Up @@ -1859,6 +1891,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)
Expand Down Expand Up @@ -1894,6 +1935,8 @@ case object SGlobalMethods extends MonoTypeMethods {
groupGeneratorMethod,
xorMethod,
serializeMethod,
powHitMethod,
deserializeToMethod,
encodeNBitsMethod,
decodeNBitsMethod,
fromBigEndianBytesMethod
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 @@ -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
Expand Down
6 changes: 5 additions & 1 deletion data/shared/src/main/scala/sigma/data/CHeader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
22 changes: 20 additions & 2 deletions data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
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.serialization.SerializerException
import sigma.pow.Autolykos2PowValidation
import sigma.util.Extensions.BigIntegerOps
import sigma.util.NBitsUtils
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.
*
Expand Down Expand Up @@ -254,6 +259,19 @@ 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)
}


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. */
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
6 changes: 5 additions & 1 deletion docs/LangSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)`
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 707fa08

Please sign in to comment.