-
Notifications
You must be signed in to change notification settings - Fork 41
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
base: v6.0.0
Are you sure you want to change the base?
Changes from 22 commits
0843902
49dce78
1c1049e
37bb86c
6f8981f
d0f1b7d
fcc7f0f
6e47167
9e4e098
1016323
93748f1
a3cb64d
8941a8e
c8d75cd
c9d0889
299fdd5
a90e7be
37b23cd
aaa2aa9
a20c04f
2c8df31
6981c34
d0eef4c
6128fbd
ee25e40
e4a611d
c5c37ff
e87ad02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package sigma.util | ||
|
||
import java.math.BigInteger | ||
|
||
object NBitsUtils { | ||
|
||
/** | ||
* <p>The "compact" format is a representation of a whole number N using an unsigned 32 bit number similar to a | ||
* floating point format. The most significant 8 bits are the unsigned exponent of base 256. This exponent can | ||
* be thought of as "number of bytes of N". The lower 23 bits are the mantissa. Bit number 24 (0x800000) represents | ||
* the sign of N. Therefore, N = (-1^sign) * mantissa * 256^(exponent-3).</p> | ||
* | ||
* <p>Satoshi's original implementation used BN_bn2mpi() and BN_mpi2bn(). MPI uses the most significant bit of the | ||
* first byte as sign. Thus 0x1234560000 is compact 0x05123456 and 0xc0de000000 is compact 0x0600c0de. Compact | ||
* 0x05c0de00 would be -0x40de000000.</p> | ||
* | ||
* <p>Bitcoin only uses this "compact" format for encoding difficulty targets, which are unsigned 256bit quantities. | ||
* Thus, all the complexities of the sign bit and using base 256 are probably an implementation accident.</p> | ||
*/ | ||
def decodeCompactBits(compact: Long): BigInt = { | ||
val size: Int = (compact >> 24).toInt & 0xFF | ||
val bytes: Array[Byte] = new Array[Byte](4 + size) | ||
bytes(3) = size.toByte | ||
if (size >= 1) bytes(4) = ((compact >> 16) & 0xFF).toByte | ||
if (size >= 2) bytes(5) = ((compact >> 8) & 0xFF).toByte | ||
if (size >= 3) bytes(6) = (compact & 0xFF).toByte | ||
decodeMPI(bytes) | ||
} | ||
|
||
/** | ||
* @see Utils#decodeCompactBits(long) | ||
*/ | ||
def encodeCompactBits(requiredDifficulty: BigInt): Long = { | ||
val value = requiredDifficulty.bigInteger | ||
var result: Long = 0L | ||
var size: Int = value.toByteArray.length | ||
if (size <= 3) { | ||
result = value.longValue << 8 * (3 - size) | ||
} else { | ||
result = value.shiftRight(8 * (size - 3)).longValue | ||
} | ||
// The 0x00800000 bit denotes the sign. | ||
// Thus, if it is already set, divide the mantissa by 256 and increase the exponent. | ||
if ((result & 0x00800000L) != 0) { | ||
result >>= 8 | ||
size += 1 | ||
} | ||
result |= size << 24 | ||
val a: Int = if (value.signum == -1) 0x00800000 else 0 | ||
result |= a | ||
result | ||
} | ||
|
||
|
||
/** Parse 4 bytes from the byte array (starting at the offset) as unsigned 32-bit integer in big endian format. */ | ||
def readUint32BE(bytes: Array[Byte]): Long = ((bytes(0) & 0xffL) << 24) | ((bytes(1) & 0xffL) << 16) | ((bytes(2) & 0xffL) << 8) | (bytes(3) & 0xffL) | ||
|
||
/** | ||
* MPI encoded numbers are produced by the OpenSSL BN_bn2mpi function. They consist of | ||
* a 4 byte big endian length field, followed by the stated number of bytes representing | ||
* the number in big endian format (with a sign bit). | ||
* | ||
*/ | ||
private def decodeMPI(mpi: Array[Byte]): BigInteger = { | ||
|
||
val length: Int = readUint32BE(mpi).toInt | ||
val buf = new Array[Byte](length) | ||
System.arraycopy(mpi, 4, buf, 0, length) | ||
|
||
if (buf.length == 0) { | ||
BigInteger.ZERO | ||
} else { | ||
val isNegative: Boolean = (buf(0) & 0x80) == 0x80 | ||
if (isNegative) buf(0) = (buf(0) & 0x7f).toByte | ||
val result: BigInteger = new BigInteger(buf) | ||
if (isNegative) { | ||
result.negate | ||
} else { | ||
result | ||
} | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,16 +2,17 @@ package sigma.ast | |
|
||
import org.ergoplatform._ | ||
import org.ergoplatform.validation._ | ||
import sigma._ | ||
import sigma.{VersionContext, _} | ||
import sigma.ast.SCollection.{SBooleanArray, SBoxArray, SByteArray, SByteArray2, SHeaderArray} | ||
import sigma.ast.SMethod.{MethodCallIrBuilder, MethodCostFunc, javaMethodOf} | ||
import sigma.ast.SType.TypeCode | ||
import sigma.ast.syntax.{SValue, ValueOps} | ||
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.util.NBitsUtils | ||
import sigma.utils.SparseArrayContainer | ||
|
||
import scala.annotation.unused | ||
|
@@ -309,13 +310,6 @@ case object SBigIntMethods extends SNumericTypeMethods { | |
/** Type for which this container defines methods. */ | ||
override def ownerType: SMonoType = SBigInt | ||
|
||
final val ToNBitsCostInfo = OperationCostInfo( | ||
FixedCost(JitCost(5)), NamedDesc("NBitsMethodCall")) | ||
|
||
//id = 8 to make it after toBits | ||
val ToNBits = SMethod(this, "nbits", SFunc(this.ownerType, SLong), 8, ToNBitsCostInfo.costKind) | ||
.withInfo(ModQ, "Encode this big integer value as NBits") | ||
|
||
/** The following `modQ` methods are not fully implemented in v4.x and this descriptors. | ||
* This descritors are remain here in the code and are waiting for full implementation | ||
* is upcoming soft-forks at which point the cost parameters should be calculated and | ||
|
@@ -333,7 +327,7 @@ case object SBigIntMethods extends SNumericTypeMethods { | |
|
||
protected override def getMethods(): Seq[SMethod] = { | ||
if (VersionContext.current.isV6SoftForkActivated) { | ||
super.getMethods() ++ Seq(ToNBits) | ||
super.getMethods() | ||
// ModQMethod, | ||
// PlusModQMethod, | ||
// MinusModQMethod, | ||
|
@@ -343,14 +337,6 @@ case object SBigIntMethods extends SNumericTypeMethods { | |
super.getMethods() | ||
} | ||
} | ||
|
||
/** | ||
* | ||
*/ | ||
def nbits_eval(mc: MethodCall, bi: sigma.BigInt)(implicit E: ErgoTreeEvaluator): Long = { | ||
??? | ||
} | ||
|
||
} | ||
|
||
/** Methods of type `String`. */ | ||
|
@@ -1519,9 +1505,50 @@ case object SGlobalMethods extends MonoTypeMethods { | |
Xor.xorWithCosting(ls, rs) | ||
} | ||
|
||
protected override def getMethods() = super.getMethods() ++ Seq( | ||
groupGeneratorMethod, | ||
xorMethod | ||
) | ||
private lazy val EnDecodeNBitsCost = FixedCost(JitCost(5)) // the same cost for nbits encoding and decoding | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). |
||
|
||
lazy val encodeNBitsMethod: SMethod = SMethod( | ||
this, "encodeNbits", SFunc(Array(SGlobal, SBigInt), SLong), 3, 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), 4, EnDecodeNBitsCost) | ||
.withIRInfo(MethodCallIrBuilder) | ||
.withInfo(MethodCall, "Decode nbits-encoded big integer number", ArgInfo("nbits", "NBits-encoded argument")) | ||
|
||
/** | ||
* encodeNBits evaluation with costing | ||
*/ | ||
def encodeNbits_eval(mc: MethodCall, G: SigmaDslBuilder, bigInt: BigInt)(implicit E: ErgoTreeEvaluator): Long = { | ||
E.addFixedCost(EnDecodeNBitsCost, encodeNBitsMethod.opDesc) { | ||
NBitsUtils.encodeCompactBits(bigInt.asInstanceOf[CBigInt].wrappedValue) | ||
} | ||
} | ||
|
||
/** | ||
* decodeNBits evaluation with costing | ||
*/ | ||
def decodeNbits_eval(mc: MethodCall, G: SigmaDslBuilder, l: Long)(implicit E: ErgoTreeEvaluator): BigInt = { | ||
E.addFixedCost(EnDecodeNBitsCost, decodeNBitsMethod.opDesc) { | ||
CBigInt(NBitsUtils.decodeCompactBits(l).bigInteger) | ||
} | ||
} | ||
|
||
protected override def getMethods() = { | ||
if (VersionContext.current.isV6SoftForkActivated) { | ||
super.getMethods() ++ Seq( | ||
groupGeneratorMethod, | ||
xorMethod, | ||
encodeNBitsMethod, | ||
decodeNBitsMethod | ||
) | ||
} else { | ||
super.getMethods() ++ Seq( | ||
groupGeneratorMethod, | ||
xorMethod | ||
) | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import sigma.crypto.{CryptoConstants, EcPointType, Ecp} | |
import sigma.eval.Extensions.EvalCollOps | ||
import sigma.serialization.{GroupElementSerializer, SigmaSerializer} | ||
import sigma.util.Extensions.BigIntegerOps | ||
import sigma.util.NBitsUtils | ||
import sigma.validation.SigmaValidationSettings | ||
import sigma.{AvlTree, BigInt, Box, Coll, CollBuilder, GroupElement, SigmaDslBuilder, SigmaProp, VersionContext} | ||
|
||
|
@@ -175,6 +176,14 @@ class CSigmaDslBuilder extends SigmaDslBuilder { dsl => | |
|
||
override def groupGenerator: GroupElement = _generatorElement | ||
|
||
def encodeNbits(bi: BigInt): Long = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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 | ||
*/ | ||
|
There was a problem hiding this comment.
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.