hopr_crypto_types/
vrf.rs

1use hopr_crypto_random::random_bytes;
2use hopr_primitive_types::prelude::*;
3use k256::{
4    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::CurvePoint,
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, Default)]
25pub struct VrfParameters {
26    /// the pseudo-random point
27    pub V: CurvePoint,
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.clone().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.as_compressed()))
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..CurvePoint::SIZE_COMPRESSED].copy_from_slice(value.V.as_compressed().as_ref());
92        ret[CurvePoint::SIZE_COMPRESSED..CurvePoint::SIZE_COMPRESSED + 32].copy_from_slice(value.h.to_bytes().as_ref());
93        ret[CurvePoint::SIZE_COMPRESSED + 32..CurvePoint::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; CurvePoint::SIZE_COMPRESSED];
105            v.copy_from_slice(&value[..CurvePoint::SIZE_COMPRESSED]);
106            Ok(VrfParameters {
107                V: CurvePoint::try_from(&value[..CurvePoint::SIZE_COMPRESSED])?,
108                h: k256_scalar_from_bytes(&value[CurvePoint::SIZE_COMPRESSED..CurvePoint::SIZE_COMPRESSED + 32])
109                    .map_err(|_| GeneralError::ParseError("VrfParameters".into()))?,
110                s: k256_scalar_from_bytes(
111                    &value[CurvePoint::SIZE_COMPRESSED + 32..CurvePoint::SIZE_COMPRESSED + 32 + 32],
112                )
113                .map_err(|_| GeneralError::ParseError("VrfParameters".into()))?,
114            })
115        } else {
116            Err(GeneralError::ParseError("VrfParameters".into()))
117        }
118    }
119}
120
121const VRF_PARAMETERS_SIZE: usize = CurvePoint::SIZE_COMPRESSED + 32 + 32;
122impl BytesEncodable<VRF_PARAMETERS_SIZE> for VrfParameters {}
123
124impl VrfParameters {
125    /// Verifies that VRF values are valid.
126    /// The SC performs the verification. This method is here just to test correctness.
127    #[allow(non_snake_case)]
128    pub fn verify<const T: usize>(&self, creator: &Address, msg: &[u8; T], dst: &[u8]) -> Result<()> {
129        let cap_B = self.get_encoded_payload(creator, msg, dst)?;
130
131        let R_v: ProjectivePoint<Secp256k1> = cap_B * self.s - self.V.clone().into_projective_point() * self.h;
132
133        let h_check = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
134            &[
135                creator.as_ref(),
136                &self.V.as_uncompressed().as_bytes()[1..],
137                &R_v.to_affine().to_encoded_point(false).as_bytes()[1..],
138                msg,
139            ],
140            &[dst],
141        )
142        .or(Err(CalculationError))?;
143
144        if h_check != self.h {
145            return Err(CalculationError);
146        }
147
148        Ok(())
149    }
150
151    /// Performs a scalar point multiplication of `self.h` and `self.v`
152    /// and returns the point in affine coordinates.
153    ///
154    /// Used to create the witness values needed by the smart contract.
155    pub fn get_h_v_witness(&self) -> k256::EncodedPoint {
156        (self.V.affine * self.h).to_affine().to_encoded_point(false)
157    }
158
159    /// Performs a scalar point multiplication with the encoded payload
160    /// and `self.s`. Expands the payload and applies the hash_to_curve
161    /// algorithm.
162    ///
163    /// Used to create the witness values needed by the smart contract.
164    pub fn get_s_b_witness<const T: usize>(
165        &self,
166        creator: &Address,
167        msg: &[u8; T],
168        dst: &[u8],
169    ) -> crate::errors::Result<k256::EncodedPoint> {
170        Ok((self.get_encoded_payload(creator, msg, dst)? * self.s)
171            .to_affine()
172            .to_encoded_point(false))
173    }
174
175    /// Takes the message upon which the VRF gets computed, the domain separation tag
176    /// and the Ethereum address of the creator, expand the raw data with the
177    /// `ExpandMsgXmd` algorithm (https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-expand_message_xmd)
178    /// and applies the hash-to-curve function to it.
179    ///
180    /// Finally, returns an elliptic curve point on Secp256k1.
181    fn get_encoded_payload<const T: usize>(
182        &self,
183        creator: &Address,
184        msg: &[u8; T],
185        dst: &[u8],
186    ) -> crate::errors::Result<k256::ProjectivePoint> {
187        Secp256k1::hash_from_bytes::<ExpandMsgXmd<sha3::Keccak256>>(&[creator.as_ref(), msg], &[dst])
188            .or(Err(CalculationError))
189    }
190}
191
192/// Takes a private key, the corresponding Ethereum address and a payload
193/// and creates all parameters that are required by the smart contract
194/// to prove that a ticket is a win.
195#[allow(non_snake_case)]
196pub fn derive_vrf_parameters<T: AsRef<[u8]>>(
197    msg: T,
198    chain_keypair: &ChainKeypair,
199    dst: &[u8],
200) -> crate::errors::Result<VrfParameters> {
201    let chain_addr = chain_keypair.public().to_address();
202    let B = Secp256k1::hash_from_bytes::<ExpandMsgXmd<sha3::Keccak256>>(&[chain_addr.as_ref(), msg.as_ref()], &[dst])?;
203
204    let a: Scalar = chain_keypair.into();
205
206    if a.is_zero().into() {
207        return Err(crate::errors::CryptoError::InvalidSecretScalar);
208    }
209
210    let V = B * a;
211
212    let r = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
213        &[
214            &a.to_bytes(),
215            &V.to_affine().to_encoded_point(false).as_bytes()[1..],
216            &random_bytes::<64>(),
217        ],
218        &[dst],
219    )?;
220
221    let R_v = B * r;
222
223    let h = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
224        &[
225            chain_addr.as_ref(),
226            &V.to_affine().to_encoded_point(false).as_bytes()[1..],
227            &R_v.to_affine().to_encoded_point(false).as_bytes()[1..],
228            msg.as_ref(),
229        ],
230        &[dst],
231    )?;
232    let s = r + h * a;
233
234    Ok(VrfParameters {
235        V: V.to_affine().into(),
236        h,
237        s,
238    })
239}
240
241#[cfg(test)]
242mod tests {
243    use hex_literal::hex;
244
245    use super::*;
246    use crate::types::Hash;
247
248    lazy_static::lazy_static! {
249        static ref ALICE: ChainKeypair = ChainKeypair::from_secret(&hex!("e17fe86ce6e99f4806715b0c9412f8dad89334bf07f72d5834207a9d8f19d7f8")).expect("lazy static keypair should be valid");
250        static ref ALICE_ADDR: Address = ALICE.public().to_address();
251
252        static ref TEST_MSG: [u8; 32] = hex!("8248a966b9215e154c8f673cb154da030916be3fb31af3b1220419a1c98eeaed");
253        static ref ALICE_VRF_OUTPUT: [u8; 97] = hex!("02a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff0652ee2b835e7c475fde5adfedeb7cc31ecdd690f13ac6bb59ed046ca4c189c9996fe60abaad8c93e771c19acfe697e15c1e5ed6a182b2960bf8c7bd687e77a9975");
254
255        static ref WRONG_V_POINT_PREFIX: [u8; 97] = hex!("01a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff0652ee2b835e7c475fde5adfedeb7cc31ecdd690f13ac6bb59ed046ca4c189c9996fe60abaad8c93e771c19acfe697e15c1e5ed6a182b2960bf8c7bd687e77a9975");
256        static ref H_NOT_IN_FIELD: [u8; 97] = hex!("02a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff065fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe60abaad8c93e771c19acfe697e15c1e5ed6a182b2960bf8c7bd687e77a9975");
257        static ref S_NOT_IN_FIELD: [u8; 97] = hex!("02a4e1fa28e8a40348baf79b576a6e040b370b74893d355cd48fc382d5235ff0652ee2b835e7c475fde5adfedeb7cc31ecdd690f13ac6bb59ed046ca4c189c9996ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
258    }
259
260    #[test]
261    fn vrf_values_serialize_deserialize() -> anyhow::Result<()> {
262        let vrf_values = derive_vrf_parameters(*TEST_MSG, &ALICE, Hash::default().as_ref())?;
263
264        let deserialized = VrfParameters::try_from(ALICE_VRF_OUTPUT.as_ref())?;
265
266        // check for regressions
267        assert_eq!(vrf_values.V, deserialized.V);
268        assert!(
269            deserialized
270                .verify(&ALICE_ADDR, &TEST_MSG, Hash::default().as_ref())
271                .is_ok()
272        );
273
274        // PartialEq is intentionally not implemented for VrfParameters
275        let vrf: [u8; VrfParameters::SIZE] = vrf_values.clone().into();
276        let other = VrfParameters::try_from(vrf.as_ref())?;
277        assert!(vrf_values.s == other.s && vrf_values.V == other.V && vrf_values.h == other.h);
278
279        Ok(())
280    }
281
282    #[test]
283    fn vrf_values_serialize_deserialize_bad_examples() {
284        assert!(VrfParameters::try_from(WRONG_V_POINT_PREFIX.as_ref()).is_err());
285
286        assert!(VrfParameters::try_from(H_NOT_IN_FIELD.as_ref()).is_err());
287
288        assert!(VrfParameters::try_from(S_NOT_IN_FIELD.as_ref()).is_err());
289    }
290
291    #[test]
292    fn vrf_values_crypto() -> anyhow::Result<()> {
293        let vrf_values = derive_vrf_parameters(*TEST_MSG, &ALICE, Hash::default().as_ref())?;
294
295        assert!(
296            vrf_values
297                .verify(&ALICE_ADDR, &TEST_MSG, Hash::default().as_ref())
298                .is_ok()
299        );
300
301        Ok(())
302    }
303}