diff --git a/ethsnarks/poseidon.py b/ethsnarks/poseidon.py deleted file mode 100644 index 1e05aea..0000000 --- a/ethsnarks/poseidon.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python - -""" -Implements the Poseidon permutation: - -Starkad and Poseidon: New Hash Functions for Zero Knowledge Proof Systems - - Lorenzo Grassi, Daniel Kales, Dmitry Khovratovich, Arnab Roy, Christian Rechberger, and Markus Schofnegger - - https://eprint.iacr.org/2019/458.pdf - -Other implementations: - - - https://github.com/shamatar/PoseidonTree/ - - https://github.com/iden3/circomlib/blob/master/src/poseidon.js - - https://github.com/dusk-network/poseidon252 -""" - -from math import log2 -from collections import namedtuple -from pyblake2 import blake2b -from .field import SNARK_SCALAR_FIELD - - -_PoseidonParams = namedtuple('_PoseidonParams', ('p', 't', 'nRoundsF', 'nRoundsP', 'seed', 'e', 'constants_C', 'constants_M')) - - -def poseidon_params(p, t, nRoundsF, nRoundsP, seed, e, constants_C=None, constants_M=None): - assert nRoundsF % 2 == 0 and nRoundsF > 0 - assert nRoundsP > 0 - assert t >= 2 - assert isinstance(seed, bytes) - - M = 128 # security target, in bits - n = log2(p) - N = n * t - - if p % 2 == 3: - assert e == 3 - grobner_attack_ratio_rounds = 0.32 - grobner_attack_ratio_sboxes = 0.18 - interpolation_attack_ratio = 0.63 - elif p % 5 != 1: - assert e == 5 - grobner_attack_ratio_rounds = 0.21 - grobner_attack_ratio_sboxes = 0.14 - interpolation_attack_ratio = 0.43 - else: - # XXX: in other cases use, can we use 7? - raise ValueError('Invalid p for congruency') - - # Verify that the parameter choice exceeds the recommendations to prevent attacks - # iacr.org/2019/458 § 3 Cryptanalysis Summary of Starkad and Poseidon Hashes (pg 10) - # Figure 1 - #print('(nRoundsF + nRoundsP)', (nRoundsF + nRoundsP)) - #print('Interpolation Attackable Rounds', ((interpolation_attack_ratio * min(n, M)) + log2(t))) - assert (nRoundsF + nRoundsP) > ((interpolation_attack_ratio * min(n, M)) + log2(t)) - # Figure 3 - #print('grobner_attack_ratio_rounds', ((2 + min(M, n)) * grobner_attack_ratio_rounds)) - assert (nRoundsF + nRoundsP) > ((2 + min(M, n)) * grobner_attack_ratio_rounds) - # Figure 4 - #print('grobner_attack_ratio_sboxes', (M * grobner_attack_ratio_sboxes)) - assert (nRoundsF + (t * nRoundsP)) > (M * grobner_attack_ratio_sboxes) - - # iacr.org/2019/458 § 4.1 Minimize "Number of S-Boxes" - # In order to minimize the number of S-boxes for given n and t, the goal is to and - # the best ratio between RP and RF that minimizes: - # number of S-Boxes = t · RF + RP - # - Use S-box x^q - # - Select R_F to 6 o rhigher - # - Select R_P that minimizes tRF +RP such that no inequation (1),(3),(4),(5) is satisfied. - - if constants_C is None: - constants_C = list(poseidon_constants(p, seed + b'_constants', nRoundsF + nRoundsP)) - if constants_M is None: - constants_M = poseidon_matrix(p, seed + b'_matrix_0000', t) - - # iacr.org/2019/458 § 4.1 6 SNARKs Application via Poseidon-π - # page 16 formula (8) and (9) - n_constraints = (nRoundsF * t) + nRoundsP - if e == 5: - n_constraints *= 3 - elif e == 3: - n_constraints *= 2 - #print('n_constraints', n_constraints) - - return _PoseidonParams(p, t, nRoundsF, nRoundsP, seed, e, constants_C, constants_M) - - -def H(arg): - if isinstance(arg, int): - arg = arg.to_bytes(32, 'little') - # XXX: ensure that (digest_size*8) >= log2(p) - hashed = blake2b(data=arg, digest_size=32).digest() - return int.from_bytes(hashed, 'little') - - -def poseidon_constants(p, seed, n): - assert isinstance(n, int) - for _ in range(n): - seed = H(seed) - yield seed % p - - -def poseidon_matrix(p, seed, t): - """ - iacr.org/2019/458 § 2.3 About the MDS Matrix (pg 8) - Also: - - https://en.wikipedia.org/wiki/Cauchy_matrix - """ - c = list(poseidon_constants(p, seed, t * 2)) - return [[pow((c[i] - c[t+j]) % p, p - 2, p) for j in range(t)] - for i in range(t)] - - -DefaultParams = poseidon_params(SNARK_SCALAR_FIELD, 6, 8, 57, b'poseidon', 5) - - -def poseidon_sbox(state, i, params): - """ - iacr.org/2019/458 § 2.2 The Hades Strategy (pg 6) - - In more details, assume R_F = 2 · R_f is an even number. Then - - the first R_f rounds have a full S-Box layer, - - the middle R_P rounds have a partial S-Box layer (i.e., 1 S-Box layer), - - the last R_f rounds have a full S-Box layer - """ - half_F = params.nRoundsF // 2 - e, p = params.e, params.p - if i < half_F or i >= (half_F + params.nRoundsP): - for j, _ in enumerate(state): - state[j] = pow(_, e, p) - else: - state[0] = pow(state[0], e, p) - - -def poseidon_mix(state, M, p): - """ - The mixing layer is a matrix vector product of the state with the mixing matrix - - https://mathinsight.org/matrix_vector_multiplication - """ - return [ sum([M[i][j] * _ for j, _ in enumerate(state)]) % p - for i in range(len(M)) ] - - -def poseidon(inputs, chained=False, params=None): - """ - Main instansiation of the Poseidon permutation - - The state is `t` elements wide, there are `F` full-rounds - followed by `P` partial rounds, then `F` full rounds again. - - [ ARK ] --, - | | | | | | | - [ SBOX ] - Full Round - | | | | | | | - [ MIX ] --` - - - [ ARK ] --, - | | | | | | | - [ SBOX ] - Partial Round - | | Only 1 element is substituted in partial round - [ MIX ] --` - - There are F+P rounds for the full permutation. - - You can provide `r = N - 2s` bits of input per round, where `s` is the desired - security level, in most cases this means you can provide `t-1` inputs with - appropriately chosen parameters. The permutation can be 'chained' together - to form a sponge construct. - """ - if params is None: - params = DefaultParams - assert isinstance(params, _PoseidonParams) - assert len(inputs) > 0 - if not chained: - # Don't allow inputs to exceed the rate, unless in chained mode - assert len(inputs) < params.t - state = [0] * params.t - state[:len(inputs)] = inputs - for i, C_i in enumerate(params.constants_C): - state = [_ + C_i for _ in state] # ARK(.) - poseidon_sbox(state, i, params) - state = poseidon_mix(state, params.constants_M, params.p) - for j, val in enumerate(state): - print('o[%d][%d]' % (i, j), '=', val) - if chained: - # Provide the full state as output in 'chained' mode - return state - return state[0] - - -if __name__ == "__main__": - assert DefaultParams.constants_C[0] == 14397397413755236225575615486459253198602422701513067526754101844196324375522 - assert DefaultParams.constants_C[-1] == 10635360132728137321700090133109897687122647659471659996419791842933639708516 - assert DefaultParams.constants_M[0][0] == 19167410339349846567561662441069598364702008768579734801591448511131028229281 - assert DefaultParams.constants_M[-1][-1] == 20261355950827657195644012399234591122288573679402601053407151083849785332516 - assert poseidon([1,2]) == 12242166908188651009877250812424843524687801523336557272219921456462821518061 diff --git a/ethsnarks/shamirspoly.py b/ethsnarks/shamirspoly.py index 7fb734a..de6af26 100644 --- a/ethsnarks/shamirspoly.py +++ b/ethsnarks/shamirspoly.py @@ -40,3 +40,13 @@ def g(i, n): coefficient = g(i, n) total = total + (yi * coefficient) return total + + +def inverse_lagrange(points, y): + x = 0 + for i, (x_i, y_i) in enumerate(points): + for j, (x_j, y_j) in enumerate(points): + if j != i: + x_i = x_i * (y - y_j) / (y_i - y_j) + x += x_i + return x diff --git a/test/test_merkle.py b/test/test_merkle.py index 2a84f46..d86c573 100644 --- a/test/test_merkle.py +++ b/test/test_merkle.py @@ -47,30 +47,37 @@ def test_known1(self): def test_update(self): # Verify that items in the tree can be updated - for hasher in [DEFAULT_HASHER, MerkleHasher_Poseidon.factory()]: - tree = MerkleTree(2, hasher=hasher) - tree.append(FQ.random()) - tree.append(FQ.random()) - proof_0_before = tree.proof(0) - proof_1_before = tree.proof(1) - root_before = tree.root - self.assertTrue(proof_0_before.verify(tree.root)) - self.assertTrue(proof_1_before.verify(tree.root)) - - leaf_0_after = FQ.random() - tree.update(0, leaf_0_after) - root_after_0 = tree.root - proof_0_after = tree.proof(0) - self.assertTrue(proof_0_after.verify(tree.root)) - self.assertNotEqual(root_before, root_after_0) - - leaf_1_after = FQ.random() - tree.update(1, leaf_1_after) - root_after_1 = tree.root - proof_1_after = tree.proof(1) - self.assertTrue(proof_1_after.verify(tree.root)) - self.assertNotEqual(root_before, root_after_1) - self.assertNotEqual(root_after_0, root_after_1) + poseidon_factory = MerkleHasher_Poseidon.factory() + for hasher in [DEFAULT_HASHER, poseidon_factory]: + widths = [2] + if hasher == poseidon_factory: + widths += [3, 4] + for width in widths: + sizes = [width, width**2, width**3] + for n_items in sizes: + tree = MerkleTree(n_items, width=width, hasher=hasher) + tree.append(FQ.random()) + tree.append(FQ.random()) + proof_0_before = tree.proof(0) + proof_1_before = tree.proof(1) + root_before = tree.root + self.assertTrue(proof_0_before.verify(tree.root)) + self.assertTrue(proof_1_before.verify(tree.root)) + + leaf_0_after = FQ.random() + tree.update(0, leaf_0_after) + root_after_0 = tree.root + proof_0_after = tree.proof(0) + self.assertTrue(proof_0_after.verify(tree.root)) + self.assertNotEqual(root_before, root_after_0) + + leaf_1_after = FQ.random() + tree.update(1, leaf_1_after) + root_after_1 = tree.root + proof_1_after = tree.proof(1) + self.assertTrue(proof_1_after.verify(tree.root)) + self.assertNotEqual(root_before, root_after_1) + self.assertNotEqual(root_after_0, root_after_1) def test_known_2pow28(self): tree = MerkleTree(2<<28)