Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction builders and multisig #889

Draft
wants to merge 20 commits into
base: v5.0.13-RC
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8ff3525
multisig: added AddressBook
aslesarenko Jul 11, 2023
e0c4255
multisig: fixed property("Evaluation - ColdWallet Contract Example")
aslesarenko Jul 12, 2023
8aaa6c1
Merge remote-tracking branch 'origin/v5.0.10-RC' into multisig
aslesarenko Jul 18, 2023
8e44f0e
Merge remote-tracking branch 'origin/v5.0.10-RC' into multisig
aslesarenko Jul 28, 2023
4bfedca
multisig: some refactoring
aslesarenko Jul 28, 2023
e77b596
Merge remote-tracking branch 'origin/refactoring' into multisig
aslesarenko Jul 28, 2023
5206ff1
multisig: SigmaBoolean.collectLeaves v1
aslesarenko Jul 28, 2023
42f8297
multisig: collecting PositionedLeafs
aslesarenko Jul 28, 2023
0703aae
multisig: moving classes around + get SigningActions for a singer (v1)
aslesarenko Jul 29, 2023
8f6664b
multisig: execute of CreateCommitment action
aslesarenko Jul 29, 2023
71a4799
multisig: store updated session on server
aslesarenko Jul 29, 2023
6ed6579
multisig: fixed test
aslesarenko Jul 30, 2023
3ee7f68
multisig: added Signer.generateCommitments
aslesarenko Jul 30, 2023
7798f6c
Merge remote-tracking branch 'origin/refactoring' into multisig
aslesarenko Jul 30, 2023
7038800
multisig: fixes after merge
aslesarenko Jul 30, 2023
f925133
Merge remote-tracking branch 'origin/refactoring' into multisig
aslesarenko Jul 30, 2023
dff15cb
multisig: store hints in Signer, move execution to Signer
aslesarenko Jul 31, 2023
9923e18
multisig: check post-condition of each signer generated commitments
aslesarenko Jul 31, 2023
2846140
multisig: execute CreateSignature action (v1)
aslesarenko Aug 1, 2023
b95564d
multisig: public signer can create signed transaction from ready session
aslesarenko Aug 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions common/shared/src/main/scala/scalan/util/CollectionUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 update(updater: A => A): A = updater(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
Expand Down
14 changes: 4 additions & 10 deletions core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]

Expand Down Expand Up @@ -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

Expand All @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ sealed trait ErgoAddress {
def networkPrefix: NetworkPrefix
}

object ErgoAddress {
def fromSigmaBoolean(sb: SigmaBoolean)(implicit encoder: ErgoAddressEncoder): ErgoAddress = sb match {
case pk: ProveDlog => P2PKAddress(pk)
case _ => Pay2SAddress(ErgoTree.fromSigmaBoolean(sb))
}
}

/** Implementation of pay-to-public-key [[ErgoAddress]]. */
class P2PKAddress(val pubkey: ProveDlog,
val pubkeyBytes: Array[Byte])
Expand Down
41 changes: 41 additions & 0 deletions interpreter/shared/src/main/scala/sigmastate/NodePosition.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package sigmastate

/**
* Data type which encodes position of a node in a tree.
*
* Position is encoded like following (the example provided is for CTHRESHOLD(2, Seq(pk1, pk2, pk3 && pk4)) :
*
* 0
* / | \
* / | \
* 0-0 0-1 0-2
* /|
* / |
* / |
* / |
* 0-2-0 0-2-1
*
* So a hint associated with pk1 has a position "0-0", pk4 - "0-2-1" .
*
* Please note that "0" prefix is for a crypto tree. There are several kinds of trees during evaluation.
* Initial mixed tree (ergoTree) would have another prefix.
*
* @param positions - positions from root (inclusive) in top-down order
*/
case class NodePosition(positions: Seq[Int]) {
def child(childIdx: Int): NodePosition = NodePosition(positions :+ childIdx)
def ++(path: Seq[Int]): NodePosition = NodePosition(positions ++ path)
override def toString: String = positions.mkString("-")
}

