Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.0] Global.fromBigEndianBytes implementation #1013

Merged
merged 8 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion core/shared/src/main/scala/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package sigma

import java.math.BigInteger
import sigma.ast.SType

import java.math.BigInteger
import sigma.data._

/**
Expand Down Expand Up @@ -763,5 +764,8 @@ trait SigmaDslBuilder {

/** Returns a byte-wise XOR of the two collections of bytes. */
def xor(l: Coll[Byte], r: Coll[Byte]): Coll[Byte]

/** Returns a number decoded from provided big-endian bytes array. */
def fromBigEndianBytes[T](bytes: Coll[Byte])(implicit cT: RType[T]): T
}

Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,9 @@ object ReflectionData {
},
mkMethod(clazz, "decodePoint", Array[Class[_]](cColl)) { (obj, args) =>
obj.asInstanceOf[SigmaDslBuilder].decodePoint(args(0).asInstanceOf[Coll[Byte]])
},
mkMethod(clazz, "fromBigEndianBytes", Array[Class[_]](cColl, classOf[RType[_]])) { (obj, args) =>
obj.asInstanceOf[SigmaDslBuilder].fromBigEndianBytes(args(0).asInstanceOf[Coll[Byte]])(args(1).asInstanceOf[RType[_]])
}
)
)
Expand Down
22 changes: 21 additions & 1 deletion data/shared/src/main/scala/sigma/ast/SigmaPredef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,25 @@ object SigmaPredef {
)
)

val FromBigEndianBytesFunc = PredefinedFunc("fromBigEndianBytes",
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.fromBigEndianBytesMethod.withConcreteTypes(Map(tT -> resType)),
args.toIndexedSeq,
Map(tT -> resType)
)
}),
docInfo = OperationInfo(MethodCall,
"""Deserializes provided big endian bytes into a numeric value of given type.
""".stripMargin,
Seq(ArgInfo("bytes", "bytes to deserialize"))
)
)

val globalFuncs: Map[String, PredefinedFunc] = Seq(
AllOfFunc,
AnyOfFunc,
Expand Down Expand Up @@ -448,7 +467,8 @@ object SigmaPredef {
SubstConstantsFunc,
ExecuteFromVarFunc,
ExecuteFromSelfRegFunc,
SerializeFunc
SerializeFunc,
FromBigEndianBytesFunc
).map(f => f.name -> f).toMap

def comparisonOp(symbolName: String, opDesc: ValueCompanion, desc: String, args: Seq[ArgInfo]) = {
Expand Down
17 changes: 15 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.Evaluation.stypeToRType
import sigma._
import sigma.ast.SCollection.{SBooleanArray, SBoxArray, SByteArray, SByteArray2, SHeaderArray}
import sigma.ast.SMethod.{MethodCallIrBuilder, MethodCostFunc, javaMethodOf}
Expand Down Expand Up @@ -480,7 +481,6 @@ case object SBigIntMethods extends SNumericTypeMethods {
}
}


}

/** Methods of type `String`. */
Expand Down Expand Up @@ -1691,6 +1691,18 @@ case object SGlobalMethods extends MonoTypeMethods {
Xor.xorWithCosting(ls, rs)
}

private val BigEndianBytesCostKind = FixedCost(JitCost(10))

// id = 4 is reserved for deserializeTo ()
lazy val fromBigEndianBytesMethod = SMethod(
this, "fromBigEndianBytes", SFunc(Array(SGlobal, SByteArray), tT, Array(paramT)), 5, BigEndianBytesCostKind, Seq(tT))
.withIRInfo(MethodCallIrBuilder,
javaMethodOf[SigmaDslBuilder, Coll[Byte], RType[_]]("fromBigEndianBytes"),
{ mtype => Array(mtype.tRange) })
.withInfo(MethodCall,
"Decode a number from big endian bytes.",
ArgInfo("first", "Bytes which are big-endian encoded number."))

