Skip to content

Commit

Permalink
multisig: public signer can create signed transaction from ready session
Browse files Browse the repository at this point in the history
  • Loading branch information
aslesarenko committed Aug 1, 2023
1 parent 2846140 commit b95564d
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package sigmastate.interpreter

import java.math.BigInteger
import sigmastate.{NodePosition, SigmaLeaf, UncheckedTree}
import sigmastate.{NodePosition, PositionedLeaf, SigmaLeaf, UncheckedTree}
import sigmastate.Values.SigmaBoolean
import sigmastate.basics.FirstProverMessage
import sigmastate.basics.VerifierMessage.Challenge
Expand Down Expand Up @@ -128,6 +128,14 @@ case class HintsBag(hints: Seq[Hint]) {
/** @return a new bag with hints satisfying the predicate `p`. */
def filter(p: Hint => Boolean): HintsBag = HintsBag(hints.filter(p))

/** @return true if there is a proof in this bag for the given leaf of sigma proposition. */
def hasProofFor(pl: PositionedLeaf): Boolean = {
hints.exists {
case RealSecretProof(image, _, _, position) => pl.leaf == image && pl.position == position
case _ => false
}
}

override def toString: String = s"HintsBag(${hints.mkString("\n")})"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,21 +212,25 @@ class AppkitProvingInterpreter(
* Note, this method doesn't require context to generate proofs (aka signatures).
*
* @param reducedTx unsigend transaction augmented with reduced
* @param baseCost initial cost before signing
* @param inputBagsOpt optional sequence of hints bags for each input
* @return a new signed transaction with all inputs signed and the cost of this transaction
* The returned cost includes:
* - the costs of obtaining reduced transaction
* - the cost of verification of each signed input
*/
def signReduced(reducedTx: ReducedTransaction, baseCost: Int): SignedTransaction = {
def signReduced(reducedTx: ReducedTransaction, baseCost: Int, inputBagsOpt: Option[IndexedSeq[HintsBag]] = None): SignedTransaction = {
val provedInputs = mutable.ArrayBuilder.make[Input]
val unsignedTx = reducedTx.ergoTx.unsignedTx
val inputBags = inputBagsOpt.getOrElse(
IndexedSeq.fill(unsignedTx.inputs.length)(HintsBag.empty))

val maxCost = params.maxBlockCost
var currentCost: Long = baseCost

for ((reducedInput, boxIdx) <- reducedTx.ergoTx.reducedInputs.zipWithIndex ) {
for ((reducedInput, boxIdx) <- reducedTx.ergoTx.reducedInputs.zipWithIndex) {
val unsignedInput = unsignedTx.inputs(boxIdx)
val proverResult = proveReduced(reducedInput, unsignedTx.messageToSign)
val proverResult = proveReduced(reducedInput, unsignedTx.messageToSign, inputBags(boxIdx))
val signedInput = Input(unsignedInput.boxId, proverResult)

val verificationCost = estimateCryptoVerifyCost(reducedInput.reductionResult.value).toBlockCost
Expand Down
18 changes: 17 additions & 1 deletion sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import special.collection.{Coll, CollBuilder, PairColl}
import special.sigma.{Header, PreHeader}

import scala.collection.compat.BuildFrom
import scala.collection.{GenIterable, immutable}
import scala.collection.{GenIterable, immutable, mutable}
import scala.reflect.ClassTag

object Extensions {
Expand Down Expand Up @@ -222,4 +222,20 @@ object Extensions {
v.updated(i, newItem).asInstanceOf[C[T]]
}
}

/** extension methods for IndexedSeq */
implicit class MutableMapOps[K, V](val m: mutable.Map[K, V]) extends AnyVal {
/** Modifies the Map by applying a function to the given element if it exists.
*
* @param k the key of the element to modify.
* @param f the function to apply to the element.
* @return the new value associated with the specified key
*/
def modifyIfExists(k: K)(f: V => V): Option[V] = {
m.updateWith(k) {
case Some(v) => Some(f(v))
case None => None
}
}
}
}
12 changes: 12 additions & 0 deletions sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ class SigmaProver(private[sdk] val _prover: AppkitProvingInterpreter, networkPre
_prover.signReduced(tx, tx.ergoTx.cost)
}

/** Signs a given ReducedTransaction using the prover's secret keys and hints.
* @param tx - transaction to sign
* @param inputHints - hints containing proofs for all inputs
*/
def signReduced(tx: ReducedTransaction, inputBags: IndexedSeq[HintsBag]): SignedTransaction = {
val nInputs = tx.ergoTx.reducedInputs.length
require(nInputs == inputBags.length,
s"Number of bags ${inputBags.length} must be equal to number of inputs $nInputs")

_prover.signReduced(tx, tx.ergoTx.cost, Some(inputBags))
}

def generateCommitments(sigmaTree: SigmaBoolean): HintsBag = {
_prover.generateCommitments(sigmaTree)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.ergoplatform.sdk.multisig

import org.ergoplatform.sdk.Extensions.MutableMapOps
import sigmastate.SigmaLeaf

import scala.collection.mutable
Expand Down Expand Up @@ -29,6 +30,14 @@ class CosigningServer {
def updateSession(session: SigningSession): Unit = {
sessions.put(session.id, session)
}

def getReadySessions: Seq[SigningSession] = {
sessions.values.filter(_.isReady).toSeq
}

def finishSession(sessionId: SessionId): Unit = {
sessions.modifyIfExists(sessionId)(_.finish())
}
}

object CosigningServer {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.ergoplatform.sdk.multisig

import org.ergoplatform.P2PKAddress
import org.ergoplatform.sdk.{ReducedTransaction, SigmaProver}
import org.ergoplatform.sdk.{ReducedTransaction, SigmaProver, SignedTransaction}
import scalan.reflection.memoize
import sigmastate.SigmaLeaf
import sigmastate.Values.SigmaBoolean
Expand Down Expand Up @@ -89,6 +89,10 @@ class Signer(val prover: SigmaProver) {
session.addHintsAt(action.inputIndex, newHints)
}

def createSignedTransaction(session: SigningSession): SignedTransaction = {
prover.signReduced(session.reduced, session.collectedHints)
}

override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match {
case s: Signer => s.prover == prover
case _ => false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package org.ergoplatform.sdk.multisig

import org.ergoplatform.sdk.Extensions.IndexedSeqOps
import org.ergoplatform.sdk.ReducedTransaction
import sigmastate.{PositionedLeaf, SigmaLeaf}
import sigmastate.interpreter.{Hint, HintsBag}
import sigmastate.{PositionedLeaf, SigmaLeaf}

case class SessionId(value: String) extends AnyVal

Expand All @@ -20,7 +20,8 @@ case class CreateSignature(signerPk: SigmaLeaf, inputIndex: Int, leaf: Positione

case class SigningSession(
reduced: ReducedTransaction,
collectedHints: Vector[HintsBag]
collectedHints: Vector[HintsBag],
isFinished: Boolean = false
) {
require(reduced.ergoTx.reducedInputs.length == collectedHints.length,
s"Collected hints should be provided for each input, but got ${collectedHints.length} hints for ${reduced.ergoTx.reducedInputs.length} inputs")
Expand All @@ -42,6 +43,17 @@ case class SigningSession(
copy(collectedHints = collectedHints.modify(inputIndex, _.addHints(hints: _*)))
}

def isReady: Boolean = {
positionsToProve.zipWithIndex.forall { case (positions, inputIndex) =>
val inputHints = collectedHints(inputIndex)
positions.forall { pl => inputHints.hasProofFor(pl) }
}
}

/** Mark this session as finished, no more actions can be performed. */
def finish(): SigningSession = {
copy(isFinished = true)
}

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matcher
.build()
)

/** Special signer without secretes */
val publicSigner = Signer(ProverBuilder.forMainnet(mainnetParameters).build())

val alice = createSigner("Alice secret")
val bob = createSigner("Bob secret")
val carol = createSigner("Carol secret")
Expand Down Expand Up @@ -121,7 +124,7 @@ class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matcher
// anyone can start a session (e.g. Alice)
val sessionId = server.addSession(alice.startCosigning(reduced))

// each cosigner generated a commitment and stores it in the session
// each cosigner generates a commitment and stores it in the session
cosigners.zipWithIndex.foreach { case (signer, i) =>
val signerPk = signer.pubkey

Expand Down Expand Up @@ -152,7 +155,7 @@ class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matcher
server.getSession(sessionId).get.collectedHints.size shouldBe cosigners.size


// each cosigner generated a commitment and stores it in the session
// each cosigner generates a proof and stores it in the session
cosigners.zipWithIndex.foreach { case (signer, i) =>
val signerPk = signer.pubkey
val session = server.getSessionsFor(signer.allKeys).head
Expand All @@ -165,6 +168,13 @@ class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matcher
server.updateSession(newSession)
}

// complete signing of the ready session
val session = server.getReadySessions.head
val signedTx = publicSigner.createSignedTransaction(session)

// send the signed transaction to the network and wait for confirmation
// then finish the session
server.finishSession(session.id)
}
}

Expand Down

0 comments on commit b95564d

Please sign in to comment.