Skip to content

Commit

Permalink
Merge pull request #900 from ScorexFoundation/tx-signing-js
Browse files Browse the repository at this point in the history
Reduction and signing of Fleet transactions
  • Loading branch information
aslesarenko authored Aug 28, 2023
2 parents 2b72f2f + ce203cc commit 98c6f06
Show file tree
Hide file tree
Showing 60 changed files with 1,351 additions and 414 deletions.
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ lazy val commonSettings = Seq(
scalacOptions ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, 13)) =>
Seq("-Ywarn-unused:_,imports", "-Ywarn-unused:imports", "-release", "8")
Seq("-Ywarn-unused:_,imports", "-Ywarn-unused:imports", "-Wconf:src=src_managed/.*:silent", "-release", "8")
case Some((2, 12)) =>
Seq("-Ywarn-unused:_,imports", "-Ywarn-unused:imports", "-release", "8")
case Some((2, 11)) =>
Expand Down Expand Up @@ -282,7 +282,7 @@ lazy val interpreterJS = interpreter.js
},
Compile / npmDependencies ++= Seq(
"sigmajs-crypto-facade" -> sigmajsCryptoFacadeVersion,
"@fleet-sdk/common" -> "0.1.0-alpha.14"
"@fleet-sdk/common" -> "0.1.3"
)
)

Expand Down
3 changes: 3 additions & 0 deletions common/shared/src/main/scala/scalan/util/CollectionUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ object CollectionUtil {
}

