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 one 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 = vk.map(|_| LotteryTargetValue::get_one());
174            (vk, target)
175        };
176
177        ClosedRegistrationEntry::new(
178            entry.get_verification_key_for_concatenation(),
179            entry.get_stake(),
180            #[cfg(feature = "future_snark")]
181            schnorr_verification_key,
182            #[cfg(feature = "future_snark")]
183            target_value,
184        )
185    }
186}
187
188impl Hash for ClosedRegistrationEntry {
189    /// Hashes the registration entry by hashing the stake first, then the verification key.
190    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
191        self.stake.hash(state);
192        self.verification_key_for_concatenation.hash(state);
193        #[cfg(feature = "future_snark")]
194        {
195            self.verification_key_for_snark.hash(state);
196            self.lottery_target_value.hash(state);
197        }
198    }
199
200    fn hash_slice<H: std::hash::Hasher>(data: &[Self], state: &mut H)
201    where
202        Self: Sized,
203    {
204        for piece in data {
205            piece.hash(state)
206        }
207    }
208}
209
210impl PartialOrd for ClosedRegistrationEntry {
211    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
212        Some(std::cmp::Ord::cmp(self, other))
213    }
214}
215
216impl Ord for ClosedRegistrationEntry {
217    /// Orders by stake first, then by Verification key for concatenation.
218    ///
219    /// Note: this ordering intentionally excludes the snark fields
220    /// (`VerificationKeyForSnark`, `LotteryTargetValue`), as we do not need them for ordering
221    /// the Merkle tree leaves.
222    fn cmp(&self, other: &Self) -> Ordering {
223        self.stake.cmp(&other.stake).then(
224            self.verification_key_for_concatenation
225                .cmp(&other.verification_key_for_concatenation),
226        )
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use rand_chacha::ChaCha20Rng;
233    use rand_core::SeedableRng;
234    use std::cmp::Ordering;
235
236    use crate::{
237        VerificationKeyProofOfPossessionForConcatenation, signature_scheme::BlsSigningKey,
238    };
239
240    #[cfg(feature = "future_snark")]
241    use crate::{VerificationKeyForSnark, signature_scheme::SchnorrSigningKey};
242
243    use super::*;
244
245    fn create_closed_registration_entry(
246        rng: &mut ChaCha20Rng,
247        stake: Stake,
248    ) -> ClosedRegistrationEntry {
249        let bls_sk = BlsSigningKey::generate(rng);
250        let bls_pk = VerificationKeyProofOfPossessionForConcatenation::from(&bls_sk);
251
252        #[cfg(feature = "future_snark")]
253        let schnorr_verification_key = {
254            let sk = SchnorrSigningKey::generate(rng);
255            VerificationKeyForSnark::new_from_signing_key(sk.clone())
256        };
257        ClosedRegistrationEntry::new(
258            bls_pk.vk,
259            stake,
260            #[cfg(feature = "future_snark")]
261            Some(schnorr_verification_key),
262            #[cfg(feature = "future_snark")]
263            Some(LotteryTargetValue::get_one()),
264        )
265    }
266
267    #[test]
268    fn test_ord_different_stakes() {
269        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
270
271        let entry_low_stake = create_closed_registration_entry(&mut rng, 100);
272        let entry_high_stake = create_closed_registration_entry(&mut rng, 200);
273
274        assert_eq!(entry_low_stake.cmp(&entry_high_stake), Ordering::Less);
275        assert_eq!(entry_high_stake.cmp(&entry_low_stake), Ordering::Greater);
276    }
277
278    #[test]
279    fn test_ord_same_stake_different_keys() {
280        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
281
282        let entry1 = create_closed_registration_entry(&mut rng, 100);
283        let entry2 = create_closed_registration_entry(&mut rng, 100);
284
285        let cmp_result = entry1.cmp(&entry2);
286        assert!(cmp_result == Ordering::Less || cmp_result == Ordering::Greater);
287
288        assert_eq!(entry2.cmp(&entry1), cmp_result.reverse());
289    }
290
291    mod golden {
292        use super::*;
293
294        #[cfg(not(feature = "future_snark"))]
295        const GOLDEN_BYTES: &[u8; 104] = &[
296            143, 161, 255, 48, 78, 57, 204, 220, 25, 221, 164, 252, 248, 14, 56, 126, 186, 135,
297            228, 188, 145, 181, 52, 200, 97, 99, 213, 46, 0, 199, 193, 89, 187, 88, 29, 135, 173,
298            244, 86, 36, 83, 54, 67, 164, 6, 137, 94, 72, 6, 105, 128, 128, 93, 48, 176, 11, 4,
299            246, 138, 48, 180, 133, 90, 142, 192, 24, 193, 111, 142, 31, 76, 111, 110, 234, 153,
300            90, 208, 192, 31, 124, 95, 102, 49, 158, 99, 52, 220, 165, 94, 251, 68, 69, 121, 16,
301            224, 194, 0, 0, 0, 0, 0, 0, 0, 1,
302        ];
303
304        #[cfg(feature = "future_snark")]
305        const GOLDEN_BYTES: &[u8; 200] = &[
306            143, 161, 255, 48, 78, 57, 204, 220, 25, 221, 164, 252, 248, 14, 56, 126, 186, 135,
307            228, 188, 145, 181, 52, 200, 97, 99, 213, 46, 0, 199, 193, 89, 187, 88, 29, 135, 173,
308            244, 86, 36, 83, 54, 67, 164, 6, 137, 94, 72, 6, 105, 128, 128, 93, 48, 176, 11, 4,
309            246, 138, 48, 180, 133, 90, 142, 192, 24, 193, 111, 142, 31, 76, 111, 110, 234, 153,
310            90, 208, 192, 31, 124, 95, 102, 49, 158, 99, 52, 220, 165, 94, 251, 68, 69, 121, 16,
311            224, 194, 0, 0, 0, 0, 0, 0, 0, 1, 200, 194, 6, 212, 77, 254, 23, 111, 33, 34, 139, 71,
312            131, 196, 108, 13, 217, 75, 187, 131, 158, 77, 197, 163, 30, 123, 151, 237, 157, 232,
313            167, 10, 45, 121, 194, 155, 110, 46, 240, 74, 141, 138, 78, 228, 92, 179, 58, 63, 233,
314            239, 84, 114, 149, 77, 188, 93, 8, 22, 11, 12, 45, 186, 211, 56, 1, 0, 0, 0, 0, 0, 0,
315            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,
316        ];
317
318        fn golden_value() -> ClosedRegistrationEntry {
319            let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
320            let bls_sk = BlsSigningKey::generate(&mut rng);
321            let bls_pk = VerificationKeyProofOfPossessionForConcatenation::from(&bls_sk);
322
323            #[cfg(feature = "future_snark")]
324            let schnorr_verification_key = {
325                let sk = SchnorrSigningKey::generate(&mut rng);
326                VerificationKeyForSnark::new_from_signing_key(sk.clone())
327            };
328            ClosedRegistrationEntry::new(
329                bls_pk.vk,
330                1,
331                #[cfg(feature = "future_snark")]
332                Some(schnorr_verification_key),
333                #[cfg(feature = "future_snark")]
334                Some(LotteryTargetValue::get_one()),
335            )
336        }
337
338        #[test]
339        fn golden_conversions() {
340            let value = ClosedRegistrationEntry::from_bytes(GOLDEN_BYTES)
341                .expect("This from bytes should not fail");
342            assert_eq!(golden_value(), value);
343
344            let serialized = ClosedRegistrationEntry::to_bytes(value);
345            let golden_serialized = ClosedRegistrationEntry::to_bytes(golden_value());
346            assert_eq!(golden_serialized, serialized);
347        }
348    }
349}