Skip to content

Commit

Permalink
merging v6.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
kushti committed Sep 2, 2024
2 parents baef87f + 2cd57e1 commit c7918ad
Show file tree
Hide file tree
Showing 36 changed files with 1,224 additions and 178 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

yarn.lock
*.log
yarn.lock
docs/spec/out/
test-out/
flamegraphs/
Expand Down
17 changes: 17 additions & 0 deletions core/shared/src/main/scala/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,23 @@ trait Header {

/** Miner votes for changing system parameters. */
def votes: Coll[Byte] //3 bytes

/** Bytes which are coming from future versions of the protocol, so
* their meaning is not known to current version of Sigma, but they
* are stored to get the same id as future version users.
*/
def unparsedBytes: Coll[Byte]

/**
* @return header bytes without proof of work, a PoW is generated over them
*/
def serializeWithoutPoW: Coll[Byte]

/**
* @return result of header's proof-of-work validation
*/
def checkPow: Boolean

}

/** Runtime representation of Context ErgoTree type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ object ReflectionData {
},
mkMethod(clazz, "powDistance", Array[Class[_]]()) { (obj, _) =>
obj.asInstanceOf[Header].powDistance
},
mkMethod(clazz, "checkPow", Array[Class[_]]()) { (obj, _) =>
obj.asInstanceOf[Header].checkPow
}
)
)
Expand Down
84 changes: 84 additions & 0 deletions core/shared/src/main/scala/sigma/util/NBitsUtils.scala
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
}
}
}

}
182 changes: 182 additions & 0 deletions data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package org.ergoplatform

import scorex.crypto.authds.ADDigest
import scorex.crypto.hash.{Blake2b256, Digest32}
import scorex.util.ModifierId
import sigma.Colls
import sigma.crypto.{BigIntegers, CryptoConstants, EcPointType}
import sigma.serialization.{GroupElementSerializer, SigmaByteReader, SigmaByteWriter, SigmaSerializer}

import scala.runtime.ScalaRunTime
import scala.util.hashing.MurmurHash3



/**
* Solution for an Autolykos PoW puzzle.
*
* In Autolykos v.1 all the four fields are used, in Autolykos v.2 only pk and n fields are used.
*
* @param pk - miner public key. Should be used to collect block rewards
* @param w - one-time public key. Prevents revealing of miners secret
* @param n - nonce (8 bytes)
* @param d - distance between pseudo-random number, corresponding to nonce `n` and a secret,
* corresponding to `pk`. The lower `d` is, the harder it was to find this solution.
*/
class AutolykosSolution(val pk: EcPointType,
val w: EcPointType,
val n: Array[Byte],
val d: BigInt) {

val encodedPk: Array[Byte] = GroupElementSerializer.toBytes(pk)

override def hashCode(): Int = {
var h = pk.hashCode()
h = h * 31 + w.hashCode()
h = h * 31 + MurmurHash3.arrayHash(n)
h = h * 31 + d.hashCode()
h
}

override def equals(obj: Any): Boolean = {
obj match {
case other: AutolykosSolution =>
this.pk == other.pk &&
this.n.sameElements(other.n) &&
this.w == other.w &&
this.d == other.d

case _ => false
}
}
}


object AutolykosSolution {
// "pk", "w" and "d" values for Autolykos v2 solution, where they not passed from outside
val pkForV2: EcPointType = CryptoConstants.dlogGroup.identity
val wForV2: EcPointType = CryptoConstants.dlogGroup.generator
val dForV2: BigInt = 0

object sigmaSerializerV1 extends SigmaSerializer[AutolykosSolution, AutolykosSolution] {
override def serialize(s: AutolykosSolution, w: SigmaByteWriter): Unit = {
GroupElementSerializer.serialize(s.pk, w)
GroupElementSerializer.serialize(s.w, w)
require(s.n.length == 8) // non-consensus check on prover side
w.putBytes(s.n)
val dBytes = BigIntegers.asUnsignedByteArray(s.d.bigInteger)
w.putUByte(dBytes.length)
w.putBytes(dBytes)
}

override def parse(r: SigmaByteReader): AutolykosSolution = {
val pk = GroupElementSerializer.parse(r)
val w = GroupElementSerializer.parse(r)
val nonce = r.getBytes(8)
val dBytesLength = r.getUByte()
val d = BigInt(BigIntegers.fromUnsignedByteArray(r.getBytes(dBytesLength)))
new AutolykosSolution(pk, w, nonce, d)
}
}

object sigmaSerializerV2 extends SigmaSerializer[AutolykosSolution, AutolykosSolution] {
override def serialize(s: AutolykosSolution, w: SigmaByteWriter): Unit = {
GroupElementSerializer.serialize(s.pk, w)
require(s.n.length == 8) // non-consensus check on prover side
w.putBytes(s.n)
}

override def parse(r: SigmaByteReader): AutolykosSolution = {
val pk = GroupElementSerializer.parse(r)
val nonce = r.getBytes(8)
new AutolykosSolution(pk, wForV2, nonce, dForV2)
}
}
}