object NodePosition {
/**
* Prefix to encode node positions in a crypto tree.
*/
val CryptoTreePrefix = NodePosition(Seq(0))

/**
* Prefix to encode node positions in an ErgoTree instance.
*/
val ErgoTreePrefix = NodePosition(Seq(1))
}
41 changes: 0 additions & 41 deletions interpreter/shared/src/main/scala/sigmastate/UnprovenTree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,47 +34,6 @@ trait ProofTreeConjecture extends ProofTree {
val children: Seq[ProofTree]
}

/**
* Data type which encodes position of a node in a tree.
*
* Position is encoded like following (the example provided is for CTHRESHOLD(2, Seq(pk1, pk2, pk3 && pk4)) :
*
* 0
* / | \
* / | \
* 0-0 0-1 0-2
* /|
* / |
* / |
* / |
* 0-2-0 0-2-1
*
* So a hint associated with pk1 has a position "0-0", pk4 - "0-2-1" .
*
* Please note that "0" prefix is for a crypto tree. There are several kinds of trees during evaluation.
* Initial mixed tree (ergoTree) would have another prefix.
*
* @param positions - positions from root (inclusive) in top-down order
*/
case class NodePosition(positions: Seq[Int]) {

def child(childIdx: Int): NodePosition = NodePosition(positions :+ childIdx)

override def toString: String = positions.mkString("-")
}

object NodePosition {
/**
* Prefix to encode node positions in a crypto tree.
*/
val CryptoTreePrefix = NodePosition(Seq(0))

/**
* Prefix to encode node positions in an ErgoTree instance.
*/
val ErgoTreePrefix = NodePosition(Seq(1))
}

/**
* A node of a sigma-tree used by the prover. See ProverInterpreter comments and the
* ErgoScript white-paper https://ergoplatform.org/docs/ErgoScript.pdf , Appendix A, for details
Expand Down
21 changes: 21 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,15 @@ 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`.
* @param position - position of this node in the tree
* @param buf - buffer to collect leaves into
*/
def collectLeaves(position: NodePosition, buf: mutable.ArrayBuffer[PositionedLeaf]): Unit
}

