Skip to content

Commit

Permalink
Merge branch 'main' into feat/crypto-upgrade
Browse files Browse the repository at this point in the history
Signed-off-by: Brandon H. Gomes <bhgomes@pm.me>
  • Loading branch information
bhgomes committed Aug 22, 2022
2 parents 37b64ce + 0960ab2 commit b3d921c
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]
### Added
- [\#206](https://github.com/Manta-Network/manta-rs/pull/206) Move Poseidon sage script to test the hardcoded round constant values.
- [\#172](https://github.com/Manta-Network/manta-rs/pull/172) Add abstract Phase 2 for Groth16 trusted setup
- [\#197](https://github.com/Manta-Network/manta-rs/pull/197) Add ECLAIR utilities for next circuit upgrade

Expand Down
12 changes: 12 additions & 0 deletions manta-pay/src/crypto/poseidon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* `round_constants.rs`: Generates round constants.
* `mds_hardcoded_tests/correct_mds_generation.sage`: Generates hardcoded tests based on sage script adapted from [here](https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/generate_parameters_grain.sage).
* `mds_hardcoded_tests/width*n*`: Contains a hardcoded $n\times n$ MDS matrix generated from the sage script.
* `parameters_hardcoded_test/generate_parameters_grain_deterministic.sage`: Generates hardcoded tests based on sage script adapted from [here](https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/generate_parameters_grain.sage).
* `parameters_hardcoded_test/lfsr_values`: Contains a hardcoded list of round constants generated from the sage script.
* `permutation_hardcoded_test/poseidonperm_bls381_width3.sage`: Generates hardcoded tests based on sage script adapted from [here](https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/poseidonperm_x5_255_3.sage).
* `permutation_hardcoded_test/width3`: Contains a hardcoded width-3 permutation outputs generated from the sage script.

Expand All @@ -36,4 +38,14 @@ The following script generates permutation hardcoded test values:
cd permutation_hardcoded_test
sage poseidonperm_bls381_width3.sage
cd ..
```

## Generate round constants Hardcoded Tests from SAGE

The following script generates secure MDS matrices:

```sh
cd parameters_hardcoded_test
sage generate_parameters_grain_deterministic.sage 1 0 255 3 8 55 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
cd ..
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
generate_parameters_grain_deterministic.sage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
# adapted from: https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/generate_parameters_grain.sage

# Remark: This script contains functionality for GF(2^n), but currently works only over GF(p)! A few small adaptations are needed for GF(2^n).
from sage.rings.polynomial.polynomial_gf2x import GF2X_BuildIrred_list

if len(sys.argv) < 7:
print("Usage: <script> <field> <s_box> <field_size> <num_cells> <R_F> <R_P> (<prime_number_hex>)")
print("field = 1 for GF(p)")
print("s_box = 0 for x^alpha, s_box = 1 for x^(-1)")
exit()

# Parameters
FIELD = int(sys.argv[1]) # 0 .. GF(2^n), 1 .. GF(p)
SBOX = int(sys.argv[2]) # 0 .. x^alpha, 1 .. x^(-1)
FIELD_SIZE = int(sys.argv[3]) # n
NUM_CELLS = int(sys.argv[4]) # t
R_F_FIXED = int(sys.argv[5])
R_P_FIXED = int(sys.argv[6])

INIT_SEQUENCE = []

PRIME_NUMBER = 0
if FIELD == 1 and len(sys.argv) != 8:
print("Please specify a prime number (in hex format)!")
exit()
elif FIELD == 1 and len(sys.argv) == 8:
PRIME_NUMBER = int(sys.argv[7], 16) # e.g. 0xa7, 0xFFFFFFFFFFFFFEFF, 0xa1a42c3efd6dbfe08daa6041b36322ef

F = GF(PRIME_NUMBER)

def grain_sr_generator():
bit_sequence = INIT_SEQUENCE
for _ in range(0, 160):
new_bit = bit_sequence[62] ^^ bit_sequence[51] ^^ bit_sequence[38] ^^ bit_sequence[23] ^^ bit_sequence[13] ^^ bit_sequence[0]
bit_sequence.pop(0)
bit_sequence.append(new_bit)
while True:
new_bit = bit_sequence[62] ^^ bit_sequence[51] ^^ bit_sequence[38] ^^ bit_sequence[23] ^^ bit_sequence[13] ^^ bit_sequence[0]
bit_sequence.pop(0)
bit_sequence.append(new_bit)
while new_bit == 0:
new_bit = bit_sequence[62] ^^ bit_sequence[51] ^^ bit_sequence[38] ^^ bit_sequence[23] ^^ bit_sequence[13] ^^ bit_sequence[0]
bit_sequence.pop(0)
bit_sequence.append(new_bit)
new_bit = bit_sequence[62] ^^ bit_sequence[51] ^^ bit_sequence[38] ^^ bit_sequence[23] ^^ bit_sequence[13] ^^ bit_sequence[0]
bit_sequence.pop(0)
bit_sequence.append(new_bit)
new_bit = bit_sequence[62] ^^ bit_sequence[51] ^^ bit_sequence[38] ^^ bit_sequence[23] ^^ bit_sequence[13] ^^ bit_sequence[0]
bit_sequence.pop(0)
bit_sequence.append(new_bit)
yield new_bit
grain_gen = grain_sr_generator()

def grain_random_bits(num_bits):
random_bits = [next(grain_gen) for i in range(0, num_bits)]
random_int = int("".join(str(i) for i in random_bits), 2)
return random_int

def init_generator(field, sbox, n, t, R_F, R_P):
# Generate initial sequence based on parameters
bit_list_field = [_ for _ in (bin(FIELD)[2:].zfill(2))]
bit_list_sbox = [_ for _ in (bin(SBOX)[2:].zfill(4))]
bit_list_n = [_ for _ in (bin(FIELD_SIZE)[2:].zfill(12))]
bit_list_t = [_ for _ in (bin(NUM_CELLS)[2:].zfill(12))]
bit_list_R_F = [_ for _ in (bin(R_F)[2:].zfill(10))]
bit_list_R_P = [_ for _ in (bin(R_P)[2:].zfill(10))]
bit_list_1 = [1] * 30
global INIT_SEQUENCE
INIT_SEQUENCE = bit_list_field + bit_list_sbox + bit_list_n + bit_list_t + bit_list_R_F + bit_list_R_P + bit_list_1
INIT_SEQUENCE = [int(_) for _ in INIT_SEQUENCE]

def generate_constants(field, n, t, R_F, R_P, prime_number):
round_constants = []
num_constants = (R_F + R_P) * t
if field == 0:
for i in range(0, num_constants):
random_int = grain_random_bits(n)
round_constants.append(random_int)
elif field == 1:
for i in range(0, num_constants):
random_int = grain_random_bits(n)
while random_int >= prime_number:
random_int = grain_random_bits(n)
round_constants.append(random_int)
return round_constants

def print_round_constants(round_constants, n, field):
print("Number of round constants:", len(round_constants))
if field == 0:
print("Round constants for GF(2^n):")
elif field == 1:
print("Round constants for GF(p):")
hex_length = int(ceil(float(n) / 4)) + 2 # +2 for "0x"
print(["{0:#0{1}x}".format(entry, hex_length) for entry in round_constants])

def print_round_constants_arkff(round_constants):
print("Round constants for ark-ff:")
print("vec![" + ",".join(['Fp(field_new!(Fr, "{}"))'.format(int(entry)) for entry in round_constants]) + "]")

def create_mds_p(n, t):
M = matrix(F, t, t)

# Sample random distinct indices and assign to xs and ys
while True:
flag = True
# Tom's Note: TODO
rand_list = [F(i) for i in range(0, 2*t)]
xs = rand_list[:t]
ys = rand_list[t:]
for i in range(0, t):
for j in range(0, t):
if (flag == False) or ((xs[i] + ys[j]) == 0):
flag = False
else:
entry = (xs[i] + ys[j])^(-1)
M[i, j] = entry
if flag == False:
continue
return M

def generate_vectorspace(round_num, M, M_round, NUM_CELLS):
t = NUM_CELLS
s = 1
V = VectorSpace(F, t)
if round_num == 0:
return V
elif round_num == 1:
return V.subspace(V.basis()[s:])
else:
mat_temp = matrix(F)
for i in range(0, round_num-1):
add_rows = []
for j in range(0, s):
add_rows.append(M_round[i].rows()[j][s:])
mat_temp = matrix(mat_temp.rows() + add_rows)
r_k = mat_temp.right_kernel()
extended_basis_vectors = []
for vec in r_k.basis():
extended_basis_vectors.append(vector([0]*s + list(vec)))
S = V.subspace(extended_basis_vectors)
return S

def subspace_times_matrix(subspace, M, NUM_CELLS):
t = NUM_CELLS
V = VectorSpace(F, t)
subspace_basis = subspace.basis()
new_basis = []
for vec in subspace_basis:
new_basis.append(M * vec)
return V.subspace(new_basis)

# Returns True if the matrix is considered secure, False otherwise
def algorithm_1(M, NUM_CELLS):
t = NUM_CELLS
s = 1
r = floor((t - s) / float(s))

# Generate round matrices
M_round = []
for j in range(0, t+1):
M_round.append(M^(j+1))

for i in range(1, r+1):
mat_test = M^i
entry = mat_test[0, 0]
mat_target = matrix.circulant(vector([entry] + ([F(0)] * (t-1))))

if (mat_test - mat_target) == matrix.circulant(vector([F(0)] * (t))):
return [False, 1]

S = generate_vectorspace(i, M, M_round, t)
V = VectorSpace(F, t)

basis_vectors= []
for eigenspace in mat_test.eigenspaces_right(format='galois'):
if (eigenspace[0] not in F):
continue
vector_subspace = eigenspace[1]
intersection = S.intersection(vector_subspace)
basis_vectors += intersection.basis()
IS = V.subspace(basis_vectors)

if IS.dimension() >= 1 and IS != V:
return [False, 2]
for j in range(1, i+1):
S_mat_mul = subspace_times_matrix(S, M^j, t)
if S == S_mat_mul:
print("S.basis():\n", S.basis())
return [False, 3]

return [True, 0]

# Returns True if the matrix is considered secure, False otherwise
def algorithm_2(M, NUM_CELLS):
t = NUM_CELLS
s = 1
V = VectorSpace(F, t)
trail = [None, None]
test_next = False
I = range(0, s)
I_powerset = list(sage.misc.misc.powerset(I))[1:]
for I_s in I_powerset:
test_next = False
new_basis = []
for l in I_s:
new_basis.append(V.basis()[l])
IS = V.subspace(new_basis)
for i in range(s, t):
new_basis.append(V.basis()[i])
full_iota_space = V.subspace(new_basis)
for l in I_s:
v = V.basis()[l]
while True:
delta = IS.dimension()
v = M * v
IS = V.subspace(IS.basis() + [v])
if IS.dimension() == t or IS.intersection(full_iota_space) != IS:
test_next = True
break
if IS.dimension() <= delta:
break
if test_next == True:
break
if test_next == True:
continue
return [False, [IS, I_s]]
return [True, None]

# Returns True if the matrix is considered secure, False otherwise
def algorithm_3(M, NUM_CELLS):
t = NUM_CELLS
s = 1
V = VectorSpace(F, t)
l = 4*t
for r in range(2, l+1):
next_r = False
res_alg_2 = algorithm_2(M^r, t)
if res_alg_2[0] == False:
return [False, None]
return [True, None]

def generate_matrix(FIELD, FIELD_SIZE, NUM_CELLS):
if FIELD == 0:
print("Matrix generation not implemented for GF(2^n).")
exit(1)
elif FIELD == 1:
mds_matrix = create_mds_p(FIELD_SIZE, NUM_CELLS)
result_1 = algorithm_1(mds_matrix, NUM_CELLS)
result_2 = algorithm_2(mds_matrix, NUM_CELLS)
result_3 = algorithm_3(mds_matrix, NUM_CELLS)
while result_1[0] == False or result_2[0] == False or result_3[0] == False:
mds_matrix = create_mds_p(FIELD_SIZE, NUM_CELLS)
result_1 = algorithm_1(mds_matrix, NUM_CELLS)
result_2 = algorithm_2(mds_matrix, NUM_CELLS)
result_3 = algorithm_3(mds_matrix, NUM_CELLS)
return mds_matrix

def print_linear_layer(M, n, t):
print("n:", n)
print("t:", t)
print("N:", (n * t))
print("Result Algorithm 1:\n", algorithm_1(M, NUM_CELLS))
print("Result Algorithm 2:\n", algorithm_2(M, NUM_CELLS))
print("Result Algorithm 3:\n", algorithm_3(M, NUM_CELLS))
hex_length = int(ceil(float(n) / 4)) + 2 # +2 for "0x"
print("Prime number:", "0x" + hex(PRIME_NUMBER))
matrix_string = "["
for i in range(0, t):
matrix_string += str(["{0:#0{1}x}".format(int(entry), hex_length) for entry in M[i]])
if i < (t-1):
matrix_string += ","
matrix_string += "]"
print("MDS matrix:\n", matrix_string)

def print_linear_layer_arkff(M, t):
matrix_string = "vec!["
for i in range(0, t):
matrix_string += "vec![" + ",".join(["field_new!(Fr, {})".format(int(entry)) for entry in M[i]]) + "]"
if i < (t-1):
matrix_string += ","
matrix_string += "]"
print("MDS for ark-ff:\n", matrix_string)

# Init
init_generator(FIELD, SBOX, FIELD_SIZE, NUM_CELLS, R_F_FIXED, R_P_FIXED)

# Round constants
round_constants = generate_constants(FIELD, FIELD_SIZE, NUM_CELLS, R_F_FIXED, R_P_FIXED, PRIME_NUMBER)
print_round_constants(round_constants, FIELD_SIZE, FIELD)
print_round_constants_arkff(round_constants)

# Matrix
linear_layer = generate_matrix(FIELD, FIELD_SIZE, NUM_CELLS)
print_linear_layer(linear_layer, FIELD_SIZE, NUM_CELLS)
print_linear_layer_arkff(linear_layer, NUM_CELLS)

Large diffs are not rendered by default.

26 changes: 5 additions & 21 deletions manta-pay/src/crypto/poseidon/round_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,29 +82,13 @@ mod test {
use ark_bls12_381::Fr;
use manta_crypto::arkworks::ff::field_new;

/// Checks if [`GrainLFSR`] is consistent with hardcoded outputs from the `sage` script found at
/// <https://github.com/Manta-Network/Plonk-Prototype/tree/poseidon_hash_clean> with the
/// following parameters:
///
/// ```shell
/// sage generate_parameters_grain_deterministic.sage 1 0 255 3 8 55 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
/// ```
/// Checks if [`GrainLFSR`] matches hardcoded sage outputs.
#[test]
fn grain_lfsr_is_consistent() {
let test_cases = include!("parameters_hardcoded_test/lfsr_values");
let mut lfsr = generate_lfsr(255, 3, 8, 55);
assert_eq!(
sample_field_element::<Fp<Fr>, _>(&mut lfsr),
Fp(field_new!(
Fr,
"41764196652518280402801918994067134807238124178723763855975902025540297174931"
))
);
assert_eq!(
sample_field_element::<Fp<Fr>, _>(&mut lfsr),
Fp(field_new!(
Fr,
"12678502092746318913289523392430826887011664085277767208266352862540971998250"
))
);
for x in test_cases {
assert_eq!(sample_field_element::<Fp<Fr>, _>(&mut lfsr), x);
}
}
}
Loading

0 comments on commit b3d921c

Please sign in to comment.