From 2f8100ee385a2705908dbba45e8ea4094a53750d Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 10 Jul 2023 14:51:39 +0200 Subject: [PATCH 01/27] multisig: rename ErgoLikeParameters -> BlockchainParameters --- .../sdk/js/BlockchainParameters.scala | 48 +++++++------- .../ergoplatform/sdk/js/ProverBuilder.scala | 5 +- .../sdk/AppkitProvingInterpreter.scala | 13 ++-- .../sdk/BlockchainParameters.scala | 30 +++++++++ .../org/ergoplatform/sdk/ProverBuilder.scala | 3 +- .../sdk/ReducingInterpreter.scala | 4 +- .../protocol/context/ErgoLikeParameters.scala | 63 ------------------- 7 files changed, 63 insertions(+), 103 deletions(-) create mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainParameters.scala delete mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeParameters.scala diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala index 4f4cc87010..eb421e171b 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala @@ -1,34 +1,30 @@ package org.ergoplatform.sdk.js -import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters +import org.ergoplatform.sdk import scala.scalajs.js.UndefOr import scala.scalajs.js.annotation.JSExportTopLevel +import org.ergoplatform.sdk.Iso._ +/** JS exported version of the [[sdk.BlockchainParameters]] class with the same fields. + * @see sdk.BlockchainParameters + */ @JSExportTopLevel("BlockchainParameters") class BlockchainParameters( - val storageFeeFactor: Int, - val minValuePerByte: Int, - val maxBlockSize: Int, - val tokenAccessCost: Int, - val inputCost: Int, - val dataInputCost: Int, - val outputCost: Int, - val maxBlockCost: Int, - val _softForkStartingHeight: UndefOr[Int], - val _softForkVotesCollected: UndefOr[Int], - val blockVersion: Byte -) extends ErgoLikeParameters { - import org.ergoplatform.sdk.Iso._ - /** - * @return height when voting for a soft-fork had been started - */ - override def softForkStartingHeight: Option[Int] = - Isos.isoUndefOr[Int, Int](identityIso).to(_softForkStartingHeight) - - /** - * @return votes for soft-fork collected in previous epochs - */ - override def softForkVotesCollected: Option[Int] = - Isos.isoUndefOr[Int, Int](identityIso).to(_softForkVotesCollected) -} + storageFeeFactor: Int, + minValuePerByte: Int, + maxBlockSize: Int, + tokenAccessCost: Int, + inputCost: Int, + dataInputCost: Int, + outputCost: Int, + maxBlockCost: Int, + _softForkStartingHeight: UndefOr[Int], + _softForkVotesCollected: UndefOr[Int], + blockVersion: Byte +) extends sdk.BlockchainParameters( + storageFeeFactor, minValuePerByte, maxBlockSize, tokenAccessCost, inputCost, dataInputCost, + outputCost, maxBlockCost, + Isos.isoUndefOr[Int, Int](identityIso).to(_softForkStartingHeight), + Isos.isoUndefOr[Int, Int](identityIso).to(_softForkVotesCollected), blockVersion +) diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala index 4dfce27d44..4d35d033aa 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala @@ -1,9 +1,8 @@ package org.ergoplatform.sdk.js import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix -import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters import org.ergoplatform.sdk -import org.ergoplatform.sdk.SecretString +import org.ergoplatform.sdk.{BlockchainParameters, SecretString} import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel @@ -12,7 +11,7 @@ import sigmastate.eval.SigmaDsl /** Equivalent of [[sdk.ProverBuilder]] available from JS. */ @JSExportTopLevel("ProverBuilder") -class ProverBuilder(parameters: ErgoLikeParameters, networkPrefix: NetworkPrefix) extends js.Object { +class ProverBuilder(parameters: BlockchainParameters, networkPrefix: NetworkPrefix) extends js.Object { val _builder = new sdk.ProverBuilder(parameters, networkPrefix) /** Configure this builder to use the given seed when building a new prover. diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala index d39187c48a..850eff8113 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala @@ -5,20 +5,19 @@ import org.ergoplatform._ import org.ergoplatform.sdk.Extensions.{CollOps, PairCollOps} import org.ergoplatform.sdk.JavaHelpers.{TokenColl, UniversalConverter} import org.ergoplatform.sdk.utils.ArithUtils -import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeParameters, ErgoLikeStateContext} +import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeStateContext, TransactionContext} import org.ergoplatform.sdk.wallet.secrets.ExtendedSecretKey +import org.ergoplatform.validation.ValidationRules import scalan.util.Extensions.LongOps +import scorex.crypto.authds.ADDigest import sigmastate.Values.SigmaBoolean -import sigmastate.{AvlTreeData, VersionContext} import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} import sigmastate.basics.{DiffieHellmanTupleProverInput, SigmaProtocolPrivateInput} import sigmastate.interpreter.Interpreter.{ReductionResult, estimateCryptoVerifyCost} -import sigmastate.interpreter.{ContextExtension, CostedProverResult, HintsBag, Interpreter, ProverInterpreter, ProverResult} +import sigmastate.interpreter._ import sigmastate.serialization.SigmaSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} -import org.ergoplatform.sdk.wallet.protocol.context.TransactionContext -import org.ergoplatform.validation.ValidationRules -import scorex.crypto.authds.ADDigest +import sigmastate.{AvlTreeData, VersionContext} import java.util import java.util.{Objects, List => JList} @@ -37,7 +36,7 @@ class AppkitProvingInterpreter( val secretKeys: IndexedSeq[ExtendedSecretKey], val dLogInputs: IndexedSeq[DLogProverInput], val dhtInputs: IndexedSeq[DiffieHellmanTupleProverInput], - params: ErgoLikeParameters) + params: BlockchainParameters) extends ReducingInterpreter(params) with ProverInterpreter { override type CTX = ErgoLikeContext diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainParameters.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainParameters.scala new file mode 100644 index 0000000000..1113a8dbd6 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainParameters.scala @@ -0,0 +1,30 @@ +package org.ergoplatform.sdk + +/** Blockchain parameters re-adjustable via miners voting and voting-related data. + * All these fields are included into extension section of a first block of a voting epoch. + * + * @param storageFeeFactor cost of storing 1 byte in UTXO for four years, in nanoErgs + * @param minValuePerByte cost of a transaction output, in computation unit + * @param maxBlockSize max block size, in bytes + * @param tokenAccessCost cost of a token contained in a transaction, in computation unit + * @param inputCost cost of a transaction input, in computation unit + * @param dataInputCost cost of a transaction data input, in computation unit + * @param outputCost cost of a transaction output, in computation unit + * @param maxBlockCost computation units limit per block + * @param softForkStartingHeight height when voting for a soft-fork had been started + * @param softForkVotesCollected votes for soft-fork collected in previous epochs + * @param blockVersion Protocol version activated on the network + */ +case class BlockchainParameters( + storageFeeFactor: Int, + minValuePerByte: Int, + maxBlockSize: Int, + tokenAccessCost: Int, + inputCost: Int, + dataInputCost: Int, + outputCost: Int, + maxBlockCost: Int, + softForkStartingHeight: Option[Int], + softForkVotesCollected: Option[Int], + blockVersion: Byte +) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala index 2bdfe48fb1..45f339c6ca 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala @@ -1,7 +1,6 @@ package org.ergoplatform.sdk import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix -import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters import org.ergoplatform.sdk.wallet.secrets.ExtendedSecretKey import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.basics.{DLogProtocol, DiffieHellmanTupleProverInput} @@ -11,7 +10,7 @@ import java.math.BigInteger import scala.collection.mutable.ArrayBuffer /** A builder class for constructing a `Prover` with specified secrets. */ -class ProverBuilder(parameters: ErgoLikeParameters, networkPrefix: NetworkPrefix) { +class ProverBuilder(parameters: BlockchainParameters, networkPrefix: NetworkPrefix) { private var _masterKey: Option[ExtendedSecretKey] = None /** Generated EIP-3 secret keys paired with their derivation path index. */ diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala index a78c49e412..8354fc9a97 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala @@ -3,7 +3,7 @@ package org.ergoplatform.sdk import org.ergoplatform.sdk.Extensions.{CollOps, PairCollOps} import org.ergoplatform.sdk.JavaHelpers.UniversalConverter import org.ergoplatform.sdk.utils.ArithUtils -import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeParameters, ErgoLikeStateContext, TransactionContext} +import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeStateContext, TransactionContext} import org.ergoplatform.validation.ValidationRules import org.ergoplatform.{ErgoLikeContext, ErgoLikeInterpreter} import scalan.util.Extensions.LongOps @@ -20,7 +20,7 @@ import java.util.{Objects, List => JList} import scala.collection.mutable /** Interpreter that can reduce transactions with given chain parameters. */ -class ReducingInterpreter(params: ErgoLikeParameters) extends ErgoLikeInterpreter { +class ReducingInterpreter(params: BlockchainParameters) extends ErgoLikeInterpreter { override type CTX = ErgoLikeContext import org.ergoplatform.sdk.Iso._ diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeParameters.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeParameters.scala deleted file mode 100644 index b84387e2b3..0000000000 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeParameters.scala +++ /dev/null @@ -1,63 +0,0 @@ -package org.ergoplatform.sdk.wallet.protocol.context - -/** - * Blockchain parameters readjustable via miners voting and voting-related data. - * All these fields are included into extension section of a first block of a voting epoch. - */ -trait ErgoLikeParameters { - - /** - * @return cost of storing 1 byte in UTXO for four years, in nanoErgs - */ - def storageFeeFactor: Int - - /** - * @return cost of a transaction output, in computation unit - */ - def minValuePerByte: Int - - /** - * @return max block size, in bytes - */ - def maxBlockSize: Int - - /** - * @return cost of a token contained in a transaction, in computation unit - */ - def tokenAccessCost: Int - - /** - * @return cost of a transaction input, in computation unit - */ - def inputCost: Int - - /** - * @return cost of a transaction data input, in computation unit - */ - def dataInputCost: Int - - /** - * @return cost of a transaction output, in computation unit - */ - def outputCost: Int - - /** - * @return computation units limit per block - */ - def maxBlockCost: Int - - /** - * @return height when voting for a soft-fork had been started - */ - def softForkStartingHeight: Option[Int] - - /** - * @return votes for soft-fork collected in previous epochs - */ - def softForkVotesCollected: Option[Int] - - /** - * @return Protocol version - */ - def blockVersion: Byte -} From de8d4772a859c62f8495c17f722c283197d42798 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 10 Jul 2023 15:39:50 +0200 Subject: [PATCH 02/27] multisig: rename ErgoLikeStateContext -> BlockchainStateContext --- .../sdk/js/BlockchainStateContext.scala | 2 +- .../scala/org/ergoplatform/sdk/js/Isos.scala | 10 ++--- .../org/ergoplatform/sdk/js/IsosSpec.scala | 11 +++-- .../sdk/AppkitProvingInterpreter.scala | 6 +-- .../org/ergoplatform/sdk/ProverBuilder.scala | 9 +++- .../sdk/ReducingInterpreter.scala | 4 +- .../org/ergoplatform/sdk/SigmaProver.scala | 14 +++---- .../context/BlockchainStateContext.scala | 15 +++++++ .../context/ErgoLikeStateContext.scala | 41 ------------------- .../sdk/wallet/utils/Generators.scala | 1 - 10 files changed, 45 insertions(+), 68 deletions(-) create mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/BlockchainStateContext.scala delete mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeStateContext.scala diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala index 0b91cbae57..98244c5653 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala @@ -3,7 +3,7 @@ package org.ergoplatform.sdk.js import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel -/** Equivalent of [[org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeStateContext]] available from JS. */ +/** Equivalent of [[org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext]] available from JS. */ @JSExportTopLevel("BlockchainStateContext") class BlockchainStateContext( val sigmaLastHeaders: js.Array[Header], diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index 1184602482..074d59753a 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -4,7 +4,7 @@ import org.ergoplatform.ErgoBox._ import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, UnsignedErgoLikeTransaction, UnsignedInput} import org.ergoplatform.sdk.{ExtendedInputBox, Iso} import org.ergoplatform.sdk.JavaHelpers.UniversalConverter -import org.ergoplatform.sdk.wallet.protocol.context.{CErgoLikeStateContext, ErgoLikeStateContext} +import org.ergoplatform.sdk.wallet.protocol.context import scalan.RType import scorex.crypto.authds.{ADDigest, ADKey} import scorex.util.ModifierId @@ -179,16 +179,16 @@ object Isos { } } - implicit val isoBlockchainStateContext: Iso[BlockchainStateContext, ErgoLikeStateContext] = new Iso[BlockchainStateContext, ErgoLikeStateContext] { - override def to(a: BlockchainStateContext): ErgoLikeStateContext = { - CErgoLikeStateContext( + implicit val isoBlockchainStateContext: Iso[BlockchainStateContext, context.BlockchainStateContext] = new Iso[BlockchainStateContext, context.BlockchainStateContext] { + override def to(a: BlockchainStateContext): context.BlockchainStateContext = { + context.BlockchainStateContext( sigmaLastHeaders = isoArrayToColl(isoHeader).to(a.sigmaLastHeaders), previousStateDigest = isoStringToColl.to(a.previousStateDigest), sigmaPreHeader = isoPreHeader.to(a.sigmaPreHeader) ) } - override def from(b: ErgoLikeStateContext): BlockchainStateContext = { + override def from(b: context.BlockchainStateContext): BlockchainStateContext = { new BlockchainStateContext( sigmaLastHeaders = isoArrayToColl(isoHeader).from(b.sigmaLastHeaders), previousStateDigest = isoStringToColl.from(b.previousStateDigest), diff --git a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala index f8e7962055..e3a389be0d 100644 --- a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala +++ b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala @@ -1,14 +1,13 @@ package org.ergoplatform.sdk.js import org.ergoplatform.ErgoBox.{AdditionalRegisters, BoxId, TokenId} -import org.ergoplatform.sdk.{ExtendedInputBox, Iso} import org.ergoplatform._ -import org.ergoplatform.sdk.wallet.protocol.context.{CErgoLikeStateContext, ErgoLikeStateContext} +import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext +import org.ergoplatform.sdk.{ExtendedInputBox, Iso} import org.scalacheck.{Arbitrary, Gen} import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -import scorex.crypto.authds.ADDigest import sigmastate.SType import sigmastate.Values.Constant import sigmastate.eval.Colls @@ -27,11 +26,11 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca extension <- contextExtensionGen } yield ExtendedInputBox(box, extension) - lazy val ergoLikeStateContextGen: Gen[ErgoLikeStateContext] = for { + lazy val blockchainStateContextGen: Gen[BlockchainStateContext] = for { stateRoot <- avlTreeGen headers <- headersGen(stateRoot) preHeader <- preHeaderGen(headers.headOption.map(_.id).getOrElse(modifierIdBytesGen.sample.get)) - } yield CErgoLikeStateContext( + } yield BlockchainStateContext( sigmaLastHeaders = Colls.fromItems(headers:_*), previousStateDigest = stateRoot.digest, sigmaPreHeader = preHeader @@ -93,7 +92,7 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca } property("Iso.isoBlockchainStateContext") { - forAll(ergoLikeStateContextGen) { (c: ErgoLikeStateContext) => + forAll(blockchainStateContextGen) { (c: BlockchainStateContext) => roundtrip(Isos.isoBlockchainStateContext)(c) } } diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala index 850eff8113..7e723af92a 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala @@ -5,7 +5,7 @@ import org.ergoplatform._ import org.ergoplatform.sdk.Extensions.{CollOps, PairCollOps} import org.ergoplatform.sdk.JavaHelpers.{TokenColl, UniversalConverter} import org.ergoplatform.sdk.utils.ArithUtils -import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeStateContext, TransactionContext} +import org.ergoplatform.sdk.wallet.protocol.context.{BlockchainStateContext, TransactionContext} import org.ergoplatform.sdk.wallet.secrets.ExtendedSecretKey import org.ergoplatform.validation.ValidationRules import scalan.util.Extensions.LongOps @@ -80,7 +80,7 @@ class AppkitProvingInterpreter( * The returned cost doesn't include `baseCost`. */ def sign(unreducedTx: UnreducedTransaction, - stateContext: ErgoLikeStateContext, + stateContext: BlockchainStateContext, baseCost: Int): Try[SignedTransaction] = Try { val maxCost = params.maxBlockCost var currentCost: Long = baseCost @@ -113,7 +113,7 @@ class AppkitProvingInterpreter( unsignedTx: UnsignedErgoLikeTransaction, boxesToSpend: IndexedSeq[ExtendedInputBox], dataBoxes: IndexedSeq[ErgoBox], - stateContext: ErgoLikeStateContext, + stateContext: BlockchainStateContext, baseCost: Int, tokensToBurn: IndexedSeq[ErgoToken]): ReducedErgoLikeTransaction = { if (unsignedTx.inputs.length != boxesToSpend.length) throw new Exception("Not enough boxes to spend") diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala index 45f339c6ca..1e13962e18 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala @@ -1,6 +1,6 @@ package org.ergoplatform.sdk -import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix +import org.ergoplatform.ErgoAddressEncoder.{MainnetNetworkPrefix, NetworkPrefix} import org.ergoplatform.sdk.wallet.secrets.ExtendedSecretKey import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.basics.{DLogProtocol, DiffieHellmanTupleProverInput} @@ -26,7 +26,7 @@ class ProverBuilder(parameters: BlockchainParameters, networkPrefix: NetworkPref def withMnemonic( mnemonicPhrase: SecretString, mnemonicPass: SecretString, - usePre1627KeyDerivation: Boolean + usePre1627KeyDerivation: Boolean = false ): ProverBuilder = { _masterKey = Some(JavaHelpers.seedToMasterKey(mnemonicPhrase, mnemonicPass, usePre1627KeyDerivation)) this @@ -88,3 +88,8 @@ class ProverBuilder(parameters: BlockchainParameters, networkPrefix: NetworkPref } } +object ProverBuilder { + def forMainnet(parameters: BlockchainParameters): ProverBuilder = + new ProverBuilder(parameters, MainnetNetworkPrefix) +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala index 8354fc9a97..900d7e90ce 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala @@ -3,7 +3,7 @@ package org.ergoplatform.sdk import org.ergoplatform.sdk.Extensions.{CollOps, PairCollOps} import org.ergoplatform.sdk.JavaHelpers.UniversalConverter import org.ergoplatform.sdk.utils.ArithUtils -import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeStateContext, TransactionContext} +import org.ergoplatform.sdk.wallet.protocol.context.{BlockchainStateContext, TransactionContext} import org.ergoplatform.validation.ValidationRules import org.ergoplatform.{ErgoLikeContext, ErgoLikeInterpreter} import scalan.util.Extensions.LongOps @@ -56,7 +56,7 @@ class ReducingInterpreter(params: BlockchainParameters) extends ErgoLikeInterpre */ def reduceTransaction( unreducedTx: UnreducedTransaction, - stateContext: ErgoLikeStateContext, + stateContext: BlockchainStateContext, baseCost: Int ): ReducedTransaction = { val unsignedTx = unreducedTx.unsignedTx diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala index 255534e14b..ac1fbe95d5 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala @@ -2,7 +2,7 @@ package org.ergoplatform.sdk import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix import org.ergoplatform._ -import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeStateContext +import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext import sigmastate.eval.{CostingSigmaDslBuilder, SigmaDsl} import sigmastate.interpreter.HintsBag import sigmastate.utils.Helpers.TryOps @@ -37,16 +37,16 @@ class SigmaProver(_prover: AppkitProvingInterpreter, networkPrefix: NetworkPrefi addresses } - /** Signs a given `UnreducedTransaction` using the prover's secret keys and the provided `ErgoLikeStateContext`. + /** Signs a given `UnreducedTransaction` using the prover's secret keys and the provided [[BlockchainStateContext]]. * Uses baseCost == 0. */ - def sign(stateCtx: ErgoLikeStateContext, tx: UnreducedTransaction): SignedTransaction = + def sign(stateCtx: BlockchainStateContext, tx: UnreducedTransaction): SignedTransaction = sign(stateCtx, tx, baseCost = 0) - /** Signs a given `UnreducedTransaction` using the prover's secret keys and the provided `ErgoLikeStateContext`. + /** Signs a given `UnreducedTransaction` using the prover's secret keys and the provided [[BlockchainStateContext]]. * Uses the given baseCost. */ - def sign(stateCtx: ErgoLikeStateContext, tx: UnreducedTransaction, baseCost: Int): SignedTransaction = { + def sign(stateCtx: BlockchainStateContext, tx: UnreducedTransaction, baseCost: Int): SignedTransaction = { val signed = _prover .sign(tx, stateContext = stateCtx, baseCost = baseCost) .getOrThrow @@ -65,9 +65,9 @@ class SigmaProver(_prover: AppkitProvingInterpreter, networkPrefix: NetworkPrefi } /** Reduces a given `UnreducedTransaction` using the prover's secret keys and the - * provided `ErgoLikeStateContext` with a base cost. + * provided [[BlockchainStateContext]] with a base cost. */ - def reduce(stateCtx: ErgoLikeStateContext, tx: UnreducedTransaction, baseCost: Int): ReducedTransaction = { + def reduce(stateCtx: BlockchainStateContext, tx: UnreducedTransaction, baseCost: Int): ReducedTransaction = { val reduced = _prover.reduceTransaction( unreducedTx = tx, stateContext = stateCtx, baseCost = baseCost) reduced diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/BlockchainStateContext.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/BlockchainStateContext.scala new file mode 100644 index 0000000000..5c8d97df4f --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/BlockchainStateContext.scala @@ -0,0 +1,15 @@ +package org.ergoplatform.sdk.wallet.protocol.context + +import special.collection.Coll + +/** Blockchain context used in tx signing. + * + * @param sigmaLastHeaders fixed number (10 in Ergo) of last block headers + * @param previousStateDigest UTXO set digest from a last header (of sigmaLastHeaders) + * @param sigmaPreHeader returns pre-header (header without certain fields) of the current block + */ +case class BlockchainStateContext( + sigmaLastHeaders: Coll[special.sigma.Header], + previousStateDigest: Coll[Byte], + sigmaPreHeader: special.sigma.PreHeader +) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeStateContext.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeStateContext.scala deleted file mode 100644 index b150c83218..0000000000 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeStateContext.scala +++ /dev/null @@ -1,41 +0,0 @@ -package org.ergoplatform.sdk.wallet.protocol.context - -import scorex.crypto.authds.ADDigest -import special.collection.Coll - -import java.util - -/** - * Blockchain context used in transaction validation. - */ -trait ErgoLikeStateContext { - - /** - * @return fixed number (10 in Ergo) of last block headers - */ - def sigmaLastHeaders: Coll[special.sigma.Header] - - // todo remove from ErgoLikeContext and from ErgoStateContext - /** - * @return UTXO set digest from a last header (of sigmaLastHeaders) - */ - def previousStateDigest: Coll[Byte] - - /** - * @return returns pre-header (header without certain fields) of the current block - */ - def sigmaPreHeader: special.sigma.PreHeader -} - -/** Representis the Ergo-like state context for tx signing. - * - * @param sigmaLastHeaders the last headers of the Sigma blockchain - * @param previousStateDigest the bytes representing the previous state digest - * @param sigmaPreHeader the pre-header object - */ -case class CErgoLikeStateContext( - sigmaLastHeaders: Coll[special.sigma.Header], - previousStateDigest: Coll[Byte], - sigmaPreHeader: special.sigma.PreHeader -) extends ErgoLikeStateContext { -} diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/utils/Generators.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/utils/Generators.scala index e0672d4a3d..e4cc09c4a2 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/utils/Generators.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/utils/Generators.scala @@ -17,7 +17,6 @@ import sigmastate.eval.Extensions._ import scorex.util.{ModifierId, bytesToId} import sigmastate.eval._ import sigmastate.helpers.TestingHelpers._ -import scorex.crypto.hash.Digest32 import sigmastate.crypto.CryptoFacade trait Generators { From b0672844905bb885c7d934194c520c47c5fd1efe Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 11 Jul 2023 00:06:42 +0200 Subject: [PATCH 03/27] multisig: implemented OutBoxBuilder, BlockchainContext --- .../main/scala/special/sigma/SigmaDsl.scala | 14 +--- .../ergoplatform/sdk/BlockchainContext.scala | 22 +++++ .../sdk/BlockchainParameters.scala | 22 +++++ .../org/ergoplatform/sdk/Extensions.scala | 10 ++- .../org/ergoplatform/sdk/JavaHelpers.scala | 3 - .../org/ergoplatform/sdk/NetworkType.scala | 49 ++++++++++++ .../org/ergoplatform/sdk/OutBoxBuilder.scala | 80 +++++++++++++++++++ .../ergoplatform/sdk/multisig/Signer.scala | 15 ++++ .../sdk/multisig/SigningSpec.scala | 56 +++++++++++++ 9 files changed, 257 insertions(+), 14 deletions(-) create mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainContext.scala create mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/NetworkType.scala create mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/OutBoxBuilder.scala create mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala create mode 100644 sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala diff --git a/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala b/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala index 186aa59bd7..462c5382ab 100644 --- a/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala +++ b/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala @@ -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 @@ -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] @@ -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 @@ -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] diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainContext.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainContext.scala new file mode 100644 index 0000000000..d5b6edaf69 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainContext.scala @@ -0,0 +1,22 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext +import special.collection.Coll +import special.sigma.Header + +/** Represents a specific context of blockchain for execution + * of transaction building scenario. + * It contains methods for accessing blockchain data, current blockchain state, + * node information etc. + * An instance of this class can also be used to create new builders + * for creating new transactions and provers (used for transaction signing). + */ +case class BlockchainContext( + networkType: NetworkType, + parameters: BlockchainParameters, + stateContext: BlockchainStateContext +) { + def headers: Coll[Header] = stateContext.sigmaLastHeaders + + def height: Int = headers(0).height +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainParameters.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainParameters.scala index 1113a8dbd6..4a1014b112 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainParameters.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BlockchainParameters.scala @@ -28,3 +28,25 @@ case class BlockchainParameters( softForkVotesCollected: Option[Int], blockVersion: Byte ) + +/** Global parameters used by SDK */ +object BlockchainParameters { + /** A number of blocks a miner should wait before he/she can spend block reward. + * This is part of Ergo protocol and cannot be changed. + */ + val MinerRewardDelay_Mainnet = 720 + + val MinerRewardDelay_Testnet = 720 + + /** One Erg is 10^9 NanoErg */ + val OneErg: Long = 1000 * 1000 * 1000 + + /** Minimum transaction fee in NanoErgs as it is defined in Ergo protocol. */ + val MinFee: Long = 1000 * 1000 + + /** Minimum value for a change. It can be used to compute change output value. + * If computed change is less than this value, it is added to the fee + * and `change` output in not added to the transaction. + */ + val MinChangeValue: Long = 1000 * 1000 +} \ No newline at end of file diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala index 840ec21576..37ae9c03b0 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala @@ -2,8 +2,10 @@ package org.ergoplatform.sdk import debox.cfor import scalan.RType -import scalan.rtypeToClassTag // actually required +import scalan.rtypeToClassTag // actually used +import sigmastate.eval.CPreHeader import special.collection.{Coll, CollBuilder, PairColl} +import special.sigma.{Header, PreHeader} import scala.collection.compat.BuildFrom import scala.collection.{GenIterable, immutable} @@ -196,4 +198,10 @@ object Extensions { builder.pairCollFromArrays(ks, vs) } } + + implicit class HeaderOps(val header: Header) extends AnyVal { + def toPreHeader(h: Header): PreHeader = { + CPreHeader(h.version, h.parentId, h.timestamp, h.nBits, h.height, h.minerPk, h.votes) + } + } } diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala index 4a49e23283..34c5574b9f 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala @@ -294,9 +294,6 @@ object JavaHelpers { ErgoAlgos.encode(ErgoAlgos.hash(s)) } - def toPreHeader(h: Header): special.sigma.PreHeader = { - CPreHeader(h.version, h.parentId, h.timestamp, h.nBits, h.height, h.minerPk, h.votes) - } def toSigmaBoolean(ergoTree: ErgoTree): SigmaBoolean = { val prop = ergoTree.toProposition(ergoTree.isConstantSegregation) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/NetworkType.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/NetworkType.scala new file mode 100644 index 0000000000..806ce6b014 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/NetworkType.scala @@ -0,0 +1,49 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.ErgoAddressEncoder +import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix + +/** + * Enumeration of network types as they are defined by Ergo specification of {@link ErgoAddress}. + */ +abstract class NetworkType { + + /** + * The network prefix code used in Ergo addresses + */ + val networkPrefix: NetworkPrefix + + /** + * verbose name for network type as reported by Node API + */ + val verboseName: String +} + +object NetworkType { + /** Mainnet network type. + * + * @see ErgoAddressEncoder#MainnetNetworkPrefix() + */ + case object Mainnet extends NetworkType { + override val networkPrefix: NetworkPrefix = ErgoAddressEncoder.MainnetNetworkPrefix + override val verboseName = "mainnet" + } + + /** Testnet network type. + * + * @see ErgoAddressEncoder#TestnetNetworkPrefix() + */ + case object Testnet extends NetworkType { + override val networkPrefix: NetworkPrefix = ErgoAddressEncoder.TestnetNetworkPrefix + override val verboseName = "testnet" + } + + /** @return network type for given verbose name */ + def fromName(name: String): Option[NetworkType] = name match { + case "mainnet" => Some(Mainnet) + case "testnet" => Some(Testnet) + case _ => None + } + +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/OutBoxBuilder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/OutBoxBuilder.scala new file mode 100644 index 0000000000..4ee93c1cb3 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/OutBoxBuilder.scala @@ -0,0 +1,80 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.ErgoBox.TokenId +import org.ergoplatform.sdk.JavaHelpers.collRType +import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, SigmaConstants} +import scalan.RType +import sigmastate.SType +import sigmastate.Values.{Constant, ErgoTree, EvaluatedValue} +import sigmastate.eval.Colls + +import scala.collection.mutable.ArrayBuffer + +class OutBoxBuilder(val _txB: UnsignedTransactionBuilder) { + private val _ctx = _txB.ctx + private var _value: Long = 0 + private var _contract: ErgoTree = _ + private val _tokens = ArrayBuffer.empty[ErgoToken] + private val _registers = ArrayBuffer.empty[Constant[_]] + private var _creationHeightOpt: Option[Int] = None + + def value(value: Long): this.type = { + _value = value + this + } + + def contract(contract: ErgoTree): this.type = { + _contract = contract + this + } + + def tokens(tokens: ErgoToken*): this.type = { + require(tokens.nonEmpty, "At least one token should be specified") + val maxTokens = SigmaConstants.MaxTokens.value + require(tokens.size <= maxTokens, SigmaConstants.MaxTokens.description + s": $maxTokens") + _tokens ++= tokens + this + } + + def registers(registers: Constant[_]*): this.type = { + require(registers.nonEmpty, "At least one register should be specified") + _registers.clear() + _registers ++= registers + this + } + + def creationHeight(height: Int): OutBoxBuilder = { + _creationHeightOpt = Some(height) + this + } + + def build: OutBox = { + require(_contract != null, "Contract is not defined") + val ergoBoxCandidate = OutBoxBuilder.createBoxCandidate( + _value, _contract, _tokens.toSeq, _registers.toSeq, + creationHeight = _creationHeightOpt.getOrElse(_txB.ctx.height)) + OutBox(ergoBoxCandidate) + } +} + +object OutBoxBuilder { + def apply(txB: UnsignedTransactionBuilder): OutBoxBuilder = new OutBoxBuilder(txB) + + private[sdk] def createBoxCandidate( + value: Long, tree: ErgoTree, + tokens: Seq[ErgoToken], + registers: Seq[Constant[_]], creationHeight: Int): ErgoBoxCandidate = { + import org.ergoplatform.ErgoBox.nonMandatoryRegisters + val nRegs = registers.length + require(nRegs <= nonMandatoryRegisters.length, + s"Too many additional registers $nRegs. Max allowed ${nonMandatoryRegisters.length}") + implicit val TokenIdRType: RType[TokenId] = collRType(RType.ByteType).asInstanceOf[RType[TokenId]] + val ts = Colls.fromItems(tokens.map(Iso.isoErgoTokenToPair.to(_)): _*) + val rs = registers.zipWithIndex.map { case (c, i) => + val id = ErgoBox.nonMandatoryRegisters(i) + id -> c.asInstanceOf[EvaluatedValue[_ <: SType]] + }.toMap + new ErgoBoxCandidate(value, tree, creationHeight, ts, rs) + } +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala new file mode 100644 index 0000000000..b7ab3eb7a8 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala @@ -0,0 +1,15 @@ +package org.ergoplatform.sdk.multisig + +import org.ergoplatform.sdk.SigmaProver + +case class Signer(prover: SigmaProver) { +} + +object Signer { + + + def main(args: Array[String]): Unit = { + +// val pkAlice = + } +} \ No newline at end of file diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala new file mode 100644 index 0000000000..8173edd0e0 --- /dev/null +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala @@ -0,0 +1,56 @@ +package org.ergoplatform.sdk.multisig + +import org.ergoplatform.sdk.{BlockchainParameters, ProverBuilder, ReducedTransaction, SecretString} +import org.scalatest.matchers.should.Matchers +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + +class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers { + + val testMainnetParameters = BlockchainParameters( + storageFeeFactor = 1250000, + minValuePerByte = 360, + maxBlockSize = 1271009, + tokenAccessCost = 100, + inputCost = 2407, + dataInputCost = 100, + outputCost = 184, + maxBlockCost = 8001091, + softForkStartingHeight = None, + softForkVotesCollected = None, + blockVersion = 3 + ) + + def createSigner(secret: String): Signer = Signer( + ProverBuilder.forMainnet(testMainnetParameters) + .withMnemonic(SecretString.create(secret), SecretString.empty()) + .build() + ) + + val alice = createSigner("Alice secret") + val bob = createSigner("Bob secret") + val carol = createSigner("Carol secret") + + def createRtx(ctx: BlockchainContext, inputs: Seq[InputBox]): ReducedTransaction = { + val txB = ctx.newTxBuilder() + val output = txB.outBoxBuilder() + .value(inputs.map(_.getValue).sum - Parameters.MinFee) + .contract(truePropContract(ctx)).build() + val feeOut = txB.outBoxBuilder() + .value(Parameters.MinFee) + .contract(ctx.newContract(ErgoTreePredef.feeProposition())) + .build() + val unsigned = txB + .addInputs(inputs: _*) + .addOutputs(output, feeOut) + .build() + val prover = ctx.newProverBuilder.build // NOTE, prover without secrets + val reduced = prover.reduce(unsigned, 0) + ??? + } + + property("Signing workflow") { + + } +} + From b1ca88da0c578ed79dea32bf0cf71ee246378d9b Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 11 Jul 2023 15:24:21 +0200 Subject: [PATCH 04/27] multisig: implemented UnsignedTransactionBuilder --- .../ergoplatform/sdk/BoxSelectionResult.scala | 15 ++ .../ergoplatform/sdk/ExtendedInputBox.scala | 5 +- .../org/ergoplatform/sdk/Extensions.scala | 4 +- .../sdk/InputBoxesValidator.scala | 127 +++++++++ .../org/ergoplatform/sdk/JavaHelpers.scala | 28 +- .../sdk/UnsignedTransactionBuilder.scala | 240 ++++++++++++++++++ .../sdk/multisig/SigningSpec.scala | 32 +-- 7 files changed, 420 insertions(+), 31 deletions(-) create mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/BoxSelectionResult.scala create mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/InputBoxesValidator.scala create mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/UnsignedTransactionBuilder.scala diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/BoxSelectionResult.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BoxSelectionResult.scala new file mode 100644 index 0000000000..d63ae82a44 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/BoxSelectionResult.scala @@ -0,0 +1,15 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.ErgoBoxAssets + +/** + * Containter for box selector output + * + * @param inputBoxes - transaction inputs chosen by a selector + * @param changeBoxes - change outputs + * @param payToReemissionBox - pay-to-reemission output mde according to EIP-27, if needed + */ +class BoxSelectionResult[T <: ErgoBoxAssets]( + val inputBoxes: Seq[T], + val changeBoxes: Seq[ErgoBoxAssets], + val payToReemissionBox: Option[ErgoBoxAssets]) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala index 311475c134..04a3c578ec 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala @@ -1,6 +1,6 @@ package org.ergoplatform.sdk -import org.ergoplatform.{ErgoBox, UnsignedInput} +import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, UnsignedInput} import sigmastate.interpreter.ContextExtension /** Input ErgoBox paired with context variables (aka ContextExtensions). @@ -17,3 +17,6 @@ case class ExtendedInputBox( ) { def toUnsignedInput: UnsignedInput = new UnsignedInput(box.id, extension) } + +case class OutBox(candidate: ErgoBoxCandidate) + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala index 37ae9c03b0..82d6724f12 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala @@ -199,8 +199,8 @@ object Extensions { } } - implicit class HeaderOps(val header: Header) extends AnyVal { - def toPreHeader(h: Header): PreHeader = { + implicit class HeaderOps(val h: Header) extends AnyVal { + def toPreHeader: PreHeader = { CPreHeader(h.version, h.parentId, h.timestamp, h.nBits, h.height, h.minerPk, h.votes) } } diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/InputBoxesValidator.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/InputBoxesValidator.scala new file mode 100644 index 0000000000..7e3e24a8b7 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/InputBoxesValidator.scala @@ -0,0 +1,127 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.SigmaConstants.MaxBoxSize +import org.ergoplatform.sdk.wallet.Constants.MaxAssetsPerBox +import org.ergoplatform.sdk.wallet.{AssetUtils, TokensMap} +import org.ergoplatform.{ErgoBoxAssets, ErgoBoxAssetsHolder} +import scorex.util.ModifierId + +import scala.collection.mutable + +object BoxSelection { + // from https://github.com/ergoplatform/ergo/blob/2ce78a0380977b8ca354518edca93a5269ac9f53/src/main/scala/org/ergoplatform/settings/Parameters.scala#L258-L258 + private val MinValuePerByteDefault = 30 * 12 + + val MinBoxValue: Long = (MaxBoxSize.value / 2L) * MinValuePerByteDefault + + trait Error { + def message: String + } + + final case class NotEnoughErgsError( + message: String, + balanceFound: Long) extends Error + + final case class NotEnoughTokensError( + message: String, + tokensFound: Map[ModifierId, Long]) extends Error + + final case class NotEnoughCoinsForChangeBoxesError(message: String) extends Error + + /** + * Pass through implementation of the box selector. Unlike DefaultBoxSelector from ergo-wallet, + * it does not select input boxes. We do this in SDK ourselves and do not need the selector + * to interfere with how we built our transaction. Instead, this selector performs validation + * and calculates the necessary change box + */ + class InputBoxesValidator { + + def select[T <: ErgoBoxAssets](inputBoxes: Iterator[T], + externalFilter: T => Boolean, + targetBalance: Long, + targetAssets: TokensMap): Either[Error, BoxSelectionResult[T]] = { + //mutable structures to collect results + val res = mutable.Buffer[T]() + var currentBalance = 0L + val currentAssets = mutable.Map[ModifierId, Long]() + + // select all input boxes - we only validate here + inputBoxes.foreach { box: T => + currentBalance = currentBalance + box.value + AssetUtils.mergeAssetsMut(currentAssets, box.tokens) + res += box + } + + if (currentBalance - targetBalance >= 0) { + //now check if we found all tokens + if (targetAssets.forall { + case (id, targetAmt) => currentAssets.getOrElse(id, 0L) >= targetAmt + }) { + formChangeBoxes(currentBalance, targetBalance, currentAssets, targetAssets) match { + case Right(changeBoxes) => Right(new BoxSelectionResult(res.toSeq, changeBoxes, None)) + case Left(error) => Left(error) + } + } else { + Left(NotEnoughTokensError( + s"Not enough tokens in input boxes to send $targetAssets (found only $currentAssets)", currentAssets.toMap) + ) + } + } else { + Left(NotEnoughErgsError( + s"not enough boxes to meet ERG needs $targetBalance (found only $currentBalance)", currentBalance) + ) + } + } + + /** + * Helper method to construct change outputs + * + * @param foundBalance - ERG balance of boxes collected + * (spendable only, so after possibly deducting re-emission tokens) + * @param targetBalance - ERG amount to be transferred to recipients + * @param foundBoxAssets - assets balances of boxes + * @param targetBoxAssets - assets amounts to be transferred to recipients + * @return + */ + def formChangeBoxes(foundBalance: Long, + targetBalance: Long, + foundBoxAssets: mutable.Map[ModifierId, Long], + targetBoxAssets: TokensMap): Either[Error, Seq[ErgoBoxAssets]] = { + AssetUtils.subtractAssetsMut(foundBoxAssets, targetBoxAssets) + val changeBoxesAssets: Seq[mutable.Map[ModifierId, Long]] = foundBoxAssets.grouped(MaxAssetsPerBox).toSeq + val changeBalance = foundBalance - targetBalance + //at least a minimum amount of ERG should be assigned per a created box + if (changeBoxesAssets.size * MinBoxValue > changeBalance) { + Left(NotEnoughCoinsForChangeBoxesError( + s"Not enough nanoERGs ($changeBalance nanoERG) to create ${changeBoxesAssets.size} change boxes, \nfor $changeBoxesAssets" + )) + } else { + val changeBoxes = if (changeBoxesAssets.nonEmpty) { + val baseChangeBalance = changeBalance / changeBoxesAssets.size + + val changeBoxesNoBalanceAdjusted = changeBoxesAssets.map { a => + ErgoBoxAssetsHolder(baseChangeBalance, a.toMap) + } + + val modifiedBoxOpt = changeBoxesNoBalanceAdjusted.headOption.map { firstBox => + ErgoBoxAssetsHolder( + changeBalance - baseChangeBalance * (changeBoxesAssets.size - 1), + firstBox.tokens + ) + } + + modifiedBoxOpt.toSeq ++ changeBoxesNoBalanceAdjusted.tail + } else if (changeBalance > 0) { + Seq(ErgoBoxAssetsHolder(changeBalance)) + } else { + Seq.empty + } + + Right(changeBoxes) + } + } + + } + +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala index 34c5574b9f..52c653e9db 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala @@ -109,21 +109,25 @@ object Iso extends LowPriorityIsos { override def from(t: Token): ErgoToken = new ErgoToken(t._1.toArray, t._2) } - implicit val isoJListErgoTokenToMapPair: Iso[JList[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] = - new Iso[JList[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] { - override def to(a: JList[ErgoToken]): mutable.LinkedHashMap[ModifierId, Long] = { - import JavaHelpers._ + implicit val isoErgoTokenSeqToLinkedMap: Iso[IndexedSeq[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] = + new Iso[IndexedSeq[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] { + override def to(a: IndexedSeq[ErgoToken]): mutable.LinkedHashMap[ModifierId, Long] = { val lhm = new mutable.LinkedHashMap[ModifierId, Long]() - a.convertTo[IndexedSeq[Token]] - .map(t => bytesToId(t._1.toArray) -> t._2) - .foldLeft(lhm)(_ += _) + a.foreach { et => + val t = isoErgoTokenToPair.to(et) + lhm += bytesToId(t._1.toArray) -> t._2 + } + lhm } - override def from(t: mutable.LinkedHashMap[ModifierId, Long]): JList[ErgoToken] = { - import JavaHelpers._ - val pairs: IndexedSeq[Token] = t.toIndexedSeq - .map(t => (Digest32Coll @@ Colls.fromArray(idToBytes(t._1))) -> t._2) - pairs.convertTo[JList[ErgoToken]] + override def from(t: mutable.LinkedHashMap[ModifierId, Long]): IndexedSeq[ErgoToken] = { + val pairs = t.toIndexedSeq + .map { t => + val id = Digest32Coll @@ Colls.fromArray(idToBytes(t._1)) + val value = t._2 + isoErgoTokenToPair.from((id, value)) + } + pairs } } diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/UnsignedTransactionBuilder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/UnsignedTransactionBuilder.scala new file mode 100644 index 0000000000..12d029d6c9 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/UnsignedTransactionBuilder.scala @@ -0,0 +1,240 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.ErgoBox.TokenId +import org.ergoplatform._ +import org.ergoplatform.sdk.BlockchainParameters.MinChangeValue +import org.ergoplatform.sdk.BoxSelection.InputBoxesValidator +import org.ergoplatform.sdk.Extensions.HeaderOps +import org.ergoplatform.sdk.wallet.{AssetUtils, TokensMap} +import scorex.util.{ModifierId, bytesToId} +import sigmastate.eval.Extensions.ArrayOps +import sigmastate.utils.Extensions.ModifierIdOps +import special.collection.Coll +import special.collection.Extensions.CollBytesOps +import special.sigma.PreHeader + +import scala.collection.mutable.ArrayBuffer +import scala.util.Try + +class UnsignedTransactionBuilder(val ctx: BlockchainContext) { + private[sdk] val _inputs: ArrayBuffer[ExtendedInputBox] = ArrayBuffer.empty[ExtendedInputBox] + private[sdk] val _outputs: ArrayBuffer[OutBox] = ArrayBuffer.empty[OutBox] + private[sdk] val _dataInputs: ArrayBuffer[ErgoBox] = ArrayBuffer.empty[ErgoBox] + + private var _tokensToBurn: Option[ArrayBuffer[ErgoToken]] = None + private var _feeAmount: Option[Long] = None + private var _changeAddress: Option[ErgoAddress] = None + private var _ph: Option[PreHeader] = None + + def preHeader(ph: PreHeader): this.type = { + require(_ph.isEmpty, "PreHeader is already specified") + _ph = Some(ph) + this + } + + def addInputs(boxes: ExtendedInputBox*): this.type = { + _inputs ++= boxes + this + } + + def addDataInputs(boxes: ErgoBox*): this.type = { + _dataInputs ++= boxes + this + } + + def addOutputs(outBoxes: OutBox*): this.type = { + _outputs ++= outBoxes + this + } + + def fee(feeAmount: Long): this.type = { + require(_feeAmount.isEmpty, "Fee already defined") + _feeAmount = Some(feeAmount) + this + } + + def addTokensToBurn(tokens: ErgoToken*): this.type = { + if (_tokensToBurn.isEmpty) + _tokensToBurn = Some(ArrayBuffer.empty[ErgoToken]) + + _tokensToBurn.get ++= tokens + this + } + + def sendChangeTo(changeAddress: ErgoAddress): this.type = { + require(_changeAddress.isEmpty, "Change address is already specified") + _changeAddress = Some(changeAddress) + this + } + + private def getDefined[T](opt: Option[T], msg: => String): T = { + opt match { + case Some(x) => x + case _ => + throw new IllegalArgumentException("requirement failed: " + msg) + } + } + + def build: UnreducedTransaction = { + val boxesToSpend = _inputs.toIndexedSeq + val outputCandidates = _outputs.map(c => c.candidate).toIndexedSeq + require(!outputCandidates.isEmpty, "Output boxes are not specified") + + val dataInputBoxes = _dataInputs.toIndexedSeq + val dataInputs = _dataInputs.map(box => DataInput(box.id)).toIndexedSeq + require(_feeAmount.isEmpty || _feeAmount.get >= BlockchainParameters.MinFee, + s"When fee amount is defined it should be >= ${BlockchainParameters.MinFee}, got ${_feeAmount.get}") + val changeAddress = getDefined(_changeAddress, "Change address is not defined") + val inputBoxesSeq = boxesToSpend.map(eb => eb.box) + val requestedToBurn = _tokensToBurn.fold(IndexedSeq.empty[ErgoToken])(_.toIndexedSeq) + val burnTokens = Iso.isoErgoTokenSeqToLinkedMap.to(requestedToBurn).toMap + val rewardDelay = ctx.networkType match { + case NetworkType.Mainnet => BlockchainParameters.MinerRewardDelay_Mainnet + case NetworkType.Testnet => BlockchainParameters.MinerRewardDelay_Testnet + } + val tx = UnsignedTransactionBuilder.buildUnsignedTx( + inputs = inputBoxesSeq, dataInputs = dataInputs, outputCandidates = outputCandidates, + currentHeight = ctx.height, createFeeOutput = _feeAmount, + changeAddress = changeAddress, minChangeValue = MinChangeValue, + minerRewardDelay = rewardDelay, + burnTokens = burnTokens).get + + // the method above don't accept ContextExtension along with inputs, thus, after the + // transaction has been built we need to zip with the extensions that have been + // attached to the inputBoxes + val txWithExtensions = new UnsignedErgoLikeTransaction( + inputs = boxesToSpend.map(_.toUnsignedInput), + tx.dataInputs, tx.outputCandidates + ) + UnreducedTransaction(txWithExtensions, boxesToSpend, dataInputBoxes, requestedToBurn) + } + + def preHeader: PreHeader = _ph.getOrElse(ctx.headers(0).toPreHeader) + + def outBoxBuilder = OutBoxBuilder(this) + + def networkType: NetworkType = ctx.networkType + + def inputBoxes: IndexedSeq[ExtendedInputBox] = _inputs.toIndexedSeq + + def outputBoxes: IndexedSeq[OutBox] = _outputs.toIndexedSeq +} + +object UnsignedTransactionBuilder { + def apply(ctx: BlockchainContext): UnsignedTransactionBuilder = new UnsignedTransactionBuilder(ctx) + + private def validateStatelessChecks( + inputs: IndexedSeq[ErgoBox], dataInputs: IndexedSeq[DataInput], + outputCandidates: Seq[ErgoBoxCandidate]): Unit = { + // checks from ErgoTransaction.validateStateless + require(inputs.nonEmpty, "inputs cannot be empty") + require(outputCandidates.nonEmpty, "outputCandidates cannot be empty") + require(inputs.size <= Short.MaxValue, s"too many inputs - ${inputs.size} (max ${Short.MaxValue})") + require(dataInputs.size <= Short.MaxValue, s"too many dataInputs - ${dataInputs.size} (max ${Short.MaxValue})") + require(outputCandidates.size <= Short.MaxValue, + s"too many outputCandidates - ${outputCandidates.size} (max ${Short.MaxValue})") + require(outputCandidates.forall(_.value >= 0), s"outputCandidate.value must be >= 0") + val outputSumTry = Try(outputCandidates.map(_.value).reduce(java7.compat.Math.addExact(_, _))) + require(outputSumTry.isSuccess, s"Sum of transaction output values should not exceed ${Long.MaxValue}") + require(inputs.distinct.size == inputs.size, s"There should be no duplicate inputs") + } + + def collectOutputTokens(outputCandidates: Seq[ErgoBoxCandidate]): TokensMap = { + AssetUtils.mergeAssets( + initialMap = Map.empty[ModifierId, Long], + maps = outputCandidates.map(b => collTokensToMap(b.additionalTokens)): _*) + } + + def collTokensToMap(tokens: Coll[(TokenId, Long)]): TokensMap = + tokens.toArray.map(t => t._1.toModifierId -> t._2).toMap + + def tokensMapToColl(tokens: TokensMap): Coll[(TokenId, Long)] = + tokens.toArray.map { t => t._1.toTokenId -> t._2 }.toColl + + /** Creates unsigned transaction from given inputs and outputs adding outputs with miner's fee and change + * Runs required checks ensuring that resulted transaction will be successfully validated by a node. + * + * @param inputs - input boxes + * @param dataInputs - data inputs + * @param outputCandidates - output candidate boxes + * @param currentHeight - current height (used in miner's fee box and change box) + * @param createFeeOutput - optional fee amount to put in a new miner's fee box, which will be + * created by this method. If None, then feeOut is not created. + * @param changeAddress - address where to send change from the input boxes + * @param minChangeValue - minimum change value to send, otherwise add to miner's fee + * @param minerRewardDelay - reward delay to encode in miner's fee box + * @return unsigned transaction + */ + def buildUnsignedTx( + inputs: IndexedSeq[ErgoBox], + dataInputs: IndexedSeq[DataInput], + outputCandidates: Seq[ErgoBoxCandidate], + currentHeight: Int, + createFeeOutput: Option[Long], + changeAddress: ErgoAddress, + minChangeValue: Long, + minerRewardDelay: Int, + burnTokens: TokensMap = Map.empty + ): Try[UnsignedErgoLikeTransaction] = Try { + validateStatelessChecks(inputs, dataInputs, outputCandidates) + + // TODO: implement all appropriate checks from ErgoTransaction.validateStatefull + val feeAmount = createFeeOutput.getOrElse(0L) + require(createFeeOutput.fold(true)(_ > 0), s"expected fee amount > 0, got $feeAmount") + val inputTotal = inputs.map(_.value).sum + val outputSum = outputCandidates.map(_.value).sum + val outputTotal = outputSum + feeAmount + val changeAmt = inputTotal - outputTotal + require(changeAmt >= 0, s"total inputs $inputTotal is less then total outputs $outputTotal") + val firstInputBoxId = bytesToId(inputs(0).id) + val tokensOut = collectOutputTokens(outputCandidates) + // remove minted tokens if any + val tokensOutNoMinted = tokensOut.filterKeys(_ != firstInputBoxId) + val mintedTokensNum = tokensOut.size - tokensOutNoMinted.size + require(mintedTokensNum <= 1, s"Only one token can be minted, but found $mintedTokensNum") + require(burnTokens.values.forall(_ > 0), + s"Incorrect burnTokens specification, positive values are expected: $burnTokens") + // add burnTokens to target assets so that they are excluded from the change outputs + // thus total outputs assets will be reduced which is interpreted as _token burning_ + val tokensOutWithBurned = AssetUtils.mergeAssets(tokensOutNoMinted.toMap, burnTokens) + val boxSelector = new InputBoxesValidator + val selection = boxSelector.select[ErgoBox](inputs.iterator, _ => true, outputTotal, tokensOutWithBurned) match { + case Left(err) => throw new IllegalArgumentException( + s"failed to calculate change for outputTotal: $outputTotal, \ntokens: $tokensOut, \nburnTokens: $burnTokens, \ninputs: $inputs, \nreason: $err") + case Right(v) => v + } + // although we're only interested in change boxes, make sure selection contains exact inputs + assert(selection.inputBoxes == inputs, s"unexpected selected boxes, expected: $inputs, got ${selection.inputBoxes}") + val changeBoxes = selection.changeBoxes + val changeBoxesHaveTokens = changeBoxes.exists(_.tokens.nonEmpty) + val changeGoesToFee = changeAmt < minChangeValue && !changeBoxesHaveTokens + require(!changeGoesToFee || (changeAmt == 0 || createFeeOutput.isDefined), + s"""When change=$changeAmt < minChangeValue=$minChangeValue it is added to miner's fee, + |in this case createFeeOutput should be defined + |""".stripMargin) + val feeOutOpt = createFeeOutput.map { fee => + // if computed changeAmt is too small give it to miner as tips + val actualFee = if (changeGoesToFee) fee + changeAmt else fee + new ErgoBoxCandidate( + actualFee, + ErgoTreePredef.feeProposition(minerRewardDelay), + currentHeight + ) + } + val addedChangeOut = if (!changeGoesToFee) { + val script = changeAddress.script + changeBoxes.map { cb => + new ErgoBoxCandidate(cb.value, script, currentHeight, tokensMapToColl(cb.tokens)) + } + } else { + Seq() + } + val finalOutputCandidates = outputCandidates ++ feeOutOpt ++ addedChangeOut + new UnsignedErgoLikeTransaction( + inputs.map(b => new UnsignedInput(b.id)), + dataInputs, + finalOutputCandidates.toIndexedSeq + ) + } +} + diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala index 8173edd0e0..cc07e0c4f1 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala @@ -1,6 +1,6 @@ package org.ergoplatform.sdk.multisig -import org.ergoplatform.sdk.{BlockchainParameters, ProverBuilder, ReducedTransaction, SecretString} +import org.ergoplatform.sdk.{BlockchainContext, BlockchainParameters, ExtendedInputBox, ProverBuilder, ReducedTransaction, SecretString} import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks @@ -31,21 +31,21 @@ class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matcher val bob = createSigner("Bob secret") val carol = createSigner("Carol secret") - def createRtx(ctx: BlockchainContext, inputs: Seq[InputBox]): ReducedTransaction = { - val txB = ctx.newTxBuilder() - val output = txB.outBoxBuilder() - .value(inputs.map(_.getValue).sum - Parameters.MinFee) - .contract(truePropContract(ctx)).build() - val feeOut = txB.outBoxBuilder() - .value(Parameters.MinFee) - .contract(ctx.newContract(ErgoTreePredef.feeProposition())) - .build() - val unsigned = txB - .addInputs(inputs: _*) - .addOutputs(output, feeOut) - .build() - val prover = ctx.newProverBuilder.build // NOTE, prover without secrets - val reduced = prover.reduce(unsigned, 0) + def createRtx(ctx: BlockchainContext, inputs: Seq[ExtendedInputBox]): ReducedTransaction = { +// val txB = ctx.newTxBuilder() +// val output = txB.outBoxBuilder() +// .value(inputs.map(_.getValue).sum - Parameters.MinFee) +// .contract(truePropContract(ctx)).build() +// val feeOut = txB.outBoxBuilder() +// .value(Parameters.MinFee) +// .contract(ctx.newContract(ErgoTreePredef.feeProposition())) +// .build() +// val unsigned = txB +// .addInputs(inputs: _*) +// .addOutputs(output, feeOut) +// .build() +// val prover = ctx.newProverBuilder.build // NOTE, prover without secrets +// val reduced = prover.reduce(unsigned, 0) ??? } From 7d41fd22c966c720195aa8e7a0b043e7edad10dc Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 11 Jul 2023 16:44:43 +0200 Subject: [PATCH 05/27] multisig: creation of ReducedTransaction --- .../scala/scalan/util/CollectionUtil.scala | 3 + .../ergoplatform/sdk/ExtendedInputBox.scala | 21 ++++- .../org/ergoplatform/sdk/Extensions.scala | 4 + .../org/ergoplatform/sdk/OutBoxBuilder.scala | 2 +- .../org/ergoplatform/sdk/SigmaProver.scala | 5 +- .../sdk/UnsignedTransactionBuilder.scala | 4 +- .../ergoplatform/sdk/multisig/Signer.scala | 2 + .../sdk/multisig/SigningSpec.scala | 86 +++++++++++++++---- 8 files changed, 104 insertions(+), 23 deletions(-) diff --git a/common/shared/src/main/scala/scalan/util/CollectionUtil.scala b/common/shared/src/main/scala/scalan/util/CollectionUtil.scala index b9b79dd65c..0ee4030e2c 100644 --- a/common/shared/src/main/scala/scalan/util/CollectionUtil.scala +++ b/common/shared/src/main/scala/scalan/util/CollectionUtil.scala @@ -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 diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala index 04a3c578ec..05cd83daea 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala @@ -1,6 +1,7 @@ package org.ergoplatform.sdk import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, UnsignedInput} +import scorex.util.ModifierId import sigmastate.interpreter.ContextExtension /** Input ErgoBox paired with context variables (aka ContextExtensions). @@ -16,7 +17,25 @@ case class ExtendedInputBox( extension: ContextExtension ) { def toUnsignedInput: UnsignedInput = new UnsignedInput(box.id, extension) + def value: Long = box.value } -case class OutBox(candidate: ErgoBoxCandidate) +case class OutBox(candidate: ErgoBoxCandidate) { + /** + * Converts this box candidate into a new instance of {@link ExtendedInputBox} by + * associating it with the given transaction and output position. + * This method can be used to create input boxed from scratch, without + * retrieving them from the UTXOs. Thus created boxes can be indistinguishable from those + * loaded from blockchain node, and as result can be used to create new transactions. + * This method can also be used to create chains of transactions in advance + * + * @param txId the id of the transaction of which created the box which will be returned + * @param outputIndex zero-based position (index) of the box in the outputs of the transaction. + * @return a new {@link ExtendedInputBox} representing UTXO box as an input of a next transaction. + */ + def convertToInputWith(txId: String, boxIndex: Short): ExtendedInputBox = { + val box = candidate.toBox(ModifierId @@ txId, boxIndex) + ExtendedInputBox(box, ContextExtension.empty) + } +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala index 82d6724f12..426090b125 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala @@ -204,4 +204,8 @@ object Extensions { CPreHeader(h.version, h.parentId, h.timestamp, h.nBits, h.height, h.minerPk, h.votes) } } + + implicit class DoubleOps(val i: Double) extends AnyVal { + def erg: Long = (i * 1000000000L).toLong + } } diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/OutBoxBuilder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/OutBoxBuilder.scala index 4ee93c1cb3..5c2f531588 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/OutBoxBuilder.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/OutBoxBuilder.scala @@ -48,7 +48,7 @@ class OutBoxBuilder(val _txB: UnsignedTransactionBuilder) { this } - def build: OutBox = { + def build(): OutBox = { require(_contract != null, "Contract is not defined") val ergoBoxCandidate = OutBoxBuilder.createBoxCandidate( _value, _contract, _tokens.toSeq, _registers.toSeq, diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala index ac1fbe95d5..8090627083 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala @@ -16,7 +16,10 @@ import special.sigma.{BigInt, SigmaProp} class SigmaProver(_prover: AppkitProvingInterpreter, networkPrefix: NetworkPrefix) { implicit val ergoAddressEncoder: ErgoAddressEncoder = ErgoAddressEncoder(networkPrefix) - /** Returns the Pay-to-Public-Key (P2PK) address associated with the prover's public key. */ + /** Returns the Pay-to-Public-Key (P2PK) address associated with the prover's public key. + * The returned address corresponds to the master secret derived from the mnemonic + * phrase configured in the [[ProverBuilder]]. + */ def getP2PKAddress: P2PKAddress = { val pk = _prover.pubKeys(0) P2PKAddress(pk) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/UnsignedTransactionBuilder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/UnsignedTransactionBuilder.scala index 12d029d6c9..e48f26fd6f 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/UnsignedTransactionBuilder.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/UnsignedTransactionBuilder.scala @@ -75,7 +75,7 @@ class UnsignedTransactionBuilder(val ctx: BlockchainContext) { } } - def build: UnreducedTransaction = { + def build(): UnreducedTransaction = { val boxesToSpend = _inputs.toIndexedSeq val outputCandidates = _outputs.map(c => c.candidate).toIndexedSeq require(!outputCandidates.isEmpty, "Output boxes are not specified") @@ -111,7 +111,7 @@ class UnsignedTransactionBuilder(val ctx: BlockchainContext) { def preHeader: PreHeader = _ph.getOrElse(ctx.headers(0).toPreHeader) - def outBoxBuilder = OutBoxBuilder(this) + def outBoxBuilder: OutBoxBuilder = OutBoxBuilder(this) def networkType: NetworkType = ctx.networkType diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala index b7ab3eb7a8..e329153b21 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala @@ -1,8 +1,10 @@ package org.ergoplatform.sdk.multisig +import org.ergoplatform.P2PKAddress import org.ergoplatform.sdk.SigmaProver case class Signer(prover: SigmaProver) { + def masterAddress: P2PKAddress = prover.getP2PKAddress } object Signer { diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala index cc07e0c4f1..bde2e04fcf 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala @@ -1,11 +1,21 @@ package org.ergoplatform.sdk.multisig -import org.ergoplatform.sdk.{BlockchainContext, BlockchainParameters, ExtendedInputBox, ProverBuilder, ReducedTransaction, SecretString} +import org.ergoplatform.sdk.Extensions.{DoubleOps, HeaderOps} +import org.ergoplatform.sdk.NetworkType.Mainnet +import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext +import org.ergoplatform.{ErgoAddress, ErgoTreePredef, P2PKAddress} +import org.ergoplatform.sdk.{BlockchainContext, BlockchainParameters, ExtendedInputBox, OutBox, ProverBuilder, ReducedTransaction, SecretString, UnsignedTransactionBuilder} import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import scalan.util.CollectionUtil.AnyOps +import sigmastate.TestsBase +import sigmastate.Values.{Constant, ErgoTree} +import special.sigma.SigmaTestingData -class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers { +class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers + with TestsBase + with SigmaTestingData { val testMainnetParameters = BlockchainParameters( storageFeeFactor = 1250000, @@ -21,6 +31,16 @@ class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matcher blockVersion = 3 ) + val mockTxId = "f9e5ce5aa0d95f5d54a7bc89c46730d9662397067250aa18a0039631c0f5b809" + + val ctx = BlockchainContext(Mainnet, testMainnetParameters, + BlockchainStateContext( + sigmaLastHeaders = headers, + previousStateDigest = headers(0).stateRoot.digest, + sigmaPreHeader = preHeader + ) + ) + def createSigner(secret: String): Signer = Signer( ProverBuilder.forMainnet(testMainnetParameters) .withMnemonic(SecretString.create(secret), SecretString.empty()) @@ -30,27 +50,57 @@ class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matcher val alice = createSigner("Alice secret") val bob = createSigner("Bob secret") val carol = createSigner("Carol secret") + val david = createSigner("David secret") + + def createRtx( + ctx: BlockchainContext, inputs: Seq[ExtendedInputBox], + recepient: ErgoAddress, changeAddress: ErgoAddress): ReducedTransaction = { + val txB = UnsignedTransactionBuilder(ctx) + val output = txB.outBoxBuilder + .value(inputs.map(_.value).sum - BlockchainParameters.MinFee) + .contract(recepient.script).build() + val feeOut = txB.outBoxBuilder + .value(BlockchainParameters.MinFee) + .contract(ErgoTreePredef.feeProposition()) + .build() + val unsigned = txB + .addInputs(inputs: _*) + .addOutputs(output, feeOut) + .sendChangeTo(changeAddress) + .build() + // create a prover without secrets as they are not needed for reduction + val prover = ProverBuilder.forMainnet(ctx.parameters).build() + val reduced = prover.reduce(ctx.stateContext, unsigned, 0) + reduced + } + + def createTestOut( + ctx: BlockchainContext, + amount: Long, + contract: ErgoTree, + registers: Constant[_]* + ): OutBox = { + val out = UnsignedTransactionBuilder(ctx).outBoxBuilder + .value(amount) + .contract(contract) + .perform(b => if (registers.isEmpty) b else b.registers(registers: _*)) + .build() + out + } - def createRtx(ctx: BlockchainContext, inputs: Seq[ExtendedInputBox]): ReducedTransaction = { -// val txB = ctx.newTxBuilder() -// val output = txB.outBoxBuilder() -// .value(inputs.map(_.getValue).sum - Parameters.MinFee) -// .contract(truePropContract(ctx)).build() -// val feeOut = txB.outBoxBuilder() -// .value(Parameters.MinFee) -// .contract(ctx.newContract(ErgoTreePredef.feeProposition())) -// .build() -// val unsigned = txB -// .addInputs(inputs: _*) -// .addOutputs(output, feeOut) -// .build() -// val prover = ctx.newProverBuilder.build // NOTE, prover without secrets -// val reduced = prover.reduce(unsigned, 0) - ??? + def createInput(ctx: BlockchainContext, owner: Signer): ExtendedInputBox = { + val out = createTestOut(ctx, 1.erg, owner.prover.getP2PKAddress.script) + out.convertToInputWith(mockTxId, 0) } property("Signing workflow") { + val aliceInput = createInput(ctx, alice) + val bobInput = createInput(ctx, bob) + val carolInput = createInput(ctx, carol) + val inputs = Seq(aliceInput, bobInput, carolInput) + val reduced = createRtx(ctx, inputs, alice.masterAddress, david.masterAddress) + reduced shouldNot be(null) } } From 4c5b84c2d68c4cf3f0bcf9585c6b223a687e3bdc Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 5 Aug 2023 18:01:35 +0200 Subject: [PATCH 06/27] tx-signing-js: fixed test for isoUnsignedTransaction --- .../serialization/generators/ObjectGenerators.scala | 3 ++- sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala | 2 +- sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala | 5 ++--- .../src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala index 0e15cd525e..81a95a81b8 100644 --- a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala @@ -27,6 +27,7 @@ import special.sigma._ import java.math.BigInteger import scala.collection.compat.immutable.ArraySeq +import scala.collection.immutable.VectorMap import scala.collection.mutable.ListBuffer import scala.reflect.ClassTag @@ -238,7 +239,7 @@ trait ObjectGenerators extends TypeGenerators lazy val contextExtensionGen: Gen[ContextExtension] = for { values <- Gen.sequence(contextExtensionValuesGen(0, 5))(Buildable.buildableSeq) - } yield ContextExtension(values.toMap) + } yield ContextExtension(VectorMap.from(values.sortBy(_._1))) lazy val serializedProverResultGen: Gen[ProverResult] = for { bytes <- arrayOfRange(1, 100, arbByte.arbitrary) diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index e8bca398df..3339874e30 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -200,7 +200,7 @@ object Isos { implicit val isoContextExtension: Iso[contextExtensionMod.ContextExtension, ContextExtension] = new Iso[contextExtensionMod.ContextExtension, ContextExtension] { override def to(x: contextExtensionMod.ContextExtension): ContextExtension = { var map = new ListMap[Byte, Constant[SType]]() - val keys = js.Object.keys(x) + val keys = js.Object.keys(x).sorted for ( k <- keys ) { val id = k.toInt.toByte val c = isoHexStringToConstant.to(x.apply(id).get.get) diff --git a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala index e3a389be0d..f87e9e3bce 100644 --- a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala +++ b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala @@ -37,8 +37,7 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca ) def roundtrip[A,B](iso: Iso[A,B])(b: B): Unit = { - val invIso = iso.inverse - invIso.from(invIso.to(b)) shouldBe b + iso.to(iso.from(b)) shouldBe b } override implicit val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration(minSuccessful = 30) @@ -164,7 +163,7 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca } } - ignore("Iso.isoUnsignedTransaction") { + property("Iso.isoUnsignedTransaction") { forAll { (tx: UnsignedErgoLikeTransaction) => roundtrip(Isos.isoUnsignedTransaction)(tx) } diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala index 3dfb5c4157..72c75ace73 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala @@ -91,7 +91,7 @@ class ExtensionsSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matc val left = builder.pairColl(leftKeys, leftValues) val right = builder.pairColl(rightKeys, rightValues) val res = builder.outerJoin(left, right)(l => l._2 - 2, r => r._2 - 3, i => i._2._1 + 5) - val (ks, vs) = builder.unzip(res) + val (_, vs) = builder.unzip(res) vs.sum shouldBe (col.sum * 2 + col.map(_ + 5).sum) } // test(builder.fromItems(0)) From c4369ac680b37df734b5ddccdf9458b3c6e969e7 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 5 Aug 2023 18:51:49 +0200 Subject: [PATCH 07/27] tx-signing-js: implemented Fleet transaction reduction and signing --- .../scala/org/ergoplatform/sdk/js/Isos.scala | 89 ++++++++++++++----- .../org/ergoplatform/sdk/js/SigmaProver.scala | 50 ++++++----- .../org/ergoplatform/sdk/js/IsosSpec.scala | 31 +++++-- 3 files changed, 115 insertions(+), 55 deletions(-) diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index 3339874e30..8a0965eab2 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -1,20 +1,20 @@ package org.ergoplatform.sdk.js import org.ergoplatform.ErgoBox._ -import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, UnsignedErgoLikeTransaction, UnsignedInput} +import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, ErgoLikeTransaction, Input, UnsignedErgoLikeTransaction, UnsignedInput} import org.ergoplatform.sdk.{ExtendedInputBox, Iso} import org.ergoplatform.sdk.JavaHelpers.UniversalConverter import org.ergoplatform.sdk.wallet.protocol.context import scalan.RType -import scorex.crypto.authds.{ADDigest, ADKey} +import scorex.crypto.authds.ADKey import scorex.util.ModifierId import scorex.util.encode.Base16 import sigmastate.{AvlTreeData, AvlTreeFlags, SType} import sigmastate.Values.{Constant, GroupElementConstant} import sigmastate.eval.Extensions.ArrayOps import sigmastate.eval.{CAvlTree, CBigInt, CHeader, CPreHeader, Colls, Digest32Coll, Evaluation} -import sigmastate.fleetSdkCommon.{distEsmTypesBoxesMod => boxesMod, distEsmTypesCommonMod => commonMod, distEsmTypesContextExtensionMod => contextExtensionMod, distEsmTypesInputsMod => inputsMod, distEsmTypesRegistersMod => registersMod, distEsmTypesTokenMod => tokenMod} -import sigmastate.interpreter.ContextExtension +import sigmastate.fleetSdkCommon.{distEsmTypesBoxesMod => boxesMod, distEsmTypesCommonMod => commonMod, distEsmTypesContextExtensionMod => contextExtensionMod, distEsmTypesInputsMod => inputsMod, distEsmTypesProverResultMod => proverResultMod, distEsmTypesRegistersMod => registersMod, distEsmTypesTokenMod => tokenMod} +import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.serialization.{ErgoTreeSerializer, ValueSerializer} import special.collection.Coll import special.collection.Extensions.CollBytesOps @@ -24,7 +24,7 @@ import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box import sigmastate.fleetSdkCommon.distEsmTypesCommonMod.HexString import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters import sigmastate.fleetSdkCommon.distEsmTypesTokenMod.TokenAmount -import sigmastate.fleetSdkCommon.distEsmTypesTransactionsMod.UnsignedTransaction +import sigmastate.fleetSdkCommon.distEsmTypesTransactionsMod.{SignedTransaction, UnsignedTransaction} import java.math.BigInteger import scala.collection.immutable.ListMap @@ -227,6 +227,28 @@ object Isos { inputsMod.UnsignedInput(x.boxId.convertTo[boxesMod.BoxId], isoContextExtension.from(x.extension)) } + implicit val isoProverResult: Iso[proverResultMod.ProverResult, ProverResult] = new Iso[proverResultMod.ProverResult, ProverResult] { + override def to(a: proverResultMod.ProverResult): ProverResult = { + ProverResult( + proof = isoStringToArray.to(a.proofBytes), + extension = isoContextExtension.to(a.extension) + ) + } + override def from(b: ProverResult): proverResultMod.ProverResult = { + proverResultMod.ProverResult( + isoContextExtension.from(b.extension), + isoStringToArray.from(b.proof) + ) + } + } + + implicit val isoSignedInput: Iso[inputsMod.SignedInput, Input] = new Iso[inputsMod.SignedInput, Input] { + override def to(x: inputsMod.SignedInput): Input = + Input(x.boxId.convertTo[ErgoBox.BoxId], isoProverResult.to(x.spendingProof)) + override def from(x: Input): inputsMod.SignedInput = + inputsMod.SignedInput(x.boxId.convertTo[boxesMod.BoxId], isoProverResult.from(x.spendingProof)) + } + implicit val isoDataInput: Iso[inputsMod.DataInput, DataInput] = new Iso[inputsMod.DataInput, DataInput] { override def to(x: inputsMod.DataInput): DataInput = DataInput(x.boxId.convertTo[ErgoBox.BoxId]) @@ -350,25 +372,6 @@ object Isos { } } - // Implements Iso between UnsignedTransaction and UnsignedErgoLikeTransaction - val isoUnsignedTransaction: Iso[UnsignedTransaction, UnsignedErgoLikeTransaction] = - new Iso[UnsignedTransaction, UnsignedErgoLikeTransaction] { - override def to(a: UnsignedTransaction): UnsignedErgoLikeTransaction = { - new UnsignedErgoLikeTransaction( - inputs = isoArrayToIndexed(isoUnsignedInput).to(a.inputs), - dataInputs = isoArrayToIndexed(isoDataInput).to(a.dataInputs), - outputCandidates = isoArrayToIndexed(isoBoxCandidate).to(a.outputs), - ) - } - override def from(b: UnsignedErgoLikeTransaction): UnsignedTransaction = { - UnsignedTransaction( - inputs = isoArrayToIndexed(isoUnsignedInput).from(b.inputs), - dataInputs = isoArrayToIndexed(isoDataInput).from(b.dataInputs), - outputs = isoArrayToIndexed(isoBoxCandidate).from(b.outputCandidates) - ) - } - } - val isoBox: Iso[Box[commonMod.Amount], ErgoBox] = new Iso[Box[commonMod.Amount], ErgoBox] { override def to(x: Box[commonMod.Amount]): ErgoBox = { val ergoBox = new ErgoBox( @@ -436,4 +439,42 @@ object Isos { ) } } + + val isoUnsignedTransaction: Iso[UnsignedTransaction, UnsignedErgoLikeTransaction] = + new Iso[UnsignedTransaction, UnsignedErgoLikeTransaction] { + override def to(a: UnsignedTransaction): UnsignedErgoLikeTransaction = { + new UnsignedErgoLikeTransaction( + inputs = isoArrayToIndexed(isoUnsignedInput).to(a.inputs), + dataInputs = isoArrayToIndexed(isoDataInput).to(a.dataInputs), + outputCandidates = isoArrayToIndexed(isoBoxCandidate).to(a.outputs), + ) + } + + override def from(b: UnsignedErgoLikeTransaction): UnsignedTransaction = { + UnsignedTransaction( + inputs = isoArrayToIndexed(isoUnsignedInput).from(b.inputs), + dataInputs = isoArrayToIndexed(isoDataInput).from(b.dataInputs), + outputs = isoArrayToIndexed(isoBoxCandidate).from(b.outputCandidates) + ) + } + } + + val isoSignedTransaction: Iso[SignedTransaction, ErgoLikeTransaction] = + new Iso[SignedTransaction, ErgoLikeTransaction] { + override def to(a: SignedTransaction): ErgoLikeTransaction = { + new ErgoLikeTransaction( + inputs = isoArrayToIndexed(isoSignedInput).to(a.inputs), + dataInputs = isoArrayToIndexed(isoDataInput).to(a.dataInputs), + outputCandidates = isoArrayToIndexed(isoBox).to(a.outputs), + ) + } + + override def from(tx: ErgoLikeTransaction): SignedTransaction = { + val inputs = isoArrayToIndexed(isoSignedInput).from(tx.inputs) + val dataInputs = isoArrayToIndexed(isoDataInput).from(tx.dataInputs) + val outputs = isoArrayToIndexed(isoBox).from(tx.outputs) + SignedTransaction(dataInputs, tx.id, inputs, outputs) + } + } + } \ No newline at end of file diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala index 41bf634473..10f0d135ef 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala @@ -1,6 +1,7 @@ package org.ergoplatform.sdk.js import org.ergoplatform.sdk +import org.ergoplatform.sdk.SignedTransaction import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box import sigmastate.fleetSdkCommon.{distEsmTypesCommonMod => commonMod, distEsmTypesInputsMod => inputsMod, distEsmTypesTokenMod => tokenMod, distEsmTypesTransactionsMod => transactionsMod} @@ -12,45 +13,46 @@ import scala.scalajs.js.annotation.JSExportTopLevel class SigmaProver(_prover: sdk.SigmaProver) extends js.Object { import Isos._ - //TODO finish implementation + /** Reduces the transaction to the reduced form, which is ready to be signed. + * @param stateCtx blockchain state context + * @param unsignedTx unsigned transaction to be reduced (created by Fleet builders) + * @param boxesToSpend boxes to be spent by the transaction + * @param dataInputs data inputs to be used by the transaction + * @param tokensToBurn tokens to be burned by the transaction + * @param baseCost base cost of the transaction + * @return reduced transaction + */ def reduce( stateCtx: BlockchainStateContext, unsignedTx: transactionsMod.UnsignedTransaction, boxesToSpend: js.Array[inputsMod.EIP12UnsignedInput], + dataInputs: js.Array[Box[commonMod.Amount]], + tokensToBurn: js.Array[tokenMod.TokenAmount[commonMod.Amount]], baseCost: Int): ReducedTransaction = { - val tx = sdk.UnreducedTransaction( + val unreducedTx = sdk.UnreducedTransaction( unsignedTx = isoUnsignedTransaction.to(unsignedTx), boxesToSpend = isoArrayToIndexed(isoEIP12UnsignedInput).to(boxesToSpend), - dataInputs = IndexedSeq.empty, - tokensToBurn = IndexedSeq.empty + dataInputs = isoArrayToIndexed(isoBox).to(dataInputs), + tokensToBurn = isoArrayToIndexed(isoToken.andThen(sdk.Iso.isoErgoTokenToPair.inverse)).to(tokensToBurn) ) - _prover.reduce( - isoBlockchainStateContext.to(stateCtx), - tx, - baseCost - ) - new ReducedTransaction + val ctx = isoBlockchainStateContext.to(stateCtx) + val reducedTx = _prover.reduce(ctx, unreducedTx, baseCost) + new ReducedTransaction(reducedTx) } - def reduceTransaction( - unsignedTx: transactionsMod.UnsignedTransaction, - boxesToSpend: js.Array[inputsMod.EIP12UnsignedInput], - dataBoxes: js.Array[Box[commonMod.Amount]], - stateDigest: String, - baseCost: Int, - tokensToBurn: js.Array[tokenMod.TokenAmount[commonMod.Amount]] - ): (ReducedTransaction, Int) = { - val tx = Isos.isoUnsignedTransaction.to(unsignedTx) -// val inputs: = boxesToSpend.map(isoEIP12UnsignedInput.to).toArray - - (new ReducedTransaction, 0) + /** Signs the reduced transaction. + * @param reducedTx reduced transaction to be signed + * @return signed transaction containting all the required proofs (signatures) + */ + def signReduced(reducedTx: ReducedTransaction): transactionsMod.SignedTransaction = { + val signed = _prover.signReduced(reducedTx._tx) + isoSignedTransaction.from(signed.ergoTx) } - } //TODO finish implementation @JSExportTopLevel("ReducedTransaction") -class ReducedTransaction +class ReducedTransaction(private [js] val _tx: sdk.ReducedTransaction) //TODO finish implementation @JSExportTopLevel("SigmaProverObj") diff --git a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala index f87e9e3bce..47eedf2706 100644 --- a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala +++ b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala @@ -11,7 +11,7 @@ import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import sigmastate.SType import sigmastate.Values.Constant import sigmastate.eval.Colls -import sigmastate.interpreter.ContextExtension +import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.serialization.generators.ObjectGenerators import special.collection.Coll import special.sigma @@ -102,12 +102,24 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca } } + property("Iso.isoProverResult") { + forAll { (c: ProverResult) => + roundtrip(Isos.isoProverResult)(c) + } + } + property("Iso.isoUnsignedInput") { forAll { (c: UnsignedInput) => roundtrip(Isos.isoUnsignedInput)(c) } } + property("Iso.isoSignedInput") { + forAll { (c: Input) => + roundtrip(Isos.isoSignedInput)(c) + } + } + property("Iso.isoDataInput") { forAll { (c: DataInput) => roundtrip(Isos.isoDataInput)(c) @@ -163,12 +175,6 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca } } - property("Iso.isoUnsignedTransaction") { - forAll { (tx: UnsignedErgoLikeTransaction) => - roundtrip(Isos.isoUnsignedTransaction)(tx) - } - } - property("Iso.isoBox") { forAll { (b: ErgoBox) => roundtrip(Isos.isoBox)(b) @@ -181,4 +187,15 @@ class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with Sca } } + property("Iso.isoUnsignedTransaction") { + forAll { (tx: UnsignedErgoLikeTransaction) => + roundtrip(Isos.isoUnsignedTransaction)(tx) + } + } + + property("Iso.isoSignedTransaction") { + forAll { (tx: ErgoLikeTransaction) => + roundtrip(Isos.isoSignedTransaction)(tx) + } + } } From 964feda4b57ad47ca0b0c8a7e53d2cb8f3777692 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 6 Aug 2023 16:41:02 +0200 Subject: [PATCH 08/27] tx-signing-js: fixed tests --- .../generators/ObjectGenerators.scala | 4 ++-- .../scala/org/ergoplatform/sdk/js/Isos.scala | 16 ++++++++-------- .../org/ergoplatform/sdk/js/ProverBuilder.scala | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala index 81a95a81b8..fd88370d93 100644 --- a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala @@ -27,7 +27,7 @@ import special.sigma._ import java.math.BigInteger import scala.collection.compat.immutable.ArraySeq -import scala.collection.immutable.VectorMap +import scala.collection.immutable.ListMap import scala.collection.mutable.ListBuffer import scala.reflect.ClassTag @@ -239,7 +239,7 @@ trait ObjectGenerators extends TypeGenerators lazy val contextExtensionGen: Gen[ContextExtension] = for { values <- Gen.sequence(contextExtensionValuesGen(0, 5))(Buildable.buildableSeq) - } yield ContextExtension(VectorMap.from(values.sortBy(_._1))) + } yield ContextExtension(ListMap.empty ++ values.sortBy(_._1)) lazy val serializedProverResultGen: Gen[ProverResult] = for { bytes <- arrayOfRange(1, 100, arbByte.arbitrary) diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index 8a0965eab2..4369f5e0f7 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -1,30 +1,30 @@ package org.ergoplatform.sdk.js import org.ergoplatform.ErgoBox._ -import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, ErgoLikeTransaction, Input, UnsignedErgoLikeTransaction, UnsignedInput} -import org.ergoplatform.sdk.{ExtendedInputBox, Iso} import org.ergoplatform.sdk.JavaHelpers.UniversalConverter import org.ergoplatform.sdk.wallet.protocol.context +import org.ergoplatform.sdk.{ExtendedInputBox, Iso} +import org.ergoplatform._ import scalan.RType import scorex.crypto.authds.ADKey import scorex.util.ModifierId import scorex.util.encode.Base16 -import sigmastate.{AvlTreeData, AvlTreeFlags, SType} import sigmastate.Values.{Constant, GroupElementConstant} import sigmastate.eval.Extensions.ArrayOps import sigmastate.eval.{CAvlTree, CBigInt, CHeader, CPreHeader, Colls, Digest32Coll, Evaluation} +import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box +import sigmastate.fleetSdkCommon.distEsmTypesCommonMod.HexString +import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters +import sigmastate.fleetSdkCommon.distEsmTypesTokenMod.TokenAmount +import sigmastate.fleetSdkCommon.distEsmTypesTransactionsMod.{SignedTransaction, UnsignedTransaction} import sigmastate.fleetSdkCommon.{distEsmTypesBoxesMod => boxesMod, distEsmTypesCommonMod => commonMod, distEsmTypesContextExtensionMod => contextExtensionMod, distEsmTypesInputsMod => inputsMod, distEsmTypesProverResultMod => proverResultMod, distEsmTypesRegistersMod => registersMod, distEsmTypesTokenMod => tokenMod} import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.serialization.{ErgoTreeSerializer, ValueSerializer} +import sigmastate.{AvlTreeData, AvlTreeFlags, SType} import special.collection.Coll import special.collection.Extensions.CollBytesOps import special.sigma import special.sigma.GroupElement -import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box -import sigmastate.fleetSdkCommon.distEsmTypesCommonMod.HexString -import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters -import sigmastate.fleetSdkCommon.distEsmTypesTokenMod.TokenAmount -import sigmastate.fleetSdkCommon.distEsmTypesTransactionsMod.{SignedTransaction, UnsignedTransaction} import java.math.BigInteger import scala.collection.immutable.ListMap diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala index 4d35d033aa..ffbc0e1a41 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala @@ -2,7 +2,7 @@ package org.ergoplatform.sdk.js import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix import org.ergoplatform.sdk -import org.ergoplatform.sdk.{BlockchainParameters, SecretString} +import org.ergoplatform.sdk.SecretString import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel @@ -11,8 +11,8 @@ import sigmastate.eval.SigmaDsl /** Equivalent of [[sdk.ProverBuilder]] available from JS. */ @JSExportTopLevel("ProverBuilder") -class ProverBuilder(parameters: BlockchainParameters, networkPrefix: NetworkPrefix) extends js.Object { - val _builder = new sdk.ProverBuilder(parameters, networkPrefix) +class ProverBuilder(parameters: BlockchainParameters, network: NetworkPrefix) extends js.Object { + val _builder = new sdk.ProverBuilder(parameters, network) /** Configure this builder to use the given seed when building a new prover. * From ae39af31546a80fcfb65fa3f5bc3f1f2989eb69d Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 6 Aug 2023 21:31:59 +0200 Subject: [PATCH 09/27] tx-signing-js: fixed tests (2) --- .../test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala | 1 - .../scala/sigmastate/interpreter/InterpreterContext.scala | 7 ++++--- .../serialization/generators/ObjectGenerators.scala | 6 +++--- .../src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala | 5 +++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/interpreter/js/src/test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala b/interpreter/js/src/test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala index 9fc4e43d86..63839e67ef 100644 --- a/interpreter/js/src/test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala +++ b/interpreter/js/src/test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala @@ -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 diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala index 73e0a96370..937aa37fda 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala @@ -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 @@ -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) } @@ -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:_*)) } } diff --git a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala index fd88370d93..92538aca22 100644 --- a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala @@ -27,7 +27,7 @@ import special.sigma._ import java.math.BigInteger import scala.collection.compat.immutable.ArraySeq -import scala.collection.immutable.ListMap +import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.reflect.ClassTag @@ -238,8 +238,8 @@ trait ObjectGenerators extends TypeGenerators val unsignedShortGen: Gen[Short] = Gen.chooseNum(0, Short.MaxValue).map(_.toShort) lazy val contextExtensionGen: Gen[ContextExtension] = for { - values <- Gen.sequence(contextExtensionValuesGen(0, 5))(Buildable.buildableSeq) - } yield ContextExtension(ListMap.empty ++ values.sortBy(_._1)) + values: collection.Seq[(Byte, EvaluatedValue[SType])] <- Gen.sequence(contextExtensionValuesGen(0, 5))(Buildable.buildableSeq) + } yield ContextExtension(mutable.LinkedHashMap[Byte, EvaluatedValue[SType]](values.sortBy(_._1).toSeq:_*)) lazy val serializedProverResultGen: Gen[ProverResult] = for { bytes <- arrayOfRange(1, 100, arbByte.arbitrary) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala index 42d25e09d4..aae3410df4 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -1,7 +1,6 @@ package org.ergoplatform.sdk import java.math.BigInteger - import cats.syntax.either._ import io.circe._ import io.circe.syntax._ @@ -33,6 +32,8 @@ import org.ergoplatform.ErgoLikeTransactionTemplate import org.ergoplatform.ErgoBoxCandidate import org.ergoplatform.ErgoLikeContext +import scala.collection.mutable + trait JsonCodecs { def fromTry[T](tryResult: Try[T])(implicit cursor: ACursor): Either[DecodingFailure, T] = { @@ -244,7 +245,7 @@ trait JsonCodecs { implicit val contextExtensionDecoder: Decoder[ContextExtension] = Decoder.instance({ cursor => for { - values <- cursor.as[Map[Byte, EvaluatedValue[SType]]] + values <- cursor.as[mutable.LinkedHashMap[Byte, EvaluatedValue[SType]]] } yield ContextExtension(values) }) From 6746d727f021bd9a00c9c75c0b82f8326a932bcc Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 7 Aug 2023 22:12:16 +0200 Subject: [PATCH 10/27] tx-signing-js: upgrade fleed-sdk to 0.1.0 --- build.sbt | 4 +-- .../org/ergoplatform/sdk/js/ErgoTree.scala | 2 -- .../scala/org/ergoplatform/sdk/js/Isos.scala | 18 +++++----- .../org/ergoplatform/sdk/js/SigmaProver.scala | 4 +-- .../scala/org/ergoplatform/sdk/js/Value.scala | 3 +- sigma-js/package-lock.json | 34 +++++++++---------- sigma-js/package.json | 4 +-- 7 files changed, 34 insertions(+), 35 deletions(-) diff --git a/build.sbt b/build.sbt index 371536017e..9787110c4c 100644 --- a/build.sbt +++ b/build.sbt @@ -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)) => @@ -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.0" ) ) diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ErgoTree.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ErgoTree.scala index 810cc1ea81..8469545d95 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ErgoTree.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ErgoTree.scala @@ -1,8 +1,6 @@ package org.ergoplatform.sdk.js -import scorex.util.encode.Base16 import sigmastate.Values -import sigmastate.serialization.ErgoTreeSerializer import scala.scalajs.js import scala.scalajs.js.JSConverters.JSRichIterableOnce diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index 4369f5e0f7..c5ca9692ef 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -343,8 +343,8 @@ object Isos { } } - implicit val isoBoxCandidate: Iso[boxesMod.BoxCandidate[commonMod.Amount], ErgoBoxCandidate] = new Iso[boxesMod.BoxCandidate[commonMod.Amount], ErgoBoxCandidate] { - override def to(x: boxesMod.BoxCandidate[commonMod.Amount]): ErgoBoxCandidate = { + implicit val isoBoxCandidate: Iso[boxesMod.BoxCandidate[commonMod.Amount, NonMandatoryRegisters], ErgoBoxCandidate] = new Iso[boxesMod.BoxCandidate[commonMod.Amount, NonMandatoryRegisters], ErgoBoxCandidate] { + override def to(x: boxesMod.BoxCandidate[commonMod.Amount, NonMandatoryRegisters]): ErgoBoxCandidate = { val ergoBoxCandidate = new ErgoBoxCandidate( value = isoAmount.to(x.value), ergoTree = { @@ -358,11 +358,11 @@ object Isos { ergoBoxCandidate } - override def from(x: ErgoBoxCandidate): boxesMod.BoxCandidate[commonMod.Amount] = { + override def from(x: ErgoBoxCandidate): boxesMod.BoxCandidate[commonMod.Amount, NonMandatoryRegisters] = { val ergoTree = ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(x.ergoTree) val ergoTreeStr = Base16.encode(ergoTree) val assets = isoTokenArray.from(x.additionalTokens) - boxesMod.BoxCandidate[commonMod.Amount]( + boxesMod.BoxCandidate[commonMod.Amount, NonMandatoryRegisters]( ergoTree = ergoTreeStr, value = isoAmount.from(x.value), assets = assets, @@ -372,8 +372,8 @@ object Isos { } } - val isoBox: Iso[Box[commonMod.Amount], ErgoBox] = new Iso[Box[commonMod.Amount], ErgoBox] { - override def to(x: Box[commonMod.Amount]): ErgoBox = { + val isoBox: Iso[Box[commonMod.Amount, NonMandatoryRegisters], ErgoBox] = new Iso[Box[commonMod.Amount, NonMandatoryRegisters], ErgoBox] { + override def to(x: Box[commonMod.Amount, NonMandatoryRegisters]): ErgoBox = { val ergoBox = new ErgoBox( value = isoAmount.to(x.value), ergoTree = { @@ -389,11 +389,11 @@ object Isos { ergoBox } - override def from(x: ErgoBox): Box[commonMod.Amount] = { + override def from(x: ErgoBox): Box[commonMod.Amount, NonMandatoryRegisters] = { val ergoTree = ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(x.ergoTree) val ergoTreeStr = Base16.encode(ergoTree) val assets = isoTokenArray.from(x.additionalTokens) - Box[commonMod.Amount]( + Box[commonMod.Amount, NonMandatoryRegisters]( boxId = Base16.encode(x.id), ergoTree = ergoTreeStr, value = isoAmount.from(x.value), @@ -409,7 +409,7 @@ object Isos { val isoEIP12UnsignedInput: Iso[inputsMod.EIP12UnsignedInput, ExtendedInputBox] = new Iso[inputsMod.EIP12UnsignedInput, ExtendedInputBox] { override def to(x: inputsMod.EIP12UnsignedInput): ExtendedInputBox = { - val box = Box[commonMod.Amount]( + val box = Box[commonMod.Amount, NonMandatoryRegisters]( boxId = x.boxId, ergoTree = x.ergoTree, value = x.value, diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala index 10f0d135ef..621fae4208 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala @@ -1,8 +1,8 @@ package org.ergoplatform.sdk.js import org.ergoplatform.sdk -import org.ergoplatform.sdk.SignedTransaction import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box +import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters import sigmastate.fleetSdkCommon.{distEsmTypesCommonMod => commonMod, distEsmTypesInputsMod => inputsMod, distEsmTypesTokenMod => tokenMod, distEsmTypesTransactionsMod => transactionsMod} import scala.scalajs.js @@ -26,7 +26,7 @@ class SigmaProver(_prover: sdk.SigmaProver) extends js.Object { stateCtx: BlockchainStateContext, unsignedTx: transactionsMod.UnsignedTransaction, boxesToSpend: js.Array[inputsMod.EIP12UnsignedInput], - dataInputs: js.Array[Box[commonMod.Amount]], + dataInputs: js.Array[Box[commonMod.Amount, NonMandatoryRegisters]], tokensToBurn: js.Array[tokenMod.TokenAmount[commonMod.Amount]], baseCost: Int): ReducedTransaction = { val unreducedTx = sdk.UnreducedTransaction( diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala index b926cc54aa..26e167fa0c 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala @@ -10,6 +10,7 @@ import sigmastate.crypto.Platform import sigmastate.eval.{CAvlTree, CGroupElement, CSigmaProp, Colls, CostingBox, Evaluation, SigmaDsl} import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box import sigmastate.fleetSdkCommon.distEsmTypesCommonMod +import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters import sigmastate.lang.DeserializationSigmaBuilder import sigmastate.serialization.{ConstantSerializer, DataSerializer, SigmaSerializer} import special.collection.{Coll, CollType} @@ -97,7 +98,7 @@ object Value extends js.Object { val t = data.asInstanceOf[AvlTree] Isos.isoAvlTree.to(t) case special.sigma.BoxRType => - val t = data.asInstanceOf[Box[distEsmTypesCommonMod.Amount]] + val t = data.asInstanceOf[Box[distEsmTypesCommonMod.Amount, NonMandatoryRegisters]] SigmaDsl.Box(Isos.isoBox.to(t)) case ct: CollType[a] => val xs = data.asInstanceOf[js.Array[Any]] diff --git a/sigma-js/package-lock.json b/sigma-js/package-lock.json index 3a56eb5323..a43cc7b2d5 100644 --- a/sigma-js/package-lock.json +++ b/sigma-js/package-lock.json @@ -1,17 +1,17 @@ { "name": "sigmastate-js", - "version": "0.2.0", + "version": "0.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "sigmastate-js", - "version": "0.2.0", + "version": "0.2.1", "license": "MIT", "dependencies": { - "@fleet-sdk/common": "0.1.0-alpha.14", + "@fleet-sdk/common": "0.1.0", "@noble/hashes": "1.1.4", - "sigmajs-crypto-facade": "0.0.6" + "sigmajs-crypto-facade": "0.0.7" }, "devDependencies": { "jest": "^29.0.3", @@ -592,11 +592,11 @@ "dev": true }, "node_modules/@fleet-sdk/common": { - "version": "0.1.0-alpha.14", - "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.0-alpha.14.tgz", - "integrity": "sha512-w6AMHe77FaSb759e3EwcOVRQ/lEsCdr1pXq376B+T80do5pUcWjrlrIEKKNrEJPCyqW8nNClxIqBVqkFuPbbMw==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.0.tgz", + "integrity": "sha512-UlhVFBwqSXrTixs/p+g51SGZoWKT0J/q9oAdYikJ/RCQu8rY9K+4e1o6w2s177qKscXG+jW5usAjocj2d3fLWg==", "engines": { - "node": ">=10" + "node": ">=14" } }, "node_modules/@istanbuljs/load-nyc-config": { @@ -3163,9 +3163,9 @@ } }, "node_modules/sigmajs-crypto-facade": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/sigmajs-crypto-facade/-/sigmajs-crypto-facade-0.0.6.tgz", - "integrity": "sha512-Nbz+CZ0rgMvDN76C3bQjrFHO30qSHE9Fti+Co4WvNzL9UTaxSOlfSFJy89u0QX+IkVNvXd35RqiyiekLpTUSsA==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/sigmajs-crypto-facade/-/sigmajs-crypto-facade-0.0.7.tgz", + "integrity": "sha512-4XK8ZS9NKAbo8aGnU6o5GkBW6Upl8+OK8A1KreVDMAamfvZ0iq4LoVH8rHaeEPf9moVtaC4QZY5RYI+0OwiydA==", "dependencies": { "@noble/hashes": "^1.1.4" }, @@ -3994,9 +3994,9 @@ "dev": true }, "@fleet-sdk/common": { - "version": "0.1.0-alpha.14", - "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.0-alpha.14.tgz", - "integrity": "sha512-w6AMHe77FaSb759e3EwcOVRQ/lEsCdr1pXq376B+T80do5pUcWjrlrIEKKNrEJPCyqW8nNClxIqBVqkFuPbbMw==" + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.0.tgz", + "integrity": "sha512-UlhVFBwqSXrTixs/p+g51SGZoWKT0J/q9oAdYikJ/RCQu8rY9K+4e1o6w2s177qKscXG+jW5usAjocj2d3fLWg==" }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -5935,9 +5935,9 @@ } }, "sigmajs-crypto-facade": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/sigmajs-crypto-facade/-/sigmajs-crypto-facade-0.0.6.tgz", - "integrity": "sha512-Nbz+CZ0rgMvDN76C3bQjrFHO30qSHE9Fti+Co4WvNzL9UTaxSOlfSFJy89u0QX+IkVNvXd35RqiyiekLpTUSsA==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/sigmajs-crypto-facade/-/sigmajs-crypto-facade-0.0.7.tgz", + "integrity": "sha512-4XK8ZS9NKAbo8aGnU6o5GkBW6Upl8+OK8A1KreVDMAamfvZ0iq4LoVH8rHaeEPf9moVtaC4QZY5RYI+0OwiydA==", "requires": { "@noble/hashes": "^1.1.4" } diff --git a/sigma-js/package.json b/sigma-js/package.json index b1ff6c4df7..a0f5dde4f0 100644 --- a/sigma-js/package.json +++ b/sigma-js/package.json @@ -36,8 +36,8 @@ }, "dependencies": { "@noble/hashes": "1.1.4", - "@fleet-sdk/common": "0.1.0-alpha.14", - "sigmajs-crypto-facade": "0.0.6" + "@fleet-sdk/common": "0.1.0", + "sigmajs-crypto-facade": "0.0.7" }, "devDependencies": { "jest": "^29.0.3", From f953e7d7dd8394b0581190c541843239bcd6819b Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 8 Aug 2023 00:43:42 +0200 Subject: [PATCH 11/27] tx-signing-js: upgrade fleed-sdk to 0.1.3 --- build.sbt | 2 +- sigma-js/package-lock.json | 14 +++++++------- sigma-js/package.json | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index 9787110c4c..9d43e9e0d2 100644 --- a/build.sbt +++ b/build.sbt @@ -282,7 +282,7 @@ lazy val interpreterJS = interpreter.js }, Compile / npmDependencies ++= Seq( "sigmajs-crypto-facade" -> sigmajsCryptoFacadeVersion, - "@fleet-sdk/common" -> "0.1.0" + "@fleet-sdk/common" -> "0.1.3" ) ) diff --git a/sigma-js/package-lock.json b/sigma-js/package-lock.json index a43cc7b2d5..7ed0157e8f 100644 --- a/sigma-js/package-lock.json +++ b/sigma-js/package-lock.json @@ -9,7 +9,7 @@ "version": "0.2.1", "license": "MIT", "dependencies": { - "@fleet-sdk/common": "0.1.0", + "@fleet-sdk/common": "0.1.3", "@noble/hashes": "1.1.4", "sigmajs-crypto-facade": "0.0.7" }, @@ -592,9 +592,9 @@ "dev": true }, "node_modules/@fleet-sdk/common": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.0.tgz", - "integrity": "sha512-UlhVFBwqSXrTixs/p+g51SGZoWKT0J/q9oAdYikJ/RCQu8rY9K+4e1o6w2s177qKscXG+jW5usAjocj2d3fLWg==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.3.tgz", + "integrity": "sha512-gYEkHhgGpgIcmCL3nCw8E9zHkT2WLmR+mPdxFlUE6fwcwISURbJrP6W9mF7D5Y0ShAP5Is2w3edh7AyIc7ctIQ==", "engines": { "node": ">=14" } @@ -3994,9 +3994,9 @@ "dev": true }, "@fleet-sdk/common": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.0.tgz", - "integrity": "sha512-UlhVFBwqSXrTixs/p+g51SGZoWKT0J/q9oAdYikJ/RCQu8rY9K+4e1o6w2s177qKscXG+jW5usAjocj2d3fLWg==" + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@fleet-sdk/common/-/common-0.1.3.tgz", + "integrity": "sha512-gYEkHhgGpgIcmCL3nCw8E9zHkT2WLmR+mPdxFlUE6fwcwISURbJrP6W9mF7D5Y0ShAP5Is2w3edh7AyIc7ctIQ==" }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", diff --git a/sigma-js/package.json b/sigma-js/package.json index a0f5dde4f0..032ac78a90 100644 --- a/sigma-js/package.json +++ b/sigma-js/package.json @@ -36,7 +36,7 @@ }, "dependencies": { "@noble/hashes": "1.1.4", - "@fleet-sdk/common": "0.1.0", + "@fleet-sdk/common": "0.1.3", "sigmajs-crypto-facade": "0.0.7" }, "devDependencies": { From b948a31df8d128b72af668c5e9d111d11dc366a9 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 14 Aug 2023 17:02:59 +0200 Subject: [PATCH 12/27] tx-signing-js: fixes after merge --- .../src/test/scala/sigmastate/lang/SigmaParserTest.scala | 9 +++++---- sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala | 8 ++++---- .../org/ergoplatform/sdk/AppkitProvingInterpreter.scala | 1 - 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala b/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala index e28cd71473..42d02fefb5 100644 --- a/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala +++ b/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala @@ -1,7 +1,8 @@ package sigmastate.lang import fastparse.Parsed -import org.ergoplatform.{ErgoAddressEncoder, ErgoBox} +import fastparse.Parsed.Failure +import org.ergoplatform.ErgoBox import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks @@ -25,9 +26,9 @@ class SigmaParserTest extends AnyPropSpec with ScalaCheckPropertyChecks with Mat v.sourceContext.isDefined shouldBe true assertSrcCtxForAllNodes(v) v - case f@Parsed.Failure(_, _, extra) => - val traced = extra.traced - println(s"\nTRACE: ${traced.trace}") + case f: Failure => + val traced = f.extra.trace() + println(s"\nTRACE: ${traced.msg}") f.get // force show error diagnostics } } diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index 2dd636681c..4ee64c9b96 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -2,8 +2,8 @@ package org.ergoplatform.sdk.js import org.ergoplatform.ErgoBox._ import org.ergoplatform.sdk.JavaHelpers.UniversalConverter -import org.ergoplatform.sdk.wallet.protocol.context.{CErgoLikeStateContext, ErgoLikeStateContext} import org.ergoplatform.sdk.{ExtendedInputBox, Iso} +import org.ergoplatform.sdk.wallet.protocol.context import org.ergoplatform._ import scalan.RType import scorex.crypto.authds.ADKey @@ -16,9 +16,9 @@ import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box import sigmastate.fleetSdkCommon.distEsmTypesCommonMod.HexString import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters import sigmastate.fleetSdkCommon.distEsmTypesTokenMod.TokenAmount -import sigmastate.fleetSdkCommon.distEsmTypesTransactionsMod.UnsignedTransaction -import sigmastate.fleetSdkCommon.{distEsmTypesBoxesMod => boxesMod, distEsmTypesCommonMod => commonMod, distEsmTypesContextExtensionMod => contextExtensionMod, distEsmTypesInputsMod => inputsMod, distEsmTypesRegistersMod => registersMod, distEsmTypesTokenMod => tokenMod} -import sigmastate.interpreter.ContextExtension +import sigmastate.fleetSdkCommon.distEsmTypesTransactionsMod.{SignedTransaction, UnsignedTransaction} +import sigmastate.fleetSdkCommon.{distEsmTypesBoxesMod => boxesMod, distEsmTypesCommonMod => commonMod, distEsmTypesContextExtensionMod => contextExtensionMod, distEsmTypesInputsMod => inputsMod, distEsmTypesProverResultMod => proverResultMod, distEsmTypesRegistersMod => registersMod, distEsmTypesTokenMod => tokenMod} +import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.serialization.{ErgoTreeSerializer, ValueSerializer} import sigmastate.{AvlTreeData, AvlTreeFlags, SType} import special.collection.Coll diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala index 1bbee25110..dc289ba4e6 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala @@ -9,7 +9,6 @@ import org.ergoplatform.sdk.wallet.protocol.context.{BlockchainStateContext, Tra import org.ergoplatform.sdk.wallet.secrets.ExtendedSecretKey import org.ergoplatform.validation.ValidationRules import scalan.util.Extensions.LongOps -import scorex.crypto.authds.ADDigest import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} import sigmastate.basics.{DiffieHellmanTupleProverInput, SigmaProtocolPrivateInput} From 4ec1b49712d291d6e96eb1bb4c1344da7e2e3666 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 17 Aug 2023 23:11:14 +0200 Subject: [PATCH 13/27] tx-signing-js: ReducedTransaction exported to JS and can be serialised and deserialized --- .../scala/sigmastate/eval/Extensions.scala | 3 ++ .../ergoplatform/sdk/js/ProverBuilder.scala | 11 ++++++-- .../sdk/js/ReducedTransaction.scala | 20 +++++++++++++ .../org/ergoplatform/sdk/js/SigmaProver.scala | 28 +++++++++++++------ .../org/ergoplatform/sdk/ProverBuilder.scala | 8 +++++- .../org/ergoplatform/sdk/Transactions.scala | 21 +++++++++++++- 6 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 sdk/js/src/main/scala/org/ergoplatform/sdk/js/ReducedTransaction.scala diff --git a/interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala b/interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala index d765e01c41..b4efc58348 100644 --- a/interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala @@ -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} @@ -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]) { diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala index ffbc0e1a41..2660093165 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala @@ -1,6 +1,5 @@ package org.ergoplatform.sdk.js -import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix import org.ergoplatform.sdk import org.ergoplatform.sdk.SecretString @@ -9,9 +8,15 @@ import scala.scalajs.js.annotation.JSExportTopLevel import Isos._ import sigmastate.eval.SigmaDsl -/** Equivalent of [[sdk.ProverBuilder]] available from JS. */ +/** Equivalent of [[sdk.ProverBuilder]] available from JS. + * + * @param parameters Blockchain parameters re-adjustable via miners voting and + * voting-related data. All of them are included into extension + * section of a first block of a voting epoch. + * @param network Network prefix to use for addresses. + */ @JSExportTopLevel("ProverBuilder") -class ProverBuilder(parameters: BlockchainParameters, network: NetworkPrefix) extends js.Object { +class ProverBuilder(parameters: BlockchainParameters, network: Byte) extends js.Object { val _builder = new sdk.ProverBuilder(parameters, network) /** Configure this builder to use the given seed when building a new prover. diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ReducedTransaction.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ReducedTransaction.scala new file mode 100644 index 0000000000..30c63f5c33 --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ReducedTransaction.scala @@ -0,0 +1,20 @@ +package org.ergoplatform.sdk.js + +import org.ergoplatform.sdk +import scala.scalajs.js.annotation.JSExportTopLevel + +/** Equivalent of [[sdk.ReducedTransaction]] available from JS. */ +@JSExportTopLevel("ReducedTransaction") +class ReducedTransaction(private[js] val _tx: sdk.ReducedTransaction) { + /** Serialized bytes of this transaction in hex format. */ + def toHex: String = _tx.toHex +} + +@JSExportTopLevel("ReducedTransactionObj") +object ReducedTransaction { + /** Creates a [[ReducedTransaction]] from serialized bytes in hex format. */ + def fromHex(hex: String): ReducedTransaction = { + val tx = sdk.ReducedTransaction.fromHex(hex) + new ReducedTransaction(tx) + } +} \ No newline at end of file diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala index 621fae4208..b7ca33b1de 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala @@ -13,6 +13,25 @@ import scala.scalajs.js.annotation.JSExportTopLevel class SigmaProver(_prover: sdk.SigmaProver) extends js.Object { import Isos._ + /** Returns the Pay-to-Public-Key (P2PK) address associated with the prover's public key. + * The returned address corresponds to the master secret derived from the mnemonic + * phrase configured in the [[ProverBuilder]]. + */ + def getP2PKAddress: String = { + val addr = _prover.getP2PKAddress + addr.toString + } + + /** Returns the prover's secret key. */ + def getSecretKey: js.BigInt = + isoBigInt.from(_prover.getSecretKey) + + /** Returns a sequence of EIP-3 addresses associated with the prover's secret keys. */ + def getEip3Addresses: js.Array[String] = { + val addresses = _prover.getEip3Addresses + js.Array(addresses.map(_.toString): _*) + } + /** Reduces the transaction to the reduced form, which is ready to be signed. * @param stateCtx blockchain state context * @param unsignedTx unsigned transaction to be reduced (created by Fleet builders) @@ -49,12 +68,3 @@ class SigmaProver(_prover: sdk.SigmaProver) extends js.Object { isoSignedTransaction.from(signed.ergoTx) } } - -//TODO finish implementation -@JSExportTopLevel("ReducedTransaction") -class ReducedTransaction(private [js] val _tx: sdk.ReducedTransaction) - -//TODO finish implementation -@JSExportTopLevel("SigmaProverObj") -object SigmaProver { -} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala index 1e13962e18..0704cbe8d6 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala @@ -9,7 +9,13 @@ import special.sigma.GroupElement import java.math.BigInteger import scala.collection.mutable.ArrayBuffer -/** A builder class for constructing a `Prover` with specified secrets. */ +/** A builder class for constructing a `Prover` with specified secrets. + * + * @param parameters Blockchain parameters re-adjustable via miners voting and + * voting-related data. All of them are included into extension + * section of a first block of a voting epoch. + * @param networkPrefix Network prefix to use for addresses. + */ class ProverBuilder(parameters: BlockchainParameters, networkPrefix: NetworkPrefix) { private var _masterKey: Option[ExtendedSecretKey] = None diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Transactions.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Transactions.scala index 8073b77c9e..275814a983 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Transactions.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Transactions.scala @@ -1,6 +1,9 @@ package org.ergoplatform.sdk +import org.ergoplatform.sdk.JavaHelpers.StringExtensions import org.ergoplatform.{ErgoBox, ErgoLikeTransaction, UnsignedErgoLikeTransaction} +import sigmastate.eval.Extensions.ArrayByteOps +import sigmastate.serialization.SigmaSerializer import java.util @@ -45,7 +48,23 @@ case class UnreducedTransaction( } /** Represents results for transaction reduction by [[ReducingInterpreter]]. */ -case class ReducedTransaction(ergoTx: ReducedErgoLikeTransaction) +case class ReducedTransaction(ergoTx: ReducedErgoLikeTransaction) { + /** Serialized bytes of this transaction in hex format. */ + def toHex: String = { + val w = SigmaSerializer.startWriter() + ReducedErgoLikeTransactionSerializer.serialize(ergoTx, w) + w.toBytes.toHex + } +} + +object ReducedTransaction { + /** Creates a [[ReducedTransaction]] from serialized bytes in hex format. */ + def fromHex(hex: String): ReducedTransaction = { + val r = SigmaSerializer.startReader(hex.toBytes) + val tx = ReducedErgoLikeTransactionSerializer.parse(r) + ReducedTransaction(tx) + } +} /** Represents results for transaction signing by a prover like [[SigmaProver]]. */ case class SignedTransaction(ergoTx: ErgoLikeTransaction, cost: Int) From 1bdbcebc11b423d665473434260e370f094161ff Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 18 Aug 2023 13:50:23 +0200 Subject: [PATCH 14/27] tx-signing-js: fixed CryptoContextJs.decodePoint --- .../scala/sigmastate/crypto/Platform.scala | 6 ++++- .../CryptoFacadeSpecification.scala | 24 ++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/interpreter/js/src/main/scala/sigmastate/crypto/Platform.scala b/interpreter/js/src/main/scala/sigmastate/crypto/Platform.scala index d23407e7d7..ba5460e517 100644 --- a/interpreter/js/src/main/scala/sigmastate/crypto/Platform.scala +++ b/interpreter/js/src/main/scala/sigmastate/crypto/Platform.scala @@ -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()) diff --git a/interpreter/shared/src/test/scala/sigmastate/CryptoFacadeSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/CryptoFacadeSpecification.scala index 9d5a8c116f..49fa4534bd 100644 --- a/interpreter/shared/src/test/scala/sigmastate/CryptoFacadeSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/CryptoFacadeSpecification.scala @@ -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"), @@ -54,7 +56,7 @@ 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 @@ -62,8 +64,8 @@ class CryptoFacadeSpecification extends AnyPropSpec with Matchers with ScalaChec ("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) => @@ -71,4 +73,20 @@ class CryptoFacadeSpecification extends AnyPropSpec with Matchers with ScalaChec 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 + } } \ No newline at end of file From 5fac6e722607cd754770354219cb3e7bc7611412 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 18 Aug 2023 14:11:05 +0200 Subject: [PATCH 15/27] tx-signing-js: added js.GroupElement class --- .../ergoplatform/sdk/js/GroupElement.scala | 28 +++++++++++++++++++ .../scala/org/ergoplatform/sdk/js/Value.scala | 12 +++++++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala new file mode 100644 index 0000000000..b318200cdc --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala @@ -0,0 +1,28 @@ +package org.ergoplatform.sdk.js + +import sigmastate.crypto.{CryptoFacade, CryptoFacadeJs, Ecp, Platform} +import sigmastate.eval.Extensions.ArrayByteOps + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExportTopLevel + +/** Equivalent of [[special.sigma.GroupElement]] available from JS. */ +@JSExportTopLevel("GroupElement") +class GroupElement(val point: Ecp) extends js.Object { + /** Returns the point encoded as hex string (ASN.1 encoding). + * @see CryptoFacade.getASN1Encoding + */ + def toHex: String = { + CryptoFacade.getASN1Encoding(point, true).toHex + } +} + +object GroupElement { + /** Creates a new [[GroupElement]] from the given hex string (ASN.1 encoding) + * representation of the underlying [[sigmastate.crypto.Platform.Point]]. + */ + def fromHex(hex: String): GroupElement = { + val point = CryptoFacadeJs.createCryptoContext().decodePoint(hex) + new GroupElement(new Platform.Ecp(point)) + } +} \ No newline at end of file diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala index 26e167fa0c..2ae73a781d 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala @@ -213,6 +213,14 @@ object Value extends js.Object { new Value(n, Type.BigInt) } + /** Creates a Value of GroupElement type from [[sigmastate.crypto.Platform.Point]] hex. + * @param pointHex hex of ASN representation of [[sigmastate.crypto.Platform.Point]] + */ + def ofGroupElement(pointHex: String): Value = { + val ge = GroupElement.fromHex(pointHex) + new Value(ge, Type.GroupElement) + } + /** Create Pair value from two values. */ def pairOf(l: Value, r: Value): Value = { val data = js.Array(l.data, r.data) // the l and r data have been validated @@ -238,7 +246,9 @@ object Value extends js.Object { * descriptor. * @param hex the string is obtained as hex encoding of serialized ConstantNode. * (The bytes obtained by ConstantSerializer in sigma) - * @return new deserialized ErgoValue instance + * @return new deserialized Value instance containing: + * - suitable JS value in its `data` field + * - and [[Type]] descriptor in its `tpe` field */ def fromHex(hex: String): Value = { val bytes = Base16.decode(hex).fold(t => throw t, identity) From 58140c1203cbfa17d7ac4579a2fe46c0df330a9d Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 18 Aug 2023 15:53:30 +0200 Subject: [PATCH 16/27] tx-signing-js: js tests for GroupElement --- .../scala/org/ergoplatform/sdk/js/GroupElement.scala | 5 +++-- sigma-js/sigmastate-js.d.ts | 8 ++++++++ sigma-js/tests/js/GroupElement.spec.js | 10 ++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 sigma-js/tests/js/GroupElement.spec.js diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala index b318200cdc..3e09608ba9 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala @@ -12,12 +12,13 @@ class GroupElement(val point: Ecp) extends js.Object { /** Returns the point encoded as hex string (ASN.1 encoding). * @see CryptoFacade.getASN1Encoding */ - def toHex: String = { + def toHex(): String = { CryptoFacade.getASN1Encoding(point, true).toHex } } -object GroupElement { +@JSExportTopLevel("GroupElementObj") +object GroupElement extends js.Object { /** Creates a new [[GroupElement]] from the given hex string (ASN.1 encoding) * representation of the underlying [[sigmastate.crypto.Platform.Point]]. */ diff --git a/sigma-js/sigmastate-js.d.ts b/sigma-js/sigmastate-js.d.ts index 04bc1c7f49..0ee5e572db 100644 --- a/sigma-js/sigmastate-js.d.ts +++ b/sigma-js/sigmastate-js.d.ts @@ -20,6 +20,14 @@ declare module "sigmastate-js/main" { static fromHex(value: HexString): ErgoTree; } + export declare class GroupElement { + toHex(): HexString; + } + + export declare class GroupElementObj { + static fromHex(value: HexString): GroupElement; + } + export declare class Type { name: string; toString(): string; diff --git a/sigma-js/tests/js/GroupElement.spec.js b/sigma-js/tests/js/GroupElement.spec.js new file mode 100644 index 0000000000..c80cdbaa24 --- /dev/null +++ b/sigma-js/tests/js/GroupElement.spec.js @@ -0,0 +1,10 @@ +const { GroupElementObj, ErgoTree} = require("sigmastate-js/main"); + +let pointAsn1Hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"; + +describe("GroupElement", () => { + it("should implement toHex/fromHex", () => { + let ge = GroupElementObj.fromHex(pointAsn1Hex) + expect(ge.toHex()).toEqual(pointAsn1Hex) + }); +}); \ No newline at end of file From 9917c2b9b4b91d298852b0a92864392658c95810 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 18 Aug 2023 16:33:22 +0200 Subject: [PATCH 17/27] tx-signing-js: implemented fromPointHex for js.SigmaProp --- .../ergoplatform/sdk/js/GroupElement.scala | 6 ++--- .../org/ergoplatform/sdk/js/SigmaProp.scala | 17 +++++++++++++- .../scala/org/ergoplatform/sdk/js/Value.scala | 22 ++++++++++++++----- sigma-js/sigmastate-js.d.ts | 13 +++++++++-- sigma-js/tests/js/GroupElement.spec.js | 11 ++++++---- sigma-js/tests/js/SigmaProp.spec.js | 14 ++++++++++++ 6 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 sigma-js/tests/js/SigmaProp.spec.js diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala index 3e09608ba9..cc20e704bb 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/GroupElement.scala @@ -12,7 +12,7 @@ class GroupElement(val point: Ecp) extends js.Object { /** Returns the point encoded as hex string (ASN.1 encoding). * @see CryptoFacade.getASN1Encoding */ - def toHex(): String = { + def toPointHex(): String = { CryptoFacade.getASN1Encoding(point, true).toHex } } @@ -22,8 +22,8 @@ object GroupElement extends js.Object { /** Creates a new [[GroupElement]] from the given hex string (ASN.1 encoding) * representation of the underlying [[sigmastate.crypto.Platform.Point]]. */ - def fromHex(hex: String): GroupElement = { - val point = CryptoFacadeJs.createCryptoContext().decodePoint(hex) + def fromPointHex(pointHex: String): GroupElement = { + val point = CryptoFacadeJs.createCryptoContext().decodePoint(pointHex) new GroupElement(new Platform.Ecp(point)) } } \ No newline at end of file diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProp.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProp.scala index 0722b68e5a..bc2da23561 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProp.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProp.scala @@ -1,9 +1,24 @@ package org.ergoplatform.sdk.js import sigmastate.Values.SigmaBoolean +import sigmastate.basics.DLogProtocol.ProveDlog + import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel /** Equivalent of [[special.sigma.SigmaProp]] available from JS. */ @JSExportTopLevel("SigmaProp") -class SigmaProp(val sigmaBoolean: SigmaBoolean) extends js.Object +class SigmaProp(val sigmaBoolean: SigmaBoolean) extends js.Object { +} + +@JSExportTopLevel("SigmaPropObj") +object SigmaProp extends js.Object { + /** Creates a new [[SigmaProp]] from the given hex string of public key. + * @param pointHex hex representation of elliptic curve point (ASN.1 encoded) + * @see CryptoFacade.getASN1Encoding, GroupElement.fromPointHex, Point + */ + def fromPointHex(pointHex: String): SigmaProp = { + val point = GroupElement.fromPointHex(pointHex).point + new SigmaProp(ProveDlog(point)) + } +} diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala index 2ae73a781d..0c30216dbc 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala @@ -89,8 +89,8 @@ object Value extends js.Object { val v = data.asInstanceOf[js.BigInt] SigmaDsl.BigInt(new BigInteger(v.toString(16), 16)) case special.sigma.GroupElementRType => - val point = data.asInstanceOf[Platform.Point] - SigmaDsl.GroupElement(new Platform.Ecp(point)) + val ge = data.asInstanceOf[GroupElement] + SigmaDsl.GroupElement(ge.point) case special.sigma.SigmaPropRType => val p = data.asInstanceOf[SigmaProp] SigmaDsl.SigmaProp(p.sigmaBoolean) @@ -129,8 +129,8 @@ object Value extends js.Object { val hex = SigmaDsl.toBigInteger(value.asInstanceOf[special.sigma.BigInt]).toString(10) js.BigInt(hex) case special.sigma.GroupElementRType => - val point: Platform.Point = value.asInstanceOf[CGroupElement].wrappedValue.asInstanceOf[Platform.Ecp].point - point + val point = value.asInstanceOf[CGroupElement].wrappedValue.asInstanceOf[Platform.Ecp] + new GroupElement(point) case special.sigma.SigmaPropRType => new SigmaProp(value.asInstanceOf[CSigmaProp].wrappedValue) case special.sigma.AvlTreeRType => @@ -164,6 +164,10 @@ object Value extends js.Object { n case special.sigma.BigIntRType => data.asInstanceOf[js.BigInt] + case special.sigma.GroupElementRType => + data.asInstanceOf[GroupElement] + case special.sigma.SigmaPropRType => + data.asInstanceOf[SigmaProp] case PairType(l, r) => data match { case arr: js.Array[Any @unchecked] => checkJsData(arr(0), l) @@ -217,10 +221,18 @@ object Value extends js.Object { * @param pointHex hex of ASN representation of [[sigmastate.crypto.Platform.Point]] */ def ofGroupElement(pointHex: String): Value = { - val ge = GroupElement.fromHex(pointHex) + val ge = GroupElement.fromPointHex(pointHex) new Value(ge, Type.GroupElement) } + /** Creates a Value of SigmaProp type from [[sigmastate.crypto.Platform.Point]] hex. + * @param pointHex hex of ASN representation of [[sigmastate.crypto.Platform.Point]] + */ + def ofSigmaProp(pointHex: String): Value = { + val sp = SigmaProp.fromPointHex(pointHex) + new Value(sp, Type.SigmaProp) + } + /** Create Pair value from two values. */ def pairOf(l: Value, r: Value): Value = { val data = js.Array(l.data, r.data) // the l and r data have been validated diff --git a/sigma-js/sigmastate-js.d.ts b/sigma-js/sigmastate-js.d.ts index 0ee5e572db..3914841695 100644 --- a/sigma-js/sigmastate-js.d.ts +++ b/sigma-js/sigmastate-js.d.ts @@ -21,11 +21,18 @@ declare module "sigmastate-js/main" { } export declare class GroupElement { - toHex(): HexString; + toPointHex(): HexString; } export declare class GroupElementObj { - static fromHex(value: HexString): GroupElement; + static fromPointHex(value: HexString): GroupElement; + } + + export declare class SigmaProp { + } + + export declare class SigmaPropObj { + static fromPointHex(value: HexString): SigmaProp; } export declare class Type { @@ -63,6 +70,8 @@ declare module "sigmastate-js/main" { static ofInt(value: number): Value; static ofLong(value: bigint): Value; static ofBigInt(value: bigint): Value; + static ofGroupElement(pointHex: string): Value; + static ofSigmaProp(pointHex: string): Value; static pairOf(left: Value, right: Value): Value<[R, L]>; static collOf(items: T[], type: Type): Value; static fromHex(hex: HexString): Value; diff --git a/sigma-js/tests/js/GroupElement.spec.js b/sigma-js/tests/js/GroupElement.spec.js index c80cdbaa24..6d860691be 100644 --- a/sigma-js/tests/js/GroupElement.spec.js +++ b/sigma-js/tests/js/GroupElement.spec.js @@ -1,10 +1,13 @@ -const { GroupElementObj, ErgoTree} = require("sigmastate-js/main"); +const { GroupElementObj, ValueObj } = require("sigmastate-js/main"); let pointAsn1Hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"; describe("GroupElement", () => { - it("should implement toHex/fromHex", () => { - let ge = GroupElementObj.fromHex(pointAsn1Hex) - expect(ge.toHex()).toEqual(pointAsn1Hex) + it("should implement toPointHex/fromPointHex", () => { + let ge = GroupElementObj.fromPointHex(pointAsn1Hex) + expect(ge.toPointHex()).toEqual(pointAsn1Hex) + + let v = ValueObj.ofGroupElement(pointAsn1Hex) + expect(v.toHex()).toEqual("07"/* GroupElement type id */ + pointAsn1Hex) }); }); \ No newline at end of file diff --git a/sigma-js/tests/js/SigmaProp.spec.js b/sigma-js/tests/js/SigmaProp.spec.js new file mode 100644 index 0000000000..89568120c7 --- /dev/null +++ b/sigma-js/tests/js/SigmaProp.spec.js @@ -0,0 +1,14 @@ +const { SigmaPropObj, ValueObj } = require("sigmastate-js/main"); + +let pointAsn1Hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"; + +describe("SigmaProp", () => { + it("should implement fromPointHex", () => { + let ge = SigmaPropObj.fromPointHex(pointAsn1Hex) + expect(ge).not.toBeUndefined() + + let v = ValueObj.ofSigmaProp(pointAsn1Hex) + expect(v.toHex()) + .toEqual("08"/* SigmaProp type id */ + "cd"/* ProveDlog.opCode */ + pointAsn1Hex) + }); +}); \ No newline at end of file From 3d5c498c892c542f39640d06b2d5980dbb7edc50 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 18 Aug 2023 16:56:49 +0200 Subject: [PATCH 18/27] tx-signing-js: added test for Value of type Coll[SigmaProp] --- sigma-js/sigmastate-js.d.ts | 2 +- sigma-js/tests/js/Value.spec.js | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/sigma-js/sigmastate-js.d.ts b/sigma-js/sigmastate-js.d.ts index 3914841695..34eef4df12 100644 --- a/sigma-js/sigmastate-js.d.ts +++ b/sigma-js/sigmastate-js.d.ts @@ -73,7 +73,7 @@ declare module "sigmastate-js/main" { static ofGroupElement(pointHex: string): Value; static ofSigmaProp(pointHex: string): Value; static pairOf(left: Value, right: Value): Value<[R, L]>; - static collOf(items: T[], type: Type): Value; + static collOf(items: T[], elemType: Type): Value; static fromHex(hex: HexString): Value; } diff --git a/sigma-js/tests/js/Value.spec.js b/sigma-js/tests/js/Value.spec.js index bac3251d4c..e687f8aecb 100644 --- a/sigma-js/tests/js/Value.spec.js +++ b/sigma-js/tests/js/Value.spec.js @@ -1,4 +1,4 @@ -const { TypeObj, ValueObj } = require("sigmastate-js/main"); +const { TypeObj, ValueObj, SigmaPropObj, SigmaProp} = require("sigmastate-js/main"); function testRange(factory, min, max) { expect(factory(max).data).toEqual(max); @@ -105,6 +105,14 @@ describe("Smoke tests for Values", () => { expect(collV.toHex()).toEqual(collHex) }); + it("Value of type Coll[SigmaProp]", () => { + let sp1 = SigmaPropObj.fromPointHex(groupElementHex.substring(2)) + let sp2 = SigmaPropObj.fromPointHex(sigmaPropHex.substring(4)) + let collV = ValueObj.collOf([sp1, sp2], TypeObj.SigmaProp) + + expect(collV.tpe.name).toEqual("Coll[SigmaProp]"); + }); + it("Pair Value.toHex", () => { let fst = ValueObj.ofByte(10) let snd = ValueObj.ofLong(20) From 748962dc1cd365c0dda9ff697548b50ac4244c3c Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 18 Aug 2023 22:47:37 +0200 Subject: [PATCH 19/27] tx-signing-js: fix equality of ValidationSettings --- .../org/ergoplatform/ErgoLikeContext.scala | 6 +++--- .../validation/SigmaValidationSettings.scala | 7 ++++--- .../SigmaValidationSettingsSerializer.scala | 19 +++++++++++-------- .../validation/ValidationRules.scala | 4 ++-- ...igmaValidationSettingsSerializerSpec.scala | 12 ++++++++++-- .../sdk/JsonSerializationSpec.scala | 4 ++-- 6 files changed, 32 insertions(+), 20 deletions(-) diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala index 3bf455ae6f..3b356fd4ec 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala @@ -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)" diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala index 6314ea0b20..d295b17ce7 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala @@ -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 } @@ -72,10 +72,11 @@ 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 == that.map + case that: MapSigmaValidationSettings => + map.iterator.forall { case (id, v) => that.map.get(id).exists(_ == v) } case _ => false }) - override def hashCode(): Int = map.hashCode() + override def hashCode(): Int = map.toIndexedSeq.sortBy(_._1).hashCode() } diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala index 1e2b8b352a..1b53b7dbf1 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala @@ -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. @@ -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) } } @@ -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 } } diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/validation/ValidationRules.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/ValidationRules.scala index 1110916e03..2573d3f592 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/validation/ValidationRules.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/validation/ValidationRules.scala @@ -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} diff --git a/sc/shared/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala b/sc/shared/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala index b74d4014f2..ee4ec75c3c 100644 --- a/sc/shared/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala +++ b/sc/shared/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala @@ -1,5 +1,6 @@ package org.ergoplatform.validation +import org.ergoplatform.validation.ValidationRules.{FirstRuleId, currentSettings} import org.scalatest.Assertion import sigmastate.helpers.CompilerTestingCommons import sigmastate.serialization.SerializationSpecification @@ -13,14 +14,21 @@ class SigmaValidationSettingsSerializerSpec extends SerializationSpecification w } property("ValidationRules.currentSettings round trip") { - roundtrip(ValidationRules.currentSettings) + roundtrip(currentSettings) } property("SigmaValidationSettings round trip") { forAll(ruleIdGen, statusGen, MinSuccessful(100)) { (ruleId, status) => - val vs = ValidationRules.currentSettings.updated(ruleId, status) + val vs = currentSettings.updated(ruleId, status) roundtrip(vs) } } + property("SigmaValidationSettings equality") { + val vs = currentSettings + val vs_copy = currentSettings.updated(FirstRuleId, currentSettings.getStatus(FirstRuleId).get) + val vs2 = currentSettings.updated(FirstRuleId, DisabledRule) + vs.equals(vs2) shouldBe false + vs.equals(vs_copy) shouldBe true + } } \ No newline at end of file diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala index e651a542b8..44d7030df2 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala @@ -16,7 +16,7 @@ import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.eval.Digest32Coll import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.serialization.SerializationSpecification -import sigmastate.utils.Helpers._ +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 @@ -36,7 +36,7 @@ class JsonSerializationSpec extends SerializationSpecification with JsonCodecs { } property("ErgoLikeContext should be encoded into JSON and decoded back correctly") { - forAll(ergoLikeContextGen) { v: ErgoLikeContext => jsonRoundTrip(v) } + forAll(ergoLikeContextGen, MinSuccessful(50)) { v: ErgoLikeContext => jsonRoundTrip(v) } } property("sigma.BigInt should be encoded into JSON and decoded back correctly") { From 1bf620514899e6dd4db2d087487695d4a61362bf Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 20 Aug 2023 17:53:26 +0200 Subject: [PATCH 20/27] todo-v5.x: fix undeterministic behaviour in JsonCodecs --- .../main/scala/org/ergoplatform/ErgoBox.scala | 14 ++-- .../org/ergoplatform/ErgoBoxCandidate.scala | 2 +- .../generators/ObjectGenerators.scala | 6 +- .../org/ergoplatform/sdk/JsonCodecs.scala | 13 ++-- .../sdk/JsonSerializationSpec.scala | 67 +++++++++++++++---- 5 files changed, 73 insertions(+), 29 deletions(-) diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala index 37a6e96193..9d373fb36a 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala @@ -1,11 +1,11 @@ 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._ @@ -13,12 +13,10 @@ 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 @@ -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 @@ -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") diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 8d5765caf5..da1fdc94fd 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -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 diff --git a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala index 92538aca22..2f032dface 100644 --- a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala @@ -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]) @@ -348,7 +348,9 @@ 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:_*) +// mutable.LinkedHashMap(regs.toIndexedSeq.sortBy(_._1.number):_*) def ergoBoxTokens(availableTokens: Seq[TokenId]): Gen[Coll[Token]] = for { tokens <- diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala index aae3410df4..741c84ae9f 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -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 @@ -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) }) @@ -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, @@ -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[scala.collection.Map[NonMandatoryRegisterId, EvaluatedValue[SType]]] transactionId <- cursor.downField("transactionId").as[ModifierId] index <- cursor.downField("index").as[Short] } yield new ErgoBox( diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala index 44d7030df2..1ff7402379 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala @@ -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 { @@ -84,19 +79,25 @@ 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") { @@ -119,8 +120,46 @@ class JsonSerializationSpec extends SerializationSpecification with JsonCodecs { forAll(unsignedErgoLikeTransactionGen) { v: UnsignedErgoLikeTransaction => jsonRoundTrip(v) } } + private def sortRegisters(box: ErgoBoxCandidate): ErgoBoxCandidate = box match { + case box: ErgoBox => + new ErgoBox(box.value, + box.ergoTree, + box.additionalTokens, + mutable.LinkedHashMap(box.additionalRegisters.toIndexedSeq.sortBy(_._1.number): _*), + box.transactionId, + box.index, + box.creationHeight + ) + case box: ErgoBoxCandidate => + new ErgoBoxCandidate(box.value, + box.ergoTree, + box.creationHeight, + box.additionalTokens, + mutable.LinkedHashMap(box.additionalRegisters.toIndexedSeq.sortBy(_._1.number): _*) + ) + } + + private def sortRegisters(tx: ErgoLikeTransactionTemplate[_ <: UnsignedInput]): ErgoLikeTransactionTemplate[_ <: UnsignedInput] = { + tx match { + case tx: ErgoLikeTransaction => + new ErgoLikeTransaction(tx.inputs, + tx.dataInputs, + tx.outputCandidates.map { out => + sortRegisters(out) + } + ) + case tx: UnsignedErgoLikeTransaction => + new UnsignedErgoLikeTransaction(tx.inputs, + tx.dataInputs, + tx.outputCandidates.map { out => + sortRegisters(out) + } + ) + } + } + 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 } } @@ -136,7 +175,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), From b0d6202ec402c882ae54e20ccde1bc7d4772b5c9 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 20 Aug 2023 22:16:32 +0200 Subject: [PATCH 21/27] todo-v5.x: fix contextExtensionEncoder --- .../ergoplatform/ErgoLikeTransaction.scala | 4 +- .../org/ergoplatform/sdk/JsonCodecs.scala | 94 ++++++++++++------- .../sdk/JsonSerializationSpec.scala | 8 +- 3 files changed, 66 insertions(+), 40 deletions(-) diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index 5ad3e08207..f5b8a2c15f 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -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)" } @@ -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 { @@ -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] { diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala index 741c84ae9f..4c0ea1d89b 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -239,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.apply(value) + }: _*) }) implicit val contextExtensionDecoder: Decoder[ContextExtension] = Decoder.instance({ cursor => @@ -327,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[scala.collection.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( @@ -341,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 }) diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala index 1ff7402379..a2634906fe 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala @@ -101,19 +101,19 @@ class JsonSerializationSpec extends SerializationSpecification with JsonCodecs { } 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(500)) { v: ErgoLikeTransaction => jsonRoundTrip(v) } } property("UnsignedErgoLikeTransaction should be encoded into JSON and decoded back correctly") { From ace1cdbb75793fe921217938daeacf222f08ef7a Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 20 Aug 2023 23:19:03 +0200 Subject: [PATCH 22/27] tx-signing-js: cleanups --- .../validation/SigmaValidationSettings.scala | 3 +- .../generators/ObjectGenerators.scala | 1 - .../org/ergoplatform/sdk/JsonCodecs.scala | 14 +++---- .../sdk/JsonSerializationSpec.scala | 42 +------------------ 4 files changed, 11 insertions(+), 49 deletions(-) diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala index d295b17ce7..fac738ce5a 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala @@ -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 }) diff --git a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala index 2f032dface..d566e416ba 100644 --- a/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala @@ -350,7 +350,6 @@ trait ObjectGenerators extends TypeGenerators regs <- Gen.sequence(additionalRegistersGen(regNum))(Buildable.buildableSeq) } yield Map(regs.toIndexedSeq:_*) -// mutable.LinkedHashMap(regs.toIndexedSeq.sortBy(_._1.number):_*) def ergoBoxTokens(availableTokens: Seq[TokenId]): Gen[Coll[Token]] = for { tokens <- diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala index 4c0ea1d89b..52e1565ca3 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -240,7 +240,7 @@ trait JsonCodecs { implicit val contextExtensionEncoder: Encoder[ContextExtension] = Encoder.instance({ extension => Json.obj(extension.values.toSeq.map { case (key, value) => - key.toString -> evaluatedValueEncoder.apply(value) + key.toString -> evaluatedValueEncoder(value) }: _*) }) @@ -369,7 +369,7 @@ trait JsonCodecs { implicit val ergoLikeTransactionEncoder: Encoder[ErgoLikeTransaction] = Encoder.instance({ tx => Json.obj( - "type" -> "elt".asJson, // ErgoLikeTransaction + "type" -> "ELT".asJson, // ErgoLikeTransaction "id" -> tx.id.asJson, "inputs" -> tx.inputs.asJson, "dataInputs" -> tx.dataInputs.asJson, @@ -380,7 +380,7 @@ trait JsonCodecs { 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]] } + 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) @@ -388,7 +388,7 @@ trait JsonCodecs { implicit val unsignedErgoLikeTransactionEncoder: Encoder[UnsignedErgoLikeTransaction] = Encoder.instance({ tx => Json.obj( - "type" -> "uelt".asJson, // UnsignedErgoLikeTransaction + "type" -> "UELT".asJson, // UnsignedErgoLikeTransaction "id" -> tx.id.asJson, "inputs" -> tx.inputs.asJson, "dataInputs" -> tx.dataInputs.asJson, @@ -399,7 +399,7 @@ trait JsonCodecs { 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]] } + 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) @@ -415,8 +415,8 @@ trait JsonCodecs { for { t <- cursor.downField("type").as[String] tx <- t match { - case "elt" => ergoLikeTransactionDecoder(cursor) - case "uelt" => unsignedErgoLikeTransactionDecoder(cursor) + case "ELT" => ergoLikeTransactionDecoder(cursor) + case "UELT" => unsignedErgoLikeTransactionDecoder(cursor) } } yield tx }) diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala index a2634906fe..e85757985e 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/JsonSerializationSpec.scala @@ -113,49 +113,11 @@ class JsonSerializationSpec extends SerializationSpecification with JsonCodecs { } property("ErgoLikeTransaction should be encoded into JSON and decoded back correctly") { - forAll(ergoLikeTransactionGen, MinSuccessful(500)) { 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) } - } - - private def sortRegisters(box: ErgoBoxCandidate): ErgoBoxCandidate = box match { - case box: ErgoBox => - new ErgoBox(box.value, - box.ergoTree, - box.additionalTokens, - mutable.LinkedHashMap(box.additionalRegisters.toIndexedSeq.sortBy(_._1.number): _*), - box.transactionId, - box.index, - box.creationHeight - ) - case box: ErgoBoxCandidate => - new ErgoBoxCandidate(box.value, - box.ergoTree, - box.creationHeight, - box.additionalTokens, - mutable.LinkedHashMap(box.additionalRegisters.toIndexedSeq.sortBy(_._1.number): _*) - ) - } - - private def sortRegisters(tx: ErgoLikeTransactionTemplate[_ <: UnsignedInput]): ErgoLikeTransactionTemplate[_ <: UnsignedInput] = { - tx match { - case tx: ErgoLikeTransaction => - new ErgoLikeTransaction(tx.inputs, - tx.dataInputs, - tx.outputCandidates.map { out => - sortRegisters(out) - } - ) - case tx: UnsignedErgoLikeTransaction => - new UnsignedErgoLikeTransaction(tx.inputs, - tx.dataInputs, - tx.outputCandidates.map { out => - sortRegisters(out) - } - ) - } + forAll(unsignedErgoLikeTransactionGen, MinSuccessful(50)) { v: UnsignedErgoLikeTransaction => jsonRoundTrip(v) } } property("ErgoLikeTransactionTemplate should be encoded into JSON and decoded back correctly") { From 627214fbb04fb20a93bf078c302e5d6e5a18b361 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 21 Aug 2023 19:40:12 +0200 Subject: [PATCH 23/27] tx-signing-js: downcastCollType --- .../src/main/scala/special/collection/package.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core-lib/shared/src/main/scala/special/collection/package.scala b/core-lib/shared/src/main/scala/special/collection/package.scala index 826c1bd3b5..b8be71be23 100644 --- a/core-lib/shared/src/main/scala/special/collection/package.scala +++ b/core-lib/shared/src/main/scala/special/collection/package.scala @@ -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]) } From 71f4b7e7a4bf65b7aa2baf6705993f7c42641fd4 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Aug 2023 11:38:02 +0200 Subject: [PATCH 24/27] tx-signing-js: removed Signer --- .../org/ergoplatform/sdk/multisig/Signer.scala | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala deleted file mode 100644 index e329153b21..0000000000 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/multisig/Signer.scala +++ /dev/null @@ -1,17 +0,0 @@ -package org.ergoplatform.sdk.multisig - -import org.ergoplatform.P2PKAddress -import org.ergoplatform.sdk.SigmaProver - -case class Signer(prover: SigmaProver) { - def masterAddress: P2PKAddress = prover.getP2PKAddress -} - -object Signer { - - - def main(args: Array[String]): Unit = { - -// val pkAlice = - } -} \ No newline at end of file From ac3c6b8520cbc6934b0542eda174036ea6bff113 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Aug 2023 11:57:16 +0200 Subject: [PATCH 25/27] tx-signing-js: rollback changes in MapSigmaValidationSettings --- .../ergoplatform/validation/SigmaValidationSettings.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala index fac738ce5a..e59575a272 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala @@ -72,12 +72,10 @@ 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.size == that.map.size && - map.iterator.forall { case (id, v) => that.map.get(id).exists(_ == v) } + case that: MapSigmaValidationSettings => map == that.map case _ => false }) - override def hashCode(): Int = map.toIndexedSeq.sortBy(_._1).hashCode() + override def hashCode(): Int = map.hashCode() } From d1f0a6d6521bc5133969e1802f5a0dd36b7652fb Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Aug 2023 12:01:19 +0200 Subject: [PATCH 26/27] tx-signing-js: remove SigningSpec.scala --- .../sdk/multisig/SigningSpec.scala | 106 ------------------ 1 file changed, 106 deletions(-) delete mode 100644 sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala deleted file mode 100644 index bde2e04fcf..0000000000 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/multisig/SigningSpec.scala +++ /dev/null @@ -1,106 +0,0 @@ -package org.ergoplatform.sdk.multisig - -import org.ergoplatform.sdk.Extensions.{DoubleOps, HeaderOps} -import org.ergoplatform.sdk.NetworkType.Mainnet -import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext -import org.ergoplatform.{ErgoAddress, ErgoTreePredef, P2PKAddress} -import org.ergoplatform.sdk.{BlockchainContext, BlockchainParameters, ExtendedInputBox, OutBox, ProverBuilder, ReducedTransaction, SecretString, UnsignedTransactionBuilder} -import org.scalatest.matchers.should.Matchers -import org.scalatest.propspec.AnyPropSpec -import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -import scalan.util.CollectionUtil.AnyOps -import sigmastate.TestsBase -import sigmastate.Values.{Constant, ErgoTree} -import special.sigma.SigmaTestingData - -class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers - with TestsBase - with SigmaTestingData { - - val testMainnetParameters = BlockchainParameters( - storageFeeFactor = 1250000, - minValuePerByte = 360, - maxBlockSize = 1271009, - tokenAccessCost = 100, - inputCost = 2407, - dataInputCost = 100, - outputCost = 184, - maxBlockCost = 8001091, - softForkStartingHeight = None, - softForkVotesCollected = None, - blockVersion = 3 - ) - - val mockTxId = "f9e5ce5aa0d95f5d54a7bc89c46730d9662397067250aa18a0039631c0f5b809" - - val ctx = BlockchainContext(Mainnet, testMainnetParameters, - BlockchainStateContext( - sigmaLastHeaders = headers, - previousStateDigest = headers(0).stateRoot.digest, - sigmaPreHeader = preHeader - ) - ) - - def createSigner(secret: String): Signer = Signer( - ProverBuilder.forMainnet(testMainnetParameters) - .withMnemonic(SecretString.create(secret), SecretString.empty()) - .build() - ) - - val alice = createSigner("Alice secret") - val bob = createSigner("Bob secret") - val carol = createSigner("Carol secret") - val david = createSigner("David secret") - - def createRtx( - ctx: BlockchainContext, inputs: Seq[ExtendedInputBox], - recepient: ErgoAddress, changeAddress: ErgoAddress): ReducedTransaction = { - val txB = UnsignedTransactionBuilder(ctx) - val output = txB.outBoxBuilder - .value(inputs.map(_.value).sum - BlockchainParameters.MinFee) - .contract(recepient.script).build() - val feeOut = txB.outBoxBuilder - .value(BlockchainParameters.MinFee) - .contract(ErgoTreePredef.feeProposition()) - .build() - val unsigned = txB - .addInputs(inputs: _*) - .addOutputs(output, feeOut) - .sendChangeTo(changeAddress) - .build() - // create a prover without secrets as they are not needed for reduction - val prover = ProverBuilder.forMainnet(ctx.parameters).build() - val reduced = prover.reduce(ctx.stateContext, unsigned, 0) - reduced - } - - def createTestOut( - ctx: BlockchainContext, - amount: Long, - contract: ErgoTree, - registers: Constant[_]* - ): OutBox = { - val out = UnsignedTransactionBuilder(ctx).outBoxBuilder - .value(amount) - .contract(contract) - .perform(b => if (registers.isEmpty) b else b.registers(registers: _*)) - .build() - out - } - - def createInput(ctx: BlockchainContext, owner: Signer): ExtendedInputBox = { - val out = createTestOut(ctx, 1.erg, owner.prover.getP2PKAddress.script) - out.convertToInputWith(mockTxId, 0) - } - - property("Signing workflow") { - val aliceInput = createInput(ctx, alice) - val bobInput = createInput(ctx, bob) - val carolInput = createInput(ctx, carol) - val inputs = Seq(aliceInput, bobInput, carolInput) - val reduced = createRtx(ctx, inputs, alice.masterAddress, david.masterAddress) - - reduced shouldNot be(null) - } -} - From ce203cca487c0a2476504f8a11e7a94ba8ef61b5 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 26 Aug 2023 09:11:22 +0200 Subject: [PATCH 27/27] tx-signing-js: fix Header props, more ScalaDocs --- .../main/scala/special/sigma/SigmaDsl.scala | 2 +- .../sdk/js/BlockchainParameters.scala | 46 +++++++----- .../sdk/js/BlockchainStateContext.scala | 8 ++- .../org/ergoplatform/sdk/js/Header.scala | 32 ++++++++- .../scala/org/ergoplatform/sdk/js/Isos.scala | 71 +++++++++++++++---- .../org/ergoplatform/sdk/js/PreHeader.scala | 15 +++- .../ergoplatform/sdk/js/ProverBuilder.scala | 2 +- .../sdk/js/ReducedTransaction.scala | 6 +- 8 files changed, 138 insertions(+), 44 deletions(-) diff --git a/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala b/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala index 462c5382ab..b1f27f0899 100644 --- a/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala +++ b/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala @@ -566,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. */ diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala index eb421e171b..def671c2d4 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala @@ -4,27 +4,37 @@ import org.ergoplatform.sdk import scala.scalajs.js.UndefOr import scala.scalajs.js.annotation.JSExportTopLevel -import org.ergoplatform.sdk.Iso._ + +import scala.scalajs.js /** JS exported version of the [[sdk.BlockchainParameters]] class with the same fields. + * Blockchain parameters re-adjustable via miners voting and voting-related data. + * All these fields are included into extension section of a first block of a voting epoch. + * + * @param storageFeeFactor cost of storing 1 byte in UTXO for four years, in nanoErgs + * @param minValuePerByte cost of a transaction output, in computation unit + * @param maxBlockSize max block size, in bytes + * @param tokenAccessCost cost of a token contained in a transaction, in computation unit + * @param inputCost cost of a transaction input, in computation unit + * @param dataInputCost cost of a transaction data input, in computation unit + * @param outputCost cost of a transaction output, in computation unit + * @param maxBlockCost computation units limit per block + * @param softForkStartingHeight height when voting for a soft-fork had been started + * @param softForkVotesCollected votes for soft-fork collected in previous epochs + * @param blockVersion Protocol version activated on the network * @see sdk.BlockchainParameters */ @JSExportTopLevel("BlockchainParameters") class BlockchainParameters( - storageFeeFactor: Int, - minValuePerByte: Int, - maxBlockSize: Int, - tokenAccessCost: Int, - inputCost: Int, - dataInputCost: Int, - outputCost: Int, - maxBlockCost: Int, - _softForkStartingHeight: UndefOr[Int], - _softForkVotesCollected: UndefOr[Int], - blockVersion: Byte -) extends sdk.BlockchainParameters( - storageFeeFactor, minValuePerByte, maxBlockSize, tokenAccessCost, inputCost, dataInputCost, - outputCost, maxBlockCost, - Isos.isoUndefOr[Int, Int](identityIso).to(_softForkStartingHeight), - Isos.isoUndefOr[Int, Int](identityIso).to(_softForkVotesCollected), blockVersion -) + val storageFeeFactor: Int, + val minValuePerByte: Int, + val maxBlockSize: Int, + val tokenAccessCost: Int, + val inputCost: Int, + val dataInputCost: Int, + val outputCost: Int, + val maxBlockCost: Int, + val softForkStartingHeight: UndefOr[Int], + val softForkVotesCollected: UndefOr[Int], + val blockVersion: Byte +) extends js.Object diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala index 98244c5653..425d4c7e88 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala @@ -3,10 +3,14 @@ package org.ergoplatform.sdk.js import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel -/** Equivalent of [[org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext]] available from JS. */ +/** Equivalent of [[org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext]] available from JS. + * @param sigmaLastHeaders fixed number (10 in Ergo) of last block headers + * @param previousStateDigest hex of UTXO set digest from a last header (of sigmaLastHeaders) + * @param sigmaPreHeader returns pre-header (header without certain fields) of the current block + */ @JSExportTopLevel("BlockchainStateContext") class BlockchainStateContext( val sigmaLastHeaders: js.Array[Header], val previousStateDigest: String, val sigmaPreHeader: PreHeader -) +) extends js.Object diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala index fecd4ef67c..f95b8eb0e1 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala @@ -3,22 +3,48 @@ package org.ergoplatform.sdk.js import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel -/** Equivalent of [[special.sigma.Header]] available from JS. */ +/** Equivalent of [[special.sigma.Header]] available from JS. + * Represents data of the block header available in Sigma propositions. + */ @JSExportTopLevel("Header") class Header( + /** Hex representation of ModifierId of this Header */ val id: String, + /** Block version, to be increased on every soft and hardfork. */ val version: Byte, + /** Hex representation of ModifierId of the parent block */ val parentId: String, + /** Hex hash of ADProofs for transactions in a block */ val ADProofsRoot: String, + /** AvlTree of a state after block application */ val stateRoot: AvlTree, + /** Hex of root hash (for a Merkle tree) of transactions in a block. */ val transactionsRoot: String, + /** Block timestamp (in milliseconds since beginning of Unix Epoch) */ val timestamp: js.BigInt, + /** Current difficulty in a compressed view. + * NOTE: actually it is unsigned Int */ val nBits: js.BigInt, + /** Block height */ val height: Int, + /** Hex of root hash of extension section */ val extensionRoot: String, - val minerPk: String, - val powOnetimePk: String, + + /** Miner public key (hex of EC Point). Should be used to collect block rewards. + * Part of Autolykos solution. + */ + val minerPk: GroupElement, + + /** One-time public key (hex of EC Point). Prevents revealing of miners secret. */ + val powOnetimePk: GroupElement, + + /** Hex of nonce bytes */ val powNonce: String, + + /** Distance between pseudo-random number, corresponding to nonce `powNonce` and a secret, + * corresponding to `minerPk`. The lower `powDistance` is, the harder it was to find this solution. */ val powDistance: js.BigInt, + + /** Miner votes for changing system parameters. */ val votes: String ) extends js.Object diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index 4ee64c9b96..a317e3ab29 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -2,7 +2,7 @@ package org.ergoplatform.sdk.js import org.ergoplatform.ErgoBox._ import org.ergoplatform.sdk.JavaHelpers.UniversalConverter -import org.ergoplatform.sdk.{ExtendedInputBox, Iso} +import org.ergoplatform.sdk.{Iso, ExtendedInputBox} import org.ergoplatform.sdk.wallet.protocol.context import org.ergoplatform._ import scalan.RType @@ -11,20 +11,19 @@ import scorex.util.ModifierId import scorex.util.encode.Base16 import sigmastate.Values.{Constant, GroupElementConstant} import sigmastate.eval.Extensions.ArrayOps -import sigmastate.eval.{CAvlTree, CBigInt, CHeader, CPreHeader, Colls, Digest32Coll, Evaluation} +import sigmastate.eval.{CBigInt, Digest32Coll, Evaluation, CAvlTree, Colls, CGroupElement, CPreHeader, CHeader} import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box import sigmastate.fleetSdkCommon.distEsmTypesCommonMod.HexString import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters import sigmastate.fleetSdkCommon.distEsmTypesTokenMod.TokenAmount -import sigmastate.fleetSdkCommon.distEsmTypesTransactionsMod.{SignedTransaction, UnsignedTransaction} -import sigmastate.fleetSdkCommon.{distEsmTypesBoxesMod => boxesMod, distEsmTypesCommonMod => commonMod, distEsmTypesContextExtensionMod => contextExtensionMod, distEsmTypesInputsMod => inputsMod, distEsmTypesProverResultMod => proverResultMod, distEsmTypesRegistersMod => registersMod, distEsmTypesTokenMod => tokenMod} +import sigmastate.fleetSdkCommon.distEsmTypesTransactionsMod.{UnsignedTransaction, SignedTransaction} +import sigmastate.fleetSdkCommon.{distEsmTypesProverResultMod => proverResultMod, distEsmTypesContextExtensionMod => contextExtensionMod, distEsmTypesInputsMod => inputsMod, distEsmTypesBoxesMod => boxesMod, distEsmTypesCommonMod => commonMod, distEsmTypesRegistersMod => registersMod, distEsmTypesTokenMod => tokenMod} import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.serialization.{ErgoTreeSerializer, ValueSerializer} -import sigmastate.{AvlTreeData, AvlTreeFlags, SType} +import sigmastate.{AvlTreeData, SType, AvlTreeFlags} import special.collection.Coll import special.collection.Extensions.CollBytesOps import special.sigma -import special.sigma.GroupElement import java.math.BigInteger import scala.collection.immutable.ListMap @@ -57,17 +56,26 @@ object Isos { override def from(x: Coll[Byte]): String = x.toHex } - val isoStringToGroupElement: Iso[String, GroupElement] = new Iso[String, GroupElement] { - override def to(x: String): GroupElement = { + val isoStringToGroupElement: Iso[String, sigma.GroupElement] = new Iso[String, sigma.GroupElement] { + override def to(x: String): sigma.GroupElement = { val bytes = Base16.decode(x).get ValueSerializer.deserialize(bytes).asInstanceOf[GroupElementConstant].value } - override def from(x: GroupElement): String = { + override def from(x: sigma.GroupElement): String = { val bytes = ValueSerializer.serialize(GroupElementConstant(x)) Base16.encode(bytes) } } + val isoGroupElement: Iso[GroupElement, special.sigma.GroupElement] = new Iso[GroupElement, special.sigma.GroupElement] { + override def to(x: GroupElement): sigma.GroupElement = { + CGroupElement(x.point) + } + override def from(x: sigma.GroupElement): GroupElement = { + new GroupElement(x.asInstanceOf[CGroupElement].wrappedValue) + } + } + implicit val isoBoxId: Iso[boxesMod.BoxId, ErgoBox.BoxId] = new Iso[boxesMod.BoxId, ErgoBox.BoxId] { override def to(x: boxesMod.BoxId): ErgoBox.BoxId = ADKey @@@ isoStringToArray.to(x) @@ -124,8 +132,8 @@ object Isos { nBits = isoBigIntToLong.to(a.nBits), height = a.height, extensionRoot = isoStringToColl.to(a.extensionRoot), - minerPk = isoStringToGroupElement.to(a.minerPk), - powOnetimePk = isoStringToGroupElement.to(a.powOnetimePk), + minerPk = isoGroupElement.to(a.minerPk), + powOnetimePk = isoGroupElement.to(a.powOnetimePk), powNonce = isoStringToColl.to(a.powNonce), powDistance = isoBigInt.to(a.powDistance), votes = isoStringToColl.to(a.votes) @@ -144,8 +152,8 @@ object Isos { nBits = isoBigIntToLong.from(header.nBits), height = header.height, extensionRoot = isoStringToColl.from(header.extensionRoot), - minerPk = isoStringToGroupElement.from(header.minerPk), - powOnetimePk = isoStringToGroupElement.from(header.powOnetimePk), + minerPk = isoGroupElement.from(header.minerPk), + powOnetimePk = isoGroupElement.from(header.powOnetimePk), powNonce = isoStringToColl.from(header.powNonce), powDistance = isoBigInt.from(header.powDistance), votes = isoStringToColl.from(header.votes) @@ -161,7 +169,7 @@ object Isos { timestamp = isoBigIntToLong.to(a.timestamp), nBits = isoBigIntToLong.to(a.nBits), height = a.height, - minerPk = isoStringToGroupElement.to(a.minerPk), + minerPk = isoGroupElement.to(a.minerPk), votes = isoStringToColl.to(a.votes) ) } @@ -173,12 +181,45 @@ object Isos { timestamp = isoBigIntToLong.from(header.timestamp), nBits = isoBigIntToLong.from(header.nBits), height = header.height, - minerPk = isoStringToGroupElement.from(header.minerPk), + minerPk = isoGroupElement.from(header.minerPk), votes = isoStringToColl.from(header.votes) ) } } + val isoBlockchainParameters: Iso[BlockchainParameters, sdk.BlockchainParameters] = new Iso[BlockchainParameters, sdk.BlockchainParameters] { + override def to(a: BlockchainParameters): sdk.BlockchainParameters = { + sdk.BlockchainParameters( + storageFeeFactor = a.storageFeeFactor, + minValuePerByte = a.minValuePerByte, + maxBlockSize = a.maxBlockSize, + tokenAccessCost = a.tokenAccessCost, + inputCost = a.inputCost, + dataInputCost = a.dataInputCost, + outputCost = a.outputCost, + maxBlockCost = a.maxBlockCost, + softForkStartingHeight = Isos.isoUndefOr[Int, Int](Iso.identityIso).to(a.softForkStartingHeight), + softForkVotesCollected = Isos.isoUndefOr[Int, Int](Iso.identityIso).to(a.softForkVotesCollected), + blockVersion = a.blockVersion + ) + } + override def from(b: sdk.BlockchainParameters): BlockchainParameters = { + new BlockchainParameters( + storageFeeFactor = b.storageFeeFactor, + minValuePerByte = b.minValuePerByte, + maxBlockSize = b.maxBlockSize, + tokenAccessCost = b.tokenAccessCost, + inputCost = b.inputCost, + dataInputCost = b.dataInputCost, + outputCost = b.outputCost, + maxBlockCost = b.maxBlockCost, + softForkStartingHeight = Isos.isoUndefOr[Int, Int](Iso.identityIso).from(b.softForkStartingHeight), + softForkVotesCollected = Isos.isoUndefOr[Int, Int](Iso.identityIso).from(b.softForkVotesCollected), + blockVersion = b.blockVersion + ) + } + } + implicit val isoBlockchainStateContext: Iso[BlockchainStateContext, context.BlockchainStateContext] = new Iso[BlockchainStateContext, context.BlockchainStateContext] { override def to(a: BlockchainStateContext): context.BlockchainStateContext = { context.BlockchainStateContext( diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/PreHeader.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/PreHeader.scala index 7e38032446..ae75b67ece 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/PreHeader.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/PreHeader.scala @@ -3,14 +3,25 @@ package org.ergoplatform.sdk.js import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel -/** Equivalent of [[special.sigma.PreHeader]] available from JS. */ +/** Equivalent of [[special.sigma.PreHeader]] available from JS. + * Only header fields that can be predicted by a miner. + */ @JSExportTopLevel("PreHeader") class PreHeader( + /** Block version, to be increased on every soft and hardfork. */ val version: Byte, + /** Hex of id of parent block */ val parentId: String, + /** Block timestamp (in milliseconds since beginning of Unix Epoch) */ val timestamp: js.BigInt, + + /** Current difficulty in a compressed view. + * NOTE: actually it is unsigned integer */ val nBits: js.BigInt, + /** Block height */ val height: Int, - val minerPk: String, + /** Miner public key (hex of EC Point). Should be used to collect block rewards. */ + val minerPk: GroupElement, + /** Hex of miner votes bytes for changing system parameters. */ val votes: String ) extends js.Object diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala index 2660093165..8d3b3cd2e3 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala @@ -17,7 +17,7 @@ import sigmastate.eval.SigmaDsl */ @JSExportTopLevel("ProverBuilder") class ProverBuilder(parameters: BlockchainParameters, network: Byte) extends js.Object { - val _builder = new sdk.ProverBuilder(parameters, network) + val _builder = new sdk.ProverBuilder(Isos.isoBlockchainParameters.to(parameters), network) /** Configure this builder to use the given seed when building a new prover. * diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ReducedTransaction.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ReducedTransaction.scala index 30c63f5c33..66c5ce7f38 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ReducedTransaction.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ReducedTransaction.scala @@ -1,17 +1,19 @@ package org.ergoplatform.sdk.js import org.ergoplatform.sdk + +import scala.scalajs.js import scala.scalajs.js.annotation.JSExportTopLevel /** Equivalent of [[sdk.ReducedTransaction]] available from JS. */ @JSExportTopLevel("ReducedTransaction") -class ReducedTransaction(private[js] val _tx: sdk.ReducedTransaction) { +class ReducedTransaction(val _tx: sdk.ReducedTransaction) extends js.Object { /** Serialized bytes of this transaction in hex format. */ def toHex: String = _tx.toHex } @JSExportTopLevel("ReducedTransactionObj") -object ReducedTransaction { +object ReducedTransaction extends js.Object { /** Creates a [[ReducedTransaction]] from serialized bytes in hex format. */ def fromHex(hex: String): ReducedTransaction = { val tx = sdk.ReducedTransaction.fromHex(hex)