object SigmaBoolean {
Expand Down Expand Up @@ -1006,6 +1013,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[PositionedLeaf] = {
val buf = mutable.ArrayBuffer.empty[PositionedLeaf]
sb.collectLeaves(NodePosition.CryptoTreePrefix, buf)
buf.toSeq
}

/** Traverses the tree and returns all DISTINCT leaves of sigma proposition tree. */
def distinctLeaves: Set[SigmaLeaf] = {
val buf = mutable.ArrayBuffer.empty[PositionedLeaf]
sb.collectLeaves(NodePosition.CryptoTreePrefix, buf)
buf.iterator.map(_.leaf).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 = FirstDHTupleProverMessage
Expand Down
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 @@ -125,8 +125,18 @@ case class HintsBag(hints: Seq[Hint]) {

def ++(other: HintsBag): HintsBag = HintsBag(other.hints ++ hints)

override def toString: String = s"HintsBag(${hints.mkString("\n")})"
/** @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")})"
}

object HintsBag {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils {

// Prover Step 2: If the root of the tree is marked "simulated" then the prover does not have enough witnesses
// to perform the proof. Abort.
assert(step1.real, s"Tree root should be real but was $step1")
require(step1.real, s"Tree root should be real but was $step1")

// Prover Step 3: Change some "real" nodes to "simulated" to make sure each node
// has the right number of simulated children.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ trait ProverUtils extends Interpreter {
realSecretsToExtract: Seq[SigmaBoolean],
simulatedSecretsToExtract: Seq[SigmaBoolean] = Seq.empty): HintsBag = {
val reduced = fullReduction(ergoTree, context, Interpreter.emptyEnv)
bagForMultisig(context, reduced.value, proof, realSecretsToExtract, simulatedSecretsToExtract)
bagForMultisig(reduced.value, proof, realSecretsToExtract, simulatedSecretsToExtract)
}

/**
Expand All @@ -89,15 +89,13 @@ trait ProverUtils extends Interpreter {
*
* See DistributedSigSpecification for examples of usage.
*
* @param context - context used to reduce the proposition
* @param sigmaTree - public key (in form of a sigma-tree)
* @param proof - signature for the key
* @param realSecretsToExtract - public keys of secrets with real proofs
* @param simulatedSecretsToExtract - public keys of secrets with simulated proofs
* @return - bag of OtherSecretProven and OtherCommitment hints
*/
def bagForMultisig(context: CTX,
sigmaTree: SigmaBoolean,
def bagForMultisig(sigmaTree: SigmaBoolean,
proof: Array[Byte],
realSecretsToExtract: Seq[SigmaBoolean],
simulatedSecretsToExtract: Seq[SigmaBoolean]): HintsBag = {
Expand Down
17 changes: 16 additions & 1 deletion interpreter/shared/src/main/scala/sigmastate/trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,27 @@ import scala.collection.mutable.ArrayBuffer
*/
trait SigmaConjecture extends SigmaBoolean {
def children: Seq[SigmaBoolean]

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

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

/** Represents leaf and its position in a SigmaBoolean tree. */
case class PositionedLeaf(position: NodePosition, leaf: SigmaLeaf)
object PositionedLeaf {
def at(path: Int*)(leaf: SigmaLeaf) = PositionedLeaf(NodePosition.CryptoTreePrefix ++ path, leaf)
}

/**
* AND conjunction for sigma propositions
Expand Down Expand Up @@ -130,6 +144,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(position: NodePosition, buf: mutable.ArrayBuffer[PositionedLeaf]): 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 @@ Coll[Byte](1)
Expand All @@ -24,4 +30,44 @@ 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))

def position(path: Int*): NodePosition = NodePosition.CryptoTreePrefix ++ path

val table = Table(("proposition", "leafs"),
(TrueProp, Seq()),
(FalseProp, Seq()),
(dlog1, Seq(PositionedLeaf(NodePosition.CryptoTreePrefix, dlog1))),
(dht1, Seq(PositionedLeaf(NodePosition.CryptoTreePrefix, dht1))),
(and, Seq(PositionedLeaf(position(0), dlog1), PositionedLeaf(position(1), dlog2))),
(or, Seq(PositionedLeaf(position(0), dlog1), PositionedLeaf(position(1), dlog2))),
(th, Seq(PositionedLeaf(position(0), dlog1), PositionedLeaf(position(1), dht1))),
(th2, Seq(
PositionedLeaf(position(1, 0), dlog1),
PositionedLeaf(position(1, 1), dlog2),
PositionedLeaf(position(2, 0), dlog1),
PositionedLeaf(position(2, 1), dlog2),
PositionedLeaf(position(3, 0), dlog1),
PositionedLeaf(position(3, 1), dht1),
PositionedLeaf(position(4), dlog1),
PositionedLeaf(position(5), dht1)))
)
forAll(table) { (prop: SigmaBoolean, leafs: Seq[PositionedLeaf]) =>
prop.leaves shouldBe leafs
}
th2.distinctLeaves shouldBe Set(dlog1, dlog2, dht1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ class ProverSpecification extends TestingCommons {
def checkUnrealRoot(sb: SigmaBoolean)(implicit prover: ProverInterpreter) = {
assertExceptionThrown(
checkProof(sb),
{ case e: AssertionError => e.getMessage.contains("Tree root should be real but was")
case _ => false })
exceptionLike[IllegalArgumentException]("Tree root should be real but was")
)
}

property("proof/verify completeness") {
Expand Down
Loading
Loading