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
55pub(crate) type BigInt256 = BigInt<4>;
57
58pub(crate) type BigInt2048 = BigInt<32>;
60pub(crate) type BigRational2048 = Ratio<BigInt2048>;
61
62pub(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
102pub 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
110fn 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
129pub 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 }
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}