hopr_crypto_types/
vrf.rs

1use hopr_crypto_random::random_bytes;
2use hopr_primitive_types::prelude::*;
3use k256::elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest};
4use k256::elliptic_curve::sec1::ToEncodedPoint;
5use k256::elliptic_curve::ProjectivePoint;
6use k256::{Scalar, Secp256k1};
7use serde::{
8    de::{self, Deserializer, Visitor},
9    Deserialize, Serialize,
10};
11
12use crate::errors::{CryptoError::CalculationError, Result};
13use crate::keypairs::{ChainKeypair, Keypair};
14use crate::types::CurvePoint;
15use crate::utils::k256_scalar_from_bytes;
16
17/// Bundles values given to the smart contract to prove that a ticket is a win.
18///
19/// The VRF is thereby needed because it generates on-demand deterministic
20/// entropy that can only be derived by the ticket redeemer.
21#[allow(non_snake_case)]
22#[derive(Clone, Default)]
23pub struct VrfParameters {
24    /// the pseudo-random point
25    pub V: CurvePoint,
26    pub h: Scalar,
27    pub s: Scalar,
28}
29
30impl Serialize for VrfParameters {
31    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
32    where
33        S: serde::Serializer,
34    {
35        let v: [u8; Self::SIZE] = self.clone().into();
36        serializer.serialize_bytes(v.as_ref())
37    }
38}
39
40struct VrfParametersVisitor {}
41
42impl Visitor<'_> for VrfParametersVisitor {
43    type Value = VrfParameters;
44
45    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
46        formatter.write_fmt(format_args!("a byte-array with {} elements", VrfParameters::SIZE))
47    }
48
49    fn visit_bytes<E>(self, v: &[u8]) -> std::result::Result<Self::Value, E>
50    where
51        E: de::Error,
52    {
53        VrfParameters::try_from(v).map_err(|e| de::Error::custom(e.to_string()))
54    }
55}
56
57// Use compact deserialization for tickets as they are used very often
58impl<'de> Deserialize<'de> for VrfParameters {
59    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
60    where
61        D: Deserializer<'de>,
62    {
63        deserializer.deserialize_bytes(VrfParametersVisitor {})
64    }
65}
66
67impl std::fmt::Debug for VrfParameters {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        f.debug_struct("VrfParameters")
70            .field("V", &hex::encode(self.V.as_compressed()))
71            .field("h", &hex::encode(self.h.to_bytes()))
72            .field("s", &hex::encode(self.s.to_bytes()))
73            .finish()
74    }
75}
76
77impl From<VrfParameters> for [u8; VRF_PARAMETERS_SIZE] {
78    fn from(value: VrfParameters) -> Self {
79        let mut ret = [0u8; VRF_PARAMETERS_SIZE];
80        ret[0..CurvePoint::SIZE_COMPRESSED].copy_from_slice(value.V.as_compressed().as_ref());
81        ret[CurvePoint::SIZE_COMPRESSED..CurvePoint::SIZE_COMPRESSED + 32].copy_from_slice(value.h.to_bytes().as_ref());
82        ret[CurvePoint::SIZE_COMPRESSED + 32..CurvePoint::SIZE_COMPRESSED + 64]
83            .copy_from_slice(value.s.to_bytes().as_ref());
84        ret
85    }
86}
87
88impl TryFrom<&[u8]> for VrfParameters {
89    type Error = GeneralError;
90
91    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
92        if value.len() == Self::SIZE {
93            let mut v = [0u8; CurvePoint::SIZE_COMPRESSED];
94            v.copy_from_slice(&value[..CurvePoint::SIZE_COMPRESSED]);
95            Ok(VrfParameters {
96                V: CurvePoint::try_from(&value[..CurvePoint::SIZE_COMPRESSED])?,
97                h: k256_scalar_from_bytes(&value[CurvePoint::SIZE_COMPRESSED..CurvePoint::SIZE_COMPRESSED + 32])
98                    .map_err(|_| GeneralError::ParseError("VrfParameters".into()))?,
99                s: k256_scalar_from_bytes(
100                    &value[CurvePoint::SIZE_COMPRESSED + 32..CurvePoint::SIZE_COMPRESSED + 32 + 32],
101                )
102                .map_err(|_| GeneralError::ParseError("VrfParameters".into()))?,
103            })
104        } else {
105            Err(GeneralError::ParseError("VrfParameters".into()))
106        }
107    }
108}
109
110const VRF_PARAMETERS_SIZE: usize = CurvePoint::SIZE_COMPRESSED + 32 + 32;
111impl BytesEncodable<VRF_PARAMETERS_SIZE> for VrfParameters {}
112
113impl VrfParameters {
114    /// Verifies that VRF values are valid.
115    /// The SC performs the verification. This method is here just to test correctness.
116    #[allow(non_snake_case)]
117    pub fn verify<const T: usize>(&self, creator: &Address, msg: &[u8; T], dst: &[u8]) -> Result<()> {
118        let cap_B = self.get_encoded_payload(creator, msg, dst)?;
119
120        let R_v: ProjectivePoint<Secp256k1> = cap_B * self.s - self.V.clone().into_projective_point() * self.h;
121
122        let h_check = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
123            &[
124                creator.as_ref(),
125                &self.V.as_uncompressed().as_bytes()[1..],
126                &R_v.to_affine().to_encoded_point(false).as_bytes()[1..],
127                msg,
128            ],
129            &[dst],
130        )
131        .or(Err(CalculationError))?;
132
133        if h_check != self.h {
134            return Err(CalculationError);
135        }
136
137        Ok(())
138    }
139
140    /// Performs a scalar point multiplication of `self.h` and `self.v`
141    /// and returns the point in affine coordinates.
142    ///
143    /// Used to create the witness values needed by the smart contract.
144    pub fn get_h_v_witness(&self) -> k256::EncodedPoint {
145        (self.V.affine * self.h).to_affine().to_encoded_point(false)
146    }
147
148    /// Performs a scalar point multiplication with the encoded payload
149    /// and `self.s`. Expands the payload and applies the hash_to_curve
150    /// algorithm.
151    ///
152    /// Used to create the witness values needed by the smart contract.
153    pub fn get_s_b_witness<const T: usize>(
154        &self,
155        creator: &Address,
156        msg: &[u8; T],
157        dst: &[u8],
158    ) -> crate::errors::Result<k256::EncodedPoint> {
159        Ok((self.get_encoded_payload(creator, msg, dst)? * self.s)
160            .to_affine()
161            .to_encoded_point(false))
162    }
163
164    /// Takes the message upon which the VRF gets computed, the domain separation tag
165    /// and the Ethereum address of the creator, expand the raw data with the
166    /// `ExpandMsgXmd` algorithm (https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-expand_message_xmd)
167    /// and applies the hash-to-curve function to it.
168    ///
169    /// Finally, returns an elliptic curve point on Secp256k1.
170    fn get_encoded_payload<const T: usize>(
171        &self,
172        creator: &Address,
173        msg: &[u8; T],
174        dst: &[u8],
175    ) -> crate::errors::Result<k256::ProjectivePoint> {
176        Secp256k1::hash_from_bytes::<ExpandMsgXmd<sha3::Keccak256>>(&[creator.as_ref(), msg], &[dst])
177            .or(Err(CalculationError))
178    }
179}
180
181/// Takes a private key, the corresponding Ethereum address and a payload
182/// and creates all parameters that are required by the smart contract
183/// to prove that a ticket is a win.
184#[allow(non_snake_case)]
185pub fn derive_vrf_parameters<T: AsRef<[u8]>>(
186    msg: T,
187    chain_keypair: &ChainKeypair,
188    dst: &[u8],
189) -> crate::errors::Result<VrfParameters> {
190    let chain_addr = chain_keypair.public().to_address();
191    let B = Secp256k1::hash_from_bytes::<ExpandMsgXmd<sha3::Keccak256>>(&[chain_addr.as_ref(), msg.as_ref()], &[dst])?;
192
193    let a: Scalar = chain_keypair.into();
194
195    if a.is_zero().into() {
196        return Err(crate::errors::CryptoError::InvalidSecretScalar);
197    }
198
199    let V = B * a;
200
201    let r = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
202        &[
203            &a.to_bytes(),
204            &V.to_affine().to_encoded_point(false).as_bytes()[1..],
205            &random_bytes::<64>(),
206        ],
207        &[dst],
208    )?;
209
210    let R_v = B * r;
211
212    let h = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
213        &[
214            chain_addr.as_ref(),
215            &V.to_affine().to_encoded_point(false).as_bytes()[1..],
216            &R_v.to_affine().to_encoded_point(false).as_bytes()[1..],
217            msg.as_ref(),
218        ],
219        &[dst],
220    )?;
221    let s = r + h * a;
222
223    Ok(VrfParameters {
224        V: V.to_affine().into(),
225        h,
226        s,
227    })
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233    use crate::types::Hash;
234    use hex_literal::hex;
235
236    lazy_static::lazy_static! {
237        static ref ALICE: ChainKeypair = ChainKeypair::from_secret(&hex!("e17fe86ce6e99f4806715b0c9412f8dad89334bf07f72d5834207a9d8f19d7f8")).expect("lazy static keypair should be valid");
238        static ref ALICE_ADDR: Address = ALICE.public().to_address();
239
240        static ref TEST_MSG: [u8; 32] = hex!("8248a966b9215e154c8f673cb154da030916be3fb31af3b1220419a1c98eeaed");
241        static ref ALICE_VRF_OUTPUT: [u8; 97] = hex!("02a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff0652ee2b835e7c475fde5adfedeb7cc31ecdd690f13ac6bb59ed046ca4c189c9996fe60abaad8c93e771c19acfe697e15c1e5ed6a182b2960bf8c7bd687e77a9975");
242
243        static ref WRONG_V_POINT_PREFIX: [u8; 97] = hex!("01a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff0652ee2b835e7c475fde5adfedeb7cc31ecdd690f13ac6bb59ed046ca4c189c9996fe60abaad8c93e771c19acfe697e15c1e5ed6a182b2960bf8c7bd687e77a9975");
244        static ref H_NOT_IN_FIELD: [u8; 97] = hex!("02a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff065fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe60abaad8c93e771c19acfe697e15c1e5ed6a182b2960bf8c7bd687e77a9975");
245        static ref S_NOT_IN_FIELD: [u8; 97] = hex!("02a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff0652ee2b835e7c475fde5adfedeb7cc31ecdd690f13ac6bb59ed046ca4c189c9996ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
246    }
247
248    #[test]
249    fn vrf_values_serialize_deserialize() -> anyhow::Result<()> {
250        let vrf_values = derive_vrf_parameters(&*TEST_MSG, &*ALICE, Hash::default().as_ref())?;
251
252        let deserialized = VrfParameters::try_from(ALICE_VRF_OUTPUT.as_ref())?;
253
254        // check for regressions
255        assert_eq!(vrf_values.V, deserialized.V);
256        assert!(deserialized
257            .verify(&*ALICE_ADDR, &*TEST_MSG, Hash::default().as_ref())
258            .is_ok());
259
260        // PartialEq is intentionally not implemented for VrfParameters
261        let vrf: [u8; VrfParameters::SIZE] = vrf_values.clone().into();
262        let other = VrfParameters::try_from(vrf.as_ref())?;
263        assert!(vrf_values.s == other.s && vrf_values.V == other.V && vrf_values.h == other.h);
264
265        Ok(())
266    }
267
268    #[test]
269    fn vrf_values_serialize_deserialize_bad_examples() {
270        assert!(VrfParameters::try_from(WRONG_V_POINT_PREFIX.as_ref()).is_err());
271
272        assert!(VrfParameters::try_from(H_NOT_IN_FIELD.as_ref()).is_err());
273
274        assert!(VrfParameters::try_from(S_NOT_IN_FIELD.as_ref()).is_err());
275    }
276
277    #[test]
278    fn vrf_values_crypto() -> anyhow::Result<()> {
279        let vrf_values = derive_vrf_parameters(&*TEST_MSG, &*ALICE, Hash::default().as_ref())?;
280
281        assert!(vrf_values
282            .verify(&ALICE_ADDR, &*TEST_MSG, Hash::default().as_ref())
283            .is_ok());
284
285        Ok(())
286    }
287}