GithubHelp home page GithubHelp logo

harryr / ethsnarks Goto Github PK

View Code? Open in Web Editor NEW

This project forked from barrywhitehat/semaphore

240.0 21.0 57.0 1.11 MB

A toolkit for viable zk-SNARKS on Ethereum, Web, Mobile and Desktop

License: GNU Lesser General Public License v3.0

CMake 1.56% Python 24.29% C++ 51.75% Makefile 0.85% JavaScript 0.93% Shell 1.96% Solidity 13.28% C 5.38%
zk-snarks ethereum solidity sdk algorithms research pinocchio python cplusplus-11

ethsnarks's Introduction

EthSnarks

Join the chat at https://gitter.im/ethsnarks

Zero-Knowledge proofs are coming to Ethereum and Dapps in 2019!

EthSnarks is a collection of zkSNARK circuits and supporting libraries to use them with Ethereum smart contracts, it aims to help solve one of the biggest problems facing zkSNARKS on Ethereum - cross-platform on desktop, mobile and in-browser, cheap enough to run on-chain, and with algorithms that significantly reduces the time it takes to run the prover.

The notable advantages of using EthSnarks are:

  • Reduced cost, 500k gas with 1 input, using Groth16.
  • Prove zkSNARKs in-browser, with WebAssembly and Emscripten
  • Linux, Mac and Windows builds
  • Solidity, Python and C++ support in one place
  • A growing library of gadgets and algorithms

EthSnarks is participating in the Ethereum Foundation's grants program, development will continue full-time and we will be working with companies and developers to help overcome the common challenges and hurdles that we all face. Get in-touch for more information.

WARNING: EthSnarks is beta quality software, improvements and fixes are made frequently, and documentation doesn't yet exist

Examples

Building

Build Status Build status

Unix Flavours (Linux, OSX, Ubuntu, CentOS etc.)

The following dependencies are required to build Ethsnarks:

  • cmake
  • g++ or clang++
  • gmp
  • npm / nvm

Check-out the source-code using:

git clone [email protected]:HarryR/ethsnarks.git && cd ethsnarks

After checking-out the repository you need to install the necessary dependencies, the Makefile includes pre-determined rules for different platforms, you need to run this as root or an administrator user (i.e. for Brew on OSX):

  • make fedora-dependencies (CentOS, Fedora, RHEL etc. requires dnf)
  • make ubuntu-dependencies (Ubuntu, Debian etc. requires apt-get)
  • make mac-dependencies (OSX, requires Homebrew)

Then install the Python dependencies, via Pip, into the local user directory:

  • make python-dependencies

Then build and test the project:

  • git submodule update --init --recursive
  • make

Windows (64-bit)

Install MSYS2 from https://www.msys2.org/ then open the MSYS2 Shell and run:

pacman --noconfirm -S make gmp gmp-devel gcc git cmake
git clone [email protected]:HarryR/ethsnarks.git
cd ethsnarks
git submodule update --init --recursive
cmake -E make_directory build
cmake -E chdir build cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build build

Building under 32-bit MinGW32, MSYS (not MSYS2) and Microsoft Visual Studio may be supported in future depending upon demand, but currently it's probably broken...

WASM / Browser

WebAssembly, WASM and JavaScript builds are partially supported via ethsnarks-emscripten and ethsnarks-cheerp. The build process is similar, but using the Emscripten and Cheerp toolchains.

Requests and Contributions

This project aims to help create an ecosystem where a small number of well tested but simple zkSNARK circuits can be easily integrated into your project without having to do all of the work up-front.

If you have any ideas for new components, please Open an issue, or submit a pull request.

Gadgets

We are surely increasing the range of gadgets, supporting libraries, available documentation and examples; at the moment the best way to find out how to use something is to dig into the code or ask questions via a new issue

The following gadgets are available

Maintainers

@HarryR

ethsnarks's People

Contributors

barrywhitehat avatar brechtpd avatar drewstone avatar fleupold avatar harryr avatar kevaundray avatar lovesh avatar starli-trapdoor avatar swasilyev avatar wanseob avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ethsnarks's Issues

Roadmap

What would another 6-8 months of successful development on ethsnarks look like? What is success?

Ultimately this is making a set of thoroughly-tested and cross-compatible components for developers in a way which makes using zkSNARKS viable for their Ethereum compatible project.

As a developer it should be easy to get started, with documented examples of everything they need to use ethsnarks with their project. Ideally the combination of ethsnarks and related documentation will significantly reduce the time-to-market for projects using zkSNARKs on Ethereum, and have unique advantages that other projects don't - all of this make it a safer choice.

I am attacking the problem from four directions:

  • Cross-platform support
  • Real-world examples
  • SNARK-specific algorithms, to reduce proving time
  • Developer on-boarding, making adoption easy

With the aim being a SDK for zkSNARKS, with Ethereum support, that will save months or even years of time for new projects.

High-level

Specifics

  • Pinocchio and xjsnark compiler support
    • Generate proving key, verification key, proof (as .json) via cli
    • .so/.dll/.dylib support, for embedding into a program
    • Import Pinocchio compiler (into a sub-repo)
    • Provide Pinocchio examples
    • Provide JSnark examples
    • Verify opcode functionality, compatibility
    • xjsnark test cases
  • End-to-end tests for Miximus
    • NodeJS wrapper for the prover .dll/.so file
    • Deposit, Withdraw, using the prover
  • Baby JubJub support
    • More extensive edge-case tests
    • Circuit optimisations
    • Python implementation + tests
    • Solidity implementation + tests + gas costs
    • Pedersen Hash
    • EdDSA signatures (in progress)
  • Use case documentation
    • Building a circuit in C++ - getting JSON output + CLI for free, via cli stubs
    • Integrating a zkSNARK verifier into your solidity project
    • Compiling C using Pequin?
    • Integrating with xjsnark
    • Using baby jubjub
  • Fast hashing algorithms for merkle trees
    • Academic review of algorithms and constructions
    • Python, Solidity and JavaScript support
  • Javascript support, suitable for integrating with web-apps via Metamask etc.
  • Pre-release checklist
    • Coverity scan
    • PVS-Studio scan
    • Optimised release build (make build/openmp-release)
    • Examples page
    • Docker image?

Build on Android

The goal is to make the .so files available to Android projects via JNI or some other Android compatible interface.

That means an Android project should be able to use the prove/verify interfaces of the ethsnarks modules to create parameters which can be submitted to Ethereum contracts.

References:

Ideally there will be a new repository created for Android support, where Travis builds the dependencies (ethsnarks) via a linked git repository using the Android NDK.

The result will be something that can be easily included into an Android project, either via a submodule or a separate repo with pre-built binary objects, which allows Android developers to create zkSNARK proofs compatible with the on-chain components with the least amount of fuss.

As per the emscripten build, this should probably be in a separate repository, see: https://github.com/HarryR/ethsnarks-emscripten as an example.

Remove unnecessary constraints from LongsightF R1CS + fix misunderstandings from paper

As per Daira's comments on #12 (comment)

  1. It's possible to reduce the number of constraints, Daira provided an example where exponent is 5 but uses two less constraints.

  2. It's not necessary for the exponent to create a bijection when using MiMC in Feistel mode. See §5.3

So, if there's no need for a bijection, then the exponent could be reduced to 3. Which would reduce the number of constraints anyway?

Either way, the number of constraints can be reduced:

# This is using two more constraints than necessary. If I'm reading the API correctly, you can do:

t0 = C[i] + c_xL
r1cs_constraint(t0, t0, round_squares[j])             # t1 = (C[i] + c_xL)^2
r1cs_constraint(round_squares[j], round_squares[j], round_squares[j+1]) # t2 = t1^2 = (C[i] + c_xL)^4
r1cs_constraint(round_squares[j+1], t0, rounds[i] - c_xR)      # rounds[i] - c_xR = t4*(C[i] + c_xL)
                                                                        # i.e. rounds[i] = (C[i] + c_xL)^5 + c_xR
j += 2

Have confirmed this works as expected.

Awesome :D Thanks @daira - I will implement this shortly.

Changes necessary:

  • the 3 in the round count calculation is not the exponent, it is cubing. change this back to static 3 rather than the exponent.
  • there is no need for the exponent to satisfy gcd(p-1, e) == 1
  • correct round count calculation asserts
  • create round constants for C++ and Solidity implementation, have LongsightF322p5

This will result in 644 constraints for a full round of LongsightF. And for the merkle tree (r*29) >= 322, so if we use 12 rounds that should work.

