Skip to content

Commit

Permalink
Merge pull request #402 from ScorexFoundation/i401
Browse files Browse the repository at this point in the history
serialize/bytesToSign fix
  • Loading branch information
catena2w authored Feb 22, 2019
2 parents 3415fc6 + be578a0 commit a7ffc6a
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 78 deletions.
5 changes: 3 additions & 2 deletions src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
import sigmastate.utxo.CostTable.Cost
import scorex.util.Extensions._

import scala.collection.mutable.WrappedArray.ofByte
import scala.runtime.ScalaRunTime

class ErgoBoxCandidate(val value: Long,
Expand Down Expand Up @@ -73,15 +74,15 @@ object ErgoBoxCandidate {
object serializer extends SigmaSerializer[ErgoBoxCandidate, ErgoBoxCandidate] {

def serializeBodyWithIndexedDigests(obj: ErgoBoxCandidate,
digestsInTx: Option[Array[Digest32]],
digestsInTx: Option[Array[ofByte]],
w: SigmaByteWriter): Unit = {
w.putULong(obj.value)
w.putBytes(ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(obj.ergoTree))
w.putUInt(obj.creationHeight)
w.putUByte(obj.additionalTokens.size)
obj.additionalTokens.foreach { case (id, amount) =>
if (digestsInTx.isDefined) {
val digestIndex = digestsInTx.get.indexOf(id)
val digestIndex = digestsInTx.get.indexOf(new ofByte(id))
if (digestIndex == -1) sys.error(s"failed to find token id ($id) in tx's digest index")
w.putUInt(digestIndex)
} else {
Expand Down
28 changes: 9 additions & 19 deletions src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.ergoplatform

import java.nio.ByteBuffer

import org.ergoplatform.ErgoBox.TokenId
import scorex.crypto.authds.ADKey
import scorex.crypto.hash.{Blake2b256, Digest32}
Expand All @@ -14,7 +16,6 @@ import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
import scala.collection.mutable



trait ErgoBoxReader {
def byId(boxId: ADKey): Try[ErgoBox]
}
Expand Down Expand Up @@ -99,35 +100,22 @@ object ErgoLikeTransaction {

object sigmaSerializer extends SigmaSerializer[FlattenedTransaction, FlattenedTransaction] {

def bytesToSign(inputs: IndexedSeq[ADKey],
outputCandidates: IndexedSeq[ErgoBoxCandidate]): Array[Byte] = {
//todo: set initial capacity
def bytesToSign[IT <: UnsignedInput](tx: ErgoLikeTransactionTemplate[IT]): Array[Byte] = {
val emptyProofInputs = tx.inputs.map(i => new Input(i.boxId, ProverResult.empty))
val w = SigmaSerializer.startWriter()

w.putUShort(inputs.length)
inputs.foreach { i =>
w.putBytes(i)
}
w.putUShort(outputCandidates.length)
outputCandidates.foreach { c =>
ErgoBoxCandidate.serializer.serialize(c, w)
}

serialize(FlattenedTransaction(emptyProofInputs.toArray, tx.outputCandidates.toArray), w)
w.toBytes
}

def bytesToSign[IT <: UnsignedInput](tx: ErgoLikeTransactionTemplate[IT]): Array[Byte] =
bytesToSign(tx.inputs.map(_.boxId), tx.outputCandidates)

override def serialize(ftx: FlattenedTransaction, w: SigmaByteWriter): Unit = {
w.putUShort(ftx.inputs.length)
for (input <- ftx.inputs) {
Input.serializer.serialize(input, w)
}
val digests = ftx.outputCandidates.flatMap(_.additionalTokens.map(_._1)).distinct
val digests = ftx.outputCandidates.flatMap(_.additionalTokens.map(t => new mutable.WrappedArray.ofByte(t._1))).distinct
w.putUInt(digests.length)
digests.foreach { digest =>
w.putBytes(digest)
w.putBytes(digest.array)
}
w.putUShort(ftx.outputCandidates.length)
for (out <- ftx.outputCandidates) {
Expand Down Expand Up @@ -155,6 +143,7 @@ object ErgoLikeTransaction {
FlattenedTransaction(inputsBuilder.result(), outputCandidatesBuilder.result())
}
}

}

object serializer extends SigmaSerializer[ErgoLikeTransaction, ErgoLikeTransaction] {
Expand All @@ -165,4 +154,5 @@ object ErgoLikeTransaction {
override def parse(r: SigmaByteReader): ErgoLikeTransaction =
ErgoLikeTransaction(FlattenedTransaction.sigmaSerializer.parse(r))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class ProverResult(val proof: Array[Byte], val extension: ContextExtension) {
}

object ProverResult {
val empty: ProverResult = ProverResult(Array[Byte](), ContextExtension.empty)

def apply(proof: Array[Byte], extension: ContextExtension): ProverResult =
new ProverResult(proof, extension)
Expand Down
44 changes: 44 additions & 0 deletions src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.ergoplatform

import org.scalatest.prop.GeneratorDrivenPropertyChecks
import org.scalatest.{Matchers, PropSpec}
import sigmastate.helpers.SigmaTestingCommons
import sigmastate.serialization.SigmaSerializer
import sigmastate.serialization.generators.ValueGenerators

class ErgoLikeTransactionSpec extends PropSpec
with GeneratorDrivenPropertyChecks
with Matchers
with ValueGenerators
with SigmaTestingCommons {

property("ErgoLikeTransaction: Serializer round trip") {
forAll { t: ErgoLikeTransaction => roundTripTest(t)(ErgoLikeTransaction.serializer) }
forAll { t: ErgoLikeTransaction => roundTripTestWithPos(t)(ErgoLikeTransaction.serializer) }
}

property("ErgoLikeTransaction with same token in different outputs : Serializer round trip") {
forAll { txIn: ErgoLikeTransaction =>
whenever(txIn.outputCandidates.head.additionalTokens.nonEmpty) {
val out = txIn.outputCandidates.head
val outputs = (0 until 10).map { i =>
new ErgoBoxCandidate(out.value, out.ergoTree, i, out.additionalTokens, out.additionalRegisters)
}
val tx = ErgoLikeTransaction(txIn.inputs, txIn.outputCandidates ++ outputs)
roundTripTestWithPos(tx)(ErgoLikeTransaction.serializer)

// check that token id is written only once
val w = SigmaSerializer.startWriter()
ErgoLikeTransaction.serializer.serialize(tx, w)
val bytes = w.toBytes

tx.outputCandidates.flatMap(_.additionalTokens).foreach { token =>
bytes.indexOfSlice(token._1) should not be -1
bytes.indexOfSlice(token._1) shouldBe bytes.lastIndexOfSlice(token._1)
}
}
}
}


}
72 changes: 49 additions & 23 deletions src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
package sigmastate.helpers

import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix
import org.ergoplatform.{ErgoLikeContext, ErgoAddressEncoder, ErgoBox, ErgoScriptPredef}
import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, TokenId}
import org.ergoplatform.ErgoScriptPredef.TrueProp
import org.scalatest.prop.{PropertyChecks, GeneratorDrivenPropertyChecks}
import org.scalatest.{PropSpec, Matchers}
import org.ergoplatform.{ErgoBox, ErgoLikeContext}
import org.scalacheck.Arbitrary.arbByte
import org.scalacheck.Gen
import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks}
import org.scalatest.{Assertion, Matchers, PropSpec}
import scalan.{Nullable, RType, TestContexts, TestUtils}
import scorex.crypto.hash.Blake2b256
import scorex.util._
import sigmastate.Values.{Constant, EvaluatedValue, SValue, TrueLeaf, Value, ErgoTree, GroupElementConstant}
import sigmastate.eval.{CompiletimeCosting, IRContext, Evaluation}
import scorex.util.serialization.{VLQByteStringReader, VLQByteStringWriter}
import sigma.types.{IsPrimView, PrimViewType, View}
import sigmastate.Values.{Constant, ErgoTree, EvaluatedValue, GroupElementConstant, SValue, Value}
import sigmastate.eval.{CompiletimeCosting, Evaluation, IRContext}
import sigmastate.interpreter.Interpreter.{ScriptEnv, ScriptNameProp}
import sigmastate.interpreter.{CryptoConstants, Interpreter}
import sigmastate.interpreter.Interpreter.{ScriptNameProp, ScriptEnv}
import sigmastate.lang.{TransformingSigmaBuilder, SigmaCompiler}
import sigmastate.{SGroupElement, SBoolean, SType}
import sigmastate.lang.{SigmaCompiler, TransformingSigmaBuilder}
import sigmastate.serialization.SigmaSerializer
import sigmastate.{SGroupElement, SType}
import spire.util.Opt

import scala.annotation.tailrec
import scala.language.implicitConversions
import scalan.{TestUtils, TestContexts, Nullable, RType}
import sigma.types.{PrimViewType, IsPrimView, View}
import spire.util.Opt

trait SigmaTestingCommons extends PropSpec
with PropertyChecks
Expand Down Expand Up @@ -54,12 +57,12 @@ trait SigmaTestingCommons extends PropSpec
proposition: ErgoTree,
additionalTokens: Seq[(TokenId, Long)] = Seq(),
additionalRegisters: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] = Map())
= ErgoBox(value, proposition, 0, additionalTokens, additionalRegisters)
= ErgoBox(value, proposition, 0, additionalTokens, additionalRegisters)

def createBox(value: Int,
proposition: ErgoTree,
creationHeight: Int)
= ErgoBox(value, proposition, creationHeight, Seq(), Map(), ErgoBox.allZerosModifierId)
= ErgoBox(value, proposition, creationHeight, Seq(), Map(), ErgoBox.allZerosModifierId)

class TestingIRContext extends TestContext with IRContext with CompiletimeCosting {
override def onCostingResult[T](env: ScriptEnv, tree: SValue, res: CostingResult[T]): Unit = {
Expand All @@ -71,17 +74,17 @@ trait SigmaTestingCommons extends PropSpec
}
}

def func[A:RType,B:RType](func: String)(implicit IR: IRContext): A => B = {
def func[A: RType, B: RType](func: String)(implicit IR: IRContext): A => B = {
val tA = RType[A]
val tB = RType[B]
val tpeA = Evaluation.rtypeToSType(tA)
val tpeB = Evaluation.rtypeToSType(tB)
val code =
s"""{
| val func = $func
| val res = func(getVar[${tA.name}](1).get)
| res
|}
| val func = $func
| val res = func(getVar[${tA.name}](1).get)
| res
|}
""".stripMargin
val env = Interpreter.emptyEnv
val interProp = compiler.typecheck(env, code)
Expand All @@ -95,18 +98,18 @@ trait SigmaTestingCommons extends PropSpec
case _ => in
}
val context = ErgoLikeContext.dummy(createBox(0, TrueProp))
.withBindings(1.toByte -> Constant[SType](x.asInstanceOf[SType#WrappedType], tpeA))
.withBindings(1.toByte -> Constant[SType](x.asInstanceOf[SType#WrappedType], tpeA))
val calcCtx = context.toSigmaContext(IR, isCost = false)
val res = valueFun(calcCtx)
(TransformingSigmaBuilder.unliftAny(res) match {
case Nullable(x) => // x is a value extracted from Constant
tB match {
case _: PrimViewType[_,_] => // need to wrap value into PrimValue
case _: PrimViewType[_, _] => // need to wrap value into PrimValue
View.mkPrimView(x) match {
case Opt(pv) => pv
case _ => x // cannot wrap, so just return as is
case _ => x // cannot wrap, so just return as is
}
case _ => x // don't need to wrap
case _ => x // don't need to wrap
}
case _ => res
}).asInstanceOf[B]
Expand All @@ -129,4 +132,27 @@ trait SigmaTestingCommons extends PropSpec
final def rootCause(t: Throwable): Throwable =
if (t.getCause == null) t
else rootCause(t.getCause)

protected def roundTripTest[T](v: T)(implicit serializer: SigmaSerializer[T, T]): Assertion = {
// using default sigma reader/writer
val bytes = serializer.toBytes(v)
bytes.nonEmpty shouldBe true
serializer.parse(SigmaSerializer.startReader(bytes)) shouldBe v

// using ergo's(scorex) reader/writer
val w = new VLQByteStringWriter()
serializer.serializeWithGenericWriter(v, w)
val byteStr = w.result()
byteStr.nonEmpty shouldBe true
serializer.parseWithGenericReader(new VLQByteStringReader(byteStr)) shouldEqual v
}

protected def roundTripTestWithPos[T](v: T)(implicit serializer: SigmaSerializer[T, T]): Assertion = {
val randomBytesCount = Gen.chooseNum(1, 20).sample.get
val randomBytes = Gen.listOfN(randomBytesCount, arbByte.arbitrary).sample.get.toArray
val bytes = serializer.toBytes(v)
serializer.parse(SigmaSerializer.startReader(bytes)) shouldBe v
serializer.parse(SigmaSerializer.startReader(randomBytes ++ bytes, randomBytesCount)) shouldBe v
}

}
37 changes: 4 additions & 33 deletions src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala
Original file line number Diff line number Diff line change
@@ -1,41 +1,17 @@
package sigmastate.utxo

import org.ergoplatform.{ErgoBoxCandidate, ErgoLikeTransaction, _}
import org.scalacheck.Arbitrary._
import org.scalacheck.Gen
import org.scalatest.prop.GeneratorDrivenPropertyChecks
import org.scalatest.{Assertion, Matchers, PropSpec}
import scorex.util.serialization._
import org.scalatest.{Matchers, PropSpec}
import sigmastate.helpers.SigmaTestingCommons
import sigmastate.interpreter.{ContextExtension, ProverResult}
import sigmastate.serialization.SigmaSerializer
import sigmastate.serialization.generators.ValueGenerators

class SerializationRoundTripSpec extends PropSpec
with GeneratorDrivenPropertyChecks
with Matchers
with ValueGenerators {

private def roundTripTest[T](v: T)(implicit serializer: SigmaSerializer[T, T]): Assertion = {
// using default sigma reader/writer
val bytes = serializer.toBytes(v)
bytes.nonEmpty shouldBe true
serializer.parse(SigmaSerializer.startReader(bytes)) shouldBe v

// using ergo's(scorex) reader/writer
val w = new VLQByteStringWriter()
serializer.serializeWithGenericWriter(v, w)
val byteStr = w.result()
byteStr.nonEmpty shouldBe true
serializer.parseWithGenericReader(new VLQByteStringReader(byteStr)) shouldEqual v
}

private def roundTripTestWithPos[T](v: T)(implicit serializer: SigmaSerializer[T, T]): Assertion = {
val randomBytesCount = Gen.chooseNum(1, 20).sample.get
val randomBytes = Gen.listOfN(randomBytesCount, arbByte.arbitrary).sample.get.toArray
val bytes = serializer.toBytes(v)
serializer.parse(SigmaSerializer.startReader(bytes)) shouldBe v
serializer.parse(SigmaSerializer.startReader(randomBytes ++ bytes, randomBytesCount)) shouldBe v
}
with ValueGenerators
with SigmaTestingCommons {

property("ErgoBoxCandidate: Serializer round trip") {
forAll { t: ErgoBoxCandidate => roundTripTest(t)(ErgoBoxCandidate.serializer) }
Expand All @@ -47,11 +23,6 @@ class SerializationRoundTripSpec extends PropSpec
forAll { t: ErgoBox => roundTripTestWithPos(t)(ErgoBox.sigmaSerializer) }
}

property("ErgoLikeTransaction: Serializer round trip") {
forAll { t: ErgoLikeTransaction => roundTripTest(t)(ErgoLikeTransaction.serializer) }
forAll { t: ErgoLikeTransaction => roundTripTestWithPos(t)(ErgoLikeTransaction.serializer) }
}

property("ContextExtension: Serializer round trip") {
forAll { t: ContextExtension => roundTripTest(t)(ContextExtension.serializer) }
forAll { t: ContextExtension => roundTripTestWithPos(t)(ContextExtension.serializer) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ class OracleExamplesSpecification extends SigmaTestingCommons { suite =>

lazy val prop = proposition("buyer", { ctx: Context =>
import ctx._
val okInputs = INPUTS.size == 3
val okInputs = INPUTS.length == 3
val okInput0 = INPUTS(0).propositionBytes == pkOracle.propBytes
val inReg = INPUTS(0).R4[Long].get
val okContractLogic = (inReg > 15L && pkA) || (inReg <= 15L && pkB)
Expand Down

0 comments on commit a7ffc6a

Please sign in to comment.