hopr_crypto_types/
vrf.rs

1use hopr_crypto_random::random_bytes;
2use hopr_primitive_types::prelude::*;
3use k256::{
4    AffinePoint, Scalar, Secp256k1,
5    elliptic_curve::{
6        ProjectivePoint,
7        hash2curve::{ExpandMsgXmd, GroupDigest},
8        sec1::ToEncodedPoint,
9    },
10};
11
12use crate::{
13    errors::{CryptoError::CalculationError, Result},
14    keypairs::{ChainKeypair, Keypair},
15    types::{PublicKey, affine_point_from_bytes},
16    utils::k256_scalar_from_bytes,
17};
18
19/// Bundles values given to the smart contract to prove that a ticket is a win.
20///
21/// The VRF is thereby needed because it generates on-demand deterministic
22/// entropy that can only be derived by the ticket redeemer.
23#[allow(non_snake_case)]
24#[derive(Clone, Copy, Default)]
25pub struct VrfParameters {
26    /// the pseudo-random point
27    pub V: AffinePoint,
28    pub h: Scalar,
29    pub s: Scalar,
30}
31
32#[cfg(feature = "serde")]
33impl serde::Serialize for VrfParameters {
34    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
35    where
36        S: serde::Serializer,
37    {
38        let v: [u8; Self::SIZE] = (*self).into();
39        serializer.serialize_bytes(v.as_ref())
40    }
41}
42
43#[cfg(feature = "serde")]
44mod de {
45    use serde::de;
46
47    use super::*;
48
49    pub(super) struct VrfParametersVisitor {}
50
51    impl de::Visitor<'_> for VrfParametersVisitor {
52        type Value = VrfParameters;
53
54        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
55            formatter.write_fmt(format_args!("a byte-array with {} elements", VrfParameters::SIZE))
56        }
57
58        fn visit_bytes<E>(self, v: &[u8]) -> std::result::Result<Self::Value, E>
59        where
60            E: de::Error,
61        {
62            VrfParameters::try_from(v).map_err(|e| de::Error::custom(e.to_string()))
63        }
64    }
65}
66
67// Use compact deserialization for tickets as they are used very often
68#[cfg(feature = "serde")]
69impl<'de> serde::Deserialize<'de> for VrfParameters {
70    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
71    where
72        D: serde::Deserializer<'de>,
73    {
74        deserializer.deserialize_bytes(de::VrfParametersVisitor {})
75    }
76}
77
78impl std::fmt::Debug for VrfParameters {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        f.debug_struct("VrfParameters")
81            .field("V", &hex::encode(self.V.to_encoded_point(true)))
82            .field("h", &hex::encode(self.h.to_bytes()))
83            .field("s", &hex::encode(self.s.to_bytes()))
84            .finish()
85    }
86}
87
88impl From<VrfParameters> for [u8; VRF_PARAMETERS_SIZE] {
89    fn from(value: VrfParameters) -> Self {
90        let mut ret = [0u8; VRF_PARAMETERS_SIZE];
91        ret[0..PublicKey::SIZE_COMPRESSED].copy_from_slice(value.V.to_encoded_point(true).as_bytes());
92        ret[PublicKey::SIZE_COMPRESSED..PublicKey::SIZE_COMPRESSED + 32].copy_from_slice(value.h.to_bytes().as_ref());
93        ret[PublicKey::SIZE_COMPRESSED + 32..PublicKey::SIZE_COMPRESSED + 64]
94            .copy_from_slice(value.s.to_bytes().as_ref());
95        ret
96    }
97}
98
99impl TryFrom<&[u8]> for VrfParameters {
100    type Error = GeneralError;
101
102    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
103        if value.len() == Self::SIZE {
104            let mut v = [0u8; PublicKey::SIZE_COMPRESSED];
105            v.copy_from_slice(&value[..PublicKey::SIZE_COMPRESSED]);
106            Ok(VrfParameters {
107                V: affine_point_from_bytes(&value[..PublicKey::SIZE_COMPRESSED])
108                    .map_err(|_| GeneralError::ParseError("VrfParameters.V".into()))?,
109                h: k256_scalar_from_bytes(&value[PublicKey::SIZE_COMPRESSED..PublicKey::SIZE_COMPRESSED + 32])
110                    .map_err(|_| GeneralError::ParseError("VrfParameters.h".into()))?,
111                s: k256_scalar_from_bytes(
112                    &value[PublicKey::SIZE_COMPRESSED + 32..PublicKey::SIZE_COMPRESSED + 32 + 32],
113                )
114                .map_err(|_| GeneralError::ParseError("VrfParameters.s".into()))?,
115            })
116        } else {
117            Err(GeneralError::ParseError("VrfParameters.size".into()))
118        }
119    }
120}
121
122const VRF_PARAMETERS_SIZE: usize = PublicKey::SIZE_COMPRESSED + 32 + 32;
123impl BytesEncodable<VRF_PARAMETERS_SIZE> for VrfParameters {}
124
125impl VrfParameters {
126    /// Verifies that VRF values are valid.
127    /// The SC performs the verification. This method is here just to test correctness.
128    #[allow(non_snake_case)]
129    pub fn verify<const T: usize>(&self, creator: &Address, msg: &[u8; T], dst: &[u8]) -> Result<()> {
130        let cap_B = self.get_encoded_payload(creator, msg, dst)?;
131        let v_proj = ProjectivePoint::<Secp256k1>::from(self.V);
132
133        let R_v: ProjectivePoint<Secp256k1> = cap_B * self.s - v_proj * self.h;
134
135        let h_check = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
136            &[
137                creator.as_ref(),
138                &self.V.to_encoded_point(false).as_bytes()[1..],
139                &R_v.to_affine().to_encoded_point(false).as_bytes()[1..],
140                msg,
141            ],
142            &[dst],
143        )
144        .or(Err(CalculationError))?;
145
146        if h_check != self.h {
147            return Err(CalculationError);
148        }
149
150        Ok(())
151    }
152
153    /// Returns the encoded VRF `V` value as an uncompressed point in affine coordinates.
154    pub fn get_v_encoded_point(&self) -> k256::EncodedPoint {
155        self.V.to_encoded_point(false)
156    }
157
158    /// Performs a scalar point multiplication of `self.h` and `self.v`
159    /// and returns the point in affine coordinates.
160    ///
161    /// Used to create the witness values needed by the smart contract.
162    pub fn get_h_v_witness(&self) -> k256::EncodedPoint {
163        (ProjectivePoint::<Secp256k1>::from(self.V) * self.h)
164            .to_affine()
165            .to_encoded_point(false)
166    }
167
168    /// Performs a scalar point multiplication with the encoded payload
169    /// and `self.s`. Expands the payload and applies the hash_to_curve
170    /// algorithm.
171    ///
172    /// Used to create the witness values needed by the smart contract.
173    pub fn get_s_b_witness<const T: usize>(
174        &self,
175        creator: &Address,
176        msg: &[u8; T],
177        dst: &[u8],
178    ) -> Result<k256::EncodedPoint> {
179        Ok((self.get_encoded_payload(creator, msg, dst)? * self.s)
180            .to_affine()
181            .to_encoded_point(false))
182    }
183
184    /// Takes the message upon which the VRF gets computed, the domain separation tag
185    /// and the Ethereum address of the creator, expand the raw data with the
186    /// `ExpandMsgXmd` algorithm (https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-expand_message_xmd)
187    /// and applies the hash-to-curve function to it.
188    ///
189    /// Finally, returns an elliptic curve point on Secp256k1.
190    fn get_encoded_payload<const T: usize>(
191        &self,
192        creator: &Address,
193        msg: &[u8; T],
194        dst: &[u8],
195    ) -> Result<k256::ProjectivePoint> {
196        Secp256k1::hash_from_bytes::<ExpandMsgXmd<sha3::Keccak256>>(&[creator.as_ref(), msg], &[dst])
197            .or(Err(CalculationError))
198    }
199}
200
201/// Takes a private key, the corresponding Ethereum address and a payload
202/// and creates all parameters that are required by the smart contract
203/// to prove that a ticket is a win.
204#[cfg(feature = "rust-ecdsa")]
205#[allow(non_snake_case)]
206pub fn derive_vrf_parameters<T: AsRef<[u8]>>(
207    msg: T,
208    chain_keypair: &ChainKeypair,
209    dst: &[u8],
210) -> crate::errors::Result<VrfParameters> {
211    let chain_addr = chain_keypair.public().to_address();
212    let B = Secp256k1::hash_from_bytes::<ExpandMsgXmd<sha3::Keccak256>>(&[chain_addr.as_ref(), msg.as_ref()], &[dst])?;
213
214    let a: Scalar = chain_keypair.into();
215
216    let V = B * a;
217
218    let r = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
219        &[
220            &a.to_bytes(),
221            &V.to_affine().to_encoded_point(false).as_bytes()[1..],
222            &random_bytes::<64>(),
223        ],
224        &[dst],
225    )?;
226
227    let R_v = B * r;
228
229    let h = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
230        &[
231            chain_addr.as_ref(),
232            &V.to_affine().to_encoded_point(false).as_bytes()[1..],
233            &R_v.to_affine().to_encoded_point(false).as_bytes()[1..],
234            msg.as_ref(),
235        ],
236        &[dst],
237    )?;
238    let s = r + h * a;
239
240    Ok(VrfParameters { V: V.to_affine(), h, s })
241}
242
243/// Takes a private key, the corresponding Ethereum address and a payload
244/// and creates all parameters that are required by the smart contract
245/// to prove that a ticket is a win.
246#[cfg(not(feature = "rust-ecdsa"))]
247#[allow(non_snake_case)]
248pub fn derive_vrf_parameters<T: AsRef<[u8]>>(
249    msg: T,
250    chain_keypair: &ChainKeypair,
251    dst: &[u8],
252) -> Result<VrfParameters> {
253    let chain_addr = chain_keypair.public().to_address();
254    let B = Secp256k1::hash_from_bytes::<ExpandMsgXmd<sha3::Keccak256>>(&[chain_addr.as_ref(), msg.as_ref()], &[dst])?
255        .to_affine();
256
257    let a = secp256k1::Scalar::from_be_bytes(chain_keypair.secret().clone().into())
258        .map_err(|_| crate::errors::CryptoError::InvalidSecretScalar)?;
259
260    let B_pk = secp256k1::PublicKey::from_byte_array_uncompressed(
261        B.to_encoded_point(false)
262            .as_bytes()
263            .try_into()
264            .map_err(|_| crate::errors::CryptoError::InvalidPublicKey)?,
265    )
266    .map_err(|_| crate::errors::CryptoError::InvalidPublicKey)?;
267
268    let V = B_pk
269        .mul_tweak(secp256k1::global::SECP256K1, &a)
270        .map_err(|_| CalculationError)?;
271
272    let r = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
273        &[
274            &a.to_be_bytes(),
275            &V.serialize_uncompressed()[1..],
276            &random_bytes::<64>(),
277        ],
278        &[dst],
279    )?;
280
281    let r_scalar = secp256k1::Scalar::from_be_bytes(r.to_bytes().into())
282        .map_err(|_| crate::errors::CryptoError::InvalidSecretScalar)?;
283
284    let R_v = B_pk
285        .mul_tweak(secp256k1::global::SECP256K1, &r_scalar)
286        .map_err(|_| CalculationError)?;
287
288    let h = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
289        &[
290            chain_addr.as_ref(),
291            &V.serialize_uncompressed()[1..],
292            &R_v.serialize_uncompressed()[1..],
293            msg.as_ref(),
294        ],
295        &[dst],
296    )?;
297    let s = r + h * Scalar::from(chain_keypair);
298
299    let V = affine_point_from_bytes(&V.serialize_uncompressed()).map_err(|_| CalculationError)?;
300
301    Ok(VrfParameters { V, h, s })
302}
303
304#[cfg(test)]
305mod tests {
306    use hex_literal::hex;
307    use k256::elliptic_curve::ScalarPrimitive;
308    use sha3::Keccak256;
309
310    use super::*;
311    use crate::types::Hash;
312
313    lazy_static::lazy_static! {
314        static ref ALICE: ChainKeypair = ChainKeypair::from_secret(&hex!("e17fe86ce6e99f4806715b0c9412f8dad89334bf07f72d5834207a9d8f19d7f8")).expect("lazy static keypair should be valid");
315        static ref ALICE_ADDR: Address = ALICE.public().to_address();
316
317        static ref TEST_MSG: [u8; 32] = hex!("8248a966b9215e154c8f673cb154da030916be3fb31af3b1220419a1c98eeaed");
318        static ref ALICE_VRF_OUTPUT: [u8; 97] = hex!("02a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff0652ee2b835e7c475fde5adfedeb7cc31ecdd690f13ac6bb59ed046ca4c189c9996fe60abaad8c93e771c19acfe697e15c1e5ed6a182b2960bf8c7bd687e77a9975");
319
320        static ref WRONG_V_POINT_PREFIX: [u8; 97] = hex!("01a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff0652ee2b835e7c475fde5adfedeb7cc31ecdd690f13ac6bb59ed046ca4c189c9996fe60abaad8c93e771c19acfe697e15c1e5ed6a182b2960bf8c7bd687e77a9975");
321        static ref H_NOT_IN_FIELD: [u8; 97] = hex!("02a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff065fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe60abaad8c93e771c19acfe697e15c1e5ed6a182b2960bf8c7bd687e77a9975");
322        static ref S_NOT_IN_FIELD: [u8; 97] = hex!("02a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff0652ee2b835e7c475fde5adfedeb7cc31ecdd690f13ac6bb59ed046ca4c189c9996ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
323    }
324
325    #[test]
326    fn vrf_values_serialize_deserialize() -> anyhow::Result<()> {
327        let vrf_values = derive_vrf_parameters(*TEST_MSG, &ALICE, Hash::default().as_ref())?;
328
329        let deserialized = VrfParameters::try_from(ALICE_VRF_OUTPUT.as_ref())?;
330
331        // check for regressions
332        assert_eq!(vrf_values.V, deserialized.V);
333        assert!(
334            deserialized
335                .verify(&ALICE_ADDR, &TEST_MSG, Hash::default().as_ref())
336                .is_ok()
337        );
338
339        // PartialEq is intentionally not implemented for VrfParameters
340        let vrf: [u8; VrfParameters::SIZE] = vrf_values.clone().into();
341        let other = VrfParameters::try_from(vrf.as_ref())?;
342        assert!(vrf_values.s == other.s && vrf_values.V == other.V && vrf_values.h == other.h);
343
344        Ok(())
345    }
346
347    #[test]
348    fn vrf_values_serialize_deserialize_bad_examples() {
349        assert!(VrfParameters::try_from(WRONG_V_POINT_PREFIX.as_ref()).is_err());
350
351        assert!(VrfParameters::try_from(H_NOT_IN_FIELD.as_ref()).is_err());
352
353        assert!(VrfParameters::try_from(S_NOT_IN_FIELD.as_ref()).is_err());
354    }
355
356    #[test]
357    fn vrf_values_crypto() -> anyhow::Result<()> {
358        let vrf_values = derive_vrf_parameters(*TEST_MSG, &ALICE, Hash::default().as_ref())?;
359
360        assert!(
361            vrf_values
362                .verify(&ALICE_ADDR, &TEST_MSG, Hash::default().as_ref())
363                .is_ok()
364        );
365
366        Ok(())
367    }
368
369    #[test]
370    fn test_vrf_parameter_generation() -> anyhow::Result<()> {
371        let dst = b"some DST tag";
372        let priv_key: [u8; 32] = hex!("f13233ff60e1f618525dac5f7d117bef0bad0eb0b0afb2459f9cbc57a3a987ba"); // dummy
373        let message = hex!("f13233ff60e1f618525dac5f7d117bef0bad0eb0b0afb2459f9cbc57a3a987ba"); // dummy
374
375        let keypair = ChainKeypair::from_secret(&priv_key)?;
376        // vrf verification algorithm
377        let pub_key = PublicKey::from_privkey(&priv_key)?;
378
379        let params = derive_vrf_parameters(message, &keypair, dst)?;
380
381        let cap_b =
382            Secp256k1::hash_from_bytes::<ExpandMsgXmd<Keccak256>>(&[pub_key.to_address().as_ref(), &message], &[dst])?;
383
384        assert_eq!(
385            params.get_s_b_witness(&keypair.public().to_address(), &message, dst)?,
386            (cap_b * params.s).to_encoded_point(false)
387        );
388
389        let a: Scalar = ScalarPrimitive::<Secp256k1>::from_slice(&priv_key)?.into();
390        assert_eq!(params.get_h_v_witness(), (cap_b * a * params.h).to_encoded_point(false));
391
392        let r_v: ProjectivePoint<Secp256k1> =
393            cap_b * params.s - ProjectivePoint::<Secp256k1>::from(params.V) * params.h;
394
395        let h_check = Secp256k1::hash_to_scalar::<ExpandMsgXmd<Keccak256>>(
396            &[
397                pub_key.to_address().as_ref(),
398                &params.V.to_encoded_point(false).as_bytes()[1..],
399                &r_v.to_affine().to_encoded_point(false).as_bytes()[1..],
400                &message,
401            ],
402            &[dst],
403        )?;
404
405        assert_eq!(h_check, params.h);
406
407        Ok(())
408    }
409}