From 2290554c02f15d586574b58d6ecb1d2821b7f702 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 17 Mar 2021 00:54:47 +0300 Subject: [PATCH 01/52] test-prove-verify: extract method generateProof --- .../sigmastate/interpreter/Interpreter.scala | 3 ++- .../interpreter/ProverInterpreter.scala | 16 +++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 0f213abcbb..4ccf5e6680 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -364,7 +364,8 @@ trait Interpreter extends ScorexLogging { checkCommitments(sp, message) } } catch { - case e: Exception => log.warn("Improper signature: ", e); false + case e: Exception => + log.warn("Improper signature: ", e); false } } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala index da0f26dac6..6f95f986cc 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala @@ -4,9 +4,10 @@ import java.math.BigInteger import gf2t.{GF2_192, GF2_192_Poly} import org.bitbucket.inkytonik.kiama.attribution.AttributionCore -import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{everywherebu, everywheretd, rule} +import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{rule, everywheretd, everywherebu} import org.bitbucket.inkytonik.kiama.rewriting.Strategy import scalan.util.CollectionUtil._ +import sigmastate.TrivialProp.{TrueProp, FalseProp} import sigmastate.Values._ import sigmastate._ import sigmastate.basics.DLogProtocol._ @@ -116,7 +117,6 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor context: CTX, message: Array[Byte], hintsBag: HintsBag = HintsBag.empty): Try[CostedProverResult] = Try { - import TrivialProp._ val initCost = ergoTree.complexity + context.initCost val remainingLimit = context.costLimit - initCost @@ -127,8 +127,15 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor val ctxUpdInitCost = context.withInitCost(initCost).asInstanceOf[CTX] val (reducedProp, cost) = fullReduction(ergoTree, ctxUpdInitCost, env) + val proof = generateProof(reducedProp, message, hintsBag) - val proofTree = reducedProp match { + CostedProverResult(proof, ctxUpdInitCost.extension, cost) + } + + def generateProof(sb: SigmaBoolean, + message: Array[Byte], + hintsBag: HintsBag): Array[Byte] = { + val proofTree = sb match { case TrueProp => NoProof case FalseProp => error("Script reduced to false") case sigmaTree => @@ -137,9 +144,8 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor } // Prover Step 10: output the right information into the proof val proof = SigSerializer.toBytes(proofTree) - CostedProverResult(proof, ctxUpdInitCost.extension, cost) + proof } - /** * Prover Step 1: This step will mark as "real" every node for which the prover can produce a real proof. * This step may mark as "real" more nodes than necessary if the prover has more than the minimal From 654f965ef276c55a2116b7a7db21fb3335b1b272 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 17 Mar 2021 00:55:56 +0300 Subject: [PATCH 02/52] test-prove-verify: added property("proof/verify completeness") --- .../sigmastate/utxo/ProverSpecification.scala | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala index 695e579e98..b22fcbf1f3 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala @@ -1,10 +1,14 @@ package sigmastate.utxo -import sigmastate.{CAND, CAndUnproven, COR, COrUnproven, CTHRESHOLD, CThresholdUnproven, NodePosition, UnprovenSchnorr} +import org.ergoplatform.ErgoLikeInterpreter +import scorex.crypto.hash.Blake2b256 +import sigmastate._ import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.FirstDLogProverMessage -import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, SecP256K1} +import sigmastate.basics.{SecP256K1, FirstDiffieHellmanTupleProverMessage} import sigmastate.helpers.{ErgoLikeTestProvingInterpreter, SigmaTestingCommons} +import sigmastate.interpreter.{ProverInterpreter, HintsBag} +import sigmastate.lang.exceptions.InterpreterException class ProverSpecification extends SigmaTestingCommons { @@ -104,4 +108,84 @@ class ProverSpecification extends SigmaTestingCommons { c1.children(1).asInstanceOf[UnprovenSchnorr].position shouldBe NodePosition(Seq(0, 0, 1)) } + val message = Blake2b256("some message based on tx content") + + def checkProof(sb: SigmaBoolean)(implicit prover: ProverInterpreter) = { + val verifier = new ErgoLikeInterpreter() + val proof = prover.generateProof(sb, message, HintsBag.empty) + val ok = verifier.verifySignature(sb, message, proof) + ok shouldBe true + } + + 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 }) + } + + property("proof/verify completeness") { + implicit val prover = new ErgoLikeTestProvingInterpreter + val pk0 = prover.dlogSecrets(0).publicImage + val pk1 = prover.dlogSecrets(1).publicImage + val dht0 = prover.dhSecrets(0).publicImage + + val otherProver = new ErgoLikeTestProvingInterpreter + val pkUnknown0 = otherProver.dlogSecrets(0).publicImage + val pkUnknown1 = otherProver.dlogSecrets(1).publicImage + val pkUnknown2 = otherProver.dlogSecrets(2).publicImage + + assertExceptionThrown( + checkProof(TrivialProp.FalseProp), + { case e: InterpreterException => + e.getMessage.contains("Script reduced to false") + case _ => false }) + + checkProof(pk0) + checkProof(dht0) + + checkProof(CAND(Array(pk0))) + checkUnrealRoot(CAND(Array(pkUnknown0))) + + checkProof(CAND(Array(pk0, pk1))) + checkUnrealRoot(CAND(Array(pk0, pkUnknown0))) + checkUnrealRoot(CAND(Array(pkUnknown0, pk0))) + checkUnrealRoot(CAND(Array(pkUnknown0, pkUnknown1))) + + checkProof(COR(Array(pk0))) + checkUnrealRoot(COR(Array(pkUnknown0))) + + checkProof(COR(Array(pk0, pk1))) + checkProof(COR(Array(pk0, pkUnknown0))) + checkProof(COR(Array(pkUnknown0, pk0))) + checkUnrealRoot(COR(Array(pkUnknown0, pkUnknown1))) + + checkProof(CTHRESHOLD(1, Array(pk0))) + checkUnrealRoot(CTHRESHOLD(1, Array(pkUnknown0))) + + checkProof(CTHRESHOLD(1, Array(pk0, pk1))) + checkProof(CTHRESHOLD(1, Array(pk0, pkUnknown1))) + checkProof(CTHRESHOLD(1, Array(pkUnknown1, pk0))) + checkUnrealRoot(CTHRESHOLD(1, Array(pkUnknown0, pkUnknown1))) + + checkProof(CTHRESHOLD(1, Array(pk0, pk1, dht0))) + checkProof(CTHRESHOLD(1, Array(pk0, pkUnknown0, dht0))) + checkProof(CTHRESHOLD(1, Array(pkUnknown0, pkUnknown1, dht0))) + checkProof(CTHRESHOLD(1, Array(pkUnknown0, dht0, pkUnknown1))) + checkProof(CTHRESHOLD(1, Array(dht0, pkUnknown0, pkUnknown1))) + checkUnrealRoot(CTHRESHOLD(1, Array(pkUnknown0, pkUnknown1, pkUnknown2))) + + checkProof(CTHRESHOLD(2, Array(pk0, pk1, dht0))) + checkProof(CTHRESHOLD(2, Array(pkUnknown0, pk0, dht0))) + checkProof(CTHRESHOLD(2, Array(pk0, pkUnknown0, dht0))) + checkProof(CTHRESHOLD(2, Array(pk0, dht0, pkUnknown0))) + checkUnrealRoot(CTHRESHOLD(2, Array(pk0, pkUnknown0, pkUnknown1))) + checkUnrealRoot(CTHRESHOLD(2, Array(pkUnknown0, pk0, pkUnknown1))) + checkUnrealRoot(CTHRESHOLD(2, Array(pkUnknown0, pkUnknown1, pk0))) + + checkProof(CTHRESHOLD(3, Array(pk0, pk1, dht0))) + checkUnrealRoot(CTHRESHOLD(3, Array(pkUnknown0, pk0, dht0))) + checkUnrealRoot(CTHRESHOLD(3, Array(pk0, pkUnknown0, dht0))) + checkUnrealRoot(CTHRESHOLD(3, Array(pk0, dht0, pkUnknown0))) + } } From 7d0e282a98096b1413f1a10a7a93abde4139c2d5 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 17 Mar 2021 12:22:11 +0300 Subject: [PATCH 03/52] test-prove-verify: support BigInt in SigmaPPrint --- sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala index 582c31f9ae..97dead976e 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala @@ -103,6 +103,9 @@ object SigmaPPrint extends PPrinter { case v: BigInteger => Tree.Apply("new BigInteger", treeifyMany(v.toString(16), 16)) + case v: BigInt => + Tree.Apply("BigInt", treeifyMany(v.toString(16), 16)) + case wa: mutable.WrappedArray[Byte @unchecked] if wa.elemTag == ClassTag.Byte => treeifyByteArray(wa.array) From 02a551a5112b22c9e12735ab78e9a7beeb6d4f04 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 17 Mar 2021 12:23:54 +0300 Subject: [PATCH 04/52] test-prove-verify: added property("SigSerializer test vectors") --- .../SigSerializerSpecification.scala | 337 +++++++++++++++++- 1 file changed, 325 insertions(+), 12 deletions(-) diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index fa9ebab9e2..cd814bd589 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -1,15 +1,20 @@ package sigmastate.serialization +import java.math.BigInteger import java.util +import org.ergoplatform.settings.ErgoAlgos import org.scalacheck.{Arbitrary, Gen} import org.scalatest.Assertion import sigmastate.Values.SigmaBoolean import sigmastate._ -import sigmastate.basics.DLogProtocol.ProveDlog -import sigmastate.basics.ProveDHTuple -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons, ErgoLikeTransactionTesting} +import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} +import sigmastate.basics.VerifierMessage.Challenge +import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple} +import sigmastate.helpers.{ErgoLikeTransactionTesting, ErgoLikeContextTesting, SigmaTestingCommons, ContextEnrichingTestProvingInterpreter} +import sigmastate.interpreter.Interpreter import sigmastate.serialization.generators.ObjectGenerators +import sigmastate.utils.Helpers import scala.util.Random @@ -21,20 +26,20 @@ class SigSerializerSpecification extends SigmaTestingCommons private lazy val prover = new ContextEnrichingTestProvingInterpreter() private lazy val interpreterProveDlogGen: Gen[ProveDlog] = - Gen.oneOf(prover.dlogSecrets.map(secret => ProveDlog(secret.publicImage.h))) + Gen.oneOf(prover.dlogSecrets.map(_.publicImage)) private lazy val interpreterProveDHTGen = - Gen.oneOf( - prover.dhSecrets - .map(_.commonInput) - .map(ci => ProveDHTuple(ci.g, ci.h, ci.u, ci.v))) + Gen.oneOf(prover.dhSecrets.map(_.publicImage)) private def exprTreeNodeGen: Gen[SigmaBoolean] = for { left <- exprTreeGen right <- exprTreeGen + third <- exprTreeGen node <- Gen.oneOf( - COR(Seq(left, right)), - CAND(Seq(left, right)) + COR(Array(left, right)), + CAND(Array(left, right)) + // TODO v5.0: uncomment to test property("SigSerializer round trip") + // CTHRESHOLD(2, Array(left, right, third)) ) } yield node @@ -82,11 +87,18 @@ class SigSerializerSpecification extends SigmaTestingCommons try { // get sigma conjectures out of transformers - val prop = prover.reduceToCrypto(ctx, expr).get._1 + val tree = mkTestErgoTree(expr) + val prop = prover.fullReduction(tree, ctx, Interpreter.emptyEnv)._1 - val proof = prover.prove(mkTestErgoTree(expr), ctx, challenge).get.proof + val proof = prover.prove(tree, ctx, challenge).get.proof val proofTree = SigSerializer.parseAndComputeChallenges(prop, proof) roundTrip(proofTree, prop) +// uncomment to print more useful test cases +// val testCase = ProofTestCase(prop, proof, proofTree) +// println( +// s"""------------------------------- +// |${sigmastate.helpers.SigmaPPrint(testCase, width = 150, height = 150)} +// |""".stripMargin) } catch { case t: Throwable => t.printStackTrace() @@ -95,4 +107,305 @@ class SigSerializerSpecification extends SigmaTestingCommons } } + case class ProofTestCase( + prop: SigmaBoolean, + proof: Array[Byte], + uncheckedTree: UncheckedTree + ) + + property("SigSerializer test vectors") { + val cases = Seq( + ProofTestCase( + prop = ProveDlog( + Helpers.decodeECPoint("02e8e77123e300f8324e7b5c4cbe0f7ac616e0b78fc45f28f54fa6696231fc8ec3") + ), + proof = ErgoAlgos.decodeUnsafe( + "c6429b70f4926a3ba1454f1aec116075f9e9fbe8a8f72114b277b8462a8b9098f5d4c934ab2876eb1b5707f3119e209bdbbad831e7cc4a41" + ), + uncheckedTree = UncheckedSchnorr( + ProveDlog( + Helpers.decodeECPoint("02e8e77123e300f8324e7b5c4cbe0f7ac616e0b78fc45f28f54fa6696231fc8ec3") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("c6429b70f4926a3ba1454f1aec116075f9e9fbe8a8f72114"), + SecondDLogProverMessage( + BigInt("b277b8462a8b9098f5d4c934ab2876eb1b5707f3119e209bdbbad831e7cc4a41", 16) + ) + ) + ), + ProofTestCase( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("024ebfeb5a2b6ad997e40efb4888b3f091a611df8298cf7fb24315b4d112ad7c3c"), + Helpers.decodeECPoint("03d41afc8c5875a8d52439d088b66ed63a5d64f16e1efd7f17c6036a923c637e5c"), + Helpers.decodeECPoint("034132d4c7eb387f12ef40ba3ec03723bda0ee5707f7471185aafc316167e85137") + ), + ErgoAlgos.decodeUnsafe( + "9ec740b57353cb2f6035bb1a481b0066b2fdc0406a6fa67ebb2e6f44a38052b3f564fafcd477c4eb8cda1a8a553a4a5f38f1e1084d6a69f0" + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("024ebfeb5a2b6ad997e40efb4888b3f091a611df8298cf7fb24315b4d112ad7c3c"), + Helpers.decodeECPoint("03d41afc8c5875a8d52439d088b66ed63a5d64f16e1efd7f17c6036a923c637e5c"), + Helpers.decodeECPoint("034132d4c7eb387f12ef40ba3ec03723bda0ee5707f7471185aafc316167e85137") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("9ec740b57353cb2f6035bb1a481b0066b2fdc0406a6fa67e"), + SecondDiffieHellmanTupleProverMessage( + new BigInteger("bb2e6f44a38052b3f564fafcd477c4eb8cda1a8a553a4a5f38f1e1084d6a69f0", 16) + ) + ) + ), + ProofTestCase( + CAND( + List( + ProveDlog( + Helpers.decodeECPoint("03670a10fcf68531423e3aa8bdad2d755eb5363ac53068e80d44578861f80abef3") + ), + ProveDlog( + Helpers.decodeECPoint("0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb87") + ) + ) + ), + ErgoAlgos.decodeUnsafe( + "a00b476899e583aefc18b237a7a70e73baace72aa533271a561d3432c347dcaec8975fdefb36389abe21656aadcfda0a0259681ce17bc47c9539ae1e7068292bb9646a9ffe4e11653495bd67588cfd6454d82cc455036e5b" + ), + CAndUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a"), + List( + UncheckedSchnorr( + ProveDlog( + Helpers.decodeECPoint( + "03670a10fcf68531423e3aa8bdad2d755eb5363ac53068e80d44578861f80abef3" + ) + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a"), + SecondDLogProverMessage( + BigInt("561d3432c347dcaec8975fdefb36389abe21656aadcfda0a0259681ce17bc47c", 16) + ) + ), + UncheckedSchnorr( + ProveDlog( + Helpers.decodeECPoint( + "0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb87" + ) + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("a00b476899e583aefc18b237a7a70e73baace72aa533271a"), + SecondDLogProverMessage( + BigInt("9539ae1e7068292bb9646a9ffe4e11653495bd67588cfd6454d82cc455036e5b", 16) + ) + ) + ) + ) + ), + ProofTestCase( + COR( + List( + ProveDlog( + Helpers.decodeECPoint("0344789e3a797e713103f2a8edd673fac35e56d414c584e575aaa750f3e8728b5b") + ), + ProveDlog( + Helpers.decodeECPoint("0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb87") + ) + ) + ), + ErgoAlgos.decodeUnsafe( + "c617e65a2ca62ac97bc33a33b76cb669622129ba0e094ad96287d97c2c6d6c8e48790d7c44961f7d958d59222ab4d7c814808a466a3e66e6f98e02d421757baa2842288b8d02787b5111db2e8924623790175e5bf27a2e4513e8eb196c22c8cf26a9d7b51cd7e386508db9c12b070d84" + ), + COrUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("c617e65a2ca62ac97bc33a33b76cb669622129ba0e094ad9"), + List( + UncheckedSchnorr( + ProveDlog( + Helpers.decodeECPoint( + "0344789e3a797e713103f2a8edd673fac35e56d414c584e575aaa750f3e8728b5b" + ) + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("6287d97c2c6d6c8e48790d7c44961f7d958d59222ab4d7c8"), + SecondDLogProverMessage( + BigInt("14808a466a3e66e6f98e02d421757baa2842288b8d02787b5111db2e89246237", 16) + ) + ), + UncheckedSchnorr( + ProveDlog( + Helpers.decodeECPoint( + "0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb87" + ) + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("a4903f2600cb464733ba374ff3faa914f7ac709824bd9d11"), + SecondDLogProverMessage( + BigInt("90175e5bf27a2e4513e8eb196c22c8cf26a9d7b51cd7e386508db9c12b070d84", 16) + ) + ) + ) + ) + ), + ProofTestCase( + COR( + Array( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("03a5f4c3b8217557514df3df8537ca13f991b11538935b2ea407e8b24afcabe509"), + Helpers.decodeECPoint("029837d12c86c29c92e74229dfd3fcb10933b696685209b14baa74dbabacb2dee5"), + Helpers.decodeECPoint("03f17cefec3911966dc9952090325267a5cf7f9b0be76b02623021989d7f0007a2") + ), + COR( + Array( + ProveDlog(Helpers.decodeECPoint("03f997167c03aa234732e3a68126b371dffa1e409f62ca8fa18cea6acd1dbe54d5")), + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("03f5921dde02233135665d006838fcb783deca634ee333c5541cc05a9012e684ee"), + Helpers.decodeECPoint("039b65625db7aad6d86599355b7cac785e6b5ac85b8a32e0d6927b324704d0a261"), + Helpers.decodeECPoint("02fc58b939b105231da101540c87e56f5703460c179935aaee47137f3c367904f1") + ) + ) + ) + ) + ), + ErgoAlgos.decodeUnsafe( + "96addfddcc197bdbacf5c0142fb16c39384b3699fa47da7dffd3149193b042fda134c0e208fefcb791379959ac6fc731adf47e32000fc75e2923dba482c843c7f6b684cbf2ceec5bfdf5fe6d13cabe5d15f8295ca4e8094fba3c4716bfdfc3c462417a79a61fcc487d6997a42739d533eebffa3b420a6e2e44616a1341e5baa1165c6c22e91a81addd97c3bd2fe40ecdbbda6f43bf71240da8dac878c044c16d42a4b34c536bbb1b" + ), + COrUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("96addfddcc197bdbacf5c0142fb16c39384b3699fa47da7d"), + List( + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("03a5f4c3b8217557514df3df8537ca13f991b11538935b2ea407e8b24afcabe509"), + Helpers.decodeECPoint("029837d12c86c29c92e74229dfd3fcb10933b696685209b14baa74dbabacb2dee5"), + Helpers.decodeECPoint("03f17cefec3911966dc9952090325267a5cf7f9b0be76b02623021989d7f0007a2") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("ffd3149193b042fda134c0e208fefcb791379959ac6fc731"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("adf47e32000fc75e2923dba482c843c7f6b684cbf2ceec5bfdf5fe6d13cabe5d", 16)) + ), + COrUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("697ecb4c5fa939260dc100f6274f908ea97cafc056281d4c"), + List( + UncheckedSchnorr( + ProveDlog(Helpers.decodeECPoint("03f997167c03aa234732e3a68126b371dffa1e409f62ca8fa18cea6acd1dbe54d5")), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("15f8295ca4e8094fba3c4716bfdfc3c462417a79a61fcc48"), + SecondDLogProverMessage(BigInt("7d6997a42739d533eebffa3b420a6e2e44616a1341e5baa1165c6c22e91a81ad", 16)) + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("03f5921dde02233135665d006838fcb783deca634ee333c5541cc05a9012e684ee"), + Helpers.decodeECPoint("039b65625db7aad6d86599355b7cac785e6b5ac85b8a32e0d6927b324704d0a261"), + Helpers.decodeECPoint("02fc58b939b105231da101540c87e56f5703460c179935aaee47137f3c367904f1") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("7c86e210fb413069b7fd47e09890534acb3dd5b9f037d104"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("dd97c3bd2fe40ecdbbda6f43bf71240da8dac878c044c16d42a4b34c536bbb1b", 16)) + ) + ) + ) + ) + ) + ), + ProofTestCase( + COR( + Array( + CAND( + Array( + ProveDlog(Helpers.decodeECPoint("0368c0d88d9eb2972bbfc23c961de6307f6a944352cbfe316f262401feabdaa87d")), + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02badd523c2f5c12f4a3d4d667efb6ce8e95c8ad007cc697c34e91884335b55249"), + Helpers.decodeECPoint("02c455e55dc7bc731c5a487778e84814080fb70cc59957bc2a40c45373fe1ce14c"), + Helpers.decodeECPoint("029d4ec275379f9212a53e15994aef203dcec43a177c0b1f40afcf592e5753ce67") + ) + ) + ), + COR( + Array( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa7"), + Helpers.decodeECPoint("03a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e2"), + Helpers.decodeECPoint("0315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a") + ), + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa7"), + Helpers.decodeECPoint("03a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e2"), + Helpers.decodeECPoint("0315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a") + ) + ) + ) + ) + ), + ErgoAlgos.decodeUnsafe( + "4fdc76711fd844de0831d8e90ebaf9c622117a062b2f8b63ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2e0a3e44384f23eee260931d88e1f5241a2600a7c98545ada675fd5e627e8e84f140fc95e28775cde52e71bb4d7b5ee2564553fac5b52202530fcbcdf205b7cca145202fb2a5bb181a890eb15536b08b747ea163f6b5d32a116fa9e1eb6b348fd82d3ebc11c125e5bc3f09c499aa0a8db14dc1780b4181f9bae5ed0f743f71b82b18784380814507d810cbef61ebc0b30e7f324083e2d3d08" + ), + COrUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("4fdc76711fd844de0831d8e90ebaf9c622117a062b2f8b63"), + List( + CAndUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2"), + List( + UncheckedSchnorr( + ProveDlog(Helpers.decodeECPoint("0368c0d88d9eb2972bbfc23c961de6307f6a944352cbfe316f262401feabdaa87d")), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2"), + SecondDLogProverMessage(BigInt("e0a3e44384f23eee260931d88e1f5241a2600a7c98545ada675fd5e627e8e84f", 16)) + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02badd523c2f5c12f4a3d4d667efb6ce8e95c8ad007cc697c34e91884335b55249"), + Helpers.decodeECPoint("02c455e55dc7bc731c5a487778e84814080fb70cc59957bc2a40c45373fe1ce14c"), + Helpers.decodeECPoint("029d4ec275379f9212a53e15994aef203dcec43a177c0b1f40afcf592e5753ce67") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("ff8b9c2a4eed345a11c697f6850cf3a38763d738539ad2d2"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("140fc95e28775cde52e71bb4d7b5ee2564553fac5b52202530fcbcdf205b7cca", 16)) + ) + ) + ), + COrUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("b057ea5b5135708419f74f1f8bb60a65a572ad3e78b559b1"), + List( + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa7"), + Helpers.decodeECPoint("03a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e2"), + Helpers.decodeECPoint("0315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("145202fb2a5bb181a890eb15536b08b747ea163f6b5d32a1"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("16fa9e1eb6b348fd82d3ebc11c125e5bc3f09c499aa0a8db14dc1780b4181f9b", 16)) + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("02a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa7"), + Helpers.decodeECPoint("03a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e2"), + Helpers.decodeECPoint("0315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("a405e8a07b6ec105b167a40ad8dd02d2e298bb0113e86b10"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("ae5ed0f743f71b82b18784380814507d810cbef61ebc0b30e7f324083e2d3d08", 16)) + ) + ) + ) + ) + ) + ) + ) + + cases.foreach { c => + val sigBytes = SigSerializer.toBytes(c.uncheckedTree) + sigBytes shouldBe c.proof + val uncheckedTree = SigSerializer.parseAndComputeChallenges(c.prop, c.proof) + uncheckedTree shouldBe c.uncheckedTree + } + } } From e262036b2b000c256c2f7faef1273469eac8c24f Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 17 Mar 2021 14:13:07 +0300 Subject: [PATCH 05/52] test-prove-verify: test vectors for FiatShamirTree.toBytes --- .../main/scala/sigmastate/UnprovenTree.scala | 2 + .../SigSerializerSpecification.scala | 44 +++++++++++++++---- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala index 0cf1887ca1..2415523ef9 100644 --- a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala +++ b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala @@ -213,6 +213,8 @@ object FiatShamirTree { val internalNodePrefix = 0: Byte val leafPrefix = 1: Byte + // TODO optimize: this serialization is not the hot path during verification + /** HOTSPOT: */ def toBytes(tree: ProofTree): Array[Byte] = { def traverseNode(node: ProofTree): Array[Byte] = node match { diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index cd814bd589..d7f437c032 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -107,10 +107,16 @@ class SigSerializerSpecification extends SigmaTestingCommons } } + /** This is used to represent related test vectors to test: + * - SigSerializer.toBytes + * - SigSerializer.parseAndComputeChallenges + * - FiatShamirTree.toBytes + */ case class ProofTestCase( prop: SigmaBoolean, proof: Array[Byte], - uncheckedTree: UncheckedTree + uncheckedTree: UncheckedTree, + fiatShamirHex: String = "" ) property("SigSerializer test vectors") { @@ -131,7 +137,8 @@ class SigSerializerSpecification extends SigmaTestingCommons SecondDLogProverMessage( BigInt("b277b8462a8b9098f5d4c934ab2876eb1b5707f3119e209bdbbad831e7cc4a41", 16) ) - ) + ), + fiatShamirHex = "010027100108cd02e8e77123e300f8324e7b5c4cbe0f7ac616e0b78fc45f28f54fa6696231fc8ec373000021021d30cef8084f8659e9734099bf8e6faa89d81f908c3a62e7638da7b2a33822fc" ), ProofTestCase( ProveDHTuple( @@ -155,7 +162,8 @@ class SigSerializerSpecification extends SigmaTestingCommons SecondDiffieHellmanTupleProverMessage( new BigInteger("bb2e6f44a38052b3f564fafcd477c4eb8cda1a8a553a4a5f38f1e1084d6a69f0", 16) ) - ) + ), + "01008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798024ebfeb5a2b6ad997e40efb4888b3f091a611df8298cf7fb24315b4d112ad7c3c03d41afc8c5875a8d52439d088b66ed63a5d64f16e1efd7f17c6036a923c637e5c034132d4c7eb387f12ef40ba3ec03723bda0ee5707f7471185aafc316167e851377300004203724c887a207569c4648a81bc2d66bddeefe14b3ed259ee320773ec0c7df714490338360ba3350c16c42839878fc13b641c0a67372c4217e2ceafe1d40405f03708" ), ProofTestCase( CAND( @@ -199,7 +207,8 @@ class SigSerializerSpecification extends SigmaTestingCommons ) ) ) - ) + ), + "00000002010027100108cd03670a10fcf68531423e3aa8bdad2d755eb5363ac53068e80d44578861f80abef373000021024fc32f5fc7dad49005dc86b8ad95975d62ee4336cdddd4de8868414211370320010027100108cd0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb877300002103f084eb45540454909d3e793d876262fa184f90412c33a046a0b1c4d7c8933f67" ), ProofTestCase( COR( @@ -243,7 +252,8 @@ class SigSerializerSpecification extends SigmaTestingCommons ) ) ) - ) + ), + "00010002010027100108cd0344789e3a797e713103f2a8edd673fac35e56d414c584e575aaa750f3e8728b5b730000210207700723f7cf3a94782a56d9366f3916c548616edf9bcbaecb892f8a52b28836010027100108cd0249829d9ca70fa3974c1354d7d112390e07b826032c5a7c3bc39e56b3f480bb8773000021039072557976001866ac8a1a6a8bd921e5b18171b195e6dabffff667f9a88ab9a2" ), ProofTestCase( COR( @@ -307,7 +317,8 @@ class SigSerializerSpecification extends SigmaTestingCommons ) ) ) - ) + ), + "0001000201008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179803a5f4c3b8217557514df3df8537ca13f991b11538935b2ea407e8b24afcabe509029837d12c86c29c92e74229dfd3fcb10933b696685209b14baa74dbabacb2dee503f17cefec3911966dc9952090325267a5cf7f9b0be76b02623021989d7f0007a273000042035e192266f309bebe0a3e50f96f8161ad4dbd6136771619b7560b8ea4271ff2be036ebbb7e8e91546c16a41d79971d81513813416c06ef4ee92825065c484f43c6f00010002010027100108cd03f997167c03aa234732e3a68126b371dffa1e409f62ca8fa18cea6acd1dbe54d573000021031a93fccb6536b097682276ec047138f95ad05369b8bd24d73ecdc46571b5a7e601008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179803f5921dde02233135665d006838fcb783deca634ee333c5541cc05a9012e684ee039b65625db7aad6d86599355b7cac785e6b5ac85b8a32e0d6927b324704d0a26102fc58b939b105231da101540c87e56f5703460c179935aaee47137f3c367904f1730000420359bc0180bf8e1df00dd5021dd43cb52acd5612fa5baa3d517222a514ed7e4d1903de533bce02969892436e113cad7270de0b3d051035629abd645d3470928f3700" ), ProofTestCase( COR( @@ -397,15 +408,32 @@ class SigSerializerSpecification extends SigmaTestingCommons ) ) ) - ) + ), + "0001000200000002010027100108cd0368c0d88d9eb2972bbfc23c961de6307f6a944352cbfe316f262401feabdaa87d7300002102628c0e33748b2e120536945d77e69b3fb5fb3878c5697c20bf70b8459f476e4b01008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802badd523c2f5c12f4a3d4d667efb6ce8e95c8ad007cc697c34e91884335b5524902c455e55dc7bc731c5a487778e84814080fb70cc59957bc2a40c45373fe1ce14c029d4ec275379f9212a53e15994aef203dcec43a177c0b1f40afcf592e5753ce6773000042030064fb366dce2d424a14308d2c3d562f3558b042250cc28eab47a594efdec9c603787ca34604619468b3c677e2887a0de382a093264729a15ace38f1e3be20fd0f0001000201008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa703a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e20315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a7300004202f718c7ac18b4d942056b7237809f843811a04314044d6dd517022f6d15198c4e0329ecb543ea406fda96d3088196f4490cacdea49286c954ea2924523a57847a7d01008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa703a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e20315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a73000042021cc93df3de5b405ee306f5c7003b6abbfb7b4d0ab356c36d0c454a4c6b3403f603608a98d8aa0781ec96970353b4e22ae05454833528ae8f8468d0a8fc3f92675a" ) ) - cases.foreach { c => + cases.zipWithIndex.foreach { case (c, iCase) => val sigBytes = SigSerializer.toBytes(c.uncheckedTree) sigBytes shouldBe c.proof val uncheckedTree = SigSerializer.parseAndComputeChallenges(c.prop, c.proof) uncheckedTree shouldBe c.uncheckedTree + + // this step is performed in Interpreter.checkCommitments + val newRoot = prover.computeCommitments(c.uncheckedTree).get.asInstanceOf[UncheckedSigmaTree] + val fiatShamirBytes = FiatShamirTree.toBytes(newRoot) + val hex = ErgoAlgos.encode(fiatShamirBytes) + + if (c.fiatShamirHex.isEmpty) { + // output test vector + val vector = sigmastate.helpers.SigmaPPrint(hex, width = 150, height = 150) + println( + s"""case $iCase: ------------------------- + |hex: $vector + |""".stripMargin) + } + + hex shouldBe c.fiatShamirHex } } } From 358d194f839b7bc41f36873a91df2da1af7c984c Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 16 Mar 2021 17:15:54 +0300 Subject: [PATCH 06/52] verify-opt: refactor SigmaBoolean.estimateCost --- .../src/main/scala/sigmastate/Values.scala | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 63c6deda93..1b5c5ddfa9 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -585,36 +585,27 @@ object Values { case _: ProveDlog => CostTable.proveDlogEvalCost case _: ProveDHTuple => CostTable.proveDHTupleEvalCost case and: CAND => - val children = and.children.toArray - val nChildren = children.length - var sum = 0 - cfor(0)(_ < nChildren, _ + 1) { i => - val c = estimateCost(children(i)) - sum = Math.addExact(sum, c) - } - sum - case or: COR => - val children = or.children.toArray - val nChildren = children.length - var sum = 0 - cfor(0)(_ < nChildren, _ + 1) { i => - val c = estimateCost(children(i)) - sum = Math.addExact(sum, c) - } - sum + childrenCost(and.children) + case or: COR => + childrenCost(or.children) case th: CTHRESHOLD => - val children = th.children.toArray - val nChildren = children.length - var sum = 0 - cfor(0)(_ < nChildren, _ + 1) { i => - val c = estimateCost(children(i)) - sum = Math.addExact(sum, c) - } - sum + childrenCost(th.children) case _ => CostTable.MinimalCost } + /** Compute the total cost of the given children. */ + private def childrenCost(children: Seq[SigmaBoolean]): Int = { + val childrenArr = children.toArray + val nChildren = childrenArr.length + var sum = 0 + cfor(0)(_ < nChildren, _ + 1) { i => + val c = estimateCost(childrenArr(i)) + sum = Math.addExact(sum, c) + } + sum + } + /** HOTSPOT: don't beautify this code */ object serializer extends SigmaSerializer[SigmaBoolean, SigmaBoolean] { val dhtSerializer = ProveDHTupleSerializer(ProveDHTuple.apply) From 6aacbf5d45adb325f3a6c10331f6c64fe79aea67 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 17 Mar 2021 23:26:42 +0300 Subject: [PATCH 07/52] test-prove-verify: fix equality for GF2_192 and GF2_192_Poly --- sigmastate/src/main/java/gf2t/GF2_192.java | 19 ++++++++++++++++--- .../src/main/java/gf2t/GF2_192_Poly.java | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/sigmastate/src/main/java/gf2t/GF2_192.java b/sigmastate/src/main/java/gf2t/GF2_192.java index ac40b9b071..dc0c166bcf 100644 --- a/sigmastate/src/main/java/gf2t/GF2_192.java +++ b/sigmastate/src/main/java/gf2t/GF2_192.java @@ -28,16 +28,29 @@ */ package gf2t; +import java.util.Arrays; + public class GF2_192 { private final long [] word = new long[3]; /** * - * @param that the field element with which to compare + * @param obj the field element with which to compare * @return true if and only if this and that represent the same field element */ - public boolean equals(GF2_192 that) { - return this.word[0]==that.word[0] && this.word[1]==that.word[1] && this.word[2]==that.word[2]; + @Override + public boolean equals(Object obj) { + if (this == obj) return true; // equal references + if (obj instanceof GF2_192) { + GF2_192 that = (GF2_192)obj; + return this.word[0]==that.word[0] && this.word[1]==that.word[1] && this.word[2]==that.word[2]; + } + return false; // different types + } + + @Override + public int hashCode() { + return Arrays.hashCode(word); } // using irreducible polynomial x^192+x^7+x^2+x+1 diff --git a/sigmastate/src/main/java/gf2t/GF2_192_Poly.java b/sigmastate/src/main/java/gf2t/GF2_192_Poly.java index 2e1e2cafc1..d3ccd61fc3 100644 --- a/sigmastate/src/main/java/gf2t/GF2_192_Poly.java +++ b/sigmastate/src/main/java/gf2t/GF2_192_Poly.java @@ -29,6 +29,8 @@ package gf2t; +import java.util.Arrays; + public class GF2_192_Poly { private final GF2_192 [] c; // must be not null and of length at least 1 @@ -227,4 +229,19 @@ public byte[] toByteArray(Boolean coeff0) { public byte[] coeff0Bytes() { return c[0].toByteArray(); } + + @Override + public int hashCode() { + return 31 * Arrays.deepHashCode(c) + deg; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj instanceof GF2_192_Poly) { + GF2_192_Poly that = (GF2_192_Poly)obj; + return Arrays.deepEquals(c, that.c) && deg == that.deg; + } + return false; + } } From 564db6bcc64700b2f2de4b0ab1d47882b0d839d5 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 17 Mar 2021 23:29:11 +0300 Subject: [PATCH 08/52] test-prove-verify: fix equality for UncheckedTree family --- .../main/scala/sigmastate/UncheckedTree.scala | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala b/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala index be1df762fd..8b4f13220e 100644 --- a/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala +++ b/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala @@ -2,11 +2,11 @@ package sigmastate import java.util -import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, ProveDlog, SecondDLogProverMessage} +import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, SecondDLogProverMessage, ProveDlog} import sigmastate.basics.VerifierMessage.Challenge import sigmastate.Values.SigmaBoolean import gf2t.GF2_192_Poly -import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, ProveDHTuple, SecondDiffieHellmanTupleProverMessage} +import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple, FirstDiffieHellmanTupleProverMessage} sealed trait UncheckedTree extends ProofTree @@ -18,10 +18,14 @@ sealed trait UncheckedSigmaTree extends UncheckedTree { trait UncheckedConjecture extends UncheckedSigmaTree with ProofTreeConjecture { - override def equals(obj: Any): Boolean = obj match { + override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { case x: UncheckedConjecture => util.Arrays.equals(challenge, x.challenge) && children == x.children - } + case _ => false + }) + + override def hashCode(): Int = + 31 * util.Arrays.hashCode(challenge) + children.hashCode() } trait UncheckedLeaf[SP <: SigmaBoolean] extends UncheckedSigmaTree with ProofTreeLeaf { @@ -34,12 +38,19 @@ case class UncheckedSchnorr(override val proposition: ProveDlog, secondMessage: SecondDLogProverMessage) extends UncheckedLeaf[ProveDlog] { - override def equals(obj: Any): Boolean = obj match { + override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { case x: UncheckedSchnorr => util.Arrays.equals(challenge, x.challenge) && commitmentOpt == x.commitmentOpt && secondMessage == x.secondMessage case _ => false + }) + + override def hashCode(): Int = { + var h = util.Arrays.hashCode(challenge) + h = 31 * h + commitmentOpt.hashCode() + h = 31 * h + secondMessage.hashCode() + h } } @@ -50,12 +61,21 @@ case class UncheckedDiffieHellmanTuple(override val proposition: ProveDHTuple, secondMessage: SecondDiffieHellmanTupleProverMessage) extends UncheckedLeaf[ProveDHTuple] { - override def equals(obj: Any): Boolean = obj match { + override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { case x: UncheckedDiffieHellmanTuple => proposition == x.proposition && commitmentOpt == x.commitmentOpt && util.Arrays.equals(challenge, x.challenge) && secondMessage == x.secondMessage + case _ => false + }) + + override def hashCode(): Int = { + var h = proposition.hashCode() + h = 31 * h + commitmentOpt.hashCode() + h = 31 * h + util.Arrays.hashCode(challenge) + h = 31 * h + secondMessage.hashCode() + h } } @@ -84,6 +104,7 @@ case class CThresholdUncheckedNode(override val challenge: Challenge, override val conjectureType = ConjectureType.ThresholdConjecture override def canEqual(other: Any) = other.isInstanceOf[CThresholdUncheckedNode] + override def equals(other: Any) = (this eq other.asInstanceOf[AnyRef]) || (other match { case other: CThresholdUncheckedNode => util.Arrays.equals(challenge, other.challenge) && @@ -92,4 +113,12 @@ case class CThresholdUncheckedNode(override val challenge: Challenge, polynomialOpt == other.polynomialOpt case _ => false }) + + override def hashCode(): Int = { + var h = util.Arrays.hashCode(challenge) + h = 31 * h + children.hashCode + h = 31 * h + k.hashCode() + h = 31 * h + polynomialOpt.hashCode() + h + } } From 71948a75388a1b5bb8961bf2e141cc1e953165d4 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 17 Mar 2021 23:30:23 +0300 Subject: [PATCH 09/52] test-prove-verify: optimized version of parseAndComputeChallenges --- .../main/scala/sigmastate/SigSerializer.scala | 92 ++++++++++++++++++- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala index 40fa3d8ad6..5e529b414e 100644 --- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala @@ -5,10 +5,13 @@ import sigmastate.basics.DLogProtocol.{SecondDLogProverMessage, ProveDlog} import sigmastate.basics.VerifierMessage.Challenge import sigmastate.Values.SigmaBoolean import sigmastate.interpreter.CryptoConstants -import sigmastate.utils.Helpers +import sigmastate.utils.{SigmaByteReader, Helpers} import Helpers.xor import gf2t.GF2_192_Poly +import scalan.Nullable import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple} +import sigmastate.serialization.SigmaSerializer +import spire.syntax.all.cfor object SigSerializer { @@ -137,14 +140,95 @@ object SigSerializer { } // Verifier doesn't need the polynomial anymore -- hence pass in None - CThresholdUncheckedNode(challenge, seq, t.k, None) -> (finalPos - pos) + CThresholdUncheckedNode(challenge, seq, t.k, Some(polynomial)) -> (finalPos - pos) } } if (bytes.isEmpty) NoProof - else + else { // Verifier step 1: Read the root challenge from the proof. - traverseNode(exp, bytes, 0, challengeOpt = None)._1 // get the root hash, then call + val res = traverseNode(exp, bytes, 0, challengeOpt = None)._1 // get the root hash, then call + val r = SigmaSerializer.startReader(bytes) + val resNew = parseAndComputeChallenges(exp, r, Nullable.None) + assert(res == resNew) + res + } } + + /** HOTSPOT: don't beautify the code + * Note, Nullable is used instead of Option to avoid allocations. */ + def parseAndComputeChallenges(exp: SigmaBoolean, + r: SigmaByteReader, + challengeOpt: Nullable[Challenge] = Nullable.None): UncheckedSigmaTree = { + // Verifier Step 2: Let e_0 be the challenge in the node here (e_0 is called "challenge" in the code) + val challenge = if (challengeOpt.isEmpty) { + Challenge @@ r.getBytes(hashSize) + } else { + challengeOpt.get + } + + exp match { + case dl: ProveDlog => + // Verifier Step 3: For every leaf node, read the response z provided in the proof. + val z = BigIntegers.fromUnsignedByteArray(r.getBytes(order)) + UncheckedSchnorr(dl, None, challenge, SecondDLogProverMessage(z)) + + case dh: ProveDHTuple => + // Verifier Step 3: For every leaf node, read the response z provided in the proof. + val z = BigIntegers.fromUnsignedByteArray(r.getBytes(order)) + UncheckedDiffieHellmanTuple(dh, None, challenge, SecondDiffieHellmanTupleProverMessage(z)) + + case and: CAND => + // Verifier Step 2: If the node is AND, then all of its children get e_0 as the challenge + val nChildren = and.children.length + val children = new Array[UncheckedSigmaTree](nChildren) + cfor(0)(_ < nChildren, _ + 1) { i => + children(i) = parseAndComputeChallenges(and.children(i), r, Nullable(challenge)) + } + CAndUncheckedNode(challenge, children) + + case or: COR => + // Verifier Step 2: If the node is OR, then each of its children except rightmost + // one gets the challenge given in the proof for that node. + // The rightmost child gets a challenge computed as an XOR of the challenges of all the other children and e_0. + + // Read all the children but the last and compute the XOR of all the challenges including e_0 + val nChildren = or.children.length + val children = new Array[UncheckedSigmaTree](nChildren) + val xorBuf = challenge.clone() + val iLastChild = nChildren - 1 + cfor(0)(_ < iLastChild, _ + 1) { i => + val parsedChild = parseAndComputeChallenges(or.children(i), r, Nullable.None) + children(i) = parsedChild + Helpers.xorU(xorBuf, parsedChild.challenge) // xor it into buffer + } + val lastChild = or.children(iLastChild) + + // use the computed XOR for last child's challenge + children(iLastChild) = parseAndComputeChallenges( + lastChild, r, challengeOpt = Nullable(Challenge @@ xorBuf)) + + COrUncheckedNode(challenge, children) + + case t: CTHRESHOLD => + // Verifier Step 2: If the node is THRESHOLD, + // evaluate the polynomial Q(x) at points 1, 2, ..., n to get challenges for child 1, 2, ..., n, respectively. + + // Read the polynomial -- it has n-k coefficients + val nChildren = t.children.length + val coeffBytes = r.getBytes(hashSize * (nChildren - t.k)) + val polynomial = GF2_192_Poly.fromByteArray(challenge, coeffBytes) + + val children = new Array[UncheckedSigmaTree](nChildren) + cfor(0)(_ < nChildren, _ + 1) { i => + val c = Challenge @@ polynomial.evaluate((i + 1).toByte).toByteArray + children(i) = parseAndComputeChallenges(t.children(i), r, Nullable(c)) + } + + // Verifier doesn't need the polynomial anymore -- hence pass in None + CThresholdUncheckedNode(challenge, children, t.k, Some(polynomial)) + } + } + } \ No newline at end of file From febf7a02a697a26efab77d8ad3a5bab9426afa17 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 18 Mar 2021 00:19:08 +0300 Subject: [PATCH 10/52] test-prove-verify: SigmaPPrint for GF2_192_Poly --- .../src/test/scala/sigmastate/helpers/SigmaPPrint.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala index 97dead976e..17dffe49d6 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala @@ -2,6 +2,7 @@ package sigmastate.helpers import java.math.BigInteger +import gf2t.GF2_192_Poly import org.ergoplatform.ErgoBox import org.ergoplatform.ErgoBox.RegisterId import org.ergoplatform.settings.ErgoAlgos @@ -11,7 +12,7 @@ import pprint.{Tree, PPrinter} import scalan.RType import scalan.RType.PrimitiveType import sigmastate.SCollection._ -import sigmastate.Values.{ValueCompanion, ConstantNode, ErgoTree} +import sigmastate.Values.{ValueCompanion, ErgoTree, ConstantNode} import sigmastate.interpreter.CryptoConstants.EcPointType import sigmastate.lang.SigmaTyper import sigmastate.lang.Terms.MethodCall @@ -106,6 +107,11 @@ object SigmaPPrint extends PPrinter { case v: BigInt => Tree.Apply("BigInt", treeifyMany(v.toString(16), 16)) + case poly: GF2_192_Poly => + val c0 = poly.coeff0Bytes() + val others = poly.toByteArray(false) // don't output + Tree.Apply("GF2_192_Poly.fromByteArray", treeifyMany(c0, others)) + case wa: mutable.WrappedArray[Byte @unchecked] if wa.elemTag == ClassTag.Byte => treeifyByteArray(wa.array) From fcbebe31e00d0d04dac41da4890cc3f8afd949f1 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 17 Mar 2021 23:26:42 +0300 Subject: [PATCH 11/52] test-prove-verify: fix equality for GF2_192 and GF2_192_Poly --- sigmastate/src/main/java/gf2t/GF2_192.java | 19 ++++++++++++++++--- .../src/main/java/gf2t/GF2_192_Poly.java | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/sigmastate/src/main/java/gf2t/GF2_192.java b/sigmastate/src/main/java/gf2t/GF2_192.java index ac40b9b071..dc0c166bcf 100644 --- a/sigmastate/src/main/java/gf2t/GF2_192.java +++ b/sigmastate/src/main/java/gf2t/GF2_192.java @@ -28,16 +28,29 @@ */ package gf2t; +import java.util.Arrays; + public class GF2_192 { private final long [] word = new long[3]; /** * - * @param that the field element with which to compare + * @param obj the field element with which to compare * @return true if and only if this and that represent the same field element */ - public boolean equals(GF2_192 that) { - return this.word[0]==that.word[0] && this.word[1]==that.word[1] && this.word[2]==that.word[2]; + @Override + public boolean equals(Object obj) { + if (this == obj) return true; // equal references + if (obj instanceof GF2_192) { + GF2_192 that = (GF2_192)obj; + return this.word[0]==that.word[0] && this.word[1]==that.word[1] && this.word[2]==that.word[2]; + } + return false; // different types + } + + @Override + public int hashCode() { + return Arrays.hashCode(word); } // using irreducible polynomial x^192+x^7+x^2+x+1 diff --git a/sigmastate/src/main/java/gf2t/GF2_192_Poly.java b/sigmastate/src/main/java/gf2t/GF2_192_Poly.java index 2e1e2cafc1..d3ccd61fc3 100644 --- a/sigmastate/src/main/java/gf2t/GF2_192_Poly.java +++ b/sigmastate/src/main/java/gf2t/GF2_192_Poly.java @@ -29,6 +29,8 @@ package gf2t; +import java.util.Arrays; + public class GF2_192_Poly { private final GF2_192 [] c; // must be not null and of length at least 1 @@ -227,4 +229,19 @@ public byte[] toByteArray(Boolean coeff0) { public byte[] coeff0Bytes() { return c[0].toByteArray(); } + + @Override + public int hashCode() { + return 31 * Arrays.deepHashCode(c) + deg; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj instanceof GF2_192_Poly) { + GF2_192_Poly that = (GF2_192_Poly)obj; + return Arrays.deepEquals(c, that.c) && deg == that.deg; + } + return false; + } } From 4ec7ccf0516af4f460081c775bbb0665d7227bc1 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 18 Mar 2021 00:23:37 +0300 Subject: [PATCH 12/52] test-prove-verify: fix roundtrip property for SigSerializer --- sigmastate/src/main/scala/sigmastate/SigSerializer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala index 40fa3d8ad6..2045754400 100644 --- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala @@ -137,7 +137,7 @@ object SigSerializer { } // Verifier doesn't need the polynomial anymore -- hence pass in None - CThresholdUncheckedNode(challenge, seq, t.k, None) -> (finalPos - pos) + CThresholdUncheckedNode(challenge, seq, t.k, Some(polynomial)) -> (finalPos - pos) } } From 428075954dcecb5eaa07bbce883c0b988b9b627b Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 18 Mar 2021 00:26:18 +0300 Subject: [PATCH 13/52] test-prove-verify: test vectors for SigSerializer with CTHRESHOLD --- .../SigSerializerSpecification.scala | 97 ++++++++++++++++--- 1 file changed, 84 insertions(+), 13 deletions(-) diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index d7f437c032..1c654f1ac2 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -3,15 +3,16 @@ package sigmastate.serialization import java.math.BigInteger import java.util +import gf2t.GF2_192_Poly import org.ergoplatform.settings.ErgoAlgos import org.scalacheck.{Arbitrary, Gen} import org.scalatest.Assertion import sigmastate.Values.SigmaBoolean import sigmastate._ -import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} +import sigmastate.basics.DLogProtocol.{SecondDLogProverMessage, ProveDlog} import sigmastate.basics.VerifierMessage.Challenge import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple} -import sigmastate.helpers.{ErgoLikeTransactionTesting, ErgoLikeContextTesting, SigmaTestingCommons, ContextEnrichingTestProvingInterpreter} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons, ErgoLikeTransactionTesting} import sigmastate.interpreter.Interpreter import sigmastate.serialization.generators.ObjectGenerators import sigmastate.utils.Helpers @@ -37,10 +38,9 @@ class SigSerializerSpecification extends SigmaTestingCommons third <- exprTreeGen node <- Gen.oneOf( COR(Array(left, right)), - CAND(Array(left, right)) - // TODO v5.0: uncomment to test property("SigSerializer round trip") - // CTHRESHOLD(2, Array(left, right, third)) - ) + CAND(Array(left, right)), + CTHRESHOLD(2, Array(left, right, third)) + ) if SigmaBoolean.estimateCost(node) <= 100000 // limit size of the generated tree } yield node private def exprTreeGen: Gen[SigmaBoolean] = @@ -91,10 +91,11 @@ class SigSerializerSpecification extends SigmaTestingCommons val prop = prover.fullReduction(tree, ctx, Interpreter.emptyEnv)._1 val proof = prover.prove(tree, ctx, challenge).get.proof - val proofTree = SigSerializer.parseAndComputeChallenges(prop, proof) - roundTrip(proofTree, prop) + val uncheckedTree = SigSerializer.parseAndComputeChallenges(prop, proof) + roundTrip(uncheckedTree, prop) // uncomment to print more useful test cases -// val testCase = ProofTestCase(prop, proof, proofTree) +// val fiatShamirHex = getFiatShamirHex(uncheckedTree) +// val testCase = ProofTestCase(prop, proof, uncheckedTree, fiatShamirHex) // println( // s"""------------------------------- // |${sigmastate.helpers.SigmaPPrint(testCase, width = 150, height = 150)} @@ -119,6 +120,15 @@ class SigSerializerSpecification extends SigmaTestingCommons fiatShamirHex: String = "" ) + /** This steps are performed in Interpreter.checkCommitments and should be in sync with + * that code. */ + def getFiatShamirHex(uncheckedTree: UncheckedTree): String = { + val newRoot = prover.computeCommitments(uncheckedTree).get.asInstanceOf[UncheckedSigmaTree] + val fiatShamirBytes = FiatShamirTree.toBytes(newRoot) + val hex = ErgoAlgos.encode(fiatShamirBytes) + hex + } + property("SigSerializer test vectors") { val cases = Seq( ProofTestCase( @@ -410,6 +420,70 @@ class SigSerializerSpecification extends SigmaTestingCommons ) ), "0001000200000002010027100108cd0368c0d88d9eb2972bbfc23c961de6307f6a944352cbfe316f262401feabdaa87d7300002102628c0e33748b2e120536945d77e69b3fb5fb3878c5697c20bf70b8459f476e4b01008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802badd523c2f5c12f4a3d4d667efb6ce8e95c8ad007cc697c34e91884335b5524902c455e55dc7bc731c5a487778e84814080fb70cc59957bc2a40c45373fe1ce14c029d4ec275379f9212a53e15994aef203dcec43a177c0b1f40afcf592e5753ce6773000042030064fb366dce2d424a14308d2c3d562f3558b042250cc28eab47a594efdec9c603787ca34604619468b3c677e2887a0de382a093264729a15ace38f1e3be20fd0f0001000201008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa703a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e20315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a7300004202f718c7ac18b4d942056b7237809f843811a04314044d6dd517022f6d15198c4e0329ecb543ea406fda96d3088196f4490cacdea49286c954ea2924523a57847a7d01008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802a5af61e5c0eaad47ffdf29a91afffb1791295fff434831802e7f36b885fc2aa703a9aa914199bb0e3b00ff4dd6ff82ec34d5f451825c28c41dc6432c763b6061e20315d84dba1b29074f766e57bb11843687da899180cf2487ccecd0a3ec5f05365a73000042021cc93df3de5b405ee306f5c7003b6abbfb7b4d0ab356c36d0c454a4c6b3403f603608a98d8aa0781ec96970353b4e22ae05454833528ae8f8468d0a8fc3f92675a" + ), + ProofTestCase( + CTHRESHOLD( + 2, + Array( + ProveDlog(Helpers.decodeECPoint("03a5a5234701fff48be4ed1b3e1fab446657eeddb52e2573c52b9c4021f2403866")), + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("036b52166c82e61d0954d521a8a8a20af0eb1adb7f23a9c3ee1ebac1242e35ac18"), + Helpers.decodeECPoint("0339a5debbb2bb67aa560e98dbfc4050e8ca0643683314cd1bc911f11c5477a312"), + Helpers.decodeECPoint("02730455ebb8c01a89dced09c5253c9bfa4b1471d1068ba30ab226104a6551c461") + ), + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("029775461de9886800dac39ef14f535e38dc3b2719770d69627007aee9f638e918"), + Helpers.decodeECPoint("02fdc07913da5615db53a0351a47131832d77e29c6520248e82a568d02b09d4604"), + Helpers.decodeECPoint("03cefefa1511430ca2a873759107085f269f6fbcd4e836db7760749f52b7f7923a") + ) + ) + ), + ErgoAlgos.decodeUnsafe( + "c94696c3e3089d9fd1174c18e6dd22f1be8003bbea08011fcf39310e7c9049c1c9966198b8d63a2f19e98843b81b74399f662dba4e764cd548406dd180453dd1bc0e24562f0184d189ca25a41ca8b54ada857dd649d3228a8c359ac499d430ecada3f92d5206cddeffb16248068c1003477d717e04afbf206c87a59ce5263ee7cc4020b5772d91b1df00bd72b15347fd" + ), + CThresholdUncheckedNode( + Challenge @@ ErgoAlgos.decodeUnsafe("c94696c3e3089d9fd1174c18e6dd22f1be8003bbea08011f"), + List( + UncheckedSchnorr( + ProveDlog(Helpers.decodeECPoint("03a5a5234701fff48be4ed1b3e1fab446657eeddb52e2573c52b9c4021f2403866")), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("067fa7cd9f98d45e18812d805e0b18dea7698bf852137526"), + SecondDLogProverMessage(BigInt("9f662dba4e764cd548406dd180453dd1bc0e24562f0184d189ca25a41ca8b54a", 16)) + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("036b52166c82e61d0954d521a8a8a20af0eb1adb7f23a9c3ee1ebac1242e35ac18"), + Helpers.decodeECPoint("0339a5debbb2bb67aa560e98dbfc4050e8ca0643683314cd1bc911f11c5477a312"), + Helpers.decodeECPoint("02730455ebb8c01a89dced09c5253c9bfa4b1471d1068ba30ab226104a6551c461") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("5735f4df1b280e1d423a8f28977057af8c52123c9a3fe96d"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("da857dd649d3228a8c359ac499d430ecada3f92d5206cddeffb16248068c1003", 16)) + ), + UncheckedDiffieHellmanTuple( + ProveDHTuple( + Helpers.decodeECPoint("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Helpers.decodeECPoint("029775461de9886800dac39ef14f535e38dc3b2719770d69627007aee9f638e918"), + Helpers.decodeECPoint("02fdc07913da5615db53a0351a47131832d77e29c6520248e82a568d02b09d4604"), + Helpers.decodeECPoint("03cefefa1511430ca2a873759107085f269f6fbcd4e836db7760749f52b7f7923a") + ), + None, + Challenge @@ ErgoAlgos.decodeUnsafe("980cc5d167b847dc8baceeb02fa66d8095bb9a7f22249d54"), + SecondDiffieHellmanTupleProverMessage(new BigInteger("477d717e04afbf206c87a59ce5263ee7cc4020b5772d91b1df00bd72b15347fd", 16)) + ) + ), + 2, + Some( + GF2_192_Poly.fromByteArray( + ErgoAlgos.decodeUnsafe("c94696c3e3089d9fd1174c18e6dd22f1be8003bbea08011f"), + ErgoAlgos.decodeUnsafe("cf39310e7c9049c1c9966198b8d63a2f19e98843b81b7439") + ) + ) + ), + "0002020003010027100108cd03a5a5234701fff48be4ed1b3e1fab446657eeddb52e2573c52b9c4021f2403866730000210337af2d4066efa710279bcf8e1e53bb0cc7a164d04b5b1734f182fc33517a1aa701008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798036b52166c82e61d0954d521a8a8a20af0eb1adb7f23a9c3ee1ebac1242e35ac180339a5debbb2bb67aa560e98dbfc4050e8ca0643683314cd1bc911f11c5477a31202730455ebb8c01a89dced09c5253c9bfa4b1471d1068ba30ab226104a6551c46173000042033569a1a0dc21c510fb415b8cf11bc79d40ba0c9344ac4f6d031e0cefb0c5a86a039e35b6fde9f8443e18527807007619874abcc5a6d28d8ad9324bf89749d2f72901008a100108ce0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798029775461de9886800dac39ef14f535e38dc3b2719770d69627007aee9f638e91802fdc07913da5615db53a0351a47131832d77e29c6520248e82a568d02b09d460403cefefa1511430ca2a873759107085f269f6fbcd4e836db7760749f52b7f7923a7300004203e4ffc0fd026f9b9ab6d80d13e453f14caf09b1157ffca8d3da899b52f6b10d1603e119655077c1fda14ab47138496b50f4e7305edbfd0a6925952a4650d4f2729f" ) ) @@ -419,10 +493,7 @@ class SigSerializerSpecification extends SigmaTestingCommons val uncheckedTree = SigSerializer.parseAndComputeChallenges(c.prop, c.proof) uncheckedTree shouldBe c.uncheckedTree - // this step is performed in Interpreter.checkCommitments - val newRoot = prover.computeCommitments(c.uncheckedTree).get.asInstanceOf[UncheckedSigmaTree] - val fiatShamirBytes = FiatShamirTree.toBytes(newRoot) - val hex = ErgoAlgos.encode(fiatShamirBytes) + val hex = getFiatShamirHex(c.uncheckedTree) if (c.fiatShamirHex.isEmpty) { // output test vector From 7a772ef8f6b249ee6f16d98629841c86a4ed0ad1 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 18 Mar 2021 13:40:05 +0300 Subject: [PATCH 14/52] test-prove-verify: clarified implementation of to256BitValueExact --- common/src/main/scala/scalan/util/Extensions.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/common/src/main/scala/scalan/util/Extensions.scala b/common/src/main/scala/scalan/util/Extensions.scala index 7a820585fe..d144d6dc96 100644 --- a/common/src/main/scala/scalan/util/Extensions.scala +++ b/common/src/main/scala/scalan/util/Extensions.scala @@ -164,7 +164,14 @@ object Extensions { * @see BigInteger#longValueExact */ @inline final def to256BitValueExact: BigInteger = { - if (x.bitLength() <= 255) x // TODO HF: allow 256 bit values + // Comparing with 255 is correct because bitLength() method excludes the sign bit. + // For example, these are the boundary values: + // (new BigInteger("80" + "00" * 31, 16)).bitLength() = 256 + // (new BigInteger("7F" + "ff" * 31, 16)).bitLength() = 255 + // (new BigInteger("-7F" + "ff" * 31, 16)).bitLength() = 255 + // (new BigInteger("-80" + "00" * 31, 16)).bitLength() = 255 + // (new BigInteger("-80" + "00" * 30 + "01", 16)).bitLength() = 256 + if (x.bitLength() <= 255) x else throw new ArithmeticException("BigInteger out of 256 bit range"); } From 1b9f4d1f15022ff8fa4837eecfaae6642270faf9 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 18 Mar 2021 17:32:33 +0300 Subject: [PATCH 15/52] test-prove-verify: cleanup, clarification and optimization of imports --- .../src/main/scala/sigmastate/UnprovenTree.scala | 2 +- .../scala/sigmastate/interpreter/Interpreter.scala | 8 ++++++-- .../sigmastate/interpreter/ProverInterpreter.scala | 4 ++-- .../sigmastate/crypto/SigningSpecification.scala | 9 +++++++++ .../scala/sigmastate/helpers/SigmaPPrint.scala | 9 ++++----- .../serialization/SigSerializerSpecification.scala | 14 ++++++++++---- .../sigmastate/utxo/ProverSpecification.scala | 6 +++--- 7 files changed, 35 insertions(+), 17 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala index 2415523ef9..db780a729c 100644 --- a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala +++ b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala @@ -213,7 +213,7 @@ object FiatShamirTree { val internalNodePrefix = 0: Byte val leafPrefix = 1: Byte - // TODO optimize: this serialization is not the hot path during verification + // TODO optimize: this serialization is on the hot path during verification /** HOTSPOT: */ def toBytes(tree: ProofTree): Array[Byte] = { diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 4ccf5e6680..0d47a21d01 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -364,8 +364,12 @@ trait Interpreter extends ScorexLogging { checkCommitments(sp, message) } } catch { - case e: Exception => - log.warn("Improper signature: ", e); false + case t: Throwable => + // TODO coverage: property("handle improper signature") doesn't lead to exception + // because the current implementation of parseAndComputeChallenges doesn't check + // signature format + log.warn("Improper signature: ", t); + false } } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala index 6f95f986cc..75c5781ab4 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala @@ -4,10 +4,10 @@ import java.math.BigInteger import gf2t.{GF2_192, GF2_192_Poly} import org.bitbucket.inkytonik.kiama.attribution.AttributionCore -import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{rule, everywheretd, everywherebu} +import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{everywherebu, everywheretd, rule} import org.bitbucket.inkytonik.kiama.rewriting.Strategy import scalan.util.CollectionUtil._ -import sigmastate.TrivialProp.{TrueProp, FalseProp} +import sigmastate.TrivialProp.{FalseProp, TrueProp} import sigmastate.Values._ import sigmastate._ import sigmastate.basics.DLogProtocol._ diff --git a/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala b/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala index 5a81fcdfa4..9c5c1ae390 100644 --- a/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala @@ -29,6 +29,15 @@ class SigningSpecification extends SigmaTestingCommons { printSimpleSignature(msg: Array[Byte]) } + property("handle improper signature") { + val pi = new ErgoLikeTestProvingInterpreter() + val sigmaTree: SigmaBoolean = pi.publicKeys.head + val verifier = new ErgoLikeTestInterpreter + val msg = "any message".getBytes("UTF-8") + val sig = "invalid signature".getBytes("UTF-8") + verifier.verifySignature(sigmaTree, msg, sig) shouldBe false + } + property("threshold signature test vector") { val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get diff --git a/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala index 17dffe49d6..5e6df2a349 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala @@ -6,22 +6,21 @@ import gf2t.GF2_192_Poly import org.ergoplatform.ErgoBox import org.ergoplatform.ErgoBox.RegisterId import org.ergoplatform.settings.ErgoAlgos - -import scala.collection.mutable -import pprint.{Tree, PPrinter} +import pprint.{PPrinter, Tree} import scalan.RType import scalan.RType.PrimitiveType import sigmastate.SCollection._ -import sigmastate.Values.{ValueCompanion, ErgoTree, ConstantNode} +import sigmastate.Values.{ConstantNode, ErgoTree, ValueCompanion} +import sigmastate._ import sigmastate.interpreter.CryptoConstants.EcPointType import sigmastate.lang.SigmaTyper import sigmastate.lang.Terms.MethodCall import sigmastate.serialization.GroupElementSerializer import sigmastate.utxo.SelectField -import sigmastate._ import special.collection.Coll import special.sigma.GroupElement +import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.reflect.ClassTag diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index 1c654f1ac2..278f52ec9c 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -9,10 +9,10 @@ import org.scalacheck.{Arbitrary, Gen} import org.scalatest.Assertion import sigmastate.Values.SigmaBoolean import sigmastate._ -import sigmastate.basics.DLogProtocol.{SecondDLogProverMessage, ProveDlog} +import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple} -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons, ErgoLikeTransactionTesting} +import sigmastate.basics.{ProveDHTuple, SecondDiffieHellmanTupleProverMessage} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTransactionTesting, SigmaTestingCommons} import sigmastate.interpreter.Interpreter import sigmastate.serialization.generators.ObjectGenerators import sigmastate.utils.Helpers @@ -41,6 +41,7 @@ class SigSerializerSpecification extends SigmaTestingCommons CAND(Array(left, right)), CTHRESHOLD(2, Array(left, right, third)) ) if SigmaBoolean.estimateCost(node) <= 100000 // limit size of the generated tree + // to fit into Box size limit } yield node private def exprTreeGen: Gen[SigmaBoolean] = @@ -93,13 +94,16 @@ class SigSerializerSpecification extends SigmaTestingCommons val proof = prover.prove(tree, ctx, challenge).get.proof val uncheckedTree = SigSerializer.parseAndComputeChallenges(prop, proof) roundTrip(uncheckedTree, prop) -// uncomment to print more useful test cases + +// uncomment the following lines to print test cases with test vectors +// they can be added to property("SigSerializer test vectors") // val fiatShamirHex = getFiatShamirHex(uncheckedTree) // val testCase = ProofTestCase(prop, proof, uncheckedTree, fiatShamirHex) // println( // s"""------------------------------- // |${sigmastate.helpers.SigmaPPrint(testCase, width = 150, height = 150)} // |""".stripMargin) + } catch { case t: Throwable => t.printStackTrace() @@ -130,6 +134,8 @@ class SigSerializerSpecification extends SigmaTestingCommons } property("SigSerializer test vectors") { + // this list of test cases contains test vectors generated by v4.0.2 + // as such they can be used to catch changes in behaviour of later versions val cases = Seq( ProofTestCase( prop = ProveDlog( diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala index b22fcbf1f3..3439a7df4c 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/ProverSpecification.scala @@ -2,12 +2,12 @@ package sigmastate.utxo import org.ergoplatform.ErgoLikeInterpreter import scorex.crypto.hash.Blake2b256 -import sigmastate._ import sigmastate.Values.SigmaBoolean +import sigmastate._ import sigmastate.basics.DLogProtocol.FirstDLogProverMessage -import sigmastate.basics.{SecP256K1, FirstDiffieHellmanTupleProverMessage} +import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, SecP256K1} import sigmastate.helpers.{ErgoLikeTestProvingInterpreter, SigmaTestingCommons} -import sigmastate.interpreter.{ProverInterpreter, HintsBag} +import sigmastate.interpreter.{HintsBag, ProverInterpreter} import sigmastate.lang.exceptions.InterpreterException class ProverSpecification extends SigmaTestingCommons { From 68900fd9f15ad6285b946c8d351cc395555464b3 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 19 Mar 2021 15:38:36 +0300 Subject: [PATCH 16/52] test-prove-verify: optimize equality of UncheckedDiffieHellmanTuple --- .../main/scala/sigmastate/UncheckedTree.scala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala b/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala index 8b4f13220e..b8bd59dc66 100644 --- a/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala +++ b/sigmastate/src/main/scala/sigmastate/UncheckedTree.scala @@ -40,15 +40,17 @@ case class UncheckedSchnorr(override val proposition: ProveDlog, override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { case x: UncheckedSchnorr => - util.Arrays.equals(challenge, x.challenge) && - commitmentOpt == x.commitmentOpt && + // NOTE, proposition is not compared because it is included into challenge + // like `challenge = hash(prop ++ msg)` + commitmentOpt == x.commitmentOpt && + util.Arrays.equals(challenge, x.challenge) && secondMessage == x.secondMessage case _ => false }) override def hashCode(): Int = { - var h = util.Arrays.hashCode(challenge) - h = 31 * h + commitmentOpt.hashCode() + var h = commitmentOpt.hashCode() + h = 31 * h + util.Arrays.hashCode(challenge) h = 31 * h + secondMessage.hashCode() h } @@ -63,16 +65,16 @@ case class UncheckedDiffieHellmanTuple(override val proposition: ProveDHTuple, override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { case x: UncheckedDiffieHellmanTuple => - proposition == x.proposition && - commitmentOpt == x.commitmentOpt && + // NOTE, proposition is not compared because it is included into challenge + // like `challenge = hash(prop ++ msg)` + commitmentOpt == x.commitmentOpt && util.Arrays.equals(challenge, x.challenge) && secondMessage == x.secondMessage case _ => false }) override def hashCode(): Int = { - var h = proposition.hashCode() - h = 31 * h + commitmentOpt.hashCode() + var h = commitmentOpt.hashCode() h = 31 * h + util.Arrays.hashCode(challenge) h = 31 * h + secondMessage.hashCode() h From f1114b014b2989e1e71b0fc61035c49f35a89608 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 19 Mar 2021 17:06:58 +0300 Subject: [PATCH 17/52] verify-opt: optimized SigSerializer --- .../main/scala/sigmastate/SigSerializer.scala | 246 ++++++++---------- .../sigmastate/interpreter/Interpreter.scala | 2 +- .../interpreter/ProverInterpreter.scala | 4 +- .../SigSerializerSpecification.scala | 6 +- 4 files changed, 115 insertions(+), 143 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala index 5e529b414e..31451845a5 100644 --- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala @@ -1,16 +1,16 @@ package sigmastate +import gf2t.GF2_192_Poly import org.bouncycastle.util.BigIntegers -import sigmastate.basics.DLogProtocol.{SecondDLogProverMessage, ProveDlog} -import sigmastate.basics.VerifierMessage.Challenge +import scalan.Nullable import sigmastate.Values.SigmaBoolean +import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} +import sigmastate.basics.VerifierMessage.Challenge +import sigmastate.basics.{ProveDHTuple, SecondDiffieHellmanTupleProverMessage} import sigmastate.interpreter.CryptoConstants -import sigmastate.utils.{SigmaByteReader, Helpers} -import Helpers.xor -import gf2t.GF2_192_Poly -import scalan.Nullable -import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple} +import sigmastate.lang.exceptions.SerializerException import sigmastate.serialization.SigmaSerializer +import sigmastate.utils.{Helpers, SigmaByteReader, SigmaByteWriter} import spire.syntax.all.cfor object SigSerializer { @@ -18,149 +18,121 @@ object SigSerializer { val hashSize = CryptoConstants.soundnessBits / 8 val order = CryptoConstants.groupSize - def toBytes(tree: UncheckedTree): Array[Byte] = { - - def traverseNode(node: UncheckedSigmaTree, - acc: Array[Byte], - writeChallenge: Boolean = true): Array[Byte] = { - val parentChal = if (writeChallenge) node.challenge else Array.emptyByteArray - node match { - case dl: UncheckedSchnorr => - acc ++ - parentChal ++ - BigIntegers.asUnsignedByteArray(order, dl.secondMessage.z.bigInteger) - case dh: UncheckedDiffieHellmanTuple => - acc ++ - parentChal ++ - BigIntegers.asUnsignedByteArray(order, dh.secondMessage.z) - case and: CAndUncheckedNode => - // don't write children's challenges -- they are equal to the challenge of this node - and.children.foldLeft(acc ++ parentChal) { case (ba, child) => - traverseNode(child, ba, writeChallenge = false) - } - case or: COrUncheckedNode => - // don't write last child's challenge -- it's computed by the verifier via XOR - val res = or.children.init.foldLeft(acc ++ parentChal) { case (ba, child) => - traverseNode(child, ba, writeChallenge = true) - } - traverseNode(or.children.last, res, writeChallenge = false) - - case t: CThresholdUncheckedNode => - // write the polynomial, except the zero coefficient - val poly = t.polynomialOpt.get.toByteArray(false) - - // don't write children's challenges - t.children.foldLeft(acc ++ parentChal ++ poly) { case (ba, child) => - traverseNode(child, ba, writeChallenge = false) - } - - case _ => ??? - } - } - + /** Recursively traverses the given node and serializes challenges and prover messages + * to the given writer. + * Note, sigma propositions and commitments are not serialized. + * + * @param tree tree to traverse and serialize + * @return the proof bytes containing all the serialized challenges and prover messages + * (aka `z` values) + */ + def toProofBytes(tree: UncheckedTree): Array[Byte] = { tree match { - case NoProof => Array.emptyByteArray - case t: UncheckedSigmaTree => traverseNode(t, Array[Byte](), writeChallenge = true) // always write the root challenge + case NoProof => + Array.emptyByteArray + case t: UncheckedSigmaTree => + val w = SigmaSerializer.startWriter() + toProofBytes(t, w, writeChallenge = true) + val res = w.toBytes + res } } + /** Recursively traverses the given node and serializes challenges and prover messages + * to the given writer. + * Note, sigma propositions and commitments are not serialized. + * + * @param node subtree to traverse + * @param w writer to put the bytes + * @param writeChallenge if true, than node.challenge is serialized, and omitted + * otherwise. + */ + def toProofBytes(node: UncheckedSigmaTree, + w: SigmaByteWriter, + writeChallenge: Boolean): Unit = { + if (writeChallenge) { + w.putBytes(node.challenge) + } + node match { + case dl: UncheckedSchnorr => + val z = BigIntegers.asUnsignedByteArray(order, dl.secondMessage.z.bigInteger) + w.putBytes(z) + + case dh: UncheckedDiffieHellmanTuple => + val z = BigIntegers.asUnsignedByteArray(order, dh.secondMessage.z) + w.putBytes(z) + + case and: CAndUncheckedNode => + // don't write children's challenges -- they are equal to the challenge of this node + val cs = and.children.toArray + cfor(0)(_ < cs.length, _ + 1) { i => + val child = cs(i) + toProofBytes(child, w, writeChallenge = false) + } - def parseAndComputeChallenges(exp: SigmaBoolean, bytes: Array[Byte]): UncheckedTree = { - - /** - * Verifier Step 2: In a top-down traversal of the tree, obtain the challenges for the children of every - * non-leaf node by reading them from the proof or computing them. - * Verifier Step 3: For every leaf node, read the response z provided in the proof. - * - * @param exp - * @param bytes - * @param pos - * @param challengeOpt if non-empty, then the challenge has been computed for this node by its parent; - * else it needs to be read from the proof - * @return - */ - def traverseNode(exp: SigmaBoolean, - bytes: Array[Byte], - pos: Int, - challengeOpt: Option[Challenge] = None): (UncheckedSigmaTree, Int) = { - // Verifier Step 2: Let e_0 be the challenge in the node here (e_0 is called "challenge" in the code) - val (challenge, chalLen) = if (challengeOpt.isEmpty) { - (Challenge @@ bytes.slice(pos, pos + hashSize)) -> hashSize - } else { - challengeOpt.get -> 0 - } - exp match { - case dl: ProveDlog => - // Verifier Step 3: For every leaf node, read the response z provided in the proof. - val z = BigIntegers.fromUnsignedByteArray(bytes.slice(pos + chalLen, pos + chalLen + order)) - UncheckedSchnorr(dl, None, challenge, SecondDLogProverMessage(z)) -> (chalLen + order) - - case dh: ProveDHTuple => - // Verifier Step 3: For every leaf node, read the response z provided in the proof. - val z = BigIntegers.fromUnsignedByteArray(bytes.slice(pos + chalLen, pos + chalLen + order)) - UncheckedDiffieHellmanTuple(dh, None, challenge, SecondDiffieHellmanTupleProverMessage(z)) -> (chalLen + order) - - case and: CAND => - // Verifier Step 2: If the node is AND, then all of its children get e_0 as the challenge - val (seq, finalPos) = and.children.foldLeft(Seq[UncheckedSigmaTree]() -> (pos + chalLen)) { case ((s, p), child) => - val (rewrittenChild, consumed) = traverseNode(child, bytes, p, Some(challenge)) - (s :+ rewrittenChild, p + consumed) - } - CAndUncheckedNode(challenge, seq) -> (finalPos - pos) - - case or: COR => - // Verifier Step 2: If the node is OR, then each of its children except rightmost - // one gets the challenge given in the proof for that node. - // The rightmost child gets a challenge computed as an XOR of the challenges of all the other children and e_0. - - // Read all the children but the last and compute the XOR of all the challenges including e_0 - val (seq, lastPos, lastChallenge) = or.children.init.foldLeft((Seq[UncheckedSigmaTree](), pos + chalLen, challenge)) { - case ((s, p, challengeXOR), child) => - val (rewrittenChild, consumed) = traverseNode(child, bytes, p, challengeOpt = None) - val ch = Challenge @@ xor(challengeXOR, rewrittenChild.challenge) - (s :+ rewrittenChild, p + consumed, ch) - } - // use the computed XOR for last child's challenge - val (lastChild, numRightChildBytes) = traverseNode(or.children.last, bytes, lastPos, Some(lastChallenge)) - COrUncheckedNode(challenge, seq :+ lastChild) -> (lastPos + numRightChildBytes - pos) - - case t: CTHRESHOLD => - // Verifier Step 2: If the node is THRESHOLD, - // evaluate the polynomial Q(x) at points 1, 2, ..., n to get challenges for child 1, 2, ..., n, respectively. - - // Read the polynomial -- it has n-k coefficients - val endPolyPos = pos + chalLen + hashSize * (t.children.length - t.k) - val polynomial = GF2_192_Poly.fromByteArray(challenge, bytes.slice(pos + chalLen, endPolyPos)) - - - val (seq, finalPos, _) = t.children.foldLeft((Seq[UncheckedSigmaTree](), endPolyPos, 1)) { - case ((s, p, childIndex), child) => - val (rewrittenChild, consumed) = traverseNode(child, bytes, p, Some(Challenge @@ polynomial.evaluate(childIndex.toByte).toByteArray)) - (s :+ rewrittenChild, p + consumed, childIndex + 1) - } - - // Verifier doesn't need the polynomial anymore -- hence pass in None - CThresholdUncheckedNode(challenge, seq, t.k, Some(polynomial)) -> (finalPos - pos) - } + case or: COrUncheckedNode => + // don't write last child's challenge -- it's computed by the verifier via XOR + val cs = or.children.toArray + val iLastChild = cs.length - 1 + cfor(0)(_ < iLastChild, _ + 1) { i => + val child = cs(i) + toProofBytes(child, w, writeChallenge = true) + } + toProofBytes(cs(iLastChild), w, writeChallenge = false) + + case t: CThresholdUncheckedNode => + // write the polynomial, except the zero coefficient + val poly = t.polynomialOpt.get.toByteArray(false) + w.putBytes(poly) + + // don't write children's challenges + val cs = t.children.toArray + cfor(0)(_ < cs.length, _ + 1) { i => + val child = cs(i) + toProofBytes(child, w, writeChallenge = false) + } + + case _ => + throw new SerializerException(s"Don't know how to execute toBytes($node)") } + } - if (bytes.isEmpty) + /** Verifier Step 2: In a top-down traversal of the tree, obtain the challenges for the + * children of every non-leaf node by reading them from the proof or computing them. + * Verifier Step 3: For every leaf node, read the response z provided in the proof. + * + * @param exp sigma proposition which defines the structure of bytes from the reader + * @param proof proof to extract challenges from + * @return An instance of [[UncheckedTree]] i.e. either [[NoProof]] or [[UncheckedSigmaTree]] + */ + def parseAndComputeChallenges(exp: SigmaBoolean, proof: Array[Byte]): UncheckedTree = { + if (proof.isEmpty) NoProof else { - // Verifier step 1: Read the root challenge from the proof. - val res = traverseNode(exp, bytes, 0, challengeOpt = None)._1 // get the root hash, then call - val r = SigmaSerializer.startReader(bytes) - val resNew = parseAndComputeChallenges(exp, r, Nullable.None) - assert(res == resNew) + // Verifier step 1: Read the root challenge from the proof. + val r = SigmaSerializer.startReader(proof) + val res = parseAndComputeChallenges(exp, r, Nullable.None) res } } - /** HOTSPOT: don't beautify the code - * Note, Nullable is used instead of Option to avoid allocations. */ - def parseAndComputeChallenges(exp: SigmaBoolean, - r: SigmaByteReader, - challengeOpt: Nullable[Challenge] = Nullable.None): UncheckedSigmaTree = { + /** Verifier Step 2: In a top-down traversal of the tree, obtain the challenges for the + * children of every non-leaf node by reading them from the proof or computing them. + * Verifier Step 3: For every leaf node, read the response z provided in the proof. + * + * @param exp sigma proposition which defines the structure of bytes from the reader + * @param r reader to extract challenges from + * @param challengeOpt if non-empty, then the challenge has been computed for this node + * by its parent; else it needs to be read from the proof (via reader) + * @return An instance of [[UncheckedSigmaTree]] + * + * HOTSPOT: don't beautify the code + * Note, Nullable is used instead of Option to avoid allocations. + */ + def parseAndComputeChallenges( + exp: SigmaBoolean, + r: SigmaByteReader, + challengeOpt: Nullable[Challenge] = Nullable.None): UncheckedSigmaTree = { // Verifier Step 2: Let e_0 be the challenge in the node here (e_0 is called "challenge" in the code) val challenge = if (challengeOpt.isEmpty) { Challenge @@ r.getBytes(hashSize) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 0d47a21d01..f563b6b211 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -341,7 +341,7 @@ trait Interpreter extends ScorexLogging { context: CTX, proof: ProofT, message: Array[Byte]): Try[VerificationResult] = { - verify(Interpreter.emptyEnv, ergoTree, context, SigSerializer.toBytes(proof), message) + verify(Interpreter.emptyEnv, ergoTree, context, SigSerializer.toProofBytes(proof), message) } /** diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala index 75c5781ab4..4c0d180b2f 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala @@ -143,7 +143,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor prove(unprovenTree, message, hintsBag) } // Prover Step 10: output the right information into the proof - val proof = SigSerializer.toBytes(proofTree) + val proof = SigSerializer.toProofBytes(proofTree) proof } /** @@ -599,7 +599,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor hintsBag: HintsBag): Try[Array[Byte]] = Try { val unprovenTree = convertToUnproven(sigmaTree) val proofTree = prove(unprovenTree, message, hintsBag) - SigSerializer.toBytes(proofTree) + SigSerializer.toProofBytes(proofTree) } } diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index 278f52ec9c..af1e2c5e3c 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -63,8 +63,8 @@ class SigSerializerSpecification extends SigmaTestingCommons private def roundTrip(uncheckedTree: UncheckedTree, exp: SigmaBoolean): Assertion = { - val bytes = SigSerializer.toBytes(uncheckedTree) - val parsedUncheckedTree = SigSerializer.parseAndComputeChallenges(exp, bytes) + val proof = SigSerializer.toProofBytes(uncheckedTree) + val parsedUncheckedTree = SigSerializer.parseAndComputeChallenges(exp, proof) isEquivalent(uncheckedTree, parsedUncheckedTree) shouldBe true } @@ -494,7 +494,7 @@ class SigSerializerSpecification extends SigmaTestingCommons ) cases.zipWithIndex.foreach { case (c, iCase) => - val sigBytes = SigSerializer.toBytes(c.uncheckedTree) + val sigBytes = SigSerializer.toProofBytes(c.uncheckedTree) sigBytes shouldBe c.proof val uncheckedTree = SigSerializer.parseAndComputeChallenges(c.prop, c.proof) uncheckedTree shouldBe c.uncheckedTree From 632f64189d5f7e46a97dae7c0a7e1b422bb9d8b9 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 19 Mar 2021 20:16:23 +0300 Subject: [PATCH 18/52] verify-opt: added method SigmaByteWriter.putShortBytes --- .../src/main/scala/sigmastate/utils/SigmaByteWriter.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala index 733b4c348b..4c3ef03610 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala @@ -87,6 +87,14 @@ class SigmaByteWriter(val w: Writer, w.putBytes(xs); this } + /** Put the two bytes of the big-endian representation of the Short value into the + * writer. */ + @inline def putShortBytes(value: Short): this.type = { + w.put((value >> 8).toByte) + w.put(value.toByte) + this + } + @inline def putBits(xs: Array[Boolean]): this.type = { w.putBits(xs); this } @inline def putBits(xs: Array[Boolean], info: DataInfo[Bits]): this.type = { ValueSerializer.addArgInfo(info) From 08903ce9776777341dd4dd52064ad7eba160fb1e Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 19 Mar 2021 20:17:46 +0300 Subject: [PATCH 19/52] verify-opt: optimization of FiatShamirTree.toBytes --- .../main/scala/sigmastate/UnprovenTree.scala | 99 +++++++++++-------- 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala index db780a729c..7cbe19f3d6 100644 --- a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala +++ b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala @@ -2,13 +2,15 @@ package sigmastate import java.math.BigInteger -import com.google.common.primitives.Shorts import gf2t.GF2_192_Poly +import sigmastate.Values.{ErgoTree, SigmaBoolean, SigmaPropConstant} import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, ProveDlog} import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.Values.{ErgoTree, SigmaBoolean, SigmaPropConstant} import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, FirstProverMessage, ProveDHTuple} import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer +import sigmastate.serialization.SigmaSerializer +import sigmastate.utils.SigmaByteWriter +import spire.syntax.all.cfor import scala.language.existentials @@ -197,51 +199,64 @@ case class UnprovenDiffieHellmanTuple(override val proposition: ProveDHTuple, override def withPosition(updatedPosition: NodePosition) = this.copy(position = updatedPosition) } - -/** - * Prover Step 7: Convert the tree to a string s for input to the Fiat-Shamir hash function. - * The conversion should be such that the tree can be unambiguously parsed and restored given the string. - * For each non-leaf node, the string should contain its type (OR or AND). - * For each leaf node, the string should contain the Sigma-protocol statement being proven and the commitment. - * The string should not contain information on whether a node is marked "real" or "simulated", - * and should not contain challenges, responses, or the real/simulated flag for any node. - * - */ // TODO coverage (8h): write a test that restores the tree from this string and check that the result is equal, // in order to make sure this conversion is unambiguous object FiatShamirTree { - val internalNodePrefix = 0: Byte - val leafPrefix = 1: Byte + val internalNodePrefix: Byte = 0 + val leafPrefix: Byte = 1 - // TODO optimize: this serialization is on the hot path during verification - /** HOTSPOT: */ + /** Prover Step 7: Convert the tree to a byte array `s` for input to the Fiat-Shamir hash + * function. + * See the other overload for details. */ def toBytes(tree: ProofTree): Array[Byte] = { + val w = SigmaSerializer.startWriter() + toBytes(tree, w) + w.toBytes + } - def traverseNode(node: ProofTree): Array[Byte] = node match { - case l: ProofTreeLeaf => - val propTree = ErgoTree.withSegregation(SigmaPropConstant(l.proposition)) - val propBytes = DefaultSerializer.serializeErgoTree(propTree) - val commitmentBytes = l.commitmentOpt.get.bytes - leafPrefix +: - ((Shorts.toByteArray(propBytes.length.toShort) ++ propBytes) ++ - (Shorts.toByteArray(commitmentBytes.length.toShort) ++ commitmentBytes)) - - case c: ProofTreeConjecture => - val childrenCountBytes = Shorts.toByteArray(c.children.length.toShort) - val conjBytes = Array(internalNodePrefix, c.conjectureType.id.toByte) - val thresholdByte = c match { - case unproven: CThresholdUnproven => - Array(unproven.k.toByte) - case unchecked: CThresholdUncheckedNode => - Array(unchecked.k.toByte) - case _ => Array() - } - - c.children.foldLeft(conjBytes ++ thresholdByte ++ childrenCountBytes) { case (acc, ch) => - acc ++ traverseNode(ch) - } - } - - traverseNode(tree) + /** Prover Step 7: Convert the tree to a byte array `s` for input to the Fiat-Shamir hash + * function. The conversion should be such that the tree can be unambiguously parsed and + * restored given the array. + * For each non-leaf node, the string should contain its type (OR or AND). + * For each leaf node, the string should contain the Sigma-protocol statement being + * proven and the commitment. + * The string should not contain information on whether a node is marked "real" or + * "simulated", and should not contain challenges, responses and/or the real/simulated + * flag for any node. + * + * @param tree the tree to take commitments from + * @param w writer which is used for serialization + * + * HOTSPOT: don't beautify the code + */ + def toBytes(tree: ProofTree, w: SigmaByteWriter): Unit = tree match { + case l: ProofTreeLeaf => + val propTree = ErgoTree.withSegregation(SigmaPropConstant(l.proposition)) + val propBytes = DefaultSerializer.serializeErgoTree(propTree) + val commitmentBytes = l.commitmentOpt.get.bytes + w.put(leafPrefix) + w.putShortBytes(propBytes.length.toShort) + w.putBytes(propBytes) + w.putShortBytes(commitmentBytes.length.toShort) + w.putBytes(commitmentBytes) + + case c: ProofTreeConjecture => + w.put(internalNodePrefix) + w.put(c.conjectureType.id.toByte) + c match { + case unproven: CThresholdUnproven => + w.put(unproven.k.toByte) + case unchecked: CThresholdUncheckedNode => + w.put(unchecked.k.toByte) + case _ => + } + val childrenCount = c.children.length.toShort + w.putShortBytes(childrenCount) + + val cs = c.children.toArray + cfor(0)(_ < cs.length, _ + 1) { i => + val child = cs(i) + toBytes(child, w) + } } } From f43274b3ec696ab857a822bbe5a8394eb03e3b76 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 20 Mar 2021 00:38:30 +0300 Subject: [PATCH 20/52] verify-opt: fix issue with Nullable --- .../main/scala/sigmastate/SigSerializer.scala | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala index 31451845a5..2ad7239b70 100644 --- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala @@ -2,7 +2,6 @@ package sigmastate import gf2t.GF2_192_Poly import org.bouncycastle.util.BigIntegers -import scalan.Nullable import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} import sigmastate.basics.VerifierMessage.Challenge @@ -111,7 +110,7 @@ object SigSerializer { else { // Verifier step 1: Read the root challenge from the proof. val r = SigmaSerializer.startReader(proof) - val res = parseAndComputeChallenges(exp, r, Nullable.None) + val res = parseAndComputeChallenges(exp, r, null) res } } @@ -132,12 +131,12 @@ object SigSerializer { def parseAndComputeChallenges( exp: SigmaBoolean, r: SigmaByteReader, - challengeOpt: Nullable[Challenge] = Nullable.None): UncheckedSigmaTree = { + challengeOpt: Challenge = null): UncheckedSigmaTree = { // Verifier Step 2: Let e_0 be the challenge in the node here (e_0 is called "challenge" in the code) - val challenge = if (challengeOpt.isEmpty) { + val challenge = if (challengeOpt == null) { Challenge @@ r.getBytes(hashSize) } else { - challengeOpt.get + challengeOpt } exp match { @@ -156,7 +155,7 @@ object SigSerializer { val nChildren = and.children.length val children = new Array[UncheckedSigmaTree](nChildren) cfor(0)(_ < nChildren, _ + 1) { i => - children(i) = parseAndComputeChallenges(and.children(i), r, Nullable(challenge)) + children(i) = parseAndComputeChallenges(and.children(i), r, challenge) } CAndUncheckedNode(challenge, children) @@ -171,7 +170,7 @@ object SigSerializer { val xorBuf = challenge.clone() val iLastChild = nChildren - 1 cfor(0)(_ < iLastChild, _ + 1) { i => - val parsedChild = parseAndComputeChallenges(or.children(i), r, Nullable.None) + val parsedChild = parseAndComputeChallenges(or.children(i), r, null) children(i) = parsedChild Helpers.xorU(xorBuf, parsedChild.challenge) // xor it into buffer } @@ -179,7 +178,7 @@ object SigSerializer { // use the computed XOR for last child's challenge children(iLastChild) = parseAndComputeChallenges( - lastChild, r, challengeOpt = Nullable(Challenge @@ xorBuf)) + lastChild, r, challengeOpt = Challenge @@ xorBuf) COrUncheckedNode(challenge, children) @@ -195,7 +194,7 @@ object SigSerializer { val children = new Array[UncheckedSigmaTree](nChildren) cfor(0)(_ < nChildren, _ + 1) { i => val c = Challenge @@ polynomial.evaluate((i + 1).toByte).toByteArray - children(i) = parseAndComputeChallenges(t.children(i), r, Nullable(c)) + children(i) = parseAndComputeChallenges(t.children(i), r, c) } // Verifier doesn't need the polynomial anymore -- hence pass in None From 4b231ad2260570af406083075538c004a36262dc Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 22 Mar 2021 15:58:56 +0300 Subject: [PATCH 21/52] verify-opt: use Helpers.concatArrays --- .../sigmastate/interpreter/Interpreter.scala | 28 +++++++++---------- .../interpreter/ProverInterpreter.scala | 2 +- .../main/scala/sigmastate/utils/Helpers.scala | 11 ++++++-- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index f563b6b211..6590b77b5a 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -1,27 +1,27 @@ package sigmastate.interpreter -import java.util import java.lang.{Math => JMath} +import java.util -import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, rule, everywherebu} +import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{everywherebu, rule, strategy} import org.bitbucket.inkytonik.kiama.rewriting.Strategy import org.ergoplatform.validation.SigmaValidationSettings -import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, DLogInteractiveProver} +import org.ergoplatform.validation.ValidationRules._ +import scalan.util.BenchmarkUtil import scorex.util.ScorexLogging import sigmastate.SCollection.SByteArray import sigmastate.Values._ -import sigmastate.eval.{IRContext, Evaluation} -import sigmastate.lang.Terms.ValueOps +import sigmastate.basics.DLogProtocol.{DLogInteractiveProver, FirstDLogProverMessage} import sigmastate.basics._ -import sigmastate.interpreter.Interpreter.{VerificationResult, ScriptEnv, WhenSoftForkReductionResult} -import sigmastate.lang.exceptions.{InterpreterException, CostLimitException} -import sigmastate.serialization.{ValueSerializer, SigmaSerializer} +import sigmastate.eval.{Evaluation, IRContext} +import sigmastate.interpreter.Interpreter.{ScriptEnv, VerificationResult, WhenSoftForkReductionResult} +import sigmastate.lang.Terms.ValueOps +import sigmastate.lang.exceptions.{CostLimitException, InterpreterException} +import sigmastate.serialization.{SigmaSerializer, ValueSerializer} +import sigmastate.utils.Helpers +import sigmastate.utils.Helpers._ import sigmastate.utxo.DeserializeContext import sigmastate.{SType, _} -import org.ergoplatform.validation.ValidationRules._ -import scalan.{MutableLazy, Nullable} -import scalan.util.BenchmarkUtil -import sigmastate.utils.Helpers._ import scala.util.{Success, Try} @@ -289,14 +289,14 @@ trait Interpreter extends ScorexLogging { private def checkCommitments(sp: UncheckedSigmaTree, message: Array[Byte]): Boolean = { // Perform Verifier Step 4 val newRoot = computeCommitments(sp).get.asInstanceOf[UncheckedSigmaTree] - + val bytes = Helpers.concatArrays(FiatShamirTree.toBytes(newRoot), message) /** * Verifier Steps 5-6: Convert the tree to a string `s` for input to the Fiat-Shamir hash function, * using the same conversion as the prover in 7 * Accept the proof if the challenge at the root of the tree is equal to the Fiat-Shamir hash of `s` * (and, if applicable, the associated data). Reject otherwise. */ - val expectedChallenge = CryptoFunctions.hashFn(FiatShamirTree.toBytes(newRoot) ++ message) + val expectedChallenge = CryptoFunctions.hashFn(bytes) util.Arrays.equals(newRoot.challenge, expectedChallenge) } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala index 4c0d180b2f..b716a0ceef 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala @@ -91,7 +91,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor // Prover Step 8: compute the challenge for the root of the tree as the Fiat-Shamir hash of propBytes // and the message being signed. - val rootChallenge = Challenge @@ CryptoFunctions.hashFn(propBytes ++ message) + val rootChallenge = Challenge @@ CryptoFunctions.hashFn(Helpers.concatArrays(propBytes, message)) val step8 = step6.withChallenge(rootChallenge) // Prover Step 9: complete the proof by computing challenges at real nodes and additionally responses at real leaves diff --git a/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala b/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala index cb79246333..4417b4412c 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala @@ -54,11 +54,16 @@ object Helpers { result } + /** Concatenates two arrays into a new resulting array. + * All items of both arrays are copied to the result using System.arraycopy. + */ def concatArrays[T:ClassTag](arr1: Array[T], arr2: Array[T]): Array[T] = { - val length: Int = arr1.length + arr2.length + val l1 = arr1.length + val l2 = arr2.length + val length: Int = l1 + l2 val result: Array[T] = new Array[T](length) - System.arraycopy(arr1, 0, result, 0, arr1.length) - System.arraycopy(arr2, 0, result, arr1.length, arr2.length) + System.arraycopy(arr1, 0, result, 0, l1) + System.arraycopy(arr2, 0, result, l1, l2) result } From 992dc82f5a4dda21b4ead90af3ac81737b3a52e2 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 22 Mar 2021 18:40:49 +0300 Subject: [PATCH 22/52] prepare-v5.0: change ReductionResult into case class --- .../sigmastate/interpreter/Interpreter.scala | 18 ++++++------ .../PrecompiledScriptProcessor.scala | 4 +-- .../interpreter/ProverInterpreter.scala | 6 ++-- .../sigmastate/interpreter/ProverUtils.scala | 4 +-- .../sigmastate/CalcSha256Specification.scala | 2 +- .../sigmastate/CostingSpecification.scala | 2 +- .../TestingInterpreterSpecification.scala | 20 ++++++------- .../SigSerializerSpecification.scala | 2 +- .../ErgoLikeInterpreterSpecification.scala | 4 +-- .../utxo/ThresholdSpecification.scala | 28 +++++++++---------- 10 files changed, 45 insertions(+), 45 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 6590b77b5a..fba32a01ba 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -150,7 +150,7 @@ trait Interpreter extends ScorexLogging { CheckCalcFunc(IR)(calcF) val calcCtx = context.toSigmaContext(isCost = false) val res = Interpreter.calcResult(IR)(calcCtx, calcF) - SigmaDsl.toSigmaBoolean(res) -> estimatedCost + ReductionResult(SigmaDsl.toSigmaBoolean(res), estimatedCost) } } @@ -175,13 +175,13 @@ trait Interpreter extends ScorexLogging { */ def fullReduction(ergoTree: ErgoTree, context: CTX, - env: ScriptEnv): (SigmaBoolean, Long) = { + env: ScriptEnv): ReductionResult = { val prop = propositionFromErgoTree(ergoTree, context) prop match { case SigmaPropConstant(p) => val sb = SigmaDsl.toSigmaBoolean(p) val cost = SigmaBoolean.estimateCost(sb) - (sb, cost) + ReductionResult(sb, cost) case _ if !ergoTree.hasDeserialize => val r = precompiledScriptProcessor.getReducer(ergoTree, context.validationSettings) r.reduce(context) @@ -258,14 +258,14 @@ trait Interpreter extends ScorexLogging { } val contextWithCost = context.withInitCost(initCost).asInstanceOf[CTX] - val (cProp, cost) = fullReduction(ergoTree, contextWithCost, env) + val res = fullReduction(ergoTree, contextWithCost, env) - val checkingResult = cProp match { + val checkingResult = res.value match { case TrivialProp.TrueProp => true case TrivialProp.FalseProp => false - case _ => verifySignature(cProp, message, proof) + case cProp => verifySignature(cProp, message, proof) } - checkingResult -> cost + checkingResult -> res.cost }) if (outputComputedResults) { res.foreach { case (_, cost) => @@ -386,7 +386,7 @@ object Interpreter { * The first component is the value of SigmaProp type which represents a statement * verifiable via sigma protocol. * The second component is the estimated cost of consumed by the contract execution. */ - type ReductionResult = (SigmaBoolean, Long) + case class ReductionResult(value: SigmaBoolean, cost: Long) type ScriptEnv = Map[String, Any] val emptyEnv: ScriptEnv = Map.empty[String, Any] @@ -406,7 +406,7 @@ object Interpreter { /** The result of script reduction when soft-fork condition is detected by the old node, * in which case the script is reduced to the trivial true proposition and takes up 0 cost. */ - val WhenSoftForkReductionResult: ReductionResult = TrivialProp.TrueProp -> 0 + val WhenSoftForkReductionResult: ReductionResult = ReductionResult(TrivialProp.TrueProp, 0) /** Executes the given `calcF` graph in the given context. * @param IR container of the graph (see [[IRContext]]) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index d5124b5e43..dfc932c42c 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -33,7 +33,7 @@ trait ScriptReducer { /** Used as a fallback reducer when precompilation failed due to soft-fork condition. */ case object WhenSoftForkReducer extends ScriptReducer { - override def reduce(context: InterpreterContext): (Values.SigmaBoolean, Long) = { + override def reduce(context: InterpreterContext): ReductionResult = { WhenSoftForkReductionResult } } @@ -87,7 +87,7 @@ case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRC val calcF = costingRes.calcF val calcCtx = context.toSigmaContext(isCost = false) val res = Interpreter.calcResult(IR)(calcCtx, calcF) - SigmaDsl.toSigmaBoolean(res) -> estimatedCost + ReductionResult(SigmaDsl.toSigmaBoolean(res), estimatedCost) } } } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala index b716a0ceef..a7b174ed6c 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala @@ -126,10 +126,10 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor val ctxUpdInitCost = context.withInitCost(initCost).asInstanceOf[CTX] - val (reducedProp, cost) = fullReduction(ergoTree, ctxUpdInitCost, env) - val proof = generateProof(reducedProp, message, hintsBag) + val res = fullReduction(ergoTree, ctxUpdInitCost, env) + val proof = generateProof(res.value, message, hintsBag) - CostedProverResult(proof, ctxUpdInitCost.extension, cost) + CostedProverResult(proof, ctxUpdInitCost.extension, res.cost) } def generateProof(sb: SigmaBoolean, diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverUtils.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverUtils.scala index 25f8be0a65..6c9c0cf279 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverUtils.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverUtils.scala @@ -17,7 +17,7 @@ trait ProverUtils extends Interpreter { def generateCommitmentsFor(ergoTree: ErgoTree, context: CTX, generateFor: Seq[SigmaBoolean]): HintsBag = { - val reducedTree = fullReduction(ergoTree, context, Interpreter.emptyEnv)._1 + val reducedTree = fullReduction(ergoTree, context, Interpreter.emptyEnv).value generateCommitmentsFor(reducedTree, generateFor) } @@ -79,7 +79,7 @@ trait ProverUtils extends Interpreter { proof: Array[Byte], realSecretsToExtract: Seq[SigmaBoolean], simulatedSecretsToExtract: Seq[SigmaBoolean] = Seq.empty): HintsBag = { - val reducedTree = fullReduction(ergoTree, context, Interpreter.emptyEnv)._1 + val reducedTree = fullReduction(ergoTree, context, Interpreter.emptyEnv).value bagForMultisig(context, reducedTree, proof, realSecretsToExtract, simulatedSecretsToExtract) } diff --git a/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala b/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala index 7062c0153a..28a28c9d91 100644 --- a/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala +++ b/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala @@ -33,7 +33,7 @@ class CalcSha256Specification extends SigmaTestingCommons forAll(objects) { (in, result) => val expectedResult = decodeString(result) val calcSha256 = EQ(CalcSha256(stringToByteConstant(in)), expectedResult) - val res = int.reduceToCrypto(ctx, calcSha256).get._1 + val res = int.reduceToCrypto(ctx, calcSha256).get.value res shouldBe TrivialProp.TrueProp } } diff --git a/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala b/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala index ce20a30ea4..4baf488d3f 100644 --- a/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala @@ -82,7 +82,7 @@ class CostingSpecification extends SigmaTestingData with CrossVersionProps { def cost(script: String)(expCost: Int): Unit = { val ergoTree = compiler.compile(env, script) - val res = interpreter.reduceToCrypto(context, env, ergoTree).get._2 + val res = interpreter.reduceToCrypto(context, env, ergoTree).get.cost if (printCosts) println(script + s" --> cost $res") res shouldBe ((expCost * CostTable.costFactorIncrease / CostTable.costFactorDecrease) + CostTable.interpreterInitCost).toLong diff --git a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index 47f9fc457d..e50723f5f0 100644 --- a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -51,27 +51,27 @@ class TestingInterpreterSpecification extends SigmaTestingCommons val dk1 = SigmaPropConstant(DLogProverInput.random().publicImage).isProven val ctx = testingContext(h) - prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h - 1)), dk1)).get._1 should( + prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h - 1)), dk1)).get.value should( matchPattern { case _: SigmaBoolean => }) - prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h)), dk1)).get._1 should ( + prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h)), dk1)).get.value should ( matchPattern { case _: SigmaBoolean => }) { - val res = prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h + 1)), dk1)).get._1 + val res = prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h + 1)), dk1)).get.value res should matchPattern { case TrivialProp.FalseProp => } } { - val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h - 1)), dk1)).get._1 + val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h - 1)), dk1)).get.value res should matchPattern { case TrivialProp.TrueProp => } } { - val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h)), dk1)).get._1 + val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h)), dk1)).get.value res should matchPattern { case TrivialProp.TrueProp => } } { - val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h + 1)), dk1)).get._1 + val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h + 1)), dk1)).get.value res should matchPattern { case _: SigmaBoolean => } } } @@ -91,18 +91,18 @@ class TestingInterpreterSpecification extends SigmaTestingCommons assert(prover.reduceToCrypto(ctx, OR( AND(LE(Height, IntConstant(h + 1)), AND(dk1, dk2)), AND(GT(Height, IntConstant(h + 1)), dk1) - )).get._1.isInstanceOf[CAND]) + )).get.value.isInstanceOf[CAND]) assert(prover.reduceToCrypto(ctx, OR( AND(LE(Height, IntConstant(h - 1)), AND(dk1, dk2)), AND(GT(Height, IntConstant(h - 1)), dk1) - )).get._1.isInstanceOf[ProveDlog]) + )).get.value.isInstanceOf[ProveDlog]) prover.reduceToCrypto(ctx, OR( AND(LE(Height, IntConstant(h - 1)), AND(dk1, dk2)), AND(GT(Height, IntConstant(h + 1)), dk1) - )).get._1 shouldBe TrivialProp.FalseProp + )).get.value shouldBe TrivialProp.FalseProp prover.reduceToCrypto(ctx, OR( @@ -112,7 +112,7 @@ class TestingInterpreterSpecification extends SigmaTestingCommons ), AND(GT(Height, IntConstant(h - 1)), LE(Height, IntConstant(h + 1))) ) - ).get._1 shouldBe TrivialProp.TrueProp + ).get.value shouldBe TrivialProp.TrueProp } } diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index af1e2c5e3c..0e0b267863 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -89,7 +89,7 @@ class SigSerializerSpecification extends SigmaTestingCommons try { // get sigma conjectures out of transformers val tree = mkTestErgoTree(expr) - val prop = prover.fullReduction(tree, ctx, Interpreter.emptyEnv)._1 + val prop = prover.fullReduction(tree, ctx, Interpreter.emptyEnv).value val proof = prover.prove(tree, ctx, challenge).get.proof val uncheckedTree = SigSerializer.parseAndComputeChallenges(prop, proof) diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala index c53345bfd0..39d82cf304 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala @@ -45,12 +45,12 @@ class ErgoLikeInterpreterSpecification extends SigmaTestingCommons val exp = TrueLeaf e shouldBe exp - val res = verifier.reduceToCrypto(ctx, exp).get._1 + val res = verifier.reduceToCrypto(ctx, exp).get.value res shouldBe TrivialProp.TrueProp val res2 = verifier.reduceToCrypto(ctx, EQ(ByteArrayConstant(h1.treeWithSegregation(ergoTreeHeaderInTests).bytes), - ByteArrayConstant(h2.treeWithSegregation(ergoTreeHeaderInTests).bytes))).get._1 + ByteArrayConstant(h2.treeWithSegregation(ergoTreeHeaderInTests).bytes))).get.value res2 shouldBe TrivialProp.FalseProp } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala index 6443263507..8fba6f7f47 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala @@ -67,7 +67,7 @@ class ThresholdSpecification extends SigmaTestingCommons } val prop2And = CAND(Seq(pubkeyA, pubkeyB, pubkeyC)).toSigmaProp - proverA.reduceToCrypto(ctx, compiledProp2).get._1 shouldBe proverA.reduceToCrypto(ctx, prop2And).get._1 + proverA.reduceToCrypto(ctx, compiledProp2).get.value shouldBe proverA.reduceToCrypto(ctx, prop2And).get.value // this example is from the white paper val (compiledTree3, compiledProp3) = compileAndCheck(env, @@ -85,7 +85,7 @@ class ThresholdSpecification extends SigmaTestingCommons proverD.prove(compiledTree3, ctx, fakeMessage).isFailure shouldBe true val prop3Or = COR(Seq(pubkeyA, pubkeyB, pubkeyC)).toSigmaProp - proverA.reduceToCrypto(ctx, compiledProp3).get._1 shouldBe proverA.reduceToCrypto(ctx, prop3Or).get._1 + proverA.reduceToCrypto(ctx, compiledProp3).get.value shouldBe proverA.reduceToCrypto(ctx, prop3Or).get.value val (compiledTree4, _) = compileAndCheck(env, """{ @@ -118,8 +118,8 @@ class ThresholdSpecification extends SigmaTestingCommons case class TestCase(numTrue: Int, vector: Seq[SigmaPropValue], dlogOnlyVector: DlogOnlyVector) case class DlogOnlyVector(v: Seq[SigmaPropValue]) { - lazy val orVersion = prover.reduceToCrypto(ctx, SigmaOr(v)).get._1 - lazy val andVersion = prover.reduceToCrypto(ctx, SigmaAnd(v)).get._1 + lazy val orVersion = prover.reduceToCrypto(ctx, SigmaOr(v)).get.value + lazy val andVersion = prover.reduceToCrypto(ctx, SigmaAnd(v)).get.value } // Sequence of three secrets, in order to build test cases with 0, 1, 2, or 3 ProveDlogs inputs @@ -167,57 +167,57 @@ class ThresholdSpecification extends SigmaTestingCommons pReduced.mapOrThrow(_ => true) shouldBe true if (t.dlogOnlyVector.v.isEmpty) { // Case 0: no ProveDlogs in the test vector -- just booleans if (t.numTrue >= bound) { - pReduced.get._1 shouldBe TrivialProp.TrueProp + pReduced.get.value shouldBe TrivialProp.TrueProp case0TrueHit = true } else { - pReduced.get._1 shouldBe TrivialProp.FalseProp + pReduced.get.value shouldBe TrivialProp.FalseProp case0FalseHit = true } } else if (t.dlogOnlyVector.v.length == 1) { // Case 1: 1 ProveDlog in the test vector // Should be just true if numTrue>=bound if (t.numTrue >= bound) { - pReduced.get._1 shouldBe TrivialProp.TrueProp + pReduced.get.value shouldBe TrivialProp.TrueProp case1TrueHit = true } // Should be false if bound>numTrue + 1 else if (bound > t.numTrue + 1) { - pReduced.get._1 shouldBe TrivialProp.FalseProp + pReduced.get.value shouldBe TrivialProp.FalseProp case1FalseHit = true } // if bound is exactly numTrue+1, should be just dlog else if (bound == t.numTrue + 1) { - SigmaPropConstant(pReduced.get._1.asInstanceOf[ProveDlog]) shouldBe t.dlogOnlyVector.v.head + SigmaPropConstant(pReduced.get.value.asInstanceOf[ProveDlog]) shouldBe t.dlogOnlyVector.v.head case1DLogHit = true } } else { // Case 2: more than 1 ProveDlogs in the test vector // Should be just true if numTrue>=bound if (t.numTrue >= bound) { - pReduced.get._1 shouldBe TrivialProp.TrueProp + pReduced.get.value shouldBe TrivialProp.TrueProp case2TrueHit = true } // Should be false if bound>numTrue + dlogOnlyVector.length else if (bound > t.numTrue + t.dlogOnlyVector.v.length) { - pReduced.get._1 shouldBe TrivialProp.FalseProp + pReduced.get.value shouldBe TrivialProp.FalseProp case2FalseHit = true } // if bound is exactly numTrue+dlogOnlyVector, should be just AND of all dlogs else if (bound == t.numTrue + t.dlogOnlyVector.v.length) { - pReduced.get._1 shouldBe t.dlogOnlyVector.andVersion + pReduced.get.value shouldBe t.dlogOnlyVector.andVersion case2AndHit = true } // if bound is exactly numTrue+1, should be just OR of all dlogs else if (bound == t.numTrue + 1) { - pReduced.get._1 shouldBe t.dlogOnlyVector.orVersion + pReduced.get.value shouldBe t.dlogOnlyVector.orVersion case2OrHit = true } // else should be AtLeast else { val atLeastReduced = prover.reduceToCrypto(ctx, AtLeast(bound - t.numTrue, t.dlogOnlyVector.v)) - pReduced.get._1 shouldBe atLeastReduced.get._1 + pReduced.get.value shouldBe atLeastReduced.get.value case2AtLeastHit = true } } From d31e94f63c22c9cc598aba56ee494eaaacc6900c Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 29 Mar 2021 20:45:29 +0300 Subject: [PATCH 23/52] prepare-v5.0: use Evaluation.addCostChecked --- .../main/scala/sigmastate/eval/IRContext.scala | 15 ++++++++++----- .../sigmastate/interpreter/Interpreter.scala | 18 +++++------------- .../PrecompiledScriptProcessor.scala | 4 ++-- .../SoftForkabilitySpecification.scala | 2 +- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala index 3f5a6e41e5..f5b59538c2 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala @@ -97,7 +97,7 @@ trait IRContext extends Evaluation with TreeBuilding { estimatedCost } - /** TODO soft-fork: Version Based Costing + /* TODO soft-fork: Version Based Costing * The following is based on ErgoTree.header checks performed during deserialization and * described in `ErgoTreeSerializer` * The next version should ensure that v2 node contained both old and new version of casting @@ -115,6 +115,14 @@ trait IRContext extends Evaluation with TreeBuilding { * And taking into account the cleaning of the garbage and cutting the blockchain history, * the old scripts at some point will die out of the blockchain. */ + /** Increase the cost using the given `costF` graph starting from `initCost`. + * The total cost is limited by `maxCost` + * @param ctx script execution context + * @param costF graph of cost formula which predicts script execution cost in the given ctx + * @param maxCost limit on the total cost + * @param initCost initial cost, which is increased by the additional cost computed by costF + * @return new total cost if Success, or exception wrapped in Failure + */ def checkCostWithContext(ctx: SContext, costF: Ref[((Context, (Int, Size[Context]))) => Int], maxCost: Long, initCost: Long): Try[Int] = Try { val costFun = compile[(SContext, (Int, SSize[SContext])), Int, (Context, (Int, Size[Context])), Int]( @@ -127,10 +135,7 @@ trait IRContext extends Evaluation with TreeBuilding { } val scaledCost = JMath.multiplyExact(estimatedCost.toLong, CostTable.costFactorIncrease.toLong) / CostTable.costFactorDecrease - val totalCost = JMath.addExact(initCost, scaledCost) - if (totalCost > maxCost) { - throw new CostLimitException(totalCost, Evaluation.msgCostLimitError(totalCost, maxCost), None) - } + val totalCost = Evaluation.addCostChecked(initCost, scaledCost, maxCost) totalCost.toInt } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 3990369e43..3165180b17 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -57,11 +57,7 @@ trait Interpreter extends ScorexLogging { val script = ValueSerializer.deserialize(r) // Why ValueSerializer? read NOTE above val scriptComplexity = r.complexity - val currCost = JMath.addExact(context.initCost, scriptComplexity) - val remainingLimit = context.costLimit - currCost - if (remainingLimit <= 0) { - throw new CostLimitException(currCost, Evaluation.msgCostLimitError(currCost, context.costLimit), None) - } + val currCost = Evaluation.addCostChecked(context.initCost, scriptComplexity, context.costLimit) val ctx1 = context.withInitCost(currCost).asInstanceOf[CTX] (ctx1, script) } @@ -133,7 +129,7 @@ trait Interpreter extends ScorexLogging { implicit val vs = context.validationSettings val maxCost = context.costLimit val initCost = context.initCost - trySoftForkable[ReductionResult](whenSoftFork = WhenSoftForkReductionResult) { + trySoftForkable[ReductionResult](whenSoftFork = WhenSoftForkReductionResult(initCost)) { val costingRes = doCostingEx(env, exp, true) val costF = costingRes.costF IR.onCostingResult(env, exp, costingRes) @@ -251,12 +247,8 @@ trait Interpreter extends ScorexLogging { // else proceed normally } - val initCost = JMath.addExact(ergoTree.complexity.toLong, context.initCost) - val remainingLimit = context.costLimit - initCost - if (remainingLimit <= 0) { - // TODO cover with tests (2h) - throw new CostLimitException(initCost, Evaluation.msgCostLimitError(initCost, context.costLimit), None) - } + val complexityCost = ergoTree.complexity.toLong + val initCost = Evaluation.addCostChecked(context.initCost, complexityCost, context.costLimit) val contextWithCost = context.withInitCost(initCost).asInstanceOf[CTX] val res = fullReduction(ergoTree, contextWithCost, env) @@ -407,7 +399,7 @@ object Interpreter { /** The result of script reduction when soft-fork condition is detected by the old node, * in which case the script is reduced to the trivial true proposition and takes up 0 cost. */ - val WhenSoftForkReductionResult: ReductionResult = ReductionResult(TrivialProp.TrueProp, 0) + def WhenSoftForkReductionResult(cost: Long): ReductionResult = ReductionResult(TrivialProp.TrueProp, cost) /** Executes the given `calcF` graph in the given context. * @param IR container of the graph (see [[IRContext]]) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index dfc932c42c..4efa94ff54 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -34,7 +34,7 @@ trait ScriptReducer { /** Used as a fallback reducer when precompilation failed due to soft-fork condition. */ case object WhenSoftForkReducer extends ScriptReducer { override def reduce(context: InterpreterContext): ReductionResult = { - WhenSoftForkReductionResult + WhenSoftForkReductionResult(context.initCost) } } @@ -78,7 +78,7 @@ case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRC implicit val vs = context.validationSettings val maxCost = context.costLimit val initCost = context.initCost - trySoftForkable[ReductionResult](whenSoftFork = WhenSoftForkReductionResult) { + trySoftForkable[ReductionResult](whenSoftFork = WhenSoftForkReductionResult(initCost)) { val costF = costingRes.costF val costingCtx = context.toSigmaContext(isCost = true) val estimatedCost = IR.checkCostWithContext(costingCtx, costF, maxCost, initCost).getOrThrow diff --git a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala index 2574a009be..27efc5a695 100644 --- a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala @@ -391,7 +391,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA r shouldBe WhenSoftForkReducer val ctx = createContext(blockHeight, txV2, v2vs) - r.reduce(ctx) shouldBe WhenSoftForkReductionResult + r.reduce(ctx) shouldBe WhenSoftForkReductionResult(0) } override protected def afterAll(): Unit = { From ef6ff08d3a771be9e501c7a8f78b5d964ae4a125 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 5 Apr 2021 01:25:19 +0300 Subject: [PATCH 24/52] verify-opt: support parsing partially invalid signatures --- .../main/scala/sigmastate/SigSerializer.scala | 38 ++++++++++++++----- .../sigmastate/utils/SigmaByteReader.scala | 19 ++++++++++ .../SigSerializerSpecification.scala | 12 ++++++ 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala index 2ad7239b70..9970e65883 100644 --- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala @@ -1,7 +1,10 @@ package sigmastate +import com.typesafe.scalalogging.LazyLogging import gf2t.GF2_192_Poly import org.bouncycastle.util.BigIntegers +import scorex.util.encode.Base16 +import sigmastate.SigSerializer.{logger, readBytesChecked} import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} import sigmastate.basics.VerifierMessage.Challenge @@ -12,7 +15,9 @@ import sigmastate.serialization.SigmaSerializer import sigmastate.utils.{Helpers, SigmaByteReader, SigmaByteWriter} import spire.syntax.all.cfor -object SigSerializer { +object SigSerializer extends LazyLogging { + /** Log warning message using this class's logger. */ + def warn(msg: String) = logger.warn(msg) val hashSize = CryptoConstants.soundnessBits / 8 val order = CryptoConstants.groupSize @@ -115,6 +120,16 @@ object SigSerializer { } } + /** Helper method to read requested or remaining bytes from the reader. */ + def readBytesChecked(r: SigmaByteReader, numRequestedBytes: Int, onError: String => Unit): Array[Byte] = { + val bytes = r.getBytesUnsafe(numRequestedBytes) + if (bytes.length != numRequestedBytes) { + val hex = Base16.encode(r.getAllBufferBytes) + onError(hex) + } + bytes + } + /** Verifier Step 2: In a top-down traversal of the tree, obtain the challenges for the * children of every non-leaf node by reading them from the proof or computing them. * Verifier Step 3: For every leaf node, read the response z provided in the proof. @@ -134,7 +149,8 @@ object SigSerializer { challengeOpt: Challenge = null): UncheckedSigmaTree = { // Verifier Step 2: Let e_0 be the challenge in the node here (e_0 is called "challenge" in the code) val challenge = if (challengeOpt == null) { - Challenge @@ r.getBytes(hashSize) + Challenge @@ readBytesChecked(r, hashSize, + hex => warn(s"Invalid challenge in: $hex")) } else { challengeOpt } @@ -142,12 +158,14 @@ object SigSerializer { exp match { case dl: ProveDlog => // Verifier Step 3: For every leaf node, read the response z provided in the proof. - val z = BigIntegers.fromUnsignedByteArray(r.getBytes(order)) + val z_bytes = readBytesChecked(r, order, hex => warn(s"Invalid z bytes for $dl: $hex")) + val z = BigIntegers.fromUnsignedByteArray(z_bytes) UncheckedSchnorr(dl, None, challenge, SecondDLogProverMessage(z)) case dh: ProveDHTuple => // Verifier Step 3: For every leaf node, read the response z provided in the proof. - val z = BigIntegers.fromUnsignedByteArray(r.getBytes(order)) + val z_bytes = readBytesChecked(r, order, hex => warn(s"Invalid z bytes for $dh: $hex")) + val z = BigIntegers.fromUnsignedByteArray(z_bytes) UncheckedDiffieHellmanTuple(dh, None, challenge, SecondDiffieHellmanTupleProverMessage(z)) case and: CAND => @@ -182,23 +200,25 @@ object SigSerializer { COrUncheckedNode(challenge, children) - case t: CTHRESHOLD => + case th: CTHRESHOLD => // Verifier Step 2: If the node is THRESHOLD, // evaluate the polynomial Q(x) at points 1, 2, ..., n to get challenges for child 1, 2, ..., n, respectively. // Read the polynomial -- it has n-k coefficients - val nChildren = t.children.length - val coeffBytes = r.getBytes(hashSize * (nChildren - t.k)) + val nChildren = th.children.length + val nCoefs = nChildren - th.k + val coeffBytes = readBytesChecked(r, hashSize * nCoefs, + hex => warn(s"Invalid coeffBytes for $th: $hex")) val polynomial = GF2_192_Poly.fromByteArray(challenge, coeffBytes) val children = new Array[UncheckedSigmaTree](nChildren) cfor(0)(_ < nChildren, _ + 1) { i => val c = Challenge @@ polynomial.evaluate((i + 1).toByte).toByteArray - children(i) = parseAndComputeChallenges(t.children(i), r, c) + children(i) = parseAndComputeChallenges(th.children(i), r, c) } // Verifier doesn't need the polynomial anymore -- hence pass in None - CThresholdUncheckedNode(challenge, children, t.k, Some(polynomial)) + CThresholdUncheckedNode(challenge, children, th.k, Some(polynomial)) } } diff --git a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala index ee55690b3a..c3d1826742 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala @@ -96,6 +96,25 @@ class SigmaByteReader(val r: Reader, r.getBytes(size) } + /** Reads either the given of remaining number of bytes from this reader. + * This method is `unsafe` because it may return less than requested number of bytes. + * @param numRequestedBytes + */ + @inline final def getBytesUnsafe(numRequestedBytes: Int): Array[Byte] = { + checkPositionLimit() + val bytesToRead = Math.min(numRequestedBytes, remaining) + r.getBytes(bytesToRead) + } + + /** Returns all bytes of the underlying ByteBuffer. */ + private[sigmastate] def getAllBufferBytes: Array[Byte] = { + val savedPos = position + position = 0 + val res = getBytesUnsafe(remaining) + position = savedPos + res + } + @inline override def getBits(size: Int): Array[Boolean] = { checkPositionLimit() r.getBits(size) diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index af1e2c5e3c..715657779c 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -513,4 +513,16 @@ class SigSerializerSpecification extends SigmaTestingCommons hex shouldBe c.fiatShamirHex } } + + property("Invalid signature parsing") { + forAll { bytes: Array[Byte] => + val r = SigmaSerializer.startReader(bytes) + val readBytes = r.getBytesUnsafe(bytes.length + 1) // request more than present + readBytes shouldBe bytes + + // we now at the limit position of reader, and still can get all buffer bytes + val allBytes = r.getAllBufferBytes + allBytes shouldBe bytes + } + } } From 4062de825931214f0c634e2352b4d34c20b1511c Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 10 Apr 2021 23:06:15 +0300 Subject: [PATCH 25/52] test-prove-verify: removed invalid comment --- sigmastate/src/main/scala/sigmastate/SigSerializer.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala index 2045754400..eb6207dbba 100644 --- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala @@ -136,7 +136,6 @@ object SigSerializer { (s :+ rewrittenChild, p + consumed, childIndex + 1) } - // Verifier doesn't need the polynomial anymore -- hence pass in None CThresholdUncheckedNode(challenge, seq, t.k, Some(polynomial)) -> (finalPos - pos) } } From 8ae73576eaf61a3842c87979aed88527b06ad967 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 13 Apr 2021 14:19:58 +0300 Subject: [PATCH 26/52] verify-opt: cleanup --- sigmastate/src/main/scala/sigmastate/SigSerializer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala index 2b9c97d413..cbcb93f0a9 100644 --- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala @@ -141,7 +141,7 @@ object SigSerializer extends LazyLogging { * @return An instance of [[UncheckedSigmaTree]] * * HOTSPOT: don't beautify the code - * Note, Nullable is used instead of Option to avoid allocations. + * Note, `null` is used instead of Option to avoid allocations. */ def parseAndComputeChallenges( exp: SigmaBoolean, From aa8aad5b6d2f380e0975261116431aa117ddf382 Mon Sep 17 00:00:00 2001 From: pragmaxim Date: Wed, 14 Apr 2021 17:25:20 +0200 Subject: [PATCH 27/52] sigma paper fix, atomic exchange - Bob gives token1 to Alice for ergo tokens (#723) * sigma paper fix, atomic exchange - Bob gives token1 to Alice for ergo tokens * generate pdf of latest sigma paper * fix broken links of sigma.pdf * fix grammar mistake in sigma.pdf * more fixes in sigma.pdf * important difference between context vars and registers in sigma.pdf --- docs/wpaper/sigma.pdf | Bin 272236 -> 272344 bytes docs/wpaper/sigma.tex | 13 ++++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/wpaper/sigma.pdf b/docs/wpaper/sigma.pdf index 57ae3d40c138edea3113751731752107db45f7e8..7a18a944279493539fb6c703230695130696b657 100644 GIT binary patch delta 88205 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%0A4^ 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 RMtmSp0H2jrUPA%@zW`4G&`AIQ diff --git a/docs/wpaper/sigma.tex b/docs/wpaper/sigma.tex index 6c0896c013..5f71de5240 100644 --- a/docs/wpaper/sigma.tex +++ b/docs/wpaper/sigma.tex @@ -313,7 +313,7 @@ \subsection{Context Extension and Hashing} \subsection{Box Registers and Additional Tokens} \label{sec:box-registers} -Together with its value and protecting script, a box can contain up to 10 numbered registers, \texttt{R0} through \texttt{R9}. The first four of these have fixed meaning, as follows. For a box \texttt{b}, \texttt{b.R0} is the same as \texttt{b.value} and \texttt{b.R1} is the same as \texttt{b.propositionBytes}. +Context variables are passed at box spending time whereas registers at box creation time. Together with its value and protecting script, a box can contain up to 10 numbered registers, \texttt{R0} through \texttt{R9}. The first four of these have fixed meaning, as follows. For a box \texttt{b}, \texttt{b.R0} is the same as \texttt{b.value} and \texttt{b.R1} is the same as \texttt{b.propositionBytes}. The third register, \texttt{b.R2}, is for specifying additional, secondary tokens contained in the box (the primary token amount is specified in \texttt{b.value}). \texttt{b.R2} contains a collection of pairs, the first element of each pair specifying the token id (as a collection of 32 bytes) and the second element specifying the amount (as a long value). The maximum number of tokens in a box is set to 4. For every token id, the sum of amounts in inputs boxes must be no less than the sum of amounts in output boxes. There is one exception to this rule for the creation of new tokens. When a new token type gets created in a transaction, its id is equal to the id of the input box 0. Thus, the exception for the creation of new tokens is that if the token id in some output box is equal to the id of input box 0, then an arbitrary amount of this token can be output. Because each box has a unique id (see Section~\ref{sec:context}, this exception can be applied exactly once per token type. A newly created token can be emitted in a time-controlled fashion---see Section~\ref{sec:self-replicating}. @@ -329,7 +329,7 @@ \subsection{Box Registers and Additional Tokens} In addition to registers, scripts can access two serialized versions of the box: \texttt{b.bytes} is a serialization of the entire box including all its registers, and \texttt{b.bytesWithNoRef}, which the same but without the transaction identifier and the output index~(so that a box can be viewed independently of where it appeared. \paragraph{Example: atomic exchange on a single block chain} -These box registers provide additional capabilities to \langname. Consider, for example, Alice and Bob who want to exchange tokens: they agree that Alice will give Bob 60 tokens of type \texttt{token1} (this type is mapped to an actual token id in the environment map) in exchange for 100 Ergo tokens. Alice could create an output box with value 100 and protect it with the following script: +These box registers provide additional capabilities to \langname. Consider, for example, Alice and Bob who want to exchange tokens: they agree that Bob will give Alice 60 tokens of type \texttt{token1} (this type is mapped to an actual token id in the environment map) in exchange for 100 Ergo tokens. Alice could create an output box with value 100 and protect it with the following script: \begin{verbatim} (HEIGHT > deadline && pkA) || { @@ -360,7 +360,7 @@ \subsection{Box Registers and Additional Tokens} A transaction containing these two boxes as inputs must produce two outputs: the first giving at least 60 tokens of type1 to Alice and the second giving at least 100 tokens of type2 to Bob. Once the two boxes are on the blockchain, anyone can create such a transaction using the two boxes as inputs, and thus effect the exchange between Alice and Bob. Unlike the cross-chain trading example above using hashing (which requires one side to go first), there are no potential problems with synchronization here, because the exchange will happen in a single transaction or will not happen at all. \subsection{Self-Replicating Code} \label{sec:self-replicating} -Access to box registers allow us to create self-replicating boxes, because a script can check that an output box contains the same script as \texttt{SELF}. As shown in \cite{CKM18}, this powerful tool allows for Turing-completeness as computation evolves from box to box, even if each individual script is not Turing-complete. We will demonstrate two examples of complex behavior via self-replication. +Access to box registers allows us to create self-replicating boxes, because a script can check that an output box contains the same script as \texttt{SELF}. As shown in \cite{CKM18}, this powerful tool allows for Turing-completeness as computation evolves from box to box, even if each individual script is not Turing-complete. We will demonstrate two examples of complex behavior via self-replication. \paragraph{Example: time-controlled coin emission} In this example, we will create a self-replicating box that emits new coins at each time step in order to add to the total amount of currency available. This box appears as an output in the genesis block (at height 0) of the blockchain; all "new" coins come from this box or its descendants, thus maintaining the invariant that for every transaction after the genesis block, the combined value of all inputs is equal to the combined value of all outputs. @@ -407,7 +407,7 @@ \section{Implementation} \begin{itemize} \item We estimate the time required to process the script and, if it exceeds a certain bound, refuses to evaluate it in order to prevent a denial-of-service attack. -\item The evaluation converts the script not to a Boolean value, but to a $\Sigma$-statement. This statement is a tree, with \texttt{proveDlog} or \texttt{proveDHtuple} nodes for leaves, and $\andnode$ (\texttt{\&\&}), $\ornode$ (\texttt{||}), or $\tnode$ for non-leaves. The prover (when trying to use in a transaction the box that is protected by the script) generates a proof this $\Sigma$-statement. The verifier verifies it, obtaining a Boolean value. +\item The evaluation converts the script not to a Boolean value, but to a $\Sigma$-statement. This statement is a tree, with \texttt{proveDlog} or \texttt{proveDHtuple} nodes for leaves, and $\andnode$ (\texttt{\&\&}), $\ornode$ (\texttt{||}), or $\tnode$ for non-leaves. The prover (when trying to use in a transaction the box that is protected by the script) generates a proof for this $\Sigma$-statement. The verifier verifies it, obtaining a Boolean value. \end{itemize} We describe the latter non-standard step in Appendix \ref{app:crypto}, while the former will be described in a separate @@ -420,9 +420,8 @@ \section{Further Work} \begin{enumerate} \item{} More examples, including non-interactive and fully on-chain tumbler for mixing the coins, cold wallets, oracles, initial coin offering scenario, multi-state contract defined as a finite state machine, and so on. We - already have code for such the examples done, and writing documentation about them at the moment. - \item{} Detailed description of used type system, cost estimation procedure, safety guarantees, - and abstract syntax tree format. + already have code for such examples done, and writing documentation about them at the moment. + \item{} Detailed description of used type system, cost estimation procedure, safety guarantees and abstract syntax tree format. \end{enumerate} \bibliographystyle{alpha} \bibliography{sigma.bib} From 23a13b1104a7b637577b4c94daba4f0e91c23a6f Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 21 Apr 2021 11:43:24 +0300 Subject: [PATCH 28/52] prepare-v5.0: added property("derivation from private key") --- .../ErgoAddressSpecification.scala | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala b/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala index 771a8f21cb..743f95e90d 100644 --- a/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala +++ b/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala @@ -2,24 +2,26 @@ package org.ergoplatform import java.math.BigInteger -import org.ergoplatform.ErgoAddressEncoder.{hash256, MainnetNetworkPrefix, TestnetNetworkPrefix} +import org.ergoplatform.ErgoAddressEncoder.{MainnetNetworkPrefix, TestnetNetworkPrefix, hash256} import org.ergoplatform.SigmaConstants.ScriptCostLimit import org.ergoplatform.validation.{ValidationException, ValidationRules} import org.scalatest.{Assertion, TryValues} +import scorex.crypto.hash.Blake2b256 import sigmastate.basics.DLogProtocol -import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer -import sigmastate.serialization.ValueSerializer +import sigmastate.serialization.{GroupElementSerializer, ValueSerializer} import scorex.util.encode.Base58 -import sigmastate.{SigmaAnd, SType, CrossVersionProps} -import sigmastate.Values.{UnparsedErgoTree, Constant, EvaluatedValue, ByteArrayConstant, IntConstant, ErgoTree} +import sigmastate.{CrossVersionProps, SType, SigmaAnd} +import sigmastate.Values.{ByteArrayConstant, Constant, ErgoTree, EvaluatedValue, IntConstant, UnparsedErgoTree} import sigmastate.eval.IRContext import sigmastate.helpers._ import sigmastate.helpers.TestingHelpers._ +import sigmastate.interpreter.CryptoConstants.dlogGroup import sigmastate.interpreter.{ContextExtension, CostedProverResult} -import sigmastate.interpreter.Interpreter.{ScriptNameProp, ScriptEnv} +import sigmastate.interpreter.Interpreter.{ScriptEnv, ScriptNameProp} import sigmastate.lang.Terms.ValueOps -import sigmastate.lang.exceptions.{CosterException, CostLimitException} +import sigmastate.lang.exceptions.{CostLimitException, CosterException} import sigmastate.utils.Helpers._ import special.sigma.SigmaDslTesting @@ -117,6 +119,25 @@ class ErgoAddressSpecification extends SigmaDslTesting assertResult(true)(from_tree == p2pk) } + property("derivation from private key") { + val w = new BigInteger("bb2e6f44a38052b3f564fafcd477c4eb8cda1a8a553a4a5f38f1e1084d6a69f0", 16) + val g = dlogGroup.generator + val pk = dlogGroup.exponentiate(g, w) + val pkBytes = GroupElementSerializer.toBytes(pk) + val encoder = new ErgoAddressEncoder(MainnetNetworkPrefix) + val p2pk = new P2PKAddress(ProveDlog(pk), pkBytes)(encoder) + val addrStr = p2pk.toString + + val prefix = (encoder.networkPrefix + P2PKAddress.addressTypePrefix).toByte + val bytes = prefix +: pkBytes + + val checksum = Blake2b256(bytes).take(ErgoAddressEncoder.ChecksumLength) + val expectedAddrStr = Base58.encode(bytes ++ checksum) + + addrStr shouldBe expectedAddrStr + addrStr shouldBe "9iJd9drp1KR3R7HLi7YmQbB5sJ5HFKZoPb5MxGepamggJs5vDHm" + } + property("fromProposition() should properly distinct all types of addresses from script AST") { testFromProposition(scriptVersion = 0, expectedP2S = "JryiCXrZf5VDetH1PM7rKDX3q4sLF34AdErWJFqG87Hf5ikTDf636b35Nr7goWMdRUKA3ZPxdeqFNbQzBjhnDR9SUMYwDX1tdV8ZuGgXwQPaRVcB9", From 048f5de5cdb13c5c16232862886f6fefc74260dc Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Wed, 28 Apr 2021 12:49:20 +0300 Subject: [PATCH 29/52] add Sigma AND, OR test vectors; (#724) * add Sigma AND, OR test vectors; * more test vectors for nested Sigma AND, OR; --- .../crypto/SigningSpecification.scala | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala b/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala index 9c5c1ae390..9390e1b4b3 100644 --- a/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala @@ -2,7 +2,7 @@ package sigmastate.crypto import org.scalacheck.Gen import scorex.util.encode.Base16 -import sigmastate.{AtLeast, CAND} +import sigmastate.{AtLeast, CAND, COR} import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.helpers.{ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter, SigmaTestingCommons} @@ -37,6 +37,56 @@ class SigningSpecification extends SigmaTestingCommons { val sig = "invalid signature".getBytes("UTF-8") verifier.verifySignature(sigmaTree, msg, sig) shouldBe false } + + property("AND signature test vector") { + val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get + val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger) + val sk2 = DLogProverInput(BigInt("50415569076448343263191022044468203756975150511337537963383000142821297891310").bigInteger) + val signature = Base16.decode("9b2ebb226be42df67817e9c56541de061997c3ea84e7e72dbb69edb7318d7bb525f9c16ccb1adc0ede4700a046d0a4ab1e239245460c1ba45e5637f7a2d4cc4cc460e5895125be73a2ca16091db2dcf51d3028043c2b9340").get + // check that signature is correct + val verifier = new ErgoLikeTestInterpreter + val proverResult = ProverResult(signature, ContextExtension.empty) + val sigmaTree: SigmaBoolean = CAND(Seq(sk1.publicImage, sk2.publicImage)) + verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true + } + + property("OR signature test vector") { + val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get + val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger) + val sk2 = DLogProverInput(BigInt("50415569076448343263191022044468203756975150511337537963383000142821297891310").bigInteger) + val signature = Base16.decode("ec94d2d5ef0e1e638237f53fd883c339f9771941f70020742a7dc85130aaee535c61321aa1e1367befb500256567b3e6f9c7a3720baa75ba6056305d7595748a93f23f9fc0eb9c1aaabc24acc4197030834d76d3c95ede60c5b59b4b306cd787d010e8217f34677d046646778877c669").get + // check that signature is correct + val verifier = new ErgoLikeTestInterpreter + val proverResult = ProverResult(signature, ContextExtension.empty) + val sigmaTree: SigmaBoolean = COR(Seq(sk1.publicImage, sk2.publicImage)) + verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true + } + + property("AND with OR signature test vector") { + val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get + val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger) + val sk2 = DLogProverInput(BigInt("50415569076448343263191022044468203756975150511337537963383000142821297891310").bigInteger) + val sk3 = DLogProverInput(BigInt("34648336872573478681093104997365775365807654884817677358848426648354905397359").bigInteger) + val signature = Base16.decode("397e005d85c161990d0e44853fbf14951ff76e393fe1939bb48f68e852cd5af028f6c7eaaed587f6d5435891a564d8f9a77288773ce5b526a670ab0278aa4278891db53a9842df6fba69f95f6d55cfe77dd7b4bdccc1a3378ac4524b51598cb813258f64c94e98c3ef891a6eb8cbfd2e527a9038ca50b5bb50058de55a859a169628e6ae5ba4cb0332c694e450782d6f").get + // check that signature is correct + val verifier = new ErgoLikeTestInterpreter + val proverResult = ProverResult(signature, ContextExtension.empty) + val sigmaTree: SigmaBoolean = CAND(Seq(sk1.publicImage, COR(Seq(sk2.publicImage, sk3.publicImage)))) + verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true + } + + property("OR with AND signature test vector") { + val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get + val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger) + val sk2 = DLogProverInput(BigInt("50415569076448343263191022044468203756975150511337537963383000142821297891310").bigInteger) + val sk3 = DLogProverInput(BigInt("34648336872573478681093104997365775365807654884817677358848426648354905397359").bigInteger) + val signature = Base16.decode("a58b251be319a9656c21876b1136a59f42b18835dec6076c92f7a925ba28d2030218c177ab07563003eff5250cfafeb631ef610f4d710ab8e821bf632203adf23f4376580eaa17ddb36c0138f73a88551f45d92cde2b66dfbb5906c02e4d48106ff08be4a2fc29ec242f495468692f9ddeeb029dc5d8f38e2649cf09c44b67cbcfb3de4202026fb84d23ce2b4ff0f69b").get + // check that signature is correct + val verifier = new ErgoLikeTestInterpreter + val proverResult = ProverResult(signature, ContextExtension.empty) + val sigmaTree: SigmaBoolean = COR(Seq(sk1.publicImage, CAND(Seq(sk2.publicImage, sk3.publicImage)))) + verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true + } property("threshold signature test vector") { From b7416b13ccc541bf9e1873781e40ee0fb2238bc0 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 3 May 2021 01:46:27 +0300 Subject: [PATCH 30/52] prepare-v5.0: TestData moved from SigmaDslSpecification.scala --- .../special/sigma/SigmaDslSpecification.scala | 157 +---------- .../scala/special/sigma/SigmaDslTesting.scala | 1 + .../special/sigma/SigmaTestingData.scala | 245 +++++++++++++++++- 3 files changed, 244 insertions(+), 159 deletions(-) diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala index 2ba7adb6f1..bd79e76766 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala @@ -48,161 +48,6 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui implicit def IR = createIR() - object TestData { - val BigIntZero: BigInt = CBigInt(new BigInteger("0", 16)) - val BigIntOne: BigInt = CBigInt(new BigInteger("1", 16)) - val BigIntMinusOne: BigInt = CBigInt(new BigInteger("-1", 16)) - val BigInt10: BigInt = CBigInt(new BigInteger("a", 16)) - val BigInt11: BigInt = CBigInt(new BigInteger("b", 16)) - - val ge1str = "03358d53f01276211f92d0aefbd278805121d4ff6eb534b777af1ee8abae5b2056" - val ge2str = "02dba7b94b111f3894e2f9120b577da595ec7d58d488485adf73bf4e153af63575" - val ge3str = "0290449814f5671172dd696a61b8aa49aaa4c87013f56165e27d49944e98bc414d" - - val ge1 = Helpers.decodeGroupElement(ge1str) - val ge2 = Helpers.decodeGroupElement(ge2str) - val ge3 = Helpers.decodeGroupElement(ge3str) - - val t1: AvlTree = CAvlTree( - AvlTreeData( - ADDigest @@ ErgoAlgos.decodeUnsafe("000183807f66b301530120ff7fc6bd6601ff01ff7f7d2bedbbffff00187fe89094"), - AvlTreeFlags(false, true, true), - 1, - Some(1) - ) - ) - val t2: AvlTree = CAvlTree( - AvlTreeData( - ADDigest @@ ErgoAlgos.decodeUnsafe("ff000d937f80ffd731ed802d24358001ff8080ff71007f00ad37e0a7ae43fff95b"), - AvlTreeFlags(false, false, false), - 32, - Some(64) - ) - ) - val t3: AvlTree = CAvlTree( - AvlTreeData( - ADDigest @@ ErgoAlgos.decodeUnsafe("3100d2e101ff01fc047c7f6f00ff80129df69a5090012f01ffca99f5bfff0c8036"), - AvlTreeFlags(true, false, false), - 128, - None - ) - ) - - val b1: Box = CostingBox( - false, - new ErgoBox( - 9223372036854775807L, - new ErgoTree( - 16.toByte, - Array( - SigmaPropConstant( - CSigmaProp( - ProveDlog( - Helpers.decodeECPoint( - "0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e" - ) - ) - ) - ) - ), - Right(ConstantPlaceholder(0, SSigmaProp)) - ), - Coll( - (Digest32 @@ (ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001")), - 10000000L), - (Digest32 @@ (ErgoAlgos.decodeUnsafe("a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600")), - 500L) - ), - Map( - ErgoBox.R5 -> ByteArrayConstant(Helpers.decodeBytes("7fc87f7f01ff")), - ErgoBox.R4 -> FalseLeaf - ), - ModifierId @@ ("218301ae8000018008637f0021fb9e00018055486f0b514121016a00ff718080"), - 22588.toShort, - 677407 - ) - ) - - val b2: Box = CostingBox( - false, - new ErgoBox( - 12345L, - new ErgoTree( - 0.toByte, - Vector(), - Right( - BoolToSigmaProp( - AND( - ConcreteCollection( - Array( - FalseLeaf, - XorOf( - ConcreteCollection(Array(EQ(IntConstant(1), IntConstant(1)), FalseLeaf), SBoolean) - ) - ), - SBoolean - ) - ) - ) - ) - ), - Coll(), - Map( - ErgoBox.R5 -> ByteArrayConstant( - Helpers.decodeBytes( - "297000800b80f1d56c809a8c6affbed864b87f007f6f007f00ac00018c01c4fdff011088807f0100657f00f9ab0101ff6d65" - ) - ), - ErgoBox.R4 -> TrueLeaf, - ErgoBox.R7 -> LongConstant(9223372036854775807L), - ErgoBox.R6 -> LongConstant(2115927197107005906L) - ), - ModifierId @@ ("003bd5c630803cfff6c1ff7f7fb980ff136afc011f8080b8b04ad4dbda2d7f4e"), - 1.toShort, - 1000000 - ) - ) - - val preH1: PreHeader = CPreHeader( - 0.toByte, - Helpers.decodeBytes("7fff7fdd6f62018bae0001006d9ca888ff7f56ff8006573700a167f17f2c9f40"), - 6306290372572472443L, - -3683306095029417063L, - 1, - Helpers.decodeGroupElement("026930cb9972e01534918a6f6d6b8e35bc398f57140d13eb3623ea31fbd069939b"), - Helpers.decodeBytes("ff8087") - ) - - val preH2: PreHeader = preH1.asInstanceOf[CPreHeader].copy(height = 2) - - val treeData = AvlTreeData( - ADDigest @@ ( - ErgoAlgos.decodeUnsafe("010180017f7f7b7f720c00007f7f7f0f01e857a626f37f1483d06af8077a008080") - ), - AvlTreeFlags(false, true, false), - 728138553, - Some(2147483647) - ) - val h1: Header = CHeader( - Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a"), - 0.toByte, - Helpers.decodeBytes("0180dd805b0000ff5400b997fd7f0b9b00de00fb03c47e37806a8186b94f07ff"), - Helpers.decodeBytes("01f07f60d100ffb970c3007f60ff7f24d4070bb8fffa7fca7f34c10001ffe39d"), - CAvlTree(treeData), - Helpers.decodeBytes("804101ff01000080a3ffbd006ac080098df132a7017f00649311ec0e00000100"), - 1L, - -1L, - 1, - Helpers.decodeBytes("e57f80885601b8ff348e01808000bcfc767f2dd37f0d01015030ec018080bc62"), - Helpers.decodeGroupElement("039bdbfa0b49cc6bef58297a85feff45f7bbeb500a9d2283004c74fcedd4bd2904"), - Helpers.decodeGroupElement("0361299207fa392231e23666f6945ae3e867b978e021d8d702872bde454e9abe9c"), - Helpers.decodeBytes("7f4f09012a807f01"), - CBigInt(new BigInteger("-e24990c47e15ed4d0178c44f1790cc72155d516c43c3e8684e75db3800a288", 16)), - Helpers.decodeBytes("7f0180") - ) - val h2: Header = h1.asInstanceOf[CHeader].copy(height = 2) - - } import TestData._ prepareSamples[BigInt] @@ -3581,7 +3426,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui existingPropTest("ADProofsRoot", { (x: Header) => x.ADProofsRoot})) verifyCases( - Seq((h1, Expected(Success(CAvlTree(treeData)), cost = 36092))), + Seq((h1, Expected(Success(CAvlTree(createAvlTreeData())), cost = 36092))), existingPropTest("stateRoot", { (x: Header) => x.stateRoot })) verifyCases( diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala index ea5eab5e14..89ec0ab7e0 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala @@ -43,6 +43,7 @@ class SigmaDslTesting extends PropSpec with Matchers with SigmaTestingData with SigmaContractSyntax with ObjectGenerators { suite => + override def Coll[T](items: T*)(implicit cT: RType[T]): Coll[T] = super.Coll(items:_*) lazy val spec: ContractSpec = TestContractSpec(suite)(new TestingIRContext) diff --git a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala index 1ce73cdbe2..30751521ba 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala @@ -1,8 +1,11 @@ package special.sigma +import java.math.BigInteger + +import org.ergoplatform.ErgoBox import org.ergoplatform.settings.ErgoAlgos import org.scalacheck.Gen.containerOfN -import sigmastate.AvlTreeFlags +import sigmastate._ import org.scalacheck.{Arbitrary, Gen} import sigmastate.helpers.SigmaTestingCommons import sigmastate.eval._ @@ -10,13 +13,22 @@ import sigmastate.eval.Extensions._ import org.scalacheck.util.Buildable import scalan.RType import scorex.crypto.hash.{Digest32, Blake2b256} -import scorex.crypto.authds.{ADKey, ADValue} -import sigmastate.Values.ErgoTree +import scorex.crypto.authds.{ADDigest, ADKey, ADValue} +import scorex.util.ModifierId +import sigmastate.Values._ +import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.basics.ProveDHTuple +import sigmastate.interpreter.CryptoConstants.EcPointType import sigmastate.serialization.ErgoTreeSerializer import sigmastate.serialization.generators.ObjectGenerators +import sigmastate.utils.Helpers import special.collection.Coll +import scala.reflect.ClassTag + trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { + def Coll[T](items: T*)(implicit cT: RType[T]) = CostingSigmaDslBuilder.Colls.fromItems(items:_*) + def collOfN[T: RType: Arbitrary](n: Int)(implicit b: Buildable[T, Array[T]]): Gen[Coll[T]] = { implicit val g: Gen[T] = Arbitrary.arbitrary[T] containerOfN[Array, T](n, g).map(Colls.fromArray(_)) @@ -102,6 +114,233 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { votes = Colls.emptyColl[Byte] ) + object TestData { + val BigIntZero: BigInt = CBigInt(new BigInteger("0", 16)) + val BigIntOne: BigInt = CBigInt(new BigInteger("1", 16)) + val BigIntMinusOne: BigInt = CBigInt(new BigInteger("-1", 16)) + val BigInt10: BigInt = CBigInt(new BigInteger("a", 16)) + val BigInt11: BigInt = CBigInt(new BigInteger("b", 16)) + + val BigIntMaxValueStr = "7F" + "ff" * 31 + val BigIntMaxValue_instances = new CloneSet(1000, + CBigInt(new BigInteger(BigIntMaxValueStr, 16))) + + def createBigIntMaxValue(): BigInt = BigIntMaxValue_instances.getNext + + // TODO HF: this values have bitCount == 255 (see to256BitValueExact) + val BigIntMinValue = CBigInt(new BigInteger("-7F" + "ff" * 31, 16)) + val BigIntMaxValue = createBigIntMaxValue() + val BigIntOverlimit = CBigInt(new BigInteger("7F" + "ff" * 33, 16)) + + val ge1str = "03358d53f01276211f92d0aefbd278805121d4ff6eb534b777af1ee8abae5b2056" + val ge2str = "02dba7b94b111f3894e2f9120b577da595ec7d58d488485adf73bf4e153af63575" + val ge3str = "0290449814f5671172dd696a61b8aa49aaa4c87013f56165e27d49944e98bc414d" + + val ge1_bytes = ErgoAlgos.decodeUnsafe(ge1str) + + class CloneSet[T: ClassTag](val size: Int, generator: => T) { + val instances = Array.fill(size)(generator) + var currentInst: Int = 0 + + /** Selects next instance (round-robin). */ + def getNext: T = { + val res = instances(currentInst) + currentInst = (currentInst + 1) % size + res + } + } + + val ge1_instances = new CloneSet(1000, SigmaDsl.decodePoint(Colls.fromArray(ge1_bytes))) + + /** Selects next ge1 instance (round-robin). */ + def create_ge1(): GroupElement = ge1_instances.getNext + + val ge1 = create_ge1() + + val ge2_bytes = ErgoAlgos.decodeUnsafe(ge2str) + val ge2_instances = new CloneSet(1000, SigmaDsl.decodePoint(Colls.fromArray(ge2_bytes))) + def create_ge2(): GroupElement = ge2_instances.getNext + val ge2 = create_ge2() + + val ge3 = Helpers.decodeGroupElement(ge3str) + + val t1_instances = new CloneSet(1000, CAvlTree( + AvlTreeData( + ADDigest @@ ErgoAlgos.decodeUnsafe("000183807f66b301530120ff7fc6bd6601ff01ff7f7d2bedbbffff00187fe89094"), + AvlTreeFlags(false, true, true), + 1, + Some(1) + ) + )) + + def create_t1(): AvlTree = t1_instances.getNext + + val t1: AvlTree = create_t1() + val t2: AvlTree = CAvlTree( + AvlTreeData( + ADDigest @@ ErgoAlgos.decodeUnsafe("ff000d937f80ffd731ed802d24358001ff8080ff71007f00ad37e0a7ae43fff95b"), + AvlTreeFlags(false, false, false), + 32, + Some(64) + ) + ) + val t3: AvlTree = CAvlTree( + AvlTreeData( + ADDigest @@ ErgoAlgos.decodeUnsafe("3100d2e101ff01fc047c7f6f00ff80129df69a5090012f01ffca99f5bfff0c8036"), + AvlTreeFlags(true, false, false), + 128, + None + ) + ) + + val b1_instances = new CloneSet(1000, CostingBox( + false, + new ErgoBox( + 9223372036854775807L, + new ErgoTree( + 16.toByte, + Array( + SigmaPropConstant( + CSigmaProp( + ProveDlog( + Helpers.decodeECPoint( + "0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e" + ) + ) + ) + ) + ), + Right(ConstantPlaceholder(0, SSigmaProp)) + ), + Coll( + (Digest32 @@ (ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001")), + 10000000L), + (Digest32 @@ (ErgoAlgos.decodeUnsafe("a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600")), + 500L) + ), + Map( + ErgoBox.R5 -> ByteArrayConstant(Helpers.decodeBytes("7fc87f7f01ff")), + ErgoBox.R4 -> FalseLeaf + ), + ModifierId @@ ("218301ae8000018008637f0021fb9e00018055486f0b514121016a00ff718080"), + 22588.toShort, + 677407 + ) + )) + + def create_b1(): Box = b1_instances.getNext + + val b1: Box = create_b1() + + val b2: Box = CostingBox( + false, + new ErgoBox( + 12345L, + new ErgoTree( + 0.toByte, + Vector(), + Right( + BoolToSigmaProp( + AND( + ConcreteCollection( + Array( + FalseLeaf, + XorOf( + ConcreteCollection(Array(EQ(IntConstant(1), IntConstant(1)), FalseLeaf), SBoolean) + ) + ), + SBoolean + ) + ) + ) + ) + ), + Coll(), + Map( + ErgoBox.R5 -> ByteArrayConstant( + Helpers.decodeBytes( + "297000800b80f1d56c809a8c6affbed864b87f007f6f007f00ac00018c01c4fdff011088807f0100657f00f9ab0101ff6d65" + ) + ), + ErgoBox.R4 -> TrueLeaf, + ErgoBox.R7 -> LongConstant(9223372036854775807L), + ErgoBox.R6 -> LongConstant(2115927197107005906L) + ), + ModifierId @@ ("003bd5c630803cfff6c1ff7f7fb980ff136afc011f8080b8b04ad4dbda2d7f4e"), + 1.toShort, + 1000000 + ) + ) + + val preH1_instances = new CloneSet(1000, CPreHeader( + 0.toByte, + Helpers.decodeBytes("7fff7fdd6f62018bae0001006d9ca888ff7f56ff8006573700a167f17f2c9f40"), + 6306290372572472443L, + -3683306095029417063L, + 1, + Helpers.decodeGroupElement("026930cb9972e01534918a6f6d6b8e35bc398f57140d13eb3623ea31fbd069939b"), + Helpers.decodeBytes("ff8087") + )) + + def create_preH1(): PreHeader = preH1_instances.getNext + + val preH1: PreHeader = create_preH1() + + val preH2: PreHeader = create_preH1().asInstanceOf[CPreHeader].copy(height = 2) + + def createAvlTreeData() = AvlTreeData( + ADDigest @@ ( + ErgoAlgos.decodeUnsafe("010180017f7f7b7f720c00007f7f7f0f01e857a626f37f1483d06af8077a008080") + ), + AvlTreeFlags(false, true, false), + 728138553, + Some(2147483647) + ) + + val h1_instances = new CloneSet(1000, CHeader( + Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a"), + 0.toByte, + Helpers.decodeBytes("0180dd805b0000ff5400b997fd7f0b9b00de00fb03c47e37806a8186b94f07ff"), + Helpers.decodeBytes("01f07f60d100ffb970c3007f60ff7f24d4070bb8fffa7fca7f34c10001ffe39d"), + CAvlTree(createAvlTreeData()), + Helpers.decodeBytes("804101ff01000080a3ffbd006ac080098df132a7017f00649311ec0e00000100"), + 1L, + -1L, + 1, + Helpers.decodeBytes("e57f80885601b8ff348e01808000bcfc767f2dd37f0d01015030ec018080bc62"), + Helpers.decodeGroupElement("039bdbfa0b49cc6bef58297a85feff45f7bbeb500a9d2283004c74fcedd4bd2904"), + Helpers.decodeGroupElement("0361299207fa392231e23666f6945ae3e867b978e021d8d702872bde454e9abe9c"), + Helpers.decodeBytes("7f4f09012a807f01"), + CBigInt(new BigInteger("-e24990c47e15ed4d0178c44f1790cc72155d516c43c3e8684e75db3800a288", 16)), + Helpers.decodeBytes("7f0180") + )) + + def create_h1(): Header = h1_instances.getNext + + val h1: Header = create_h1() + + val h2: Header = create_h1().asInstanceOf[CHeader].copy(height = 2) + + val dlog_instances = new CloneSet(1000, ProveDlog( + SigmaDsl.toECPoint(create_ge1()).asInstanceOf[EcPointType] + )) + + def create_dlog(): ProveDlog = dlog_instances.getNext + + val dht_instances = new CloneSet(1000, ProveDHTuple( + create_dlog().value, + SigmaDsl.toECPoint(create_ge2()).asInstanceOf[EcPointType], + create_dlog().value, + SigmaDsl.toECPoint(create_ge2()).asInstanceOf[EcPointType] + )) + + def create_dht(): ProveDHTuple = dht_instances.getNext + + def create_and() = CAND(Array(create_dlog(), create_dht())) + + def create_or() = COR(Array(create_dlog(), create_dht())) + } + /** A list of ErgoTree hexes which are the most often used in Ergo mainnet. */ val predefScriptHexes = Seq( "0008cd03a40b2249d6a9cc7eedf21188842acef44f3df110a58a687ba3e28cbc2e97ead2", From 0fc93ea0e3c5dfb6f2007fe9fcba32290b753200 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 3 May 2021 01:48:16 +0300 Subject: [PATCH 31/52] prepare-v5.0: isValueOfType method added + cleanup --- .../src/main/scala/sigmastate/types.scala | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index a65cab2988..bbfe43962f 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -9,6 +9,7 @@ import scalan.RType.GeneralType import sigmastate.SType.{TypeCode, AnyOps} import sigmastate.interpreter.CryptoConstants import sigmastate.utils.Overloading.Overload1 +import sigmastate.utils.SparseArrayContainer import scalan.util.Extensions._ import sigmastate.Values._ import sigmastate.lang.Terms._ @@ -111,7 +112,8 @@ object SType { implicit val typeSigmaProp = SSigmaProp implicit val typeBox = SBox - implicit def typeCollection[V <: SType](implicit tV: V): SCollection[V] = SCollection[V] + /** Costructs a collection type with the given type of elements. */ + implicit def typeCollection[V <: SType](implicit tV: V): SCollection[V] = SCollection[V](tV) implicit val SigmaBooleanRType: RType[SigmaBoolean] = RType.fromClassTag(classTag[SigmaBoolean]) implicit val ErgoBoxRType: RType[ErgoBox] = RType.fromClassTag(classTag[ErgoBox]) @@ -165,8 +167,6 @@ object SType { SGlobal, SHeader, SPreHeader, SAvlTree, SGroupElement, SSigmaProp, SString, SBox, SUnit, SAny) - val typeCodeToType = allPredefTypes.map(t => t.typeCode -> t).toMap - /** A mapping of object types supporting MethodCall operations. For each serialized * typeId this map contains a companion object which can be used to access the list of * corresponding methods. @@ -185,7 +185,7 @@ object SType { * should be changed and SGlobal.typeId should be preserved. The regression tests in * `property("MethodCall Codes")` should pass. */ - // TODO v5.0 (h4): should contain all numeric types (including also SNumericType) + // TODO v6.0 (h4): should contain all numeric types (including also SNumericType) // to support method calls like 10.toByte which encoded as MethodCall with typeId = 4, methodId = 1 // see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/667 lazy val types: Map[Byte, STypeCompanion] = Seq( @@ -193,6 +193,44 @@ object SType { SAvlTree, SBox, SOption, SCollection, SBigInt ).map { t => (t.typeId, t) }.toMap + /** Checks that the type of the value corresponds to the descriptor `tpe`. + * If the value has complex structure only root type constructor is checked. + * NOTE, this method is used in ErgoTree evaluation to systematically check that each + * tree node evaluates to a value of the expected type. + * Shallow runtime checks are enough if: + * 1) ErgoTree is well-typed, i.e. each sub-expression has correct types (agree with + * the argument type). + * 2) `isValueOfType == true` for each tree leaf + * 3) `isValueOfType == true` for each sub-expression + * + * @param value value to check type + * @param tpe type descriptor to check value against + * @return true if the given `value` is of type tpe` + */ + def isValueOfType[T <: SType](x: Any, tpe: T): Boolean = tpe match { + case SBoolean => x.isInstanceOf[Boolean] + case SByte => x.isInstanceOf[Byte] + case SShort => x.isInstanceOf[Short] + case SInt => x.isInstanceOf[Int] + case SLong => x.isInstanceOf[Long] + case SBigInt => x.isInstanceOf[BigInt] + case SGroupElement => x.isInstanceOf[GroupElement] + case SSigmaProp => x.isInstanceOf[SigmaProp] + case SBox => x.isInstanceOf[Box] + case _: SCollectionType[_] => x.isInstanceOf[Coll[_]] + case _: SOption[_] => x.isInstanceOf[Option[_]] + case t: STuple => + if (t.items.length == 2) x.isInstanceOf[Tuple2[_,_]] + else sys.error(s"Unsupported tuple type $t") + case SContext => x.isInstanceOf[Context] + case SAvlTree => x.isInstanceOf[AvlTree] + case SGlobal => x.isInstanceOf[SigmaDslBuilder] + case SHeader => x.isInstanceOf[Header] + case SPreHeader => x.isInstanceOf[PreHeader] + case SUnit => x.isInstanceOf[Unit] + case _ => sys.error(s"Unknown type $tpe") + } + implicit class STypeOps(val tpe: SType) extends AnyVal { def isCollectionLike: Boolean = tpe.isInstanceOf[SCollection[_]] def isCollection: Boolean = tpe.isInstanceOf[SCollectionType[_]] @@ -203,15 +241,6 @@ object SType { def isAvlTree: Boolean = tpe.isInstanceOf[SAvlTree.type] def isFunc : Boolean = tpe.isInstanceOf[SFunc] def isTuple: Boolean = tpe.isInstanceOf[STuple] - def canBeTypedAs(expected: SType): Boolean = (tpe, expected) match { - case (NoType, _) => true - case (t1, t2) if t1 == t2 => true - case (f1: SFunc, f2: SFunc) => - val okDom = f1.tDom.size == f2.tDom.size && - f1.tDom.zip(f2.tDom).forall { case (d1, d2) => d1.canBeTypedAs(d2) } - val okRange = f1.tRange.canBeTypedAs(f2.tRange) - okDom && okRange - } /** Returns true if this type is numeric (Byte, Short, etc.) * @see [[sigmastate.SNumericType]] From b26cd98d0ada81fc3bab1e369de81645cf79f2e7 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 3 May 2021 01:49:30 +0300 Subject: [PATCH 32/52] prepare-v5.0: benchmark for isValueOfType method --- .../src/main/scala/sigmastate/Values.scala | 2 +- .../scala/sigmastate/ErgoTreeBenchmarks.scala | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 1b5c5ddfa9..d99ade3a53 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -173,7 +173,7 @@ object Values { trait Constant[+S <: SType] extends EvaluatedValue[S] {} case class ConstantNode[S <: SType](value: S#WrappedType, tpe: S) extends Constant[S] { - assert(Constant.isCorrectType(value, tpe), s"Invalid type of constant value $value, expected type $tpe") + require(Constant.isCorrectType(value, tpe), s"Invalid type of constant value $value, expected type $tpe") override def companion: ValueCompanion = Constant override def opCode: OpCode = companion.opCode override def opName: String = s"Const" diff --git a/sigmastate/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala b/sigmastate/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala index 5155e1d117..d22d6aa4cf 100644 --- a/sigmastate/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala +++ b/sigmastate/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala @@ -2,7 +2,7 @@ package sigmastate import special.collections.BenchmarkGens import org.scalameter.api.Bench -import sigmastate.Values.{SValue, IntConstant} +import sigmastate.Values.{SValue, IntConstant, Constant} import sigmastate.serialization.OpCodes.PlusCode import spire.syntax.all.cfor @@ -43,4 +43,23 @@ object ErgoTreeBenchmarks extends Bench.LocalTime with BenchmarkGens { suite: Be } } } + + performance of "SType" in { + measure method "isValueOfType" in { + using(sizes) in { size => + cfor(0)(_ < size, _ + 1) { i => + SType.isValueOfType(i, SType.allPredefTypes(i % 10)) + } + } + } + } + performance of "Constant" in { + measure method "isCorrectType" in { + using(sizes) in { size => + cfor(0)(_ < size, _ + 1) { i => + Constant.isCorrectType(i, SType.allPredefTypes(i % 10)) + } + } + } + } } From 5613013e9ed1f7db96a8f49dd96597a944968a1e Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 3 May 2021 01:50:02 +0300 Subject: [PATCH 33/52] prepare-v5.0: test vectors for isValueOfType method --- .../scala/sigmastate/TypesSpecification.scala | 95 ++++++++++++++++++- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala b/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala index afc3a75eb4..b6aff0d53e 100644 --- a/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala @@ -9,17 +9,15 @@ import scorex.crypto.hash.Digest32 import sigmastate.basics.DLogProtocol import sigmastate.interpreter.CryptoConstants import special.sigma.SigmaTestingData -import sigmastate.SType.AnyOps +import sigmastate.SType.{isValueOfType, AnyOps} import sigmastate.Values.{ShortConstant, LongConstant, BigIntConstant, AvlTreeConstant, IntConstant, ByteConstant} -import sigmastate.eval.SigmaDsl +import sigmastate.eval.{CSigmaProp, SigmaDsl, CostingSigmaDslBuilder} import special.collection.Coll import special.sigma.BigInt import sigmastate.helpers.TestingHelpers._ class TypesSpecification extends SigmaTestingData { - def Coll[T](items: T*)(implicit cT: RType[T]) = SigmaDsl.Colls.fromItems(items:_*) - def check(tpe: SType, v: Any, exp: Long) = tpe.dataSize(v.asWrappedType) shouldBe exp @@ -81,4 +79,93 @@ class TypesSpecification extends SigmaTestingData { assertExceptionThrown(check(SContext, null, 0), assertError) } + property("SType.isValueOfType") { + forAll { t: SPredefType => + implicit val tWrapped = wrappedTypeGen(t) + forAll { x: SPredefType#WrappedType => + isValueOfType(x, t) shouldBe true + // since forall t. SHeader != t + isValueOfType(x, SHeader) shouldBe false + } + } + } + + import TestData._ + + property("SType.isValueOfType test vectors") { + def assertTrue(x: Any, t: SType) = { + isValueOfType(x, t) shouldBe true + } + def assertFalse(x: Any, t: SType) = { + isValueOfType(x, t) shouldBe false + } + + assertTrue(true, SBoolean) + assertTrue(false, SBoolean) + assertFalse(true, SByte) + + assertTrue(0.toByte, SByte) + assertFalse(0.toByte, SShort) + + assertTrue(0.toShort, SShort) + assertFalse(0.toShort, SInt) + + assertTrue(0, SInt) + assertFalse(0, SShort) + + assertTrue(0L, SLong) + assertFalse(0L, SShort) + + assertTrue(BigIntZero, SBigInt) + assertFalse(BigIntZero, SShort) + + assertTrue(ge1, SGroupElement) + assertFalse(ge1, SShort) + + assertTrue(CSigmaProp(create_dlog()), SSigmaProp) + assertFalse(CSigmaProp(create_dlog()), SShort) + + assertTrue(b1, SBox) + assertFalse(b1, SShort) + + val coll = Coll[Int](1, 2) + assertTrue(coll, SCollection(SInt)) + assertFalse(coll, SShort) + assertTrue(Coll[Long](1L), SCollection(SInt)) // because type check is shallow + + assertTrue(None, SOption(SInt)) + assertTrue(Some(10), SOption(SInt)) + assertTrue(Some(10), SOption(SLong)) // because type check is shallow + assertFalse(None, SShort) + assertFalse(Some(10), SShort) + + val ctx = fakeContext.toSigmaContext(false) + assertTrue(ctx, SContext) + assertFalse(ctx, SShort) + + assertTrue(t1, SAvlTree) + assertFalse(t1, SShort) + + assertTrue(CostingSigmaDslBuilder, SGlobal) + assertFalse(CostingSigmaDslBuilder, SShort) + + assertTrue(h1, SHeader) + assertFalse(h1, SShort) + + assertTrue(preH1, SPreHeader) + assertFalse(preH1, SShort) + + assertTrue((1, 1L), STuple(SInt, SLong)) + assertTrue((1, 1L), STuple(SInt, SInt)) // because type check is shallow + assertTrue((1, Some(1)), STuple(SInt, SOption(SLong))) // because type check is shallow + assertExceptionThrown( + assertTrue((1, 1L, Some(1)), STuple(SInt, SLong, SOption(SInt))), + exceptionLike[RuntimeException](s"Unsupported tuple type") + ) + + assertExceptionThrown( + assertTrue("", SString), + exceptionLike[RuntimeException](s"Unknown type") + ) + } } From d7c920bc07a68d09040bbb813d7d2cf2fec515d4 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 3 May 2021 12:40:56 +0300 Subject: [PATCH 34/52] prepare-v5.0: check SFunc in isValueOfType method --- sigmastate/src/main/scala/sigmastate/types.scala | 3 +++ .../src/test/scala/sigmastate/TypesSpecification.scala | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index bbfe43962f..c3226e4827 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -222,6 +222,9 @@ object SType { case t: STuple => if (t.items.length == 2) x.isInstanceOf[Tuple2[_,_]] else sys.error(s"Unsupported tuple type $t") + case tF: SFunc => + if (tF.tDom.length == 1) x.isInstanceOf[Function1[_,_]] + else sys.error(s"Unsupported function type $tF") case SContext => x.isInstanceOf[Context] case SAvlTree => x.isInstanceOf[AvlTree] case SGlobal => x.isInstanceOf[SigmaDslBuilder] diff --git a/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala b/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala index b6aff0d53e..c7fcb9d6b0 100644 --- a/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/TypesSpecification.scala @@ -163,6 +163,14 @@ class TypesSpecification extends SigmaTestingData { exceptionLike[RuntimeException](s"Unsupported tuple type") ) + assertTrue((x: Any) => x, SFunc(SInt, SLong)) // note, arg and result types not checked + assertFalse((x: Any) => x, STuple(SInt, SLong)) + assertFalse(1, SFunc(SInt, SLong)) + assertExceptionThrown( + assertTrue((x: Any) => x, SFunc(Array(SInt, SLong), SOption(SInt))), + exceptionLike[RuntimeException](s"Unsupported function type") + ) + assertExceptionThrown( assertTrue("", SString), exceptionLike[RuntimeException](s"Unknown type") From 1e82befbd5b518d8ba7b3670af4a2e1399d670aa Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 3 May 2021 23:45:27 +0300 Subject: [PATCH 35/52] prepare-v5.0: optimized constraints checking in DeserializationSigmaBuilder --- .../scala/sigmastate/lang/SigmaBuilder.scala | 57 ++++++++++++------- .../src/main/scala/sigmastate/types.scala | 15 ++++- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala index a322718a17..ce352fb708 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala @@ -4,10 +4,10 @@ import java.math.BigInteger import org.ergoplatform.ErgoBox import org.ergoplatform.ErgoBox.RegisterId -import sigmastate.SCollection.{SByteArray, SIntArray} +import sigmastate.SCollection.{SIntArray, SByteArray} import sigmastate.Values._ import sigmastate._ -import sigmastate.lang.Constraints.{TypeConstraint2, onlyNumeric2, sameType2} +import sigmastate.lang.Constraints._ import sigmastate.lang.Terms._ import sigmastate.lang.exceptions.ConstraintFailed import sigmastate.serialization.OpCodes @@ -19,7 +19,7 @@ import sigmastate.interpreter.CryptoConstants.EcPointType import special.collection.Coll import sigmastate.lang.SigmaTyper.STypeSubst import sigmastate.serialization.OpCodes.OpCode -import special.sigma.{AvlTree, GroupElement, SigmaProp} +import special.sigma.{AvlTree, SigmaProp, GroupElement} import spire.syntax.all.cfor import scala.util.DynamicVariable @@ -679,28 +679,32 @@ trait TransformingSigmaBuilder extends StdSigmaBuilder with TypeConstraintCheck (left, right) } + /** HOTSPOT: don't beautify the code. */ override protected def equalityOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - val (l, r) = applyUpcast(left, right) - check2(l, r, Array(sameType2)) + val t = applyUpcast(left, right) + val l = t._1; val r = t._2 + check2(l, r, SameTypeConstrain) cons(l, r) } + /** HOTSPOT: don't beautify the code. */ override protected def comparisonOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - check2(left, right, Array(onlyNumeric2)) - val (l, r) = applyUpcast(left, right) - check2(l, r, Array(sameType2)) + check2(left, right, OnlyNumericConstrain) + val t = applyUpcast(left, right) + val l = t._1; val r = t._2 + check2(l, r, SameTypeConstrain) cons(l, r) } override protected def arithOp[T <: SNumericType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - val (l, r) = applyUpcast(left, right) - cons(l, r) + val t = applyUpcast(left, right) + cons(t._1, t._2) } } @@ -709,21 +713,21 @@ trait CheckingSigmaBuilder extends StdSigmaBuilder with TypeConstraintCheck { override protected def equalityOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - check2(left, right, Array(sameType2)) + check2(left, right, SameTypeConstrain) cons(left, right) } override protected def comparisonOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - check2(left, right, Array(onlyNumeric2, sameType2)) + check2(left, right, OnlyNumericAndSameTypeConstrain) cons(left, right) } override protected def arithOp[T <: SNumericType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { - check2(left, right, Array(sameType2)) + check2(left, right, SameTypeConstrain) cons(left, right) } } @@ -734,21 +738,30 @@ case object CheckingSigmaBuilder extends StdSigmaBuilder with CheckingSigmaBuild case object DefaultSigmaBuilder extends StdSigmaBuilder with CheckingSigmaBuilder case object TransformingSigmaBuilder extends StdSigmaBuilder with TransformingSigmaBuilder + +/** Builder of ErgoTree nodes which is used in deserializers. */ case object DeserializationSigmaBuilder extends StdSigmaBuilder with TransformingSigmaBuilder object Constraints { - type Constraint2 = (SType.TypeCode, SType.TypeCode) => Boolean - type TypeConstraint2 = (SType, SType) => Boolean - type ConstraintN = Seq[SType.TypeCode] => Boolean + abstract class TypeConstraint2 { + def apply(t1: SType, t2: SType): Boolean + } - def onlyNumeric2: TypeConstraint2 = { - case (_: SNumericType, _: SNumericType) => true - case _ => false + object OnlyNumeric2 extends TypeConstraint2 { + override def apply(t1: SType, t2: SType): Boolean = t1 match { + case _: SNumericType => t2 match { + case _: SNumericType => true + case _ => false + } + case _ => false + } } - def sameType2: TypeConstraint2 = { - case (v1, v2) => v1.tpe == v2.tpe + object SameType2 extends TypeConstraint2 { + override def apply(t1: SType, t2: SType): Boolean = t1 == t2 } - def sameTypeN: ConstraintN = { tcs => tcs.tail.forall(_ == tcs.head) } + val SameTypeConstrain: Seq[TypeConstraint2] = Array(SameType2) + val OnlyNumericConstrain: Seq[TypeConstraint2] = Array(OnlyNumeric2) + val OnlyNumericAndSameTypeConstrain: Seq[TypeConstraint2] = Array(OnlyNumeric2, SameType2) } diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index c3226e4827..d30812d1d7 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -610,15 +610,18 @@ trait SNumericType extends SProduct { /** Returns a type which is larger. */ @inline def max(that: SNumericType): SNumericType = - if (this.typeIndex > that.typeIndex) this else that + if (this.numericTypeIndex > that.numericTypeIndex) this else that - /** Number of bytes to store values of this type. */ - @inline private def typeIndex: Int = allNumericTypes.indexOf(this) + /** Numeric types are ordered by the number of bytes to store the numeric values. + * @return index in the array of all numeric types. */ + @inline protected def numericTypeIndex: Int override def toString: Idn = this.getClass.getSimpleName } object SNumericType extends STypeCompanion { + /** Array of all numeric types ordered by number of bytes in the representation. */ final val allNumericTypes = Array(SByte, SShort, SInt, SLong, SBigInt) + // TODO HF (4h): this typeId is now shadowed by SGlobal.typeId // see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/667 def typeId: TypeCode = 106: Byte @@ -704,6 +707,7 @@ case object SByte extends SPrimType with SEmbeddable with SNumericType with SMon override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 1 override def isConstantSize = true + override protected def numericTypeIndex: Int = 0 override def upcast(v: AnyVal): Byte = v match { case b: Byte => b case _ => sys.error(s"Cannot upcast value $v to the type $this") @@ -723,6 +727,7 @@ case object SShort extends SPrimType with SEmbeddable with SNumericType with SMo override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 2 override def isConstantSize = true + override protected def numericTypeIndex: Int = 1 override def upcast(v: AnyVal): Short = v match { case x: Byte => x.toShort case x: Short => x @@ -742,6 +747,7 @@ case object SInt extends SPrimType with SEmbeddable with SNumericType with SMono override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 4 override def isConstantSize = true + override protected def numericTypeIndex: Int = 2 override def upcast(v: AnyVal): Int = v match { case x: Byte => x.toInt case x: Short => x.toInt @@ -763,6 +769,7 @@ case object SLong extends SPrimType with SEmbeddable with SNumericType with SMon override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 8 override def isConstantSize = true + override protected def numericTypeIndex: Int = 3 override def upcast(v: AnyVal): Long = v match { case x: Byte => x.toLong case x: Short => x.toLong @@ -799,6 +806,8 @@ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SM val Max: BigInt = SigmaDsl.BigInt(CryptoConstants.dlogGroup.order) + override protected def numericTypeIndex: Int = 4 + override def upcast(v: AnyVal): BigInt = { val bi = v match { case x: Byte => BigInteger.valueOf(x.toLong) From fdddaee2d4f3e16794b53fc70c16c45ba681da9b Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 11 May 2021 02:01:59 +0300 Subject: [PATCH 36/52] prepare-v5.0: documenting and refactoring SigmaBuilder --- .../src/main/scala/sigmastate/Values.scala | 2 +- .../scala/sigmastate/eval/Extensions.scala | 8 +- .../sigmastate/interpreter/Interpreter.scala | 13 +- .../scala/sigmastate/lang/SigmaBuilder.scala | 111 +++++++++++++----- .../scala/sigmastate/lang/SigmaParser.scala | 1 - .../main/scala/sigmastate/sigmastate.scala | 4 +- .../src/main/scala/sigmastate/types.scala | 4 +- ...UpcastOnDeserializationSpecification.scala | 4 +- 8 files changed, 103 insertions(+), 44 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index d99ade3a53..86512a5e13 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -28,7 +28,7 @@ import spire.syntax.all.cfor import scala.language.implicitConversions import scala.reflect.ClassTag -import sigmastate.lang.DefaultSigmaBuilder._ +import sigmastate.lang.CheckingSigmaBuilder._ import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.serialization.transformers.ProveDHTupleSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} diff --git a/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala b/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala index 69b8f11f7a..9f0848ecd6 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala @@ -3,12 +3,12 @@ package sigmastate.eval import java.math.BigInteger import scalan.RType -import sigmastate.{SCollection, SType, SCollectionType} +import sigmastate.{SCollectionType, SCollection, SType} import sigmastate.Values.{Constant, ConstantNode} -import sigmastate.lang.DefaultSigmaBuilder +import sigmastate.lang.CheckingSigmaBuilder import special.collection.Coll import special.sigma._ -import SType.AnyOps +import sigmastate.SType.AnyOps import org.ergoplatform.ErgoBox import spire.syntax.all._ @@ -68,7 +68,7 @@ object Extensions { implicit class DslDataOps[A](data: A)(implicit tA: RType[A]) { def toTreeData: Constant[SType] = { - DefaultSigmaBuilder.mkConstant(data.asWrappedType, Evaluation.rtypeToSType(tA)) + CheckingSigmaBuilder.mkConstant(data.asWrappedType, Evaluation.rtypeToSType(tA)) } } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 3165180b17..bdb379f72f 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -376,13 +376,19 @@ object Interpreter { type VerificationResult = (Boolean, Long) /** Result of ErgoTree reduction procedure (see `reduceToCrypto` and friends). - * The first component is the value of SigmaProp type which represents a statement - * verifiable via sigma protocol. - * The second component is the estimated cost of consumed by the contract execution. */ + * + * @param value the value of SigmaProp type which represents a logical statement + * verifiable via sigma protocol. + * @param cost the estimated cost of the contract execution. */ case class ReductionResult(value: SigmaBoolean, cost: Long) + /** Represents properties of interpreter invocation. */ type ScriptEnv = Map[String, Any] + + /** Empty interpreter properties. */ val emptyEnv: ScriptEnv = Map.empty[String, Any] + + /** Property name used to store script name. */ val ScriptNameProp = "ScriptName" /** Maximum version of ErgoTree supported by this interpreter release. @@ -442,6 +448,7 @@ object Interpreter { throw new Error(s"Context-dependent pre-processing should produce tree of type Boolean or SigmaProp but was $x") } + /** Helper method to throw errors from Interpreter. */ def error(msg: String) = throw new InterpreterException(msg) } \ No newline at end of file diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala index ce352fb708..5b220109f8 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala @@ -24,8 +24,15 @@ import spire.syntax.all.cfor import scala.util.DynamicVariable -trait SigmaBuilder { - +/** Abstract interface of ErgoTree node builders. + * Each method of the interface creates the corresponding ErgoTree node. + * The signatures of the methods reflect the constructors of the nodes. + * See the corresponding node classes for details. + */ +abstract class SigmaBuilder { + + /** Dynamic variable used to pass SourceContext to the constructors of the node. + * Used in concrete implementations of this interface. */ val currentSrcCtx = new DynamicVariable[Nullable[SourceContext]](Nullable.None) def mkEQ[T <: SType](left: Value[T], right: Value[T]): Value[SBoolean.type] @@ -259,27 +266,69 @@ trait SigmaBuilder { case _ => Nullable.None } - - def unliftAny(value: SValue): Nullable[Any] = value match { - case Constant(v, _) => Nullable(v) - case _ => Nullable.None - } } +/** Standard implementation of [[SigmaBuilder]] interface in which most of the operations + * delegate common logic to [[equalityOp]], [[comparisonOp]] and [[arithOp]] with default + * implementation. + * Note, each method of this class uses current value of `currentSrcCtx` dynamic variable + * to attach SourceContext to the created node. Thus, it is a responsibility of the + * caller to provide valid value of the `currentSrcCtx` variable. (See for example how + * this variable is used in [[SigmaParser]].) + */ class StdSigmaBuilder extends SigmaBuilder { + /** Create equality operation using given operation arguments and the given node + * constructor. + * @param left operand of the operation (left sub-expression) + * @param right operand of the operation (right sub-expression) + * @param cons constructor of the node + */ protected def equalityOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = cons(left, right) + /** Create comparison operation using given operation arguments and the given node + * constructor. + * @param left operand of the operation (left sub-expression) + * @param right operand of the operation (right sub-expression) + * @param cons constructor of the node + */ protected def comparisonOp[T <: SType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = cons(left, right) + /** Create arithmetic operation using given operation arguments and the given node + * constructor. + * @param left operand of the operation (left sub-expression) + * @param right operand of the operation (right sub-expression) + * @param cons constructor of the node + */ protected def arithOp[T <: SNumericType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = cons(left, right) + /** Helper method to check constraints on the arguments of the binary operation. + * + * @param left operand of the operation (left sub-expression) + * @param right operand of the operation (right sub-expression) + * @param constraints an array of constraints (should be WrappedArray (not List) for + * performance) + * + * HOTSPOT: called during script deserialization (don't beautify this code) + * @consensus + */ + final def check2[T <: SType](left: Value[T], + right: Value[T], + constraints: Seq[TypeConstraint2]): Unit = { + val n = constraints.length + cfor(0)(_ < n, _ + 1) { i => + val c = constraints(i) // to be efficient constraints should be WrappedArray (not List) + if (!c(left.tpe, right.tpe)) + throw new ConstraintFailed(s"Failed constraint $c for binary operation parameters ($left(tpe: ${left.tpe}), $right(tpe: ${right.tpe}))") + } + } + override def mkEQ[T <: SType](left: Value[T], right: Value[T]): Value[SBoolean.type] = equalityOp(left, right, EQ.apply[T]).withSrcCtx(currentSrcCtx.value) @@ -649,25 +698,18 @@ class StdSigmaBuilder extends SigmaBuilder { override def mkUnitConstant: Value[SUnit.type] = UnitConstant().withSrcCtx(currentSrcCtx.value) } -trait TypeConstraintCheck { - - /** HOTSPOT: called during script deserialization (don't beautify this code) - * @consensus +/** Builder which does automatic upcast of numeric arguments when necessary. + * The upcast is implemented by inserting additional Upcast nodes. + * It also performs checking of constrains. + * */ +class TransformingSigmaBuilder extends StdSigmaBuilder { + + /** Checks the types of left anf right arguments and if necessary inserts the upcast + * node. + * @param left operand of the operation (left sub-expression) + * @param right operand of the operation (right sub-expression) + * @return a pair (l,r) of the arguments appropriately upcasted. */ - def check2[T <: SType](left: Value[T], - right: Value[T], - constraints: Seq[TypeConstraint2]): Unit = { - val n = constraints.length - cfor(0)(_ < n, _ + 1) { i => - val c = constraints(i) // to be efficient constraints should be WrappedArray (not List) - if (!c(left.tpe, right.tpe)) - throw new ConstraintFailed(s"Failed constraint $c for binary operation parameters ($left(tpe: ${left.tpe}), $right(tpe: ${right.tpe}))") - } - } -} - -trait TransformingSigmaBuilder extends StdSigmaBuilder with TypeConstraintCheck { - private def applyUpcast[T <: SType](left: Value[T], right: Value[T]): (Value[T], Value[T]) = (left.tpe, right.tpe) match { case (t1: SNumericType, t2: SNumericType) if t1 != t2 => @@ -700,6 +742,7 @@ trait TransformingSigmaBuilder extends StdSigmaBuilder with TypeConstraintCheck cons(l, r) } + /** HOTSPOT: don't beautify the code. */ override protected def arithOp[T <: SNumericType, R](left: Value[T], right: Value[T], cons: (Value[T], Value[T]) => R): R = { @@ -708,7 +751,8 @@ trait TransformingSigmaBuilder extends StdSigmaBuilder with TypeConstraintCheck } } -trait CheckingSigmaBuilder extends StdSigmaBuilder with TypeConstraintCheck { +/** Builder which does checking of constraints on the numeric arguments of binary operations. */ +class CheckingSigmaBuilder extends StdSigmaBuilder { override protected def equalityOp[T <: SType, R](left: Value[T], right: Value[T], @@ -732,21 +776,26 @@ trait CheckingSigmaBuilder extends StdSigmaBuilder with TypeConstraintCheck { } } +/** Standard builder which don't perform any additional transformations and checking. */ case object StdSigmaBuilder extends StdSigmaBuilder -case object CheckingSigmaBuilder extends StdSigmaBuilder with CheckingSigmaBuilder +/** Builder which performs checking of constraints on numeric operations. */ +case object CheckingSigmaBuilder extends CheckingSigmaBuilder -case object DefaultSigmaBuilder extends StdSigmaBuilder with CheckingSigmaBuilder -case object TransformingSigmaBuilder extends StdSigmaBuilder with TransformingSigmaBuilder +/** Builder of ErgoTree nodes which is used in SigmaCompiler. */ +case object TransformingSigmaBuilder extends TransformingSigmaBuilder /** Builder of ErgoTree nodes which is used in deserializers. */ -case object DeserializationSigmaBuilder extends StdSigmaBuilder with TransformingSigmaBuilder +case object DeserializationSigmaBuilder extends TransformingSigmaBuilder object Constraints { + /** Represents a constraint on arguments of binary operation. */ abstract class TypeConstraint2 { + /** Returns true if the constraints is satisfied. */ def apply(t1: SType, t2: SType): Boolean } + /** Checks that both arguments are numeric types. */ object OnlyNumeric2 extends TypeConstraint2 { override def apply(t1: SType, t2: SType): Boolean = t1 match { case _: SNumericType => t2 match { @@ -757,10 +806,12 @@ object Constraints { } } + /** Checks that both arguments have the same type. */ object SameType2 extends TypeConstraint2 { override def apply(t1: SType, t2: SType): Boolean = t1 == t2 } + /** These constraints sets are allocated once here and reused in different places. */ val SameTypeConstrain: Seq[TypeConstraint2] = Array(SameType2) val OnlyNumericConstrain: Seq[TypeConstraint2] = Array(OnlyNumeric2) val OnlyNumericAndSameTypeConstrain: Seq[TypeConstraint2] = Array(OnlyNumeric2, SameType2) diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaParser.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaParser.scala index 83810260eb..9bd73d934f 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaParser.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaParser.scala @@ -6,7 +6,6 @@ import sigmastate._ import Values._ import scalan.Nullable import sigmastate.lang.Terms._ -import sigmastate.SCollection.SByteArray import sigmastate.lang.syntax.Basic._ import sigmastate.lang.syntax.{Core, Exprs} diff --git a/sigmastate/src/main/scala/sigmastate/sigmastate.scala b/sigmastate/src/main/scala/sigmastate/sigmastate.scala index f7b7003289..28b817a651 100644 --- a/sigmastate/src/main/scala/sigmastate/sigmastate.scala +++ b/sigmastate/src/main/scala/sigmastate/sigmastate.scala @@ -1,8 +1,8 @@ import sigmastate.Values.Value -import sigmastate.lang.DefaultSigmaBuilder +import sigmastate.lang.CheckingSigmaBuilder package object sigmastate { - import DefaultSigmaBuilder._ + import CheckingSigmaBuilder._ /** * SInt addition diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index d30812d1d7..4f9f604d20 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -595,13 +595,15 @@ trait SNumericType extends SProduct { def isCastMethod (name: String): Boolean = castMethods.contains(name) /** Upcasts the given value of a smaller type to this larger type. + * Corresponds to section 5.1.2 Widening Primitive Conversion of Java Language Spec. * @param n numeric value to be converted * @return a value of WrappedType of this type descriptor's instance. - * @throw exception if `i` has actual type which is larger than this type. + * @throw exception if `n` has actual type which is larger than this type. */ def upcast(n: AnyVal): WrappedType /** Downcasts the given value of a larger type to this smaller type. + * Corresponds to section 5.1.3 Narrowing Primitive Conversion of Java Language Spec. * @param n numeric value to be converted * @return a value of WrappedType of this type descriptor's instance. * @throw exception if the actual value of `i` cannot fit into this type. diff --git a/sigmastate/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala index 3009df8ba5..de54b62295 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala @@ -2,12 +2,12 @@ package sigmastate.serialization import org.ergoplatform.Outputs import sigmastate.Values.{ByteConstant, IntConstant, LongConstant} -import sigmastate.lang.DefaultSigmaBuilder +import sigmastate.lang.CheckingSigmaBuilder import sigmastate.utxo.ByIndex import sigmastate.{SInt, SLong, Upcast} class UpcastOnDeserializationSpecification extends SerializationSpecification { - import DefaultSigmaBuilder._ + import CheckingSigmaBuilder._ property("Upcast deserialization round trip") { forAll(comparisonExprTreeNodeGen, minSuccessful(500)) { tree => From c5fc01bb47d314db97e477c37c57ee944aaa2220 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 27 May 2021 23:58:03 +0300 Subject: [PATCH 37/52] prepare-v5.0: AVHashMap.fromSeq added --- common/src/main/scala/scalan/AnyVals.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/common/src/main/scala/scalan/AnyVals.scala b/common/src/main/scala/scalan/AnyVals.scala index 29b5a1ad7b..f1e40348da 100644 --- a/common/src/main/scala/scalan/AnyVals.scala +++ b/common/src/main/scala/scalan/AnyVals.scala @@ -43,6 +43,16 @@ class AVHashMap[K,V](val hashMap: HashMap[K,V]) extends AnyVal { @inline final def keySet: java.util.Set[K] = hashMap.keySet() } object AVHashMap { + /** Helper method to create a new map with the given capacity. */ def apply[K,V](initialCapacity: Int) = new AVHashMap[K,V](new HashMap[K,V](initialCapacity)) + + /** Helper method to create a new map form sequence of K, V pairs. */ + def fromSeq[K,V](items: Seq[(K, V)]): AVHashMap[K,V] = { + val map = new AVHashMap[K,V](new HashMap[K,V](items.length)) + items.foreach { case (k, v) => + map.put(k, v) + } + map + } } From 362feb0cdc7f4bcb1162d47c1499837fbacfca95 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 27 May 2021 23:59:47 +0300 Subject: [PATCH 38/52] prepare-v5.0: BenchmarkUtil.measureTimeNano added --- common/src/main/scala/scalan/util/BenchmarkUtil.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/src/main/scala/scalan/util/BenchmarkUtil.scala b/common/src/main/scala/scalan/util/BenchmarkUtil.scala index 8ed69a8707..39a8849742 100644 --- a/common/src/main/scala/scalan/util/BenchmarkUtil.scala +++ b/common/src/main/scala/scalan/util/BenchmarkUtil.scala @@ -31,6 +31,15 @@ object BenchmarkUtil { (res, t - t0) } + /** Execute block and measure the time of its execution in nanoseconds. */ + def measureTimeNano[T](block: => T): (T, Long) = { + val start = System.nanoTime() + val res = block + val end = System.nanoTime() + (res, end - start) + } + + def runTasks(nTasks: Int)(block: Int => Unit) = { val (_, total) = measureTime { val tasks = (1 to nTasks).map(iTask => Future(block(iTask))) From 88a5f47b88f724a3b2386abae2fe1058e5cc9865 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 28 May 2021 00:06:26 +0300 Subject: [PATCH 39/52] prepare-v5.0: ScalaDoc for getVar --- common/src/test/scala/scalan/TestUtils.scala | 5 +- docs/LangSpec.md | 71 ++++++++++++++++++-- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/common/src/test/scala/scalan/TestUtils.scala b/common/src/test/scala/scalan/TestUtils.scala index 75c5568b6f..0e1645f71d 100644 --- a/common/src/test/scala/scalan/TestUtils.scala +++ b/common/src/test/scala/scalan/TestUtils.scala @@ -3,6 +3,7 @@ package scalan import scalan.util.FileUtil import org.scalactic.TripleEquals import org.scalatest.{Inside, Matchers, TestSuite} +import scalan.util.StringUtil.StringUtilExtensions /** * Created by slesarenko on 11/10/2017. @@ -35,8 +36,8 @@ trait TestUtils extends TestSuite with Matchers with Inside with TripleEquals { protected def currentTestName: String = { val testName = _currentTestName.get() - assert(testName != null, "currentTestName called outside a test") - testName + if (testName.isNullOrEmpty) "_outside_tests_" + else testName } protected def currentTestNameAsFileName: String = FileUtil.cleanFileName(currentTestName) diff --git a/docs/LangSpec.md b/docs/LangSpec.md index 5d4e5ffd11..27ae6d9229 100644 --- a/docs/LangSpec.md +++ b/docs/LangSpec.md @@ -417,12 +417,14 @@ class Option[A] { def isDefined: Boolean; /** Returns the option's value if the option is nonempty, otherwise - * return the result of evaluating `default`. - * - * @param default the default expression, which is evaluated only if option is None. - */ - def getOrElse[B](default: => B): B - + * return the result of evaluating `default`. + * NOTE: the `default` is evaluated even if the option contains the value + * i.e. not lazily. + * + * @param default the default expression. + */ + def getOrElse[B](default: B): B + /** Returns the option's value. * @note The option must be nonempty. * @throws InterpreterException if the option is empty. @@ -883,7 +885,62 @@ def longToByteArray(input: Long): Coll[Byte] def decodePoint(bytes: Coll[Byte]): GroupElement -/** Returns value of the given type from the context by its tag.*/ +/** Extracts Context variable by id and type. + * ErgoScript is typed, so accessing a the variables is an operation which involves + * some expected type given in brackets. Thus `getVar[Int](id)` expression should + * evaluate to a valid value of the `Option[Int]` type. + * + * For example `val x = getVar[Int](10)` expects the variable, if it is present, to have + * type `Int`. At runtime the corresponding type descriptor is passed as `cT` + * parameter. + * + * There are three cases: + * 1) If the variable doesn't exist. + * Then `val x = getVar[Int](id)` succeeds and returns the None value, which conforms to + * any value of type `Option[T]` for any T. (In the example above T is equal to + * `Int`). Calling `x.get` fails when x is equal to None, but `x.isDefined` + * succeeds and returns `false`. + * 2) If the variable contains a value `v` of type `Int`. + * Then `val x = getVar[Int](id)` succeeds and returns `Some(v)`, which is a valid value + * of type `Option[Int]`. In this case, calling `x.get` succeeds and returns the + * value `v` of type `Int`. Calling `x.isDefined` returns `true`. + * 3) If the variable contains a value `v` of type T other then `Int`. + * Then `val x = getVar[Int](id)` fails, because there is no way to return a valid value + * of type `Option[Int]`. The value of variable is present, so returning it as None + * would break the typed semantics of variables collection. + * + * In some use cases one variable may have values of different types. To access such + * variable an additional variable can be used as a tag. + * + *
+  *   val tagOpt = getVar[Int](id)
+  *   val res = if (tagOpt.isDefined) {
+  *     val tag = tagOpt.get
+  *     if (tag == 1) {
+  *       val x = getVar[Int](id2).get
+  *       // compute res using value x is of type Int
+  *     } else if (tag == 2) {
+  *       val x = getVar[GroupElement](id2).get
+  *       // compute res using value x is of type GroupElement
+  *     } else if (tag == 3) {
+  *       val x = getVar[ Array[Byte] ](id2).get
+  *       // compute res using value x of type Array[Byte]
+  *     } else {
+  *       // compute `res` when `tag` is not 1, 2 or 3
+  *     }
+  *   }
+  *   else {
+  *     // compute value of res when the variable is not present
+  *   }
+  * 
+ * + * @param id zero-based identifier of the variable. + * @tparam T expected type of the variable. + * @return Some(value) if the variable is defined in the context AND has the given type. + * None otherwise + * @throws special.sigma.InvalidType exception when the type of the variable value is + * different from cT. + */ def getVar[T](tag: Int): Option[T] /** Construct a new SigmaProp value representing public key of Diffie Hellman signature protocol. From d64fd2783c57e70a8ed045a24d2b195aad8eb1f7 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 28 May 2021 00:12:09 +0300 Subject: [PATCH 40/52] prepare-v5.0: removed commented Coll.applyMany --- docs/notes.md | 1 + .../src/main/scala/special/collection/Colls.scala | 15 ++------------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/docs/notes.md b/docs/notes.md index 0f7a441ee7..aec9cac341 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -1,3 +1,4 @@ + ## Approximate sizes of different dependencies These dependencies can be removed with simple refactoring diff --git a/library-api/src/main/scala/special/collection/Colls.scala b/library-api/src/main/scala/special/collection/Colls.scala index 99827b10c7..76e74bc900 100644 --- a/library-api/src/main/scala/special/collection/Colls.scala +++ b/library-api/src/main/scala/special/collection/Colls.scala @@ -61,17 +61,6 @@ trait Coll[@specialized A] { */ def isDefinedAt(idx: Int): Boolean - /** The elements at given indexes. - * Indices start at `0` so that `xs.apply(0)` is the first element of collection `xs`. - * Note the indexing syntax `xs(i)` is a shorthand for `xs.apply(i)`. - * - * @param indexes the indexes of the elements to extract from this collection - * @return the elements at the given indexes - * @throws ArrayIndexOutOfBoundsException if `i < 0` or `length <= i` - * for any `i` in `indexes` - */ -// def applyMany(indexes: Coll[Int]): Coll[A] - /** The element of the collection or default value. * If an index is out of bounds (`i < 0 || i >= length`) then `default` value is returned. * @param i the index @@ -117,7 +106,7 @@ trait Coll[@specialized A] { /** Applies a binary operator to a start value and all elements of this collection, * going left to right. * - * @param z the start value. + * @param zero the start value. * @param op the binary operator. * @tparam B the result type of the binary operator. * @return the result of inserting `op` between consecutive elements of this collection, @@ -125,7 +114,7 @@ trait Coll[@specialized A] { * {{{ * op(...op(z, x_1), x_2, ..., x_n) * }}} - * where `x,,1,,, ..., x,,n,,` are the elements of this collection. + * where `x_1, ..., x_n` are the elements of this collection. * Returns `z` if this collection is empty. */ def foldLeft[B](zero: B, op: ((B, A)) => B): B From 8d5b44dd4c1296565b2c43e0aa5f67a90f109cb2 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 28 May 2021 09:01:03 +0300 Subject: [PATCH 41/52] prepare-v5.0: - commonsMath3 dependency; - def createVerifier; - AvlTreeVerifier added; - substConstants signature; - activatedScriptVersion property --- build.sbt | 3 +- .../main/scala/special/sigma/SigmaDsl.scala | 104 +++++++++++++++++- .../special/sigma/SigmaDslOverArrays.scala | 3 +- .../org/ergoplatform/ErgoLikeContext.scala | 5 +- .../sigmastate/eval/CostingDataContext.scala | 38 ++++--- .../special/sigma/ContractsTestkit.scala | 11 +- .../special/sigma/SigmaDslSpecification.scala | 3 +- .../special/sigma/SigmaDslStaginTests.scala | 3 +- 8 files changed, 142 insertions(+), 28 deletions(-) diff --git a/build.sbt b/build.sbt index 29e0c208e8..3884247879 100644 --- a/build.sbt +++ b/build.sbt @@ -61,6 +61,7 @@ val debox = "org.spire-math" %% "debox" % "0.8.0" val kiama = "org.bitbucket.inkytonik.kiama" %% "kiama" % "2.1.0" val fastparse = "com.lihaoyi" %% "fastparse" % "1.0.0" val commonsIo = "commons-io" % "commons-io" % "2.5" +val commonsMath3 = "org.apache.commons" % "commons-math3" % "3.2" val specialVersion = "0.6.1" val meta = "io.github.scalan" %% "meta" % specialVersion @@ -256,7 +257,7 @@ lazy val sigmastate = (project in file("sigmastate")) .dependsOn(sigmaimpl % allConfigDependency, sigmalibrary % allConfigDependency) .settings(libraryDefSettings) .settings(libraryDependencies ++= Seq( - scorexUtil, kiama, fastparse, + scorexUtil, kiama, fastparse, commonsMath3, if (scalaVersion.value == scala211) circeCore211 else circeCore, if (scalaVersion.value == scala211) circeGeneric211 else circeGeneric, if (scalaVersion.value == scala211) circeParser211 else circeParser diff --git a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala index 78488d11cf..aa1a7086ae 100644 --- a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala +++ b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala @@ -3,9 +3,12 @@ package special.sigma import java.math.BigInteger import org.bouncycastle.math.ec.ECPoint - import special.collection._ import scalan._ +import scorex.crypto.authds.{ADDigest, ADValue} +import scorex.crypto.authds.avltree.batch.Operation + +import scala.util.Try @scalan.Liftable trait CostModel { @@ -543,8 +546,40 @@ trait AvlTree { * @param proof */ def remove(operations: Coll[Coll[Byte]], proof: Coll[Byte]): Option[AvlTree] + + def createVerifier(proof: Coll[Byte]): AvlTreeVerifier +} + +trait AvlTreeVerifier { + /** + * If operation.key exists in the tree and the operation succeeds, + * returns Success(Some(v)), where v is the value associated with operation.key + * before the operation. + * If operation.key does not exists in the tree and the operation succeeds, returns Success(None). + * Returns Failure if the operation fails or the proof does not verify. + * After one failure, all subsequent operations will fail and digest + * is None. + * + * @param operation + * @return - Success(Some(old value)), Success(None), or Failure + */ + def performOneOperation(operation: Operation): Try[Option[ADValue]] + + /** Returns the max height of the tree extracted from the root digest. */ + def treeHeight: Int + + /** + * Returns Some[the current digest of the authenticated data structure], + * where the digest contains the root hash and the root height + * Returns None if the proof verification failed at construction + * or during any of the operations. + * + * @return - Some[digest] or None + */ + def digest: Option[ADDigest] } + /** Only header fields that can be predicted by a miner. * @since 2.0 */ @@ -665,8 +700,70 @@ trait Context { def preHeader: PreHeader def minerPubKey: Coll[Byte] + + /** Extracts Context variable by id and type. + * ErgoScript is typed, so accessing a the variables is an operation which involves + * some expected type given in brackets. Thus `getVar[Int](id)` expression should + * evaluate to a valid value of the `Option[Int]` type. + * + * For example `val x = getVar[Int](10)` expects the variable, if it is present, to have + * type `Int`. At runtime the corresponding type descriptor is passed as `cT` + * parameter. + * + * There are three cases: + * 1) If the variable doesn't exist. + * Then `val x = getVar[Int](id)` succeeds and returns the None value, which conforms to + * any value of type `Option[T]` for any T. (In the example above T is equal to + * `Int`). Calling `x.get` fails when x is equal to None, but `x.isDefined` + * succeeds and returns `false`. + * 2) If the variable contains a value `v` of type `Int`. + * Then `val x = getVar[Int](id)` succeeds and returns `Some(v)`, which is a valid value + * of type `Option[Int]`. In this case, calling `x.get` succeeds and returns the + * value `v` of type `Int`. Calling `x.isDefined` returns `true`. + * 3) If the variable contains a value `v` of type T other then `Int`. + * Then `val x = getVar[Int](id)` fails, because there is no way to return a valid value + * of type `Option[Int]`. The value of variable is present, so returning it as None + * would break the typed semantics of variables collection. + * + * In some use cases one variable may have values of different types. To access such + * variable an additional variable can be used as a tag. + * + *
+    *   val tagOpt = getVar[Int](id)
+    *   val res = if (tagOpt.isDefined) {
+    *     val tag = tagOpt.get
+    *     if (tag == 1) {
+    *       val x = getVar[Int](id2).get
+    *       // compute res using value x is of type Int
+    *     } else if (tag == 2) {
+    *       val x = getVar[GroupElement](id2).get
+    *       // compute res using value x is of type GroupElement
+    *     } else if (tag == 3) {
+    *       val x = getVar[ Array[Byte] ](id2).get
+    *       // compute res using value x of type Array[Byte]
+    *     } else {
+    *       // compute `res` when `tag` is not 1, 2 or 3
+    *     }
+    *   }
+    *   else {
+    *     // compute value of res when the variable is not present
+    *   }
+    * 
+ * + * @param id zero-based identifier of the variable. + * @tparam T expected type of the variable. + * @return Some(value) if the variable is defined in the context AND has the given type. + * None otherwise + * @throws special.sigma.InvalidType exception when the type of the variable value is + * different from cT. + */ def getVar[T](id: Byte)(implicit cT: RType[T]): Option[T] + def vars: Coll[AnyValue] + + /** Maximum version of ErgoTree currently activated on the network. + * See [[ErgoLikeContext]] class for details. */ + def activatedScriptVersion: Byte } @scalan.Liftable @@ -710,8 +807,7 @@ trait SigmaContract { @Reified("T") def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], - newValues: Coll[T]) - (implicit cT: RType[T]): Coll[Byte] = this.builder.substConstants(scriptBytes, positions, newValues) + newValues: Coll[T]): Coll[Byte] = this.builder.substConstants(scriptBytes, positions, newValues) } /** Runtime representation of SGlobal ErgoTree type. @@ -828,7 +924,7 @@ trait SigmaDslBuilder { * @return original scriptBytes array where only specified constants are replaced and all other bytes remain exactly the same */ @Reified("T") - def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], newValues: Coll[T])(implicit cT: RType[T]): Coll[Byte] + def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], newValues: Coll[T]): Coll[Byte] /** Decodes the given bytes to the corresponding GroupElement using default serialization. * @param encoded serialized bytes of some GroupElement value diff --git a/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala b/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala index abd72e5236..d1180960a7 100644 --- a/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala +++ b/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala @@ -80,8 +80,7 @@ class TestSigmaDslBuilder extends SigmaDslBuilder { @NeverInline override def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], - newValues: Coll[T]) - (implicit cT: RType[T]): Coll[Byte] = ??? + newValues: Coll[T]): Coll[Byte] = ??? @NeverInline override def decodePoint(encoded: Coll[Byte]): GroupElement = diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala index 8467a24ee7..22edda7ab0 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala @@ -152,9 +152,10 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData, } val vars = contextVars(varMap ++ extensions) val avlTree = CAvlTree(lastBlockUtxoRoot) + val selfBox = boxesToSpend(selfIndex).toTestBox(isCost) CostingDataContext( - dataInputs, headers, preHeader, inputs, outputs, preHeader.height, boxesToSpend(selfIndex).toTestBox(isCost), avlTree, - preHeader.minerPk.getEncoded, vars, isCost) + dataInputs, headers, preHeader, inputs, outputs, preHeader.height, selfBox, avlTree, + preHeader.minerPk.getEncoded, vars, activatedScriptVersion, isCost) } diff --git a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala index 256290ef60..24eddd380a 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala @@ -21,6 +21,7 @@ import spire.syntax.all.cfor import scala.util.{Success, Failure} import scalan.{Nullable, RType} import scorex.crypto.hash.{Digest32, Sha256, Blake2b256} +import sigmastate.Values.ErgoTree.EmptyConstants import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.basics.ProveDHTuple import sigmastate.lang.Terms.OperationId @@ -70,7 +71,8 @@ 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 - val ergoTree = ErgoTree.fromSigmaBoolean(sigmaTree) + val root = sigmaTree.toSigmaProp + val ergoTree = new ErgoTree(ErgoTree.DefaultHeader, EmptyConstants, Right(root), 0, null, None) val bytes = DefaultSerializer.serializeErgoTree(ergoTree) Colls.fromArray(bytes) } @@ -96,6 +98,19 @@ case class CSigmaProp(sigmaTree: SigmaBoolean) extends SigmaProp with WrapperOf[ override def toString: String = s"SigmaProp(${wrappedValue.showToString})" } +class CAvlTreeVerifier(startingDigest: ADDigest, + proof: SerializedAdProof, + override val keyLength: Int, + override val valueLengthOpt: Option[Int]) + extends BatchAVLVerifier[Digest32, Blake2b256.type]( + startingDigest, proof, keyLength, valueLengthOpt) + with AvlTreeVerifier { + override def treeHeight: Int = rootNodeHeight + + /** Override default logging which outputs stack trace to the console. */ + override protected def logError(t: Throwable): Unit = {} +} + /** A default implementation of [[AvlTree]] interface. * @see [[AvlTree]] for detailed descriptions */ @@ -129,14 +144,10 @@ case class CAvlTree(treeData: AvlTreeData) extends AvlTree with WrapperOf[AvlTre this.copy(treeData = td) } - private def createVerifier(proof: Coll[Byte]): BatchAVLVerifier[Digest32, Blake2b256.type] = { + override def createVerifier(proof: Coll[Byte]): AvlTreeVerifier = { val adProof = SerializedAdProof @@ proof.toArray - val bv = new BatchAVLVerifier[Digest32, Blake2b256.type]( - treeData.digest, adProof, - treeData.keyLength, treeData.valueLengthOpt) { - /** Override default logging which outputs stack trace to the console. */ - override protected def logError(t: Throwable): Unit = {} - } + val bv = new CAvlTreeVerifier( + treeData.digest, adProof, treeData.keyLength, treeData.valueLengthOpt) bv } @@ -334,6 +345,7 @@ case class CostingBox(isCost: Boolean, val ebox: ErgoBox) extends Box with Wrapp override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj != null && ( obj match { case obj: Box => util.Arrays.equals(id.toArray, obj.id.toArray) + // TODO v5.0 HF: return false when obj in not Box instead of throwing an exception })) } @@ -656,9 +668,8 @@ class CostingSigmaDslBuilder extends TestSigmaDslBuilder { dsl => CSigmaProp(dht) } - override def groupGenerator: GroupElement = { - this.GroupElement(CryptoConstants.dlogGroup.generator) - } + private lazy val _generatorElement = this.GroupElement(CryptoConstants.dlogGroup.generator) + override def groupGenerator: GroupElement = _generatorElement /** * @return the identity of the Dlog group used in ErgoTree @@ -669,8 +680,7 @@ class CostingSigmaDslBuilder extends TestSigmaDslBuilder { dsl => override def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], - newValues: Coll[T]) - (implicit cT: RType[T]): Coll[Byte] = { + newValues: Coll[T]): Coll[Byte] = { val typedNewVals = newValues.toArray.map(v => TransformingSigmaBuilder.liftAny(v) match { case Nullable(v) => v case _ => sys.error(s"Cannot evaluate substConstants($scriptBytes, $positions, $newValues): cannot lift value $v") @@ -705,6 +715,7 @@ case class CostingDataContext( lastBlockUtxoRootHash: AvlTree, _minerPubKey: Coll[Byte], vars: Coll[AnyValue], + override val activatedScriptVersion: Byte, var isCost: Boolean) extends Context { @inline override def builder: SigmaDslBuilder = CostingSigmaDslBuilder @@ -800,3 +811,4 @@ case class CostingDataContext( this.copy(vars = newVars) } } + diff --git a/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala b/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala index 884e8523be..56aad0befd 100644 --- a/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala +++ b/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala @@ -66,13 +66,16 @@ trait ContractsTestkit { def testContext(inputs: Array[Box], outputs: Array[Box], height: Int, self: Box, - tree: AvlTree, minerPk: Array[Byte], vars: Array[AnyValue]) = + tree: AvlTree, minerPk: Array[Byte], activatedScriptVersion: Byte, vars: Array[AnyValue]) = new CostingDataContext( noInputs.toColl, noHeaders, dummyPreHeader, - inputs.toColl, outputs.toColl, height, self, tree, minerPk.toColl, vars.toColl, false) + inputs.toColl, outputs.toColl, height, self, tree, + minerPk.toColl, vars.toColl, activatedScriptVersion, false) - def newContext(height: Int, self: Box, vars: AnyValue*): CostingDataContext = { - testContext(noInputs, noOutputs, height, self, emptyAvlTree, dummyPubkey, vars.toArray) + def newContext(height: Int, self: Box, activatedScriptVersion: Byte, vars: AnyValue*): CostingDataContext = { + testContext( + noInputs, noOutputs, height, self, emptyAvlTree, dummyPubkey, + activatedScriptVersion, vars.toArray) } implicit class TestContextOps(ctx: CostingDataContext) { diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala index bd79e76766..383f062371 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala @@ -3654,6 +3654,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui .append(Coll[AnyValue]( TestValue(Helpers.decodeBytes("00"), CollType(RType.ByteType)), TestValue(true, RType.BooleanType))), + activatedScriptVersion = activatedVersionInTests, false ) val ctx2 = ctx.copy(vars = Coll[AnyValue](null, null, null)) @@ -6276,7 +6277,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui }, existingFeature( { (x: (Coll[Byte], Int)) => - SigmaDsl.substConstants(x._1, Coll[Int](x._2), Coll[Any](SigmaDsl.sigmaProp(false))(RType.AnyType))(RType.AnyType) + SigmaDsl.substConstants(x._1, Coll[Int](x._2), Coll[Any](SigmaDsl.sigmaProp(false))(RType.AnyType)) }, "{ (x: (Coll[Byte], Int)) => substConstants[Any](x._1, Coll[Int](x._2), Coll[Any](sigmaProp(false))) }", FuncValue( diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslStaginTests.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslStaginTests.scala index 8ba908b7b4..a996617525 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslStaginTests.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslStaginTests.scala @@ -6,6 +6,7 @@ import scala.language.reflectiveCalls import scalan.{BaseLiftableTests, BaseCtxTests} import sigmastate.eval.Extensions._ import sigmastate.eval.{IRContext, ErgoScriptTestkit} +import sigmastate.interpreter.Interpreter class SigmaDslStaginTests extends BaseCtxTests with ErgoScriptTestkit with BaseLiftableTests { class Ctx extends TestContext with IRContext with LiftableTestKit { @@ -29,7 +30,7 @@ class SigmaDslStaginTests extends BaseCtxTests with ErgoScriptTestkit with BaseL type RSigmaProp = cake.SigmaProp val boxA1 = newAliceBox(1, 100) val boxA2 = newAliceBox(2, 200) - val ctx: SContext = newContext(10, boxA1) + val ctx: SContext = newContext(10, boxA1, Interpreter.MaxSupportedScriptVersion) .withInputs(boxA2) .withVariables(Map(1 -> toAnyValue(30), 2 -> toAnyValue(40))) val p1: SSigmaProp = new special.sigma.MockSigma(true) From 4533b6a7ae86ada20f3136c70a67a920ae7c43e1 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 30 May 2021 00:35:32 +0300 Subject: [PATCH 42/52] prepare-v5.0: - changed signature of substConstants; - various improvments --- .../scala/scalan/util/BenchmarkUtil.scala | 2 +- .../src/main/scala/special/sigma/Mocks.scala | 8 ++++---- .../scala/special/sigma/SigmaDslUnit.scala | 4 ++-- .../special/sigma/impl/SigmaDslImpl.scala | 20 +++++++++---------- .../main/scala/org/ergoplatform/ErgoBox.scala | 3 +-- .../org/ergoplatform/ErgoBoxCandidate.scala | 3 ++- .../ergoplatform/ErgoLikeInterpreter.scala | 1 + .../org/ergoplatform/SigmaConstants.scala | 7 ++++++- .../org/ergoplatform/dsl/ContractSyntax.scala | 2 +- .../scala/sigmastate/eval/Evaluation.scala | 4 ++-- .../sigmastate/eval/RuntimeCosting.scala | 2 +- .../scala/sigmastate/eval/TreeBuilding.scala | 2 +- .../special/sigma/ContractsTestkit.scala | 2 +- 13 files changed, 33 insertions(+), 27 deletions(-) diff --git a/common/src/main/scala/scalan/util/BenchmarkUtil.scala b/common/src/main/scala/scalan/util/BenchmarkUtil.scala index 39a8849742..ffaed0b16b 100644 --- a/common/src/main/scala/scalan/util/BenchmarkUtil.scala +++ b/common/src/main/scala/scalan/util/BenchmarkUtil.scala @@ -39,7 +39,7 @@ object BenchmarkUtil { (res, end - start) } - + def runTasks(nTasks: Int)(block: Int => Unit) = { val (_, total) = measureTime { val tasks = (1 to nTasks).map(iTask => Future(block(iTask))) diff --git a/sigma-impl/src/main/scala/special/sigma/Mocks.scala b/sigma-impl/src/main/scala/special/sigma/Mocks.scala index 700f4a7825..6069b29b4d 100644 --- a/sigma-impl/src/main/scala/special/sigma/Mocks.scala +++ b/sigma-impl/src/main/scala/special/sigma/Mocks.scala @@ -5,9 +5,10 @@ import scalan.{NeverInline, OverloadId} import special.collection.Builder.DefaultCollBuilder import special.collection.{Coll, Builder} -/**NOTE: this should extend SigmaProp because semantically it subclass of SigmaProp - * and DefaultSigma is used just to mixin implementations. */ - +// TODO refactor: remove this class +/** NOTE: this should extend SigmaProp because semantically it subclass of SigmaProp + * and DefaultSigma is used just to mixin implementations. + */ case class MockSigma(val _isValid: Boolean) extends SigmaProp { def propBytes: Coll[Byte] = DefaultCollBuilder.fromItems(if(isValid) 1 else 0) @@ -25,7 +26,6 @@ case class MockSigma(val _isValid: Boolean) extends SigmaProp { @NeverInline @OverloadId("or_bool") def ||(other: Boolean): SigmaProp = MockSigma(isValid || other) - } case class MockProveDlog(var isValid: Boolean, val propBytes: Coll[Byte]) extends SigmaProp { diff --git a/sigma-library/src/main/scala/special/sigma/SigmaDslUnit.scala b/sigma-library/src/main/scala/special/sigma/SigmaDslUnit.scala index 9d47cf1cc2..8dad6b3a9f 100644 --- a/sigma-library/src/main/scala/special/sigma/SigmaDslUnit.scala +++ b/sigma-library/src/main/scala/special/sigma/SigmaDslUnit.scala @@ -179,7 +179,7 @@ package special.sigma { def proveDHTuple(g: Ref[GroupElement], h: Ref[GroupElement], u: Ref[GroupElement], v: Ref[GroupElement]): Ref[SigmaProp] = this.builder.proveDHTuple(g, h, u, v); def groupGenerator: Ref[GroupElement] = this.builder.groupGenerator; def decodePoint(encoded: Ref[Coll[Byte]]): Ref[GroupElement] = this.builder.decodePoint(encoded); - @Reified(value = "T") def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]])(implicit cT: Elem[T]): Ref[Coll[Byte]] = this.builder.substConstants[T](scriptBytes, positions, newValues) + @Reified(value = "T") def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]]): Ref[Coll[Byte]] = this.builder.substConstants[T](scriptBytes, positions, newValues) }; @Liftable @WithMethodCallRecognizers trait SigmaDslBuilder extends Def[SigmaDslBuilder] { def Colls: Ref[CollBuilder]; @@ -202,7 +202,7 @@ package special.sigma { def proveDlog(g: Ref[GroupElement]): Ref[SigmaProp]; def proveDHTuple(g: Ref[GroupElement], h: Ref[GroupElement], u: Ref[GroupElement], v: Ref[GroupElement]): Ref[SigmaProp]; def groupGenerator: Ref[GroupElement]; - @Reified(value = "T") def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]])(implicit cT: Elem[T]): Ref[Coll[Byte]]; + @Reified(value = "T") def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]]): Ref[Coll[Byte]]; def decodePoint(encoded: Ref[Coll[Byte]]): Ref[GroupElement]; def avlTree(operationFlags: Ref[Byte], digest: Ref[Coll[Byte]], keyLength: Ref[Int], valueLengthOpt: Ref[WOption[Int]]): Ref[AvlTree]; def xor(l: Ref[Coll[Byte]], r: Ref[Coll[Byte]]): Ref[Coll[Byte]] diff --git a/sigma-library/src/main/scala/special/sigma/impl/SigmaDslImpl.scala b/sigma-library/src/main/scala/special/sigma/impl/SigmaDslImpl.scala index 47a377b03e..67230ff5ae 100644 --- a/sigma-library/src/main/scala/special/sigma/impl/SigmaDslImpl.scala +++ b/sigma-library/src/main/scala/special/sigma/impl/SigmaDslImpl.scala @@ -3332,11 +3332,11 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { true, false, element[GroupElement])) } - override def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]])(implicit cT: Elem[T]): Ref[Coll[Byte]] = { + override def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]]): Ref[Coll[Byte]] = { implicit val eT = newValues.eA asRep[Coll[Byte]](mkMethodCall(self, - SigmaDslBuilderClass.getMethod("substConstants", classOf[Sym], classOf[Sym], classOf[Sym], classOf[Elem[_]]), - Array[AnyRef](scriptBytes, positions, newValues, cT), + SigmaDslBuilderClass.getMethod("substConstants", classOf[Sym], classOf[Sym], classOf[Sym]), + Array[AnyRef](scriptBytes, positions, newValues), true, false, element[Coll[Byte]])) } @@ -3525,11 +3525,11 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { true, true, element[GroupElement])) } - def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]])(implicit cT: Elem[T]): Ref[Coll[Byte]] = { + def substConstants[T](scriptBytes: Ref[Coll[Byte]], positions: Ref[Coll[Int]], newValues: Ref[Coll[T]]): Ref[Coll[Byte]] = { implicit val eT = newValues.eA asRep[Coll[Byte]](mkMethodCall(source, - SigmaDslBuilderClass.getMethod("substConstants", classOf[Sym], classOf[Sym], classOf[Sym], classOf[Elem[_]]), - Array[AnyRef](scriptBytes, positions, newValues, cT), + SigmaDslBuilderClass.getMethod("substConstants", classOf[Sym], classOf[Sym], classOf[Sym]), + Array[AnyRef](scriptBytes, positions, newValues), true, true, element[Coll[Byte]])) } @@ -3793,13 +3793,13 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { } object substConstants { - def unapply(d: Def[_]): Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]], Elem[T]) forSome {type T}] = d match { + def unapply(d: Def[_]): Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]]) forSome {type T}] = d match { case MethodCall(receiver, method, args, _) if method.getName == "substConstants" && receiver.elem.isInstanceOf[SigmaDslBuilderElem[_]] => - val res = (receiver, args(0), args(1), args(2), args(3)) - Nullable(res).asInstanceOf[Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]], Elem[T]) forSome {type T}]] + val res = (receiver, args(0), args(1), args(2)) + Nullable(res).asInstanceOf[Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]]) forSome {type T}]] case _ => Nullable.None } - def unapply(exp: Sym): Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]], Elem[T]) forSome {type T}] = unapply(exp.node) + def unapply(exp: Sym): Nullable[(Ref[SigmaDslBuilder], Ref[Coll[Byte]], Ref[Coll[Int]], Ref[Coll[T]]) forSome {type T}] = unapply(exp.node) } object decodePoint { diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala index 79fbb8c436..72c2736841 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala @@ -120,8 +120,7 @@ object ErgoBox { trait RegisterId { val number: Byte def asIndex: Int = number.toInt - - override def toString: Idn = "R" + number + override def toString: String = "R" + number } abstract class MandatoryRegisterId(override val number: Byte, val purpose: String) extends RegisterId diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 82f6f5f877..19211b6aa0 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -50,6 +50,7 @@ class ErgoBoxCandidate(val value: Long, lazy val propositionBytes: Array[Byte] = ergoTree.bytes /** Serialized bytes of this Box without transaction reference data (transactionId and boxIndex). */ + // TODO v5.0: re-implement extracting directly from `ErgoBox.bytes` array lazy val bytesWithNoRef: Array[Byte] = ErgoBoxCandidate.serializer.toBytes(this) /** Creates a new [[ErgoBox]] based on this candidate using the given transaction reference data. @@ -93,7 +94,7 @@ class ErgoBoxCandidate(val value: Long, ScalaRunTime._hashCode((value, ergoTree, additionalTokens, additionalRegisters, creationHeight)) } - override def toString: Idn = s"ErgoBoxCandidate($value, $ergoTree," + + override def toString: String = s"ErgoBoxCandidate($value, $ergoTree," + s"tokens: (${additionalTokens.map(t => ErgoAlgos.encode(t._1) + ":" + t._2).toArray.mkString(", ")}), " + s"$additionalRegisters, creationHeight: $creationHeight)" diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala index 1ce5bede46..57fc66853e 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala @@ -40,4 +40,5 @@ class ErgoLikeInterpreter(implicit val IR: IRContext) extends Interpreter { }.orElse(d.default) case _ => super.substDeserialize(context, updateContext, node) } + } \ No newline at end of file diff --git a/sigmastate/src/main/scala/org/ergoplatform/SigmaConstants.scala b/sigmastate/src/main/scala/org/ergoplatform/SigmaConstants.scala index 5f3c86adc5..b1f02a2b98 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/SigmaConstants.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/SigmaConstants.scala @@ -76,6 +76,10 @@ object SigmaConstants { "size of nonce array from Autolykos POW solution in Header.powNonce array") { } + object MaxCollSize extends SizeConstant[Int](4 * 1024, 16, + "Maximum number of items in a collection") { + } + val ConstTable: Seq[SizeConstant[_]] = { val rows = Seq( MaxBoxSize, @@ -92,7 +96,8 @@ object SigmaConstants { ScriptCostLimit, MaxLoopLevelInCostFunction, VotesArraySize, - AutolykosPowSolutionNonceArraySize + AutolykosPowSolutionNonceArraySize, + MaxCollSize ) require(rows.length == rows.distinctBy(_.id).length, s"Duplicate constant id in $rows") rows diff --git a/sigmastate/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala b/sigmastate/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala index d4266bb1c6..5798762db8 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala @@ -11,7 +11,7 @@ import special.sigma.{SigmaProp, SigmaContract, Context, DslSyntaxExtensions, Si import scala.language.implicitConversions trait ContractSyntax { contract: SigmaContract => - override def builder: SigmaDslBuilder = new CostingSigmaDslBuilder + override def builder: SigmaDslBuilder = CostingSigmaDslBuilder val spec: ContractSpec val syntax = new DslSyntaxExtensions(builder) def contractEnv: ScriptEnv diff --git a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala index 0aaab8b302..205743224d 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala @@ -197,7 +197,7 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => case OM.getOrElse(_, _) => OptionGetOrElseCode case OM.fold(_, _, _) => MethodCallCode case OM.isDefined(_) => OptionIsDefinedCode - case SDBM.substConstants(_, _, _, _, _) => SubstConstantsCode + case SDBM.substConstants(_, _, _, _) => SubstConstantsCode case SDBM.longToByteArray(_, _) => LongToByteArrayCode case SDBM.byteArrayToBigInt(_, _) => ByteArrayToBigIntCode case SDBM.byteArrayToLong(_, _) => ByteArrayToLongCode @@ -572,7 +572,7 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => case SDBM.substConstants(_, In(input: special.collection.Coll[Byte]@unchecked), In(positions: special.collection.Coll[Int]@unchecked), - In(newVals: special.collection.Coll[Any]@unchecked), _) => + In(newVals: special.collection.Coll[Any]@unchecked)) => // TODO refactor: call sigmaDslBuilderValue.substConstants val typedNewVals = newVals.toArray.map(v => builder.liftAny(v) match { case Nullable(v) => v diff --git a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala index d6457d0fe5..acb955264a 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala @@ -1819,7 +1819,7 @@ trait RuntimeCosting extends CostingRules { IR: IRContext => RCCostedColl(values, costs, sizes, cost) case SubstConstants(InCollByte(bytes), InCollInt(positions), InCollAny(newValues)) => - val values = sigmaDslBuilder.substConstants(bytes.values, positions.values, newValues.values)(AnyElement) + val values = sigmaDslBuilder.substConstants(bytes.values, positions.values, newValues.values) val len = bytes.size.dataSize + newValues.size.dataSize val cost = opCost(values, Array(bytes.cost, positions.cost, newValues.cost), perKbCostOf(node, len)) mkCostedColl(values, len.toInt, cost) diff --git a/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala b/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala index ed34043c1d..32c9c303a8 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala @@ -348,7 +348,7 @@ trait TreeBuilding extends RuntimeCosting { IR: IRContext => mkByteArrayToLong(recurse(colSym)) case SDBM.decodePoint(_, colSym) => mkDecodePoint(recurse(colSym)) - case SDBM.substConstants(_, In(scriptBytes), In(positions), In(newValues), _) => + case SDBM.substConstants(_, In(scriptBytes), In(positions), In(newValues)) => mkSubstConst(scriptBytes.asByteArray, positions.asIntArray, newValues.asCollection[SType]) case Def(IfThenElseLazy(condSym, thenPSym, elsePSym)) => diff --git a/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala b/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala index 56aad0befd..ad260af0c7 100644 --- a/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala +++ b/sigmastate/src/test/scala/special/sigma/ContractsTestkit.scala @@ -46,7 +46,7 @@ trait ContractsTestkit { } def contextVars(m: Map[Byte, AnyValue]): Coll[AnyValue] = { - val maxKey = if (m.keys.isEmpty) 0 else m.keys.max + val maxKey = if (m.keys.isEmpty) 0 else m.keys.max // TODO optimize: max takes 90% of this method val res = new Array[AnyValue](maxKey) for ((id, v) <- m) { val i = id - 1 From 3e5d4fc915ab152239fab84c7f02026fa021284f Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 5 Jun 2021 12:37:44 +0300 Subject: [PATCH 43/52] prepare-v5.0: part 2: review comments addressed --- sigma-api/src/main/scala/special/sigma/SigmaDsl.scala | 11 ++++++++++- .../scala/sigmastate/eval/CostingDataContext.scala | 5 +++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala index aa1a7086ae..b7719ae0be 100644 --- a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala +++ b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala @@ -547,9 +547,18 @@ trait AvlTree { */ def remove(operations: Coll[Coll[Byte]], proof: Coll[Byte]): Option[AvlTree] + /** Creates a new instance of [[AvlTreeVerifier]] with the given `proof` and using + * properties of this AvlTree (digest, keyLength, valueLengthOpt) for constructor + * arguments. + * + * @param proof bytes of the serialized proof which is used to represent the tree. + */ def createVerifier(proof: Coll[Byte]): AvlTreeVerifier } +/** Represents operations of AVL tree verifier in an abstract (implementation independent) + * way which allows declaration of the [[AvlTree.createVerifier()]] method. + */ trait AvlTreeVerifier { /** * If operation.key exists in the tree and the operation succeeds, @@ -560,7 +569,7 @@ trait AvlTreeVerifier { * After one failure, all subsequent operations will fail and digest * is None. * - * @param operation + * @param operation an operation descriptor * @return - Success(Some(old value)), Success(None), or Failure */ def performOneOperation(operation: Operation): Try[Option[ADValue]] diff --git a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala index 24eddd380a..9b7ad8a88c 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala @@ -98,6 +98,11 @@ case class CSigmaProp(sigmaTree: SigmaBoolean) extends SigmaProp with WrapperOf[ override def toString: String = s"SigmaProp(${wrappedValue.showToString})" } +/** Implementation of the [[special.sigma.AvlTreeVerifier]] trait based on + * [[scorex.crypto.authds.avltree.batch.BatchAVLVerifier]]. + * + * @see BatchAVLVerifier, AvlTreeVerifier + */ class CAvlTreeVerifier(startingDigest: ADDigest, proof: SerializedAdProof, override val keyLength: Int, From a7daeb3e53becd6b8328dba1de26101b8328c1d4 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 5 Jun 2021 13:12:22 +0300 Subject: [PATCH 44/52] prepare-v5.0: part 2: MaxCollSize removed --- .../src/main/scala/org/ergoplatform/SigmaConstants.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/SigmaConstants.scala b/sigmastate/src/main/scala/org/ergoplatform/SigmaConstants.scala index b1f02a2b98..5f3c86adc5 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/SigmaConstants.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/SigmaConstants.scala @@ -76,10 +76,6 @@ object SigmaConstants { "size of nonce array from Autolykos POW solution in Header.powNonce array") { } - object MaxCollSize extends SizeConstant[Int](4 * 1024, 16, - "Maximum number of items in a collection") { - } - val ConstTable: Seq[SizeConstant[_]] = { val rows = Seq( MaxBoxSize, @@ -96,8 +92,7 @@ object SigmaConstants { ScriptCostLimit, MaxLoopLevelInCostFunction, VotesArraySize, - AutolykosPowSolutionNonceArraySize, - MaxCollSize + AutolykosPowSolutionNonceArraySize ) require(rows.length == rows.distinctBy(_.id).length, s"Duplicate constant id in $rows") rows From 93c30f89df5c9c903fb0082da6d3e36bd2a8d129 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 5 Jun 2021 13:26:39 +0300 Subject: [PATCH 45/52] prepare-v5.0: part 2: refactor code duplication --- .../src/main/scala/sigmastate/eval/Evaluation.scala | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala index 205743224d..ce7107476e 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala @@ -573,13 +573,8 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => In(input: special.collection.Coll[Byte]@unchecked), In(positions: special.collection.Coll[Int]@unchecked), In(newVals: special.collection.Coll[Any]@unchecked)) => - // TODO refactor: call sigmaDslBuilderValue.substConstants - val typedNewVals = newVals.toArray.map(v => builder.liftAny(v) match { - case Nullable(v) => v - case _ => sys.error(s"Cannot evaluate substConstants($input, $positions, $newVals): cannot lift value $v") - }) - val byteArray = SubstConstants.eval(input.toArray, positions.toArray, typedNewVals)(sigmaDslBuilderValue.validationSettings) - out(sigmaDslBuilderValue.Colls.fromArray(byteArray)) + val res = sigmaDslBuilderValue.substConstants(input, positions, newVals) + out(res) case CBM.replicate(In(b: special.collection.CollBuilder), In(n: Int), xSym @ In(x)) => out(b.replicate(n, x)(asType[Any](xSym.elem.sourceType))) From d200eff0b27ba761f0c8e574a23982e5f272e0fb Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Wed, 9 Jun 2021 08:48:47 +0300 Subject: [PATCH 46/52] update GPG signing key for release; (#725) --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 29e0c208e8..0c8bfebbe8 100644 --- a/build.sbt +++ b/build.sbt @@ -142,7 +142,7 @@ credentials ++= (for { pgpPublicRing := file("ci/pubring.asc") pgpSecretRing := file("ci/secring.asc") pgpPassphrase := sys.env.get("PGP_PASSPHRASE").map(_.toArray) -usePgpKeyHex("28E27A67AEA38DA458C72228CA9254B5E0640FE4") +usePgpKeyHex("C1FD62B4D44BDF702CDF2B726FF59DA944B150DD") def libraryDefSettings = commonSettings ++ testSettings ++ Seq( scalacOptions ++= Seq( From 50741ae1010ecb2ce9a24b60a119944d434428b1 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 9 Jun 2021 15:43:18 +0300 Subject: [PATCH 47/52] LangSpec.md updated --- docs/LangSpec.md | 180 +++++++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 85 deletions(-) diff --git a/docs/LangSpec.md b/docs/LangSpec.md index 5d4e5ffd11..29f8a9a319 100644 --- a/docs/LangSpec.md +++ b/docs/LangSpec.md @@ -2,28 +2,81 @@ ### Introduction -#### ErgoScript language features +ErgoScript is a language to write contracts for [Ergo +blockchain](https://ergoplatform.org). ErgoScript contracts can be compiled to +[ErgoTrees](https://ergoplatform.org/docs/ErgoTree.pdf), serialized and stored +in UTXOs. + +A good starting point to writing contracts is to use [ErgoScript by +Example](https://github.com/ergoplatform/ergoscript-by-example) with [Ergo +Playgrounds](https://github.com/ergoplatform/ergo-playgrounds) or +[Appkit](https://github.com/ergoplatform/ergo-appkit). + +ErgoScript compiler is +[published](https://mvnrepository.com/artifact/org.scorexfoundation/sigma-state) +as a library which is cross compiled for Java 7 and Java 8+ and thus can be used +from any JVM lanugage and also on Android and JavaFX platforms. + +The following example shows how source code of ErgoScript contract can be used +to create new transaction using +[Appkit](https://github.com/ergoplatform/ergo-appkit), see [full +example](https://github.com/aslesarenko/ergo-appkit-examples/blob/master/java-examples/src/main/java/org/ergoplatform/appkit/examples/FreezeCoin.java) +for details. + +```java +// To create transaction we use a builder obtained from the context +// the builder keeps relationship with the context to access necessary blockchain data. +UnsignedTransactionBuilder txB = ctx.newTxBuilder(); + +// create new box using new builder obtained from the transaction builder +// in this case we compile new ErgoContract from source ErgoScript code +OutBox newBox = txB.outBoxBuilder() + .value(amountToPay) + .contract(ctx.compileContract( + ConstantsBuilder.create() + .item("freezeDeadline", ctx.getHeight() + newBoxDelay) + .item("pkOwner", prover.getP2PKAddress().pubkey()) + .build(), + "{ " + + " val deadlinePassed = sigmaProp(HEIGHT > freezeDeadline)" + + " deadlinePassed && pkOwner " + + "}")) + .build(); +``` + +The contract is given as the string literal which contains the block of `val` +declarations followed by the logical expression. The expression defines the all +possible conditions to spend the box. The contract can also contain _named +constants_ (which cannot be represented as literals in the source code). +In the example `freezeDeadline` and `pkOwner` are named constants. The concrete +values of named constants should be given to the compiler (see `compileContract` +method) + +The following sections describe ErgoScript and its operations. + +#### ErgoScript language features overview - syntax borrowed from Scala and Kotlin - standard syntax and semantics for well known constructs (operations, code blocks, if branches etc.) -- high-order with first-class lambdas that are used in collection operations +- high-order language with first-class lambdas which are used in collection operations - call-by-value (eager evaluation) - statically typed with local type inference - blocks are expressions - semicolon inference in blocks -- type constructors: Tuple, Coll, Option +- type constructors: Pair, Coll, Option -#### Operations and constructs +#### Operations and constructs overview -- Binary operations: `>, <, >=, <=, +, -, &&, ||, ==, !=, |, *, ^, ++` +- Binary operations: `>, <, >=, <=, +, -, &&, ||, ==, !=, |, &, *, /, %, ^, ++` - predefined primitives: `blake2b256`, `byteArrayToBigInt`, `proveDlog` etc. - val declarations: `val h = blake2b256(pubkey)` - if-then-else clause: `if (x > 0) 1 else 0` - collection literals: `Coll(1, 2, 3, 4)` -- generic high-order collection operations: `map`, `fold`, `exists`, `forall`, etc. -- accessing fields of any predefined structured objects: `box.value` +- generic high-order collection operations: `map`, `filter`, `fold`, `exists`, `forall`, etc. +- accessing fields of any predefined types: `box.value` +- method invocation for predefined types: `coll.map({ x => x + 1 })` - function invocations (predefined and user defined): `proveDlog(pubkey)` -- user defined functions: `def isProven(pk: GroupElement) = proveDlog(pk).isProven` +- user defined function declarations: `def isProven(pk: GroupElement) = proveDlog(pk).isProven` - lambdas and high-order methods: `OUTPUTS.exists { (out: Box) => out.value >= minToRaise }` #### Data types @@ -31,12 +84,12 @@ In ErgoScript, everything is an object in the sense that we can call member functions and properties on any variable. Some of the types can have a special internal representation - for example, numbers and booleans can be represented as primitive values at runtime - but to the user they look like ordinary classes. -NOTE: in ErgoScript we use *type* and *class* as synonyms, we prefer *type* when talking about primitive values and -*class* when talking about methods. +NOTE: in ErgoScript we use *type*, *class* and *trait* as synonyms, we prefer *type* when talking about primitive values and +*trait* or *class* when talking about methods. Type Name | Description -----------------|------------------------------ -`Any` | a supertype of any other type +`Any` | a supertype of any other type (not used directly in ErgoScript) `Unit` | a type with a single value `()` `Boolean` | a type with two logical values `true` and `false` `Byte` | 8 bit signed integer @@ -44,17 +97,27 @@ Type Name | Description `Int` | 32 bit signed integer `Long` | 64 bit signed integer `BigInt` | 256 bit signed integer -`SigmaProp` | a type which represent a logical value which can be be obtained by executing a Sigma protocol with zero-knowledge proof of knowledge -`AvlTree` | authenticated dynamic dictionary +`SigmaProp` | a type representing a _sigma proposition_ which can be verified by executing a Sigma protocol with zero-knowledge proof of knowledge. Every contract should return a value of this type. +`AvlTree` | represents a digest of authenticated dynamic dictionary and can be used to verify proofs of operations performed on the dictionary `GroupElement` | elliptic curve points `Box` | a box containing a monetary value (in NanoErgs), tokens and registers along with a guarding proposition `Option[T]` | a container which either have some value of type `T` or none. `Coll[T]` | a collection of arbitrary length with all values of type `T` -`(T1,...,Tn)` | tuples, a collection of element where T1, ..., Tn can be different types +`(T1,T2)` | a pair of values where T1, T2 can be different types + +The type constructors `Coll`, `Option`, `(_,_)` can be used to construct complex +types as in the following example. +```scala +{ + val keyValues = OUTPUTS(0).R4[Coll[(Int, Option[Int])]].get + ... +} +``` #### Literal syntax and Constants -Literals are used to introduce values of some types directly in program text like the following examples: +Literals are used to introduce values of some types directly in program text +like in the following example: ``` val unit: Unit = () // unit constant val long: Int = 10 // interger value literal @@ -71,14 +134,15 @@ a constant of any type by using Base64 encoded string (See [predefined function] #### Primitive Types -```scala -class Boolean { - /** Convert true to 1 and false to 0 - * @since 2.0 - */ - def toByte: Byte -} +Below we specify methods of pre-defined types using Scala-like declaration of +classes. Note, the `Boolean` type doesn't have pre-defined methods in addition +to the standard operations. + +Note, ErgoScript doesn't allow to define new `class` types, however it has many +pre-defined classes with methods defined below. +Every numeric type has the following methods. +```scala /** Base supertype for all numeric types. */ class Numeric { /** Convert this Numeric value to Byte. @@ -96,79 +160,25 @@ class Numeric { */ def toInt: Int - /** Convert this Numeric value to Int. + /** Convert this Numeric value to Long. * @throws ArithmeticException if overflow happens. */ def toLong: Long - /** Convert this Numeric value to Int. */ + /** Convert this Numeric value to BigInt. */ def toBigInt: BigInt - - /** Returns a big-endian representation of this numeric in a collection of bytes. - * For example, the long value {@code 0x1213141516171819L} would yield the - * byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}}. - * @since 2.0 - */ - def toBytes: Coll[Byte] - - /** Returns a big-endian representation of this numeric in a collection of Booleans. - * Each boolean corresponds to one bit. - * @since 2.0 - */ - def toBits: Coll[Boolean] - - /** Absolute value of this numeric value. - * @since 2.0 - */ - def toAbs: Numeric - - /** Compares this numeric with that numeric for order. Returns a negative integer, zero, or a positive integer as the - * `this` is less than, equal to, or greater than `that`. - */ - def compareTo(that: Numeric): Int - - /** Returns least of the two (`this` or `that`). - * @since 2.0 - */ - def min(that: Numeric): Numeric - - /** Returns max of the two (`this` or `that`). - * @since 2.0 - */ - def max(that: Numeric): Numeric } +``` + +All the predefined numeric types inherit Numeric class and its method. +Internally the are predefined like the following. +```scala +class Byte extends Numeric class Short extends Numeric class Int extends Numeric class Long extends Numeric - -/** -* All `modQ` operations assume that Q is a global constant (an order of the only one cryptographically strong group -* which is used for all cryptographic operations). -* So it is globally and implicitly used in all methods. -* */ -class BigInt extends Numeric { - /** Returns this `mod` Q, i.e. remainder of division by Q, where Q is an order of the cryprographic group. - * @since 2.0 - */ - def modQ: BigInt - /** Adds this number with `other` by module Q. - * @since 2.0 - */ - def plusModQ(other: BigInt): BigInt // Testnet2 - /** Subracts this number with `other` by module Q. - * @since 2.0 - */ - def minusModQ(other: BigInt): BigInt // Testnet2 - /** Multiply this number with `other` by module Q. - * @since 2.0 - */ - def multModQ(other: BigInt): BigInt // Testnet2 - /** Multiply this number with `other` by module Q. - * @since Mainnet - */ - def multInverseModQ: BigInt // ??? @kushti do we need it -} +class BigInt extends Numeric ``` #### Context Data From 3f9df383e1e81454471db413188774c4830001bc Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 9 Jun 2021 22:50:56 +0300 Subject: [PATCH 48/52] LangSpec.md updated (part 2) --- docs/LangSpec.md | 731 ++++++++---------- .../main/scala/special/sigma/SigmaDsl.scala | 13 +- 2 files changed, 337 insertions(+), 407 deletions(-) diff --git a/docs/LangSpec.md b/docs/LangSpec.md index 29f8a9a319..8c3fed0c4c 100644 --- a/docs/LangSpec.md +++ b/docs/LangSpec.md @@ -171,7 +171,7 @@ class Numeric { ``` All the predefined numeric types inherit Numeric class and its method. -Internally the are predefined like the following. +Internally they are pre-defined like the following. ```scala class Byte extends Numeric @@ -183,128 +183,181 @@ class BigInt extends Numeric #### Context Data -Every script is executed in a context, which is a collection of data available for operations in the script. -The context data is available using variable `CONTEXT` which is of class `Context` defined below. -The following shortcut variables are available in every script to simplify access to the most commonly used context -data. +Every script is executed in a context, which is a collection of data available +for operations in the script. The context data is available using the `CONTEXT` +variable which is of pre-defined class `Context` which is shown below. + +There are also shortcut variables which are available in every script to +simplify access to the most commonly used context data. Variable | Type | Shortcut for ... ------------------|---------------------|---------------------- -`HEIGHT` | `Int` | `CONTEXT.height` -`SELF` | `Box` | `CONTEXT.selfBox` -`INPUTS` | `Coll[Box]` | `CONTEXT.inputs` -`OUTPUTS` | `Coll[Box]` | `CONTEXT.outputs` +`HEIGHT` | `Int` | `CONTEXT.HEIGHT` +`SELF` | `Box` | `CONTEXT.SELF` +`INPUTS` | `Coll[Box]` | `CONTEXT.INPUTS` +`OUTPUTS` | `Coll[Box]` | `CONTEXT.OUTPUTS` + +The following listing shows the methods of pre-defined `Context`, `Header`, +`PreHeader` types. ```scala -/** - * Represents data available in ErgoScript using `CONTEXT` global variable - * @since 2.0 - */ +/** Represents data available in ErgoScript using `CONTEXT` global variable */ class Context { /** Height (block number) of the block which is currently being validated. */ - def height: Int + def HEIGHT: Int /** Box whose proposition is being currently executing */ - def selfBox: Box - - /** Zero based index in `inputs` of `selfBox`. */ - def selfBoxIndex: Int + def SELF: Box - /** A collection of inputs of the current transaction, the transaction where selfBox is one of the inputs. */ - def inputs: Coll[Box] + /** A collection of inputs of the current transaction, the transaction where + * selfBox is one of the inputs. + */ + def INPUTS: Coll[Box] - /** A collection of data inputs of the current transaction. Data inputs are not going to be spent and thus don't - * participate in transaction validation as `inputs`, but data boxes are available in guarding propositions of - * `inputs` and thus can be used in spending logic. - * @since 2.0 - */ + /** A collection of data inputs of the current transaction. Data inputs are + * not going to be spent and thus don't participate in transaction validation + * as `INPUTS`, but data boxes are available in guarding propositions of + * `INPUTS` and thus can be used in spending logic. + */ def dataInputs: Coll[Box] /** A collection of outputs of the current transaction. */ - def outputs: Coll[Box] + def OUTPUTS: Coll[Box] - /** Authenticated dynamic dictionary digest representing Utxo state before current state. */ - def lastBlockUtxoRoot: AvlTree - - /** - * @since 2.0 + /** Authenticated dynamic dictionary digest representing Utxo state before + * current state. */ - def headers: Coll[SHeader] - /** - * @since 2.0 + def LastBlockUtxoRootHash: AvlTree + + /** A fixed number of last block headers in descending order (first header is + * the newest one) */ - def preheader: SPreheader + def headers: Coll[Header] + + /** Header fields that are known before the block is mined. */ + def preHeader: PreHeader } -/** Represents data of the block headers available in scripts. - * @since 2.0 - */ +/** Represents data of the block headers available in scripts. */ class Header { + /** Bytes representation of ModifierId of this Header */ + def id: Coll[Byte] + + /** Block version, to be increased on every soft and hardfork. */ def version: Byte - /** Bytes representation of ModifierId of the previous block in the blockchain */ + /** Id of parent block (as bytes) */ def parentId: Coll[Byte] // + /** Hash of ADProofs for transactions in a block */ def ADProofsRoot: Coll[Byte] // Digest32. Can we build AvlTree out of it? + + /** AvlTree) of a state after block application */ def stateRoot: Coll[Byte] // ADDigest //33 bytes! extra byte with tree height here! + + /** Root hash (for a Merkle tree) of transactions in a block. */ def transactionsRoot: Coll[Byte] // Digest32 + + /** Block timestamp (in milliseconds since beginning of Unix Epoch) */ def timestamp: Long + + /** Current difficulty in a compressed view. + * NOTE: actually it is unsigned Int*/ def nBits: Long // actually it is unsigned Int + + /** Block height */ def height: Int - def extensionRoot: Coll[Byte] // Digest32 - def minerPk: GroupElement // pk - def powOnetimePk: GroupElement // w - def powNonce: Coll[Byte] // n - def powDistance: BigInt // d + + /** Root hash of extension section (Digest32) */ + def extensionRoot: Coll[Byte] + + /** Miner public key. Should be used to collect block rewards. + * Part of Autolykos solution (pk). + */ + def minerPk: GroupElement + + /** One-time public key. Prevents revealing of miners secret. + * Part of Autolykos solution (w). + */ + def powOnetimePk: GroupElement + + /** Nonce value found by the miner. Part of Autolykos solution (n). */ + def powNonce: Coll[Byte] + + /** Distance between pseudo-random number, corresponding to nonce `powNonce` + * and a secret, corresponding to `minerPk`. The lower `powDistance` is, the + * harder it was to find this solution. + * Part of Autolykos solution (d). + */ + def powDistance: BigInt + + /** Miner votes for changing system parameters. */ + def votes: Coll[Byte] } -/** Only header fields that can be predicted by a miner - * @since 2.0 - */ -class PreHeader { // Testnet2 +/** Only header fields that can be predicted by a miner. */ +class PreHeader { + /** Block version, to be increased on every soft and hardfork. */ def version: Byte + + /** Id of parent block */ def parentId: Coll[Byte] // ModifierId + + /** Block timestamp (in milliseconds since beginning of Unix Epoch) */ def timestamp: Long - def nBits: Long // actually it is unsigned Int + + /** Current difficulty in a compressed view. + * NOTE: actually it is 32-bit unsigned Int */ + def nBits: Long + + /** Block height */ def height: Int + + /** Miner public key. Should be used to collect block rewards. */ def minerPk: GroupElement -} -class AvlTree { - /** Returns digest of the state represent by this tree. - * @since 2.0 - */ - def digest: Coll[Byte] + /** Miner votes for changing system parameters. */ + def votes: Coll[Byte] } + ``` #### Box type +Box represents a unit of storage in Ergo blockchain. It contains 10 registers +(indexed 0-9). First 4 are mandatory and the others are optional. + ```scala +/** Representation of Ergo boxes used during execution of ErgoTree operations. */ class Box { /** Box monetary value in NanoErg */ def value: Long - /** Blake2b256 hash of this box's content, basically equals to `blake2b256(bytes)` */ + /** Blake2b256 hash of this box's content, basically equals to + * `blake2b256(bytes)` + */ def id: Coll[Byte] - /** Serialized bytes of guarding script, which should be evaluated to true in order to open this box. */ + /** Serialized bytes of guarding script, which should be evaluated to true in + * order to open this box. + */ def propositionBytes: Coll[Byte] /** Serialized bytes of this box's content, including proposition bytes. */ def bytes: Coll[Byte] - /** Serialized bytes of this box's content, excluding transactionId and index of output. */ + /** Serialized bytes of this box's content, excluding transactionId and index + * of output. + */ def bytesWithoutRef: Coll[Byte] - /** If `tx` is a transaction which generated this box, then `creationInfo._1` is a height of the tx's block. - * The `creationInfo._2` is a serialized transaction identifier followed by box index in the transaction outputs. - */ + /** If `tx` is a transaction which generated this box, then `creationInfo._1` + * is a height of the tx's block. The `creationInfo._2` is a serialized + * transaction identifier followed by box index in the transaction outputs. + */ def creationInfo: (Int, Coll[Byte]) - /** Synonym of R2 obligatory register - * @since 2.0 - */ + /** Synonym of R2 obligatory register */ def tokens: Coll[(Coll[Byte], Long)] /** Extracts register by id and type. @@ -363,51 +416,171 @@ class Box { * None otherwise * @throws special.sigma.InvalidType exception when the type of the register value is * different from T. - * @since 2.0 */ - def getReg[T](i: Int): Option[T] - - /** Extracts register as Coll[Byte], deserializes it to script and then executes this script in the current context. - * The original Coll[Byte] of the script is available as getReg[Coll[Byte]](id) - * @param regId identifier of the register - * @tparam T result type of the deserialized script. - * @throws InterpreterException if the actual script type doesn't conform to T - * @return result of the script execution in the current context - * @since Mainnet - */ - def executeFromRegister[T](regId: Byte): T + def Ri[T]: Option[T] } ``` Besides properties, every box can have up to 10 numbered registers. The following syntax is supported to access registers on box objects: ``` -box.getReg[Int](3).get // access R3 register, cast its value to Int and return it -box.getReg[Int](3).isDefined // check that value of R3 is defined and has type Int -box.getReg[Int](3).isEmpty // check that value of R3 is either undefined or have not Int type -box.getReg[Int](3).getOrElse(def) // access R3 value if defined, otherwise return def +box.R3[Int].get // access R3 register, check that its value of type Int and return it +box.R3[Int].isDefined // check that value of R3 is defined and has type Int +box.R3[Int].isEmpty // check that value of R3 is undefined +box.R3[Int].getOrElse(d) // access R3 value if defined, otherwise return `d` ``` #### GroupElement ```scala +/** Base class for points on elliptic curves. */ class GroupElement { - // ... - /** - * // this should replace the currently used ^ - * @since 2.0 - */ - def exp(n: BigInt): GroupElement // Testnet2 + /** Exponentiate this GroupElement to the given number. + * @param k The power. + * @return this to the power of k. + */ + def exp(k: BigInt): GroupElement + + /** Group operation. */ + def multiply(that: GroupElement): GroupElement + + /** Inverse element in the group. */ + def negate: GroupElement + + /** Get an encoding of the point value. + * + * @return the point encoding + */ + def getEncoded: Coll[Byte] } ``` #### AvlTree ```scala +/** Type of data which efficiently authenticates potentially huge dataset having key-value dictionary interface. + * Only root hash of dynamic AVL+ tree, tree height, key length, optional value length, and access flags are stored + * in an instance of the datatype. + * + * Please note that standard hash function from `scorex.crypto.hash` is used, and height is stored along with root hash of + * the tree, thus `digest` size is always CryptoConstants.hashLength + 1 bytes. + */ class AvlTree { - /** - * @since 2.0 - */ + /** Returns digest of the state represented by this tree. + * Authenticated tree digest = root hash bytes ++ tree height + */ def digest: Coll[Byte] + + /** Flags of enabled operations packed in single byte. + * isInsertAllowed == (enabledOperations & 0x01) != 0 + * isUpdateAllowed == (enabledOperations & 0x02) != 0 + * isRemoveAllowed == (enabledOperations & 0x04) != 0 + */ + def enabledOperations: Byte + + /** All the elements under the tree have the same length of the keys */ + def keyLength: Int + + /** If non-empty, all the values under the tree are of the same length. */ + def valueLengthOpt: Option[Int] + + /** Checks if Insert operation is allowed for this tree instance. */ + def isInsertAllowed: Boolean + + /** Checks if Update operation is allowed for this tree instance. */ + def isUpdateAllowed: Boolean + + /** Checks if Remove operation is allowed for this tree instance. */ + def isRemoveAllowed: Boolean + + /** Replace digest of this tree producing a new tree. + * Since AvlTree is immutable, this tree instance remains unchanged. + * @param newDigest a new digest + * @return a copy of this AvlTree instance where `this.digest` replaced by + * `newDigest` + */ + def updateDigest(newDigest: Coll[Byte]): AvlTree + + /** Enable/disable operations of this tree producing a new tree. + * Since AvlTree is immutable, `this` tree instance remains unchanged. + * @param newOperations a new flags which specify available operations on a + * new tree. + * @return a copy of this AvlTree instance where + * `this.enabledOperations` replaced by `newOperations` + */ + def updateOperations(newOperations: Byte): AvlTree + + /** Checks if an entry with key `key` exists in this tree using proof `proof`. + * Throws exception if proof is incorrect. + * + * @note CAUTION! Does not support multiple keys check, use [[getMany]] instead. + * Return `true` if a leaf with the key `key` exists + * Return `false` if leaf with provided key does not exist. + * @param key a key of an element of this authenticated dictionary. + * @param proof data to reconstruct part of the tree enough to perform the check + */ + def contains(key: Coll[Byte], proof: Coll[Byte]): Boolean + + /** Perform a lookup of key `key` in this tree using proof `proof`. + * Throws exception if proof is incorrect + * + * @note CAUTION! Does not support multiple keys check, use [[getMany]] instead. + * Return Some(bytes) of leaf with key `key` if it exists + * Return None if leaf with provided key does not exist. + * @param key a key of an element of this authenticated dictionary. + * @param proof data to reconstruct part of the tree enough to get the value + * by the key + */ + def get(key: Coll[Byte], proof: Coll[Byte]): Option[Coll[Byte]] + + /** Perform a lookup of many keys `keys` in this tree using proof `proof`. + * + * @note CAUTION! Keys must be ordered the same way they were in lookup + * before proof was generated. + * For each key return Some(bytes) of leaf if it exists and None if is doesn't. + * @param keys keys of elements of this authenticated dictionary. + * @param proof data to reconstruct part of the tree enough to get the values + * by the keys + */ + def getMany(keys: Coll[Coll[Byte]], proof: Coll[Byte]): Coll[Option[Coll[Byte]]] + + /** Perform insertions of key-value entries into this tree using proof `proof`. + * Throws exception if proof is incorrect + * + * @note CAUTION! Pairs must be ordered the same way they were in insert ops + * before proof was generated. + * Return Some(newTree) if successful + * Return None if operations were not performed. + * @param operations collection of key-value pairs to insert in this + * authenticated dictionary. + * @param proof data to reconstruct part of the tree + */ + def insert(operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree] + + /** Perform updates of key-value entries into this tree using proof `proof`. + * Throws exception if proof is incorrect + * + * @note CAUTION! Pairs must be ordered the same way they were in update ops + * before proof was generated. + * Return Some(newTree) if successful + * Return None if operations were not performed. + * @param operations collection of key-value pairs to update in this + * authenticated dictionary. + * @param proof data to reconstruct part of the tree + */ + def update(operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree] + + /** Perform removal of entries into this tree using proof `proof`. + * Throws exception if proof is incorrect + * Return Some(newTree) if successful + * Return None if operations were not performed. + * + * @note CAUTION! Keys must be ordered the same way they were in remove ops + * before proof was generated. + * @param operations collection of keys to remove from this authenticated + * dictionary. + * @param proof data to reconstruct part of the tree + */ + def remove(operations: Coll[Coll[Byte]], proof: Coll[Byte]): Option[AvlTree] } ``` @@ -418,10 +591,6 @@ class AvlTree { * are either an instance of `Some(x)` or the value `None`. */ class Option[A] { - /** Returns true if the option is None, false otherwise. - */ - def isEmpty: Boolean; - /** Returns true if the option is an instance of Some(value), false otherwise. */ def isDefined: Boolean; @@ -439,12 +608,6 @@ class Option[A] { */ def get: A - /** Returns a singleton collection containing the $option's value - * if it is nonempty, or the empty collection if the $option is empty. - * @since 2.0 - */ - def toColl: Coll[A] - /** Returns a Some containing the result of applying $f to this option's * value if this option is nonempty. * Otherwise return None. @@ -465,18 +628,6 @@ class Option[A] { * @since 2.0 */ def filter(p: A => Boolean): Option[A] - - /** Returns the result of applying $f to this option's value if - * this option is nonempty. - * Returns None if this option is empty. - * Slightly different from `map` in that $f is expected to - * return an option (which could be None). - * - * @param f the function to apply - * @see map - * @since 2.0 - */ - def flatMap[B](f: A => Option[B]): Option[B] } ``` @@ -484,13 +635,11 @@ class Option[A] { ```scala /** Indexed (zero-based) collection of elements of type `A` - * @define Coll `Coll` - * @define coll collection * @tparam A the collection element type */ class Coll[A] { - /** The length of the collection */ - def length: Int + /** The number of elements in the collection */ + def size: Int /** The element at given index. * Indices start at `0`; `xs.apply(0)` is the first element of collection `xs`. @@ -506,7 +655,6 @@ class Coll[A] { * If an index is out of bounds (`i < 0 || i >= length`) then `default` value is returned. * @param i the index * @return the element at the given index or default value if index is out or bounds - * @since 2.0 */ def getOrElse(i: Int, default: A): A @@ -521,9 +669,8 @@ class Coll[A] { /** For this collection (x0, ..., xN) and other collection (y0, ..., yM) * produces a collection ((x0, y0), ..., (xK, yK)) where K = min(N, M) - * @since 2.0 */ - def zip[B](ys: Coll[B]): PairColl[A, B] + def zip[B](ys: Coll[B]): Coll[(A, B)] /** Tests whether a predicate holds for at least one element of this collection. * @param p the predicate used to test elements. @@ -542,7 +689,6 @@ class Coll[A] { * @param p the predicate used to test elements. * @return a new collection consisting of all elements of this collection that satisfy the given * predicate `p`. The order of the elements is preserved. - * @since 2.0 */ def filter(p: A => Boolean): Coll[A] @@ -560,11 +706,9 @@ class Coll[A] { * where `x,,1,,, ..., x,,n,,` are the elements of this collection. * Returns `z` if this collection is empty. */ - def foldLeft[B](z: B)(op: (B, A) => B): B + def fold[B](z: B, op: (B, A) => B): B - /** Produces the range of all indices of this collection [0 .. size-1] - * @since 2.0 - */ + /** Produces the range of all indices of this collection [0 .. size-1] */ def indices: Coll[Int] /** @@ -578,49 +722,9 @@ class Coll[A] { * @tparam B the element type of the returned collection. * @return a new collection of type `Coll[B]` resulting from applying the given collection-valued function * `f` to each element of this collection and concatenating the results. - * @since 2.0 */ def flatMap[B](f: A => Coll[B]): Coll[B] - /** Finds the first element of the $coll satisfying a predicate, if any. - * - * @param p the predicate used to test elements. - * @return an option value containing the first element in the $coll - * that satisfies `p`, or `None` if none exists. - */ - def find(p: A => Boolean): Option[A] - - /** Finds index of the first element satisfying some predicate after or at some start index. - * - * @param p the predicate used to test elements. - * @param from the start index - * @return the index `>= from` of the first element of this collection that satisfies the predicate `p`, - * or `-1`, if none exists. - * @since 2.0 - */ - def indexWhere(p: A => Boolean, from: Int): Int - - /** Finds index of last element satisfying some predicate before or at given end index. - * - * @param p the predicate used to test elements. - * @return the index `<= end` of the last element of this collection that satisfies the predicate `p`, - * or `-1`, if none exists. - * @since 2.0 - */ - def lastIndexWhere(p: A => Boolean, end: Int): Int - - - /** Partitions this collection in two collectionss according to a predicate. - * - * @param pred the predicate on which to partition. - * @return a pair of collections: the first collection consists of all elements that - * satisfy the predicate `p` and the second collection consists of all elements - * that don't. The relative order of the elements in the resulting collections - * will BE preserved (this is different from Scala's version of this method). - * @since 2.0 - */ - def partition(pred: A => Boolean): (Coll[A], Coll[A]) - /** Produces a new collection where a slice of elements in this collection is replaced by another sequence. * * @param from the index of the first replaced element @@ -628,7 +732,6 @@ class Coll[A] { * @param replaced the number of elements to drop in the original collection * @return a new collection consisting of all elements of this collection * except that `replaced` elements starting from `from` are replaced by `patch`. - * @since 2.0 */ def patch(from: Int, patch: Coll[A], replaced: Int): Coll[A] @@ -637,95 +740,14 @@ class Coll[A] { * @param elem the replacing element * @return a new collection which is a copy of this collection with the element at position `index` replaced by `elem`. * @throws IndexOutOfBoundsException if `index` does not satisfy `0 <= index < length`. - * @since 2.0 */ def updated(index: Int, elem: A): Coll[A] - /** Returns a copy of this collection where elements at `indexes` are replaced with `values`. - * @since 2.0 - */ - def updateMany(indexes: Coll[Int], values: Coll[A]): Coll[A] - - /** Apply m for each element of this collection, group by key and reduce each group using r. - * @returns one item for each group in a new collection of (K,V) pairs. - * @since 2.0 - */ - def mapReduce[K, V](m: A => (K,V), r: (V,V) => V): Coll[(K,V)] - - /** Partitions this $coll into a map of ${coll}s according to some discriminator function. - * - * @param key the discriminator function. - * @tparam K the type of keys returned by the discriminator function. - * @return A map from keys to ${coll}s such that the following invariant holds: - * {{{ - * (xs groupBy key)(k) = xs filter (x => key(x) == k) - * }}} - * That is, every key `k` is bound to a $coll of those elements `x` - * for which `key(x)` equals `k`. - */ - def groupBy[K: RType](key: A => K): Coll[(K, Coll[A])] - - /** Partitions this $coll into a map of ${coll}s according to some discriminator function. - * Additionally projecting each element to a new value. - * - * @param key the discriminator function. - * @param proj projection function to produce new value for each element of this $coll - * @tparam K the type of keys returned by the discriminator function. - * @tparam V the type of values returned by the projection function. - * @return A map from keys to ${coll}s such that the following invariant holds: - * {{{ - * (xs groupByProjecting (key, proj))(k) = xs filter (x => key(x) == k).map(proj) - * }}} - * That is, every key `k` is bound to projections of those elements `x` - * for which `key(x)` equals `k`. + /** Returns a copy of this collection where elements at `indexes` are replaced + * with `values`. */ - def groupByProjecting[K: RType, V: RType](key: A => K, proj: A => V): Coll[(K, Coll[V])] - - /** Produces a new collection which contains all distinct elements of this collection and also all elements of - * a given collection that are not in this collection. - * This is order preserving operation considering only first occurrences of each distinct elements. - * Any collection `xs` can be transformed to a sequence with distinct elements by using xs.unionSet(Coll()). - * - * NOTE: Use append if you don't need set semantics. - * - * @param that the collection to add. - * @since 2.0 - */ - def unionSets(that: Coll[A]): Coll[A] - - /** Computes the multiset difference between this collection and another sequence. - * - * @param that the sequence of elements to remove - * @tparam B the element type of the returned collection. - * @return a new collection which contains all elements of this collection - * except some of occurrences of elements that also appear in `that`. - * If an element value `x` appears - * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form - * part of the result, but any following occurrences will. - * @since 2.0 - */ - def diff(that: Coll[A]): Coll[A] - - /** Computes the multiset intersection between this collection and another sequence. - * @param that the sequence of elements to intersect with. - * @return a new collection which contains all elements of this collection - * which also appear in `that`. - * If an element value `x` appears - * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained - * in the result, but any following occurrences will be omitted. - * @since 2.0 - */ - def intersect(that: Coll[A]): Coll[A] + def updateMany(indexes: Coll[Int], values: Coll[A]): Coll[A] - /** Folding through all elements of this $coll starting from m.zero and applying m.plus to accumulate - * resulting value. - * - * @param m monoid object to use for summation - * @return result of the following operations (m.zero `m.plus` x1 `m.plus` x2 `m.plus` ... xN) - * @since 2.0 - */ - def sum(m: Monoid[A]): A - /** Selects an interval of elements. The returned collection is made up * of all elements `x` which satisfy the invariant: * {{{ @@ -736,71 +758,19 @@ class Coll[A] { */ def slice(from: Int, until: Int): Coll[A] - /** Puts the elements of other collection after the elements of this collection (concatenation of 2 collections) - */ + /** Puts the elements of other collection after the elements of this + * collection (concatenation of 2 collections). + */ def append(other: Coll[A]): Coll[A] - /** Returns the length of the longest prefix whose elements all satisfy some predicate. - * @param p the predicate used to test elements. - * @return the length of the longest prefix of this collection - * such that every element of the segment satisfies the predicate `p`. - * @since 2.0 - */ - def prefixLength(p: A => Boolean): Int - - /** Finds index of first occurrence of some value in this collection after or at some start index. - * @param elem the element value to search for. - * @param from the start index - * @return the index `>= from` of the first element of this collection that is equal (as determined by `==`) - * to `elem`, or `-1`, if none exists. - * @since 2.0 - */ - def indexOf(elem: A, from: Int): Int - - /** Finds index of last occurrence of some value in this collection before or at a given end index. - * - * @param elem the element value to search for. - * @param end the end index. - * @return the index `<= end` of the last element of this collection that is equal (as determined by `==`) - * to `elem`, or `-1`, if none exists. - * @since 2.0 - */ - def lastIndexOf(elem: A, end: Int): Int - - /** Finds the first element of the collection satisfying a predicate, if any. - * @param p the predicate used to test elements. - * @return an option value containing the first element in the collection - * that satisfies `p`, or `None` if none exists. - * @since 2.0 - */ - def find(p: A => Boolean): Option[A] - - /** Builds a new collection from this collection without any duplicate elements. - * @return A new collection which contains the first occurrence of every element of this collection. - * @since 2.0 - */ - def distinct: Coll[A] - - /** Tests whether this collection contains the given sequence at a given index. - * - * '''Note''': If the both the receiver object `this` and the argument - * `that` are infinite sequences this method may not terminate. - * - * @param that the sequence to test - * @param offset the index where the sequence is searched. - * @return `true` if the sequence `that` is contained in this collection at - * index `offset`, otherwise `false`. - * @since 2.0 - */ - def startsWith(that: Coll[A], offset: Int): Boolean - - /** Tests whether this collection ends with the given collection. - * @param that the collection to test - * @return `true` if this collection has `that` as a suffix, `false` otherwise. - * @since 2.0 + /** Finds index of first occurrence of some value in this collection after or + * at some start index. + * @param elem the element value to search for. + * @param from the start index + * @return the index `>= from` of the first element of this collection that is equal (as determined by `==`) + * to `elem`, or `-1`, if none exists. */ - def endsWith(that: Coll[A]): Boolean - + def indexOf(elem: A, from: Int): Int } ``` @@ -810,14 +780,15 @@ val myOutput = OUTPUTS(0) val myInput = INPUTS(0) ``` -Any collection have a `length` property which returns number of elements in a collection. +Any collection have the `size` property which returns the number of elements in +the collection. ``` -val l = OUTPUTS.length +val size = OUTPUTS.size ``` -The following script check an existence of some element in the collection satisfying some -predicate (condition) +The following script check an existence of some element in the collection +satisfying some predicate (condition) ``` val ok = OUTPUTS.exists { (box: Box) => box.value > 1000 } @@ -838,9 +809,7 @@ def allOf(conditions: Coll[Boolean]): Boolean /** Returns true if at least on element of the conditions is true */ def anyOf(conditions: Coll[Boolean]): Boolean -/** Similar to allOf, but performing logical XOR operation instead of `&&` - * @since 2.0 - */ +/** Similar to allOf, but performing logical XOR operation instead of `&&` */ def xorOf(conditions: Coll[Boolean]): Boolean /** Returns SigmaProp value which can be ZK proven to be true @@ -858,13 +827,14 @@ def atLeast(k: Int, properties: Coll[SigmaProp]): SigmaProp * - no boolean operations in the body, because otherwise the result may be disclosed * - all the operations are over SigmaProp values * - * For motivation and details see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/236 - * @since 1.9 + * For motivation and details see + * https://github.com/ScorexFoundation/sigmastate-interpreter/issues/236 */ def ZKProof(block: SSigmaProp): Boolean -/** Embedding of Boolean values to SigmaProp values. As an example, this operation allows boolean experesions - * to be used as arguments of `atLeast(..., sigmaProp(myCondition), ...)` operation. +/** Embedding of Boolean values to SigmaProp values. As an example, this + * operation allows boolean experesions to be used as arguments of + * `atLeast(..., sigmaProp(myCondition), ...)` operation. */ def sigmaProp(condition: Boolean): SigmaProp @@ -880,118 +850,75 @@ def byteArrayToBigInt(input: Coll[Byte]): BigInt /** Create Long from a collection of bytes. */ def byteArrayToLong(input: Coll[Byte]): Long -/** Returns bytes representation of Long value. - * @since 2.0 - */ +/** Returns bytes representation of Long value. */ def longToByteArray(input: Long): Coll[Byte] -/** - * Convert bytes representation of group element (ECPoint) - * to a new value of GroupElement (using org.bouncycastle.math.ec.ECCurve.decodePoint()) - * @since 1.9 +/** Convert bytes representation of group element (ECPoint) + * to a new value of GroupElement (using + * org.bouncycastle.math.ec.ECCurve.decodePoint()) */ def decodePoint(bytes: Coll[Byte]): GroupElement - /** Returns value of the given type from the context by its tag.*/ def getVar[T](tag: Int): Option[T] -/** Construct a new SigmaProp value representing public key of Diffie Hellman signature protocol. - * When executed as part of Sigma protocol allow to provide for a verifier a zero-knowledge proof - * of secret knowledge. +/** Construct a new SigmaProp value representing public key of Diffie Hellman + * signature protocol. When executed as part of Sigma protocol allow to provide + * for a verifier a zero-knowledge proof of secret knowledge. */ def proveDHTuple(g: GroupElement, h: GroupElement, u: GroupElement, v: GroupElement): SigmaProp -/** Construct a new SigmaProp value representing public key of discrete logarithm signature protocol. - * When executed as part of Sigma protocol allow to provide for a verifier a zero-knowledge proof - * of secret knowledge. +/** Construct a new SigmaProp value representing public key of discrete + * logarithm signature protocol. When executed as part of Sigma protocol allow + * to provide for a verifier a zero-knowledge proof of secret knowledge. */ def proveDlog(value: GroupElement): SigmaProp -/** Predicate function which checks whether a key is in a tree, by using a membership proof. */ -def isMember(tree: AvlTree, key: Coll[Byte], proof: Coll[Byte]): Boolean - -/** - * Perform a lookup of key `key` in a tree with root `tree` using proof `proof`. - * Throws exception if proof is incorrect - * Return Some(bytes) of leaf with key `key` if it exists - * Return None if leaf with provided key does not exist. - */ -def treeLookup(tree: AvlTree, key: Coll[Byte], proof: Coll[Byte]): Option[Coll[Byte]] - -/** Perform modification of in the tree with root `tree` using proof `proof`. - * Return Some(newTree) if successfull - * Return None if the proof was not correct - * @since 2.0 - */ -def treeModifications(tree: AvlTree, ops: Coll[Byte], proof: Coll[Byte]): Option[AvlTree] - -/** - * Transforms Base58 encoded string litereal into constant of type Coll[Byte]. - * It is a compile-time operation and only string literal (constant) can be its argument. +/** Transforms Base58 encoded string litereal into constant of type Coll[Byte]. + * It is a compile-time operation and only string literal (constant) can be its + * argument. */ def fromBase58(input: String): Coll[Byte] -/** - * Transforms Base64 encoded string litereal into constant of type Coll[Byte]. - * It is a compile-time operation and only string literal (constant) can be its argument. +/** Transforms Base64 encoded string litereal into constant of type Coll[Byte]. + * It is a compile-time operation and only string literal (constant) can be its + * argument. */ def fromBase64(input: String): Coll[Byte] -/** - * It is executed in compile time. - * The compiler takes Base58 encoding of public key as String literal and create GroupElement constant. - * Then the compiler used this constant to construct proveDlog public key out of it. - * @since 1.9 +/** It is executed in compile time. The compiler takes Base58 encoding of public + * key as String literal and create GroupElement constant. Then the compiler + * used this constant to construct proveDlog public key out of it. */ def PK(input: String): SigmaProp -/** Deserializes values from Base58 encoded binary data at compile time */ -def deserialize[T](string: String): T - -/** Extracts context variable as Coll[Byte], deserializes it to script and then executes this script in the current context. - * The original `Coll[Byte]` of the script is available as `getVar[Coll[Byte]](id)` - * @param id identifier of the context variable - * @tparam T result type of the deserialized script. - * @throws InterpreterException if the actual script type doesn't conform to T - * @return result of the script execution in the current context - * @since 2.0 +/** Deserializes values from Base58 encoded binary data at compile time into a + * value of type T. */ -def executeFromVar[T](id: Byte): T +def deserialize[T](string: String): T /** - * Transforms serialized bytes of ErgoTree with segregated constants by replacing constants - * at given positions with new values. This operation allow to use serialized scripts as - * pre-defined templates. - * The typical usage is "check that output box have proposition equal to given script bytes, - * where minerPk (constants(0)) is replaced with currentMinerPk". - * Each constant in original scriptBytes have SType serialized before actual data (see ConstantSerializer). - * During substitution each value from newValues is checked to be an instance of the corresponding type. - * This means, the constants during substitution cannot change their types. + * Transforms serialized bytes of ErgoTree with segregated constants by + * replacing constants at given positions with new values. This operation allow + * to use serialized scripts as pre-defined templates. + + * The typical usage is "check that output box have proposition equal to given + * script bytes, where minerPk (constants(0)) is replaced with currentMinerPk". + * Each constant in original scriptBytes have SType serialized before actual + * data (see ConstantSerializer). During substitution each value from newValues + * is checked to be an instance of the corresponding type. This means, the + * constants during substitution cannot change their types. * * @param scriptBytes serialized ErgoTree with ConstantSegregationFlag set to 1. - * @param positions zero based indexes in ErgoTree.constants array which should be replaced with new values - * @param newValues new values to be injected into the corresponding positions in ErgoTree.constants array - * @return original scriptBytes array where only specified constants are replaced and all other bytes remain exactly the same - * @since 1.9 + * @param positions zero based indexes in ErgoTree.constants array which should + * be replaced with new values + * @param newValues new values to be injected into the corresponding positions + * in ErgoTree.constants array + * @return original scriptBytes array where only specified constants are + * replaced and all other bytes remain exactly the same */ def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], newValues: Coll[T]): Coll[Byte] - -/** Performs outer join operation between left and right collections. - * This is a restricted version of relational join operation. - * It expects `left` and `right` collections have distinct K values in pairs (otherwise exception is thrown). - * Under this condition resulting collection has size <= left.size + right.size. - * @param l projection function executed for each element of `left` - * @param r projection function executed for each element of `right` - * @param inner projection function which is executed for matching items (K, L) and (K, R) with the same K - * @return collection of (K, O) pairs, where each key comes form either left or right collection and values are produced by projections - * @since 2.0 - */ -def outerJoin[K, L, R, O] - (left: Coll[(K, L)], right: Coll[(K, R)]) - (l: (K,L) => O, r: (K,R) => O, inner: (K,L,R) => O): Coll[(K,O)] // Mainnet - ``` ## Examples diff --git a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala index 78488d11cf..e587bb6bfb 100644 --- a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala +++ b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala @@ -487,7 +487,7 @@ trait AvlTree { * Return `true` if a leaf with the key `key` exists * Return `false` if leaf with provided key does not exist. * @param key a key of an element of this authenticated dictionary. - * @param proof + * @param proof data to reconstruct part of the tree enough to perform the check */ def contains(key: Coll[Byte], proof: Coll[Byte]): Boolean @@ -498,7 +498,8 @@ trait AvlTree { * Return Some(bytes) of leaf with key `key` if it exists * Return None if leaf with provided key does not exist. * @param key a key of an element of this authenticated dictionary. - * @param proof + * @param proof data to reconstruct part of the tree enough to get the value + * by the key */ def get(key: Coll[Byte], proof: Coll[Byte]): Option[Coll[Byte]] @@ -518,7 +519,7 @@ trait AvlTree { * Return Some(newTree) if successful * Return None if operations were not performed. * @param operations collection of key-value pairs to insert in this authenticated dictionary. - * @param proof + * @param proof data to reconstruct part of the tree */ def insert(operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree] @@ -529,7 +530,7 @@ trait AvlTree { * Return Some(newTree) if successful * Return None if operations were not performed. * @param operations collection of key-value pairs to update in this authenticated dictionary. - * @param proof + * @param proof data to reconstruct part of the tree */ def update(operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree] @@ -540,7 +541,7 @@ trait AvlTree { * * @note CAUTION! Keys must be ordered the same way they were in remove ops before proof was generated. * @param operations collection of keys to remove from this authenticated dictionary. - * @param proof + * @param proof data to reconstruct part of the tree */ def remove(operations: Coll[Coll[Byte]], proof: Coll[Byte]): Option[AvlTree] } @@ -569,6 +570,7 @@ trait PreHeader { // Testnet2 /** Miner public key. Should be used to collect block rewards. */ def minerPk: GroupElement + /** Miner votes for changing system parameters. */ def votes: Coll[Byte] } @@ -622,6 +624,7 @@ trait Header { * corresponding to `minerPk`. The lower `powDistance` is, the harder it was to find this solution. */ def powDistance: BigInt + /** Miner votes for changing system parameters. */ def votes: Coll[Byte] //3 bytes } From d590368f0e934357e945333a28851d8e07623efd Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 11 Jun 2021 12:14:48 +0300 Subject: [PATCH 49/52] LangSpec.md: fixed examples ref --- docs/LangSpec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/LangSpec.md b/docs/LangSpec.md index 8c3fed0c4c..38cbc476c4 100644 --- a/docs/LangSpec.md +++ b/docs/LangSpec.md @@ -923,4 +923,4 @@ def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], newValues: ## Examples -See [white paper for example](wpaper/sigma.tex) +See [white paper for examples](https://ergoplatform.org/docs/ErgoScript.pdf) From 542849bcc83d4509d3143062b8bb68525d5af1b6 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 16 Jun 2021 14:11:07 +0300 Subject: [PATCH 50/52] README.md updated --- README.md | 168 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 123 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 078e861e80..56f6a48062 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,123 @@ -# sigmastate-interpreter -Interpreter for a family of Sigma-State authentication languages - -## Brief Description - -Every coin in Bitcoin is protected by a program in the stack-based Script language. An interpreter for the language is -evaluating the program against a redeeming program (in the same language) as well as a context (few variables -containing information about a spending transaction and the blockchain), producing a single boolean value as a result. -While Bitcoin Script allows for some contracts to be programmed, its abilities are limited while many instructions -were removed after denial-of-service or security issues discovered. Also, to add new cryptographic primitives, for example, -ring signatures, a hard-fork is required. - -Generalizing the Bitcoin Script, we introduce a notion of an authentication language where a verifier is running an -interpreter which three inputs are a proposition defined in terms of the language, a context and also a proof (not -necessarily defined in the same language) generated by a prover for the proposition against the same context. The -interpreter is deterministically producing a boolean value and must finish evaluation for any possible inputs within -concrete constant time. - -We propose an alternative authentication language, named Σ-State. It defines guarding proposition for a coin as a logic -formula which combines predicates over a context and cryptographic statements provable via Σ-protocols with -AND, OR, k-out-of-n connectives. A prover willing to spend the coin first reduces the compound proposition to a -compound cryptographic statement by evaluating predicates over known shared context (state of the blockchain system and -a spending transaction). Then the prover is turning a corresponding (and possibly complex) Σ-protocol into a signature -with the help of a Fiat-Shamir transformation. A verifier (a full-node in a blockchain setting) then is checking the proposition -against the context and the signature. Language expressiveness is defined by a set of predicates over context and a -set of cryptographic statements. We show how the latter could be updated with a soft-fork by using a language like -ZKPDL [1], and how the former could be updated with a soft-fork by using versioning conventions. We propose a set of -context predicates for a Bitcoin-like cryptocurrency with a guarantee of constant upper-bound verification time. -We provide several examples: ring and threshold signatures, pre-issued mining rewards, crowdfunding, and demurrage currency. - -## Getting started - -Because there is currently no published version of Sigma-state interpreter, -to use it in your project you first need to: - - 1. Clone or download [sigmastate-interpreter](https://github.com/ScorexFoundation/sigmastate-interpreter) - (`git clone git@github.com:ScorexFoundation/sigmastate-interpreter.git` from command line). - 2. Run `sbt publishLocal` in the directory it was cloned to. - This will publish the artifacts in the local ivy repository (usually at `$HOME/.ivy`) - 3. In your own project add library dependency - ``` -libraryDependencies ++= Seq( - "org.scorexfoundation" %% "sigma-state" % "0.9.4" -) - ``` +# ErgoScript compiler and ErgoTree interpreter + +This repository contains implementations of ErgoScript compiler and ErgoTree +Interpreter for a family of Sigma-protocol authentication languages (or simply +Sigma language). + +This library is used internally in [Ergo +Node](https://github.com/ergoplatform/ergo) and +[ergo-wallet](https://github.com/ergoplatform/ergo/tree/master/ergo-wallet), the +public interfaces are subject to change. + +For development of Ergo applications using JVM languages (Java/Scala/Kotlin/etc) +a better alternative is to use +[Appkit](https://github.com/ergoplatform/ergo-appkit). + +## Sigma Language Background + +Every coin in Bitcoin is protected by a program in the stack-based Script +language. An interpreter for the language is evaluating the program against a +context (few variables containing information about a spending transaction and +the blockchain), producing a single boolean value as a result. While Bitcoin +Script allows for some contracts to be programmed, its abilities are limited. +Also, to add new cryptographic primitives, for example, ring signatures, a +hard-fork is required. + +Generalizing the Bitcoin Script, ErgoScript compiler and ErgoTree interpreter +implement an _authentication language_ which allows to express coin spending +conditions. The [ErgoScript +Compiler](sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala#L48) +compiles the source code into +[ErgoTree](sigmastate/src/main/scala/sigmastate/Values.scala#L990) byte code, +which can be saved in UTXO coins to protect their spending (same as in Bitcoin). + +ErgoTree, in turn, is a bytecode language and memory representation which can be +deterministically interpreted in the given _blockchain context_. +ErgoTree defines guarding proposition for a coin as a logic formula which +combines predicates over a context and cryptographic statements provable via +[Σ-protocols](https://en.wikipedia.org/wiki/Proof_of_knowledge#Sigma_protocols) +with AND, OR, k-out-of-n connectives. + +An _interacting party_ willing to spend the coin first constructs a +[prover](sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala) +with a set of secrets it knows and then the prover is executed in two steps: + +- _Reduction_ - the prover uses the ErgoTree interpreter and deterministically +reduces the ErgoTree proposition to a compound _cryptographic statement_(aka +sigma proposition, Σ-protocol) by evaluating ErgoTree over known shared context +(state of the blockchain system and a spending transaction). This step produces +a value of the [SigmaBoolean](sigmastate/src/main/scala/sigmastate/Values.scala) +type. + +- Signing - the prover is turning the obtained (and possibly +complex) Σ-proposition into a signature with the help of a [Fiat-Shamir +transformation](https://en.wikipedia.org/wiki/Fiat-Shamir_heuristic). This step +produces a _proof_ that the party knows the secrets such that the knowledge can +be verified before the spending transaction is added to the blockchain. + +To allow valid coin spending a +[verifier](sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala) +is running the ErgoTree interpreter with the following three inputs: +- a quarding proposition given by an ErgoTree +- a blockchain _context_ of the transaction being verified +- a _proof_ (aka transaction signature) generated by a _prover_ + +The verifier is executed as part of transaction validation for each input and is +executed in tree steps: + +- _Reduction_ - same as prover, the verifier uses the ErgoTree interpreter and +deterministically produces a value of the +[SigmaBoolean](sigmastate/src/main/scala/sigmastate/Values.scala) type. +However, this step must finish evaluation for any possible inputs within +concrete fixed time limit (aka maximum cost), which is checked by the interpreter. + +- Cost estimation - the verifier estimates the complexity of cryptographic Sigma +proposition (based in the size and the concrete nodes of SigmaBoolean tree). The +spending fails if the estimated cost exceeds the maximum limit. + +- Signature verification - the signature checker takes 1) the proof, 2) the +SigmaBoolean (aka [sigma +protocol](https://en.wikipedia.org/wiki/Proof_of_knowledge#Sigma_protocols) +proposition) and 3) the signed message (e.g. transaction bytes). +The checker than verifies the proof, which means it verifies that all the +necessary secrets has been known and used to construct the proof (i.e. sign the +transaction). + +## Getting Started + +This library is +[publishied](https://mvnrepository.com/artifact/org.scorexfoundation/sigma-state) +on Maven repository and can be added to the SBT configuration of Scala project. + +```scala +libraryDependencies += "org.scorexfoundation" %% "sigma-state" % "4.0.3" +``` + +## Repository Organization + +| sub-module | description | +|---|-----| +| common | Used in all other submodules and contain basic utility classes | +| core | Implementation of graph-based intermediate representation of ErgoTree, which is used in cost estimation and interpretation | +| docs | Collection of documents | +| library | Implementation of graph IR nodes for Coll, Size and other types | +| libaray-api | Declarations of interfaces | +| libaray-impl | Implementation of interfaces | +| sigma-api | Declarations of runtime interfaces which are used in ErgoTree interpreter | +| sigma-impl | Implementation of sigma-api interfaces | +| sigma-library | Implementation of graph IR nodes for Sigma types | +| sigmastate | Implementation ErgoTree, Interpreter and cost estimation | + +## References + +- [Ergo Site](https://ergoplatform.org/en/) +- [Ergo Sources](https://github.com/ergoplatform/ergo) +- [Ergo Appkit](https://github.com/ergoplatform/ergo-appkit) +- [Ergo Appkit Examples](https://github.com/aslesarenko/ergo-appkit-examples) +- [ergo-android](https://github.com/aslesarenko/ergo-android) +- [ergo-wallet-android](https://github.com/MrStahlfelge/ergo-wallet-android) +- [ErgoTree Specification](https://ergoplatform.org/docs/ErgoTree.pdf) +- [Ergo Documents](https://ergoplatform.org/en/documents/) + + + From 06f4dc42b400273ac62779430f41eb7faaa25f7c Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 16 Jun 2021 15:28:39 +0300 Subject: [PATCH 51/52] prepare-v5.0: part 2: removed unused code --- build.sbt | 3 +-- common/src/main/scala/scalan/AnyVals.scala | 9 --------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index 3884247879..29e0c208e8 100644 --- a/build.sbt +++ b/build.sbt @@ -61,7 +61,6 @@ val debox = "org.spire-math" %% "debox" % "0.8.0" val kiama = "org.bitbucket.inkytonik.kiama" %% "kiama" % "2.1.0" val fastparse = "com.lihaoyi" %% "fastparse" % "1.0.0" val commonsIo = "commons-io" % "commons-io" % "2.5" -val commonsMath3 = "org.apache.commons" % "commons-math3" % "3.2" val specialVersion = "0.6.1" val meta = "io.github.scalan" %% "meta" % specialVersion @@ -257,7 +256,7 @@ lazy val sigmastate = (project in file("sigmastate")) .dependsOn(sigmaimpl % allConfigDependency, sigmalibrary % allConfigDependency) .settings(libraryDefSettings) .settings(libraryDependencies ++= Seq( - scorexUtil, kiama, fastparse, commonsMath3, + scorexUtil, kiama, fastparse, if (scalaVersion.value == scala211) circeCore211 else circeCore, if (scalaVersion.value == scala211) circeGeneric211 else circeGeneric, if (scalaVersion.value == scala211) circeParser211 else circeParser diff --git a/common/src/main/scala/scalan/AnyVals.scala b/common/src/main/scala/scalan/AnyVals.scala index f1e40348da..6da152e524 100644 --- a/common/src/main/scala/scalan/AnyVals.scala +++ b/common/src/main/scala/scalan/AnyVals.scala @@ -45,14 +45,5 @@ class AVHashMap[K,V](val hashMap: HashMap[K,V]) extends AnyVal { object AVHashMap { /** Helper method to create a new map with the given capacity. */ def apply[K,V](initialCapacity: Int) = new AVHashMap[K,V](new HashMap[K,V](initialCapacity)) - - /** Helper method to create a new map form sequence of K, V pairs. */ - def fromSeq[K,V](items: Seq[(K, V)]): AVHashMap[K,V] = { - val map = new AVHashMap[K,V](new HashMap[K,V](items.length)) - items.foreach { case (k, v) => - map.put(k, v) - } - map - } } From e9b9740f85727463750c1bfd66eb27fc7a0b93dc Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 21 Jun 2021 14:49:44 +0300 Subject: [PATCH 52/52] README.md: fixes from review --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 56f6a48062..8b95695a6d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ErgoScript compiler and ErgoTree interpreter This repository contains implementations of ErgoScript compiler and ErgoTree -Interpreter for a family of Sigma-protocol authentication languages (or simply +Interpreter for a family of Sigma-protocol based authentication languages (or simply Sigma language). This library is used internally in [Ergo @@ -101,8 +101,8 @@ libraryDependencies += "org.scorexfoundation" %% "sigma-state" % "4.0.3" | core | Implementation of graph-based intermediate representation of ErgoTree, which is used in cost estimation and interpretation | | docs | Collection of documents | | library | Implementation of graph IR nodes for Coll, Size and other types | -| libaray-api | Declarations of interfaces | -| libaray-impl | Implementation of interfaces | +| library-api | Declarations of interfaces | +| library-impl | Implementation of interfaces | | sigma-api | Declarations of runtime interfaces which are used in ErgoTree interpreter | | sigma-impl | Implementation of sigma-api interfaces | | sigma-library | Implementation of graph IR nodes for Sigma types |