lazy val serializeMethod = SMethod(this, "serialize",
SFunc(Array(SGlobal, tT), SByteArray, Array(paramT)), 3, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
Expand Down Expand Up @@ -1725,7 +1737,8 @@ case object SGlobalMethods extends MonoTypeMethods {
Seq(
groupGeneratorMethod,
xorMethod,
serializeMethod
serializeMethod,
fromBigEndianBytesMethod
)
} else {
Seq(
Expand Down
37 changes: 37 additions & 0 deletions data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import debox.cfor
import org.ergoplatform.ErgoBox
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.crypto.{CryptoConstants, EcPointType, Ecp}
import sigma.eval.Extensions.EvalCollOps
import sigma.serialization.{DataSerializer, GroupElementSerializer, SigmaSerializer}
import sigma.serialization.{GroupElementSerializer, SerializerException, SigmaSerializer}
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 @@ -201,6 +204,40 @@ class CSigmaDslBuilder extends SigmaDslBuilder { dsl =>
this.GroupElement(p)
}

override def fromBigEndianBytes[T](bytes: Coll[Byte])(implicit cT: RType[T]): T = {
cT match {
case sigma.ByteType => if (bytes.length != 1) {
throw new IllegalArgumentException("To deserialize SByte with fromBigEndianBytes, exactly one byte should be provided")
} else {
bytes.apply(0).asInstanceOf[T]
}
case sigma.ShortType => if (bytes.length != 2) {
throw new IllegalArgumentException("To deserialize SShort with fromBigEndianBytes, exactly two bytes should be provided")
} else {
val b0 = bytes(0)
val b1 = bytes(1)
((b0 & 0xFF) << 8 | (b1 & 0xFF)).toShort.asInstanceOf[T]
}
case sigma.IntType => if (bytes.length != 4) {
throw new IllegalArgumentException("To deserialize SInt with fromBigEndianBytes, exactly four bytes should be provided")
} else {
Ints.fromByteArray(bytes.toArray).asInstanceOf[T]
}
case sigma.LongType => if (bytes.length != 8) {
throw new IllegalArgumentException("To deserialize SLong with fromBigEndianBytes, exactly eight bytes should be provided")
} else {
Longs.fromByteArray(bytes.toArray).asInstanceOf[T]
}
case sigma.BigIntRType =>
if (bytes.length > SBigInt.MaxSizeInBytes) {
throw SerializerException(s"BigInt value doesn't not fit into ${SBigInt.MaxSizeInBytes} bytes in fromBigEndianBytes")
}
CBigInt(new BigInteger(bytes.toArray)).asInstanceOf[T]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also satisfy the bit size for BigInt values, to catch the potential problem early.
See multiplication operation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done ,

        CBigInt(new BigInteger(bytes.toArray).to256BitValueExact).asInstanceOf[T]

now

// todo: UnsignedBitInt
case _ => throw new IllegalArgumentException("Unsupported type provided in fromBigEndianBytes")
}
}

/** Serializes the given `value` into bytes using the default serialization format. */
override def serialize[T](value: T)(implicit cT: RType[T]): Coll[Byte] = {
val tpe = Evaluation.rtypeToSType(cT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,10 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext =>
case SGlobalMethods.serializeMethod.name =>
val value = asRep[Any](argsV(0))
g.serialize(value)
case SGlobalMethods.fromBigEndianBytesMethod.name =>
val bytes = asRep[Coll[Byte]](argsV(0))
val cT = stypeToElem(method.stype.tRange.withSubstTypes(typeSubst))
g.fromBigEndianBytes(bytes)(cT)
case _ => throwError()
}
case (x: Ref[tNum], _: SNumericTypeMethods) => method.name match {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package sigma.compiler.ir

import sigma.ast.SType
import sigma.compiler.ir.primitives.Thunks
import sigma.data.RType
import sigma.reflection.ReflectionData.registerClassEntry
Expand Down Expand Up @@ -510,6 +511,9 @@ 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, "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
2 changes: 2 additions & 0 deletions sc/shared/src/main/scala/sigma/compiler/ir/TreeBuilding.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package sigma.compiler.ir

import org.ergoplatform._
import sigma.Evaluation.{rtypeToSType, stypeToRType}
import sigma.ast.SType.tT
import sigma.ast._
import sigma.ast.syntax.{ValueOps, _}
import sigma.data.{ProveDHTuple, ProveDlog}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ import scalan._
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 serialize[T](value: Ref[T]): Ref[Coll[Byte]]
def fromBigEndianBytes[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T]
};
trait CostModelCompanion;
trait BigIntCompanion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1971,6 +1971,13 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") {
true, false, element[Coll[Byte]]))
}

override def fromBigEndianBytes[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] = {
asRep[T](mkMethodCall(self,
SigmaDslBuilderClass.getMethod("fromBigEndianBytes", classOf[Sym], classOf[Elem[T]]),
Array[AnyRef](bytes, cT, Map(tT -> Evaluation.rtypeToSType(cT.sourceType))),
true, false, cT))
}

}

implicit object LiftableSigmaDslBuilder
Expand Down Expand Up @@ -2137,6 +2144,13 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") {
Array[AnyRef](value),
true, true, element[Coll[Byte]]))
}

def fromBigEndianBytes[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T] = {
asRep[T](mkMethodCall(source,
SigmaDslBuilderClass.getMethod("fromBigEndianBytes", classOf[Sym], classOf[Elem[T]]),
Array[AnyRef](bytes, cT),
true, true, cT, Map(tT -> Evaluation.rtypeToSType(cT.sourceType))))
}
}