implicit class AnyOps[A](val x: A) extends AnyVal {
/** Performs a specified action on the source value and returns the result. */
def perform(action: A => A): A = action(x)

/** Traverses the tree structure in a depth-first manner using the provided function to generate child nodes.
*
* @param f a function that takes a node of type A and returns a list of its children
Expand Down
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])
}
16 changes: 5 additions & 11 deletions core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,7 @@ trait BigInt {
def |(that: BigInt): BigInt = or(that)
}

/** Base class for points on elliptic curves.
*/
/** Base class for points on elliptic curves. */
trait GroupElement {
/** Checks if the provided element is an identity element. */
def isIdentity: Boolean
Expand Down Expand Up @@ -381,7 +380,6 @@ trait Box {
trait AvlTree {
/** Returns digest of the state represented by this tree.
* Authenticated tree digest = root hash bytes ++ tree height
* @since 2.0
*/
def digest: Coll[Byte]

Expand Down Expand Up @@ -529,10 +527,8 @@ trait AvlTreeVerifier {
}


/** Only header fields that can be predicted by a miner.
* @since 2.0
*/
trait PreHeader { // Testnet2
/** Only header fields that can be predicted by a miner. */
trait PreHeader {
/** Block version, to be increased on every soft and hardfork. */
def version: Byte

Expand All @@ -556,9 +552,7 @@ trait PreHeader { // Testnet2
def votes: Coll[Byte]
}

/** Represents data of the block header available in Sigma propositions.
* @since 2.0
*/
/** Represents data of the block header available in Sigma propositions. */
trait Header {
/** Bytes representation of ModifierId of this Header */
def id: Coll[Byte]
Expand All @@ -572,7 +566,7 @@ trait Header {
/** Hash of ADProofs for transactions in a block */
def ADProofsRoot: Coll[Byte] // Digest32. Can we build AvlTree out of it?

/** AvlTree) of a state after block application */
/** AvlTree of a state after block application */
def stateRoot: AvlTree

/** Root hash (for a Merkle tree) of transactions in a block. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,12 @@ object Platform {
override def infinity(): crypto.Ecp =
new Ecp(ctx.getInfinity())

override def decodePoint(encoded: Array[Byte]): crypto.Ecp =
override def decodePoint(encoded: Array[Byte]): crypto.Ecp = {
if (encoded(0) == 0) {
return infinity()
}
new Ecp(ctx.decodePoint(Base16.encode(encoded)))
}

override def generator: crypto.Ecp =
new Ecp(ctx.getGenerator())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package sigmastate.crypto

import org.scalatest.matchers.should.Matchers
import org.scalatest.propspec.AnyPropSpec
import scorex.util.encode.Base16

import scala.scalajs.js
import scala.scalajs.js.typedarray.Uint8Array
Expand Down
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 @@ -200,11 +200,11 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData,
lastBlockUtxoRoot, headers, preHeader, dataBoxes, boxesToSpend, spendingTransaction,
selfIndex, extension, validationSettings, costLimit, initCost,
activatedScriptVersion)
var hashCode = 0
var h = 0
cfor(0)(_ < state.length, _ + 1) { i =>
hashCode = 31 * hashCode + state(i).hashCode
h = 31 * h + state(i).hashCode
}
hashCode
h
}

override def toString = s"ErgoLikeContext(lastBlockUtxoRoot=$lastBlockUtxoRoot, headers=$headers, preHeader=$preHeader, dataBoxes=$dataBoxes, boxesToSpend=$boxesToSpend, spendingTransaction=$spendingTransaction, selfIndex=$selfIndex, extension=$extension, validationSettings=$validationSettings, costLimit=$costLimit, initCost=$initCost, activatedScriptVersion=$activatedScriptVersion)"
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 @@ -48,7 +48,7 @@ abstract class SigmaValidationSettings extends Iterable[(Short, (ValidationRule,
def isSoftFork(ruleId: Short, ve: ValidationException): Boolean = {
val infoOpt = get(ruleId)
infoOpt match {
case Some((_, ReplacedRule(newRuleId))) => true
case Some((_, ReplacedRule(_))) => true
case Some((rule, status)) => rule.isSoftFork(this, rule.id, status, ve.args)
case None => false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package org.ergoplatform.validation

import sigmastate.serialization.SigmaSerializer
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
import scalan.util.Extensions.{IntOps,LongOps}
import scalan.util.Extensions.{IntOps, LongOps}
import sigmastate.exceptions.SerializerException

// TODO v5.x: remove unused class and related json encoders
/** The rules are serialized ordered by ruleId.
Expand All @@ -13,9 +14,9 @@ object SigmaValidationSettingsSerializer extends SigmaSerializer[SigmaValidation
override def serialize(settings: SigmaValidationSettings, w: SigmaByteWriter): Unit = {
val rules = settings.toArray.sortBy(_._1)
w.putUInt(rules.length)
rules.foreach { r =>
w.putUShort(r._1)
RuleStatusSerializer.serialize(r._2._2, w)
rules.foreach { case (id, (_, status)) =>
w.putUShort(id)
RuleStatusSerializer.serialize(status, w)
}
}

Expand All @@ -27,10 +28,12 @@ object SigmaValidationSettingsSerializer extends SigmaSerializer[SigmaValidation
val status = RuleStatusSerializer.parse(r)
ruleId -> status
}
val initVs = ValidationRules.currentSettings
val res = parsed
.filter(pair => initVs.get(pair._1).isDefined)
.foldLeft(initVs) { (vs, rule) => vs.updated(rule._1, rule._2) }
val map = parsed.map { case (id, status) =>
val (rule, _) = ValidationRules.currentSettings.get(id)
.getOrElse(throw SerializerException(s"Rule with id $id is not registered"))
id -> (rule, status)
}.toMap
val res = new MapSigmaValidationSettings(map)
res
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package org.ergoplatform.validation

import scalan.util.Extensions.toUByte
import sigmastate.Values.{SValue, ErgoTree}
import sigmastate.Values.{ErgoTree, SValue}
import sigmastate._
import sigmastate.exceptions.{InvalidOpCode, SerializerException, ReaderPositionLimitExceeded, SigmaException, InterpreterException}
import sigmastate.exceptions.{InterpreterException, InvalidOpCode, ReaderPositionLimitExceeded, SerializerException, SigmaException}
import sigmastate.serialization.OpCodes.OpCode
import sigmastate.serialization.TypeSerializer.embeddableIdToType
import sigmastate.serialization.{OpCodes, ValueSerializer}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import debox.{cfor, Buffer => DBuffer}
import org.ergoplatform.ErgoBox
import org.ergoplatform.ErgoBox.TokenId
import scalan.{Nullable, RType}
import scorex.util.encode.Base16
import sigmastate.SType.AnyOps
import sigmastate.Values.{Constant, ConstantNode}
import sigmastate.crypto.{CryptoFacade, Ecp}
Expand Down Expand Up @@ -41,6 +42,8 @@ object Extensions {
implicit class ArrayByteOps(val arr: Array[Byte]) extends AnyVal {
/** Wraps array into TokenId instance. The source array in not cloned. */
@inline def toTokenId: TokenId = Digest32Coll @@ Colls.fromArray(arr)
/** Encodes array into hex string */
@inline def toHex: String = Base16.encode(arr)
}

implicit class EvalIterableOps[T: RType](seq: Iterable[T]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
import special.sigma
import special.sigma.AnyValue

import scala.collection.mutable

/**
* User-defined variables to be put into context.
* Each variable is identified by `id: Byte` and can be accessed from a script
Expand All @@ -20,7 +22,7 @@ import special.sigma.AnyValue
*
* @param values internal container of the key-value pairs
*/
case class ContextExtension(values: Map[Byte, EvaluatedValue[_ <: SType]]) {
case class ContextExtension(values: scala.collection.Map[Byte, EvaluatedValue[_ <: SType]]) {
def add(bindings: VarBinding*): ContextExtension =
ContextExtension(values ++ bindings)
}
Expand Down Expand Up @@ -49,8 +51,7 @@ object ContextExtension {
error(s"Negative amount of context extension values: $extSize")
val ext = (0 until extSize)
.map(_ => (r.getByte(), r.getValue().asInstanceOf[EvaluatedValue[_ <: SType]]))
.toMap[Byte, EvaluatedValue[_ <: SType]]
ContextExtension(ext)
ContextExtension(mutable.LinkedHashMap(ext:_*))
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import java.math.BigInteger

class CryptoFacadeSpecification extends AnyPropSpec with Matchers with ScalaCheckPropertyChecks {

val G_hex = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"

property("CryptoFacade.HashHmacSHA512") {
val cases = Table(
("string", "hash"),
Expand Down Expand Up @@ -54,21 +56,37 @@ class CryptoFacadeSpecification extends AnyPropSpec with Matchers with ScalaChec
}
}

property("CryptoFacade.encodePoint") {
property("CryptoFacade.getASN1Encoding") {
val ctx = CryptoFacade.createCryptoContext()
val G = ctx.generator
val Q = ctx.order
val vectors = Table(
("point", "expectedHex"),
(ctx.infinity(), "00"),
(CryptoFacade.exponentiatePoint(G, Q), "00"),
(G, "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),
(CryptoFacade.exponentiatePoint(G, BigInteger.ONE), "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),
(G, G_hex),
(CryptoFacade.exponentiatePoint(G, BigInteger.ONE), G_hex),
(CryptoFacade.exponentiatePoint(G, Q.subtract(BigInteger.ONE)), "0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
)
forAll (vectors) { (point, expectedHex) =>
val res = ErgoAlgos.encode(CryptoFacade.getASN1Encoding(point, true))
res shouldBe expectedHex
}
}

property("CryptoContext.decodePoint") {
val ctx = CryptoFacade.createCryptoContext()

val inf = ctx.decodePoint(Array[Byte](0))
CryptoFacade.isInfinityPoint(inf) shouldBe true

val G = ctx.generator
ctx.decodePoint(ErgoAlgos.decode(G_hex).get) shouldBe G

val Q = ctx.order
val Q_minus_1 = Q.subtract(BigInteger.ONE)
val maxExp = CryptoFacade.exponentiatePoint(G, Q_minus_1)
val maxExpBytes = ErgoAlgos.decode("0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798").get
ctx.decodePoint(maxExpBytes) shouldBe maxExp
}
}
Loading

0 comments on commit 98c6f06

Please sign in to comment.