Number of rounds for LongsightF:

>>> from py_ecc.bn128 import curve_order
>>> from math import log
>>> log(curve_order, 3) * 2
320.0033959662969
>>> int((log(curve_order, 3) + 1) * 2)
322

Another constraint can be removed by reducing the exponent to 4, there the constraints become:

t0 = C[i] + xL
r1cs_constraint(t0, t0, round_squares[i])             # t1 = (C[i] + c_xL)^2
r1cs_constraint(round_squares[i], round_squares[i], rounds[i] - c_xR)      # rounds[i] - c_xR = t1*t1
                                                                        # i.e. rounds[i] = (C[i] + c_xL)^4 + c_xR

or 3:

t0 = C[i] + xL
r1cs_constraint(t0, t0, round_squares[i])             # t1 = (C[i] + c_xL)^2
r1cs_constraint(round_squares[i], t0, rounds[i] - c_xR)      # rounds[i] - c_xR = t1*t1
                                                                        # i.e. rounds[i] = (C[i] + c_xL)^3 + c_xR

TODO for Harry, re-review MiMC paper in detail, analyse JK97 paper.

New component ideas - how can zkSNARKs benefit projects and companies?

We are searching for ideas where a black box of anonymity could fundamentally improve an industry or enable an entirely new one. If you have any insight we would love a coherent description of how zkSNARKS, or anonymity in general, could be combined with the blockchain.

For example, zkSNARKS could be used for:

  • An equivalent to Ring signatures
  • A token with occluded value
  • Hidden token swaps (e.g. exchanges)

But, are we thinking 'inside the box' here?

Use cases and interesting example projects

In order to make it easier for people to use EthSnarks, we need to create more example applications that demonstrate the functionality and provide a basis for people to build applications on.

Example applications:

  • Batch signature verification
  • Ring signatures
  • Pyramid scheme / Fomo 3d?
  • Dutch auction
  • Passing lots of data from EVM to a zkSNARK circuit
  • Proof of group membership
  • Semaphore implementation
  • Reputation system
  • Neural network
  • Blind exchange

Make circuits and gadgets easier to write

Currently gadgets and circuits require defining a class, which splits it into 3 parts:

  • construction
  • witness generation
  • constraints

One reason for this separation is that generating the proving key requires no inputs, and generating the witness (or simulating the program) requires no evaluation of the constraints.

Ideally it should be possible to write programs in a more procedural style as it would reduce the size of many circuits - requiring smaller and less verbose headers and more immediately understandable programs that closer resemble the pseudocode they implement.

Todo:

  • how to have circuits which are more easily identifiable as being implementations of the pseudocode?
  • two modes, witness and constraints (or both)
  • scripting system, which hides the separation?

A scripting system would be similar to the Pinocchio parser, and would allow all gadgets to be usable from within the parser without modification.

N bit SHA256

Take a vector of bits, and perform the appropriate number of SHA256 rounds chained together then suffixed with the message length.

This means that an SHA256 function with say... 128 bytes of input can be made which matches on-chain SHA256 function with the same input.

Use Miyaguchi-Preneel construct for LongsightL hash function

def compress_MiyaguchiPreneel(m_0, m_1, C, e, p):
  H_0 = LongsightL(IV, m_0, C, e, p)
  H_1 = LongsightL(H_0, m_1, C, e, p)
  return (H_0 + H_1 + m_0) % p

The following needs to be implemented:

  • LongsightL C++ implementation in libsnark
  • test_longsightl program
  • Solidity implementation of LongsightL
  • Generic libsnark Miyaguchi-Preneel construct
  • Solidity Miyaguchi-Preneel construct for LongsightL
  • Python implementation of Miyaguchi-Preneel construct
  • test_longsightl_hash_mp to test longsightl in the construct
  • Solidity tests

This is a fair chunk of work, but should solve the problem.

Reduce constraints for jubjub::PointAdder

in general, you can shave 1 constraints from the ethsnarks implementation here: https://github.com/HarryR/ethsnarks/blob/master/src/jubjub/adder.cpp
it does:

beta = x1*y2
gamma = y1*x2
delta = y1*y2
epsilon = x1*x2
tau = beta*gamma ( = x1*x2*y1*y2)
x3 * (1+d*tau) == (beta+gamma) ( => x3 = (x1*y2+y1*x2)/(1+d*x1*x2*y1*y2) )
y3 * (1-d*tau) == (delta-a*epsilon) ( => y3 = (y1*y2-a*x1*x2)/(1-d*x1*x2*y1*y2) )

if a = -1, you can do the following optimization:

beta = x1*y2
gamma = y1*x2
delta =  (x1+y1)*(x2+y2) ( = x1*x2+x1*y2+y1*x2+y1*y2)
tau = beta*gamma ( = x1*x2*y1*y2)
x3 * (1+d*tau) == (beta+gamma) ( => x3 = (x1*y2+y1*x2)/(1+d*x1*x2*y1*y2) )
y3 * (1-d*tau) == (delta - beta - gamma) ( => y3 = (y1*y2+x1*x2)/(1-d*x1*x2*y1*y2) )

maybe you can do it anyway, for a != -1?

delta = (-a*x1+y1)*(x2+y2) = -a*x1*x2 - a*x1*y2 + y1*x2 + y1*y2

and then use "delta + a*beta - gamma" in the denominator

https://ibb.co/JBhbfRt

So, it's not worthy to find a curve with a=-1 at least for the number of constraints.

Here is the new circuit:: https://github.com/iden3/circomlib/blob/master/circuits/babyjub.circom#L19

Add support for JSnark / Pinocchio intermediate format

It would be really good to support the common opcode format used by JSnark/xJSnark and make it Ethereum compatible, the source code looks fairly simple and would be trivial to include in ethsnarks.

This would allow any circuit written using XJSnark and JSnark to be proven on Ethereum.

Relevant papers:

Relevant source:

It would be good to get some static examples of opcodes + inputs, and a way of verifying the circuit and generating witnesses etc.

The Pinocchio compiler and JSnark seem to be generally compatible with each other, and thus I think this is a good direction to go in.

There are also other compilers, but they are more difficult to directly support due to requiring JavaScript or WebAssembly blocks for witness computation, so the circuit cannot be simulated easily...

Ethsnarks for Windows, and AppVeyor continuous integration

I have setup Windows builds using Appveyor, see: https://ci.appveyor.com/project/harryr/ethsnarks however I don't have a Windows computer (or VM) yet that I can use to test the builds and debug problems with the build system.

CMake supports MSYS, Cygwin and MSVC. Ideally we need to build the following files so they can be used by Windows programs:

  • pinocchio.exe
  • verifier.exe
  • ethsnarks_verifier.dll

It would also be good to verify that the unit tests work on Windows without problems, and possibly the Truffle and Python integration tests too.

Dependencies:

  • CMake
  • Boost
  • OpenSSL
  • GMP or MPIR

I have started with a skeleton appveyor.yml but AppVeyor can't find the GMP library (CMake, Boost and OpenSSL are being detected without problems).

Reduce build times

Now the project is taking about 10 minutes to build, most of this time is due to the tests and compiling all of the templates again and again for each test binary. There must be a way to reduce the build time.

I suggest:

  • static library containing gadgets, other common code
  • static library for hashpreimage, miximus - e.g. the top-level components for circuits
  • use the static libraries to build the _cli executables, tests and .so files

Hopefully that should reduce the amount of unnecessary duplicate compilations.

Need to create header files for the common code, then create an OBJECT library with the common code. Then create an object library for miximus and hashpreimage. Then make everything link against the object and static libraries.

The combination of using header files, linking against object libraries etc. will reduce the number of times each file is compiled, hopefully to only 1, aside from where templates are needed - then those should be split into .tcc files.

Floating Point Integer format, for zkSNARK circuits

It has been suggested that floating point numbers offer a larger dynamic range than integers, which is useful when dealing with cryptocurrencies that may have many zeros and arbitrary units.

Implement a floating point integer format in-circuit with matching Python implementation.

References:

Operations required:

  • Validate
  • Convert to Unsigned Integer
  • Convert from Unsigned Integer

There is no need for addition, multiplication or division operations, it is only necessary to use it as a mechanism for compressing payment amounts.

The precision required by the exponent/mantissa format should be enough to make transfers of up to 1 million units of an ERC-20 token with 18 decimal places. This is different from 'floating point', it is more like a 'high dynamic range integer' where a smaller number of bits control the exponentiation of the integer while a smaller number represent its entropy.

