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