From 929c45bd6a9374fbe4e3da7da6ecd6dd9464cd6e Mon Sep 17 00:00:00 2001 From: kev-liao <kliao6@illinois.edu> Date: Thu, 6 Sep 2018 15:11:32 -0500 Subject: [PATCH] hi --- mp1/zkp-assignment.ipynb | 665 +++++++++++++++++++++++++++++++++++++++ mp1/zkp-assignment.py | 540 +++++++++++++++++++++++++++++++ 2 files changed, 1205 insertions(+) create mode 100644 mp1/zkp-assignment.ipynb create mode 100644 mp1/zkp-assignment.py diff --git a/mp1/zkp-assignment.ipynb b/mp1/zkp-assignment.ipynb new file mode 100644 index 0000000..cc923ac --- /dev/null +++ b/mp1/zkp-assignment.ipynb @@ -0,0 +1,665 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Zero Knowledge Proofs in Python\n", + "\n", + "Examples of discrete-log zero-knowledge proofs implemented in Python\n", + "\n", + "More specifically, these are non-interactive, zero-knowledge,\n", + "proofs of knowledge. They can be analyzed and proven secure\n", + "in the random oracle model (the random oracle here is instantiated\n", + "with the SHA2 hash function).\n", + "\n", + "Lecture notes:\n", + " http://soc1024.ece.illinois.edu/teaching/ece498am/fall2017/\n", + " https://www.cs.jhu.edu/~susan/600.641/scribes/lecture10.pdf\n", + " https://www.cs.jhu.edu/~susan/600.641/scribes/lecture11.pdf\n", + " http://soc1024.web.engr.illinois.edu/teaching/ece598am/fall2016/zkproofs.pdf\n", + " \n", + "You must fill in the portions labelled #TODO. See the README.md in this\n", + "directory for submission instructions. Points are awarded as marked.\n", + "Total possible points (not including bonus): 105" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import Elliptic Curves\n", + "\n", + "The zero-knowledge proof schemes we work through here \n", + " can work with any DLog group. This implementation makes use of\n", + "the secp256k1 elliptic curve group. We call an element of this group\n", + "(i.e., a point on the curve), simply a Point.\n", + "\n", + "The order of this group, p, is a 256-bit prime number. Furthermore, p\n", + "happens to be extremely close to 2^256. Because of this, we can sample\n", + "exponents easily by choosing a random 32-byte number, and with high probability,\n", + "will be within [0,p).\n", + " uint256_from_str(rnd_bytes(32)) is an exponent.\n", + "\n", + "Sometimes this will be represented by the object Fp, which automatically handles\n", + "arithmetic modulo p. The underlying 'long' value can be extracted as `p.n` if \n", + "`type(p) is Fp`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import secp256k1\n", + "from secp256k1 import Point, q, Fq, order, p, Fp, G, curve, ser, deser, uint256_from_str, uint256_to_str, make_random_point\n", + "import os, random\n", + "\n", + "# p is the order (the # of elements in) the group, i.e., the number of points on the curve\n", + "# order = p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141\n", + "print order\n", + "print Fp # Fp is the group of exponents (integers mod p)\n", + "\n", + "# ser/deser: convert Point -> string and vice versa\n", + "# ser : Point -> str, deser : str -> Point" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print repr(G)\n", + "print repr(p * G)\n", + "print deser(ser(G))\n", + "\n", + "Hx = Fq(0x8E3871A594F9AF7A1F357A0793124AAF3358B0F020983678BCD411EE6AF387A5L)\n", + "Hy = Fq(0x83BFAA8176272D0E4D7AD3577F3A0A3B70D7E1BFC2B638CA2807562F2CA85F59L)\n", + "H = Point(curve, Hx,Hy)\n", + "# H = random_point(seed=sha2(\"H\")) # An alternate generator" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Random Oracle Functions\n", + "We need ways of sampling random strings, random integers in Z_p,\n", + "and random points in the group." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "## Default random oracle\n", + "def make_random_oracle():\n", + " \"\"\"This function returns a new random oracle, `RO`.\n", + " The random oracle maps arbitrary-length input strings `s` to\n", + " a 32-byte digest. It is initialized with an empty dictionary. \n", + " Each time RO is queried with a new value, a\n", + " random response is sampled and stored.\"\"\"\n", + "\n", + " _mapping = {}\n", + " def RO(s):\n", + " assert type(s) is str\n", + " if not s in _mapping:\n", + " _mapping[s] = os.urandom(32)\n", + " return _mapping[s]\n", + " return RO\n", + "\n", + "# Random oracle instantiated with SHA2 hash\n", + "def sha2(x):\n", + " from Crypto.Hash import SHA256\n", + " return SHA256.new(x).digest()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preliminary example: Proof of knowledge of discrete logarithm\n", + "\n", + "In this part, we provide a scheme offers a discrete log proof of `ZKP{ (a): A = a*G }`.\n", + "\n", + "Note that the statement `A` is a parameter to the scheme, as it must\n", + "be known to both the prover and verifier.\n", + "\n", + "The Prover takes several additional arguments:\n", + "\n", + " - `rnd_bytes`, such that `rnd_bytes(n)` returns an `n`-byte random string. By default, will use the operating system os.urandom. \n", + "\n", + " (Note: as this function is non-blocking, may be a poor choice if the OS runs out of entropy)\n", + "\n", + " - RO, a random oracle, such that `RO(s)` where `s` is an arbitrary length string, returns a randomly chosen value. By default, will use the sha2 hash.\n", + "\n", + "These can be overridden in later section as part of the security proof constructions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def dlog_prover(A, a, rnd_bytes=os.urandom, RO=sha2):\n", + " assert a*G == A\n", + "\n", + " # blinding factor\n", + " k = uint256_from_str(rnd_bytes(32)) % order\n", + "\n", + " # commitment\n", + " K = k*G\n", + "\n", + " # Invoke the random oracle to receive a challenge\n", + " c = uint256_from_str(RO(ser(K)))\n", + "\n", + " # response\n", + " s = Fp(k + c*a)\n", + "\n", + " return (K,s)\n", + "\n", + "\n", + "def dlog_verifier(A, prf, RO=sha2):\n", + " (K,s) = prf\n", + " assert type(A) is type(K) is Point\n", + " assert type(s) is Fp\n", + "\n", + " # Recompute c w/ the information given\n", + " c = uint256_from_str(RO(ser(K)))\n", + "\n", + " # Check the verification condition\n", + " assert s.n *G == K + c*A\n", + " return True\n", + "\n", + "\n", + "def dlog_test():\n", + " a = uint256_from_str(os.urandom(32))\n", + " A = a*G\n", + " prf = dlog_prover(A, a)\n", + " assert dlog_verifier(A, prf)\n", + " print 'Dlog correctness test complete!'\n", + "\n", + "dlog_test()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 1: Make a Pedersen commitment to your crypto egg. \n", + " Provide a ZK proof that your commitment is correct.\n", + "\n", + " Zk{ (x,r): X = x*G, C = x*G + r*H }\n", + "\n", + "By completing this proof, you prove you still have knowledge of your egg!\n", + "without revealing which.\n", + "\n", + "The verifier is provided for you. (Since we will publicly verify the proofs). You must complete the prover." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def make_pedersen_commitment(x, rnd_bytes=os.urandom):\n", + " r = uint256_from_str(rnd_bytes(32))\n", + " C = x * G + r * H\n", + " return C, r\n", + "\n", + "def pedersen_prover(C, X, x, r, rnd_bytes=os.urandom, RO=sha2):\n", + " \"\"\"\n", + " Params: \n", + " x and r are elements of Fp\n", + " C,X are Points\n", + " Returns:\n", + " prf, of the form (KX,KC,sx,sr)\n", + " \"\"\"\n", + " assert X == x * G\n", + " assert C == x * G + r * H\n", + "\n", + " # TODO: fill in your code here (10 points)\n", + "\n", + " return (KX,KC,sx,sr)\n", + "\n", + "def pedersen_verifier(C, X, prf, RO=sha2):\n", + " (KX,KC,sx,sr) = prf\n", + " assert type(KX) == type(KC) == Point\n", + " assert type(sx) == type(sr) == Fp\n", + "\n", + " # Recompute c w/ the information given\n", + " c = uint256_from_str(RO(ser(KX) + ser(KC)))\n", + "\n", + " assert sx.n *G == KX + c*X\n", + " assert sx.n *G + sr.n *H == KC + c*C\n", + " return True\n", + "\n", + "def pedersen_test():\n", + "\n", + " x = uint256_from_str(os.urandom(32))\n", + " X = x * G\n", + " C,r = make_pedersen_commitment(x)\n", + "\n", + " prf = pedersen_prover(C, X, x, r)\n", + " (KX, KC, sx, sr) = prf\n", + " print repr((ser(C), ser(KX),ser(KC),uint256_to_str(sx.n).encode('hex'),uint256_to_str(sr.n).encode('hex')))\n", + "\n", + " assert pedersen_verifier(C, X, prf)\n", + " print \"Pedersen correctness test complete!\"\n", + "\n", + "pedersen_test()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 2. Arithmetic relations\n", + "\n", + "Example: a more complicated discrete log proof\n", + " Zk{ (a, b): A=a*G, B=b*G, C = (a*(b+3)) * G }\n", + "\n", + "First rewrite as:\n", + " Zk{ (a, b): A=a*G, B=b*G, (C-3*A) = b*A) }\n", + "\n", + "You need to implement a prover and verifier for the above scheme." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def arith_prover(a, b, A, B, C, rnd_bytes=os.urandom, RO=sha2):\n", + " \"\"\"\n", + " Params: \n", + " a and b are elements of Fp\n", + " A, B, C are Points\n", + " Returns:\n", + " prf, of the form (KA,KB,KC,sa,sb)\n", + "\n", + " Must satisfy verify_proof2(A, B, C, prf)\n", + " Must be zero-knowledge\n", + " \"\"\"\n", + " assert a*G == A\n", + " assert b*G == B\n", + " assert (a*(b+3))*G == C\n", + "\n", + " # TODO: fill in your code here (10 points)\n", + "\n", + "def arith_verifier(A, B, C, prf, rnd_bytes=os.urandom, RO=sha2):\n", + " (KA,KB,KC,sa,sb) = prf\n", + " assert type(KA) == type(KB) == type(KC) == Point\n", + " assert type(sa) == type(sb) == Fp\n", + "\n", + " # TODO: fill in your code here (10 points)\n", + "\n", + "def arith_test():\n", + " # Randomly choose \"a\" and \"b\"\n", + " a = uint256_from_str(os.urandom(32))\n", + " b = uint256_from_str(os.urandom(32))\n", + " A = a*G\n", + " B = b*G\n", + " C = (a*(b+3)) * G\n", + "\n", + " prf = arith_prover(a, b, A, B, C)\n", + " assert arith_verifier(A, B, C, prf)\n", + " print \"Arithmetic Relation correctness test complete\"\n", + "\n", + "arith_test()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 3. OR composition\n", + "\n", + "In this part you will need to prove knowledge of one of two possible secrets,\n", + "\n", + " Zk{ (a,b): A = a*G OR B = b*G }\n", + "\n", + "without revealing which one it is you know.\n", + "\n", + "The verifier is provided for you." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def OR_prover(A, B, x, rnd_bytes=os.urandom, RO=sha2):\n", + " assert x*G == A or x*G == B\n", + "\n", + " # TODO: Fill your code in here (20 points)\n", + "\n", + " return (KA,KB,sa,sb,ca,cb)\n", + "\n", + "def OR_verifier(A, B, prf, RO=sha2):\n", + " (KA,KB,sa,sb,ca,cb) = prf\n", + " assert type(KA) is type(KB) is Point\n", + " assert type(sa) is type(sb) is Fp\n", + "\n", + " # Check the challenges are correctly constrained\n", + " c = uint256_from_str(RO(ser(KA) + ser(KB)))\n", + " assert (ca + cb) % p == c\n", + "\n", + " # Check each proof the same way\n", + " assert sa.n *G == KA + ca*A\n", + " assert sb.n *G == KB + cb*B\n", + "\n", + " return True\n", + "\n", + "def OR_test1():\n", + " # Try first way\n", + " a = uint256_from_str(os.urandom(32))\n", + " A = a*G\n", + " B = make_random_point()\n", + "\n", + " prf = OR_prover(A, B, a)\n", + " assert OR_verifier(A, B, prf)\n", + " print \"OR composition correctness 1 test complete!\"\n", + "\n", + "def OR_test2():\n", + " # Try second way\n", + " b = uint256_from_str(os.urandom(32))\n", + " A = make_random_point()\n", + " B = b*G\n", + "\n", + " prf = OR_prover(A, B, b)\n", + " assert OR_verifier(A, B, prf)\n", + " print \"OR composition correctness 2 test complete!\"\n", + "\n", + "OR_test1()\n", + "OR_test2()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 4. Schnorr signature\n", + "\n", + " We can write a Schnor signature as:\n", + "\n", + " SoK[m] { (x): X = x*G }\n", + "\n", + " Similar to part 1, except we the challenge is derived from the message." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def schnorr_sign(x, m, rnd_bytes=os.urandom, RO=sha2):\n", + " assert type(x) is str\n", + " assert type(m) is str\n", + "\n", + " # TODO: Your code goes here (10 points)\n", + "\n", + "def schnorr_verify(X, m, sig, RO=sha2):\n", + " assert type(X) is Point\n", + " assert type(sig) is str and len(sig) is 65\n", + " (K,s) = deser(sig[:33].encode('hex')), uint256_from_str(sig[33:])\n", + " c = uint256_from_str(RO(ser(K) + sha2(m)))\n", + " assert s *G == K + c*X\n", + " return True\n", + "\n", + "def schnorr_test():\n", + " msg = \"hello\"\n", + "\n", + " x = os.urandom(32)\n", + " X = uint256_from_str(x) * G\n", + "\n", + " sig = schnorr_sign(x, msg)\n", + " assert schnorr_verify(X, msg, sig)\n", + " print \"Schnorr Test complete\"\n", + "\n", + "schnorr_test()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 5. Range proofs\n", + "\n", + "- Create a proof that C is a commitment to a, and a is in the range [0,31].\n", + "\n", + " Zk{ (a, r): C = g^a h^r and 0 <= a <= 31 }\n", + "\n", + " Hint: You can implement this by creating commitments to the binary expansion\n", + " of a, and then proving the following:\n", + "\n", + " Zk{ (b0, b1, ... b4, r, r0, r1, ..., r4):\n", + " A = g^(b0 + 2*b1 + ... + 16*b4) h^r\n", + " and (C0 = g^(b0) h^r0) ...\n", + " and (C0 = g h^r0 OR C0 = h^r0) ... }\n", + "\n", + " Hint: to avoid too much repetition, consider implementing the Bonus question first." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def range_prover(a, r, C, rnd_bytes=os.urandom, RO=sha2):\n", + " assert type(C) is Point\n", + " assert a*G + r*H == C\n", + "\n", + " # TODO: fill in your code here (10 points)\n", + "\n", + "def range_verifier(C, prf, rnd_bytes=os.urandom, RO=sha2):\n", + " assert type(C) is Point\n", + "\n", + " # TODO: fill in your code here (10 points)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 6: Extractor and simulator\n", + "\n", + "In this part, you will implement in code a portion of the security proof\n", + "for the discrete log proof scheme from the Preliminary." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def dlog_extractor(A, Adv):\n", + " assert type(A) is Point\n", + "\n", + " ## Step 1: run the adversary to generate a proof while recording\n", + " ## the random bits and oracle queries\n", + "\n", + " # TODO: Fill your code in here (5 points)\n", + "\n", + " ## Step 2: run the adversary again, replaying the random bits,\n", + " ## and intercepting the call to the random oracle\n", + "\n", + " # TODO: Fill your code in here (5 points)\n", + "\n", + " ## Step 3: Extract a witness from the two proofs and oracle queries\n", + "\n", + " # TODO: Fill your code in here (5 points)\n", + "\n", + "def dlog_test_extractor():\n", + " # Make a test case based on running the real prover\n", + " a = uint256_from_str(os.urandom(32))\n", + " A = a * G\n", + "\n", + " def Adv(A, rnd_bytes, RO):\n", + " assert A == a * G\n", + " return dlog_prover(A, a, rnd_bytes, RO)\n", + "\n", + " a_ = dlog_extractor(A, Adv)\n", + " assert a == a_\n", + " print 'Extractor test complete!'\n", + "\n", + "def dlog_test_extractor_harder():\n", + " # Make a test case based on running a \"picky\" prover\n", + " a = uint256_from_str(os.urandom(32))\n", + " A = a * G\n", + "\n", + " def Adv(A, rnd_bytes, RO):\n", + " assert A == a * G\n", + "\n", + " while True:\n", + " # The \"picky\" prover loops until it is happy\n", + "\n", + " # Make a whimsical decision\n", + " coin = rnd_bytes(1)\n", + " if ord(coin) < 128: continue\n", + "\n", + " k = uint256_from_str(rnd_bytes(32)) % order\n", + " K = k*G\n", + " c = uint256_from_str(RO(ser(K)))\n", + "\n", + " # I only like challenges that end with 3 zero bits\n", + " if c & 0b111 != 0: continue\n", + "\n", + " # OK I'm satisfied\n", + " s = Fp(k + c*a)\n", + " return (K, s)\n", + "\n", + " a_ = dlog_extractor(A, Adv)\n", + " assert a == a_\n", + " print 'Extractor test complete!'\n", + "\n", + "dlog_test_extractor()\n", + "dlog_test_extractor_harder()\n", + "\n", + "def dlog_simulator(A, rnd_bytes):\n", + " \"\"\"\n", + " Returns:\n", + " - (prf, RO)\n", + " - prf, a tuple of the form (K,s),\n", + " where K is a Point\n", + " and s is an element of Fp\n", + " - RO is a random oracle function\n", + "\n", + " The following must hold:\n", + " - RO is a mapping from to 32-bit strings. Its output should be indistinguishable from an ordinary random oracle generated from `make_random_oracle`.\n", + " \"\"\"\n", + " # TODO: Fill in your code here (10 points)\n", + "\n", + "def dlog_test_simulator():\n", + " rnd_bytes = os.urandom\n", + " # Test with a randomly generated point on curve\n", + " A = make_random_point(rnd_bytes)\n", + "\n", + " (prf, RO) = dlog_simulator(A, rnd_bytes)\n", + "\n", + " # The proof must verify\n", + " assert dlog_verifier(A, prf, RO=RO)\n", + " print \"DLOG simulator test complete!\"\n", + "\n", + "dlog_test_simulator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Bonus Challenge: Expression compiler\n", + "\n", + " Can you take a description of a ZK scheme in Camenisch-Stadler notation,\n", + " and automatically generate the protocol for it?\n", + "\n", + " Read the ZKPDL paper for inspiration (that is a much more full-featured system!)\n", + "\n", + " To complete this bonus, the goal should be to support the following\n", + " grammar for expressing ZK constraint systems:\n", + "\n", + " ZK := EQN | ZK and ZK | ZK or ZK\n", + " EQN := BEXP = BEXP\n", + " BEXP := BEXP * BEXP | BEXP / BEXP | BEXP ^ EEXP | BIDENT\n", + " EEXP := EEXP + EEXP | EEXP - EEXP | EEXP * EEXP | EEXP / EEXP | EIDENT | ELIT\n", + " BIDENT := 'G', 'H', ...\n", + " EIDENT := 'a','b', ...\n", + " ELIT := 1,2,3, ...\n", + "\n", + " Example:\n", + " \"Zk{ (a,b): A = G^(a) * (H^10)^(3*b)\"\n", + "\n", + " would be expressed as:\n", + " ZK(\n", + " EQN( \n", + " BEXP( BIDENT('A') )\n", + " = \n", + " BEXP( \n", + " BEXP( BIDENT('G') ^ EEXP( EIDENT('a') ) )\n", + " *\n", + " BEXP( \n", + " BEXP( BEXP(BIDENT('H')) ^ EEXP(ELIT(10)) )\n", + " ^\n", + " EEXP(ELIT(3) * EIDENT('b'))\n", + " )\n", + " )\n", + " )\n", + " )\n", + "\n", + " Hints:\n", + " - Build an abstract syntax tree to traverse.\n", + " - Flatten AND and OR to a single list\n", + " - Apply operations to left and right sides until left side has no exponents\n", + " - To handle multiplications, introduce ephemeral commitments (like in Part 5)\n", + "\n", + " For example, to prove\n", + " Zk{ (a,b,c): A = g^a, B = g^b, C = g^c, D = g^(abc) }\n", + "\n", + " you could introduce an ephemeral commitment:\n", + "\n", + " E = g^(ab) h^r, for random r\n", + "\n", + " Then the extended proof is:\n", + " Zk{ (a,b,c,r,r2): A = g^a, B = g^b, C = g^c, E = A^b h^r, D = E^c h^r2 }\n", + "\n", + " where the prover sets r2 = -(c*r).\n", + "\n", + " This now has the property that the only EEXP terms are EEXP(EIDENT(_))." + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/mp1/zkp-assignment.py b/mp1/zkp-assignment.py new file mode 100644 index 0000000..4c7c691 --- /dev/null +++ b/mp1/zkp-assignment.py @@ -0,0 +1,540 @@ +""" +# Zero Knowledge Proofs in Python + +Examples of discrete-log zero-knowledge proofs implemented in Python + +More specifically, these are non-interactive, zero-knowledge, +proofs of knowledge. They can be analyzed and proven secure +in the random oracle model (the random oracle here is instantiated +with the SHA2 hash function). + +Lecture notes: + http://soc1024.ece.illinois.edu/teaching/ece498am/fall2017/ + https://www.cs.jhu.edu/~susan/600.641/scribes/lecture10.pdf + https://www.cs.jhu.edu/~susan/600.641/scribes/lecture11.pdf + http://soc1024.web.engr.illinois.edu/teaching/ece598am/fall2016/zkproofs.pdf + +You must fill in the portions labelled #TODO. See the README.md in this +directory for submission instructions. Points are awarded as marked. +Total possible points (not including bonus): 105 +""" + + +""" +## Import Elliptic Curves + +The zero-knowledge proof schemes we work through here + can work with any DLog group. This implementation makes use of +the secp256k1 elliptic curve group. We call an element of this group +(i.e., a point on the curve), simply a Point. + +The order of this group, p, is a 256-bit prime number. Furthermore, p +happens to be extremely close to 2^256. Because of this, we can sample +exponents easily by choosing a random 32-byte number, and with high probability, +will be within [0,p). + uint256_from_str(rnd_bytes(32)) is an exponent. + +Sometimes this will be represented by the object Fp, which automatically handles +arithmetic modulo p. The underlying 'long' value can be extracted as `p.n` if +`type(p) is Fp`. +""" + +import secp256k1 +from secp256k1 import Point, q, Fq, order, p, Fp, G, curve, ser, deser, uint256_from_str, uint256_to_str, make_random_point +import os, random + +# p is the order (the # of elements in) the group, i.e., the number of points on the curve +# order = p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 +print order +print Fp # Fp is the group of exponents (integers mod p) + +# ser/deser: convert Point -> string and vice versa +# ser : Point -> str, deser : str -> Point + +""" +""" +print repr(G) +print repr(p * G) +print deser(ser(G)) + +Hx = Fq(0x8E3871A594F9AF7A1F357A0793124AAF3358B0F020983678BCD411EE6AF387A5L) +Hy = Fq(0x83BFAA8176272D0E4D7AD3577F3A0A3B70D7E1BFC2B638CA2807562F2CA85F59L) +H = Point(curve, Hx,Hy) +# H = random_point(seed=sha2("H")) # An alternate generator + +""" +## Random Oracle Functions +We need ways of sampling random strings, random integers in Z_p, +and random points in the group. +""" + +## Default random oracle +def make_random_oracle(): + """This function returns a new random oracle, `RO`. + The random oracle maps arbitrary-length input strings `s` to + a 32-byte digest. It is initialized with an empty dictionary. + Each time RO is queried with a new value, a + random response is sampled and stored.""" + + _mapping = {} + def RO(s): + assert type(s) is str + if not s in _mapping: + _mapping[s] = os.urandom(32) + return _mapping[s] + return RO + +# Random oracle instantiated with SHA2 hash +def sha2(x): + from Crypto.Hash import SHA256 + return SHA256.new(x).digest() + + +""" +## Preliminary example: Proof of knowledge of discrete logarithm + +In this part, we provide a scheme offers a discrete log proof of `ZKP{ (a): A = a*G }`. + +Note that the statement `A` is a parameter to the scheme, as it must +be known to both the prover and verifier. + +The Prover takes several additional arguments: + + - `rnd_bytes`, such that `rnd_bytes(n)` returns an `n`-byte random string. By default, will use the operating system os.urandom. + + (Note: as this function is non-blocking, may be a poor choice if the OS runs out of entropy) + + - RO, a random oracle, such that `RO(s)` where `s` is an arbitrary length string, returns a randomly chosen value. By default, will use the sha2 hash. + +These can be overridden in later section as part of the security proof constructions. +""" +def dlog_prover(A, a, rnd_bytes=os.urandom, RO=sha2): + assert a*G == A + + # blinding factor + k = uint256_from_str(rnd_bytes(32)) % order + + # commitment + K = k*G + + # Invoke the random oracle to receive a challenge + c = uint256_from_str(RO(ser(K))) + + # response + s = Fp(k + c*a) + + return (K,s) + + +def dlog_verifier(A, prf, RO=sha2): + (K,s) = prf + assert type(A) is type(K) is Point + assert type(s) is Fp + + # Recompute c w/ the information given + c = uint256_from_str(RO(ser(K))) + + # Check the verification condition + assert s.n *G == K + c*A + return True + + +def dlog_test(): + a = uint256_from_str(os.urandom(32)) + A = a*G + prf = dlog_prover(A, a) + assert dlog_verifier(A, prf) + print 'Dlog correctness test complete!' + +dlog_test() + +""" +## Part 1: Make a Pedersen commitment to your crypto egg. + Provide a ZK proof that your commitment is correct. + + Zk{ (x,r): X = x*G, C = x*G + r*H } + +By completing this proof, you prove you still have knowledge of your egg! +without revealing which. + +The verifier is provided for you. (Since we will publicly verify the proofs). You must complete the prover. +""" + +def make_pedersen_commitment(x, rnd_bytes=os.urandom): + r = uint256_from_str(rnd_bytes(32)) + C = x * G + r * H + return C, r + +def pedersen_prover(C, X, x, r, rnd_bytes=os.urandom, RO=sha2): + """ + Params: + x and r are elements of Fp + C,X are Points + Returns: + prf, of the form (KX,KC,sx,sr) + """ + assert X == x * G + assert C == x * G + r * H + + # TODO: fill in your code here (10 points) + + return (KX,KC,sx,sr) + +def pedersen_verifier(C, X, prf, RO=sha2): + (KX,KC,sx,sr) = prf + assert type(KX) == type(KC) == Point + assert type(sx) == type(sr) == Fp + + # Recompute c w/ the information given + c = uint256_from_str(RO(ser(KX) + ser(KC))) + + assert sx.n *G == KX + c*X + assert sx.n *G + sr.n *H == KC + c*C + return True + +def pedersen_test(): + + x = uint256_from_str(os.urandom(32)) + X = x * G + C,r = make_pedersen_commitment(x) + + prf = pedersen_prover(C, X, x, r) + (KX, KC, sx, sr) = prf + print repr((ser(C), ser(KX),ser(KC),uint256_to_str(sx.n).encode('hex'),uint256_to_str(sr.n).encode('hex'))) + + assert pedersen_verifier(C, X, prf) + print "Pedersen correctness test complete!" + +pedersen_test() + + +""" +## Part 2. Arithmetic relations + +Example: a more complicated discrete log proof + Zk{ (a, b): A=a*G, B=b*G, C = (a*(b+3)) * G } + +First rewrite as: + Zk{ (a, b): A=a*G, B=b*G, (C-3*A) = b*A) } + +You need to implement a prover and verifier for the above scheme. +""" + +def arith_prover(a, b, A, B, C, rnd_bytes=os.urandom, RO=sha2): + """ + Params: + a and b are elements of Fp + A, B, C are Points + Returns: + prf, of the form (KA,KB,KC,sa,sb) + + Must satisfy verify_proof2(A, B, C, prf) + Must be zero-knowledge + """ + assert a*G == A + assert b*G == B + assert (a*(b+3))*G == C + + # TODO: fill in your code here (10 points) + +def arith_verifier(A, B, C, prf, rnd_bytes=os.urandom, RO=sha2): + (KA,KB,KC,sa,sb) = prf + assert type(KA) == type(KB) == type(KC) == Point + assert type(sa) == type(sb) == Fp + + # TODO: fill in your code here (10 points) + +def arith_test(): + # Randomly choose "a" and "b" + a = uint256_from_str(os.urandom(32)) + b = uint256_from_str(os.urandom(32)) + A = a*G + B = b*G + C = (a*(b+3)) * G + + prf = arith_prover(a, b, A, B, C) + assert arith_verifier(A, B, C, prf) + print "Arithmetic Relation correctness test complete" + +arith_test() + +""" +## Part 3. OR composition + +In this part you will need to prove knowledge of one of two possible secrets, + + Zk{ (a,b): A = a*G OR B = b*G } + +without revealing which one it is you know. + +The verifier is provided for you. +""" + +def OR_prover(A, B, x, rnd_bytes=os.urandom, RO=sha2): + assert x*G == A or x*G == B + + # TODO: Fill your code in here (20 points) + + return (KA,KB,sa,sb,ca,cb) + +def OR_verifier(A, B, prf, RO=sha2): + (KA,KB,sa,sb,ca,cb) = prf + assert type(KA) is type(KB) is Point + assert type(sa) is type(sb) is Fp + + # Check the challenges are correctly constrained + c = uint256_from_str(RO(ser(KA) + ser(KB))) + assert (ca + cb) % p == c + + # Check each proof the same way + assert sa.n *G == KA + ca*A + assert sb.n *G == KB + cb*B + + return True + +def OR_test1(): + # Try first way + a = uint256_from_str(os.urandom(32)) + A = a*G + B = make_random_point() + + prf = OR_prover(A, B, a) + assert OR_verifier(A, B, prf) + print "OR composition correctness 1 test complete!" + +def OR_test2(): + # Try second way + b = uint256_from_str(os.urandom(32)) + A = make_random_point() + B = b*G + + prf = OR_prover(A, B, b) + assert OR_verifier(A, B, prf) + print "OR composition correctness 2 test complete!" + +OR_test1() +OR_test2() + + +""" +## Part 4. Schnorr signature + + We can write a Schnor signature as: + + SoK[m] { (x): X = x*G } + + Similar to part 1, except we the challenge is derived from the message. +""" +def schnorr_sign(x, m, rnd_bytes=os.urandom, RO=sha2): + assert type(x) is str + assert type(m) is str + + # TODO: Your code goes here (10 points) + +def schnorr_verify(X, m, sig, RO=sha2): + assert type(X) is Point + assert type(sig) is str and len(sig) is 65 + (K,s) = deser(sig[:33].encode('hex')), uint256_from_str(sig[33:]) + c = uint256_from_str(RO(ser(K) + sha2(m))) + assert s *G == K + c*X + return True + +def schnorr_test(): + msg = "hello" + + x = os.urandom(32) + X = uint256_from_str(x) * G + + sig = schnorr_sign(x, msg) + assert schnorr_verify(X, msg, sig) + print "Schnorr Test complete" + +schnorr_test() + + +""" +## Part 5. Range proofs + +- Create a proof that C is a commitment to a, and a is in the range [0,31]. + + Zk{ (a, r): C = g^a h^r and 0 <= a <= 31 } + + Hint: You can implement this by creating commitments to the binary expansion + of a, and then proving the following: + + Zk{ (b0, b1, ... b4, r, r0, r1, ..., r4): + A = g^(b0 + 2*b1 + ... + 16*b4) h^r + and (C0 = g^(b0) h^r0) ... + and (C0 = g h^r0 OR C0 = h^r0) ... } + + Hint: to avoid too much repetition, consider implementing the Bonus question first. + +""" +def range_prover(a, r, C, rnd_bytes=os.urandom, RO=sha2): + assert type(C) is Point + assert a*G + r*H == C + + # TODO: fill in your code here (10 points) + +def range_verifier(C, prf, rnd_bytes=os.urandom, RO=sha2): + assert type(C) is Point + + # TODO: fill in your code here (10 points) + + +""" +## Part 6: Extractor and simulator + +In this part, you will implement in code a portion of the security proof +for the discrete log proof scheme from the Preliminary. +""" +def dlog_extractor(A, Adv): + assert type(A) is Point + + ## Step 1: run the adversary to generate a proof while recording + ## the random bits and oracle queries + + # TODO: Fill your code in here (5 points) + + ## Step 2: run the adversary again, replaying the random bits, + ## and intercepting the call to the random oracle + + # TODO: Fill your code in here (5 points) + + ## Step 3: Extract a witness from the two proofs and oracle queries + + # TODO: Fill your code in here (5 points) + +def dlog_test_extractor(): + # Make a test case based on running the real prover + a = uint256_from_str(os.urandom(32)) + A = a * G + + def Adv(A, rnd_bytes, RO): + assert A == a * G + return dlog_prover(A, a, rnd_bytes, RO) + + a_ = dlog_extractor(A, Adv) + assert a == a_ + print 'Extractor test complete!' + +def dlog_test_extractor_harder(): + # Make a test case based on running a "picky" prover + a = uint256_from_str(os.urandom(32)) + A = a * G + + def Adv(A, rnd_bytes, RO): + assert A == a * G + + while True: + # The "picky" prover loops until it is happy + + # Make a whimsical decision + coin = rnd_bytes(1) + if ord(coin) < 128: continue + + k = uint256_from_str(rnd_bytes(32)) % order + K = k*G + c = uint256_from_str(RO(ser(K))) + + # I only like challenges that end with 3 zero bits + if c & 0b111 != 0: continue + + # OK I'm satisfied + s = Fp(k + c*a) + return (K, s) + + a_ = dlog_extractor(A, Adv) + assert a == a_ + print 'Extractor test complete!' + +dlog_test_extractor() +dlog_test_extractor_harder() + +def dlog_simulator(A, rnd_bytes): + """ + Returns: + - (prf, RO) + - prf, a tuple of the form (K,s), + where K is a Point + and s is an element of Fp + - RO is a random oracle function + + The following must hold: + - RO is a mapping from to 32-bit strings. Its output should be indistinguishable from an ordinary random oracle generated from `make_random_oracle`. + """ + # TODO: Fill in your code here (10 points) + +def dlog_test_simulator(): + rnd_bytes = os.urandom + # Test with a randomly generated point on curve + A = make_random_point(rnd_bytes) + + (prf, RO) = dlog_simulator(A, rnd_bytes) + + # The proof must verify + assert dlog_verifier(A, prf, RO=RO) + print "DLOG simulator test complete!" + +dlog_test_simulator() + + +""" +## Bonus Challenge: Expression compiler + + Can you take a description of a ZK scheme in Camenisch-Stadler notation, + and automatically generate the protocol for it? + + Read the ZKPDL paper for inspiration (that is a much more full-featured system!) + + To complete this bonus, the goal should be to support the following + grammar for expressing ZK constraint systems: + + ZK := EQN | ZK and ZK | ZK or ZK + EQN := BEXP = BEXP + BEXP := BEXP * BEXP | BEXP / BEXP | BEXP ^ EEXP | BIDENT + EEXP := EEXP + EEXP | EEXP - EEXP | EEXP * EEXP | EEXP / EEXP | EIDENT | ELIT + BIDENT := 'G', 'H', ... + EIDENT := 'a','b', ... + ELIT := 1,2,3, ... + + Example: + "Zk{ (a,b): A = G^(a) * (H^10)^(3*b)" + + would be expressed as: + ZK( + EQN( + BEXP( BIDENT('A') ) + = + BEXP( + BEXP( BIDENT('G') ^ EEXP( EIDENT('a') ) ) + * + BEXP( + BEXP( BEXP(BIDENT('H')) ^ EEXP(ELIT(10)) ) + ^ + EEXP(ELIT(3) * EIDENT('b')) + ) + ) + ) + ) + + Hints: + - Build an abstract syntax tree to traverse. + - Flatten AND and OR to a single list + - Apply operations to left and right sides until left side has no exponents + - To handle multiplications, introduce ephemeral commitments (like in Part 5) + + For example, to prove + Zk{ (a,b,c): A = g^a, B = g^b, C = g^c, D = g^(abc) } + + you could introduce an ephemeral commitment: + + E = g^(ab) h^r, for random r + + Then the extended proof is: + Zk{ (a,b,c,r,r2): A = g^a, B = g^b, C = g^c, E = A^b h^r, D = E^c h^r2 } + + where the prover sets r2 = -(c*r). + + This now has the property that the only EEXP terms are EEXP(EIDENT(_)). +""" -- GitLab