10**18 * 1000000 = 1000000000000000000000000

The dynamic range for integers in Wei should be 0 to 1000000000000000000000000, with as much precision as possible to allow for payments which as closely match what the user desired.

Avoid copy constructors to improve performance

There are a number of places where copy constructors are used that could significantly impact performance as the size of the circuit increases. Using constant references instead of passing by value in some specific places will help a lot.

For example, in libsnark::protoboard the following methods return values rather than references:

  • r1cs_variable_assignment<FieldT> full_variable_assignment() const;
  • r1cs_primary_input<FieldT> primary_input() const;
  • r1cs_auxiliary_input<FieldT> auxiliary_input() const;
  • r1cs_constraint_system<FieldT> get_constraint_system() const;

Making these return constant references will avoid copying a lot of data.

Making several class variables public will also solve this problem.

`test_sha256_full_gadget` fails on Travis OSX

./bin/test_sha256_full_gadget || true
full_output_bytes mismatch
Expected: : 0094F6E585874FE640BE4CE636E6EF9E3ADC27620AA3221FDCF5C0A7C11C6F67
Actual: : D294F6E585874FE640BE4CE636E6EF9E3ADC27620AA3221FDCF5C0A7C11C6F67
FAIL

It seems that the expected is incorrect - the first byte is null, however it should be D2 - so it's working as expected, but there's something buggy going on which causes the check to fail or a null byte to appear.

Merkle tree using elliptic curve hashes

In order to use ECC as part of a merkle tree we need to construct a scheme where the collision resistance property is retained at every level, and where proof of the leaf preimage is required.

For zkSNARK circuits it's cheapest if the base point is fixed, this allows pre-computation of exponents of the basepoint to be used as constants in the circuit, so the doubling step is essentially eliminated - and the circuit performs only one addition step for each bit of the scalar exponent - instead of one doubling per bit of the scalar exponent and an additional addition step for every one bit of the scalar.

More specifically we want the Merkle tree to be compatible with Montgomery coordinates from the leaf to the root without requiring conversion to affine coordinates. This implies that the Montgomery X coordinate is maintained throughout, as is the Edwards Y coordinate.

Another restriction is that a third party will be proving the circuit so you can't have any secret information, so for a public key you must provide an EdDSA signature which proves ownership, then the circuit proves that entry exists in the Merkle tree.

Once an EdDSA signature has been provided, then the Y coordinate from the Edwards point of the public key can be used, then converted to Montgomery form, then the tree operates on the Montgomery curve.

The problem is that, for each step in the tree, the user must provide the adjacent point (in this case, just the X coordinate), this introduces an opportunity for malleability.

So an algorithm for the tree is:

def merkletree(P, path):
    BL = # [fixed base point, in montgomery form]
    BR = # [fixed base point, in montgomery form]
    Mx = edwards_xy_to_mont_x(P.x, P.y)
    Mz = 1
    for is_right, path_x in path:
        if is_right:
            Mx,Mz = BL^path_x + BR^Mx
        else:
            Mx,Mz = BL^Mx + BR^path_x
        Mx, Mz = mont_xz_rescale(Mx, Mz)
    return mont_xz_to_edwards_y(Mx, Mz)

Where the resulting Mx is the root of the tree, which can be converted to Edwards YZ coordinates - but it can't be converted to affine coordinates because we don't have a modulo square root function that works on-chain (too expensive). This requires two fixed-point scalar multiplications, one addition and one re-scale per level of the tree.

Is there anything that's more efficient?

comparison checks on binary

Take 2 vector of bits (a and b) and confim that vector a > b

This can be done two ways

  1. with binary comparisons
  2. with safe add where b + c == a since we cannot overflow we know that there is not other way to add to b and get a unless a > b.

Candidates for in-circuit zkSNARK hash functions

The criteria is:

  • Low multiplicative complexity
  • Implementable in Solidity / EVM with low gas cost
  • Work as a hash function
  • Can be used to construct a Merkle tree

Candidates from authenticated MACs:

Candidates from universal hashing:

Specifically for the Merkle tree, where all information is public, all that we're concerned about is collision resistance and malleability. Specifically in this case the hash127 algorithm can be used with a fixed key (r) per tree level. Where s = (r^{n+1} + (r^n * m_0) + (r^{n-1} * m_1) + ... + (r * m_{n-1})) mod p, m is split into 32 bit chunks and m != 0. The values of r^n etc. can be pre-computed.

Concerning malleability, where each message chunk is the size of a field element we can construct an input which resets the intermediate sum into a known state, the smaller the chunk size the less malleable it becomes at the cost of greater multiplicative complexity. For example with field elements as message input we could just find something that when multiplied by r is the difference between the current sum and our target value to produce a collision, and the more message blocks you have control over the easier this gets.

However, splitting into individual bits increases the complexity of the circuit as we need to verify the bitness of every message input to prevent malleability. If the messages are 256 bits each and the merkle tree is 29 deep, that requires 7424 constraints to verify all input bits. Where we compress two messages into 1 field element using 32 bit blocks it requires an additional 16 constraints per level (464 for 29 levels). Then an additional 464 constraints to compute the polynomial for the 29 levels, which puts the total at 8355 constraints.

There is another approach for LongsightF where, when used as a cipher, it may still be usable as the merkle path authenticator there the number of rounds is less significant. In this case we know that for any given result we can walk backwards to find any number of colliding inputs with control over both input parameters, essentially when used as a merkle path authenticator its construction is similar to a sponge construction in duplex mode.

However, the security of the whole authentication path comes down to finding a single colliding input which is essentially the first item in the Merkle tree path, which means the complexity isn't improved by using the merkle-damgård construct, that is finding appropriate inputs for level 28 is as linear after running level 29 in reverse. A good reference for different constructions is: https://www.emsec.rub.de/media/crypto/attachments/files/2011/03/bartkewitz.pdf

The clearest example I can think of which demonstrates this is the collision resistance of LongsightL where the key can be chosen - if you can choose the keys for each round it's very malleable. However, if the round keys are derived via squaring of a single input then you've essentially eliminated the malleability.

e.g.

def DoublevisionH_a(k, m, r, p):
    x = k
    for _ in range(0, r):
        y = (m + x) % p
        m = (y * y) % p
        x = (x * k) % p
    return m

Even with only 2 values of m this intuitively becomes an intractable problem, where for N rounds the message is multiplied by the square of the key, this makes it very similar to the polynomial used by hash127 and Poly1305+AES with the exception that the message blocks and round constants are derived from two initial values.

The problem we have is the parity of quadratic residue where both m and k are squares after the first round, for example there is a linear route backwards and we know the round function isn't bijective. Interesting references:

When used with the curve order of altBN, as used by the field of the zkSNARK circuit, we can ensure that for any fixed k and any 0 < m < p the result will be a bijection as exponentiating by 5 is also a bijection:

def DoublevisionH_b(m, p, r, k):
	x = k
	for _ in range(0, r):
		y = (m + x) % p
		m = powmod(y, 5, p)
		x = x * k
	return m

But, we can't generalise this for any k and any m as there will be overlap while still satisfying the pigeon hole principal. The best thing we can do is to use two bijective sequences, which in turn are also bijective where either k or m are fixed.

def DoublevisionH_c(m, p, r, k):
	x = k
	for _ in range(0, r):
		y = (m + x) % p
		m = powmod(y, 5, p)
		x = powmod(x, 5, p)
	return m

The problem with this though, is that by controlling either k or m you can force y to be zero, which reduces the complexity to finding an initial k than when squared/exponentiated r times results in the desired m.

Another problem is that m and k can be switched and the result is the same, when used in a Merkle tree this is bad as we need to be strictly order-enforcing, whereas when x is squared at each iteration the order is enforced, likewise if k is exponentiated in a bijective fashion m can be squared and the result is still a bijection.

The y step can be either additive or multiplicative.

However, after further testing the problem with either of these is that when m is fixed and k is variable it isn't a bijection, the function can be modified to satisfy this requirement, but then only the last value of y is used which removes a layer of complexity.

def DoublevisionH_d(m, p, r, k):
	for _ in range(0, r):
		y = (m + x) % p
		m = powmod(m, 5, p)
		k = powmod(k, 5, p)
	return y

Adding y to either k or m retains the quality of being a bijection where the same argument that's fixed is the one which y is added to. e.g. for all k \in Z_p and k=(k+y)^5 where m is constant and visa versa.

def DoublevisionH_e(m, k, p):
	y = 0
	r = 4
	for _ in range(0, r):
		y = (m + k) % p
		m = powmod(m, 5, p)
		k = powmod((k + y) % p, 5, p)
	return y

