1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/// This contains the current evaluation methods for the VRF and its link to
/// the stake distribution
use crate::chaineval::PraosNonce;
use crate::date::SlotId;
use crate::setting::ActiveSlotsCoeff;
use crate::stake::PercentStake;
use chain_crypto::{
    vrf_evaluate_and_prove, vrf_verified_get_output, vrf_verify, PublicKey, RistrettoGroup2HashDh,
    SecretKey, VerifiableRandomFunction, VrfVerification,
};
use rand_core::OsRng;

/// Threshold between 0.0 and 1.0
#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub struct Threshold(f64);

impl Threshold {
    pub fn from_u256(v: &[u8]) -> Self {
        assert_eq!(v.len(), 32);
        // TODO, only consider the highest part
        let v64 = (v[0] as u64) << 56
            | (v[1] as u64) << 48
            | (v[2] as u64) << 40
            | (v[3] as u64) << 32
            | (v[4] as u64) << 24
            | (v[5] as u64) << 16
            | (v[6] as u64) << 8
            | (v[7] as u64);
        Threshold((v64 as f64) / 18_446_744_073_709_551_616.0)
    }
}

/// previous epoch nonce and the slotid encoded in big endian
struct Input([u8; 36]);

impl Input {
    /// Create an Input from previous epoch nonce and the current slotid
    fn create(epoch_nonce: &PraosNonce, slotid: SlotId) -> Self {
        let mut input = [0u8; 36];
        input[0..32].copy_from_slice(epoch_nonce.as_ref());
        input[32..].copy_from_slice(&slotid.to_le_bytes());
        Input(input)
    }
}

/// Witness
pub type Witness = <RistrettoGroup2HashDh as VerifiableRandomFunction>::VerifiedRandomOutput;
pub type WitnessOutput = <RistrettoGroup2HashDh as VerifiableRandomFunction>::RandomOutput;

pub struct VrfEvaluator<'a> {
    pub stake: PercentStake,
    pub nonce: &'a PraosNonce,
    pub slot_id: SlotId,
    pub active_slots_coeff: ActiveSlotsCoeff,
}

pub(crate) fn witness_to_nonce(witness: &Witness) -> PraosNonce {
    let r = vrf_verified_get_output::<RistrettoGroup2HashDh>(witness);
    get_nonce(&r)
}

#[derive(Clone, Debug)]
pub enum VrfEvalFailure {
    ProofVerificationFailed,
    ThresholdNotMet {
        vrf_value: f64,
        stake_threshold: f64,
    },
}

impl<'a> VrfEvaluator<'a> {
    /// Evaluate if the threshold is above for a given input for the key and the associated stake
    ///
    /// On threshold success, the witness is returned, otherwise None is returned
    pub fn evaluate(&self, key: &SecretKey<RistrettoGroup2HashDh>) -> Option<Witness> {
        let input = Input::create(self.nonce, self.slot_id);
        let csprng = OsRng;
        let vr = vrf_evaluate_and_prove(key, &input.0, csprng);
        let r = vrf_verified_get_output::<RistrettoGroup2HashDh>(&vr);
        let t = get_threshold(&input, &r);
        if above_stake_threshold(t, &self.stake, self.active_slots_coeff) {
            Some(vr)
        } else {
            None
        }
    }

    /// verify that the witness pass the threshold for this witness for a given
    /// key and its associated stake.
    ///
    /// On success, the nonce is returned, otherwise None is returned
    pub fn verify(
        &self,
        key: &PublicKey<RistrettoGroup2HashDh>,
        witness: &'a Witness,
    ) -> Result<PraosNonce, VrfEvalFailure> {
        let input = Input::create(self.nonce, self.slot_id);
        if vrf_verify(key, &input.0, witness) == VrfVerification::Success {
            let r = vrf_verified_get_output::<RistrettoGroup2HashDh>(witness);
            // compare threshold against phi-adjusted-stake
            let threshold = get_threshold(&input, &r);
            let phi_stake = phi(self.active_slots_coeff, &self.stake);
            if threshold < phi_stake {
                Ok(get_nonce(&r))
            } else {
                Err(VrfEvalFailure::ThresholdNotMet {
                    vrf_value: threshold.0,
                    stake_threshold: phi_stake.0,
                })
            }
        } else {
            Err(VrfEvalFailure::ProofVerificationFailed)
        }
    }
}

fn above_stake_threshold(
    threshold: Threshold,
    stake: &PercentStake,
    active_slots_coeff: ActiveSlotsCoeff,
) -> bool {
    threshold < phi(active_slots_coeff, stake)
}

fn phi(active_slots_coeff: ActiveSlotsCoeff, rs: &PercentStake) -> Threshold {
    let t = rs.as_float();
    let f: f64 = active_slots_coeff.into();
    Threshold(1.0 - (1.0 - f).powf(t))
}

const DOMAIN_NONCE: &[u8] = b"NONCE";
const DOMAIN_THRESHOLD: &[u8] = b"TEST";

fn get_threshold(input: &Input, os: &WitnessOutput) -> Threshold {
    let out = os.to_output(&input.0, DOMAIN_THRESHOLD);
    // read as big endian 64 bits values from left to right.
    Threshold::from_u256(out.as_ref())
}

fn get_nonce(os: &WitnessOutput) -> PraosNonce {
    let mut nonce = [0u8; 32];
    let out = os.to_output(&[], DOMAIN_NONCE);
    nonce.copy_from_slice(out.as_ref());
    PraosNonce::from_output_array(nonce)
}