Skip to content

Commit

Permalink
sign-tx-multisig: implemented reduceTransactionInput
Browse files Browse the repository at this point in the history
  • Loading branch information
aslesarenko committed Jun 5, 2024
1 parent b7b61de commit 04e7cb6
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 20 deletions.
32 changes: 31 additions & 1 deletion sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,36 @@ class SigmaProver(_prover: sdk.SigmaProver) extends js.Object {
new ReducedTransaction(reducedTx)
}

/** Reduces the given input of transaction to the reduced form, which is ready to be
* used for signing.
*
* @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 inputIdx index of the input to reduce
* @return reduced input data (reduction result, extension)
*/
def reduceTransactionInput(
stateCtx: BlockchainStateContext,
unsignedTx: transactionsMod.UnsignedTransaction,
boxesToSpend: js.Array[inputsMod.EIP12UnsignedInput],
dataInputs: js.Array[boxesMod.Box[commonMod.Amount, NonMandatoryRegisters]],
tokensToBurn: js.Array[tokenMod.TokenAmount[commonMod.Amount]],
inputIdx: Int
): ReducedInputData = {
val unreducedTx = sdk.UnreducedTransaction(
unsignedTx = isoUnsignedTransaction.to(unsignedTx),
boxesToSpend = sigma.js.Isos.isoArrayToIndexed(isoEIP12UnsignedInput).to(boxesToSpend),
dataInputs = sigma.js.Isos.isoArrayToIndexed(sigma.js.Box.isoBox).to(dataInputs),
tokensToBurn = sigma.js.Isos.isoArrayToIndexed(sigma.data.js.Isos.isoToken.andThen(sdk.SdkIsos.isoErgoTokenToPair.inverse)).to(tokensToBurn)
)
val ctx = isoBlockchainStateContext.to(stateCtx)
val reducedInput = _prover.reduceTransactionInput(ctx, unreducedTx, inputIdx)
ReducedInputData.isoToSdk.from(reducedInput)
}

/** Signs the reduced transaction.
* @param reducedTx reduced transaction to be signed
* @return signed transaction containting all the required proofs (signatures)
Expand All @@ -77,7 +107,7 @@ class SigmaProver(_prover: sdk.SigmaProver) extends js.Object {
* All the necessary secrets should be configured in this prover to satisfy the given
* sigma proposition in the reducedInput.
*/
def signReduced(
def signReducedInput(
reducedInput: ReducedInputData,
messageHex: String,
hintsBag: UndefOr[ProverHints]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,28 +41,16 @@ class ReducingInterpreter(params: BlockchainParameters) extends ErgoLikeInterpre
ReducedInputData(res, ctxUpdInitCost.extension)
}

/** Reduce inputs of the given unsigned transaction to provable sigma propositions using
* the given context. See [[ReducedErgoLikeTransaction]] for details.
*
* @param unreducedTx unreduced transaction data to be reduced (holds unsigned transaction)
* @param stateContext state context of the blockchain in which the transaction should be signed
* @param baseCost the cost accumulated so far and before this operation
* @return a new reduced transaction with all inputs reduced and the cost of this transaction
* The returned cost include all the costs accumulated during the reduction:
* 1) `baseCost`
* 2) general costs of the transaction based on its data
* 3) reduction cost for each input.
*/
def reduceTransaction(
unreducedTx: UnreducedTransaction,
stateContext: BlockchainStateContext,
baseCost: Int
): ReducedTransaction = {
private def validateTransaction(
unreducedTx: UnreducedTransaction
): Unit = {
val unsignedTx = unreducedTx.unsignedTx
val boxesToSpend = unreducedTx.boxesToSpend
val dataBoxes = unreducedTx.dataInputs
if (unsignedTx.inputs.length != boxesToSpend.length) throw new Exception("Not enough boxes to spend")
if (unsignedTx.dataInputs.length != dataBoxes.length) throw new Exception("Not enough data boxes")
if (unsignedTx.inputs.length != boxesToSpend.length)
throw new Exception("Not enough boxes to spend")
if (unsignedTx.dataInputs.length != dataBoxes.length)
throw new Exception("Not enough data boxes")

val tokensToBurn = unreducedTx.tokensToBurn
val inputTokens = boxesToSpend.flatMap(_.box.additionalTokens.toArray)
Expand Down Expand Up @@ -96,6 +84,30 @@ class ReducingInterpreter(params: BlockchainParameters) extends ErgoLikeInterpre
}
}
}
}