In my pigeonhole surjection test, which measures the distribution of pigeons in holes for all N and M for H(N,M) e.g. for N in range(0,p): for M in range(0,p): H(N,M), the standard deviation for LongsightF hovers at around 2 and the variance at 4. Whereas with DoublevisionH_e the standard deviation hovers at around 1 and variance at 1. If I add y to both k and m before exponentiating them we get similar standard deviation and variance as LongsightF.

So we have a measurable quality which indicates a bijection that the variance is ~1 and stddev is ~1, whereas without a bijection the stddev is ~2 and variance ~4 which indicates the bijective flavour is more consistently distributed, which indicates less randomness.

But, is more random distribution a good quality? Is there a relation between the bijective nature when one parameter is constant, versus something which isn't a bijection. My gut feel is that, with a large enough |p| the additional complexity of having both m and k be x=(x+y)^5 computationally binds both parameters to the value from the previous round. It doesn't solve the "first round y is zero" problem though, but I think that's unsolvable in that you can always algebraically negate the first round with control over one parameter - but that doesn't give you control over the other one which is the crux of the matter.

Which leaves us with one interesting candidate:

def DoublevisionH_f(m, k, p, r):
	y = 0
	e = 5
	for _ in range(0, r):
		y = (m + k) % p
		m = powmod((m + y) % p, e, p)
		k = powmod((k + y) % p, e, p)
	return y

Where the exponent 5 is replaced with whatever is a bijection for that specific field. However, it still suffers the problem of the argument order being reversible due to the commutative nature of the y step, and for a Merkle tree it's absolutely necessary to have ordering.

For the ordering property we can use two sequences of round constants for both m and k, which becomes:

def DoublevisionH_g(m, k, p, r, C):
	y = 0
	e = 5
	for i in range(0, r):
		y = (m * k) % p
		m = powmod((m + y + C[(2*i)]) % p, e, p)
		k = powmod((k + y + C[(2*i)+1]) % p, e, p)
	return y

This ensures that, for round 0, when k and m are equal, y will also be equal, but for round 1, the sequences m and k will have diverged and will be dependent upon their initial value.

Furthermore, by using multiplication in the step for y the degree of the polynomial increases at each round, such that deg(f(x)g(x)) = deg(f(x)) + deg(g(x)) we can show that:

  • Round 1
    • y = m * k
    • deg(y) = 0
    • m = (m + y + C_?)^5
    • deg(m) = 5
    • k = k + y + C_?
    • deg(k) = 5
  • Round 2
    • y = m * k
    • deg(y) = deg(m) + deg(k) = 10
    • m = (m + y + C_?)^5
    • deg(m) = 10 (as the degree of y is 10)
    • k = k + y + C_?
    • deg(k) = 10
  • Round 3
    • y = m * k
    • deg(y) = deg(m) + deg(k) = 20
    • ...
  • ...

As such, the degree doubles at every round, such that after 127 rounds the degree exceeds 2^128, after 30 rounds the degree exceeds 2^31 etc. However, does that mean a collision could be found in 2^r probability using higher order differential cryptanalysis? And furthermore, is my statement about the degree of the polynomial correct, or is it limited to 5 as that's the outside exponent of the k and m equations?

An alternative which pushes the degree further up into the statement is:

def DoublevisionH_h(m, k, p, r, C):
	y = 0
	e = 5
	for i in range(0, r):
		y = (m * k) % p
		m = (powmod((m + C[(2*i)]) % p, e, p) + y) % p
		k = (powmod((k + C[(2*i)+1]) % p, e, p) + y) % p
	return y

Which makes it:

  • Round 0
    • y = (m * k)
    • m = (m + C_?)^5 + (m * k)
    • k = (k + C_?)^5 + (m * k)
  • Round 1
    • y = ((m+C_?)^5 * (k+C_?)^5)
    • deg(y) = 10
    • m = (m + C_?)^5 + y
    • deg(m) == 10 ?
    • etc.
  • etc.

In that case, if you were to multiply by y in stages m and k surely that would increase the rate of the degree at every iteration, at the cost of multiplicative complexity, for example:

def DoublevisionH_i(m, k, p, r, C):
	y = 0
	e = 5
	for i in range(0, r):
		y = (m * k) % p
		m = (powmod((m + C[(2*i)]) % p, e, p) * y) % p
		k = (powmod((k + C[(2*i)+1]) % p, e, p) * y) % p
	return y

Where:

  • Round 0 = degree 0
  • Round 1 = degree 10
  • Round 2 = degree 30
  • Round 3 = degree 70
  • etc.

However, we've diverged far away from my original proof of the bijective property, and gotten into very speculative musings of the degree of polynomials and its relation to security under higher order differential analysis.

The following paper by Xuejia Lai researches on the security of multivariate hash functions: [1] https://eprint.iacr.org/2008/350.pdf - which references MQ-HASH - [2] https://pdfs.semanticscholar.org/a258/904c3e021df8c2de621b7dfa4dc504c8f3b2.pdf - 'On Building Hash Functions From Multivariate Quadratic Equations - Billet, Robshaw & Peyrin' which is further supplemented by 'Interpreting Hash Function Security Proofs' - [3] https://infoscience.epfl.ch/record/172008/files/Juraj-Provsec2010.pdf

In [1] Lai states the degree of MQ-HASH is four... where it's necessary to compute the dth derivative for 2^d inputs, where d is the degree of the multivariate function, if the derivative is a constant we can distinguish the function sampled from a uniformly distributed function, although soemtiems its difficult to translate from mathlish, but it implies that a higher order differential attack requires computation equal to 2^d where d is the degree of the polynomial.

The reason I wanted to rely strongly on the property of a bijection for either k or m is to reduce the sparseness of the polynomial surjection for both k and m. In § 4.1 of [1] Lai describes the probability of trivial collisions, however this is represented over GF(2) rather than GF(p).

.... brain slowly melts I think a better way of analysing this would be via calculus and its relation to quadratic residue over a field.

Either way, we can break this down into the statement:

x[i] = (x[i-1] + C[i])^5 * x[i-1]

Comparative to the MiMC round function:

x[i] = (x[i-1] + C[i])^5 + x[i-2]

Where the MiMC function uses two variables, denoted by x[i-1] and x[i-2], our round function when truncated to a single variable doubles the polynomial degree. When expanded to account for two variables it becomes:

z[i] = x[i-1] * y[i-1]
x[i] = (x[i-1] + Cx[i])^5 * z[i]
y[i] = (y[i-1] + Cy[i])^5 * z[i]

So, to summarise, we now have a compression function which:

  • takes two arbitrary field elements as inputs
  • the input parameters are ordered after at least 1 round
  • the following are intractable problems:
    • finding the inverse from the result
    • finding a target collision using either k or m where the other element is a constant

I think this is good enough to be used as a compression function to form a Merkle tree.

Convert `main()` functions to use `boost::program_options` and/or reduce code duplication in program entry-points.

See: https://www.boost.org/doc/libs/1_58_0/doc/html/program_options/tutorial.html#idp337609360

If we want to be more idiomatic C++ and use less C-style stuff and enable more complex command-line options, would this be a good improvement?

There are common 'program stubs' which are used as the entry-point for many commands:

Any of the programs using the stubs gets any options added to the stubs for free; for example, if you wanted to add support for multiple provers (e.g. Groth16 and GM17) adding a --prover=... option to the stubs could more easily make a different prover available without having to significantly modify the programs which use the stubs.

Program entry-points are:

Ideally, implementing extra program options should try to make all of the main functions cleaner and more consistent with a greater amount of shared code and less duplication, in addition to adding common functionality and making it easier to extend the stub programs with extra options.

On-chain incremental Merkle tree append with reduced gas costs

The incremental merkle tree implementation included in MerkleTree.sol requires a large amount of gas to perform the update, this is because all of the nodes for the merkle tree are stored on-chain.

I have two proposals to reduce the cost of appending an item to the incremental merkle tree in a way which reduces the storage cost significantly by only storing the root and none of the intermediate levels or leaves.

Clients monitoring the contract would have to re-create the merkle tree by monitoring events emitted as every leaf is added to the tree. e.g. upon each append, it emits a LeafAppend(offset, data), then you retrieve every LeafAppend event and re-construct the merkle tree locally.

