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.0] Conversion from Long-encoded nBits representation to BigInt and back #962

Open
wants to merge 28 commits into
base: v6.0.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0843902
DecodeNBitsMethod definition
kushti Apr 2, 2024
49dce78
ToNBits def
kushti Apr 2, 2024
1c1049e
merging w. 6.0.0
kushti Apr 3, 2024
37bb86c
toNBits added to SigmaDsl
kushti Apr 4, 2024
6f8981f
NBitsUtils
kushti Apr 4, 2024
d0f1b7d
unused import removed
kushti Apr 5, 2024
fcc7f0f
merging latest 5.0.14 commits
kushti Apr 6, 2024
6e47167
nbits impl
kushti Apr 9, 2024
9e4e098
failing roundtrip test for deserialization roundtrip
kushti Apr 10, 2024
1016323
importing method def from i675
kushti Apr 10, 2024
93748f1
removing decode nbits
kushti Apr 10, 2024
a3cb64d
versioned nbits serialization roundtrip test
kushti Apr 12, 2024
8941a8e
nbits evaluation test
kushti Apr 15, 2024
c8d75cd
Merge branch 'v6.0.0' of github.com:ScorexFoundation/sigmastate-inter…
kushti May 26, 2024
c9d0889
first pow check test
kushti May 26, 2024
299fdd5
merging w. nbits encoding PR
kushti May 26, 2024
a90e7be
pow check test passing
kushti May 26, 2024
37b23cd
hit <= diff
kushti May 29, 2024
aaa2aa9
Merge branch 'v6.0.0' of github.com:ScorexFoundation/sigmastate-inter…
kushti Jul 31, 2024
a20c04f
moving to Global, finalizing costing
kushti Jul 31, 2024
2c8df31
updating collectMethods
kushti Jul 31, 2024
6981c34
fixing JS test
kushti Jul 31, 2024
d0eef4c
merging w. 6.0.0
kushti Sep 30, 2024
6128fbd
fixing ErgoTreeSpec
kushti Oct 1, 2024
ee25e40
merging w. 6.0.0
kushti Oct 1, 2024
e4a611d
fixing MethodCallSerializerSpecification
kushti Oct 4, 2024
c5c37ff
fixing JS reflection
kushti Oct 4, 2024
e87ad02
Merge branch 'v6.0.0' of github.com:ScorexFoundation/sigmastate-inter…
kushti Oct 8, 2024
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
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 @@ -727,6 +727,20 @@ trait SigmaDslBuilder {
*/
def groupGenerator: GroupElement

/**
* @return big integer provided as input approximately encoded using NBits,
Copy link
Member

Choose a reason for hiding this comment

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

A bit confusing comment, says "@return big integer" while the actual return type is Long.

* see (https://bitcoin.stackexchange.com/questions/57184/what-does-the-nbits-value-represent)
* for 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 @@ -468,6 +468,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
19 changes: 18 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 @@ -1806,6 +1809,18 @@ case object SGlobalMethods extends MonoTypeMethods {
"Decode a number from big endian bytes.",
ArgInfo("first", "Bytes which are big-endian encoded number."))

private lazy val EnDecodeNBitsCost = FixedCost(JitCost(5)) // the same cost for nbits encoding and decoding
Copy link
Member

Choose a reason for hiding this comment

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

The cost is too low. And encoding is cheaper than decoding (because of array creation).
There is a profiler in LSV5 (see the comments and afterAll method).
I suggest at least 2 for encoding and 40 for decoding.


lazy val encodeNBitsMethod: SMethod = SMethod(
this, "encodeNbits", SFunc(Array(SGlobal, SBigInt), SLong), 6, EnDecodeNBitsCost)
.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, EnDecodeNBitsCost)
.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 @@ -1841,6 +1856,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

def encodeNbits(bi: BigInt): Long = {
Copy link
Member

Choose a reason for hiding this comment

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

override missing

NBitsUtils.encodeCompactBits(bi.asInstanceOf[CBigInt].wrappedValue)
}

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 @@ -9,6 +9,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 @@ -1167,6 +1168,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 @@ -528,6 +529,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 @@ -115,6 +115,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 @@ -1978,6 +1978,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 @@ -2151,6 +2164,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 @@ -2168,9 +2195,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
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,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(6, encodeNBitsMethod), MInfo(7, decodeNBitsMethod)) // methods added in v6.0
} else {
Seq.empty[MInfo]
}), true)
Expand Down
Loading
Loading