vrf/
lib.rs

1use ark_ec::AffineCurve;
2use ark_ff::PrimeField;
3use ledger::AccountIndex;
4use message::VrfMessage;
5use mina_p2p_messages::v2::EpochSeed;
6use num::{rational::Ratio, BigInt, ToPrimitive};
7use openmina_node_account::AccountPublicKey;
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 AffineCurve>::BaseField;
23type ScalarField = <CurvePoint as AffineCurve>::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    let vrf_message = VrfMessage::new(global_slot, epoch_seed, delegator_index.as_u64());
118
119    let vrf_message_hash_curve_point = vrf_message.to_group()?;
120
121    let scaled_message_hash =
122        producer_key.secret_multiply_with_curve_point(vrf_message_hash_curve_point);
123
124    Ok(VrfOutput::new(vrf_message, scaled_message_hash))
125}
126
127/// Evaluate vrf with a specific input. Used by the block producer
128pub fn evaluate_vrf(vrf_input: VrfEvaluationInput) -> VrfResult<VrfEvaluationOutput> {
129    let VrfEvaluationInput {
130        producer_key,
131        global_slot,
132        epoch_seed,
133        delegator_index,
134        delegated_stake,
135        total_currency,
136        account_pub_key,
137    } = vrf_input;
138
139    let vrf_output = calculate_vrf(&producer_key, epoch_seed, global_slot, &delegator_index)?;
140
141    let value = vrf_output.truncated().into_repr();
142    let threshold = Threshold::new(delegated_stake, total_currency);
143
144    if threshold.threshold_met(value) {
145        Ok(VrfEvaluationOutput::SlotWon(VrfWonSlot {
146            producer: producer_key.public.into(),
147            vrf_output: Box::new(vrf_output),
148            winner_account: account_pub_key,
149            global_slot,
150            account_index: delegator_index,
151            value_with_threshold: None.or_else(|| {
152                Some((
153                    self::threshold::get_fractional(value).to_f64()?,
154                    threshold.threshold_rational.to_f64()?,
155                ))
156            }),
157        }))
158    } else {
159        Ok(VrfEvaluationOutput::SlotLost(global_slot))
160    }
161}
162
163pub fn keypair_from_bs58_string(str: &str) -> Keypair {
164    let mut secret_hex_vec = bs58::decode(str).into_vec().unwrap();
165    secret_hex_vec = secret_hex_vec[2..secret_hex_vec.len() - 4].to_vec();
166    secret_hex_vec.reverse();
167    let secret_hex = hex::encode(secret_hex_vec);
168    Keypair::from_hex(&secret_hex).unwrap()
169}
170
171#[cfg(test)]
172mod test {
173    use std::str::FromStr;
174
175    use ledger::AccountIndex;
176    use num::BigInt;
177
178    use mina_p2p_messages::{
179        bigint::BigInt as MinaBigInt,
180        v2::{EpochSeed, MinaBaseEpochSeedStableV1},
181    };
182    use openmina_node_account::AccountSecretKey;
183
184    use crate::{genesis_vrf, keypair_from_bs58_string, VrfEvaluationInput, VrfEvaluationOutput};
185
186    use super::evaluate_vrf;
187
188    #[test]
189    fn test_genesis_vrf() {
190        let out = genesis_vrf(EpochSeed::from(MinaBaseEpochSeedStableV1(
191            MinaBigInt::zero(),
192        )))
193        .unwrap();
194        let expected = "48H9Qk4D6RzS9kAJQX9HCDjiJ5qLiopxgxaS6xbDCWNaKQMQ9Y4C";
195        assert_eq!(expected, out.to_string());
196    }
197
198    #[test]
199    fn test_evaluate_vrf_lost_slot() {
200        let vrf_input = VrfEvaluationInput {
201            producer_key: keypair_from_bs58_string(
202                "EKEEpMELfQkMbJDt2fB4cFXKwSf1x4t7YD4twREy5yuJ84HBZtF9",
203            ),
204            epoch_seed: EpochSeed::from_str("2va9BGv9JrLTtrzZttiEMDYw1Zj6a6EHzXjmP9evHDTG3oEquURA")
205                .unwrap(),
206            global_slot: 518,
207            delegator_index: AccountIndex(2),
208            delegated_stake: BigInt::from_str("1000000000000000")
209                .expect("Cannot convert to BigInt"),
210            total_currency: BigInt::from_str("6000000000001000").expect("Cannot convert to BigInt"),
211            account_pub_key: AccountSecretKey::genesis_producer().public_key(),
212        };
213        let evaluation_result = evaluate_vrf(vrf_input.clone()).expect("Failed to evaluate vrf");
214        assert_eq!(
215            evaluation_result,
216            VrfEvaluationOutput::SlotLost(vrf_input.global_slot)
217        )
218    }
219
220    #[test]
221    fn test_evaluate_vrf_won_slot() {
222        let vrf_input = VrfEvaluationInput {
223            producer_key: keypair_from_bs58_string(
224                "EKEEpMELfQkMbJDt2fB4cFXKwSf1x4t7YD4twREy5yuJ84HBZtF9",
225            ),
226            epoch_seed: EpochSeed::from_str("2va9BGv9JrLTtrzZttiEMDYw1Zj6a6EHzXjmP9evHDTG3oEquURA")
227                .unwrap(),
228            global_slot: 6,
229            delegator_index: AccountIndex(2),
230            delegated_stake: BigInt::from_str("1000000000000000")
231                .expect("Cannot convert to BigInt"),
232            total_currency: BigInt::from_str("6000000000001000").expect("Cannot convert to BigInt"),
233            account_pub_key: AccountSecretKey::genesis_producer().public_key(),
234        };
235
236        let evaluation_result = evaluate_vrf(vrf_input).expect("Failed to evaluate vrf");
237
238        if let VrfEvaluationOutput::SlotWon(won_slot) = evaluation_result {
239            assert_eq!(
240                "48HHFYbaz4d7XkJpWWJw5jN1vEBfPvU31nsX4Ljn74jDo3WyTojL",
241                won_slot.vrf_output.to_string()
242            );
243            assert_eq!(0.16978997004532187, won_slot.vrf_output.fractional());
244            assert_eq!(
245                "B62qrztYfPinaKqpXaYGY6QJ3SSW2NNKs7SajBLF1iFNXW9BoALN2Aq",
246                won_slot.producer.to_string()
247            );
248        } else {
249            panic!("Slot should have been won!")
250        }
251
252        // assert_eq!(expected, evaluation_result)
253    }
254
255    #[test]
256    #[ignore]
257    fn test_slot_calculation_time_big_producer() {
258        let start = redux::Instant::now();
259        for i in 1..14403 {
260            let vrf_input = VrfEvaluationInput {
261                producer_key: keypair_from_bs58_string(
262                    "EKEEpMELfQkMbJDt2fB4cFXKwSf1x4t7YD4twREy5yuJ84HBZtF9",
263                ),
264                epoch_seed: EpochSeed::from_str(
265                    "2va9BGv9JrLTtrzZttiEMDYw1Zj6a6EHzXjmP9evHDTG3oEquURA",
266                )
267                .unwrap(),
268                global_slot: 6,
269                delegator_index: AccountIndex(i),
270                delegated_stake: BigInt::from_str("1000000000000000")
271                    .expect("Cannot convert to BigInt"),
272                total_currency: BigInt::from_str("6000000000001000")
273                    .expect("Cannot convert to BigInt"),
274                account_pub_key: AccountSecretKey::genesis_producer().public_key(),
275            };
276            let _ = evaluate_vrf(vrf_input).expect("Failed to evaluate VRF");
277            if i % 100 == 0 {
278                println!("Delegators evaluated: {}", i);
279            }
280        }
281        let elapsed = start.elapsed();
282        println!("Duration: {}", elapsed.as_secs());
283    }
284
285    #[test]
286    #[ignore]
287    fn test_first_winning_slot() {
288        for i in 0..7000 {
289            let vrf_input = VrfEvaluationInput {
290                producer_key: keypair_from_bs58_string(
291                    "EKEEpMELfQkMbJDt2fB4cFXKwSf1x4t7YD4twREy5yuJ84HBZtF9",
292                ),
293                epoch_seed: EpochSeed::from_str(
294                    "2va9BGv9JrLTtrzZttiEMDYw1Zj6a6EHzXjmP9evHDTG3oEquURA",
295                )
296                .unwrap(),
297                global_slot: i,
298                delegator_index: AccountIndex(2),
299                delegated_stake: BigInt::from_str("1000000000000000")
300                    .expect("Cannot convert to BigInt"),
301                total_currency: BigInt::from_str("6000000000001000")
302                    .expect("Cannot convert to BigInt"),
303                account_pub_key: AccountSecretKey::genesis_producer().public_key(),
304            };
305            let evaluation_result =
306                evaluate_vrf(vrf_input.clone()).expect("Failed to evaluate vrf");
307            if evaluation_result != VrfEvaluationOutput::SlotLost(vrf_input.global_slot) {
308                println!("{:?}", evaluation_result);
309            }
310        }
311    }
312}