/**
* Header of a block. It authenticates link to a previous block, other block sections
* (transactions, UTXO set transformation proofs, extension), UTXO set, votes for parameters
* to be changed and proof-of-work related data.
*
* @param version - protocol version
* @param parentId - id of a parent block header
* @param ADProofsRoot - digest of UTXO set transformation proofs
* @param stateRoot - AVL+ tree digest of UTXO set (after the block)
* @param transactionsRoot - Merkle tree digest of transactions in the block (BlockTransactions section)
* @param timestamp - block generation time reported by a miner
* @param nBits - difficulty encoded
* @param height - height of the block (genesis block height == 1)
* @param extensionRoot - Merkle tree digest of the extension section of the block
* @param powSolution - solution for the proof-of-work puzzle
* @param votes - votes for changing system parameters
* @param unparsedBytes - bytes from future versions of the protocol our version can't parse
* @param _bytes - serialized bytes of the header when not `null`
*/
case class ErgoHeader(override val version: ErgoHeader.Version,
override val parentId: ModifierId,
override val ADProofsRoot: Digest32,
override val stateRoot: ADDigest, //33 bytes! extra byte with tree height here!
override val transactionsRoot: Digest32,
override val timestamp: ErgoHeader.Timestamp,
override val nBits: Long, //actually it is unsigned int
override val height: Int,
override val extensionRoot: Digest32,
powSolution: AutolykosSolution,
override val votes: Array[Byte], //3 bytes
override val unparsedBytes: Array[Byte],
_bytes: Array[Byte]) extends
HeaderWithoutPow(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp,
nBits, height, extensionRoot, votes, unparsedBytes) {

lazy val bytes = if(_bytes != null) {
_bytes
} else {
ErgoHeader.sigmaSerializer.toBytes(this)
}

lazy val serializedId: Array[Byte] = Blake2b256.hash(bytes)

lazy val id = Colls.fromArray(serializedId)

override def hashCode(): Int = id.hashCode()

override def equals(other: Any): Boolean = other match {
case h: ErgoHeader => h.id == this.id
case _ => false
}
}


object ErgoHeader {

type Timestamp = Long

type Version = Byte

object sigmaSerializer extends SigmaSerializer[ErgoHeader, ErgoHeader] {
override def serialize(hdr: ErgoHeader, w: SigmaByteWriter): Unit = {
HeaderWithoutPowSerializer.serialize(hdr, w)
if (hdr.version == 1) {
AutolykosSolution.sigmaSerializerV1.serialize(hdr.powSolution, w)
} else {
AutolykosSolution.sigmaSerializerV2.serialize(hdr.powSolution, w)
}
}

override def parse(r: SigmaByteReader): ErgoHeader = {
val start = r.position
val headerWithoutPow = HeaderWithoutPowSerializer.parse(r)
val powSolution = if (headerWithoutPow.version == 1) {
AutolykosSolution.sigmaSerializerV1.parse(r)
} else {
AutolykosSolution.sigmaSerializerV2.parse(r)
}
val end = r.position
val len = end - start
r.position = start
val headerBytes = r.getBytes(len) // also moves position back to end
headerWithoutPow.toHeader(powSolution, headerBytes)
}
}
}
Loading

0 comments on commit c7918ad

Please sign in to comment.