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
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 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
127pub 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 }
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}