mithril_common/entities/
signer.rs

1#[cfg(feature = "future_snark")]
2use crate::crypto_helper::{
3    ProtocolSignerVerificationKeyForSnark, ProtocolSignerVerificationKeySignatureForSnark,
4};
5use crate::{
6    crypto_helper::{
7        KesEvolutions, ProtocolOpCert, ProtocolSignerVerificationKeyForConcatenation,
8        ProtocolSignerVerificationKeySignatureForConcatenation,
9    },
10    entities::{PartyId, Stake},
11};
12use std::fmt::{Debug, Formatter};
13
14use serde::{Deserialize, Serialize};
15use sha2::{Digest, Sha256};
16
17/// Signer represents a signing participant in the network
18#[derive(Clone, Eq, Serialize, Deserialize)]
19pub struct Signer {
20    /// The unique identifier of the signer
21    ///
22    /// Used only for testing when SPO pool id is not certified
23    pub party_id: PartyId,
24
25    /// The verification key for the Concatenation proof system
26    #[serde(rename = "verification_key")]
27    pub verification_key_for_concatenation: ProtocolSignerVerificationKeyForConcatenation,
28
29    /// The KES signature over the verification key for Concatenation
30    ///
31    /// None is used only for testing when SPO pool id is not certified
32    #[serde(
33        skip_serializing_if = "Option::is_none",
34        rename = "verification_key_signature"
35    )]
36    pub verification_key_signature_for_concatenation:
37        Option<ProtocolSignerVerificationKeySignatureForConcatenation>,
38
39    /// The operational certificate of stake pool operator attached to the signer node
40    ///
41    /// None is used only for testing when SPO pool id is not certified
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub operational_certificate: Option<ProtocolOpCert>,
44
45    /// The number of evolutions of the KES key since the start KES period of the operational certificate at the time of signature.
46    #[serde(rename = "kes_period", skip_serializing_if = "Option::is_none")]
47    pub kes_evolutions: Option<KesEvolutions>,
48
49    /// The verification key for the SNARK proof system
50    #[cfg(feature = "future_snark")]
51    #[serde(skip_serializing_if = "Option::is_none", default)]
52    pub verification_key_for_snark: Option<ProtocolSignerVerificationKeyForSnark>,
53
54    /// The KES signature over the verification key for SNARK
55    #[cfg(feature = "future_snark")]
56    #[serde(skip_serializing_if = "Option::is_none", default)]
57    pub verification_key_signature_for_snark:
58        Option<ProtocolSignerVerificationKeySignatureForSnark>,
59}
60
61impl PartialEq for Signer {
62    fn eq(&self, other: &Self) -> bool {
63        self.party_id.eq(&other.party_id)
64    }
65}
66
67impl PartialOrd for Signer {
68    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
69        Some(self.cmp(other))
70    }
71}
72
73impl Ord for Signer {
74    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
75        self.party_id.cmp(&other.party_id)
76    }
77}
78
79impl Signer {
80    /// Convert the given values to a vec of signers.
81    pub fn vec_from<T: Into<Signer>>(from: Vec<T>) -> Vec<Self> {
82        from.into_iter().map(|f| f.into()).collect()
83    }
84
85    /// Computes the hash of Signer
86    pub fn compute_hash(&self) -> String {
87        let mut hasher = Sha256::new();
88        hasher.update(self.party_id.as_bytes());
89        hasher.update(
90            self.verification_key_for_concatenation
91                .to_json_hex()
92                .unwrap()
93                .as_bytes(),
94        );
95
96        if let Some(verification_key_signature) = &self.verification_key_signature_for_concatenation
97        {
98            hasher.update(verification_key_signature.to_json_hex().unwrap().as_bytes());
99        }
100        if let Some(operational_certificate) = &self.operational_certificate {
101            hasher.update(operational_certificate.to_json_hex().unwrap().as_bytes());
102        }
103
104        #[cfg(feature = "future_snark")]
105        if let Some(verification_key_for_snark) = &self.verification_key_for_snark {
106            hasher.update(verification_key_for_snark.to_json_hex().unwrap().as_bytes());
107        }
108        #[cfg(feature = "future_snark")]
109        if let Some(verification_key_signature_for_snark) =
110            &self.verification_key_signature_for_snark
111        {
112            hasher.update(verification_key_signature_for_snark.to_json_hex().unwrap().as_bytes());
113        }
114
115        hex::encode(hasher.finalize())
116    }
117}
118
119impl Debug for Signer {
120    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
121        let should_be_exhaustive = f.alternate();
122        let mut debug = f.debug_struct("Signer");
123        debug.field("party_id", &self.party_id);
124
125        match should_be_exhaustive {
126            true => {
127                debug
128                    .field(
129                        "verification_key_for_concatenation",
130                        &format_args!("{:?}", self.verification_key_for_concatenation),
131                    )
132                    .field(
133                        "verification_key_signature_for_concatenation",
134                        &format_args!("{:?}", self.verification_key_signature_for_concatenation),
135                    )
136                    .field(
137                        "operational_certificate",
138                        &format_args!("{:?}", self.operational_certificate),
139                    )
140                    .field("kes_evolutions", &format_args!("{:?}", self.kes_evolutions));
141
142                #[cfg(feature = "future_snark")]
143                {
144                    debug
145                        .field(
146                            "verification_key_for_snark",
147                            &format_args!("{:?}", self.verification_key_for_snark),
148                        )
149                        .field(
150                            "verification_key_signature_for_snark",
151                            &format_args!("{:?}", self.verification_key_signature_for_snark),
152                        );
153                }
154
155                debug.finish()
156            }
157            false => debug.finish_non_exhaustive(),
158        }
159    }
160}
161
162impl From<SignerWithStake> for Signer {
163    fn from(other: SignerWithStake) -> Self {
164        Self {
165            party_id: other.party_id,
166            verification_key_for_concatenation: other.verification_key_for_concatenation,
167            verification_key_signature_for_concatenation: other
168                .verification_key_signature_for_concatenation,
169            operational_certificate: other.operational_certificate,
170            kes_evolutions: other.kes_evolutions,
171            #[cfg(feature = "future_snark")]
172            verification_key_for_snark: other.verification_key_for_snark,
173            #[cfg(feature = "future_snark")]
174            verification_key_signature_for_snark: other.verification_key_signature_for_snark,
175        }
176    }
177}
178
179/// Signer represents a signing party in the network (including its stakes)
180#[derive(Clone, Eq, Serialize, Deserialize)]
181pub struct SignerWithStake {
182    /// The unique identifier of the signer
183    ///
184    /// Used only for testing when SPO pool id is not certified
185    pub party_id: PartyId,
186
187    /// The verification key for the Concatenation proof system
188    #[serde(rename = "verification_key")]
189    pub verification_key_for_concatenation: ProtocolSignerVerificationKeyForConcatenation,
190
191    /// The KES signature over the verification key for Concatenation
192    ///
193    /// None is used only for testing when SPO pool id is not certified
194    #[serde(
195        skip_serializing_if = "Option::is_none",
196        rename = "verification_key_signature"
197    )]
198    pub verification_key_signature_for_concatenation:
199        Option<ProtocolSignerVerificationKeySignatureForConcatenation>,
200
201    /// The operational certificate of stake pool operator attached to the signer node
202    ///
203    /// None is used only for testing when SPO pool id is not certified
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub operational_certificate: Option<ProtocolOpCert>,
206
207    /// The number of evolutions of the KES key since the start KES period of the operational certificate at the time of signature.
208    #[serde(rename = "kes_period", skip_serializing_if = "Option::is_none")]
209    pub kes_evolutions: Option<KesEvolutions>,
210
211    /// The signer stake
212    pub stake: Stake,
213
214    /// The verification key for the SNARK proof system
215    #[cfg(feature = "future_snark")]
216    #[serde(skip_serializing_if = "Option::is_none", default)]
217    pub verification_key_for_snark: Option<ProtocolSignerVerificationKeyForSnark>,
218
219    /// The KES signature over the verification key for SNARK (hex encoded)
220    #[cfg(feature = "future_snark")]
221    #[serde(skip_serializing_if = "Option::is_none", default)]
222    pub verification_key_signature_for_snark:
223        Option<ProtocolSignerVerificationKeySignatureForSnark>,
224}
225
226impl PartialEq for SignerWithStake {
227    fn eq(&self, other: &Self) -> bool {
228        self.party_id.eq(&other.party_id)
229    }
230}
231
232impl PartialOrd for SignerWithStake {
233    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
234        Some(self.cmp(other))
235    }
236}
237
238impl Ord for SignerWithStake {
239    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
240        self.party_id.cmp(&other.party_id)
241    }
242}
243
244impl SignerWithStake {
245    /// Turn a [Signer] into a [SignerWithStake].
246    pub fn from_signer(signer: Signer, stake: Stake) -> Self {
247        Self {
248            party_id: signer.party_id,
249            verification_key_for_concatenation: signer.verification_key_for_concatenation,
250            verification_key_signature_for_concatenation: signer
251                .verification_key_signature_for_concatenation,
252            operational_certificate: signer.operational_certificate,
253            kes_evolutions: signer.kes_evolutions,
254            stake,
255            #[cfg(feature = "future_snark")]
256            verification_key_for_snark: signer.verification_key_for_snark,
257            #[cfg(feature = "future_snark")]
258            verification_key_signature_for_snark: signer.verification_key_signature_for_snark,
259        }
260    }
261
262    /// Computes the hash of SignerWithStake
263    pub fn compute_hash(&self) -> String {
264        let mut hasher = Sha256::new();
265        hasher.update(self.party_id.as_bytes());
266        hasher.update(
267            self.verification_key_for_concatenation
268                .to_json_hex()
269                .unwrap()
270                .as_bytes(),
271        );
272
273        if let Some(verification_key_signature) = &self.verification_key_signature_for_concatenation
274        {
275            hasher.update(verification_key_signature.to_json_hex().unwrap().as_bytes());
276        }
277        if let Some(operational_certificate) = &self.operational_certificate {
278            hasher.update(operational_certificate.to_json_hex().unwrap().as_bytes());
279        }
280        hasher.update(self.stake.to_be_bytes());
281
282        #[cfg(feature = "future_snark")]
283        if let Some(verification_key_for_snark) = &self.verification_key_for_snark {
284            hasher.update(verification_key_for_snark.to_json_hex().unwrap().as_bytes());
285        }
286        #[cfg(feature = "future_snark")]
287        if let Some(verification_key_signature_for_snark) =
288            &self.verification_key_signature_for_snark
289        {
290            hasher.update(verification_key_signature_for_snark.to_json_hex().unwrap().as_bytes());
291        }
292
293        hex::encode(hasher.finalize())
294    }
295}
296
297impl Debug for SignerWithStake {
298    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
299        let should_be_exhaustive = f.alternate();
300        let mut debug = f.debug_struct("SignerWithStake");
301        debug.field("party_id", &self.party_id).field("stake", &self.stake);
302
303        match should_be_exhaustive {
304            true => {
305                debug
306                    .field(
307                        "verification_key_for_concatenation",
308                        &format_args!("{:?}", self.verification_key_for_concatenation),
309                    )
310                    .field(
311                        "verification_key_signature_for_concatenation",
312                        &format_args!("{:?}", self.verification_key_signature_for_concatenation),
313                    )
314                    .field(
315                        "operational_certificate",
316                        &format_args!("{:?}", self.operational_certificate),
317                    )
318                    .field("kes_evolutions", &format_args!("{:?}", self.kes_evolutions));
319
320                #[cfg(feature = "future_snark")]
321                {
322                    debug
323                        .field(
324                            "verification_key_for_snark",
325                            &format_args!("{:?}", self.verification_key_for_snark),
326                        )
327                        .field(
328                            "verification_key_signature_for_snark",
329                            &format_args!("{:?}", self.verification_key_signature_for_snark),
330                        );
331                }
332
333                debug.finish()
334            }
335            false => debug.finish_non_exhaustive(),
336        }
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use crate::test::{builder::MithrilFixtureBuilder, double::fake_keys};
343
344    use super::*;
345
346    #[test]
347    fn test_stake_signers_from_into() {
348        let verification_key = MithrilFixtureBuilder::default()
349            .with_signers(1)
350            .build()
351            .signers_with_stake()[0]
352            .verification_key_for_concatenation;
353        let signer_expected = Signer {
354            party_id: "1".to_string(),
355            verification_key_for_concatenation: verification_key,
356            verification_key_signature_for_concatenation: None,
357            operational_certificate: None,
358            kes_evolutions: None,
359            #[cfg(feature = "future_snark")]
360            verification_key_for_snark: None,
361            #[cfg(feature = "future_snark")]
362            verification_key_signature_for_snark: None,
363        };
364        let signer_with_stake = SignerWithStake {
365            party_id: "1".to_string(),
366            verification_key_for_concatenation: verification_key,
367            verification_key_signature_for_concatenation: None,
368            operational_certificate: None,
369            kes_evolutions: None,
370            stake: 100,
371            #[cfg(feature = "future_snark")]
372            verification_key_for_snark: None,
373            #[cfg(feature = "future_snark")]
374            verification_key_signature_for_snark: None,
375        };
376
377        let signer_into: Signer = signer_with_stake.into();
378        assert_eq!(signer_expected, signer_into);
379    }
380
381    #[test]
382    fn test_signer_compute_hash() {
383        const HASH_EXPECTED: &str =
384            "02778791113dcd8647b019366e223bfe3aa8a054fa6d9d1918b6b669de485f1c";
385
386        let build_signer = |party_id: &str, key_index: usize| Signer {
387            party_id: party_id.to_string(),
388            verification_key_for_concatenation: fake_keys::signer_verification_key()[key_index]
389                .try_into()
390                .unwrap(),
391            verification_key_signature_for_concatenation: None,
392            operational_certificate: None,
393            kes_evolutions: None,
394            #[cfg(feature = "future_snark")]
395            verification_key_for_snark: None,
396            #[cfg(feature = "future_snark")]
397            verification_key_signature_for_snark: None,
398        };
399
400        assert_eq!(HASH_EXPECTED, build_signer("1", 3).compute_hash());
401        assert_ne!(HASH_EXPECTED, build_signer("0", 3).compute_hash());
402        assert_ne!(HASH_EXPECTED, build_signer("1", 0).compute_hash());
403    }
404
405    #[test]
406    fn test_signer_with_stake_compute_hash() {
407        #[cfg(not(feature = "future_snark"))]
408        const EXPECTED_HASH: &str =
409            "9a832baccd04aabfc419f57319e3831a1655a95bf3bf5ed96a1167d1e81b5085";
410        #[cfg(feature = "future_snark")]
411        const EXPECTED_HASH: &str =
412            "6158c4f514b1e15dc745845dac9014e710ee6b2f0c5b2b1023d5207cf6b75db9";
413        let signers = MithrilFixtureBuilder::default()
414            .with_signers(2)
415            .build()
416            .signers_with_stake();
417        let signer = signers[0].clone();
418
419        assert_eq!(EXPECTED_HASH, signer.compute_hash());
420
421        {
422            let mut signer_different_party_id = signer.clone();
423            signer_different_party_id.party_id = "whatever".to_string();
424
425            assert_ne!(EXPECTED_HASH, signer_different_party_id.compute_hash());
426        }
427        {
428            let mut signer_different_verification_key = signer.clone();
429            signer_different_verification_key.verification_key_for_concatenation =
430                signers[1].verification_key_for_concatenation;
431
432            assert_ne!(
433                EXPECTED_HASH,
434                signer_different_verification_key.compute_hash()
435            );
436        }
437        {
438            let mut signer_different_stake = signer.clone();
439            signer_different_stake.stake += 20;
440
441            assert_ne!(EXPECTED_HASH, signer_different_stake.compute_hash());
442        }
443
444        #[cfg(feature = "future_snark")]
445        {
446            let mut signer_different_verification_key_for_snark = signer.clone();
447            signer_different_verification_key_for_snark.verification_key_for_snark =
448                signers[1].verification_key_for_snark;
449
450            assert_ne!(
451                EXPECTED_HASH,
452                signer_different_verification_key_for_snark.compute_hash()
453            );
454        }
455
456        #[cfg(feature = "future_snark")]
457        {
458            let mut signer_different_verification_key_signature_for_snark = signer.clone();
459            signer_different_verification_key_signature_for_snark
460                .verification_key_signature_for_snark =
461                signers[1].verification_key_signature_for_snark;
462
463            assert_ne!(
464                EXPECTED_HASH,
465                signer_different_verification_key_signature_for_snark.compute_hash()
466            );
467        }
468    }
469}