diff --git a/build.sbt b/build.sbt index 2a4c9da3a6..7fd9727c9a 100644 --- a/build.sbt +++ b/build.sbt @@ -17,12 +17,12 @@ lazy val commonSettings = Seq( scalaVersion := scala212, scalacOptions ++= { CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 13)) => Seq("-Ywarn-unused:_,imports", "-Ywarn-unused:imports") - case Some((2, 12)) => Seq("-Ywarn-unused:_,imports", "-Ywarn-unused:imports") + case Some((2, 13)) => Seq("-Ywarn-unused:_,imports", "-Ywarn-unused:imports", "-release", "8") + case Some((2, 12)) => Seq("-Ywarn-unused:_,imports", "-Ywarn-unused:imports", "-release", "8") case Some((2, 11)) => Seq() case _ => sys.error("Unsupported scala version") } - } ++ scalacReleaseOption, + }, javacOptions ++= javacReleaseOption, resolvers += Resolver.sonatypeRepo("public"), licenses := Seq("CC0" -> url("https://creativecommons.org/publicdomain/zero/1.0/legalcode")), @@ -56,14 +56,6 @@ lazy val commonSettings = Seq( ), ) -def scalacReleaseOption = { - if (System.getProperty("java.version").startsWith("1.")) - // java <9 "-release" is not supported - Seq() - else - Seq("-release", "8") // this is passed to javac as `javac -release 8` -} - def javacReleaseOption = { if (System.getProperty("java.version").startsWith("1.")) // java <9 "--release" is not supported diff --git a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala index 76c283c3e6..38ef31700c 100644 --- a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala +++ b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala @@ -2,7 +2,6 @@ package special.sigma import java.math.BigInteger -import org.bouncycastle.math.ec.ECPoint import special.collection._ import scalan._ import scorex.crypto.authds.{ADDigest, ADValue} @@ -200,7 +199,8 @@ trait BigInt { @scalan.Liftable @WithMethodCallRecognizers trait GroupElement { - def isInfinity: Boolean + /** Checks if the provided element is an identity element. */ + def isIdentity: Boolean /** Exponentiate this GroupElement to the given number. * @param k The power. diff --git a/sigma-api/src/main/scala/special/sigma/package.scala b/sigma-api/src/main/scala/special/sigma/package.scala index b02b187ea5..dcbb71ef64 100644 --- a/sigma-api/src/main/scala/special/sigma/package.scala +++ b/sigma-api/src/main/scala/special/sigma/package.scala @@ -2,7 +2,6 @@ package special import java.math.BigInteger -import org.bouncycastle.math.ec.ECPoint import scalan.RType import scalan.RType.GeneralType @@ -30,5 +29,4 @@ package object sigma { implicit val SigmaDslBuilderRType: RType[SigmaDslBuilder] = RType.fromClassTag(classTag[SigmaDslBuilder]) implicit val BigIntegerRType: RType[BigInteger] = GeneralType(classTag[BigInteger]) - implicit val ECPointRType: RType[ECPoint] = GeneralType(classTag[ECPoint]) } \ No newline at end of file diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoAddress.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoAddress.scala index 0a9fdfb49c..1a2b5b2878 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoAddress.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoAddress.scala @@ -123,7 +123,7 @@ object P2PKAddress { /** Constructs [[P2PKAddress]] instance using the public key of the given [[ProveDlog]]. */ def apply(pubkey: ProveDlog)(implicit encoder: ErgoAddressEncoder): P2PKAddress = { - val bs = GroupElementSerializer.toBytes(pubkey.h) + val bs = GroupElementSerializer.toBytes(pubkey.value) new P2PKAddress(pubkey, bs) } } diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala index d7f3875337..045274e75b 100644 --- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala @@ -2,7 +2,7 @@ package sigmastate import com.typesafe.scalalogging.LazyLogging import gf2t.GF2_192_Poly -import org.bouncycastle.util.BigIntegers +import sigmastate.crypto.BigIntegers import scorex.util.encode.Base16 import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} diff --git a/sigmastate/src/main/scala/sigmastate/basics/BcDlogGroup.scala b/sigmastate/src/main/scala/sigmastate/basics/BcDlogGroup.scala index 2fb7583a67..f4b9b4c8f3 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/BcDlogGroup.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/BcDlogGroup.scala @@ -1,28 +1,29 @@ package sigmastate.basics import java.math.BigInteger -import org.bouncycastle.asn1.x9.X9ECParameters -import org.bouncycastle.crypto.ec.CustomNamedCurves -import org.bouncycastle.math.ec.custom.sec.SecP256K1Point -import org.bouncycastle.math.ec.ECPoint -import org.bouncycastle.util.BigIntegers +import sigmastate.crypto.BigIntegers import debox.cfor +import sigmastate.crypto.{CryptoContext, CryptoFacade} -import scala.collection.{Seq, mutable} -import scala.util.Try +import scala.collection.mutable -abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) extends DlogGroup[ElemType] { - lazy val curve = x9params.getCurve +/** Base class for EC-based groups where DLOG problem is hard (with bouncycastle-like interface). + * @param ctx context which abstracts basic operations with curve and elements. + */ +abstract class BcDlogGroup(val ctx: CryptoContext) extends DlogGroup { + /** Characteristic of the finite field of the underlying curve. */ + lazy val p: BigInteger = ctx.fieldCharacteristic - //modulus of the field - lazy val p: BigInteger = curve.getField.getCharacteristic - - //order of the group - lazy val q = x9params.getN + /** Order of the group as defined in ASN.1 def for Elliptic-Curve ECParameters structure. + * See X9.62, for further details. + * For reference implementation see `org.bouncycastle.asn1.x9.X9ECParameters.getN`. + */ + lazy val q: BigInteger = ctx.order - //Now that we have p, we can calculate k which is the maximum length in bytes - // of a string to be converted to a Group Element of this group. + /** Now that we have p, we can calculate k which is the maximum length in bytes + * of a string to be converted to a Group Element of this group. + */ lazy val k = calcK(p) /** @@ -31,15 +32,15 @@ abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) ex * It is composed of two main elements. The group element for which the optimized computations * are built for, called the base and a vector of group elements that are the result of * exponentiations of order 1,2,4,8, - */ - private class GroupElementsExponentiations(base: ElemType) //group element for which the optimized computations are built for - /** + * * The constructor creates a map structure in memory. * Then calculates the exponentiations of order 1,2,4,8 for the given base and save them in the map. * - * @param base + * @param base group element for which the optimized computations are built for * @throws IllegalArgumentException - */ { // build new vector of exponentiations + * + */ + private class GroupElementsExponentiations(base: ElemType) { private val exponentiations = new mutable.ListBuffer[ElemType]() @@ -99,22 +100,11 @@ abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) ex private val exponentiationsCache = mutable.Map[ElemType, GroupElementsExponentiations]() - //Create the generator - //Assume that (x,y) are the coordinates of a point that is indeed a generator but check that (x,y) are the coordinates of a point. - override lazy val generator: ElemType = x9params.getG.asInstanceOf[ElemType] - - /** - * Checks if the given x and y represent a valid point on the given curve, - * i.e. if the point (x, y) is a solution of the curves equation. - * - * @param x coefficient of the point - * @param y coefficient of the point - * @return true if the given x and y represented a valid point on the given curve + /** Creates the generator. + * Assume that (x,y) are the coordinates of a point that is indeed a generator but + * check that (x,y) are the coordinates of a point. */ - def checkCurveMembership(x: BigInteger, y: BigInteger): Boolean = { - Try(curve.validatePoint(x, y)).isSuccess - } - + override lazy val generator: ElemType = ctx.generator /** * This function calculates k, the maximum length in bytes of a string to be converted to a Group Element of this group. @@ -132,16 +122,14 @@ abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) ex } /** - * * @return the order of this Dlog group */ - override lazy val order: BigInteger = x9params.getN + override lazy val order: BigInteger = ctx.order /** - * * @return the identity of this Dlog group */ - override lazy val identity: ElemType = curve.getInfinity.asInstanceOf[ElemType] + override lazy val identity: ElemType = ctx.infinity.asInstanceOf[ElemType] /** * Calculates the inverse of the given GroupElement. @@ -151,7 +139,7 @@ abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) ex * @throws IllegalArgumentException **/ override def inverseOf(groupElement: ElemType): ElemType = - groupElement.negate().asInstanceOf[ElemType] + CryptoFacade.negatePoint(groupElement) /** * Raises the base GroupElement to the exponent. The result is another GroupElement. @@ -163,16 +151,12 @@ abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) ex */ override def exponentiate(base: ElemType, exponent: BigInteger): ElemType = { //infinity remains the same after any exponentiate - if (base.isInfinity) return base + if (CryptoFacade.isInfinityPoint(base)) return base //If the exponent is negative, convert it to be the exponent modulus q. val exp = if (exponent.compareTo(BigInteger.ZERO) < 0) exponent.mod(order) else exponent - /* - * BC treats EC as additive group while we treat that as multiplicative group. - * Therefore, exponentiate point is multiply. - */ - base.multiply(exp).asInstanceOf[ElemType] + CryptoFacade.exponentiatePoint(base, exp) } @@ -186,7 +170,7 @@ abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) ex //However, if a specific Dlog Group has a more efficient implementation then is it advised to override this function in that concrete //Dlog group. For example we do so in CryptoPpDlogZpSafePrime. val one = BigInteger.ONE - val qMinusOne = x9params.getN.subtract(one) + val qMinusOne = ctx.order.subtract(one) // choose a random number x in Zq* val randNum = BigIntegers.createRandomInRange(one, qMinusOne, secureRandom) // compute g^x to get a new element @@ -213,22 +197,7 @@ abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) ex * @throws IllegalArgumentException */ override def multiplyGroupElements(groupElement1: ElemType, groupElement2: ElemType): ElemType = - groupElement1.add(groupElement2).asInstanceOf[ElemType] - - - /** - * Computes the product of several exponentiations with distinct bases - * and distinct exponents. - * Instead of computing each part separately, an optimization is used to - * compute it simultaneously. - * - * @param groupElements - * @param exponentiations - * @return the exponentiation result - */ - override def simultaneousMultipleExponentiations(groupElements: Array[ElemType], - exponentiations: Array[BigInteger]): ElemType = - computeLL(groupElements, exponentiations) + CryptoFacade.multiplyPoints(groupElement1, groupElement2) /** * Computes the product of several exponentiations of the same base @@ -273,113 +242,7 @@ abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) ex */ override lazy val maxLengthOfByteArrayForEncoding: Int = k - - /* - * Computes the simultaneousMultiplyExponentiate using a naive algorithm - */ - protected def computeNaive(groupElements: Array[ElemType], exponentiations: Array[BigInteger]): ElemType = - groupElements.zip(exponentiations) - .iterator - .map { case (base, exp) => exponentiate(base, exp) } - .foldLeft(identity) { case (r, elem) => multiplyGroupElements(elem, r) } - - - /* - * Compute the simultaneousMultiplyExponentiate by LL algorithm. - * The code is taken from the pseudo code of LL algorithm in http://dasan.sejong.ac.kr/~chlim/pub/multi_exp.ps. - */ - protected def computeLL(groupElements: Array[ElemType], exponentiations: Array[BigInteger]): ElemType = { - val n = groupElements.length - //get the biggest exponent - val bigExp = exponentiations.max - val t = bigExp.bitLength //num bits of the biggest exponent. - val w = getLLW(t) //window size, choose it according to the value of t - - //h = n/w - val h = if ((n % w) == 0) n / w else (n / w) + 1 - - //create pre computation table - val preComp = createLLPreCompTable(groupElements, w, h) - - //holds the computation result - var result: ElemType = computeLoop(exponentiations, w, h, preComp, identity, t - 1) - //computes the first loop of the algorithm. This loop returns in the next part of the algorithm with one single tiny change. - - //computes the third part of the algorithm - (t - 2).to(0, -1).foreach { j => - //Y = Y^2 - result = exponentiate(result, new BigInteger("2")) - //computes the inner loop - result = computeLoop(exponentiations, w, h, preComp, result, j) - } - result - } - - /* - * Computes the loop the repeats in the algorithm. - * for k=0 to h-1 - * e=0 - * for i=kw to kw+w-1 - * if the bitIndex bit in ci is set: - * calculate e += 2^(i-kw) - * result = result *preComp[k][e] - * - */ - private def computeLoop(exponentiations: Array[BigInteger], w: Int, h: Int, preComp: Seq[Seq[ElemType]], result: ElemType, bitIndex: Int) = { - var res = result - cfor(0)(_ < h, _ + 1) { k => - var e = 0 - cfor(k * w)(_ < (k * w + w), _ + 1) { i => - if (i < exponentiations.length) { //if the bit is set, change the e value - if (exponentiations(i).testBit(bitIndex)) { - val twoPow = Math.pow(2, i - k * w).toInt - e += twoPow - } - } - } - res = multiplyGroupElements(res, preComp(k)(e)) - } - res - } - - /* - * Creates the preComputation table. - */ - private def createLLPreCompTable(groupElements: Array[ElemType], w: Int, h: Int) = { - val twoPowW = Math.pow(2, w).toInt - //create the pre-computation table of size h*(2^(w)) - val preComp: Seq[mutable.Seq[ElemType]] = Seq.fill(h)(mutable.Seq.fill(twoPowW)(identity)) - - cfor(0)(_ < h, _ + 1) { k => - cfor(0)(_ < twoPowW, _ + 1) { e => - cfor(0)(_ < w, _ + 1) { i => - val baseIndex = k * w + i - if (baseIndex < groupElements.length) { - val base = groupElements(baseIndex) - //if bit i in e is set, change preComp[k][e] - if ((e & (1 << i)) != 0) { //bit i is set - preComp(k)(e) = multiplyGroupElements(preComp(k)(e), base) - } - } - } - } - } - preComp - } - - /* - * returns the w value according to the given t - */ - private def getLLW(t: Int): Int = { - if (t <= 10) 2 - else if (t <= 24) 3 - else if (t <= 60) 4 - else if (t <= 144) 5 - else if (t <= 342) 6 - else if (t <= 797) 7 - else if (t <= 1828) 8 - else 9 - } } -object SecP256K1 extends BcDlogGroup[SecP256K1Point](CustomNamedCurves.getByName("secp256k1")) \ No newline at end of file +/** Implementation of [[BcDlogGroup]] using SecP256K1 curve. */ +object SecP256K1Group extends BcDlogGroup(CryptoFacade.createCryptoContext()) \ No newline at end of file diff --git a/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala b/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala index 75db9c69d2..38cfe1d8cb 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala @@ -2,7 +2,7 @@ package sigmastate.basics import java.math.BigInteger -import org.bouncycastle.util.BigIntegers +import sigmastate.crypto.BigIntegers import sigmastate.Values._ import Value.PropositionCode import scorex.util.encode.Base16 @@ -27,8 +27,8 @@ object DLogProtocol { extends SigmaProofOfKnowledgeLeaf[DLogSigmaProtocol, DLogProverInput] { override def size: Int = 1 override val opCode: OpCode = OpCodes.ProveDlogCode - lazy val h: EcPointType = value - lazy val pkBytes: Array[Byte] = GroupElementSerializer.toBytes(h) + /** Serialized bytes of the elliptic curve point (using GroupElementSerializer). */ + lazy val pkBytes: Array[Byte] = GroupElementSerializer.toBytes(value) } object ProveDlog { @@ -102,6 +102,7 @@ object DLogProtocol { SecondDLogProverMessage(z) } + /** Simulation of sigma protocol. */ def simulate(publicInput: ProveDlog, challenge: Challenge): (FirstDLogProverMessage, SecondDLogProverMessage) = { val qMinusOne = dlogGroup.order.subtract(BigInteger.ONE) @@ -111,7 +112,7 @@ object DLogProtocol { //COMPUTE a = g^z*h^(-e) (where -e here means -e mod q) val e: BigInteger = new BigInteger(1, challenge) val minusE = dlogGroup.order.subtract(e) - val hToE = dlogGroup.exponentiate(publicInput.h, minusE) + val hToE = dlogGroup.exponentiate(publicInput.value, minusE) val gToZ = dlogGroup.exponentiate(dlogGroup.generator, z) val a = dlogGroup.multiplyGroupElements(gToZ, hToE) FirstDLogProverMessage(a) -> SecondDLogProverMessage(z) @@ -133,7 +134,7 @@ object DLogProtocol { challenge: Challenge, secondMessage: SecondDLogProverMessage): EcPointType = { val g = dlogGroup.generator - val h = proposition.h + val h = proposition.value dlogGroup.multiplyGroupElements( dlogGroup.exponentiate(g, secondMessage.z.underlying()), diff --git a/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala b/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala index 5316bc70c9..9122cc78cd 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala @@ -2,7 +2,7 @@ package sigmastate.basics import java.math.BigInteger -import org.bouncycastle.util.BigIntegers +import sigmastate.crypto.BigIntegers import sigmastate.Values.Value.PropositionCode import sigmastate._ import sigmastate.basics.VerifierMessage.Challenge diff --git a/sigmastate/src/main/scala/sigmastate/basics/DlogGroup.scala b/sigmastate/src/main/scala/sigmastate/basics/DlogGroup.scala index 8df0f87e00..f7df1c8e3c 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/DlogGroup.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/DlogGroup.scala @@ -2,7 +2,7 @@ package sigmastate.basics import java.math.BigInteger import java.security.SecureRandom -import org.bouncycastle.math.ec.ECPoint +import sigmastate.crypto.{CryptoFacade, Ecp} /** @@ -19,7 +19,9 @@ import org.bouncycastle.math.ec.ECPoint * * @tparam ElemType is concrete type */ -trait DlogGroup[ElemType <: ECPoint] { +trait DlogGroup { + /** The type of the elements of this Dlog group */ + type ElemType = Ecp val secureRandom = new SecureRandom() @@ -95,23 +97,12 @@ trait DlogGroup[ElemType <: ECPoint] { // if the given element is the identity, get a new random element while ( { - randGen.isInfinity + CryptoFacade.isInfinityPoint(randGen) }) randGen = createRandomElement() randGen } - /** - * Computes the product of several exponentiations with distinct bases - * and distinct exponents. - * Instead of computing each part separately, an optimization is used to - * compute it simultaneously. - * @param groupElements - * @param exponentiations - * @return the exponentiation result - */ - def simultaneousMultipleExponentiations(groupElements: Array[ElemType], exponentiations: Array[BigInteger]): ElemType - /** * Computes the product of several exponentiations of the same base * and distinct exponents. diff --git a/sigmastate/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala b/sigmastate/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala index 199cdd1bf8..3327d94ebf 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala @@ -1,15 +1,7 @@ package sigmastate.basics -import java.security.SecureRandom - -import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.interpreter.CryptoConstants -import sigmastate.{SigmaProofOfKnowledgeLeaf, UncheckedTree} import supertagged.TaggedType -import scala.concurrent.Future - - /* Abstracting Sigma protocols Functionality to get: @@ -59,93 +51,10 @@ trait SigmaProtocol[SP <: SigmaProtocol[SP]] { trait SigmaProtocolCommonInput[SP <: SigmaProtocol[SP]] { - val soundness: Int = CryptoConstants.soundnessBits } trait SigmaProtocolPrivateInput[SP <: SigmaProtocol[SP], CI <: SigmaProtocolCommonInput[SP]] { def publicImage: CI } -/** - * common interface for both Prover and Verifier - */ -trait Party[SP <: SigmaProtocol[SP], CI <: SigmaProtocolCommonInput[SP]] { - val publicInput: CI -} - -//implement it as a FSM-DSL waitfor - then - action - then - waitfor - etc -trait InteractiveParty - -trait Prover[SP <: SigmaProtocol[SP], -CI <: SigmaProtocolCommonInput[SP], -PI <: SigmaProtocolPrivateInput[SP, CI]] extends Party[SP, CI] { - val privateInputOpt: Option[PI] -} - - -trait InteractiveProver[SP <: SigmaProtocol[SP], CI <: SigmaProtocolCommonInput[SP], PI <: SigmaProtocolPrivateInput[SP, CI]] - extends Prover[SP, CI, PI] with InteractiveParty { - - def firstMessage: SP#A - def secondMessage(challenge: Challenge): SP#Z - - def simulate(challenge: Challenge): (SP#A, SP#Z) -} - -trait SimulatingProver[SP <: SigmaProtocol[SP], CI <: SigmaProtocolCommonInput[SP]] { - val challenge: Challenge -} - - -trait ZeroKnowledgeProofOfKnowledge[SP <: SigmaProtocol[SP]] - -trait NonInteractiveProver[SP <: SigmaProtocol[SP], - PI <: SigmaProtocolPrivateInput[SP, CI], - CI <: SigmaProofOfKnowledgeLeaf[SP, PI], - P <: UncheckedTree] - extends Prover[SP, CI, PI] { - - def prove(challenge: Array[Byte]): P -} - -trait Verifier[SP <: SigmaProtocol[SP], CI <: SigmaProtocolCommonInput[SP]] extends Party[SP, CI] { - type P <: Prover[SP, CI, _] - type ST <: SigmaProtocolTranscript[SP, CI] - - lazy val challenge = Challenge({ - val ch = new Array[Byte](publicInput.soundness / 8) - new SecureRandom().nextBytes(ch) //modifies ch - ch - }) - - val publicInput: CI - - def prover: P - - def transcript: Future[Option[ST]] -} - -/** - * Sigma Protocol transcript enough for verification - * - * @tparam SP - * @tparam CI - */ -trait SigmaProtocolTranscript[SP <: SigmaProtocol[SP], CI <: SigmaProtocolCommonInput[SP]] { - - /** Common input known to both prover and verifier. */ - val x: CI - - /** First prover message */ - val a: SP#A - - /** Challenge created by verifier and sent to prover */ - val e: Challenge - - /** Second prover message - response to the challenge */ - val z: SP#Z - - /** Returns true if the verifier has accepted the prover's reponse to the challenge. */ - def accepted: Boolean -} diff --git a/sigmastate/src/main/scala/sigmastate/crypto/BigIntegers.scala b/sigmastate/src/main/scala/sigmastate/crypto/BigIntegers.scala new file mode 100644 index 0000000000..0e55f12532 --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/crypto/BigIntegers.scala @@ -0,0 +1,104 @@ +package sigmastate.crypto + +import java.math.BigInteger +import java.security.SecureRandom + +/** Re-implementation in Scala of select set of utility methods from + * org.bouncycastle.util.BigIntegers. + */ +object BigIntegers { + + /** The value 0 as a BigInteger. */ + val ZERO: BigInteger = BigInteger.valueOf(0) + + private val MAX_ITERATIONS = 1000 + + /** Create the given number of random bits. + * @param bitLength the number of random bits to create. + * @param random a source of randomness. + * @return a byte array containing random bits. + */ + @throws[IllegalArgumentException] + def createRandom(bitLength: Int, random: SecureRandom): Array[Byte] = { + if (bitLength < 1) throw new IllegalArgumentException("bitLength must be at least 1") + val nBytes = (bitLength + 7) / 8 + val rv = new Array[Byte](nBytes) + random.nextBytes(rv) + + // strip off any excess bits in the MSB + val xBits = 8 * nBytes - bitLength + rv(0) = (rv(0) & 255 >>> xBits).toByte + rv + } + + /** + * Return a positive BigInteger in the range of 0 to 2**bitLength - 1. + * + * @param bitLength maximum bit length for the generated BigInteger. + * @param random a source of randomness. + * @return a positive BigInteger + */ + def createRandomBigInteger( + bitLength: Int, + random: SecureRandom): BigInteger = { + new BigInteger(1, createRandom(bitLength, random)) + } + + /** + * Return a random BigInteger not less than 'min' and not greater than 'max' + * + * @param min the least value that may be generated + * @param max the greatest value that may be generated + * @param random the source of randomness + * @return a random BigInteger value in the range [min,max] + */ + def createRandomInRange( + min: BigInteger, + max: BigInteger, + random: SecureRandom): BigInteger = { + val cmp = min.compareTo(max) + if (cmp >= 0) { + if (cmp > 0) throw new IllegalArgumentException("'min' may not be greater than 'max'") + return min + } + + if (min.bitLength > max.bitLength / 2) + return createRandomInRange(ZERO, max.subtract(min), random).add(min) + + for ( i <- 0 until MAX_ITERATIONS ) { + val x = createRandomBigInteger(max.bitLength, random) + if (x.compareTo(min) >= 0 && x.compareTo(max) <= 0) return x + } + // fall back to a faster (restricted) method + createRandomBigInteger(max.subtract(min).bitLength - 1, random).add(min) + } + + /** + * Return the passed in value as an unsigned byte array of the specified length, padded with + * leading zeros as necessary.. + * + * @param length the fixed length of the result + * @param value the value to be converted. + * @return a byte array padded to a fixed length with leading zeros. + */ + def asUnsignedByteArray(length: Int, value: BigInteger): Array[Byte] = { + val bytes = value.toByteArray + if (bytes.length == length) return bytes + val start = if (bytes(0) == 0) 1 else 0 + + val count = bytes.length - start + if (count > length) + throw new IllegalArgumentException("standard length exceeded for value") + + val tmp = new Array[Byte](length) + System.arraycopy(bytes, start, tmp, tmp.length - count, count) + tmp + } + + /** Converts a byte array to a BigInteger, treating the array as bits of the unsigned + * integer. + * @param buf the byte array to convert + * @return the resulting positive BigInteger + */ + def fromUnsignedByteArray(buf: Array[Byte]) = new BigInteger(1, buf) +} diff --git a/sigmastate/src/main/scala/sigmastate/crypto/CryptoContext.scala b/sigmastate/src/main/scala/sigmastate/crypto/CryptoContext.scala new file mode 100644 index 0000000000..b5aceec849 --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/crypto/CryptoContext.scala @@ -0,0 +1,30 @@ +package sigmastate.crypto + +import java.math.BigInteger + +/** A context for cryptographic operations. */ +abstract class CryptoContext { + /** The characteristics of the underlying finite field. */ + def fieldCharacteristic: BigInteger + + /** The order of the underlying group. */ + def order: BigInteger + + /** Validates a point. + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + * @return the point if it is valid + * @throws IllegalArgumentException if the coordinates do not represent a valid point + */ + def validatePoint(x: BigInteger, y: BigInteger): Ecp + + /** The point at infinity. */ + def infinity(): Ecp + + /** Decodes a point from its byte representation. */ + def decodePoint(encoded: Array[Byte]): Ecp + + /** The generator of the underlying group. */ + def generator: Ecp +} + diff --git a/sigmastate/src/main/scala/sigmastate/crypto/CryptoContextJvm.scala b/sigmastate/src/main/scala/sigmastate/crypto/CryptoContextJvm.scala new file mode 100644 index 0000000000..6a3420242f --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/crypto/CryptoContextJvm.scala @@ -0,0 +1,30 @@ +package sigmastate.crypto + +import org.bouncycastle.asn1.x9.X9ECParameters + +import java.math.BigInteger + +/** JVM implementation of context for cryptographic operations using Bouncycastle. */ +class CryptoContextJvm(x9params: X9ECParameters) extends CryptoContext { + private lazy val curve = x9params.getCurve + + override def fieldCharacteristic: BigInteger = curve.getField.getCharacteristic + + override def order: BigInteger = x9params.getN + + override def generator: Ecp = { + Platform.Ecp(x9params.getG) + } + + override def validatePoint(x: BigInteger, y: BigInteger): Ecp = { + Platform.Ecp(curve.validatePoint(x, y)) + } + + override def infinity(): Ecp = { + Platform.Ecp(curve.getInfinity) + } + + override def decodePoint(encoded: Array[Byte]): Ecp = { + Platform.Ecp(curve.decodePoint(encoded)) + } +} diff --git a/sigmastate/src/main/scala/sigmastate/crypto/CryptoFacade.scala b/sigmastate/src/main/scala/sigmastate/crypto/CryptoFacade.scala new file mode 100644 index 0000000000..54ec07940c --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/crypto/CryptoFacade.scala @@ -0,0 +1,86 @@ +package sigmastate.crypto + +import java.math.BigInteger + +/** A facade for cryptographic operations. The concrete implementation is delegated to the + * Platform object, which is resolved by the compiler to either JVM or JS implementation. + * Cross-platform code should use this facade instead of the Platform object directly. + */ +object CryptoFacade { + /** Create a new context for cryptographic operations. */ + def createCryptoContext(): CryptoContext = Platform.createContext() + + /** * Normalization ensures that any projective coordinate is 1, and therefore that the x, y + * coordinates reflect those of the equivalent point in an affine coordinate system. + * + * @return a new ECPoint instance representing the same point, but with normalized coordinates + */ + def normalizePoint(p: Ecp): Ecp = Platform.normalizePoint(p) + + /** Negate a point. */ + def negatePoint(p: Ecp): Ecp = Platform.negatePoint(p) + + /** Check if a point is infinity. */ + def isInfinityPoint(p: Ecp): Boolean = Platform.isInfinityPoint(p) + + /** Exponentiate a point. + * + * @param p point to exponentiate + * @param n exponent + * @return p to the power of n (`p^n`) + */ + def exponentiatePoint(p: Ecp, n: BigInteger): Ecp = Platform.exponentiatePoint(p, n) + + /** Multiply two points. + * + * @param p1 first point + * @param p2 second point + * @return group multiplication (p1 * p2) + */ + def multiplyPoints(p1: Ecp, p2: Ecp): Ecp = Platform.multiplyPoints(p1, p2) + + /** Return simplified string representation of the point (used only for debugging) */ + def showPoint(p: Ecp): String = Platform.showPoint(p) + + /** Returns the sign of the field element. */ + def signOf(p: ECFieldElem): Boolean = Platform.signOf(p) + + /** Returns byte representation of the given field element. */ + def encodeFieldElem(p: ECFieldElem): Array[Byte] = Platform.encodeFieldElem(p) + + /** Returns the x-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use normalize() to get a point where the coordinates have their + * affine values, or use getAffineXCoord() if you expect the point to already have been + * normalized. + * + * @return the x-coordinate of this point + */ + def getXCoord(p: Ecp): ECFieldElem = Platform.getXCoord(p) + + /** Returns the y-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use normalize() to get a point where the coordinates have their + * affine values, or use getAffineYCoord() if you expect the point to already have been + * normalized. + * + * @return the y-coordinate of this point + */ + def getYCoord(p: Ecp): ECFieldElem = Platform.getYCoord(p) + + /** Returns the affine x-coordinate after checking that this point is normalized. + * + * @return The affine x-coordinate of this point + * @throws IllegalStateException if the point is not normalized + */ + def getAffineXCoord(p: Ecp): ECFieldElem = Platform.getAffineXCoord(p) + + /** Returns the affine y-coordinate after checking that this point is normalized + * + * @return The affine y-coordinate of this point + * @throws IllegalStateException if the point is not normalized + */ + def getAffineYCoord(p: Ecp): ECFieldElem = Platform.getAffineYCoord(p) +} diff --git a/sigmastate/src/main/scala/sigmastate/crypto/Platform.scala b/sigmastate/src/main/scala/sigmastate/crypto/Platform.scala new file mode 100644 index 0000000000..5cb4a9f345 --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/crypto/Platform.scala @@ -0,0 +1,106 @@ +package sigmastate.crypto + +import org.bouncycastle.crypto.ec.CustomNamedCurves +import org.bouncycastle.math.ec.{ECFieldElement, ECPoint} + +import java.math.BigInteger + +/** JVM specific implementation of crypto methods*/ +object Platform { + /** Returns the x-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use normalize() to get a point where the coordinates have their + * affine values, or use getAffineXCoord() if you expect the point to already have been + * normalized. + * + * @return the x-coordinate of this point + */ + def getXCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getXCoord) + + /** Returns the y-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use normalize() to get a point where the coordinates have their + * affine values, or use getAffineYCoord() if you expect the point to already have been + * normalized. + * + * @return the y-coordinate of this point + */ + def getYCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getYCoord) + + /** Returns the affine x-coordinate after checking that this point is normalized. + * + * @return The affine x-coordinate of this point + * @throws IllegalStateException if the point is not normalized + */ + def getAffineXCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getAffineXCoord) + + /** Returns the affine y-coordinate after checking that this point is normalized + * + * @return The affine y-coordinate of this point + * @throws IllegalStateException if the point is not normalized + */ + def getAffineYCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getAffineYCoord) + + /** Returns byte representation of the given field element. */ + def encodeFieldElem(p: ECFieldElem): Array[Byte] = p.value.getEncoded + + /** Returns the value of bit 0 in BigInteger representation of this point. */ + def signOf(p: ECFieldElem): Boolean = p.value.testBitZero() + + /** * Normalization ensures that any projective coordinate is 1, and therefore that the x, y + * coordinates reflect those of the equivalent point in an affine coordinate system. + * + * @return a new ECPoint instance representing the same point, but with normalized coordinates + */ + def normalizePoint(p: Ecp): Ecp = Ecp(p.value.normalize()) + + /** Return simplified string representation of the point (used only for debugging) */ + def showPoint(p: Ecp): String = { + val rawX = p.value.getRawXCoord.toString.substring(0, 6) + val rawY = p.value.getRawYCoord.toString.substring(0, 6) + s"ECPoint($rawX,$rawY,...)" + } + + /** Multiply two points. + * @param p1 first point + * @param p2 second point + * @return group multiplication (p1 * p2) + */ + def multiplyPoints(p1: Ecp, p2: Ecp): Ecp = { + /* + * BC treats EC as additive group while we treat that as multiplicative group. + */ + Ecp(p1.value.add(p2.value)) + } + + /** Exponentiate a point. + * @param p point to exponentiate + * @param n exponent + * @return p to the power of n (p^n) + */ + def exponentiatePoint(p: Ecp, n: BigInteger): Ecp = { + /* + * BC treats EC as additive group while we treat that as multiplicative group. + * Therefore, exponentiate point is multiply. + */ + Ecp(p.value.multiply(n)) + } + + /** Check if a point is infinity. */ + def isInfinityPoint(p: Ecp): Boolean = p.value.isInfinity + + /** Negate a point. */ + def negatePoint(p: Ecp): Ecp = Ecp(p.value.negate()) + + /** Wrapper for point type. */ + case class Ecp(private[crypto] val value: ECPoint) + + /** Wrapper for field element type. */ + case class ECFieldElem(value: ECFieldElement) + + /** Create a new context for cryptographic operations. */ + def createContext(): CryptoContext = new CryptoContextJvm(CustomNamedCurves.getByName("secp256k1")) + +} diff --git a/sigmastate/src/main/scala/sigmastate/crypto/package.scala b/sigmastate/src/main/scala/sigmastate/crypto/package.scala new file mode 100644 index 0000000000..24d797be6f --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/crypto/package.scala @@ -0,0 +1,8 @@ +package sigmastate + +package object crypto { + /** Instance of Elliptic Curve point. */ + type Ecp = Platform.Ecp + /** Instance of Elliptic Curve field element. */ + type ECFieldElem = Platform.ECFieldElem +} diff --git a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala index c152cb1716..09912c7588 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala @@ -4,7 +4,6 @@ import com.google.common.primitives.{Ints, Longs} import java.math.BigInteger import java.util.Arrays -import org.bouncycastle.math.ec.ECPoint import org.ergoplatform.{ErgoBox, SigmaConstants} import org.ergoplatform.validation.ValidationRules import scalan.OverloadHack.Overloaded1 @@ -27,6 +26,7 @@ import scorex.crypto.hash.{Blake2b256, Digest32, Sha256} import sigmastate.Values.ErgoTree.EmptyConstants import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.basics.ProveDHTuple +import sigmastate.crypto.{CryptoFacade, Ecp} import sigmastate.lang.TransformingSigmaBuilder import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.serialization.{GroupElementSerializer, SigmaSerializer} @@ -96,7 +96,7 @@ case class CBigInt(override val wrappedValue: BigInteger) extends BigInt with Wr /** A default implementation of [[GroupElement]] interface. * @see [[GroupElement]] for detailed descriptions */ -case class CGroupElement(override val wrappedValue: EcPointType) extends GroupElement with WrapperOf[ECPoint] { +case class CGroupElement(override val wrappedValue: Ecp) extends GroupElement with WrapperOf[Ecp] { val dsl = CostingSigmaDslBuilder override def toString: String = s"GroupElement(${Extensions.showECPoint(wrappedValue)})" @@ -104,16 +104,16 @@ case class CGroupElement(override val wrappedValue: EcPointType) extends GroupEl override def getEncoded: Coll[Byte] = dsl.Colls.fromArray(GroupElementSerializer.toBytes(wrappedValue)) - override def isInfinity: Boolean = wrappedValue.isInfinity + override def isIdentity: Boolean = CryptoFacade.isInfinityPoint(wrappedValue) override def exp(k: BigInt): GroupElement = - dsl.GroupElement(wrappedValue.multiply(k.asInstanceOf[CBigInt].wrappedValue)) + dsl.GroupElement(CryptoFacade.exponentiatePoint(wrappedValue, k.asInstanceOf[CBigInt].wrappedValue)) override def multiply(that: GroupElement): GroupElement = - dsl.GroupElement(wrappedValue.add(that.asInstanceOf[CGroupElement].wrappedValue)) + dsl.GroupElement(CryptoFacade.multiplyPoints(wrappedValue, that.asInstanceOf[CGroupElement].wrappedValue)) override def negate: GroupElement = - dsl.GroupElement(wrappedValue.negate()) + dsl.GroupElement(CryptoFacade.negatePoint(wrappedValue)) } /** A default implementation of [[SigmaProp]] interface. @@ -131,6 +131,7 @@ case class CSigmaProp(sigmaTree: SigmaBoolean) extends SigmaProp with WrapperOf[ override def propBytes: Coll[Byte] = { // in order to have comparisons like `box.propositionBytes == pk.propBytes` we need to make sure // the same serialization method is used in both cases + // TODO v6.0: add `pk.propBytes(version)` val root = sigmaTree.toSigmaProp val ergoTree = new ErgoTree(ErgoTree.DefaultHeader, EmptyConstants, Right(root), 0, null, None) val bytes = DefaultSerializer.serializeErgoTree(ergoTree) @@ -509,7 +510,8 @@ class CostingSigmaDslBuilder extends SigmaDslBuilder { dsl => override def toBigInteger(n: BigInt): BigInteger = n.asInstanceOf[CBigInt].wrappedValue - def GroupElement(p: ECPoint): GroupElement = p match { + /** Wraps the given elliptic curve point into GroupElement type. */ + def GroupElement(p: Ecp): GroupElement = p match { case ept: EcPointType => CGroupElement(ept) case m => sys.error(s"Point of type ${m.getClass} is not supported") } @@ -523,9 +525,12 @@ class CostingSigmaDslBuilder extends SigmaDslBuilder { dsl => /** Extract `sigmastate.AvlTreeData` from DSL's `AvlTree` type. */ def toAvlTreeData(p: AvlTree): AvlTreeData = p.asInstanceOf[CAvlTree].treeData - /** Extract `org.bouncycastle.math.ec.ECPoint` from DSL's `GroupElement` type. */ - def toECPoint(ge: GroupElement): ECPoint = ge.asInstanceOf[CGroupElement].wrappedValue + /** Extract `sigmastate.crypto.Ecp` from DSL's `GroupElement` type. */ + def toECPoint(ge: GroupElement): Ecp = ge.asInstanceOf[CGroupElement].wrappedValue + /** Creates a new AvlTree instance with the given parameters. + * @see AvlTreeData for details + */ override def avlTree(operationFlags: Byte, digest: Coll[Byte], keyLength: Int, valueLengthOpt: Option[Int]): CAvlTree = { val treeData = AvlTreeData(ADDigest @@ digest.toArray, AvlTreeFlags(operationFlags), keyLength, valueLengthOpt) CAvlTree(treeData) diff --git a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala index 830d6817d8..d54c2e8f7a 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala @@ -1,6 +1,5 @@ package sigmastate.eval -import org.bouncycastle.math.ec.ECPoint import org.ergoplatform._ import scalan.RType import scalan.RType._ @@ -99,7 +98,6 @@ object Evaluation { case BigIntegerRType => SBigInt case BigIntRType => SBigInt - case ECPointRType => SGroupElement case GroupElementRType => SGroupElement case AvlTreeRType => SAvlTree @@ -147,7 +145,6 @@ object Evaluation { case _: BigInteger => BigIntegerRType case _: special.sigma.BigInt => BigIntRType - case _: ECPoint => ECPointRType case _: GroupElement => GroupElementRType case _: ErgoBox => ErgoBoxRType diff --git a/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala b/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala index bf982e3269..a5d93ba25c 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala @@ -1,7 +1,5 @@ package sigmastate.eval -import org.bouncycastle.math.ec.ECPoint - import java.math.BigInteger import scalan.RType import sigmastate.{SCollection, SCollectionType, SType} @@ -13,6 +11,7 @@ import sigmastate.SType.AnyOps import org.ergoplatform.ErgoBox import debox.{Buffer => DBuffer} import debox.cfor +import sigmastate.crypto.{CryptoFacade, Ecp} object Extensions { private val Colls = CostingSigmaDslBuilder.Colls @@ -89,14 +88,12 @@ object Extensions { } /** Shortened String representation of `source` GroupElement. */ - def showECPoint(p: ECPoint): String = { - if (p.isInfinity) { - "INF" + def showECPoint(p: Ecp): String = { + if (p.isIdentity) { + "IDENTITY" } else { - val rawX = p.getRawXCoord.toString.substring(0, 6) - val rawY = p.getRawYCoord.toString.substring(0, 6) - s"ECPoint($rawX,$rawY,...)" + CryptoFacade.showPoint(p) } } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala index 1da6c8c27e..1d4a3a2d28 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala @@ -2,17 +2,21 @@ package sigmastate.interpreter import java.math.BigInteger import java.security.SecureRandom +import sigmastate.basics.{BcDlogGroup, SecP256K1Group} +import sigmastate.crypto.Ecp -import org.bouncycastle.math.ec.custom.sec.SecP256K1Point -import sigmastate.basics.{BcDlogGroup, SecP256K1} - +/** Constants used in crypto operations implementation. */ object CryptoConstants { - type EcPointType = SecP256K1Point + /** Type of group elements used in the signature scheme. */ + type EcPointType = Ecp + /** Length of encoded group element in bytes. */ val EncodedGroupElementLength: Byte = 33 - val dlogGroup: BcDlogGroup[EcPointType] = SecP256K1 + /** Group used in the signature scheme. */ + val dlogGroup: BcDlogGroup = SecP256K1Group + /** Secure random generator used in the signature scheme. */ val secureRandom: SecureRandom = dlogGroup.secureRandom /** Size of the binary representation of any group element (2 ^ groupSizeBits == ) */ @@ -27,6 +31,7 @@ object CryptoConstants { /** Length of hash function used in the signature scheme. Blake2b hash function is used. */ val hashLengthBits = 256 + /** Length of hash in bytes. */ val hashLength: Int = hashLengthBits / 8 /** A size of challenge in Sigma protocols, in bits. @@ -37,6 +42,10 @@ object CryptoConstants { */ implicit val soundnessBits: Int = 192.ensuring(_ < groupSizeBits, "2^t < q condition is broken!") + /** Generates random bytes using secure random generator. + * @param howMany number of bytes to generate + * @return generated bytes in a new array + */ def secureRandomBytes(howMany: Int): Array[Byte] = { val bytes = new Array[Byte](howMany) secureRandom.nextBytes(bytes) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala index d004d42566..b8523cb430 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala @@ -1,5 +1,6 @@ package sigmastate.serialization +import sigmastate.crypto.CryptoFacade import sigmastate.interpreter.CryptoConstants import sigmastate.interpreter.CryptoConstants.EcPointType import sigmastate.util.safeNewArray @@ -21,12 +22,12 @@ object GroupElementSerializer extends SigmaSerializer[EcPointType, EcPointType] private lazy val identityPointEncoding = Array.fill(encodingSize)(0: Byte) override def serialize(point: EcPointType, w: SigmaByteWriter): Unit = { - val bytes = if (point.isInfinity) { + val bytes = if (CryptoFacade.isInfinityPoint(point)) { identityPointEncoding } else { - val normed = point.normalize() - val ySign = normed.getAffineYCoord.testBitZero() - val X = normed.getXCoord.getEncoded + val normed = CryptoFacade.normalizePoint(point) + val ySign = CryptoFacade.signOf(CryptoFacade.getAffineYCoord(normed)) + val X = CryptoFacade.encodeFieldElem(CryptoFacade.getXCoord(normed)) val PO = safeNewArray[Byte](X.length + 1) PO(0) = (if (ySign) 0x03 else 0x02).toByte System.arraycopy(X, 0, PO, 1, X.length) @@ -38,7 +39,7 @@ object GroupElementSerializer extends SigmaSerializer[EcPointType, EcPointType] override def parse(r: SigmaByteReader): EcPointType = { val encoded = r.getBytes(encodingSize) if (encoded(0) != 0) { - curve.curve.decodePoint(encoded).asInstanceOf[EcPointType] + curve.ctx.decodePoint(encoded) } else { curve.identity } diff --git a/sigmastate/src/test/scala/org/ergoplatform/JsonSerializationSpec.scala b/sigmastate/src/test/scala/org/ergoplatform/JsonSerializationSpec.scala index ee33061c68..8fbd80987f 100644 --- a/sigmastate/src/test/scala/org/ergoplatform/JsonSerializationSpec.scala +++ b/sigmastate/src/test/scala/org/ergoplatform/JsonSerializationSpec.scala @@ -123,7 +123,7 @@ class JsonSerializationSpec extends SigmaTestingCommons with SerializationSpecif val minerPkHex = "0326df75ea615c18acc6bb4b517ac82795872f388d5d180aac90eaa84de750b942" val minerPk = Base16.decode(minerPkHex).map { point => ProveDlog( - CryptoConstants.dlogGroup.curve.decodePoint(point).asInstanceOf[CryptoConstants.EcPointType] + CryptoConstants.dlogGroup.ctx.decodePoint(point).asInstanceOf[CryptoConstants.EcPointType] ) }.get val regs = Map( diff --git a/sigmastate/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala b/sigmastate/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala index b77d3c5b0a..b5b0b8b33b 100644 --- a/sigmastate/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala @@ -1,11 +1,11 @@ package sigmastate.crypto import java.math.BigInteger - import org.scalacheck.Gen -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.{SigmaPPrint, SigmaTestingCommons} import sigmastate.interpreter.CryptoConstants import sigmastate.interpreter.CryptoConstants.EcPointType +import sigmastate.utils.Helpers import scala.util.Random @@ -54,4 +54,52 @@ class GroupLawsSpecification extends SigmaTestingCommons { } } + private def printPoints(points: Seq[(String, Any)]) = { + points.foreach { case (name, p) => + println(s"val $name = ${SigmaPPrint.apply(p).plainText}") + } + } + +// uncommment to generate new test vectors +// +// property("generate initial points") { +// printPoints(Seq( +// "identity" -> group.identity, +// "order" -> group.order, +// "p1" -> group.createRandomElement(), +// "p2" -> group.createRandomElement(), +// "p3" -> group.createRandomElement() +// )) +// } + + val p1: Ecp = Helpers.decodeECPoint("0381c5275b1d50c39a0c36c4561c3a37bff1d87e37a9ad69eab029e426c0b1a8ac") + val p2 = Helpers.decodeECPoint("02198064ec24024bb8b300e20dd18e33cc1fccb0fea73940bd9a1d3d9d6c3ddd8f") + val p3 = Helpers.decodeECPoint("02e135f5f905fb843698d48959c6c792b2c6f9605b90be2280d53b4b69ef23e8a9") + +// uncommment to generate new test vectors + property("generate op results") { + printPoints(Seq( + "p1" -> p1, + "p2" -> p2, + "p3" -> p3, + "p1.add(p2)" -> CryptoFacade.multiplyPoints(p1, p2), + "p1.multiply(order)" -> CryptoFacade.exponentiatePoint(p1, group.order), + "p1.multiply(order + 1)" -> CryptoFacade.exponentiatePoint(p1, group.order.add(BigInteger.ONE)), + "p1.inverse" -> CryptoFacade.negatePoint(p1) + )) + } + + property("check test vectors") { + CryptoFacade.multiplyPoints(p1, p2) shouldBe + Helpers.decodeECPoint("03de5e9c2806c05cd45a57d18c469743f42a0d2c84370b6662eb39d8a2990abed8") + + CryptoFacade.exponentiatePoint(p1, group.order) shouldBe + Helpers.decodeECPoint("000000000000000000000000000000000000000000000000000000000000000000") + + CryptoFacade.exponentiatePoint(p1, group.order.add(BigInteger.ONE)) shouldBe + Helpers.decodeECPoint("0381c5275b1d50c39a0c36c4561c3a37bff1d87e37a9ad69eab029e426c0b1a8ac") + + CryptoFacade.negatePoint(p1) shouldBe + Helpers.decodeECPoint("0281c5275b1d50c39a0c36c4561c3a37bff1d87e37a9ad69eab029e426c0b1a8ac") + } } diff --git a/sigmastate/src/test/scala/sigmastate/eval/BasicOpsTests.scala b/sigmastate/src/test/scala/sigmastate/eval/BasicOpsTests.scala index 4d024fc33d..58da469c82 100644 --- a/sigmastate/src/test/scala/sigmastate/eval/BasicOpsTests.scala +++ b/sigmastate/src/test/scala/sigmastate/eval/BasicOpsTests.scala @@ -1,9 +1,9 @@ package sigmastate.eval import java.math.BigInteger -import org.bouncycastle.crypto.ec.CustomNamedCurves import org.scalatest.{FunSuite, Matchers} import sigmastate.TrivialProp +import sigmastate.basics.SecP256K1Group import special.sigma.{ContractsTestkit, SigmaDslBuilder, SigmaProp} import scala.language.implicitConversions @@ -33,7 +33,7 @@ class BasicOpsTests extends FunSuite with ContractsTestkit with Matchers { // TODO this is valid for BigIntModQ type (https://github.com/ScorexFoundation/sigmastate-interpreter/issues/554) ignore("ByteArrayToBigInt should always produce big int less than dlog group order") { - val groupOrder = CustomNamedCurves.getByName("secp256k1").getN + val groupOrder = SecP256K1Group.ctx.order SigmaDsl.byteArrayToBigInt( Colls.fromArray(groupOrder.subtract(BigInteger.ONE).toByteArray) diff --git a/sigmastate/src/test/scala/sigmastate/lang/LangTests.scala b/sigmastate/src/test/scala/sigmastate/lang/LangTests.scala index 945816aab2..e6889e9c32 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/LangTests.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/LangTests.scala @@ -5,7 +5,6 @@ import sigmastate.Values.{LongConstant, SValue, Value, SigmaBoolean, ConcreteCol import sigmastate._ import java.math.BigInteger -import org.bouncycastle.math.ec.ECPoint import org.scalatest.Matchers import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.SCollection.SByteArray @@ -39,10 +38,10 @@ trait LangTests extends Matchers with NegativeTesting { val ecp2 = dlog.multiplyGroupElements(ecp1, ecp1) val ecp3 = dlog.multiplyGroupElements(ecp2, ecp2) val ecp4 = dlog.multiplyGroupElements(ecp3, ecp3) - val g1 = CostingSigmaDslBuilder.GroupElement(ecp1.asInstanceOf[ECPoint]) - val g2 = CostingSigmaDslBuilder.GroupElement(ecp2.asInstanceOf[ECPoint]) - val g3 = CostingSigmaDslBuilder.GroupElement(ecp3.asInstanceOf[ECPoint]) - val g4 = CostingSigmaDslBuilder.GroupElement(ecp4.asInstanceOf[ECPoint]) + val g1 = CostingSigmaDslBuilder.GroupElement(ecp1) + val g2 = CostingSigmaDslBuilder.GroupElement(ecp2) + val g3 = CostingSigmaDslBuilder.GroupElement(ecp3) + val g4 = CostingSigmaDslBuilder.GroupElement(ecp4) protected val n1: BigInt = BigInt(10).underlying() protected val n2: BigInt = BigInt(20).underlying() diff --git a/sigmastate/src/test/scala/sigmastate/serialization/GroupElementSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/GroupElementSerializerSpecification.scala index dd17ce069f..5be6cc5ff9 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/GroupElementSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/GroupElementSerializerSpecification.scala @@ -1,5 +1,6 @@ package sigmastate.serialization +import sigmastate.crypto.CryptoFacade import sigmastate.interpreter.CryptoConstants import sigmastate.eval._ @@ -12,7 +13,7 @@ class GroupElementSerializerSpecification extends SerializationSpecification { val bytes = GroupElementSerializer.toBytes(identity) bytes.length shouldBe CryptoConstants.EncodedGroupElementLength bytes.forall(_ == 0) shouldBe true - GroupElementSerializer.parse(SigmaSerializer.startReader(bytes, 0)).isInfinity shouldBe true + GroupElementSerializer.parse(SigmaSerializer.startReader(bytes, 0)).isIdentity shouldBe true } property("point roundtrip") { @@ -20,8 +21,10 @@ class GroupElementSerializerSpecification extends SerializationSpecification { val bytes = GroupElementSerializer.toBytes(ge.value) bytes.length shouldBe CryptoConstants.EncodedGroupElementLength val restored = GroupElementSerializer.parse(SigmaSerializer.startReader(bytes, 0)) - restored.normalize().getAffineXCoord shouldBe ge.value.normalize().getAffineXCoord - restored.normalize().getAffineYCoord shouldBe ge.value.normalize().getAffineYCoord + CryptoFacade.getAffineXCoord(CryptoFacade.normalizePoint(restored)) shouldBe + CryptoFacade.getAffineXCoord(CryptoFacade.normalizePoint(ge.value)) + CryptoFacade.getAffineYCoord(CryptoFacade.normalizePoint(restored)) shouldBe + CryptoFacade.getAffineYCoord(CryptoFacade.normalizePoint(ge.value)) } } } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala index d5f76503b4..88b29bac4f 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala @@ -5,7 +5,7 @@ import scorex.crypto.hash.Blake2b256 import sigmastate.Values.SigmaBoolean import sigmastate._ import sigmastate.basics.DLogProtocol.FirstDLogProverMessage -import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, SecP256K1} +import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, SecP256K1Group} import sigmastate.helpers.{ErgoLikeTestProvingInterpreter, SigmaTestingCommons} import sigmastate.interpreter.{HintsBag, ProverInterpreter} import sigmastate.lang.exceptions.InterpreterException @@ -38,7 +38,7 @@ class ProverSpecification extends SigmaTestingCommons { val r = h.ownCommitments.head.secretRandomness // g^r == a - SecP256K1.exponentiate(SecP256K1.generator, r) shouldBe a.ecData + SecP256K1Group.exponentiate(SecP256K1Group.generator, r) shouldBe a.ecData val h2 = prover.generateCommitmentsFor(pk3, Seq(pk)) h2.hints.size shouldBe 2 diff --git a/sigmastate/src/test/scala/sigmastate/utxo/examples/DHTupleExampleSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/examples/DHTupleExampleSpecification.scala index 8cd017d6e9..657beb98ab 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/examples/DHTupleExampleSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/examples/DHTupleExampleSpecification.scala @@ -35,7 +35,7 @@ class DHTupleExampleSpecification extends SigmaTestingCommons val x:BigInteger = alice.dlogSecrets.head.w // x is Alice's private key - val g_x = alicePubKey.h // g_x is Alice's public key (g_x = g^x) + val g_x = alicePubKey.value // g_x is Alice's public key (g_x = g^x) val env = Map( ScriptNameProp -> "env", @@ -63,7 +63,7 @@ class DHTupleExampleSpecification extends SigmaTestingCommons val y:BigInteger = bob.dlogSecrets.head.w // y is Bob's private key val bobPubKey: ProveDlog = bob.dlogSecrets.head.publicImage - val g_y = GroupElementConstant(bobPubKey.h) // Bob's public key g^y + val g_y = GroupElementConstant(bobPubKey.value) // Bob's public key g^y val g_xy = GroupElementConstant(dlogGroup.exponentiate(g_x, y)) // g^xy diff --git a/sigmastate/src/test/scala/sigmastate/utxo/examples/MixExampleSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/examples/MixExampleSpecification.scala index b626751312..99806ca35b 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/examples/MixExampleSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/examples/MixExampleSpecification.scala @@ -30,7 +30,7 @@ class MixExampleSpecification extends SigmaTestingCommons val x: BigInteger = alice.dlogSecrets.head.w // x is Alice's private key - val gX = alicePubKey.h // g_x is Alice's public key (g_x = g^x) + val gX = alicePubKey.value // g_x is Alice's public key (g_x = g^x) // Alternative 1: // val g_x = alicePubKey.value // Alternative 2: @@ -118,7 +118,7 @@ class MixExampleSpecification extends SigmaTestingCommons val y: BigInteger = bob.dlogSecrets.head.w // y is Bob's private key - val gY = GroupElementConstant(bobPubKey.h) // g^y + val gY = GroupElementConstant(bobPubKey.value) // g^y val gY_alt = GroupElementConstant(dlogGroup.exponentiate(g, y)) gY shouldBe gY_alt