1use num_bigint::{BigUint, RandBigInt};
16use num_traits::One;
17use rand_core::OsRng;
18use sha2::{Digest, Sha256};
19
20#[derive(Debug, Clone)]
22pub struct VdfParams {
23 pub n: BigUint,
25 pub t: u64,
27}
28
29#[derive(Debug, Clone)]
31pub struct VdfOutput {
32 pub y: BigUint,
34 pub proof: BigUint,
36}
37
38pub fn setup(t: u64, prime_bits: u32) -> VdfParams {
40 let p = generate_prime(prime_bits);
41 let q = generate_prime(prime_bits);
42 let n = &p * &q;
43 VdfParams { n, t }
44}
45
46pub fn eval(params: &VdfParams, x: &BigUint) -> VdfOutput {
49 let mut y = x.clone();
50 for _ in 0..params.t {
51 y = (&y * &y) % ¶ms.n;
52 }
53
54 let l = hash_to_prime(&y, x);
56
57 let exponent = BigUint::one() << params.t;
59 let quotient = &exponent / &l;
60 let proof = x.modpow("ient, ¶ms.n);
61
62 VdfOutput { y, proof }
63}
64
65pub fn verify(params: &VdfParams, x: &BigUint, output: &VdfOutput) -> bool {
67 let l = hash_to_prime(&output.y, x);
68
69 let two_t = BigUint::one() << params.t;
72 let r = &two_t % &l;
73 let pi_l = output.proof.modpow(&l, ¶ms.n);
74 let x_r = x.modpow(&r, ¶ms.n);
75 let rhs = (&pi_l * x_r) % ¶ms.n;
76
77 output.y == rhs
78}
79
80fn hash_to_prime(y: &BigUint, x: &BigUint) -> BigUint {
85 let mut hasher = Sha256::new();
86 hasher.update(b"vdf-prime");
87 hasher.update(y.to_bytes_be());
88 hasher.update(x.to_bytes_be());
89 let mut candidate = BigUint::from_bytes_be(&hasher.finalize()) | BigUint::one();
90 if candidate < BigUint::from(3u32) {
91 candidate = BigUint::from(3u32);
92 }
93 loop {
94 if miller_rabin(&candidate, 20) {
95 return candidate;
96 }
97 candidate += 2u32;
98 }
99}
100
101fn generate_prime(bits: u32) -> BigUint {
102 let mut rng = OsRng;
103 loop {
104 if bits < 2 {
105 continue;
106 }
107 let top = BigUint::one() << (bits - 1);
108 let candidate = rng.gen_biguint(bits as u64) | top | BigUint::one();
109 if miller_rabin(&candidate, 20) {
110 return candidate;
111 }
112 }
113}
114
115fn miller_rabin(n: &BigUint, rounds: u32) -> bool {
119 let two = BigUint::from(2u32);
120 let three = BigUint::from(3u32);
121 if n == &two || n == &three {
122 return true;
123 }
124 if (n & &BigUint::one()) == BigUint::from(0u32) || n < &three {
125 return false;
126 }
127
128 let one = BigUint::one();
129 let n_minus_one = n - &one;
130
131 let mut d = n_minus_one.clone();
132 let mut r: u32 = 0;
133 loop {
134 if (&d & &one) == BigUint::from(0u32) {
135 d >>= 1;
136 r += 1;
137 } else {
138 break;
139 }
140 }
141
142 let mut rng = OsRng;
143 'outer: for _ in 0..rounds {
144 let a = rng.gen_biguint_range(&two, &n_minus_one);
145 if a < two || a >= n_minus_one {
146 continue;
147 }
148 let mut x = a.modpow(&d, n);
149 if x == one || x == n_minus_one {
150 continue;
151 }
152 for _ in 0..r.saturating_sub(1) {
153 x = (&x * &x) % n;
154 if x == n_minus_one {
155 continue 'outer;
156 }
157 }
158 return false;
159 }
160 true
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 fn make_params(t: u64) -> VdfParams {
168 setup(t, 128)
169 }
170
171 #[test]
172 fn eval_produces_output() {
173 let params = make_params(100);
174 let x = BigUint::from(42u32);
175 let output = eval(¶ms, &x);
176 assert!(output.y > BigUint::from(0u32));
177 assert!(output.proof > BigUint::from(0u32));
178 }
179
180 #[test]
181 fn verify_accepts_correct_output() {
182 let params = make_params(50);
183 let x = BigUint::from(123u32);
184 let output = eval(¶ms, &x);
185 assert!(verify(¶ms, &x, &output));
186 }
187
188 #[test]
189 fn verify_rejects_tampered_proof() {
190 let params = make_params(50);
191 let x = BigUint::from(123u32);
192 let output = eval(¶ms, &x);
193 let tampered = VdfOutput {
194 y: output.y.clone(),
195 proof: &output.proof + BigUint::one(),
196 };
197 assert!(!verify(¶ms, &x, &tampered));
198 }
199
200 #[test]
201 fn verify_rejects_tampered_output() {
202 let params = make_params(50);
203 let x = BigUint::from(123u32);
204 let output = eval(¶ms, &x);
205 let tampered = VdfOutput {
206 y: &output.y + BigUint::one(),
207 proof: output.proof.clone(),
208 };
209 assert!(!verify(¶ms, &x, &tampered));
210 }
211
212 #[test]
213 fn verify_rejects_wrong_input() {
214 let params = make_params(50);
215 let x = BigUint::from(123u32);
216 let output = eval(¶ms, &x);
217 assert!(!verify(¶ms, &BigUint::from(124u32), &output));
219 }
220
221 #[test]
222 fn eval_is_deterministic() {
223 let params = make_params(100);
224 let x = BigUint::from(999u32);
225 let y1 = eval(¶ms, &x).y;
226 let y2 = eval(¶ms, &x).y;
227 assert_eq!(y1, y2);
228 }
229
230 #[test]
231 fn different_inputs_different_outputs() {
232 let params = make_params(50);
233 let y1 = eval(¶ms, &BigUint::from(1u32)).y;
234 let y2 = eval(¶ms, &BigUint::from(2u32)).y;
235 assert_ne!(y1, y2);
236 }
237
238 #[test]
239 fn zero_delay_returns_input() {
240 let params = make_params(0);
241 let x = BigUint::from(42u32);
242 let output = eval(¶ms, &x);
243 assert_eq!(output.y, x % ¶ms.n);
244 }
245
246 #[test]
247 fn large_delay_completes() {
248 let params = make_params(1000);
249 let x = BigUint::from(7u32);
250 let output = eval(¶ms, &x);
251 assert!(output.y < params.n);
252 }
253
254 #[test]
255 fn hash_to_prime_returns_an_actual_prime() {
256 let y = BigUint::from(42u32);
257 let x = BigUint::from(99u32);
258 let l = hash_to_prime(&y, &x);
259 assert!(l > BigUint::from(2u32));
260 assert!((l.clone() & &BigUint::one()) == BigUint::one());
261 assert!(miller_rabin(&l, 20));
262 }
263
264 #[test]
265 fn hash_to_prime_is_deterministic() {
266 let y = BigUint::from(42u32);
267 let x = BigUint::from(99u32);
268 let p1 = hash_to_prime(&y, &x);
269 let p2 = hash_to_prime(&y, &x);
270 assert_eq!(p1, p2);
271 }
272}