vrf/
output.rs

1use ark_ec::short_weierstrass_jacobian::GroupAffine;
2use ark_ff::{BigInteger, BigInteger256, PrimeField};
3use ledger::{proofs::transaction::field_to_bits, AppendToInputs, ToInputs};
4use mina_p2p_messages::v2::ConsensusVrfOutputTruncatedStableV1;
5use num::{BigInt, BigRational, One, ToPrimitive};
6use o1_utils::FieldHelpers;
7use poseidon::hash::params::MINA_VRF_OUTPUT;
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10
11use crate::{BaseField, BigInt2048, ScalarField};
12
13use super::serialize::{ark_deserialize, ark_serialize};
14
15use super::{message::VrfMessage, CurvePoint};
16
17#[derive(Clone, Debug)]
18pub struct VrfOutputHashInput {
19    message: VrfMessage,
20    g: CurvePoint,
21}
22
23impl VrfOutputHashInput {
24    pub fn new(message: VrfMessage, g: CurvePoint) -> Self {
25        Self { message, g }
26    }
27}
28
29impl ToInputs for VrfOutputHashInput {
30    fn to_inputs(&self, inputs: &mut poseidon::hash::Inputs) {
31        let Self {
32            message,
33            g: GroupAffine { x, y, .. },
34        } = self;
35
36        inputs.append(message);
37        inputs.append(x);
38        inputs.append(y);
39    }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
43pub struct VrfOutput {
44    message: VrfMessage,
45    #[serde(serialize_with = "ark_serialize", deserialize_with = "ark_deserialize")]
46    output: CurvePoint,
47}
48
49impl VrfOutput {
50    pub fn new(message: VrfMessage, output: CurvePoint) -> Self {
51        Self { message, output }
52    }
53
54    pub fn raw(&self) -> CurvePoint {
55        self.output
56    }
57
58    pub fn hash(&self) -> BaseField {
59        let hash_input = VrfOutputHashInput::new(self.message.clone(), self.output);
60        hash_input.hash_with_param(&MINA_VRF_OUTPUT)
61    }
62
63    pub fn truncated(&self) -> ScalarField {
64        let hash = self.hash();
65        let bits = field_to_bits::<_, 256>(hash);
66
67        let repr = BigInteger256::from_bits_le(&bits[..bits.len() - 3]);
68        ScalarField::from_repr(repr).unwrap()
69    }
70
71    pub fn truncated_with_prefix_and_checksum(&self) -> Vec<u8> {
72        let mut output_bytes = Vec::new();
73        let prefix = vec![0x15, 0x20];
74
75        output_bytes.extend(prefix);
76
77        output_bytes.extend(self.truncated().to_bytes());
78
79        // checksum
80        let checksum_hash = Sha256::digest(&Sha256::digest(&output_bytes[..])[..]);
81        output_bytes.extend(&checksum_hash[..4]);
82
83        output_bytes
84    }
85
86    pub fn fractional(&self) -> f64 {
87        // ocaml:   Bignum_bigint.(shift_left one length_in_bits))
88        //          where: length_in_bits = Int.min 256 (Field.size_in_bits - 2)
89        //                 Field.size_in_bits = 255
90        let two_tpo_256 = BigInt::one() << 253u32;
91
92        let vrf_out: BigInt2048 = BigInt2048::from_bytes_be(
93            num::bigint::Sign::Plus,
94            &self.truncated().into_repr().to_bytes_be(),
95        );
96
97        BigRational::new(vrf_out, two_tpo_256).to_f64().unwrap()
98    }
99
100    pub fn to_base_58(&self) -> String {
101        let bytes = self.truncated_with_prefix_and_checksum();
102        bs58::encode(bytes).into_string()
103    }
104}
105
106impl std::fmt::Display for VrfOutput {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        let encoded = self.to_base_58();
109        write!(f, "{encoded}")
110    }
111}
112
113impl From<&VrfOutput> for ConsensusVrfOutputTruncatedStableV1 {
114    fn from(value: &VrfOutput) -> Self {
115        let bytes = value.truncated().to_bytes();
116        Self(bytes.into())
117    }
118}
119
120impl From<VrfOutput> for ConsensusVrfOutputTruncatedStableV1 {
121    fn from(value: VrfOutput) -> Self {
122        Self::from(&value)
123    }
124}
125
126#[cfg(test)]
127mod test {
128    use mina_p2p_messages::v2::ConsensusVrfOutputTruncatedStableV1;
129
130    use mina_p2p_messages::{
131        bigint::BigInt as MinaBigInt,
132        v2::{EpochSeed, MinaBaseEpochSeedStableV1},
133    };
134
135    use crate::{genesis_vrf, output::VrfOutput};
136
137    #[test]
138    fn test_serialization() {
139        let vrf_output = genesis_vrf(EpochSeed::from(MinaBaseEpochSeedStableV1(
140            MinaBigInt::zero(),
141        )))
142        .unwrap();
143
144        let serialized = serde_json::to_string(&vrf_output).unwrap();
145        let deserialized: VrfOutput = serde_json::from_str(&serialized).unwrap();
146
147        assert_eq!(vrf_output, deserialized);
148    }
149
150    #[test]
151    fn test_conv_to_mina_type() {
152        let vrf_output = genesis_vrf(EpochSeed::from(MinaBaseEpochSeedStableV1(
153            MinaBigInt::zero(),
154        )))
155        .unwrap();
156
157        let converted = ConsensusVrfOutputTruncatedStableV1::from(vrf_output);
158        let converted_string = serde_json::to_string_pretty(&converted).unwrap();
159        let converted_string_deser: String = serde_json::from_str(&converted_string).unwrap();
160        let expected = String::from("39cyg4ZmMtnb_aFUIerNAoAJV8qtkfOpq0zFzPspjgM=");
161
162        assert_eq!(expected, converted_string_deser);
163    }
164}