mithril_stm/protocol/key_registration/
closed_registration_entry.rs

1use serde::{Deserialize, Serialize, Serializer, ser::SerializeTuple};
2use std::cmp::Ordering;
3use std::hash::Hash;
4
5use crate::{RegisterError, RegistrationEntry, Stake, StmResult, VerificationKeyForConcatenation};
6
7#[cfg(feature = "future_snark")]
8use crate::{LotteryTargetValue, VerificationKeyForSnark};
9
10/// Represents a registration entry of a closed key registration.
11#[derive(PartialEq, Eq, Clone, Debug, Copy, Deserialize)]
12pub struct ClosedRegistrationEntry {
13    verification_key_for_concatenation: VerificationKeyForConcatenation,
14    stake: Stake,
15    #[cfg(feature = "future_snark")]
16    #[serde(skip_serializing_if = "Option::is_none", default)]
17    verification_key_for_snark: Option<VerificationKeyForSnark>,
18    #[cfg(feature = "future_snark")]
19    #[serde(skip_serializing_if = "Option::is_none", default)]
20    lottery_target_value: Option<LotteryTargetValue>,
21}
22
23impl ClosedRegistrationEntry {
24    /// Creates a new closed registration entry.
25    pub fn new(
26        verification_key_for_concatenation: VerificationKeyForConcatenation,
27        stake: Stake,
28        #[cfg(feature = "future_snark")] verification_key_for_snark: Option<
29            VerificationKeyForSnark,
30        >,
31        #[cfg(feature = "future_snark")] lottery_target_value: Option<LotteryTargetValue>,
32    ) -> Self {
33        ClosedRegistrationEntry {
34            verification_key_for_concatenation,
35            stake,
36            #[cfg(feature = "future_snark")]
37            verification_key_for_snark,
38            #[cfg(feature = "future_snark")]
39            lottery_target_value,
40        }
41    }
42
43    /// Gets the verification key for concatenation.
44    pub fn get_verification_key_for_concatenation(&self) -> VerificationKeyForConcatenation {
45        self.verification_key_for_concatenation
46    }
47
48    /// Gets the stake.
49    pub fn get_stake(&self) -> Stake {
50        self.stake
51    }
52
53    #[cfg(feature = "future_snark")]
54    /// Gets the verification key for snark.
55    pub fn get_verification_key_for_snark(&self) -> Option<VerificationKeyForSnark> {
56        self.verification_key_for_snark
57    }
58
59    #[cfg(feature = "future_snark")]
60    /// Gets the lottery target value.
61    pub fn get_lottery_target_value(&self) -> Option<LotteryTargetValue> {
62        self.lottery_target_value
63    }
64
65    /// Converts the registration entry to bytes.
66    /// Uses 96 bytes for the verification key for concatenation and 8 bytes for the stake
67    /// (u64 big-endian).
68    /// #[cfg(feature = "future_snark")] Uses 64 bytes for the verification key for snark and 32
69    /// bytes for the lottery target value
70    /// The order is backward compatible with previous implementations.
71    pub(crate) fn to_bytes(self) -> Vec<u8> {
72        #[cfg(feature = "future_snark")]
73        let capacity = 200;
74        #[cfg(not(feature = "future_snark"))]
75        let capacity = 104;
76
77        let mut result = Vec::with_capacity(capacity);
78        result.extend_from_slice(&self.verification_key_for_concatenation.to_bytes());
79        result.extend_from_slice(&self.stake.to_be_bytes());
80
81        #[cfg(feature = "future_snark")]
82        if let (Some(schnorr_vk), Some(target_value)) =
83            (&self.verification_key_for_snark, &self.lottery_target_value)
84        {
85            result.extend_from_slice(&schnorr_vk.to_bytes());
86            result.extend_from_slice(&target_value.to_bytes());
87        }
88
89        result
90    }
91
92    /// Creates a registration entry from bytes.
93    /// Expects 96 bytes for the verification key for concatenation and 8 bytes for the stake
94    /// (u64 big-endian).
95    /// #[cfg(feature = "future_snark")] Expects 64 bytes for the verification key for snark and 32
96    /// bytes for the lottery target value.
97    /// The order is backward compatible with previous implementations.
98    pub(crate) fn from_bytes(bytes: &[u8]) -> StmResult<Self> {
99        let verification_key_for_concatenation = VerificationKeyForConcatenation::from_bytes(
100            bytes.get(..96).ok_or(RegisterError::SerializationError)?,
101        )?;
102        let mut u64_bytes = [0u8; 8];
103        u64_bytes.copy_from_slice(bytes.get(96..104).ok_or(RegisterError::SerializationError)?);
104        let stake = Stake::from_be_bytes(u64_bytes);
105
106        #[cfg(feature = "future_snark")]
107        let (verification_key_for_snark, lottery_target_value) = {
108            let schnorr_verification_key = bytes
109                .get(104..168)
110                .map(VerificationKeyForSnark::from_bytes)
111                .transpose()?;
112
113            let lottery_target_value =
114                bytes.get(168..200).map(LotteryTargetValue::from_bytes).transpose()?;
115
116            match (schnorr_verification_key, lottery_target_value) {
117                (Some(_), None) | (None, Some(_)) => {
118                    return Err(RegisterError::SerializationError.into());
119                }
120                _ => {}
121            }
122            (schnorr_verification_key, lottery_target_value)
123        };
124
125        Ok(ClosedRegistrationEntry {
126            verification_key_for_concatenation,
127            stake,
128            #[cfg(feature = "future_snark")]
129            verification_key_for_snark,
130            #[cfg(feature = "future_snark")]
131            lottery_target_value,
132        })
133    }
134}
135
136impl Serialize for ClosedRegistrationEntry {
137    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
138        #[cfg(not(feature = "future_snark"))]
139        {
140            let mut tuple = serializer.serialize_tuple(2)?;
141            tuple.serialize_element(&self.verification_key_for_concatenation)?;
142            tuple.serialize_element(&self.stake)?;
143            tuple.end()
144        }
145        #[cfg(feature = "future_snark")]
146        {
147            let has_snark_fields = self.verification_key_for_snark.is_some()
148                && self.lottery_target_value.is_some()
149                && cfg!(feature = "future_snark");
150            let tuples_number = if has_snark_fields { 4 } else { 2 };
151            let mut tuple = serializer.serialize_tuple(tuples_number)?;
152            tuple.serialize_element(&self.verification_key_for_concatenation)?;
153            tuple.serialize_element(&self.stake)?;
154            if has_snark_fields {
155                tuple.serialize_element(&self.verification_key_for_snark)?;
156                tuple.serialize_element(&self.lottery_target_value)?;
157            }
158            tuple.end()
159        }
160    }
161}
162
163/// Converts the registration entry into a closed registration entry for given total stake.
164/// This is where we will compute the lottery target value in the future.
165/// `LotteryTargetValue` is set to (modulus - 1) for now.
166/// TODO: Compute the lottery target value based on the total stake and the entry's stake.
167impl From<(RegistrationEntry, Stake)> for ClosedRegistrationEntry {
168    fn from(entry_total_stake: (RegistrationEntry, Stake)) -> Self {
169        let (entry, _total_stake) = entry_total_stake;
170        #[cfg(feature = "future_snark")]
171        let (schnorr_verification_key, target_value) = {
172            let vk = entry.get_verification_key_for_snark();
173            let target =
174                vk.map(|_| &LotteryTargetValue::default() - &LotteryTargetValue::get_one());
175            (vk, target)
176        };
177
178        ClosedRegistrationEntry::new(
179            entry.get_verification_key_for_concatenation(),
180            entry.get_stake(),
181            #[cfg(feature = "future_snark")]
182            schnorr_verification_key,
183            #[cfg(feature = "future_snark")]
184            target_value,
185        )
186    }
187}
188
189impl Hash for ClosedRegistrationEntry {
190    /// Hashes the registration entry by hashing the stake first, then the verification key.
191    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
192        self.stake.hash(state);
193        self.verification_key_for_concatenation.hash(state);
194        #[cfg(feature = "future_snark")]
195        {
196            self.verification_key_for_snark.hash(state);
197            self.lottery_target_value.hash(state);
198        }
199    }
200
201    fn hash_slice<H: std::hash::Hasher>(data: &[Self], state: &mut H)
202    where
203        Self: Sized,
204    {
205        for piece in data {
206            piece.hash(state)
207        }
208    }
209}
210
211impl PartialOrd for ClosedRegistrationEntry {
212    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
213        Some(std::cmp::Ord::cmp(self, other))
214    }
215}
216
217impl Ord for ClosedRegistrationEntry {
218    /// Orders by stake first, then by Verification key for concatenation.
219    ///
220    /// Note: this ordering intentionally excludes the snark fields
221    /// (`VerificationKeyForSnark`, `LotteryTargetValue`), as we do not need them for ordering
222    /// the Merkle tree leaves.
223    fn cmp(&self, other: &Self) -> Ordering {
224        self.stake.cmp(&other.stake).then(
225            self.verification_key_for_concatenation
226                .cmp(&other.verification_key_for_concatenation),
227        )
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use rand_chacha::ChaCha20Rng;
234    use rand_core::SeedableRng;
235    use std::cmp::Ordering;
236
237    use crate::{
238        VerificationKeyProofOfPossessionForConcatenation, signature_scheme::BlsSigningKey,
239    };
240
241    #[cfg(feature = "future_snark")]
242    use crate::{VerificationKeyForSnark, signature_scheme::SchnorrSigningKey};
243
244    use super::*;
245
246    fn create_closed_registration_entry(
247        rng: &mut ChaCha20Rng,
248        stake: Stake,
249    ) -> ClosedRegistrationEntry {
250        let bls_sk = BlsSigningKey::generate(rng);
251        let bls_pk = VerificationKeyProofOfPossessionForConcatenation::from(&bls_sk);
252
253        #[cfg(feature = "future_snark")]
254        let schnorr_verification_key = {
255            let sk = SchnorrSigningKey::generate(rng);
256            VerificationKeyForSnark::new_from_signing_key(sk.clone())
257        };
258        ClosedRegistrationEntry::new(
259            bls_pk.vk,
260            stake,
261            #[cfg(feature = "future_snark")]
262            Some(schnorr_verification_key),
263            #[cfg(feature = "future_snark")]
264            Some(LotteryTargetValue::get_one()),
265        )
266    }
267
268    #[test]
269    fn test_ord_different_stakes() {
270        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
271
272        let entry_low_stake = create_closed_registration_entry(&mut rng, 100);
273        let entry_high_stake = create_closed_registration_entry(&mut rng, 200);
274
275        assert_eq!(entry_low_stake.cmp(&entry_high_stake), Ordering::Less);
276        assert_eq!(entry_high_stake.cmp(&entry_low_stake), Ordering::Greater);
277    }
278
279    #[test]
280    fn test_ord_same_stake_different_keys() {
281        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
282
283        let entry1 = create_closed_registration_entry(&mut rng, 100);
284        let entry2 = create_closed_registration_entry(&mut rng, 100);
285
286        let cmp_result = entry1.cmp(&entry2);
287        assert!(cmp_result == Ordering::Less || cmp_result == Ordering::Greater);
288
289        assert_eq!(entry2.cmp(&entry1), cmp_result.reverse());
290    }
291
292    mod golden {
293        use super::*;
294
295        #[cfg(not(feature = "future_snark"))]
296        const GOLDEN_BYTES: &[u8; 104] = &[
297            143, 161, 255, 48, 78, 57, 204, 220, 25, 221, 164, 252, 248, 14, 56, 126, 186, 135,
298            228, 188, 145, 181, 52, 200, 97, 99, 213, 46, 0, 199, 193, 89, 187, 88, 29, 135, 173,
299            244, 86, 36, 83, 54, 67, 164, 6, 137, 94, 72, 6, 105, 128, 128, 93, 48, 176, 11, 4,
300            246, 138, 48, 180, 133, 90, 142, 192, 24, 193, 111, 142, 31, 76, 111, 110, 234, 153,
301            90, 208, 192, 31, 124, 95, 102, 49, 158, 99, 52, 220, 165, 94, 251, 68, 69, 121, 16,
302            224, 194, 0, 0, 0, 0, 0, 0, 0, 1,
303        ];
304
305        #[cfg(feature = "future_snark")]
306        const GOLDEN_BYTES: &[u8; 200] = &[
307            143, 161, 255, 48, 78, 57, 204, 220, 25, 221, 164, 252, 248, 14, 56, 126, 186, 135,
308            228, 188, 145, 181, 52, 200, 97, 99, 213, 46, 0, 199, 193, 89, 187, 88, 29, 135, 173,
309            244, 86, 36, 83, 54, 67, 164, 6, 137, 94, 72, 6, 105, 128, 128, 93, 48, 176, 11, 4,
310            246, 138, 48, 180, 133, 90, 142, 192, 24, 193, 111, 142, 31, 76, 111, 110, 234, 153,
311            90, 208, 192, 31, 124, 95, 102, 49, 158, 99, 52, 220, 165, 94, 251, 68, 69, 121, 16,
312            224, 194, 0, 0, 0, 0, 0, 0, 0, 1, 200, 194, 6, 212, 77, 254, 23, 111, 33, 34, 139, 71,
313            131, 196, 108, 13, 217, 75, 187, 131, 158, 77, 197, 163, 30, 123, 151, 237, 157, 232,
314            167, 10, 45, 121, 194, 155, 110, 46, 240, 74, 141, 138, 78, 228, 92, 179, 58, 63, 233,
315            239, 84, 114, 149, 77, 188, 93, 8, 22, 11, 12, 45, 186, 211, 56, 1, 0, 0, 0, 0, 0, 0,
316            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
317        ];
318
319        fn golden_value() -> ClosedRegistrationEntry {
320            let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
321            let bls_sk = BlsSigningKey::generate(&mut rng);
322            let bls_pk = VerificationKeyProofOfPossessionForConcatenation::from(&bls_sk);
323
324            #[cfg(feature = "future_snark")]
325            let schnorr_verification_key = {
326                let sk = SchnorrSigningKey::generate(&mut rng);
327                VerificationKeyForSnark::new_from_signing_key(sk.clone())
328            };
329            ClosedRegistrationEntry::new(
330                bls_pk.vk,
331                1,
332                #[cfg(feature = "future_snark")]
333                Some(schnorr_verification_key),
334                #[cfg(feature = "future_snark")]
335                Some(LotteryTargetValue::get_one()),
336            )
337        }
338
339        #[test]
340        fn golden_conversions() {
341            let value = ClosedRegistrationEntry::from_bytes(GOLDEN_BYTES)
342                .expect("This from bytes should not fail");
343            assert_eq!(golden_value(), value);
344
345            let serialized = ClosedRegistrationEntry::to_bytes(value);
346            let golden_serialized = ClosedRegistrationEntry::to_bytes(golden_value());
347            assert_eq!(golden_serialized, serialized);
348        }
349    }
350}