Skip to content

Commit

Permalink
Merge pull request #408 from ScorexFoundation/i405
Browse files Browse the repository at this point in the history
Add context under signature
  • Loading branch information
catena2w authored Feb 27, 2019
2 parents a7ffc6a + 14628c6 commit 2e5b285
Show file tree
Hide file tree
Showing 54 changed files with 928 additions and 764 deletions.
11 changes: 5 additions & 6 deletions src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import sigmastate.lang.Terms._
import sigmastate.serialization.{ErgoTreeSerializer, SigmaSerializer}
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
import sigmastate.utxo.CostTable.Cost
import scorex.util.Extensions._

import scala.collection.mutable.WrappedArray.ofByte
import scala.runtime.ScalaRunTime
Expand Down Expand Up @@ -74,17 +73,17 @@ object ErgoBoxCandidate {
object serializer extends SigmaSerializer[ErgoBoxCandidate, ErgoBoxCandidate] {

def serializeBodyWithIndexedDigests(obj: ErgoBoxCandidate,
digestsInTx: Option[Array[ofByte]],
tokensInTx: 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(new ofByte(id))
if (digestIndex == -1) sys.error(s"failed to find token id ($id) in tx's digest index")
w.putUInt(digestIndex)
if (tokensInTx.isDefined) {
val tokenIndex = tokensInTx.get.indexOf(new ofByte(id))
if (tokenIndex == -1) sys.error(s"failed to find token id ($id) in tx's digest index")
w.putUInt(tokenIndex)
} else {
w.putBytes(id)
}
Expand Down
156 changes: 79 additions & 77 deletions src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
package org.ergoplatform

import java.nio.ByteBuffer

import org.ergoplatform.ErgoBox.TokenId
import scorex.crypto.authds.ADKey
import scorex.crypto.hash.{Blake2b256, Digest32}
import scorex.util._
import scorex.util.serialization.{Reader, Serializer, Writer}
import sigmastate.interpreter.ProverResult
import sigmastate.serialization.SigmaSerializer

import scala.util.Try
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}

import scala.collection.mutable
import scala.util.Try


trait ErgoBoxReader {
def byId(boxId: ADKey): Try[ErgoBox]
}


/**
* Base trait of a real transaction to be used in Ergo network.
* May be in unsigned (`UnsignedErgoLikeTransaction`) or in signed (`ErgoLikeTransaction`) version.
*
* Consists of:
*
* @param inputs - inputs, that will be spent by this transaction.
* TODO @param dataInputs - inputs, that are not going to be spent by transaction, but will be
* reachable from inputs scripts. `dataInputs` scripts will not be executed,
* thus their scripts costs are not included in transaction cost and
* they do not contain spending proofs.
* @param outputCandidates - box candidates to be created by this transaction.
* Differ from ordinary ones in that they do not include transaction id and index
*/
trait ErgoLikeTransactionTemplate[IT <: UnsignedInput] {
val inputs: IndexedSeq[IT]
val outputCandidates: IndexedSeq[ErgoBoxCandidate]
Expand All @@ -32,13 +41,15 @@ trait ErgoLikeTransactionTemplate[IT <: UnsignedInput] {
lazy val outputs: IndexedSeq[ErgoBox] =
outputCandidates.indices.map(idx => outputCandidates(idx).toBox(id, idx.toShort))

lazy val messageToSign: Array[Byte] =
ErgoLikeTransaction.FlattenedTransaction.sigmaSerializer.bytesToSign(this)
lazy val messageToSign: Array[Byte] = ErgoLikeTransaction.bytesToSign(this)

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


/**
* Unsigned version of `ErgoLikeTransactionTemplate`
*/
class UnsignedErgoLikeTransaction(override val inputs: IndexedSeq[UnsignedInput],
override val outputCandidates: IndexedSeq[ErgoBoxCandidate])
extends ErgoLikeTransactionTemplate[UnsignedInput] {
Expand All @@ -58,10 +69,7 @@ object UnsignedErgoLikeTransaction {
}

/**
* Fully signed transaction
*
* @param inputs
* @param outputCandidates
* Signed version of `ErgoLikeTransactionTemplate`
*/
class ErgoLikeTransaction(override val inputs: IndexedSeq[Input],
override val outputCandidates: IndexedSeq[ErgoBoxCandidate])
Expand All @@ -80,79 +88,73 @@ class ErgoLikeTransaction(override val inputs: IndexedSeq[Input],
override def hashCode(): Int = id.hashCode()
}

object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction, ErgoLikeTransaction] {

object ErgoLikeTransaction {

val TransactionIdBytesSize: Short = 32

def apply(inputs: IndexedSeq[Input], outputCandidates: IndexedSeq[ErgoBoxCandidate]) =
new ErgoLikeTransaction(inputs, outputCandidates)
override def serialize(tx: ErgoLikeTransaction, w: SigmaByteWriter): Unit = {
// serialize transaction inputs
w.putUShort(tx.inputs.length)
for (input <- tx.inputs) {
Input.serializer.serialize(input, w)
}
// serialize distinct ids of tokens in transaction outputs
val distinctTokenIds = tx.outputCandidates
.flatMap(_.additionalTokens.map(t => new mutable.WrappedArray.ofByte(t._1)))
.distinct
.toArray
w.putUInt(distinctTokenIds.length)
distinctTokenIds.foreach { tokenId =>
w.putBytes(tokenId.array)
}
// serialize outputs
w.putUShort(tx.outputCandidates.length)
for (out <- tx.outputCandidates) {
ErgoBoxCandidate.serializer.serializeBodyWithIndexedDigests(out, Some(distinctTokenIds), w)
}
}

def apply(ftx: FlattenedTransaction): ErgoLikeTransaction =
new ErgoLikeTransaction(ftx.inputs, ftx.outputCandidates)

case class FlattenedTransaction(inputs: Array[Input],
outputCandidates: Array[ErgoBoxCandidate])

object FlattenedTransaction {
def apply(tx: ErgoLikeTransaction): FlattenedTransaction =
FlattenedTransaction(tx.inputs.toArray, tx.outputCandidates.toArray)

object sigmaSerializer extends SigmaSerializer[FlattenedTransaction, FlattenedTransaction] {

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()
serialize(FlattenedTransaction(emptyProofInputs.toArray, tx.outputCandidates.toArray), w)
w.toBytes
}

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(t => new mutable.WrappedArray.ofByte(t._1))).distinct
w.putUInt(digests.length)
digests.foreach { digest =>
w.putBytes(digest.array)
}
w.putUShort(ftx.outputCandidates.length)
for (out <- ftx.outputCandidates) {
ErgoBoxCandidate.serializer.serializeBodyWithIndexedDigests(out, Some(digests), w)
}
}

override def parse(r: SigmaByteReader): FlattenedTransaction = {
val inputsCount = r.getUShort()
val inputsBuilder = mutable.ArrayBuilder.make[Input]()
for (_ <- 0 until inputsCount) {
inputsBuilder += Input.serializer.parse(r)
}
val digestsCount = r.getUInt().toInt
val digestsBuilder = mutable.ArrayBuilder.make[Digest32]()
for (_ <- 0 until digestsCount) {
digestsBuilder += Digest32 @@ r.getBytes(TokenId.size)
}
val digests = digestsBuilder.result()
val outsCount = r.getUShort()
val outputCandidatesBuilder = mutable.ArrayBuilder.make[ErgoBoxCandidate]()
for (_ <- 0 until outsCount) {
outputCandidatesBuilder += ErgoBoxCandidate.serializer.parseBodyWithIndexedDigests(Some(digests), r)
}
FlattenedTransaction(inputsBuilder.result(), outputCandidatesBuilder.result())
}
override def parse(r: SigmaByteReader): ErgoLikeTransaction = {
val inputsCount = r.getUShort()
val inputsBuilder = mutable.ArrayBuilder.make[Input]()
for (_ <- 0 until inputsCount) {
inputsBuilder += Input.serializer.parse(r)
}

val tokensCount = r.getUInt().toInt
val tokensBuilder = mutable.ArrayBuilder.make[Digest32]()
for (_ <- 0 until tokensCount) {
tokensBuilder += Digest32 @@ r.getBytes(TokenId.size)
}
val tokens = tokensBuilder.result()
val outsCount = r.getUShort()
val outputCandidatesBuilder = mutable.ArrayBuilder.make[ErgoBoxCandidate]()
for (_ <- 0 until outsCount) {
outputCandidatesBuilder += ErgoBoxCandidate.serializer.parseBodyWithIndexedDigests(Some(tokens), r)
}
ErgoLikeTransaction(inputsBuilder.result(), outputCandidatesBuilder.result())
}

object serializer extends SigmaSerializer[ErgoLikeTransaction, ErgoLikeTransaction] {
}


object ErgoLikeTransaction {

override def serialize(tx: ErgoLikeTransaction, w: SigmaByteWriter): Unit =
FlattenedTransaction.sigmaSerializer.serialize(FlattenedTransaction(tx), w)
val TransactionIdBytesSize: Short = 32

override def parse(r: SigmaByteReader): ErgoLikeTransaction =
ErgoLikeTransaction(FlattenedTransaction.sigmaSerializer.parse(r))
/**
* Bytes that should be signed by provers.
* Contains all the transaction bytes except of signatures itself
*/
def bytesToSign[IT <: UnsignedInput](tx: ErgoLikeTransactionTemplate[IT]): Array[Byte] = {
val emptyProofInputs = tx.inputs.map(_.inputToSign)
val w = SigmaSerializer.startWriter()
val txWithoutProofs = ErgoLikeTransaction(emptyProofInputs, tx.outputCandidates)
ErgoLikeTransactionSerializer.serialize(txWithoutProofs, w)
w.toBytes
}

def apply(inputs: IndexedSeq[Input], outputCandidates: IndexedSeq[ErgoBoxCandidate]) =
new ErgoLikeTransaction(inputs, outputCandidates)

val serializer: SigmaSerializer[ErgoLikeTransaction, ErgoLikeTransaction] = ErgoLikeTransactionSerializer

}
42 changes: 27 additions & 15 deletions src/main/scala/org/ergoplatform/Input.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,49 @@ import java.util

import org.ergoplatform.ErgoBox.BoxId
import scorex.crypto.authds.ADKey
import sigmastate.interpreter.ProverResult
import scorex.util.encode.Base16
import sigmastate.interpreter.{ContextExtension, ProverResult}
import sigmastate.serialization.SigmaSerializer
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}

/**
* Inputs of formed, but unsigned transaction
*
* @param boxId - id of a box to spent
* @param extension - user-defined variables to be put into context
*/
class UnsignedInput(val boxId: BoxId, val extension: ContextExtension) {

def this(boxId: BoxId) = this(boxId, ContextExtension.empty)

class UnsignedInput(val boxId: BoxId) {
require(boxId.size == BoxId.size, s"incorrect boxId size, expected: $BoxId.size, got: ${boxId.size}")

// todo check whether it is correct to compare inputs (Input use the same equals) by boxId only?
override def equals(obj: Any): Boolean = obj match {
case x: UnsignedInput => util.Arrays.equals(boxId, x.boxId)
case _ => false
}
}

object UnsignedInput {
object serializer extends SigmaSerializer[UnsignedInput, UnsignedInput] {

@inline
override def serialize(obj: UnsignedInput, w: SigmaByteWriter): Unit =
w.putBytes(obj.boxId)

@inline
override def parse(r: SigmaByteReader): UnsignedInput =
new UnsignedInput(ADKey @@ r.getBytes(BoxId.size))
}
/**
* Input, that should be signed by prover and verified by verifier.
* Contains all the input data except of signature itself.
*/
def inputToSign: Input = Input(boxId: BoxId, ProverResult(Array[Byte](), extension))
}

/**
* Fully signed transaction input
*
* @param boxId - id of a box to spent
* @param spendingProof - proof of spending correctness
*/
case class Input(override val boxId: BoxId, spendingProof: ProverResult)
extends UnsignedInput(boxId) {
extends UnsignedInput(boxId, spendingProof.extension) {
override def toString: String = s"Input(${Base16.encode(boxId)},$spendingProof)"
}

object Input {

object serializer extends SigmaSerializer[Input, Input] {

override def serialize(obj: Input, w: SigmaByteWriter): Unit = {
Expand All @@ -49,4 +60,5 @@ object Input {
Input(ADKey @@ boxId, spendingProof)
}
}

}
12 changes: 7 additions & 5 deletions src/main/scala/sigmastate/interpreter/Context.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import sigmastate.Values.EvaluatedValue
import sigmastate.eval.Evaluation
import sigmastate.serialization.SigmaSerializer
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
import scorex.util.Extensions._
import special.sigma
import special.sigma.AnyValue

/**
* Variables to be put into context
* User-defined variables to be put into context
*
* @param values - key-value pairs
*/
case class ContextExtension(values: Map[Byte, EvaluatedValue[_ <: SType]]) {
def add(bindings: (Byte, EvaluatedValue[_ <: SType])*): ContextExtension =
Expand All @@ -24,7 +25,7 @@ object ContextExtension {

override def serialize(obj: ContextExtension, w: SigmaByteWriter): Unit = {
w.putUByte(obj.values.size)
obj.values.foreach{ case (id, v) => w.put(id).putValue(v) }
obj.values.foreach { case (id, v) => w.put(id).putValue(v) }
}

override def parse(r: SigmaByteReader): ContextExtension = {
Expand All @@ -35,16 +36,17 @@ object ContextExtension {
ContextExtension(ext)
}
}

}


trait Context{
trait Context {
val extension: ContextExtension

def withExtension(newExtension: ContextExtension): Context

def withBindings(bindings: (Byte, EvaluatedValue[_ <: SType])*): Context = {
val ext = extension.add(bindings:_*)
val ext = extension.add(bindings: _*)
withExtension(ext)
}

Expand Down
Loading

0 comments on commit 2e5b285

Please sign in to comment.