vrf/
lib.rs

1use ark_ec::{AffineRepr, CurveGroup};
2use ark_ff::PrimeField;
3use ledger::AccountIndex;
4use message::VrfMessage;
5use mina_node_account::AccountPublicKey;
6use mina_p2p_messages::v2::EpochSeed;
7use num::{rational::Ratio, BigInt, ToPrimitive};
8use output::VrfOutput;
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12use mina_curves::pasta::curves::pallas::Pallas as CurvePoint;
13use mina_signer::Keypair;
14use threshold::Threshold;
15
16mod message;
17pub mod output;
18mod serialize;
19mod threshold;
20
21type VrfResult<T> = std::result::Result<T, VrfError>;
22type BaseField = <CurvePoint as AffineRepr>::BaseField;
23type ScalarField = <CurvePoint as AffineRepr>::ScalarField;
24
25#[derive(Error, Debug)]
26pub enum VrfError {
27    #[error("Failed to decode field from hex string: {0}")]
28    HexDecodeError(#[from] hex::FromHexError),
29
30    #[error("Failed to parse decimal big integer from string: {0}")]
31    BigIntParseError(#[from] num::bigint::ParseBigIntError),
32
33    #[error("Field conversion error: {0}")]
34    FieldHelpersError(#[from] o1_utils::field_helpers::FieldHelpersError),
35
36    #[error("Failed to decode the base58 string: {0}")]
37    Base58DecodeError(#[from] bs58::decode::Error),
38
39    #[error("Scalar field does not exists from repr: {0:?}")]
40    ScalarFieldFromReprError(BaseField),
41
42    #[error("Cannot convert rational to f64")]
43    RationalToF64,
44
45    #[error("Cannot find a curve point for {0}")]
46    ToGroupError(BaseField),
47
48    #[error("The vrf_output is missing from the witness")]
49    NoOutputError,
50
51    #[error("The witness is invalid")]
52    IvalidWitness,
53}
54
55/// 256 bits
56pub(crate) type BigInt256 = BigInt<4>;
57
58/// 2048 bits
59pub(crate) type BigInt2048 = BigInt<32>;
60pub(crate) type BigRational2048 = Ratio<BigInt2048>;
61
62/// 4096 bits
63pub(crate) type BigInt4096 = BigInt<64>;
64pub(crate) type BigRational4096 = Ratio<BigInt4096>;
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct VrfWonSlot {
68    pub producer: AccountPublicKey,
69    pub winner_account: AccountPublicKey,
70    pub global_slot: u32,
71    pub account_index: AccountIndex,
72    pub vrf_output: Box<VrfOutput>,
73    pub value_with_threshold: Option<(f64, f64)>,
74}
75
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub enum VrfEvaluationOutput {
78    SlotWon(VrfWonSlot),
79    SlotLost(u32),
80}
81
82impl VrfEvaluationOutput {
83    pub fn global_slot(&self) -> u32 {
84        match self {
85            Self::SlotWon(v) => v.global_slot,
86            Self::SlotLost(v) => *v,
87        }
88    }
89}
90
91#[derive(Debug, Clone, PartialEq)]
92pub struct VrfEvaluationInput {
93    pub producer_key: Keypair,
94    pub global_slot: u32,
95    pub epoch_seed: EpochSeed,
96    pub account_pub_key: AccountPublicKey,
97    pub delegator_index: AccountIndex,
98    pub delegated_stake: BigInt,
99    pub total_currency: BigInt,
100}
101
102/// Generates the VRF output for the genesis block
103pub fn genesis_vrf(epoch_seed: EpochSeed) -> VrfResult<VrfOutput> {
104    let genesis_keypair =
105        keypair_from_bs58_string("EKFKgDtU3rcuFTVSEpmpXSkukjmX4cKefYREi6Sdsk7E7wsT7KRw");
106
107    calculate_vrf(&genesis_keypair, epoch_seed, 0, &AccountIndex(0))
108}
109
110/// Calculates the VRF output
111fn calculate_vrf(
112    producer_key: &Keypair,
113    epoch_seed: EpochSeed,
114    global_slot: u32,
115    delegator_index: &AccountIndex,
116) -> VrfResult<VrfOutput> {
117    use core::ops::Mul;
118    let vrf_message = VrfMessage::new(global_slot, epoch_seed, delegator_index.as_u64());
119
120    let vrf_message_hash_curve_point = vrf_message.to_group()?;
121
122    let scaled_message_hash = vrf_message_hash_curve_point
123        .mul(producer_key.secret.clone().into_scalar())
124        .into_affine();
125
126    Ok(VrfOutput::new(vrf_message, scaled_message_hash))
127}
128
129/// Evaluate vrf with a specific input. Used by the block producer
130pub fn evaluate_vrf(vrf_input: VrfEvaluationInput) -> VrfResult<VrfEvaluationOutput> {
131    let VrfEvaluationInput {
132        producer_key,
133        global_slot,
134        epoch_seed,
135        delegator_index,
136        delegated_stake,
137        total_currency,
138        account_pub_key,
139    } = vrf_input;
140
141    let vrf_output = calculate_vrf(&producer_key, epoch_seed, global_slot, &delegator_index)?;
142
143    let value = vrf_output.truncated().into_bigint();
144    let threshold = Threshold::new(delegated_stake, total_currency);
145
146    if threshold.threshold_met(value) {
147        Ok(VrfEvaluationOutput::SlotWon(VrfWonSlot {
148            producer: producer_key.public.into(),
149            vrf_output: Box::new(vrf_output),
150            winner_account: account_pub_key,
151            global_slot,
152            account_index: delegator_index,
153            value_with_threshold: None.or_else(|| {
154                Some((
155                    self::threshold::get_fractional(value).to_f64()?,
156                    threshold.threshold_rational.to_f64()?,
157                ))
158            }),
159        }))
160    } else {
161        Ok(VrfEvaluationOutput::SlotLost(global_slot))
162    }
163}
164
165pub fn keypair_from_bs58_string(str: &str) -> Keypair {
166    let mut secret_hex_vec = bs58::decode(str).into_vec().unwrap();
167    secret_hex_vec = secret_hex_vec[2..secret_hex_vec.len() - 4].to_vec();
168    secret_hex_vec.reverse();
169    let secret_hex = hex::encode(secret_hex_vec);
170    Keypair::from_hex(&secret_hex).unwrap()
171}
172
173#[cfg(test)]
174mod test {
175    use std::str::FromStr;
176
177    use ledger::AccountIndex;
178    use num::BigInt;
179
180    use mina_node_account::AccountSecretKey;
181    use mina_p2p_messages::{
182        bigint::BigInt as MinaBigInt,
183        v2::{EpochSeed, MinaBaseEpochSeedStableV1},
184    };
185
186    use crate::{genesis_vrf, keypair_from_bs58_string, VrfEvaluationInput, VrfEvaluationOutput};
187
188    use super::evaluate_vrf;
189
190    #[test]
191    fn test_genesis_vrf() {
192        let out = genesis_vrf(EpochSeed::from(MinaBaseEpochSeedStableV1(
193            MinaBigInt::zero(),
194        )))
195        .unwrap();
196        let expected = "48H9Qk4D6RzS9kAJQX9HCDjiJ5qLiopxgxaS6xbDCWNaKQMQ9Y4C";
197        assert_eq!(expected, out.to_string());
198    }
199
200    #[test]
201    fn test_evaluate_vrf_lost_slot() {
202        let vrf_input = VrfEvaluationInput {
203            producer_key: keypair_from_bs58_string(
204                "EKEEpMELfQkMbJDt2fB4cFXKwSf1x4t7YD4twREy5yuJ84HBZtF9",
205            ),
206            epoch_seed: EpochSeed::from_str("2va9BGv9JrLTtrzZttiEMDYw1Zj6a6EHzXjmP9evHDTG3oEquURA")
207                .unwrap(),
208            global_slot: 518,
209            delegator_index: AccountIndex(2),
210            delegated_stake: BigInt::from_str("1000000000000000")
211                .expect("Cannot convert to BigInt"),
212            total_currency: BigInt::from_str("6000000000001000").expect("Cannot convert to BigInt"),
213            account_pub_key: AccountSecretKey::genesis_producer().public_key(),
214        };
215        let evaluation_result = evaluate_vrf(vrf_input.clone()).expect("Failed to evaluate vrf");
216        assert_eq!(
217            evaluation_result,
218            VrfEvaluationOutput::SlotLost(vrf_input.global_slot)
219        )
220    }
221
222    #[test]
223    fn test_evaluate_vrf_won_slot() {
224        let vrf_input = VrfEvaluationInput {
225            producer_key: keypair_from_bs58_string(
226                "EKEEpMELfQkMbJDt2fB4cFXKwSf1x4t7YD4twREy5yuJ84HBZtF9",
227            ),
228            epoch_seed: EpochSeed::from_str("2va9BGv9JrLTtrzZttiEMDYw1Zj6a6EHzXjmP9evHDTG3oEquURA")
229                .unwrap(),
230            global_slot: 6,
231            delegator_index: AccountIndex(2),
232            delegated_stake: BigInt::from_str("1000000000000000")
233                .expect("Cannot convert to BigInt"),
234            total_currency: BigInt::from_str("6000000000001000").expect("Cannot convert to BigInt"),
235            account_pub_key: AccountSecretKey::genesis_producer().public_key(),
236        };
237
238        let evaluation_result = evaluate_vrf(vrf_input).expect("Failed to evaluate vrf");
239
240        if let VrfEvaluationOutput::SlotWon(won_slot) = evaluation_result {
241            assert_eq!(
242                "48HHFYbaz4d7XkJpWWJw5jN1vEBfPvU31nsX4Ljn74jDo3WyTojL",
243                won_slot.vrf_output.to_string()
244            );
245            assert_eq!(0.16978997004532187, won_slot.vrf_output.fractional());
246            assert_eq!(
247                "B62qrztYfPinaKqpXaYGY6QJ3SSW2NNKs7SajBLF1iFNXW9BoALN2Aq",
248                won_slot.producer.to_string()
249            );
250        } else {
251            panic!("Slot should have been won!")
252        }
253
254        // assert_eq!(expected, evaluation_result)
255    }
256
257    #[test]
258    #[ignore]
259    fn test_slot_calculation_time_big_producer() {
260        let start = redux::Instant::now();
261        for i in 1..14403 {
262            let vrf_input = VrfEvaluationInput {
263                producer_key: keypair_from_bs58_string(
264                    "EKEEpMELfQkMbJDt2fB4cFXKwSf1x4t7YD4twREy5yuJ84HBZtF9",
265                ),
266                epoch_seed: EpochSeed::from_str(
267                    "2va9BGv9JrLTtrzZttiEMDYw1Zj6a6EHzXjmP9evHDTG3oEquURA",
268                )
269                .unwrap(),
270                global_slot: 6,
271                delegator_index: AccountIndex(i),
272                delegated_stake: BigInt::from_str("1000000000000000")
273                    .expect("Cannot convert to BigInt"),
274                total_currency: BigInt::from_str("6000000000001000")
275                    .expect("Cannot convert to BigInt"),
276                account_pub_key: AccountSecretKey::genesis_producer().public_key(),
277            };
278            let _ = evaluate_vrf(vrf_input).expect("Failed to evaluate VRF");
279            if i % 100 == 0 {
280                println!("Delegators evaluated: {}", i);
281            }
282        }
283        let elapsed = start.elapsed();
284        println!("Duration: {}", elapsed.as_secs());
285    }
286
287    #[test]
288    #[ignore]
289    fn test_first_winning_slot() {
290        for i in 0..7000 {
291            let vrf_input = VrfEvaluationInput {
292                producer_key: keypair_from_bs58_string(
293                    "EKEEpMELfQkMbJDt2fB4cFXKwSf1x4t7YD4twREy5yuJ84HBZtF9",
294                ),
295                epoch_seed: EpochSeed::from_str(
296                    "2va9BGv9JrLTtrzZttiEMDYw1Zj6a6EHzXjmP9evHDTG3oEquURA",
297                )
298                .unwrap(),
299                global_slot: i,
300                delegator_index: AccountIndex(2),
301                delegated_stake: BigInt::from_str("1000000000000000")
302                    .expect("Cannot convert to BigInt"),
303                total_currency: BigInt::from_str("6000000000001000")
304                    .expect("Cannot convert to BigInt"),
305                account_pub_key: AccountSecretKey::genesis_producer().public_key(),
306            };
307            let evaluation_result =
308                evaluate_vrf(vrf_input.clone()).expect("Failed to evaluate vrf");
309            if evaluation_result != VrfEvaluationOutput::SlotLost(vrf_input.global_slot) {
310                println!("{:?}", evaluation_result);
311            }
312        }
313    }
314}