Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/tx-signing-js' into todo-v5.x
Browse files Browse the repository at this point in the history
  • Loading branch information
aslesarenko committed Aug 21, 2023
2 parents 99a4081 + 627214f commit fc6fdb0
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,14 @@ package object collection {
/** Implicit resolution of `Coll[A]` type descriptor, given a descriptor of `A`. */
implicit def collRType[A](implicit tA: RType[A]): RType[Coll[A]] = CollType[A](tA)

/** Conversion to underlying descriptor class.
* Allows syntax like
*
* ```val tColl: RType[Coll[A]] = ...; tColl.tItem```
*
* where `tItem` is a method of `CollType`, but is not defined on `RType`.
*/
implicit def downcastCollType[A](ct: RType[Coll[A]]): CollType[A] = ct.asInstanceOf[CollType[A]]

implicit val collBuilderRType: RType[CollBuilder] = RType.fromClassTag(classTag[CollBuilder])
}
14 changes: 6 additions & 8 deletions interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
package org.ergoplatform

import scorex.utils.{Ints, Shorts}
import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, Token}
import org.ergoplatform.ErgoBox.{AdditionalRegisters, Token}
import org.ergoplatform.settings.ErgoAlgos
import scorex.crypto.authds.ADKey
import scorex.crypto.hash.{Blake2b256, Digest32}
import scorex.crypto.hash.Blake2b256
import scorex.util._
import scorex.utils.{Ints, Shorts}
import sigmastate.SCollection.SByteArray
import sigmastate.SType.AnyOps
import sigmastate.Values._
import sigmastate._
import sigmastate.eval.Extensions._
import sigmastate.eval._
import sigmastate.serialization.SigmaSerializer
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter, Helpers}
import sigmastate.utils.{Helpers, SigmaByteReader, SigmaByteWriter}
import sigmastate.utxo.ExtractCreationInfo
import special.collection._

import scala.runtime.ScalaRunTime

/**
* Box (aka coin, or an unspent output) is a basic concept of a UTXO-based cryptocurrency. In Bitcoin, such an object
* is associated with some monetary value (arbitrary, but with predefined precision, so we use integer arithmetic to
Expand Down Expand Up @@ -52,7 +50,7 @@ class ErgoBox(
override val value: Long,
override val ergoTree: ErgoTree,
override val additionalTokens: Coll[Token] = Colls.emptyColl[Token],
override val additionalRegisters: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] = Map.empty,
override val additionalRegisters: AdditionalRegisters = Map.empty,
val transactionId: ModifierId,
val index: Short,
override val creationHeight: Int
Expand Down Expand Up @@ -136,7 +134,7 @@ object ErgoBox {
/** Represents id of optional registers of a box. */
sealed abstract class NonMandatoryRegisterId(override val number: Byte) extends RegisterId

type AdditionalRegisters = Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]]
type AdditionalRegisters = scala.collection.Map[NonMandatoryRegisterId, EvaluatedValue[_ <: SType]]

