Skip to content

Commit 041b835

Browse files
authored
Merge pull request #17 from alxmirap/master
Adds test vectors for the Fisher-Yates Shuffling function.
2 parents dc20cbc + bd1247e commit 041b835

File tree

3 files changed

+925
-0
lines changed

3 files changed

+925
-0
lines changed

shuffle/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Fisher-Yates Shuffle function test vectors
2+
3+
We offer test vectors for the implementation of the shuffle function as described in Appendix F of the Gray Paper.
4+
This section presents two definitions, a recursive one that uses an integer sequence as random seeds in Eq 329, and one (Eq 331) which takes a 32-byte hash as entropy, and is defined in terms of the previous one.
5+
6+
The implementation of Eq 329 is not generally used in the Gray Paper, and so we present test vectors only for the version of Eq 331. However, the present script creates these test cases with recourse to the Eq 329 implementation, to reproduce exactly the definition of the Gray Paper.
7+
8+
Note: All gray paper references in this document are to version 0.4.3, October 21st 2024.
9+
10+
## Notes about test parameters
11+
12+
In every case, the output must be a permutation of the input sequence, and this is checked by the generating script.
13+
In the GP, at the moment, this function is used only to shuffle sequences of validators or cores, both of which are identified by integers. For that reason, all test cases use integers as the input type. All the input elements are distinct to ensure the final positions of each element are unambiguously determined.
14+
15+
All the inputs to each test case are integer sequences, ranging from 0 to n-1, where n is the length. The test cases are described only by the entropy and the length of the input, and so the actual input sequence has to be recreated when using these test cases.
16+
17+
## Test vectors
18+
19+
The following test cases are given:
20+
- 0
21+
- 8
22+
- 16
23+
- 20
24+
- 50
25+
- 100
26+
- 200
27+
- 341
28+
29+
Each test case has this format is a Json object with 3 fields:
30+
* **[input]**: the length of the input sequence
31+
* **[entropy]**: a hex-string description of the 32 bytes used for entropy
32+
* **[output]**: an array containing the shuffled input sequence

shuffle/main.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env python3
2+
3+
import hashlib
4+
import json
5+
import sys
6+
7+
def hash(data):
8+
return hashlib.blake2b(data, digest_size=32).digest()
9+
10+
def hex(data):
11+
return ''.join(f'{x:02x}' for x in data)
12+
13+
def compute_shuffle_eq329(s, r):
14+
if len(s) > 0:
15+
l = len(s)
16+
index = r[0] % l
17+
head = s[index]
18+
19+
s_post = s.copy()
20+
s_post[index] = s[l-1]
21+
22+
return [head] + compute_shuffle_eq329(s_post[:-1], r[1:])
23+
else:
24+
return []
25+
26+
def number_vector(n):
27+
return [x for x in range(n)]
28+
29+
def uniform_seed(n):
30+
return [n] * 32
31+
32+
def linear_seed():
33+
return [i for i in range(32)]
34+
35+
def varied_seed(n):
36+
# large 32 bit prime
37+
large_prime = 2147483647
38+
result = []
39+
next = n % large_prime
40+
41+
# next is always a 32-bit prime, so it fits in 4 bytes.
42+
# this loop generates a cycle of modular exponents of a generator.
43+
# if this generator is unknown, this cycle is in practice unpredictable,
44+
# so this should generate a sequence that looks a bit like random
45+
# and should be enough for simple tests.
46+
for i in range(8):
47+
result = result + list(to_le_bytes(next, 4))
48+
next = next * n % large_prime
49+
50+
return result
51+
52+
def inputs_Eq331():
53+
zero_seed = uniform_seed(0)
54+
ff_seed = uniform_seed(255)
55+
simple_seed = linear_seed()
56+
irregular_seed = varied_seed(1_000_000_000_000)
57+
58+
yield(number_vector(0), zero_seed)
59+
yield(number_vector(8), ff_seed)
60+
yield(number_vector(16), simple_seed)
61+
yield(number_vector(20), irregular_seed)
62+
63+
yield(number_vector(50), zero_seed)
64+
yield(number_vector(100), ff_seed)
65+
yield(number_vector(200), simple_seed)
66+
yield(number_vector(341), irregular_seed)
67+
68+
69+
def to_le_bytes(n, k):
70+
return n.to_bytes(k, 'little')
71+
72+
def from_le_bytes(b):
73+
return int.from_bytes(b, 'little')
74+
75+
def compute_q(h, l):
76+
result = []
77+
for i in range(l):
78+
preimage = bytes(h.copy()) + to_le_bytes(i // 8, 4)
79+
offset = 4*i % 32
80+
slice = hash(preimage)[offset:offset + 4]
81+
result.append(from_le_bytes(slice))
82+
83+
return result
84+
85+
def compute_shuffle_eq331(s, h):
86+
l = len(s)
87+
r = compute_q(h, l)
88+
89+
return compute_shuffle_eq329(s, r)
90+
91+
def is_permutation(list1, list2):
92+
return set(list1) == set(list2)
93+
94+
def test_vectors_eq331():
95+
for term in inputs_Eq331():
96+
s, r = term
97+
output = compute_shuffle_eq331(s, r)
98+
assert(is_permutation(s, output))
99+
100+
yield {
101+
'input': len(s),
102+
'entropy': hex(r),
103+
'output': output,
104+
}
105+
106+
def main():
107+
json.dump(list(test_vectors_eq331()), sys.stdout, indent=' ')
108+
109+
main()

0 commit comments

Comments
 (0)