Skip to content

Commit

Permalink
multisig: SigmaBoolean.collectLeaves v1
Browse files Browse the repository at this point in the history
  • Loading branch information
aslesarenko committed Jul 28, 2023
1 parent e77b596 commit 5206ff1
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 34 deletions.
18 changes: 18 additions & 0 deletions interpreter/shared/src/main/scala/sigmastate/Values.scala
Original file line number Diff line number Diff line change
Expand Up @@ -735,8 +735,12 @@ object Values {
trait SigmaBoolean {
/** Unique id of the node class used in serialization of SigmaBoolean. */
val opCode: OpCode

/** Size of the proposition tree (number of nodes). */
def size: Int

/** Recursively collect all the leaves of this sigma expression into `buf`. */
def collectLeaves(buf: mutable.ArrayBuffer[SigmaLeaf]): Unit
}

object SigmaBoolean {
Expand Down Expand Up @@ -1006,6 +1010,20 @@ object Values {
s"ProveDHTuple(${showECPoint(gv)}, ${showECPoint(hv)}, ${showECPoint(uv)}, ${showECPoint(vv)})"
case _ => sb.toString
}

/** Traverses the tree and returns all leaves nodes of sigma proposition tree. */
def leaves(): Seq[SigmaLeaf] = {
val buf = mutable.ArrayBuffer.empty[SigmaLeaf]
sb.collectLeaves(buf)
buf.toSeq
}

/** Traverses the tree and returns all DISTINCT leaves of sigma proposition tree. */
def distinctLeaves: Set[SigmaLeaf] = {
val buf = mutable.ArrayBuffer.empty[SigmaLeaf]
sb.collectLeaves(buf)
buf.iterator.toSet
}
}

sealed trait BlockItem extends NotReadyValue[SType] {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package sigmastate.basics

import java.math.BigInteger

import sigmastate.crypto.BigIntegers
import sigmastate.Values.Value.PropositionCode
import sigmastate._
import sigmastate.basics.VerifierMessage.Challenge
import sigmastate.eval.SigmaDsl
import CryptoConstants.EcPointType
import sigmastate.serialization.{OpCodes, GroupElementSerializer}
import sigmastate.serialization.{GroupElementSerializer, OpCodes}
import sigmastate.serialization.OpCodes.OpCode
import special.sigma.SigmaProp

import scala.collection.mutable


trait DiffieHellmanTupleProtocol extends SigmaProtocol[DiffieHellmanTupleProtocol] {
override type A = FirstDiffieHellmanTupleProverMessage
Expand Down
12 changes: 11 additions & 1 deletion interpreter/shared/src/main/scala/sigmastate/trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,21 @@ import scala.collection.mutable.ArrayBuffer
*/
trait SigmaConjecture extends SigmaBoolean {
def children: Seq[SigmaBoolean]

override def collectLeaves(buf: mutable.ArrayBuffer[SigmaLeaf]): Unit = {
cfor(0)(_ < children.length, _ + 1) { i =>
children(i).collectLeaves(buf)
}
}
}

/**
* Basic trait for leafs of crypto-trees, such as ProveDlog and ProveDiffieHellman instances
*/
trait SigmaLeaf extends SigmaBoolean
trait SigmaLeaf extends SigmaBoolean {
override def collectLeaves(buf: mutable.ArrayBuffer[SigmaLeaf]): Unit =
buf += this
}


/**
Expand Down Expand Up @@ -130,6 +139,7 @@ case class CTHRESHOLD(k: Int, children: Seq[SigmaBoolean]) extends SigmaConjectu
abstract class TrivialProp(val condition: Boolean) extends SigmaBoolean with Product1[Boolean] {
override def _1: Boolean = condition
override def canEqual(that: Any): Boolean = that != null && that.isInstanceOf[TrivialProp]
override def collectLeaves(buf: mutable.ArrayBuffer[SigmaLeaf]): Unit = () // not a leaf
}
object TrivialProp {
// NOTE: the corresponding unapply is missing because any implementation (even using Nullable)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package sigmastate

import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import sigmastate.TrivialProp.{FalseProp, TrueProp}
import sigmastate.Values.SigmaBoolean
import sigmastate.basics.DLogProtocol.ProveDlog
import sigmastate.basics.ProveDHTuple
import sigmastate.basics.VerifierMessage.Challenge
import sigmastate.crypto.{GF2_192, GF2_192_Poly}
import sigmastate.utils.Helpers
import special.sigma.SigmaTestingData

class SigmaProtocolSpecification extends SigmaTestingData {
class SigmaProtocolSpecification extends SigmaTestingData with ScalaCheckPropertyChecks {

property("CThresholdUncheckedNode equality") {
val c1 = Challenge @@ Array[Byte](1)
Expand All @@ -24,4 +30,34 @@ class SigmaProtocolSpecification extends SigmaTestingData {
assertResult(true)(n4 != n5)
}

property("collecting SigmaBoolean leaves") {

val dlog1 = ProveDlog(Helpers.decodeECPoint("0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e"))
val dlog2 = ProveDlog(Helpers.decodeECPoint("02af645874c3b53465a5e9d820eb207d6001258c3b708f0d31d7c2e342833dce64"))
val dht1 = ProveDHTuple(
Helpers.decodeECPoint("021b4c0f54304b9c427d5c87846dd56a2fa361cd34a6cb8a2090aef043c9383198"),
Helpers.decodeECPoint("026826a4a9d0ec937c24d72da381ee6b5e74e49fb79a6a23a03fe0aa2cab3448ba"),
Helpers.decodeECPoint("02535153378ce30df1b31137680748de728f8512d29dfeeb1f331ac6a787cd00d8"),
Helpers.decodeECPoint("03d00d0174cdffd7ce3b77ef45ef9573c18fb76929fb3340f7ceea8d0be9bf5c4a")
)
val and = CAND(Seq(dlog1, dlog2))
val or = COR(Seq(dlog1, dlog2))
val th = CTHRESHOLD(1, Seq(dlog1, dht1))
val th2 = CTHRESHOLD(2, Seq(TrueProp, and, or, th, dlog1, dht1))

val table = Table(("proposition", "leafs"),
(TrueProp, Seq()),
(FalseProp, Seq()),
(dlog1, Seq(dlog1)),
(dht1, Seq(dht1)),
(and, Seq(dlog1, dlog2)),
(or, Seq(dlog1, dlog2)),
(th, Seq(dlog1, dht1)),
(th2, Seq(dlog1, dlog2, dlog1, dlog2, dlog1, dht1, dlog1, dht1))
)
forAll(table) { (prop: SigmaBoolean, leafs: Seq[SigmaLeaf]) =>
prop.leaves shouldBe leafs
}
th2.leaves.iterator.distinct.toSet shouldBe Set(dlog1, dlog2, dht1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,26 @@ class DistributedSigSpecification extends CompilerTestingCommons

val bagA = HintsBag(Seq(dlBKnown, dlCKnown))

// Alice starts co-signing (needs all commitments in the hingsBag)
val proofAlice = proverA.prove(prop, ctx, fakeMessage, bagA).get

val bagC = proverB.bagForMultisig(ctx, prop, proofAlice.proof, Seq(pubkeyAlice))
.addHint(carolHints.ownCommitments.head)
.addHint(dlBKnown)

// Carol continues co-signing
val proofCarol = proverC.prove(prop, ctx, fakeMessage, bagC).get

val bagB = proverB.bagForMultisig(ctx, prop, proofCarol.proof, Seq(pubkeyAlice, pubkeyCarol))
.addHint(bobHints.ownCommitments.head)

// Bob finishes co-signing
val proofBob = proverB.prove(prop, ctx, fakeMessage, bagB).get

// Proof generated by Alice without getting Bob's part is not correct
verifier.verify(prop, ctx, proofAlice, fakeMessage).get._1 shouldBe false

// Proof generated by Alice without getting Bob's part is not correct
// Proof generated by Carol without getting Bob's part is not correct
verifier.verify(prop, ctx, proofCarol, fakeMessage).get._1 shouldBe false

// Compound proof from Bob is correct
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
package org.ergoplatform.sdk.multisig

import org.ergoplatform.P2PKAddress
import org.ergoplatform.sdk.SigmaProver
import org.ergoplatform.sdk.{ReducedTransaction, SigmaProver}

case class Signer(prover: SigmaProver) {
def masterAddress: P2PKAddress = prover.getP2PKAddress
def eip3Addresses: Seq[P2PKAddress] = prover.getEip3Addresses
}

object Signer {


def main(args: Array[String]): Unit = {

// val pkAlice =
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package org.ergoplatform.sdk.multisig

import org.ergoplatform.sdk.Extensions.DoubleOps
import org.ergoplatform.sdk.NetworkType.Mainnet
import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext
import org.ergoplatform.sdk._
import org.ergoplatform.{ErgoAddress, ErgoTreePredef}
import org.ergoplatform.{ErgoAddress, ErgoTreePredef, P2PKAddress}
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 scorex.util.ModifierId
import sigmastate.{SigmaLeaf, TestsBase}
import sigmastate.Values.{Constant, ErgoTree}
import special.sigma.SigmaTestingData

Expand Down Expand Up @@ -43,6 +43,19 @@ class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matcher
)
)

case class Signer(prover: SigmaProver) {
def masterAddress: P2PKAddress = prover.getP2PKAddress

def eip3Addresses: Seq[P2PKAddress] = prover.getEip3Addresses

def startCosigning(reduced: ReducedTransaction): SigningSession = {
SigningSession(reduced)
}
}

object Signer {
}

def createSigner(secret: String): Signer = Signer(
ProverBuilder.forMainnet(mainnetParameters)
.withMnemonic(SecretString.create(secret), SecretString.empty())
Expand Down Expand Up @@ -125,32 +138,52 @@ class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matcher
}
}

class SigningSession(
val reduced: ReducedTransaction
)
object SigningSession {
def apply(reduced: ReducedTransaction): SigningSession = {
case class SigningSession(
reduced: ReducedTransaction
) {
def txId: ModifierId = reduced.ergoTx.unsignedTx.id

/** Returns a set of public keys (leaf sigma propositions) for each input. */
def participants: Seq[Set[SigmaLeaf]] = {
val inputs = reduced.ergoTx.reducedInputs
val participants = inputs.zipWithIndex.map { case (reducedInput, i) =>
inputs.map { reducedInput =>
val sb = reducedInput.reductionResult.value
sb
sb.distinctLeaves
}
new SigningSession(reduced)
}
}

object SigningSession {
}

class CosigningServer(
val addressBook: AddressBook
)

) {
val sessions: mutable.Map[String, SigningSession] = mutable.Map.empty

def addSession(session: SigningSession): Unit = {
require(!sessions.contains(session.txId), s"Session for tx ${session.txId} already exists")
sessions.put(session.txId, session)
}

def getSessionsFor(signer: Signer): Seq[SigningSession] = {
sessions.values.toSeq
}
}
object CosigningServer {
def apply(addressBook: AddressBook): CosigningServer = {
new CosigningServer(addressBook)
}
}

property("Signing workflow") {
val cosigners = Seq(alice, bob, carol)
val inputs = cosigners.map(createInput(ctx, _))
val reduced = createReducedTx(ctx, inputs, alice.masterAddress, david.masterAddress)

reduced shouldNot be(null)

// neither of cosigners can sign the transaction
// none of cosigners can sign the transaction
cosigners.foreach(signer =>
assertExceptionThrown(
signer.prover.signReduced(reduced),
Expand All @@ -163,7 +196,15 @@ class SigningSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matcher
addressBook.get(s.masterAddress) shouldBe Some(s)
)

val session = new SigningSession(reduced)
val server = CosigningServer(addressBook)

// anyone can start a session (e.g. Alice)
server.addSession(alice.startCosigning(reduced))

// participants can retrieve the session
{
val session = server.getSessionsFor(alice).head
}

}
}
Expand Down

0 comments on commit 5206ff1

Please sign in to comment.