vrf_mod/vrf/
mod.rs

1//! Module using the OpenSSL library to offer Verfiable Random Function (VRF) functionality
2//!
3//! ## References
4//!
5//! * [RFC6969](https://www.rfc-editor.org/rfc/rfc6979)
6//! * [VRF-draft-05](https://tools.ietf.org/pdf/draft-irtf-cfrg-vrf-05)
7//!
8//! `sk`: the private key for the vrf
9//!
10//! `pk`: the public key for the vrf
11//!
12//! `alpha`: the input to be hashed by the vrf
13//!
14//! `beta`: the vrf hash output; 
15//!         beta = VRF_hash(sk, alpha)
16//!
17//! `pi`: the vrf proof; 
18//!         pi = VRF_prove(sk, alpha)
19//!
20//! `prover`: the prover holds the private vrf key 'sk' and public vrf key 'pk'
21//!
22//! `verifier`: the verifier holds the public vrf key 'pk'
23//!
24//! The prover generates beta and pi.
25//! 
26//! To deterministically obtain the vrf hash output beta directly from the proof pi:
27//! 
28//! `beta` = VRF_proof_to_hash(pi)
29//! 
30//! `VRF_hash(sk, alpha)` = VRF_proof_to_hash(VRF_prove(sk, alpha))
31//!
32//! pi allows a verifier holding the public key pk to verify the correctness of beta as the vrf hash of the input alpha under key pk
33//! 
34//! # Verfication:
35//! *    VRF_verify(pk, alpha, pi)
36//!
37//! # Output if valid:
38//! *    (valid, beta = VRF_proof_to_hash(pi)) 
39//!
40//! ## Features
41//!
42//! * Compute VRF proof
43//! * Verify VRF proof
44//!
45use openssl::{
46    bn::{BigNum, BigNumContext},
47    error::ErrorStack,
48    hash::{Hasher, MessageDigest},
49    rsa::{Rsa},
50    pkey::{Private, Public}
51};
52use bytes::BytesMut;
53use thiserror::Error;
54use std::{
55    os::raw::c_ulong,
56};
57use crate::VRF as VRF_trait;
58
59mod primitives;
60
61use primitives::{
62    i20sp,
63    os2ip,
64};
65
66/// Cipher suites for VRF
67#[allow(non_camel_case_types)]
68#[derive(Debug)]
69pub enum VRFCipherSuite {
70    /// Set MGF hash function as SHA-1
71    PKI_MGF_MGF1_SHA1,
72    /// Set MGF hash function as SHA-256
73    PKI_MGF_MGF1_SHA256
74}
75
76/// Error types that can be raised
77#[derive(Error, Debug)]
78pub enum Error {
79    /// Error raised from `openssl::error::ErrorStack` with a specific code
80    #[error("Error with code: {code:?}")]
81    CodedError { code: c_ulong },
82    /// The mask length is invalid
83    #[error("Invalid mask length")]
84    InvalidMaskLength,
85    /// The modulus length is invalid
86    #[error("Invalid modulus `n` length")]
87    InvalidModulusLength,
88    /// The modulus length is invalid
89    #[error("Invalid message `m` length")]
90    InvalidMessageLength,
91    /// The proof is invalid
92    #[error("Invalid proof")]
93    InvalidProof,
94    /// Unknown error
95    #[error("Unknown error")]
96    Unknown,
97}
98
99impl From<ErrorStack> for Error {
100    /// Transform error from `openssl::error::ErrorStack` to `Error::CodedError` or `Error::Unknown`
101    fn from(error: ErrorStack) -> Self {
102        match error.errors().get(0).map(openssl::error::Error::code) {
103            Some(code) => Error::CodedError { code },
104            _ => Error::Unknown {},
105        }
106    }
107}
108
109pub struct VRF {
110    // BigNum arithmetic context
111    bn_ctx: BigNumContext,
112    // Ciphersuite identity
113    _cipher_suite: VRFCipherSuite,
114    // Hasher structure
115    hasher: Hasher, 
116    // Length in bytes of hash function output
117    hlen: usize,
118}
119
120impl VRF {
121    /// Associated function to initialize a VRF structure with an initialized context for the given cipher suite.
122    ///
123    /// # Arguments:
124    ///
125    /// *    `suite`: Identifying ciphersuite
126    ///
127    /// # Returns:
128    ///
129    /// *    a VRF struct if successful
130    ///
131    pub fn from_suite(
132        suite: VRFCipherSuite
133    ) -> Result<Self, Error> {
134        // Context for BigNum algebra
135        let bn_ctx = BigNumContext::new()?;
136
137        // Set digest type
138        let digest_type = match suite {
139            VRFCipherSuite::PKI_MGF_MGF1_SHA1 => MessageDigest::sha1(),
140            VRFCipherSuite::PKI_MGF_MGF1_SHA256 => MessageDigest::sha256(),
141        };
142
143        // Length in bytes of hash function output
144        let hlen = digest_type.size();
145
146        let hasher = Hasher::new(digest_type)?;
147
148        Ok(VRF {
149            bn_ctx,
150            _cipher_suite: suite,
151            hasher,
152            hlen,
153        })
154    }
155
156    /// RSASP1 signature primitive defined in
157    /// [Section 5.2.1 of \[RFC8017\]](https://datatracker.ietf.org/doc/pdf/rfc8017#section-5.2.1)
158    ///
159    /// # Arguments: 
160    ///
161    /// *    `secret_key`: Rsa private key
162    /// *    `message`: BigNum message representation
163    ///
164    /// # Returns:
165    ///
166    /// *    a signature representative
167    ///
168    pub fn rsasp1(&mut self, 
169        secret_key: &Rsa<Private>, 
170        message: &BigNum
171    ) -> Result<BigNum, Error> {
172        let n = secret_key.n();
173        let d = secret_key.d();
174        let mut signature = BigNum::new()?;
175
176        if *message > (n - &BigNum::from_u32(1)?) && !message.is_negative() {
177            return Err(Error::InvalidMessageLength);
178        }
179
180        signature.mod_exp(&message, d, n, &mut self.bn_ctx)?;
181        Ok(signature)
182    }
183
184    /// RSAVP1 verification primitive defined in
185    /// [Section 5.2.2 of \[RFC8017\]](https://datatracker.ietf.org/doc/pdf/rfc8017#section-5.2.2)
186    /// 
187    /// # Arguments:
188    ///
189    /// *    `public_key`: Rsa public key
190    /// *    `signature`: signed message to extract
191    ///
192    /// # Returns: 
193    ///
194    /// *    a BigNum representing the message extracted from the signature
195    ///
196    pub fn rsavp1(&mut self, 
197        public_key: &Rsa<Public>, 
198        signature: &BigNum
199    ) -> Result<BigNum, Error> {
200        let n = public_key.n();
201        let e = public_key.e();
202        let mut message = BigNum::new()?;
203
204        if *signature > (n - &BigNum::from_u32(1)?) && !signature.is_negative() {
205            return Err(Error::InvalidMessageLength);
206        }
207
208        message.mod_exp(&signature, e, n, &mut self.bn_ctx)?;
209        Ok(message)
210    }
211
212    /// MGF1 mask generation function based on the hash function hash as defined
213    /// in [Section B.2.1 of \[RFC8017\]](https://datatracker.ietf.org/doc/pdf/rfc8017)
214    ///
215    /// # Arguments:
216    ///
217    /// *    `mgf_seed`: seed from which mask is generated, an octet string
218    /// *    `mask_len`: intended length in octets of the mask; max length 2 ^ 32
219    ///
220    /// # Returns: 
221    ///
222    /// *    an octet string of length mask_len
223    ///
224    pub fn mgf1(&mut self, 
225        mgf_seed: &[u8], 
226        mask_len: usize
227    ) -> Result<Vec<u8>, Error> {
228        let max_len: usize = u32::MAX.try_into().unwrap();
229        if mask_len > max_len + 1 {
230            return Err(Error::InvalidMaskLength);
231        }
232        
233        let mut octet = BytesMut::with_capacity(mask_len);
234        
235        // ceil (maskLen / hlen) -> (maskLen + hlen - 1) / hlen
236        let iterations = (mask_len + &self.hlen - 1) / &self.hlen ;
237    
238        for counter in 0..iterations {
239            let mut num = BigNum::from_u32(counter as u32)?;
240            let c = i20sp(&mut num, 4).unwrap();
241            
242            // Load the seed into the hash buffer
243            //  Hash(mgf_seed || c)
244            self.hasher.update(&mgf_seed).unwrap();
245            self.hasher.update(c.as_slice()).unwrap();
246            // Digest hash
247            let digest = self.hasher.finish().unwrap().to_vec();
248            octet.extend_from_slice(digest.as_slice());
249        }
250        // Output the leading octets
251        Ok(octet[0..mask_len].to_vec())
252    }
253}
254
255impl VRF_trait for VRF {
256    type Error = Error;
257
258    /// RSA-FDH-VRF prooving algorithm as defined
259    /// in Section 4.1 of [VRF-draft-05](https://tools.ietf.org/pdf/draft-irtf-cfrg-vrf-05)
260    ///
261    /// # Arguments:
262    ///
263    /// *    `secret_key`: RSA private key
264    /// *    `alpha_string`: VRF hash input, an octet string
265    ///
266    /// # Returns:
267    ///
268    /// *    `pi_string`: proof, an octet string of length k
269    ///
270    fn prove(
271        &mut self, 
272        secret_key: &Rsa<Private>, 
273        alpha_string: &[u8]
274    ) -> Result<Vec<u8>, Error> {
275        let one_string: u8 = 0x01;
276        let mut n = secret_key.n().to_owned().unwrap();
277
278        let k = &n.to_vec().len();
279        if !(*k < 4_294_967_296usize) {
280            return Err(Error::InvalidModulusLength);
281        }
282        
283        // mgf1(one_string || i20sp(k, 4) || i20sp(n, k) || alpha_string, k - 1)
284        // create a mutable byte sequence to concatenate entries
285        let mut sequence = BytesMut::new();
286        sequence.extend_from_slice(&[one_string]);
287
288        let mut num = BigNum::from_u32(*k as u32)?;
289        let i2 = i20sp(&mut num, 4).unwrap();
290        sequence.extend_from_slice(i2.as_slice());
291
292        let i3 = i20sp(&mut n, *k).unwrap();
293        sequence.extend_from_slice(i3.as_slice());
294
295        sequence.extend_from_slice(alpha_string);
296
297        let em = self.mgf1(&sequence, k - 1).unwrap();
298        let m = os2ip(&em.as_slice()).unwrap();
299        let mut s = self.rsasp1(secret_key, &m).unwrap();
300        let pi_string = i20sp(&mut s, *k).unwrap();
301
302        Ok(pi_string)
303    }
304
305    /// RSA-FDH-VRF proof to hash algorithm as defined
306    /// in Section 4.2 of [VRF-draft-05](https://tools.ietf.org/pdf/draft-irtf-cfrg-vrf-05)
307    /// 
308    /// # Arguments:
309    ///
310    /// *    `pi_string`: proof, an octet string of length k
311    ///
312    /// # Returns:
313    /// 
314    /// *    `beta_string`: VRF hash output, an octet string of length hLen
315    ///
316    fn proof_to_hash(
317        &mut self, 
318        pi_string: &[u8]
319    ) -> Result<Vec<u8>, Error> {
320        let two_string: u8 = 0x02;
321        self.hasher.update(&[two_string]).unwrap();
322        self.hasher.update(pi_string).unwrap();
323        let beta_string = self.hasher.finish().unwrap().to_vec();
324
325        Ok(beta_string)
326    }
327
328    /// RSA-FDH-VRF verifying algorithm as defined
329    /// in Section 4.3 of [VRF-draft-05](https://tools.ietf.org/pdf/draft-irtf-cfrg-vrf-05)
330    ///
331    /// # Arguments:
332    ///
333    /// *    `public_key`: RSA public key
334    /// *    `alpha_string`: VRF hash input, an octet string
335    /// *    `pi_string`: proof to be verified, an octet string of length n
336    ///
337    /// # Returns:
338    ///
339    /// *   `beta_string`: VRF hash output, an octet string of length hLen
340    ///
341    fn verify(
342        &mut self, 
343        public_key: &Rsa<Public>, 
344        alpha_string: &[u8], 
345        pi_string: &[u8]
346    ) -> Result<Vec<u8>, Error> {
347        let s = os2ip(pi_string).unwrap();
348        let mut m = self.rsavp1(public_key, &s).unwrap();
349        
350        let mut n = public_key.n().to_owned().unwrap();
351        let k = &n.to_vec().len();
352
353        let em = i20sp(&mut m, *k - 1).unwrap();
354        let one_string: u8 = 0x01;
355
356        let mut sequence = BytesMut::new();
357        sequence.extend_from_slice(&[one_string]);
358
359        let mut num = BigNum::from_u32(*k as u32)?;
360        let i2 = i20sp(&mut num, 4).unwrap();
361        sequence.extend_from_slice(i2.as_slice());
362
363        let i3 = i20sp(&mut n, *k).unwrap();
364        sequence.extend_from_slice(i3.as_slice());
365
366        sequence.extend_from_slice(alpha_string);
367
368        let em_ = self.mgf1(&sequence, k - 1).unwrap();
369
370        if em == em_ {
371            Ok(self.proof_to_hash(pi_string).unwrap())
372        } else {
373            return Err(Error::InvalidProof);
374        }
375    } 
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    #[test]
383    fn test_rsasp1() {
384        let mut vrf = VRF::from_suite(VRFCipherSuite::PKI_MGF_MGF1_SHA256).unwrap();
385
386        let pkey = include_bytes!("../test/rsa.pem");
387        let private_key = Rsa::private_key_from_pem(pkey).unwrap();
388
389        // hex encoded -> 'this is a sample message'
390        let alpha = BigNum::from_hex_str("7468697320697320612073616d706c65206d657373616765").unwrap();
391        // signature -> an integer between 0 < n - 1
392        let signature = vrf.rsasp1(&private_key, &alpha).unwrap();
393        
394        let expected_signature = BigNum::from_dec_str("\
395            1776277981368162742811103276847038001291908781090057338317637858700131454\
396            3368853810456104465225789241093347677520344819844115213079786239362994134\
397            5965350441718127796651075868428100553726539595770065215247642182314807705\
398            5539991297172940996514772987752142846280297196586316332720263746969001076\
399            9732878056030898069509511139305651648477603830169517327114345051395234682\
400            9733897544214649098209309949711422768149757935850125331268865861756758528\
401            2933996843972618288453248328023531638084508626181473370553595306726617362\
402            0383437871866902044174482588333308357293433575058467801404176149537871016\
403            058216362423935315679183767657269\
404        ").unwrap();
405        assert_eq!(signature, expected_signature);
406    }
407
408    #[test]
409    fn test_rsavp1() {
410        let mut vrf = VRF::from_suite(VRFCipherSuite::PKI_MGF_MGF1_SHA256).unwrap();
411
412        let key = include_bytes!("../test/rsa.pem.pub");
413        let public_key = Rsa::public_key_from_pem(key).unwrap();
414
415        let signature = BigNum::from_dec_str("\
416            1776277981368162742811103276847038001291908781090057338317637858700131454\
417            3368853810456104465225789241093347677520344819844115213079786239362994134\
418            5965350441718127796651075868428100553726539595770065215247642182314807705\
419            5539991297172940996514772987752142846280297196586316332720263746969001076\
420            9732878056030898069509511139305651648477603830169517327114345051395234682\
421            9733897544214649098209309949711422768149757935850125331268865861756758528\
422            2933996843972618288453248328023531638084508626181473370553595306726617362\
423            0383437871866902044174482588333308357293433575058467801404176149537871016\
424            058216362423935315679183767657269\
425        ").unwrap();
426
427        let message = vrf.rsavp1(&public_key, &signature).unwrap();
428        let expected_message = BigNum::from_hex_str("7468697320697320612073616d706c65206d657373616765").unwrap();
429    
430        assert_eq!(message, expected_message);
431    }
432
433
434    #[test]
435    fn test_mgf1_sha1() {
436        let mut vrf = VRF::from_suite(VRFCipherSuite::PKI_MGF_MGF1_SHA1).unwrap();
437        let seed: &[u8; 32] = &[
438            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
439            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
440            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
441            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
442        ];
443        let mask_len: usize = 32;
444
445        let mask = vrf.mgf1(seed, mask_len).unwrap();
446        let expected_mask = vec![
447            18, 71, 204, 28, 245, 187, 190, 206, 
448            148, 32, 216, 166, 247, 180, 135, 157, 
449            20, 48, 62, 183, 78, 20, 23, 68, 
450            203, 35, 17, 162, 222, 173, 133, 120
451        ];
452
453        assert_eq!(mask, expected_mask);
454    }
455
456    #[test]
457    fn test_mgf1_sha256() {
458        let mut vrf = VRF::from_suite(VRFCipherSuite::PKI_MGF_MGF1_SHA256).unwrap();
459        let seed: &[u8; 32] = &[
460            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
461            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
462            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
463            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
464        ];
465        let mask_len: usize = 32;
466
467        let mask = vrf.mgf1(seed, mask_len).unwrap();
468        let expected_mask = vec![
469            63, 108, 39, 36, 162, 26, 59, 41, 
470            239, 136, 106, 82, 170, 65, 75, 236, 
471            150, 196, 111, 122, 241, 55, 198, 54, 
472            6, 82, 9, 255, 137, 44, 238, 108
473        ];
474
475        assert_eq!(mask, expected_mask);
476    }
477
478    #[test]
479    fn test_prove_sha1() {
480        let mut vrf = VRF::from_suite(VRFCipherSuite::PKI_MGF_MGF1_SHA1).unwrap();
481        
482        let pkey = include_bytes!("../test/rsa.pem");
483        let private_key = Rsa::private_key_from_pem(pkey).unwrap();
484
485        // hex encoded -> 'this is a sample message'
486        let alpha = hex::decode("7468697320697320612073616d706c65206d657373616765").unwrap();
487        
488        let pi = vrf.prove(&private_key, &alpha).unwrap();
489        let pi_string = hex::encode(pi);
490
491        let expected_pi_string = "\
492            6d8b0b748f637f3edc779981fd23ff07d10\
493            f573155f0473262116459310c4a4dde54df\
494            ced09bbfd437bbe776985073624c27ab594\
495            d166b674188b8b760928a4d32e6fdb2e61e\
496            6e51f7e2b10589a13d10bbf97285df54edb\
497            42675a685ea3063df7e3cce2f2c9329936a\
498            489e54168d47e78d5eeddf44e5db8bbe535\
499            0facf272c446a9a22872d382a10e0424c18\
500            6e0709915a33325362ebfbb6caa574877e8\
501            7af5c7a8054d9665055f04d094557887eee\
502            805b7c77f1d221b9d84ad5c8917a480558c\
503            49547d3531687eadd6020254d07949f0999\
504            a1b80a61abfccc4aa278d7fe525866aa2f4\
505            abe2b99083abd7c4ac2043de91e795b04c3\
506            9c76d90b07d0fcf6af6824";
507
508        assert_eq!(pi_string, expected_pi_string);
509    }
510
511    #[test]
512    fn test_prove_sha256() {
513        let mut vrf = VRF::from_suite(VRFCipherSuite::PKI_MGF_MGF1_SHA256).unwrap();
514        
515        let pkey = include_bytes!("../test/rsa.pem");
516        let private_key = Rsa::private_key_from_pem(pkey).unwrap();
517
518        // hex encoded -> 'this is a sample message'
519        let alpha = hex::decode("7468697320697320612073616d706c65206d657373616765").unwrap();
520        
521        let pi = vrf.prove(&private_key, &alpha).unwrap();
522        let pi_string = hex::encode(pi);
523
524        let expected_pi_string = "\
525            6fd6d34a832b37a4ecf7efbb78526311792\
526            7ddd46c3fc1be34609a395916fa873d26ad\
527            d37c41ce275e66b394fb53bae084d7ef420\
528            cd64882e90d0c54303ca832845199d2fbe4\
529            b65aa7b7e350e96b23b9adc2cc4e982b26b\
530            d0d399820f47a7174b0ca09d60f115683fd\
531            c38f193698b215adc234313ad4706d07cf5\
532            a2db9c2eec0a0154d486ae20f7cb05d5ffa\
533            74502b352436e3d8952a093bfb10ef0dcf9\
534            7f68ae1e28fb0a26948cb12d826cdb7632e\
535            06e4f6321a0a4cc106b5d99e9471f53efdf\
536            c89d57fef14561745b08bebb3ef176aa41e\
537            7630cb7444cb0df27606a31917992b11e8d\
538            b2e3b3a5f7182d417cebaa7faa3afbfb575\
539            8e2259fa3cd8aaa86514b3";
540
541        assert_eq!(pi_string, expected_pi_string);
542    }
543
544    #[test]
545    fn test_verify_sha1() {
546        let mut vrf = VRF::from_suite(VRFCipherSuite::PKI_MGF_MGF1_SHA1).unwrap();
547
548        let key = include_bytes!("../test/rsa.pem.pub");
549        let public_key = Rsa::public_key_from_pem(key).unwrap();
550
551        // hex encoded -> 'this is a sample message'
552        let alpha = hex::decode("7468697320697320612073616d706c65206d657373616765").unwrap();
553        let pi = hex::decode("\
554            6d8b0b748f637f3edc779981fd23ff07d10\
555            f573155f0473262116459310c4a4dde54df\
556            ced09bbfd437bbe776985073624c27ab594\
557            d166b674188b8b760928a4d32e6fdb2e61e\
558            6e51f7e2b10589a13d10bbf97285df54edb\
559            42675a685ea3063df7e3cce2f2c9329936a\
560            489e54168d47e78d5eeddf44e5db8bbe535\
561            0facf272c446a9a22872d382a10e0424c18\
562            6e0709915a33325362ebfbb6caa574877e8\
563            7af5c7a8054d9665055f04d094557887eee\
564            805b7c77f1d221b9d84ad5c8917a480558c\
565            49547d3531687eadd6020254d07949f0999\
566            a1b80a61abfccc4aa278d7fe525866aa2f4\
567            abe2b99083abd7c4ac2043de91e795b04c3\
568            9c76d90b07d0fcf6af6824").unwrap();
569        
570        let beta = vrf.verify(&public_key, &alpha, &pi).unwrap();
571        let expected_beta = hex::decode(
572            "3f996d9e247556eaf70518680fc4a9f40a566f52"
573        ).unwrap();
574
575        assert_eq!(beta, expected_beta);
576    }
577
578    #[test]
579    fn test_verify_sha256() {
580        let mut vrf = VRF::from_suite(VRFCipherSuite::PKI_MGF_MGF1_SHA256).unwrap();
581
582        let key = include_bytes!("../test/rsa.pem.pub");
583        let public_key = Rsa::public_key_from_pem(key).unwrap();
584
585        // hex encoded -> 'this is a sample message'
586        let alpha = hex::decode("7468697320697320612073616d706c65206d657373616765").unwrap();
587        let pi = hex::decode("\
588            6fd6d34a832b37a4ecf7efbb78526311792\
589            7ddd46c3fc1be34609a395916fa873d26ad\
590            d37c41ce275e66b394fb53bae084d7ef420\
591            cd64882e90d0c54303ca832845199d2fbe4\
592            b65aa7b7e350e96b23b9adc2cc4e982b26b\
593            d0d399820f47a7174b0ca09d60f115683fd\
594            c38f193698b215adc234313ad4706d07cf5\
595            a2db9c2eec0a0154d486ae20f7cb05d5ffa\
596            74502b352436e3d8952a093bfb10ef0dcf9\
597            7f68ae1e28fb0a26948cb12d826cdb7632e\
598            06e4f6321a0a4cc106b5d99e9471f53efdf\
599            c89d57fef14561745b08bebb3ef176aa41e\
600            7630cb7444cb0df27606a31917992b11e8d\
601            b2e3b3a5f7182d417cebaa7faa3afbfb575\
602            8e2259fa3cd8aaa86514b3").unwrap();
603        
604        let beta = vrf.verify(&public_key, &alpha, &pi).unwrap();
605        let expected_beta = hex::decode(
606            "440a1644d54afc1055f0fbb4c2ef0c3d67abd0e42978a7c196b9758a7340f5a8"
607        ).unwrap();
608
609        assert_eq!(beta, expected_beta);
610    }
611}