/** Reduce inputs of the given unsigned transaction to provable sigma propositions using
* the given context. See [[ReducedErgoLikeTransaction]] for details.
*
* @param unreducedTx unreduced transaction data to be reduced (holds unsigned transaction)
* @param stateContext state context of the blockchain in which the transaction should be signed
* @param baseCost the cost accumulated so far and before this operation
* @return a new reduced transaction with all inputs reduced and the cost of this transaction
* The returned cost include all the costs accumulated during the reduction:
* 1) `baseCost`
* 2) general costs of the transaction based on its data
* 3) reduction cost for each input.
*/
def reduceTransaction(
unreducedTx: UnreducedTransaction,
stateContext: BlockchainStateContext,
baseCost: Int
): ReducedTransaction = {
validateTransaction(unreducedTx)
val unsignedTx = unreducedTx.unsignedTx
val boxesToSpend = unreducedTx.boxesToSpend
val dataBoxes = unreducedTx.dataInputs

// Cost of transaction initialization: we should read and parse all inputs and data inputs,
// and also iterate through all outputs to check rules
val initialCost = ArithUtils.addExact(
Expand Down Expand Up @@ -154,4 +166,52 @@ class ReducingInterpreter(params: BlockchainParameters) extends ErgoLikeInterpre
cost = (currentCost - baseCost).toIntExact)
ReducedTransaction(reducedTx)
}

/** Reduce the given input of the given unsigned transaction to compute sigma propositions using
* the given context. See [[ReducedErgoLikeTransaction]] for details.
*
* @param unreducedTx unreduced transaction data to be reduced (holds unsigned transaction)
* @param inputIdx index of the input to reduce
* @param stateContext state context of the blockchain in which the transaction should be signed
* @return a new reduced transaction with all inputs reduced and the cost of this transaction
* The returned cost include all the costs accumulated during the reduction:
* 1) `baseCost`
* 2) general costs of the transaction based on its data
* 3) reduction cost for each input.
*/
def reduceTransactionInput(
unreducedTx: UnreducedTransaction,
inputIdx: Int,
stateContext: BlockchainStateContext,
): ReducedInputData = {
validateTransaction(unreducedTx)
val unsignedTx = unreducedTx.unsignedTx
val boxesToSpend = unreducedTx.boxesToSpend
val dataBoxes = unreducedTx.dataInputs

val transactionContext = TransactionContext(boxesToSpend.map(_.box), dataBoxes, unsignedTx)

val inputBox = boxesToSpend(inputIdx)
val unsignedInput = unsignedTx.inputs(inputIdx)
require(util.Arrays.equals(unsignedInput.boxId, inputBox.box.id))

val context = new ErgoLikeContext(
AvlTreeData.avlTreeFromDigest(stateContext.previousStateDigest),
stateContext.sigmaLastHeaders,
stateContext.sigmaPreHeader,
transactionContext.dataBoxes,
transactionContext.boxesToSpend,
transactionContext.spendingTransaction,
inputIdx.toShort,
inputBox.extension,
ValidationRules.currentSettings,
costLimit = params.maxBlockCost,
initCost = 0, // not computed in this method
activatedScriptVersion = (params.blockVersion - 1).toByte
)

val reducedInput = reduce(Interpreter.emptyEnv, inputBox.box.ergoTree, context)
reducedInput
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ class SigmaProver(var _prover: AppkitProvingInterpreter, networkPrefix: NetworkP
reduced
}

/** Reduces a given input of the given `UnreducedTransaction` using the prover's secret
* keys and the provided [[BlockchainStateContext]] with a base cost.
*/
def reduceTransactionInput(stateCtx: BlockchainStateContext, tx: UnreducedTransaction, inputIdx: Int): ReducedInputData = {
val inputData = _prover.reduceTransactionInput(
unreducedTx = tx, inputIdx, stateContext = stateCtx)
inputData
}

/** Signs a given ReducedTransaction using the prover's secret keys. */
def signReduced(tx: ReducedTransaction, hints: Option[TransactionHintsBag] = None): SignedTransaction = {
_prover.signReduced(tx, tx.ergoTx.cost, hints)
Expand Down

0 comments on commit 04e7cb6

Please sign in to comment.