vrf_contract_verifier/
types.rs1use crate::constants::{
2 CHALLENGE_LENGTH,
3 VRF_HASH_TO_CURVE_DOMAIN,
4 EXPAND_MESSAGE_OUTPUT_LENGTH,
5 SUITE_STRING,
6 CHALLENGE_GENERATION_DOMAIN_SEPARATOR_FRONT,
7 CHALLENGE_GENERATION_DOMAIN_SEPARATOR_BACK,
8 PROOF_TO_HASH_DOMAIN_SEPARATOR_FRONT,
9 PROOF_TO_HASH_DOMAIN_SEPARATOR_BACK
10};
11use sha2::{Digest, Sha512};
12
13
14pub use curve25519_dalek_ng::ristretto::{CompressedRistretto, RistrettoPoint};
15pub use curve25519_dalek_ng::scalar::Scalar;
16
17use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander};
19
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum VerificationError {
24 InvalidProof,
25 InvalidInput,
26 InvalidPublicKey,
27 InvalidProofLength,
28 DecompressionFailed,
29 InvalidScalar,
30 InvalidGamma,
31 ZeroPublicKey,
32 ExpandMessageXmdFailed,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct VrfPublicKey(pub [u8; 32]);
38
39impl VrfPublicKey {
40 pub fn validate(&self) -> Result<RistrettoPoint, VerificationError> {
43 if self.0.iter().all(|&b| b == 0) {
45 return Err(VerificationError::ZeroPublicKey);
46 }
47
48 let pk_point = CompressedRistretto(self.0)
50 .decompress()
51 .ok_or(VerificationError::InvalidPublicKey)?;
52
53 if pk_point == RistrettoPoint::default() {
55 return Err(VerificationError::ZeroPublicKey);
56 }
57
58 Ok(pk_point)
59 }
60}
61
62#[cfg_attr(feature = "near", near_sdk::near(serializers = [borsh, json]))]
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct VrfProof {
66 pub gamma: [u8; 32], pub c: [u8; CHALLENGE_LENGTH], pub s: [u8; 32], }
70
71pub type VrfOutput = [u8; 64];
73
74impl VrfProof {
75 pub fn verify(
77 &self,
78 input: &[u8],
79 public_key: &VrfPublicKey,
80 ) -> Result<VrfOutput, VerificationError> {
81 let output = self.to_output()?;
82 self.verify_output(input, public_key, &output)?;
83 Ok(output)
84 }
85
86 pub fn verify_output(
88 &self,
89 input: &[u8],
90 public_key: &VrfPublicKey,
91 expected_output: &VrfOutput,
92 ) -> Result<(), VerificationError> {
93 let pk_point = public_key.validate()?;
95
96 let gamma = CompressedRistretto(self.gamma)
98 .decompress()
99 .ok_or(VerificationError::DecompressionFailed)?;
100
101 let s = Scalar::from_canonical_bytes(self.s)
103 .ok_or(VerificationError::InvalidScalar)?;
104
105 let h = hash_to_curve(&pk_point, input)?;
107
108 let challenge = challenge_from_hash(&self.c);
110
111 use curve25519_dalek_ng::constants::RISTRETTO_BASEPOINT_POINT;
115 let u = &s * &RISTRETTO_BASEPOINT_POINT - &challenge * &pk_point;
116 let v = &s * &h - &challenge * γ
117
118 let c_prime = generate_challenge(&pk_point, &h, &gamma, &u, &v)?;
120
121 if c_prime != self.c {
123 return Err(VerificationError::InvalidProof);
124 }
125
126 let computed_output = proof_to_hash(&gamma)?;
128 if computed_output != *expected_output {
129 return Err(VerificationError::InvalidProof);
130 }
131
132 Ok(())
133 }
134
135 pub fn to_output(&self) -> Result<VrfOutput, VerificationError> {
137 let gamma = CompressedRistretto(self.gamma)
138 .decompress()
139 .ok_or(VerificationError::InvalidGamma)?;
140 proof_to_hash(&gamma)
141 }
142}
143
144pub fn hash_to_curve(pk: &RistrettoPoint, input: &[u8]) -> Result<RistrettoPoint, VerificationError> {
149 let pk_compressed = pk.compress().0;
150
151 let mut expanded_message = ExpandMsgXmd::<Sha512>::expand_message(
153 &[&pk_compressed, input],
154 &[VRF_HASH_TO_CURVE_DOMAIN],
155 EXPAND_MESSAGE_OUTPUT_LENGTH,
156 )
157 .map_err(|_| VerificationError::ExpandMessageXmdFailed)?;
158
159 let mut uniform_bytes = [0u8; EXPAND_MESSAGE_OUTPUT_LENGTH];
160 expanded_message.fill_bytes(&mut uniform_bytes);
161
162 Ok(RistrettoPoint::from_uniform_bytes(&uniform_bytes))
163}
164
165pub fn challenge_from_hash(hash: &[u8; CHALLENGE_LENGTH]) -> Scalar {
167 let mut scalar_bytes = [0u8; 32];
169 scalar_bytes[..CHALLENGE_LENGTH].copy_from_slice(hash);
170 Scalar::from_bytes_mod_order(scalar_bytes)
171}
172
173fn generate_challenge(
176 pk: &RistrettoPoint,
177 h: &RistrettoPoint,
178 gamma: &RistrettoPoint,
179 u: &RistrettoPoint,
180 v: &RistrettoPoint,
181) -> Result<[u8; CHALLENGE_LENGTH], VerificationError> {
182 let mut hasher = Sha512::new();
183
184 let pk_bytes = pk.compress().0;
185 let h_bytes = h.compress().0;
186 let gamma_bytes = gamma.compress().0;
187 let u_bytes = u.compress().0;
188 let v_bytes = v.compress().0;
189
190 hasher.update(SUITE_STRING);
191 hasher.update([CHALLENGE_GENERATION_DOMAIN_SEPARATOR_FRONT]);
192 hasher.update(pk_bytes);
193 hasher.update(h_bytes);
194 hasher.update(gamma_bytes);
195 hasher.update(u_bytes);
196 hasher.update(v_bytes);
197 hasher.update([CHALLENGE_GENERATION_DOMAIN_SEPARATOR_BACK]);
198 let hash = hasher.finalize();
199
200 let mut challenge = [0u8; CHALLENGE_LENGTH];
202 challenge.copy_from_slice(&hash[..CHALLENGE_LENGTH]);
203
204 Ok(challenge)
205}
206
207pub fn proof_to_hash(gamma: &RistrettoPoint) -> Result<VrfOutput, VerificationError> {
210 let mut hasher = Sha512::new();
211 hasher.update(SUITE_STRING);
212 hasher.update([PROOF_TO_HASH_DOMAIN_SEPARATOR_FRONT]);
213 hasher.update(&gamma.compress().0);
214 hasher.update([PROOF_TO_HASH_DOMAIN_SEPARATOR_BACK]);
215 Ok(hasher.finalize().into())
216}