// entityUnref: single unref method for each type family
Expand All @@ -2154,7 +2168,9 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") {
override protected def collectMethods: Map[RMethod, MethodDesc] = {
super.collectMethods ++
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"
"Colls", "verifyZK", "atLeast", "allOf", "allZK", "anyOf", "anyZK", "xorOf", "sigmaProp", "blake2b256", "sha256",
"byteArrayToBigInt", "longToByteArray", "byteArrayToLong", "proveDlog", "proveDHTuple", "groupGenerator", "substConstants",
"decodePoint", "avlTree", "xor", "serialize", "fromBigEndianBytes"
))
}
}
Expand Down
94 changes: 91 additions & 3 deletions sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import sigma.ast.ErgoTree.ZeroHeader
import sigma.ast.SCollection.SByteArray
import sigma.ast.syntax.TrueSigmaProp
import sigma.ast.{SInt, _}
import sigma.data.{CBigInt, CBox, CHeader, ExactNumeric}
import sigma.data.{CBigInt, CBox, CHeader, CSigmaDslBuilder, ExactNumeric, RType}
import sigma.eval.{CostDetails, SigmaDsl, TracedCost}
import sigma.serialization.ValueCodes.OpCode
import sigma.data.{RType}
import sigma.util.Extensions.{BooleanOps, ByteOps, IntOps, LongOps}
import sigma.util.Extensions.{BooleanOps, IntOps}
import sigmastate.exceptions.MethodNotFound
import sigmastate.utils.Extensions.ByteOpsForSigma
import sigmastate.utils.Helpers
Expand Down Expand Up @@ -1523,4 +1522,93 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite =>
)
}


property("Global - fromBigEndianBytes") {
import sigma.data.OrderingOps.BigIntOrdering

def byteFromBigEndianBytes: Feature[Byte, Boolean] = {
newFeature(
{ (x: Byte) => CSigmaDslBuilder.fromBigEndianBytes[Byte](Colls.fromArray(Array(x))) == x},
"{ (x: Byte) => fromBigEndianBytes[Byte](x.toBytes) == x }",
sinceVersion = VersionContext.V6SoftForkVersion
)
}

verifyCases(
Seq(
5.toByte -> new Expected(ExpectedResult(Success(true), None)),
Byte.MaxValue -> new Expected(ExpectedResult(Success(true), None)),
Byte.MinValue -> new Expected(ExpectedResult(Success(true), None))
),
byteFromBigEndianBytes
)

def shortFromBigEndianBytes: Feature[Short, Boolean] = {
newFeature(
{ (x: Short) => CSigmaDslBuilder.fromBigEndianBytes[Short](Colls.fromArray(Shorts.toByteArray(x))) == x},
"{ (x: Short) => fromBigEndianBytes[Short](x.toBytes) == x }",
sinceVersion = VersionContext.V6SoftForkVersion
)
}

verifyCases(
Seq(
5.toShort -> new Expected(ExpectedResult(Success(true), None)),
Short.MaxValue -> new Expected(ExpectedResult(Success(true), None)),
Short.MinValue -> new Expected(ExpectedResult(Success(true), None))
),
shortFromBigEndianBytes
)

def intFromBigEndianBytes: Feature[Int, Boolean] = {
newFeature(
{ (x: Int) => CSigmaDslBuilder.fromBigEndianBytes[Int](Colls.fromArray(Ints.toByteArray(x))) == x},
"{ (x: Int) => fromBigEndianBytes[Int](x.toBytes) == x }",
sinceVersion = VersionContext.V6SoftForkVersion
)
}

verifyCases(
Seq(
5 -> new Expected(ExpectedResult(Success(true), None)),
Int.MaxValue -> new Expected(ExpectedResult(Success(true), None))
),
intFromBigEndianBytes
)

def longFromBigEndianBytes: Feature[Long, Boolean] = {
newFeature(
{ (x: Long) => CSigmaDslBuilder.fromBigEndianBytes[Long](Colls.fromArray(Longs.toByteArray(x))) == x},
"{ (x: Long) => fromBigEndianBytes[Long](x.toBytes) == x }",
sinceVersion = VersionContext.V6SoftForkVersion
)
}

verifyCases(
Seq(
5L -> new Expected(ExpectedResult(Success(true), None)),
Long.MinValue -> new Expected(ExpectedResult(Success(true), None))
),
longFromBigEndianBytes
)

def bigIntFromBigEndianBytes: Feature[BigInt, Boolean] = {
newFeature(
{ (x: BigInt) => CSigmaDslBuilder.fromBigEndianBytes[BigInt](x.toBytes) == x},
"{ (x: BigInt) => Global.fromBigEndianBytes[BigInt](x.toBytes) == x }",
sinceVersion = VersionContext.V6SoftForkVersion
)
}

verifyCases(
Seq(
CBigInt(BigInteger.valueOf(50)) -> new Expected(ExpectedResult(Success(true), None)),
CBigInt(BigInteger.valueOf(-500000000000L)) -> new Expected(ExpectedResult(Success(true), None)),
CBigInt(sigma.crypto.CryptoConstants.groupOrder.divide(BigInteger.valueOf(2))) -> new Expected(ExpectedResult(Success(true), None))
),
bigIntFromBigEndianBytes
)

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit with C
(SGlobal.typeId, Seq(
MInfo(1, groupGeneratorMethod), MInfo(2, xorMethod)
) ++ (if (isV6Activated) {
Seq(MInfo(3, serializeMethod)) // methods added in v6.0
Seq(MInfo(3, serializeMethod), MInfo(5, fromBigEndianBytesMethod)) // methods added in v6.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth making a note about id = 4.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

} else {
Seq.empty[MInfo]
}), true)
Expand Down
Loading
Loading