object R0 extends MandatoryRegisterId(0, "Monetary value, in Ergo tokens")
object R1 extends MandatoryRegisterId(1, "Guarding script")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class ErgoBoxCandidate(val value: Long,
val ergoTree: ErgoTree,
val creationHeight: Int,
val additionalTokens: Coll[Token] = Colls.emptyColl,
val additionalRegisters: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] = Map())
val additionalRegisters: AdditionalRegisters = Map())
extends ErgoBoxAssets {

/** Transforms this tree to a proposition, substituting the constants if the constant
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ trait ErgoLikeTransactionTemplate[IT <: UnsignedInput] {

lazy val inputIds: IndexedSeq[ADKey] = inputs.map(_.boxId)

override def toString = s"ErgoLikeTransactionTemplate(dataInputs=$dataInputs, inputs=$inputs, outputCandidates=$outputCandidates)"
}


Expand All @@ -76,6 +75,8 @@ class UnsignedErgoLikeTransaction(override val inputs: IndexedSeq[UnsignedInput]
}

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

override def toString = s"UnsignedErgoLikeTransaction(dataInputs=$dataInputs, inputs=$inputs, outputCandidates=$outputCandidates)"
}

object UnsignedErgoLikeTransaction {
Expand Down Expand Up @@ -105,6 +106,7 @@ class ErgoLikeTransaction(override val inputs: IndexedSeq[Input],
}

override def hashCode(): Int = id.hashCode()
override def toString = s"ErgoLikeTransaction(dataInputs=$dataInputs, inputs=$inputs, outputCandidates=$outputCandidates)"
}

object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction, ErgoLikeTransaction] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ sealed class MapSigmaValidationSettings(private val map: Map[Short, (ValidationR

override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match {
case that: MapSigmaValidationSettings =>
map.iterator.forall { case (id, v) => that.map.get(id).exists(_ == v) }
map.size == that.map.size &&
map.iterator.forall { case (id, v) => that.map.get(id).exists(_ == v) }
case _ => false
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ trait ObjectGenerators extends TypeGenerators
lazy val evaluatedValueGen: Gen[EvaluatedValue[SType]] =
Gen.oneOf(booleanConstGen.asInstanceOf[Gen[EvaluatedValue[SType]]], byteArrayConstGen, longConstGen)

def additionalRegistersGen(cnt: Byte): Seq[Gen[(NonMandatoryRegisterId, EvaluatedValue[SType])]] = {
def additionalRegistersGen(cnt: Byte): Seq[Gen[(NonMandatoryRegisterId, EvaluatedValue[_ <: SType])]] = {
scala.util.Random.shuffle((0 until cnt).toList)
.map(_ + ErgoBox.startingNonMandatoryIndex)
.map(rI => ErgoBox.registerByIndex(rI).asInstanceOf[NonMandatoryRegisterId])
Expand Down Expand Up @@ -348,7 +348,8 @@ trait ObjectGenerators extends TypeGenerators
lazy val additionalRegistersGen: Gen[AdditionalRegisters] = for {
regNum <- Gen.chooseNum[Byte](0, ErgoBox.nonMandatoryRegistersCount)
regs <- Gen.sequence(additionalRegistersGen(regNum))(Buildable.buildableSeq)
} yield regs.toMap
} yield
Map(regs.toIndexedSeq:_*)

def ergoBoxTokens(availableTokens: Seq[TokenId]): Gen[Coll[Token]] = for {
tokens <-
Expand Down
105 changes: 67 additions & 38 deletions sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import sigmastate.interpreter.{ContextExtension, ProverResult}
import sigmastate.{AvlTreeData, AvlTreeFlags, SType}
import special.collection.Coll
import special.sigma.{AnyValue, Header, PreHeader}

import scala.util.Try
import sigmastate.utils.Helpers._ // required for Scala 2.11
import org.ergoplatform.ErgoBox
Expand Down Expand Up @@ -238,9 +239,9 @@ trait JsonCodecs {
})

implicit val contextExtensionEncoder: Encoder[ContextExtension] = Encoder.instance({ extension =>
extension.values.map { case (key, value) =>
key -> evaluatedValueEncoder(value)
}.asJson
Json.obj(extension.values.toSeq.map { case (key, value) =>
key.toString -> evaluatedValueEncoder(value)
}: _*)
})

implicit val contextExtensionDecoder: Decoder[ContextExtension] = Decoder.instance({ cursor =>
Expand All @@ -259,8 +260,8 @@ trait JsonCodecs {
implicit val proverResultDecoder: Decoder[ProverResult] = Decoder.instance({ cursor =>
for {
proofBytes <- cursor.downField("proofBytes").as[Array[Byte]]
extMap <- cursor.downField("extension").as[Map[Byte, EvaluatedValue[SType]]]
} yield ProverResult(proofBytes, ContextExtension(extMap))
ext <- cursor.downField("extension").as[ContextExtension]
} yield ProverResult(proofBytes, ext)
})


Expand Down Expand Up @@ -296,13 +297,17 @@ trait JsonCodecs {
decodeErgoTree(_.asInstanceOf[ErgoTree])
}

implicit def registersEncoder[T <: EvaluatedValue[_ <: SType]]: Encoder[Map[NonMandatoryRegisterId, T]] = Encoder.instance({ m =>
implicit def registersEncoder[T <: EvaluatedValue[_ <: SType]]: Encoder[scala.collection.Map[NonMandatoryRegisterId, T]] = Encoder.instance({ m =>
Json.obj(
m.toSeq
.sortBy(_._1.number)
.map { case (k, v) => registerIdEncoder(k) -> evaluatedValueEncoder(v) }: _*)
})

implicit def registersDecoder[T <: EvaluatedValue[_ <: SType]]: Decoder[scala.collection.Map[NonMandatoryRegisterId, T]] = Decoder.instance({ implicit m =>
m.as[mutable.LinkedHashMap[NonMandatoryRegisterId, EvaluatedValue[SType]]].asInstanceOf[Decoder.Result[scala.collection.Map[NonMandatoryRegisterId, T]]]
})

implicit val ergoBoxEncoder: Encoder[ErgoBox] = Encoder.instance({ box =>
Json.obj(
"boxId" -> box.id.asJson,
Expand All @@ -322,7 +327,7 @@ trait JsonCodecs {
ergoTreeBytes <- cursor.downField("ergoTree").as[Array[Byte]]
additionalTokens <- cursor.downField("assets").as(Decoder.decodeSeq(assetDecoder))
creationHeight <- cursor.downField("creationHeight").as[Int]
additionalRegisters <- cursor.downField("additionalRegisters").as[Map[NonMandatoryRegisterId, EvaluatedValue[SType]]]
additionalRegisters <- cursor.downField("additionalRegisters").as(registersDecoder)
transactionId <- cursor.downField("transactionId").as[ModifierId]
index <- cursor.downField("index").as[Short]
} yield new ErgoBox(
Expand All @@ -336,62 +341,86 @@ trait JsonCodecs {
)
})

implicit val ergoBoxCandidateEncoder: Encoder[ErgoBoxCandidate] = Encoder.instance({ box =>
Json.obj(
"value" -> box.value.asJson,
"ergoTree" -> ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(box.ergoTree).asJson,
"assets" -> box.additionalTokens.toArray.toSeq.asJson,
"creationHeight" -> box.creationHeight.asJson,
"additionalRegisters" -> box.additionalRegisters.asJson
)
})

implicit val ergoBoxCandidateDecoder: Decoder[ErgoBoxCandidate] = Decoder.instance({ cursor =>
for {
value <- cursor.downField("value").as[Long]
ergoTreeBytes <- cursor.downField("ergoTree").as[Array[Byte]]
additionalTokens <- cursor.downField("assets").as(Decoder.decodeSeq(assetDecoder))
creationHeight <- cursor.downField("creationHeight").as[Int]
additionalRegisters <- cursor.downField("additionalRegisters").as(registersDecoder)
} yield new ErgoBoxCandidate(
value = value,
ergoTree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(ergoTreeBytes),
creationHeight = creationHeight,
additionalTokens = additionalTokens.toColl,
additionalRegisters = additionalRegisters
)
})

implicit val ergoLikeTransactionEncoder: Encoder[ErgoLikeTransaction] = Encoder.instance({ tx =>
Json.obj(
"type" -> "ELT".asJson, // ErgoLikeTransaction
"id" -> tx.id.asJson,
"inputs" -> tx.inputs.asJson,
"dataInputs" -> tx.dataInputs.asJson,
"outputs" -> tx.outputs.asJson
"outputs" -> tx.outputCandidates.asJson
)
})

implicit val ergoLikeTransactionDecoder: Decoder[ErgoLikeTransaction] = Decoder.instance({ implicit cursor =>
for {
t <- cursor.downField("type").as[String]
inputs <- {require(t == "ELT"); cursor.downField("inputs").as[IndexedSeq[Input]] }
dataInputs <- cursor.downField("dataInputs").as[IndexedSeq[DataInput]]
outputs <- cursor.downField("outputs").as[IndexedSeq[ErgoBoxCandidate]]
} yield new ErgoLikeTransaction(inputs, dataInputs, outputs)
})

implicit val unsignedErgoLikeTransactionEncoder: Encoder[UnsignedErgoLikeTransaction] = Encoder.instance({ tx =>
Json.obj(
"type" -> "UELT".asJson, // UnsignedErgoLikeTransaction
"id" -> tx.id.asJson,
"inputs" -> tx.inputs.asJson,
"dataInputs" -> tx.dataInputs.asJson,
"outputs" -> tx.outputs.asJson
"outputs" -> tx.outputCandidates.asJson
)
})

implicit val unsignedErgoLikeTransactionDecoder: Decoder[UnsignedErgoLikeTransaction] = Decoder.instance({ implicit cursor =>
for {
t <- cursor.downField("type").as[String]
inputs <- {require(t == "UELT"); cursor.downField("inputs").as[IndexedSeq[UnsignedInput]] }
dataInputs <- cursor.downField("dataInputs").as[IndexedSeq[DataInput]]
outputs <- cursor.downField("outputs").as[IndexedSeq[ErgoBoxCandidate]]
} yield new UnsignedErgoLikeTransaction(inputs, dataInputs, outputs)
})

implicit def ergoLikeTransactionTemplateEncoder[T <: UnsignedInput]: Encoder[ErgoLikeTransactionTemplate[T]] = Encoder.instance({
case transaction: ErgoLikeTransaction => ergoLikeTransactionEncoder(transaction)
case transaction: UnsignedErgoLikeTransaction => unsignedErgoLikeTransactionEncoder(transaction)
case t => throw new SigmaException(s"Don't know how to encode transaction $t")
})

implicit val transactionOutputsDecoder: Decoder[(ErgoBoxCandidate, Option[BoxId])] = Decoder.instance({ cursor =>
implicit val ergoLikeTransactionTemplateDecoder: Decoder[ErgoLikeTransactionTemplate[_ <: UnsignedInput]] = Decoder.instance({ implicit cursor =>
for {
maybeId <- cursor.downField("boxId").as[Option[BoxId]]
value <- cursor.downField("value").as[Long]
creationHeight <- cursor.downField("creationHeight").as[Int]
ergoTree <- cursor.downField("ergoTree").as[ErgoTree]
assets <- cursor.downField("assets").as[Seq[(ErgoBox.TokenId, Long)]] // TODO optimize: encode directly into Coll avoiding allocation of Tuple2 for each element
registers <- cursor.downField("additionalRegisters").as[Map[NonMandatoryRegisterId, EvaluatedValue[SType]]]
} yield (new ErgoBoxCandidate(value, ergoTree, creationHeight, assets.toColl, registers), maybeId)
t <- cursor.downField("type").as[String]
tx <- t match {
case "ELT" => ergoLikeTransactionDecoder(cursor)
case "UELT" => unsignedErgoLikeTransactionDecoder(cursor)
}
} yield tx
})

implicit val ergoLikeTransactionDecoder: Decoder[ErgoLikeTransaction] = Decoder.instance({ implicit cursor =>
for {
inputs <- cursor.downField("inputs").as[IndexedSeq[Input]]
dataInputs <- cursor.downField("dataInputs").as[IndexedSeq[DataInput]]
outputsWithIndex <- cursor.downField("outputs").as[IndexedSeq[(ErgoBoxCandidate, Option[BoxId])]]
} yield new ErgoLikeTransaction(inputs, dataInputs, outputsWithIndex.map(_._1))
})

implicit val unsignedErgoLikeTransactionDecoder: Decoder[UnsignedErgoLikeTransaction] = Decoder.instance({ implicit cursor =>
for {
inputs <- cursor.downField("inputs").as[IndexedSeq[UnsignedInput]]
dataInputs <- cursor.downField("dataInputs").as[IndexedSeq[DataInput]]
outputsWithIndex <- cursor.downField("outputs").as[IndexedSeq[(ErgoBoxCandidate, Option[BoxId])]]
} yield new UnsignedErgoLikeTransaction(inputs, dataInputs, outputsWithIndex.map(_._1))
})

implicit val ergoLikeTransactionTemplateDecoder: Decoder[ErgoLikeTransactionTemplate[_ <: UnsignedInput]] = {
ergoLikeTransactionDecoder.asInstanceOf[Decoder[ErgoLikeTransactionTemplate[_ <: UnsignedInput]]] or
unsignedErgoLikeTransactionDecoder.asInstanceOf[Decoder[ErgoLikeTransactionTemplate[_ <: UnsignedInput]]]
}

implicit val sigmaValidationSettingsEncoder: Encoder[SigmaValidationSettings] = Encoder.instance({ v =>
SigmaValidationSettingsSerializer.toBytes(v).asJson
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,9 @@ import sigmastate.serialization.SerializationSpecification
import sigmastate.utils.Helpers.DecoderResultOps // required for Scala 2.11 (extension method toTry)
import special.collection.Coll
import special.sigma.{Header, PreHeader}
import org.ergoplatform.ErgoLikeContext
import org.ergoplatform.DataInput
import org.ergoplatform.Input
import org.ergoplatform.UnsignedInput
import org.ergoplatform.ErgoBox
import org.ergoplatform.ErgoLikeTransaction
import org.ergoplatform.UnsignedErgoLikeTransaction
import org.ergoplatform.ErgoLikeTransactionTemplate
import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, ErgoLikeContext, ErgoLikeTransaction, ErgoLikeTransactionTemplate, Input, UnsignedErgoLikeTransaction, UnsignedInput}

import scala.collection.mutable

class JsonSerializationSpec extends SerializationSpecification with JsonCodecs {

Expand Down Expand Up @@ -84,43 +79,49 @@ class JsonSerializationSpec extends SerializationSpecification with JsonCodecs {
}

property("Input should be encoded into JSON and decoded back correctly") {
forAll(inputGen) { v: Input => jsonRoundTrip(v) }
forAll(inputGen, MinSuccessful(50)) { v: Input => jsonRoundTrip(v) }
}

property("UnsignedInput should be encoded into JSON and decoded back correctly") {
forAll(unsignedInputGen) { v: UnsignedInput => jsonRoundTrip(v) }
forAll(unsignedInputGen, MinSuccessful(50)) { v: UnsignedInput => jsonRoundTrip(v) }
}

property("ContextExtension should be encoded into JSON and decoded back correctly") {
forAll(contextExtensionGen) { v: ContextExtension => jsonRoundTrip(v) }
forAll(contextExtensionGen, MinSuccessful(500)) { v: ContextExtension => jsonRoundTrip(v) }
}

property("AdditionalRegisters should be encoded into JSON and decoded back correctly") {
forAll(additionalRegistersGen, MinSuccessful(500)) { regs =>
jsonRoundTrip(regs)(registersEncoder, registersDecoder)
}
}

property("ProverResult should be encoded into JSON and decoded back correctly") {
forAll(serializedProverResultGen) { v: ProverResult => jsonRoundTrip(v) }
forAll(serializedProverResultGen, MinSuccessful(500)) { v: ProverResult => jsonRoundTrip(v) }
}

property("AvlTreeData should be encoded into JSON and decoded back correctly") {
forAll(avlTreeDataGen) { v: AvlTreeData => jsonRoundTrip(v) }
forAll(avlTreeDataGen, MinSuccessful(500)) { v: AvlTreeData => jsonRoundTrip(v) }
}

property("ErgoTree should be encoded into JSON and decoded back correctly") {
forAll(ergoTreeGen) { v: ErgoTree => jsonRoundTrip(v) }
forAll(ergoTreeGen, MinSuccessful(500)) { v: ErgoTree => jsonRoundTrip(v) }
}

property("ErgoBox should be encoded into JSON and decoded back correctly") {
forAll(ergoBoxGen) { v: ErgoBox => jsonRoundTrip(v) }
forAll(ergoBoxGen, MinSuccessful(500)) { v: ErgoBox => jsonRoundTrip(v) }
}

property("ErgoLikeTransaction should be encoded into JSON and decoded back correctly") {
forAll(ergoLikeTransactionGen) { v: ErgoLikeTransaction => jsonRoundTrip(v) }
forAll(ergoLikeTransactionGen, MinSuccessful(50)) { v: ErgoLikeTransaction => jsonRoundTrip(v) }
}

property("UnsignedErgoLikeTransaction should be encoded into JSON and decoded back correctly") {
forAll(unsignedErgoLikeTransactionGen) { v: UnsignedErgoLikeTransaction => jsonRoundTrip(v) }
forAll(unsignedErgoLikeTransactionGen, MinSuccessful(50)) { v: UnsignedErgoLikeTransaction => jsonRoundTrip(v) }
}

property("ErgoLikeTransactionTemplate should be encoded into JSON and decoded back correctly") {
forAll(ergoLikeTransactionTemplateGen) { v: ErgoLikeTransactionTemplate[_ <: UnsignedInput] =>
forAll(ergoLikeTransactionTemplateGen, MinSuccessful(50)) { v: ErgoLikeTransactionTemplate[_ <: UnsignedInput] =>
v.asJson.as(ergoLikeTransactionTemplateDecoder).toTry.get shouldEqual v
}
}
Expand All @@ -136,7 +137,7 @@ class JsonSerializationSpec extends SerializationSpecification with JsonCodecs {
CryptoConstants.dlogGroup.ctx.decodePoint(point).asInstanceOf[CryptoConstants.EcPointType]
)
}.get
val regs = Map(
val regs = scala.collection.Map(
R7 -> LongArrayConstant(Array(1L, 2L, 1234123L)),
R4 -> ByteConstant(1),
R6 -> IntConstant(10),
Expand Down

0 comments on commit fc6fdb0

Please sign in to comment.