It would be possible for light-clients to use a verifiably honest third party to store the merkle tree for them, it would need to monitor the contract and be able to provide proofs for any item that can be verified using the on-chain contract.

  1. Pre-compute the whole merkle tree, using 'unique elements' as placeholders for everything, allow updates in-sequence by providing the elements proof and the new leaf hash.

  2. Require a proof of the last element in the tree

Note on 'unique items'

For any given node in the tree, to be used as an incremental merkle tree it's necessary to have 'synthetic and unique items' which can be computed quickly to create proofs using items which don't exist yet. These balance out the tree.

e.g. if you have a tree with one leaf (L) the second leaf is one of these synthetic items, it's a unique placeholder (X)

   r
 /   \
L     X

When you add another leaf, the synthetic item disappears, in its place the real item is used

   r
 /   \
L     o

These synthetic items allow you to make a merkle tree with a fixed depth (required for the prover circuit), with a fixed depth of 3 this becomes (with one item), which requires two synthetic items to balance out the tree.

     r
   /   \
  n     X
 /  \
L    X

Precomputation

Precomputing the whole tree would need to store (2*N)-1 items, e.g. 2<<31 items would require (2<<32)-1 total items in storage. However, this would only need to be computed once to find the root node, as all other items are predictable.

In this tree, every node and every leaf is synthetic, every node is real (as it's derived from values, albeit synthetic ones), and the root must be real, e.g.

         r
     /       \
    n         n
  /   \     /   \
 X     X   X     X

The root and every node can be created using a deterministic number of items in-memory at any point in time, with all unnecessary items (those which will never be used again) could be discarded as they can be can be re-computed.

The problem comes when verifying the root, which requires recomputation of everything in the path and its dependencies, to avoid the cost of computation you would have to store ((2*N)-1) * HashSize items on disk, with 2<<27 items (268m) and a hash size of 32 bytes this requires 8gb of storage.

This also requires the tree to be updated whenever items are added, otherwise subsequent proofs would be based on incorrect data, however it could be updated incrementally - changing only one item and then recomputing all the nodes in the upward path to the root.

The amount of storage vs the cost of re-computing the tree can be balanced out by caching/storing the top N levels of the tree, and requiring re-computation of N levels of the subtree. e.g. where C is cached the number of levels below is the number of items which need recomputing, e.g. 2^N, in this case N is one as only one level needs recomputing.

e.g. if height is 3, number cached is 2 then number recomputed is 2^(3-2)

         r
     /       \
    n         C
  /   \
 X     X 

e.g. if Height is 29, cached is 15, total size of cache (for 32byte items) is 1 MiB and (32768 items) and the number of items requiring recomputation is 16384, with SHA2-256 this number looks like a reasonable trade-off, however with LongsightF the field operations may make this many times more expensive.

The on-chain component knows the number of 'real' leaves, and requires a merkle proof of the next item (which doesn't exist yet) along with the new leaf value to re-compute the root, it doesn't need to store anything other than the root and the sequence (or, count, of real leaves).

Append via Proof of End

This follows the incremental merkle tree model more closely and requires no precomputation, however the on-chain contract must be aware of which nodes are synthetic and which aren't so that it can enforce the synthetic nodes values and re-compute ones from the previous proof which have been changed as a result.

e.g. if there is a tree with 1 item and you want to add another, you provide proof of the first item, from which proof of the 2nd item and therefore the root can be derived.

The advantage of this, versus the pre-computed tree, is that knowing the previous valid merkle path, sequence number and how the synthetic nodes are created then you can add an item to the tree without knowing all of the values. this makes it very efficient for 'light clients'.

See diagrams below for reference.

TODO: find simple algorithm to determine the common pairs from two merkle paths to verify that one was added immediately after the other. They must share at least 1 common node.

        r
     /     \
    n1      Y
  /   \   
 L1    X 

Proof of L1 is [0,0] [X,Y]

        r
     /     \
    n1      Y
  /   \  
 L1   L2

Proof of L2 is [1,0] [L1,Y]

        r
     /     \
    n1      n2
  /   \    /  \
 L1   L2  L3   Z

Proof of L3 is [0,1], [Z,n1]

        r
     /     \
    n1      n2
  /   \    /  \
 L1   L2  L3  L4

Proof of L4 is [1,1], [L3,n1]

Reducing size of proofs, by being 'synthetic aware'

Any synthetic node included in the path can be omitted if there is a deterministic algorithm to identify which nodes in the path are synthetic using only the sequence (total number of items) of the merkle tree.

e.g. with the following tree, providing a proof of L requires X and Y, where the address bits are [0, 0] and the path is [X, Y]. However, because the leaf X and node Y are synthetic the path can be empty and all that is required are the address bits - then the on-chain contract can re-create the synthetic parts. It would need to verify the synthetic items in the path anyway, otherwise tree proofs could potentially be faked.

       r
     /  \
    n    Y
  /   \
 L     X 

Because the synthetic items need to be verified, the only question is whether or not excluding them from the msg.data for the function parameters, versus the cost of keeping track of which items were synthetic vs which are real (e.g. if item 1 is synthetic, and item 2 is real, the path would be [X, Z] where Y is skipped) versus the cost of omitting these items.

Problems with both approaches

The problem with both of the approaches is that it only allows one insert into the merkle tree per block, as while there will be a conflict if two people try to append at the same time.

unfortunately the way to fix this is to store the merkle tree on-chain.

it may be possible to use a side-chain which aggregates updates and submits them to the main-chain in one go, however IMO this adds an unnecessary component which can be interfered with etc.

General optimisations for libsnark

Need to verify or reflect on the following opportunities for optimisation:

  • are vtable lookups used for G1, G2 and G12 arithmetic operations? if so, can they be avoided
    • Create test executables for different cases, load in IDA, do analysis work to see if vtables are being used
  • Does rvalue usage reduce number of copy constructors, realistically, in any code that's used?
  • Can improvements for general BN256 operations be improved by using the same improvements from https://github.com/cloudflare/bn256 ?
  • Run through valgrind, cachegrind etc.
  • Use -flto and -fwhole-programwith GCC, see: https://stackoverflow.com/questions/13674894/when-to-use-certain-optimizations-such-as-fwhole-program-and-fprofile-generate - all oflibsnarkcan be built like this, butlibgmp` uses the system implementation

Pairings on Twisted Edwards Curves

The following papers cover this:

Faster Pairing Computations on Curves with High-Degree Twists.pdf
pairings_lange.pdf
TransComp2013.pdf

Slideshow:

Reduce exponent to 3 for LongsightF

As per §5.3 of https://eprint.iacr.org/2016/492.pdf

Remember that for MiMC-n/n, d has to satisfy the condition gcd(d, 2^n − 1) = 1 in order to be a permutation, while in the case of MiMC-2n/n (that is, for Feistel Networks) this condition is not necessary.

...

Thus, the number of rounds to guarantee the security against the algebraic attacks doesn’t change choosing exponent of the form 2^t + 1 for t > 1. That is, both from the security point of view and from the implementation one, there is no advantage to choose exponents of the form 2^t + 1 greater than 3.

I think it's safe to say that the current exponent 5 can be reduced to 3.

However, there is ambiguity in the MiMC paper...

Crash in `stub_main_genkeys`

This was causing crashes in ethsnarks-miximus and other projects, but only with GCC on Linux, it doesn't seem to be a problem with clang++ on OSX.

The pointer to pb.constraint_system.constraints seems to differ across calls.

template<class GadgetT>
int stub_genkeys( const char *pk_file, const char *vk_file )
{
    ppT::init_public_params();

    ProtoboardT pb;
    GadgetT mod(pb, "module");
    mod.generate_r1cs_constraints();

    return stub_genkeys_from_pb(pb, pk_file, vk_file);
}

Then in stub_genkeys_from_pb it crashes in the copy constructor for libsnark::r1cs_constraint_system during call to get_constraint_system.

int stub_genkeys_from_pb( ProtoboardT& bt
    pb, const char *pk_file, const char *vk_file )
{
    const auto constraints = pb.get_constraint_system();

Backtrace from GDB:

gdb) p &pb
$7 = (ethsnarks::ProtoboardT *) 0x7fffffffdd00
(gdb) down
#16 0x000000000043b510 in ethsnarks::stub_genkeys_from_pb (pb=..., pk_file=0x7fffffffe27e ".keys/miximus.pk.raw", vk_file=0x7fffffffe293 ".keys/miximus.vk.json")
    at /home/user/github.com/HarryR/ethsnarks-miximus/ethsnarks/src/stubs.cpp:46
46	    const auto constraints = pb.get_constraint_system();
(gdb) p &pb
$8 = (ethsnarks::ProtoboardT *) 0x7fffffffdd00
(gdb) p *pb
No symbol "operator*" in current context.
(gdb) p pb.constraint_system.constraints.size()
$9 = 1756833606647864122
(gdb) p &pb.constraint_system 
$10 = (libsnark::r1cs_constraint_system<libff::Fp_model<4, (libff::bigint<4> const&)(&libff::alt_bn128_modulus_r)> > *) 0x7fffffffdda0
(gdb) p &pb.constraint_system.constraints
$11 = (std::__debug::vector<libsnark::r1cs_constraint<libff::Fp_model<4, (libff::bigint<4> const&)(&libff::alt_bn128_modulus_r)> >, std::allocator<libsnark::r1cs_constraint<libff::Fp_model<4, (libff::bigint<4> const&)(&libff::alt_bn128_modulus_r)> > > > *) 0x7fffffffddb0
(gdb) up
#17 0x000000000040fcb9 in ethsnarks::stub_genkeys<ethsnarks::mod_miximus> (pk_file=0x7fffffffe27e ".keys/miximus.pk.raw", vk_file=0x7fffffffe293 ".keys/miximus.vk.json")
    at /home/user/github.com/HarryR/ethsnarks-miximus/ethsnarks/src/stubs.hpp:29
29	    return stub_genkeys_from_pb(pb, pk_file, vk_file);
(gdb) p &pb.constraint_system.constraints
$12 = (std::vector<libsnark::r1cs_constraint<libff::Fp_model<4, (libff::bigint<4> const&)(&libff::alt_bn128_modulus_r)> >, std::allocator<libsnark::r1cs_constraint<libff::Fp_model<4, (libff::bigint<4> const&)(&libff::alt_bn128_modulus_r)> > > > *) 0x7fffffffdd70

GDB shows &pb.constraint_system.constraints in stub_genkeys_from_pb differs from the address in stub_genkeys. In stub_genkeys the address is correct (0x7fffffffdd70), but one level down in stub_genkeys_from_pb the address is incorrect (0x7fffffffddb0).

Safe Add and Safe Subtract

For many applications there's a need for a safe add and safe subtract.

This should be an N bit number with overflow protection.

This is related to #64

Use README.md to better promote the benefits of ethsnarks

For example:

  • Miximus / UXTO example, with end-to-end tests
  • Groth16 (minus GT), cheaper on-chain Gas costs for proofs (450k + 40k per public input)
  • Fastest in-circuit Merkle tree
  • Build once, run on many platforms (Web, Mobile, Desktop)

What are the other benefits of using ethsnarks?

Release build on OSX/clang optimisation causes problems

As per HarryR/ethsnarks-miximus#7 (comment)

Under the release build there is a problem where it crashes (while proving) immediately before:

  • QAP number of variables: 22897

This is likely an odd bug with the optimiser passing an invalid object reference so the number of variables exceeds the number of roots of unity (e.g. it gets a 64bit pointer, which exceeds 2**28).

There is a second bug:

from file '/tmp/lto.o' means the weak symbol cannot be overridden at runtime. This was likely caused by different translation units being compiled with different visibility settings.

When including ethsnarks or libff in a .dylib, where by default visibility should be hidden apart from the necessary external module symbols.

Implement Jarvis and Friday

From:

This ticket includes the following sub-tasks:

  • Analyse MiMC, Jarvis & Friday papers
  • Verify if the Python pseudocode below implements it faithfully
  • Wait for further analysis from somebody qualified to analyse security, or further papers
  • Implement Python version of Jarvis and Friday, add unit tests
  • Implement libsnark circuit which matches Python implementation
  • Implement Solidity functions which match Python and libsnark implementations

Jarvis is an improvement upon MiMC, it hopefully attains the same level of security in a way which costs much less to verify in a zkSNARK circuit.

MiMC-p/p (sometimes called LongsightL) is has been implemented and included in Ethsnarks in combination with the Preneel one-way compression function:

This allows for very cheap merkle-trees and hashing, in a way which is 'field-native' - that is, it uses field elements rather than bits to achieve hashing and encryption of many bits per operation rather than many operations per bit.

However, it would be good to implement Jarvis as an alternative to LongsightL and MiMC-p/p, as this could reduce the number of constraints required for large Merkle trees, and significantly proving time.

The difference between MiMC and Jarvis is that the modulo inverse is used to increase the degree of the polynomial to excessive numbers, rather than relying on a specified number of rounds to increase the degree to an acceptable number.

The Preneel one-way compression function is already implemented, and used in conjunction with MiMC in a very similar manner to Jarvis and Friday, however Jarvis is more complex to compute on EVM (requiring modulo inverse at every round via the modexp instruction), but relatively simple to verify (in the same way that constraints are verified).

The following are my notes on a possible implementation:

Making all these changes, the Jarvis round function becomes:

Where the key expansion function is:

The key derivation function being:

def jarvis_keys(k, c):
    # key is master key
    # c is list of round constants
    for c_i in c:
        k = (1/k) + c_i
        yield k  # emit sequence of derived keys

The Jarvis function would be:

def jarvis(k, m, c):
    # m is a message
    # k is master key
    # c is list of round constants, length is the number of rounds
    s_i = m + k
    for k_i in jarvis_keys(k, c):
        s_i = (1/s_i) + k_i
    return s_i

The Friday function is

def friday(k, messages, c):
    for m in messages:
        k = k + m + jarvis(k, m, c)
    return k

Without accounting for x = 0, this can be enforced with one constraint per round:

a = s_i
b = 1/a + k_i
assert (a * (b - k_i)) == 1

While accounting for x=0 and x!=0, this can be enforced with 3 constraints per round:

# r = f(x); uses linear combination to add round key?
assert (1 - b) * b == 0
assert (1 - b) * x == 0
assert r * (x + b - 1) == b

Compiling on mac and other issues

The fix

I don't have a good grasp on making Makefiles so versatile, but wanted to document the issues I faced compiling/building this repo on a MacBook 13" Mid-2014 with MacOS High Sierra version 10.13.6 and how I resolved them to build and compile the contracts according to an updated MakeFile.

Dependences:

Yarn - Make sure you have yarn installed as this will be the node package manager used.

I ran into issues initially compiling libsnark, which has seen similar issues on their board. The solution that I found was to include the following in my ~/.bash_profile and use the makefile located in https://github.com/drewstone/ethsnarks with Node 10.9.0 (I use n for nodejs management.

LD_LIBRARY_PATH=/usr/local/opt/openssl/lib:"${LD_LIBRARY_PATH}"                    
CPATH=/usr/local/opt/openssl/include:"${CPATH}"                                    
PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig:"${PKG_CONFIG_PATH}"          
export LD_LIBRARY_PATH CPATH PKG_CONFIG_PATH 

Hopefully this helps someone running on Mac in the future.

For clarity, the entirety of the makefile is below:

ROOT_DIR := $(shell dirname $(realpath $(MAKEFILE_LIST)))

PYTHON ?= python3
NAME ?= ethsnarks
NPM ?= yarn
GANACHE ?= $(ROOT_DIR)/node_modules/.bin/ganache-cli
TRUFFLE ?= $(ROOT_DIR)/node_modules/.bin/truffle

COVERAGE = $(PYTHON) -mcoverage run --source=$(NAME) -p


#######################################################################


all: build/src/libmiximus.so truffle-compile

clean: coverage-clean
	rm -rf build
	find . -name '__pycache__' -exec rm -rf '{}' ';'


#######################################################################


build:
	mkdir -p build

bin/miximus_genKeys: build/Makefile
	make -C build

build/src/libmiximus.so: build/Makefile
	make -C build

build/Makefile: build CMakeLists.txt
	git submodule update --init --recursive && cd build && CPPFLAGS=-I/usr/local/opt/openssl/include LDFLAGS=-L/usr/local/opt/openssl/lib PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig cmake  .. -DWITH_PROCPS=OFF -DWITH_SUPERCOP=OFF
	

#######################################################################


.PHONY: test
test: cxx-tests truffle-test python-test

python-test:
	$(COVERAGE) -m unittest discover test/

cxx-tests:
	./bin/test_longsightf
	./bin/test_one_of_n
	./bin/test_shamir_poly
	./bin/test_sha256_full_gadget
	./bin/test_field_packing > /dev/null
	time ./bin/hashpreimage_cli genkeys zksnark_element/hpi.pk.raw zksnark_element/hpi.vk.json
	ls -lah zksnark_element/hpi.pk.raw
	time ./bin/hashpreimage_cli prove zksnark_element/hpi.pk.raw 0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a089f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 zksnark_element/hpi.proof.json
	time ./bin/hashpreimage_cli verify zksnark_element/hpi.vk.json zksnark_element/hpi.proof.json
	time ./bin/test_load_proofkey zksnark_element/hpi.pk.raw


#######################################################################


coverage: coverage-combine coverage-report

coverage-clean:
	rm -rf .coverage .coverage.* htmlcov

coverage-combine:
	$(PYTHON) -m coverage combine

coverage-report:
	$(PYTHON) -m coverage report

coverage-html:
	$(PYTHON) -m coverage html


#######################################################################


python-dependencies: requirements requirements-dev

requirements:
	$(PYTHON) -m pip install -r requirements.txt

requirements-dev:
	$(PYTHON) -m pip install -r requirements-dev.txt

fedora-dependencies:
	dnf install procps-ng-devel gmp-devel boost-devel cmake g++

ubuntu-dependencies:
	apt-get install cmake make g++ libgmp-dev libboost-all-dev libprocps-dev

zksnark_element/pk.json: ./bin/miximus_genKeys
	$< 3 zksnark_element/pk.json zksnark_element/vk.json


#######################################################################


nvm-install:
	./utils/nvm-install
	nvm install --lts

node_modules:
	$(NPM) install

$(TRUFFLE): node_modules

$(GANACHE): node_modules

.PHONY: truffle-test
truffle-test: $(TRUFFLE)
	$(NPM) run test

truffle-compile: $(TRUFFLE)
	$(TRUFFLE) compile

testrpc: $(TRUFFLE)
	$(NPM) run testrpc


#######################################################################


python-pyflakes:
	$(PYTHON) -mpyflakes $(NAME)

python-pylint:
	$(PYTHON) -mpylint $(NAME)

C++ coding standards and code quality suggestions

We need to slowly improve the quality of the C++ code used in the ethsnarks project.

Any insight from skilled C++ practitioners is welcome, especially if it provides an example of before & after, and how this improves code quality etc.

For example:

  • namespaces aren't used for ethsnarks code, but should be
  • CMake files could be cleaner
  • common patterns could be made into composable units/templates
  • anti-patterns and unnecessary duplication
  • C++11 functionality which can be taken advantage of
  • reducing memory usage, avoiding copy constructors

Increase number of rounds for MiMC (or LongsightL)

So, I should've originally listened to Daira, as she's a beacon of wisdom and insight, and further research has shown that MiMC-p/p with 12 rounds is very insecure.

This ticket aims to address the shortcomings of the current implementation and making it 'secure' with some small tweaks.

Define LongsightMP-R/p/e as Miyaguchi–Preneel applied to MiMC-R/p/e. We need R = log7(p) ~= 91 rounds for the non-Feistel variant of MiMC-R/p/7. Raising to the power 7 requires 4 constraints. So, for the BLS12-381 p,

  • LongsightMP-91/p/7 requires 728 constraints.
  • LongsightL-91/p/7 requires 1092 constraints.
    (For reference, the insecure LongsightF-322/p/3 required 644 constraints.)

Exponent of 5 is a bijection over F_p for the altbn scalar field used by Ethereum, need to verify if exponent of 7 is a bijection also.

With exponent=7, each round requires 4 constraints for exponentiation:

def round_e7(x, p):
    a = (x*x) % p # x^2
    b = (a*a) % p # x^4
    c = (a*b) % p # x^6
    return (c*x) % p# x^7

Where 91 rounds requires 364 constraints when using a round-constants.

R = 91 == ceil(log(p) / log(7))
R = 161 == ceil(log(p) / log(3))

The round constants for the keys can be generated as a hash stream using keccak256.

def round_constants(seed, p, R):
    for _ in range(R):
        seed = H(seed)
        yield seed

Any personalisation must change the initial seed, to make it derive a unique sequence of round constants. The constants are used to personalise the use of the key.

Note that for the first round the constant is zero, and for the last round the key is appended to the result, this matches the following implementations and is required to ensure the result is bijective:

Pseudocode:

def mimc(k, x, seed, p, e, R=91):
    for c_i in [0] + round_constants(seed, p, R - 2):
        a = (x + k + c_i) % p
        x = (a ** e) % p
   return (x + k) % p

The Miyaguchi–Preneel one-way construct can be implemented as a linear combination between rounds, returning a linear combination with the previous result, requiring no additional constraints.

def mimc_pe7_mp(k, x, seed, p, e, R=91):
    for x_i in x:
        k = (k + x_i + mimc(k, x_i, seed, p, e, R)) % p
        yield k
  • Rename to MiMC_MP? when used with Miyaguchi–Preneel compression function
  • Verify exponent of 7 is a bijection over F_p (for the altbn scalar field)
  • Use exponent of 7 for Longsight MP, with 91 or 46 rounds
  • Make computation of round constants automatic rather than pre-computed (applies to both C++ and Solidity versions)
  • Gas benchmark for Solidity implementation.

Use case - Prescriptions

Say Bob goes to the doctor and is prescribed a medication that he needs. The doctor writes a prescription for Bob in a ZkSNARK-like contract system. The prescription can be represented as a token that has specific properties. The prescription is authenticated by the specific doctor for the specific patient Bob to fill a quantity and dose of a drug for a designated period. Prescriptions are non-transferrable so Bob cannot transfer the prescription to his friend Alice to fill for herself. However the prescription is transferrable between pharmacies and doctors.

Bob is given the prescription by the doctor and he wants his prescription filled at some discount online drugstore because they deliver fast and are more affordable than his local pharmacy. Bob transfers the prescription to the online store. This particular prescription, however, is a controlled substance and requires verification of Bob's identity. Bob submits his ZkSNARK-style identity token to the ZkSNARK-style online pharmacy contract which authenticates that he is the correct Bob that matches the one on the prescription token.

Cindy works at the discount online pharmacy and likes to look up people's facebook profile when people like Bob order prescriptions because she is weird and has nothing better to do. But because Bob used his ZkSNARK-style identity token, she doesn't even know Bob's name but can still verify him against the prescription's control.

But Cindy needs to know how much to charge Bob for the prescription, so Bob submits his ZkSNARK-style health insurance token, which keeps Bob's information private but has properties for this category of prescription that lets Cindy know how much to charge Bob to fulfill the prescription and ship the medication to him.

There are plenty more examples in the medical industry alone with HIPAA and treatments where privacy, authentication, accuracy, and billing are all paramount.

Pre-release sanity checks for C++ code

It would be good to catch obscure errors ahead of major releases which may not be caught by unit tests. I suggest an additional set of tests, which run all of the C++ unit tests, under the following set of tools:

memcheck has an option to create a page per memory allocation, which while expensive to check can also accurately detect bounds overflows.

I also added the cxx-lint target to the Makefile which uses cppcheck to find common errors in the source code.

Other options available are:

I think that it's worth running the whole project through this set of tools at least twice, once as an interim mid-way through development, and once before major releases, so that we have as many opportunities as possible to catch and fix subtle or previously unrealised bugs.

However, none of these tools can help without thorough tests which verify the critical paths and their edge cases.

Streaming Prover, reduce memory overhead of prover

See:

There are optimisations which can be made which reduce the necessary amount of in-memory data required for the proving key.

  1. Don't use Jacobian coordinates for the proving key while it's in-memory (removes the X coord), allocate X coord on stack when performing calculation
  2. Use a directly accessible format which allows the proving key to be mmap'd and let the kernel figure it out.
  3. Anything which is used independently and sequentially means the rest can be discarded:

The proving key consists of five parts - PK_A, PK_B, PK_C, PK_K, PK_H which are used independently and sequentially.

Load each one at a time, rather than all of them.

Either way, need to create a benchmark for prove which tracks the peak memory usage.

Any changes should avoid modifying libsnark directly, we still want to use it as a submodule.


With the mmap approach, if the offsets & sizes of sections for A, B, C, K and H are known ahead of time, then madvise can be used depending on the access pattern. If it's purely sequential then MADV_SEQUENTIAL can be used, however if it's at random then doing a pre-emptive sequential read into memory will offer a performance improvement.

Either way - disk reads vs memory usage trade-off again.

Build WASM prover / module

For any of the SNARK circuits, we should be able to build a WebAssembly module which:

  • exports prove / verify functions
  • allows loading proving key from HTTP backend

This seems fairly straightforward, using the current cmake project, to build target webassembly using Emscriptem, without modifying any of the C++ code heavily.

References:

This will need to be a separate repo which uses the ethsnarks repo as a sub-module.

Use correct native library extension on various platforms

At the moment the tests hard-code .so, but on OSX .dylib is the library extension and on Windows .dll is

import platform
from ctypes import *

# get the right filename
if platform.uname()[0] == "Windows":
    name = "win.dll"
if platform.uname()[0] == "Linux":
    name = "linux.so"
else:
    name = "osx.dylib"

# load the library
lib = cdll.LoadLibrary(name)

Can probably add a utility function which appends the correct library extension, and use that in tests?

Updated curve parameters with a=-1

As implemented by Matter Inc. which changes the curve parameters to be able to use the unmodified sapling-crypto repository: https://github.com/matterinc/sapling-crypto

The C++ implementation is parameterised, the Python and Solidity implementations aren't.

Related to #103 (to match the number of constraints as the zcash/bellman implementation)

They have scaled the parameters so the Bellman implementation of Jubjub can be used without modifying the constraints, this means the security proof from ZCash with their specific constraints can be more directly translated / be applicable.

//! scaling = 1911982854305225074381251344103329931637610209014896889891168275855466657090 
//! a' = 21888242871839275222246405745257275088548364400416034343698204186575808495616 == -1 = a*scale^2 mod P
//! d' = 12181644023421730124874158521699555681764249180949974110617291017600649128846 == -(168696/168700) = d*scale^2

For the Python and Solidity implementations this requires the addition of the Scale parameter.

Additionally, need to update the ejubjub.sage file to demonstrate that we can transform from the existing curve parameters to the modified ones.

Additionally need to verify that the jubjub tests defined by zcash are implemented:

  • (1 / d) is nonsquare
  • -d is nonsquare
  • (1 / -d) is nonsquare
  • Check that A^2 - 4 is nonsquare
  • Check that A - 2 is nonsquare

Additionally, there is a test to verify that the number of windows per generator in the Pedersen hash does not allow for collisions:

Sponge hash for GF(p)

The goal of this research is to identify a cryptographically secure hash function which can be used to authenticate a Merkle tree path and to construct a Merkle tree.

As per:

We start with the knowledge that, for the curve order for altBN as used by the zkSNARK implementation, an exponent of 5 creates a bijection. Secondly, any bijection over a prime field satisfies the pigeonhole principal of even distribution for any number of inputs. This is our one-way function: ƒ(x) = x^5. We then define a specialisation ƒs(x) = (x+s)^5 where s is a unique and randomly chosen constant where 0 < s < p.

In 2 § 1.1.3 they make most siblings hard by defining a hash function which takes one s per x: h'_{s_1...s_n}(x_1...x_n) = h_s_1(x_1) * ... * h_s_n(x_n) - however in the case where x is the size of a field element it is possible to inject the modular inverse for the previous step to bring the state back to 0, and the next element (in a merkle path proof) to negate the next element etc. which would create an easily malleable construction that could produce an arbitrary output with little to no computation time. For this reason the range of user input must be significantly limited.

I am proposing that the permutation function described above be used in conjunction with the sponge construction3 to create a hashing function which is both collision resistant and has low multiplicative complexity.

The function operates on 4-bit chunks of a 508 bit input. It compresses two field elements into one in 127+1 rounds.

p = py_ecc.bn128.curve_order

def chunks(l, n):
    n = max(1, n)
    return (l[i:i+n] for i in range(0, len(l), n))

def ShortsightH_round(b, L, R, C):
    return R + powmod(b + C, 5, p), L

def ShortsightH(x):
    assert len(x) == 508
    L = 0
    R = 0
    for i, b in enumerate(chunk(x, 4)):
        b = int(''.join(map(str, b)), 2)
        C = sha3("ShortsightH/"+str(p)+"/508/4/" + str(i))
        L, R = ShortsightH_round(b, L, R, C)
    C = sha3("ShortsightH/"+str(p)+"/508/4/final")
    L, R = ShortsightH_round(0, L, R, C)
    return L

However, this is less efficient to compute in Solidity/EVM which could make updating the merkle tree more expensive (as bitwise operations are expensive), but given the number of bits at a time is 4 we can do a simple integer division by 16 to do the equivalent of x = x >> 4, then extract the lowest 4 bits using x & 15. The problem comes with the overlap for 2x 256 bit words where the last 2 bits of the first word become the first 2 bits of the second word - but - this can be easily handled with a multiply by 4.

The most relevant analysis of a function similar to this would be the the Cryptanalasys of IOTA's Curl hash function, but in this case the analysis doesn't directly translate. The question becomes one of:

Given z = (x + (y + C)^5) mod p, knowing C and z how do you determine x and y. Where y is a whole field element in size this is a difficult problem, but when the size of y is small, say 4 bits, you can trivially compute 16 different possible (x, y) values that satisfy the equation by doing x = z - (n + C)^5 for n in 0..15. From this we can work backwards to brute-force a set of inputs which result in the same hash in linear time of O(r^16) where r is the number of absorb rounds - which means it's fundamentally broken.

This puts us between a rock and a hard place when using addition to within the round function, if we were to change the round function to the following, then we have a function which according to 2 § 1.1.3 makes most siblings hard:

def ShortsightH_round(b, L, R, C):
    return (R * powmod(b + C, 5, p)) % p, L

From there we now have something which:

  1. Is tied closer to the theoretical proof from 2
  2. Is resistant to malleability due to the limited input size
  3. Increases in complexity at every round

The only problem is the second to last round can be derived from the known constant C as b is 0, as you can compute the modulo inverse of C^5 to find R from the previous round. But then, what stops you from working backwards using a linear brute force approach of L_{i-1} = R_i * modinv((b_i+C_i)^5) to find the previous round?

From this the conclusions I can come to are:

  • MiMC when used in a Sponge construction on a bit-by-bit basis, is fundamentally flawed.
  • LongsightF when used with large L and R inputs doesn't have this flaw
  • ShortsightH, when used with the modification above on 4bit inputs has this flaw

Essentially the problem is if you take a single field element as the output then it's possible to work backwards when using the sponge construction, but when absorbing 4 bits at a time and emitting 4 bits at a time (where each field element is truncated to 4 bits) then you have something more robust.

I'm going to leave LongsightF as-is, as it's an adequate solution for now and serves my purposes.

Optimisations using the Montgomery form of the curve, reduce constraints and multiplications

Because the Baby JubJub curve is a twisted Edwards curve it has an equivalent Montgomery form.

Using the Montgomery form of the curve it's possible to perform addition and doubling with roughly half the number of constraints/multiplications. This will significantly reduce the complexity of the SNARK circuit, and will give a performance boost for Solidity/EVM based algorithms.

This depends on the relationship between the X point on the Montgomery curve and the Y point on the Edwards curve, and specifically conversion in projective coordinates between the two.

However, because the prime field doesn't lend its self to efficient square roots, e.g. the usual tricks with William's or Pollard's algorithms for modulo square roots, we need to discard the X coordinate from the Edwards curve, and the Y coordinate from the Montgomery form. The Y coordinate of the Edwards form of Baby JubJub is all that's necessary to uniquely identify the point - so in lieu of recovering the X coordinate it contains everything necessary to uniquely identify a point on the curve. The Montgomery form allows addition and doubling using only the X coordinate, which can be translated into into the Y coordinate of the Edwards curve - so therefore we can perform doubling and addition while discarding 50% of the information with no loss of security.


In this ticket, the following requirements need to be fulfilled:

  • libsnark code to convert from Edwards XY to Montgomery XZ and Edwards YZ coordinates
  • libsnark addition and doubling on Montgomery XZ coordinates
  • Montgomery ladder implementation
  • Use Montgomery XZ coordinates for Merkle trees using Pedersen hashes.

Use groth16 prover / verifier

This requires using the ZoKrates fork of libsnark or bellman:

Work needed to support this:

  • change libsnark submodule to the ZoKrates libsnark
  • Push libsnark authors to add Groth16 support
  • Python implementation of Groth16 verifier
  • ^ Solidity
  • in import.cpp and export.cpp add support for r1cs_gg_ppzksnark_zok proof format & VK formats
  • IMO we should support both provers using templated types, so the existing stuff can be re-cast from one prover to another easily.
  • different modules for supporting one or the other? or same module but multiple methods. e.g. libX_groth16.so and libX_default.so or libX.so with prove_groth16 and prove_default ?

If the new one is groth16 the old one is grothN?, what year was his paper put out that specified the method which uses many more pairings.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.