From c4369ac680b37df734b5ddccdf9458b3c6e969e7 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 5 Aug 2023 18:51:49 +0200 Subject: [PATCH] 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) + } + } }