1use getrandom::SysRng;
9use p256::FieldBytes;
10use p256::elliptic_curve::PrimeField;
11use p256::elliptic_curve::rand_core::{Rng, UnwrapErr};
12use p256::elliptic_curve::sec1::{FromSec1Point, ToSec1Point};
13use p256::{AffinePoint, ProjectivePoint, Scalar};
14use serde::{Deserialize, Serialize};
15use sha2::{Digest, Sha256};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct VrfProof {
20 pub gamma_hex: String,
21 pub c_hex: String,
22 pub s_hex: String,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct VrfOutput {
28 pub output_hex: String,
29 pub proof: VrfProof,
30}
31
32pub fn prove(secret: &Scalar, public: &AffinePoint, alpha: &[u8]) -> VrfOutput {
34 use p256::elliptic_curve::Field;
35
36 let h_point = hash_to_curve(alpha);
38
39 let gamma = (ProjectivePoint::from(h_point) * secret).to_affine();
41
42 let mut k_bytes = [0u8; 32];
44 UnwrapErr(SysRng).fill_bytes(&mut k_bytes);
45 let k_fb = p256::FieldBytes::from(k_bytes);
46 let k = Option::<Scalar>::from(Scalar::from_repr(k_fb))
47 .unwrap_or_else(|| Scalar::random(&mut UnwrapErr(SysRng)));
48
49 let k_g = (ProjectivePoint::GENERATOR * k).to_affine();
51 let k_h = (ProjectivePoint::from(h_point) * k).to_affine();
52
53 let c = challenge(public, &h_point, &gamma, &k_g, &k_h, alpha);
55
56 let s = k + secret * &c;
58
59 let output = hash_output(&gamma);
61
62 VrfOutput {
63 output_hex: hex::encode(output),
64 proof: VrfProof {
65 gamma_hex: hex::encode(gamma.to_sec1_point(true).as_bytes()),
66 c_hex: hex::encode(scalar_to_bytes(&c)),
67 s_hex: hex::encode(scalar_to_bytes(&s)),
68 },
69 }
70}
71
72pub fn verify(public: &AffinePoint, alpha: &[u8], output: &VrfOutput) -> bool {
74 let gamma = match decode_point(&output.proof.gamma_hex) {
75 Some(p) => p,
76 None => return false,
77 };
78 let c = match decode_scalar(&output.proof.c_hex) {
79 Some(s) => s,
80 None => return false,
81 };
82 let s = match decode_scalar(&output.proof.s_hex) {
83 Some(s) => s,
84 None => return false,
85 };
86
87 let h_point = hash_to_curve(alpha);
88
89 let neg_c = -c;
91 let u = (ProjectivePoint::GENERATOR * s + ProjectivePoint::from(*public) * neg_c).to_affine();
92
93 let v = (ProjectivePoint::from(h_point) * s + ProjectivePoint::from(gamma) * neg_c).to_affine();
95
96 let c_expected = challenge(public, &h_point, &gamma, &u, &v, alpha);
98 if c_expected != c {
99 return false;
100 }
101
102 let expected_output = hex::encode(hash_output(&gamma));
104 expected_output == output.output_hex
105}
106
107fn reduce_to_scalar(mut bytes: [u8; 32]) -> Scalar {
111 loop {
112 if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(bytes))) {
113 return s;
114 }
115 let mut h = Sha256::new();
116 h.update(b"confium-scalar-reduce-v1");
117 h.update(bytes);
118 bytes = h.finalize().into();
119 }
120}
121
122fn hash_to_curve(alpha: &[u8]) -> AffinePoint {
123 let mut counter = 0u32;
124 loop {
125 let mut hasher = Sha256::new();
126 hasher.update(b"confium-vrf-h2c");
127 hasher.update(alpha);
128 hasher.update(counter.to_be_bytes());
129 let hash = hasher.finalize();
130
131 let fb = p256::FieldBytes::try_from(&hash[..]).expect("digest is 32 bytes");
132 let ct = Scalar::from_repr(fb);
133 if let Some(scalar) = Option::<Scalar>::from(ct) {
134 let point = ProjectivePoint::GENERATOR * scalar;
135 return point.to_affine();
136 }
137 counter += 1;
138 }
139}
140
141fn challenge(
142 public: &AffinePoint,
143 h: &AffinePoint,
144 gamma: &AffinePoint,
145 u: &AffinePoint,
146 v: &AffinePoint,
147 alpha: &[u8],
148) -> Scalar {
149 let mut hasher = Sha256::new();
150 hasher.update(b"confium-vrf-c");
151 hasher.update(public.to_sec1_point(true).as_bytes());
152 hasher.update(h.to_sec1_point(true).as_bytes());
153 hasher.update(gamma.to_sec1_point(true).as_bytes());
154 hasher.update(u.to_sec1_point(true).as_bytes());
155 hasher.update(v.to_sec1_point(true).as_bytes());
156 hasher.update(alpha);
157 let hash = hasher.finalize();
158
159 let bytes: [u8; 32] = hash.into();
160 reduce_to_scalar(bytes)
161}
162
163fn hash_output(gamma: &AffinePoint) -> [u8; 32] {
164 let mut hasher = Sha256::new();
165 hasher.update(b"confium-vrf-out");
166 hasher.update(gamma.to_sec1_point(true).as_bytes());
167 let result = hasher.finalize();
168 let mut out = [0u8; 32];
169 out.copy_from_slice(&result);
170 out
171}
172
173fn scalar_to_bytes(s: &Scalar) -> [u8; 32] {
174 s.to_repr().into()
175}
176
177fn decode_point(hex_str: &str) -> Option<AffinePoint> {
178 let bytes = hex::decode(hex_str).ok()?;
179 let encoded =
180 p256::elliptic_curve::sec1::Sec1Point::<p256::NistP256>::from_bytes(&bytes).ok()?;
181 Option::<AffinePoint>::from(AffinePoint::from_sec1_point(&encoded))
182}
183
184fn decode_scalar(hex_str: &str) -> Option<Scalar> {
185 let bytes = hex::decode(hex_str).ok()?;
186 if bytes.len() != 32 {
187 return None;
188 }
189 let arr: [u8; 32] = bytes.as_slice().try_into().ok()?;
190 let fb = p256::FieldBytes::from(arr);
191 Option::<Scalar>::from(Scalar::from_repr(fb))
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use p256::elliptic_curve::Field;
198
199 fn random_keypair() -> (Scalar, AffinePoint) {
200 use p256::elliptic_curve::rand_core::Rng;
201 let mut buf = [0u8; 32];
202 UnwrapErr(SysRng).fill_bytes(&mut buf);
203 let fb = p256::FieldBytes::from(buf);
204 let secret = Option::<Scalar>::from(Scalar::from_repr(fb))
205 .unwrap_or_else(|| Scalar::random(&mut UnwrapErr(SysRng)));
206 let public = (ProjectivePoint::GENERATOR * secret).to_affine();
207 (secret, public)
208 }
209
210 #[test]
211 fn valid_proof_verifies() {
212 let (secret, public) = random_keypair();
213 let output = prove(&secret, &public, b"test-alpha");
214 assert!(verify(&public, b"test-alpha", &output));
215 }
216
217 #[test]
218 fn output_is_deterministic() {
219 let (secret, public) = random_keypair();
220 let out1 = prove(&secret, &public, b"alpha");
221 let out2 = prove(&secret, &public, b"alpha");
222 assert_eq!(out1.output_hex, out2.output_hex);
223 }
224
225 #[test]
226 fn different_alphas_different_outputs() {
227 let (secret, public) = random_keypair();
228 let out1 = prove(&secret, &public, b"alpha1");
229 let out2 = prove(&secret, &public, b"alpha2");
230 assert_ne!(out1.output_hex, out2.output_hex);
231 }
232
233 #[test]
234 fn different_keys_different_outputs() {
235 let (s1, p1) = random_keypair();
236 let (s2, p2) = random_keypair();
237 let out1 = prove(&s1, &p1, b"alpha");
238 let out2 = prove(&s2, &p2, b"alpha");
239 assert_ne!(out1.output_hex, out2.output_hex);
240 }
241
242 #[test]
243 fn wrong_public_key_rejected() {
244 let (secret, _) = random_keypair();
245 let (_, wrong_public) = random_keypair();
246 let output = prove(&secret, &wrong_public, b"alpha");
247 let (_, _correct_public) = random_keypair();
249 let (_, other_public) = random_keypair();
252 assert!(
253 !verify(&other_public, b"alpha", &output) || verify(&wrong_public, b"alpha", &output)
254 );
255 }
256
257 #[test]
258 fn wrong_alpha_rejected() {
259 let (secret, public) = random_keypair();
260 let output = prove(&secret, &public, b"correct");
261 assert!(!verify(&public, b"wrong", &output));
262 }
263
264 #[test]
265 fn output_is_32_bytes() {
266 let (secret, public) = random_keypair();
267 let output = prove(&secret, &public, b"test");
268 let bytes = hex::decode(&output.output_hex).unwrap();
269 assert_eq!(bytes.len(), 32);
270 }
271}