Skip to content

Commit

Permalink
Merge pull request #962 from ergoplatform/i675-2
Browse files Browse the repository at this point in the history
[6.0.0] Conversion from Long-encoded nBits representation to BigInt and back
  • Loading branch information
kushti authored Nov 19, 2024
2 parents b074b28 + 367d320 commit 494221a
Show file tree
Hide file tree
Showing 18 changed files with 3,650 additions and 22 deletions.
14 changes: 14 additions & 0 deletions core/shared/src/main/scala/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,20 @@ trait SigmaDslBuilder {
*/
def groupGenerator: GroupElement

/**
* @return NBits-encoded approximate representation of given big integer,
* see (https://bitcoin.stackexchange.com/questions/57184/what-does-the-nbits-value-represent)
* for NBits format details
*/
def encodeNbits(bi: BigInt): Long

/**
* @return big integer decoded from NBits value provided,
* see (https://bitcoin.stackexchange.com/questions/57184/what-does-the-nbits-value-represent)
* for format details
*/
def decodeNbits(l: Long): BigInt

/**
* Transforms serialized bytes of ErgoTree with segregated constants by replacing constants
* at given positions with new values. This operation allow to use serialized scripts as
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,12 @@ 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, "encodeNbits", Array[Class[_]](classOf[BigInt])) { (obj, args) =>
obj.asInstanceOf[SigmaDslBuilder].encodeNbits(args(0).asInstanceOf[BigInt])
},
mkMethod(clazz, "decodeNbits", Array[Class[_]](classOf[Long])) { (obj, args) =>
obj.asInstanceOf[SigmaDslBuilder].decodeNbits(args(0).asInstanceOf[Long])
}
)
)
Expand Down
8 changes: 8 additions & 0 deletions data/shared/src/main/scala/sigma/SigmaDataReflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ object SigmaDataReflection {
)
)

registerClassEntry(classOf[ByteArrayToLong],
constructors = Array(
mkConstructor(Array(classOf[Value[_]])) { args =>
new ByteArrayToLong(args(0).asInstanceOf[Value[SByteArray]])
}
)
)

registerClassEntry(classOf[LongToByteArray],
constructors = Array(
mkConstructor(Array(classOf[Value[_]])) { args =>
Expand Down
21 changes: 20 additions & 1 deletion data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ import org.ergoplatform._
import org.ergoplatform.validation._
import sigma.Evaluation.stypeToRType
import sigma._
import sigma.{VersionContext, _}
import sigma.ast.SCollection.{SBooleanArray, SBoxArray, SByteArray, SByteArray2, SHeaderArray}
import sigma.ast.SGlobalMethods.{decodeNBitsMethod, encodeNBitsMethod}
import sigma.ast.SMethod.{MethodCallIrBuilder, MethodCostFunc, javaMethodOf}
import sigma.ast.SType.{TypeCode, paramT, tT}
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.reflection.RClass
import sigma.serialization.CoreByteWriter.ArgInfo
import sigma.serialization.{DataSerializer, SigmaByteWriter, SigmaSerializer}
import sigma.util.NBitsUtils
import sigma.utils.SparseArrayContainer

import scala.annotation.unused
Expand Down Expand Up @@ -1842,6 +1845,20 @@ case object SGlobalMethods extends MonoTypeMethods {
"Decode a number from big endian bytes.",
ArgInfo("first", "Bytes which are big-endian encoded number."))

private lazy val EncodeNBitsCost = FixedCost(JitCost(25)) // cost for nbits encoding

private lazy val DecodeNBitsCost = FixedCost(JitCost(50)) // cost for nbits decoding

lazy val encodeNBitsMethod: SMethod = SMethod(
this, "encodeNbits", SFunc(Array(SGlobal, SBigInt), SLong), 6, EncodeNBitsCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "Encode big integer number as nbits", ArgInfo("bigInt", "Big integer"))

lazy val decodeNBitsMethod: SMethod = SMethod(
this, "decodeNbits", SFunc(Array(SGlobal, SLong), SBigInt), 7, DecodeNBitsCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "Decode nbits-encoded big integer number", ArgInfo("nbits", "NBits-encoded argument"))

lazy val serializeMethod = SMethod(this, "serialize",
SFunc(Array(SGlobal, tT), SByteArray, Array(paramT)), 3, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
Expand Down Expand Up @@ -1877,6 +1894,8 @@ case object SGlobalMethods extends MonoTypeMethods {
groupGeneratorMethod,
xorMethod,
serializeMethod,
encodeNBitsMethod,
decodeNBitsMethod,
fromBigEndianBytesMethod
)
} else {
Expand Down
9 changes: 9 additions & 0 deletions data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import sigma.eval.Extensions.EvalCollOps
import sigma.serialization.{DataSerializer, GroupElementSerializer, SigmaSerializer}
import sigma.serialization.{GroupElementSerializer, SerializerException, SigmaSerializer}
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}

Expand Down Expand Up @@ -178,6 +179,14 @@ class CSigmaDslBuilder extends SigmaDslBuilder { dsl =>

override def groupGenerator: GroupElement = _generatorElement

override def encodeNbits(bi: BigInt): Long = {
NBitsUtils.encodeCompactBits(bi.asInstanceOf[CBigInt].wrappedValue)
}

override def decodeNbits(l: Long): BigInt = {
CBigInt(NBitsUtils.decodeCompactBits(l).bigInteger)
}

/**
* @return the identity of the Dlog group used in ErgoTree
*/
Expand Down
4 changes: 2 additions & 2 deletions data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ abstract class ErgoTreeEvaluator {
* @param opDesc the operation descriptor to associate the cost with (when costTracingEnabled)
* @param block operation executed under the given cost
*/
def addFixedCost(costKind: FixedCost, opDesc: OperationDesc)(block: => Unit): Unit
def addFixedCost[R](costKind: FixedCost, opDesc: OperationDesc)(block: => R): R

def addFixedCost(costInfo: OperationCostInfo[FixedCost])(block: => Unit): Unit
def addFixedCost[R](costInfo: OperationCostInfo[FixedCost])(block: => R): R

/** Adds the given cost to the `coster`. If tracing is enabled, creates a new cost item
* with the given operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ class CErgoTreeEvaluator(
}

/** @hotspot don't beautify the code */
override def addFixedCost(costKind: FixedCost, opDesc: OperationDesc)(block: => Unit): Unit = {
override def addFixedCost[R](costKind: FixedCost, opDesc: OperationDesc)(block: => R): R = {
var costItem: FixedCostItem = null
if (settings.costTracingEnabled) {
costItem = FixedCostItem(opDesc, costKind)
Expand All @@ -307,16 +307,17 @@ class CErgoTreeEvaluator(
}
val start = System.nanoTime()
coster.add(costKind.cost)
val _ = block
val res = block
val end = System.nanoTime()
profiler.addCostItem(costItem, end - start)
res
} else {
coster.add(costKind.cost)
block
}
}

override def addFixedCost(costInfo: OperationCostInfo[FixedCost])(block: => Unit): Unit = {
override def addFixedCost[R](costInfo: OperationCostInfo[FixedCost])(block: => R): R = {
addFixedCost(costInfo.costKind, costInfo.opDesc)(block)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class MethodCallSerializerSpecification extends SerializationSpecification {
code
}

an[Exception] should be thrownBy (
an[SerializerException] should be thrownBy (
VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) {
code
}
Expand Down Expand Up @@ -68,4 +68,47 @@ class MethodCallSerializerSpecification extends SerializationSpecification {
})
}

property("MethodCall deserialization round trip for Global.encodeNBits") {
def code = {
val bi = BigIntConstant(5)
val expr = MethodCall(Global,
SGlobalMethods.encodeNBitsMethod,
Vector(bi),
Map()
)
roundTripTest(expr)
}

// should be ok
VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) {
code
}

an[ValidationException] should be thrownBy (
VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) {
code
})
}

property("MethodCall deserialization round trip for Global.decodeNBits") {
def code = {
val l = LongConstant(5)
val expr = MethodCall(Global,
SGlobalMethods.decodeNBitsMethod,
Vector(l),
Map()
)
roundTripTest(expr)
}

VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) {
code
}

an[ValidationException] should be thrownBy (
VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) {
code
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ 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.crypto.SecP256K1Group
import sigma.data.{CSigmaDslBuilder => SigmaDsl, TrivialProp}
import sigma.data.{CBigInt, TrivialProp}
import sigma.eval.SigmaDsl
import sigma.util.Extensions.SigmaBooleanOps
import sigma.util.NBitsUtils

import java.math.BigInteger
import sigma.{ContractsTestkit, SigmaProp}
import sigmastate.interpreter.{CErgoTreeEvaluator, CostAccumulator}
import sigmastate.interpreter.CErgoTreeEvaluator.DefaultProfiler

import scala.language.implicitConversions

Expand Down Expand Up @@ -63,4 +68,27 @@ class BasicOpsTests extends AnyFunSuite with ContractsTestkit with Matchers {
box.creationInfo._1 shouldBe a [Integer]
}

/**
* Checks BigInt.nbits evaluation for SigmaDSL as well as AST interpreter (MethodCall) layers
*/
test("nbits evaluation") {
SigmaDsl.encodeNbits(CBigInt(BigInteger.valueOf(0))) should be
(NBitsUtils.encodeCompactBits(0))

val es = CErgoTreeEvaluator.DefaultEvalSettings
val accumulator = new CostAccumulator(
initialCost = JitCost(0),
costLimit = Some(JitCost.fromBlockCost(es.scriptCostLimitInEvaluator)))
val evaluator = new CErgoTreeEvaluator(
context = null,
constants = ErgoTree.EmptyConstants,
coster = accumulator, DefaultProfiler, es)

val res = MethodCall(Global, SGlobalMethods.encodeNBitsMethod, IndexedSeq(BigIntConstant(BigInteger.valueOf(0))), Map.empty)
.evalTo[Long](Map.empty)(evaluator)

res should be (NBitsUtils.encodeCompactBits(0))

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import sigma.ast.syntax.{SValue, ValueOps}
import sigma.ast._
import sigma.compiler.ir.core.MutableLazy
import sigma.crypto.EcPointType
import sigma.VersionContext
import sigma.data.ExactIntegral.{ByteIsExactIntegral, IntIsExactIntegral, LongIsExactIntegral, ShortIsExactIntegral}
import sigma.data.ExactOrdering.{ByteIsExactOrdering, IntIsExactOrdering, LongIsExactOrdering, ShortIsExactOrdering}
import sigma.data.{CSigmaDslBuilder, ExactIntegral, ExactNumeric, ExactOrdering, Lazy, Nullable}
Expand Down Expand Up @@ -1177,6 +1178,12 @@ 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.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.serializeMethod.name =>
val value = asRep[Any](argsV(0))
g.serialize(value)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package sigma.compiler.ir

import sigma.{BigInt, SigmaDslBuilder}
import sigma.ast.SType
import sigma.compiler.ir.primitives.Thunks
import sigma.data.RType
Expand Down Expand Up @@ -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, "encodeNbits", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.SigmaDslBuilder].encodeNbits(args(0).asInstanceOf[ctx.Ref[ctx.BigInt]])
},
mkMethod(clazz, "decodeNbits", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.SigmaDslBuilder].decodeNbits(args(0).asInstanceOf[ctx.Ref[Long]])
},
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 @@ -116,6 +116,8 @@ import scalan._
/** 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 encodeNbits(bi: Ref[BigInt]): Ref[Long]
def decodeNbits(l: Ref[Long]): Ref[BigInt]
def serialize[T](value: Ref[T]): Ref[Coll[Byte]]
def fromBigEndianBytes[T](bytes: Ref[Coll[Byte]])(implicit cT: Elem[T]): Ref[T]
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1996,6 +1996,19 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") {
true, false, cT))
}

override def encodeNbits(bi: Ref[BigInt]): Ref[Long] = {
asRep[Long](mkMethodCall(self,
SigmaDslBuilderClass.getMethod("encodeNbits", classOf[Sym]),
Array[AnyRef](bi),
true, false, element[Long]))
}

override def decodeNbits(l: Ref[Long]): Ref[BigInt] = {
asRep[BigInt](mkMethodCall(self,
SigmaDslBuilderClass.getMethod("decodeNbits", classOf[Sym]),
Array[AnyRef](l),
true, false, element[BigInt]))
}
}

implicit object LiftableSigmaDslBuilder
Expand Down Expand Up @@ -2169,6 +2182,20 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") {
Array[AnyRef](bytes, cT),
true, true, cT, Map(tT -> Evaluation.rtypeToSType(cT.sourceType))))
}

override def encodeNbits(bi: Ref[BigInt]): Ref[Long] = {
asRep[Long](mkMethodCall(source,
SigmaDslBuilderClass.getMethod("encodeNbits", classOf[Sym]),
Array[AnyRef](bi),
true, true, element[Long]))
}

override def decodeNbits(l: Ref[Long]): Ref[BigInt] = {
asRep[BigInt](mkMethodCall(source,
SigmaDslBuilderClass.getMethod("decodeNbits", classOf[Sym]),
Array[AnyRef](l),
true, true, element[BigInt]))
}
}

// entityUnref: single unref method for each type family
Expand All @@ -2186,9 +2213,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", "serialize", "fromBigEndianBytes"
"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"
))
}
}
Expand Down
Loading

0 comments on commit 494221a

Please sign in to comment.