massa_models/
endorsement.rs

1// Copyright (c) 2022 MASSA LABS <info@massa.net>
2
3use crate::block_id::{BlockIdDeserializer, BlockIdSerializer};
4use crate::prehash::PreHashed;
5use crate::secure_share::{Id, SecureShare, SecureShareContent};
6use crate::slot::{Slot, SlotDeserializer, SlotSerializer};
7use crate::{block_id::BlockId, error::ModelsError};
8use massa_hash::{Hash, HashDeserializer};
9use massa_serialization::{
10    DeserializeError, Deserializer, SerializeError, Serializer, U32VarIntDeserializer,
11    U32VarIntSerializer, U64VarIntDeserializer, U64VarIntSerializer,
12};
13use massa_signature::PublicKey;
14use nom::error::{context, ErrorKind};
15use nom::sequence::tuple;
16use nom::Parser;
17use nom::{
18    error::{ContextError, ParseError},
19    IResult,
20};
21use serde::{Deserialize, Serialize};
22use serde_with::{DeserializeFromStr, SerializeDisplay};
23use std::fmt::Formatter;
24use std::ops::Bound::{Excluded, Included};
25use std::{fmt::Display, str::FromStr};
26use transition::Versioned;
27
28/// Endorsement ID size in bytes
29pub const ENDORSEMENT_ID_SIZE_BYTES: usize = massa_hash::HASH_SIZE_BYTES;
30
31/// endorsement id
32#[allow(missing_docs)]
33#[transition::versioned(versions("0"))]
34#[derive(
35    Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, SerializeDisplay, DeserializeFromStr,
36)]
37pub struct EndorsementId(Hash);
38
39const ENDORSEMENTID_PREFIX: char = 'E';
40const ENDORSEMENTID_VERSION: u64 = 0;
41
42impl PreHashed for EndorsementId {}
43
44impl Id for EndorsementId {
45    fn new(hash: Hash) -> Self {
46        EndorsementId::EndorsementIdV0(EndorsementIdV0(hash))
47    }
48
49    fn get_hash(&self) -> &Hash {
50        match self {
51            EndorsementId::EndorsementIdV0(endorsement_id) => endorsement_id.get_hash(),
52        }
53    }
54}
55
56#[transition::impl_version(versions("0"))]
57impl EndorsementId {
58    fn get_hash(&self) -> &Hash {
59        &self.0
60    }
61}
62
63impl std::fmt::Display for EndorsementId {
64    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
65        match self {
66            EndorsementId::EndorsementIdV0(id) => write!(f, "{}", id),
67        }
68    }
69}
70
71#[transition::impl_version(versions("0"))]
72impl std::fmt::Display for EndorsementId {
73    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
74        let u64_serializer = U64VarIntSerializer::new();
75        // might want to allocate the vector with capacity in order to avoid re-allocation
76        let mut bytes: Vec<u8> = Vec::new();
77        u64_serializer
78            .serialize(&ENDORSEMENTID_VERSION, &mut bytes)
79            .map_err(|_| std::fmt::Error)?;
80        bytes.extend(self.0.to_bytes());
81        write!(
82            f,
83            "{}{}",
84            ENDORSEMENTID_PREFIX,
85            bs58::encode(bytes).with_check().into_string()
86        )
87    }
88}
89
90impl std::fmt::Debug for EndorsementId {
91    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
92        write!(f, "{}", self)
93    }
94}
95
96impl FromStr for EndorsementId {
97    type Err = ModelsError;
98    /// ## Example
99    /// ```rust
100    /// # use massa_hash::Hash;
101    /// # use std::str::FromStr;
102    /// # use massa_models::endorsement::EndorsementId;
103    /// # use crate::massa_models::secure_share::Id;
104    /// # let endo_id = EndorsementId::new(Hash::compute_from("endo_id".as_bytes()));
105    /// let ser = endo_id.to_string();
106    /// let res_endo_id = EndorsementId::from_str(&ser).unwrap();
107    /// assert_eq!(endo_id, res_endo_id);
108    /// ```
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        let mut chars = s.chars();
111        match chars.next() {
112            Some(prefix) if prefix == ENDORSEMENTID_PREFIX => {
113                let data = chars.collect::<String>();
114                let decoded_bs58_check = bs58::decode(data)
115                    .with_check(None)
116                    .into_vec()
117                    .map_err(|_| ModelsError::EndorsementIdParseError)?;
118                let endorsement_id_deserializer = EndorsementIdDeserializer::new();
119                let (rest, endorsement_id) = endorsement_id_deserializer
120                    .deserialize::<DeserializeError>(&decoded_bs58_check[..])
121                    .map_err(|_| ModelsError::EndorsementIdParseError)?;
122                if rest.is_empty() {
123                    Ok(endorsement_id)
124                } else {
125                    Err(ModelsError::EndorsementIdParseError)
126                }
127            }
128            _ => Err(ModelsError::EndorsementIdParseError),
129        }
130    }
131}
132
133#[transition::impl_version(versions("0"))]
134impl FromStr for EndorsementId {
135    type Err = ModelsError;
136    fn from_str(s: &str) -> Result<Self, Self::Err> {
137        let mut chars = s.chars();
138        match chars.next() {
139            Some(prefix) if prefix == ENDORSEMENTID_PREFIX => {
140                let data = chars.collect::<String>();
141                let decoded_bs58_check = bs58::decode(data)
142                    .with_check(None)
143                    .into_vec()
144                    .map_err(|_| ModelsError::EndorsementIdParseError)?;
145                let endorsement_id_deserializer = EndorsementIdDeserializer::new();
146                let (rest, endorsement_id) = endorsement_id_deserializer
147                    .deserialize::<DeserializeError>(&decoded_bs58_check[..])
148                    .map_err(|_| ModelsError::EndorsementIdParseError)?;
149                if rest.is_empty() {
150                    Ok(endorsement_id)
151                } else {
152                    Err(ModelsError::EndorsementIdParseError)
153                }
154            }
155            _ => Err(ModelsError::EndorsementIdParseError),
156        }
157    }
158}
159
160struct EndorsementIdDeserializer {
161    version_deserializer: U64VarIntDeserializer,
162    hash_deserializer: HashDeserializer,
163}
164
165impl EndorsementIdDeserializer {
166    pub fn new() -> Self {
167        Self {
168            version_deserializer: U64VarIntDeserializer::new(Included(0), Included(u64::MAX)),
169            hash_deserializer: HashDeserializer::new(),
170        }
171    }
172}
173
174impl Deserializer<EndorsementId> for EndorsementIdDeserializer {
175    fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
176        &self,
177        buffer: &'a [u8],
178    ) -> IResult<&'a [u8], EndorsementId, E> {
179        // Verify that we at least have a version and something else
180        if buffer.len() < 2 {
181            return Err(nom::Err::Error(E::from_error_kind(buffer, ErrorKind::Eof)));
182        }
183        let (rest, endorsement_id_version) = self
184            .version_deserializer
185            .deserialize(buffer)
186            .map_err(|_: nom::Err<E>| {
187                nom::Err::Error(E::from_error_kind(buffer, ErrorKind::Eof))
188            })?;
189        match endorsement_id_version {
190            <EndorsementId!["0"]>::VERSION => {
191                let (rest, endorsement_id) = self.deserialize(rest)?;
192                Ok((rest, EndorsementIdVariant!["0"](endorsement_id)))
193            }
194            _ => Err(nom::Err::Error(E::from_error_kind(buffer, ErrorKind::Eof))),
195        }
196    }
197}
198
199#[transition::impl_version(versions("0"), structures("EndorsementId"))]
200impl Deserializer<EndorsementId> for EndorsementIdDeserializer {
201    fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
202        &self,
203        buffer: &'a [u8],
204    ) -> IResult<&'a [u8], EndorsementId, E> {
205        context("Failed OperationId deserialization", |input| {
206            self.hash_deserializer.deserialize(input)
207        })
208        .map(EndorsementId)
209        .parse(buffer)
210    }
211}
212impl Display for Endorsement {
213    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214        writeln!(
215            f,
216            "Endorsed block: {} at slot {}",
217            self.endorsed_block, self.slot
218        )?;
219        writeln!(f, "Index: {}", self.index)?;
220        Ok(())
221    }
222}
223
224/// an endorsement, as sent in the network
225#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
226pub struct Endorsement {
227    /// Slot in which the endorsement can be included
228    pub slot: Slot,
229    /// Endorsement index inside the including block
230    pub index: u32,
231    /// Hash of endorsed block.
232    /// This is the parent in thread `self.slot.thread` of the block in which the endorsement is included
233    pub endorsed_block: BlockId,
234}
235
236#[cfg(any(test, feature = "test-exports"))]
237impl SecureShareEndorsement {
238    // TODO: gh-issue #3398
239    /// Used under testing conditions to validate an instance of Self
240    pub fn check_invariants(&self) -> Result<(), Box<dyn std::error::Error>> {
241        if let Err(e) = self.verify_signature(Some(*crate::config::CHAINID)) {
242            return Err(e.into());
243        }
244        if self.content.slot.thread >= crate::config::THREAD_COUNT {
245            Err("Endorsement slot on non-existent thread".into())
246        } else if self.content.index >= crate::config::ENDORSEMENT_COUNT {
247            Err("Endorsement index out of range".into())
248        } else {
249            Ok(())
250        }
251    }
252}
253
254/// Wrapped endorsement
255pub type SecureShareEndorsement = SecureShare<Endorsement, EndorsementId>;
256
257impl SecureShareContent for Endorsement {
258    /// Compute the signed hash. `sig_chain_id` selects the layout:
259    /// * `None` -> legacy (chain-agnostic) layout, byte-for-byte identical to the
260    ///   pre-fix layout (used pre MIP-0002 activation).
261    /// * `Some(chain_id)` -> chain-scoped layout: `chain_id` is folded into the
262    ///   signed hash so the same endorsement signed on two different chains
263    ///   produces two different signed hashes. This prevents cross-chain replay
264    ///   of endorsements as denunciations (F90 / PDF #11).
265    fn compute_signed_hash(
266        &self,
267        public_key: &PublicKey,
268        content_hash: &Hash,
269        sig_chain_id: Option<u64>,
270    ) -> Hash {
271        let mut signed_data: Vec<u8> = Vec::new();
272        signed_data.extend(public_key.to_bytes());
273        signed_data.extend(
274            EndorsementDenunciationData::new(self.slot, self.index, sig_chain_id).to_bytes(),
275        );
276        signed_data.extend(content_hash.to_bytes());
277        Hash::compute_from(&signed_data)
278    }
279}
280
281/// Serializer for `Endorsement`
282#[derive(Clone)]
283pub struct EndorsementSerializer {
284    slot_serializer: SlotSerializer,
285    u32_serializer: U32VarIntSerializer,
286    block_id_serializer: BlockIdSerializer,
287}
288
289impl EndorsementSerializer {
290    /// Creates a new `EndorsementSerializer`
291    pub fn new() -> Self {
292        EndorsementSerializer {
293            slot_serializer: SlotSerializer::new(),
294            u32_serializer: U32VarIntSerializer::new(),
295            block_id_serializer: BlockIdSerializer::new(),
296        }
297    }
298}
299
300impl Default for EndorsementSerializer {
301    fn default() -> Self {
302        Self::new()
303    }
304}
305
306impl Serializer<Endorsement> for EndorsementSerializer {
307    /// ## Example:
308    /// ```rust
309    /// use massa_models::{slot::Slot, block_id::BlockId, endorsement::{Endorsement, EndorsementSerializer}};
310    /// use massa_serialization::Serializer;
311    /// use massa_hash::Hash;
312    ///
313    /// let endorsement = Endorsement {
314    ///   slot: Slot::new(1, 2),
315    ///   index: 0,
316    ///   endorsed_block: BlockId::generate_from_hash(Hash::compute_from("test".as_bytes()))
317    /// };
318    /// let mut buffer = Vec::new();
319    /// EndorsementSerializer::new().serialize(&endorsement, &mut buffer).unwrap();
320    /// ```
321    fn serialize(&self, value: &Endorsement, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
322        self.slot_serializer.serialize(&value.slot, buffer)?;
323        self.u32_serializer.serialize(&value.index, buffer)?;
324        self.block_id_serializer
325            .serialize(&value.endorsed_block, buffer)?;
326        Ok(())
327    }
328}
329
330/// Deserializer for `Endorsement`
331pub struct EndorsementDeserializer {
332    slot_deserializer: SlotDeserializer,
333    index_deserializer: U32VarIntDeserializer,
334    block_id_deserializer: BlockIdDeserializer,
335}
336
337impl EndorsementDeserializer {
338    /// Creates a new `EndorsementDeserializer`
339    pub fn new(thread_count: u8, endorsement_count: u32) -> Self {
340        EndorsementDeserializer {
341            slot_deserializer: SlotDeserializer::new(
342                (Included(0), Included(u64::MAX)),
343                (Included(0), Excluded(thread_count)),
344            ),
345            index_deserializer: U32VarIntDeserializer::new(
346                Included(0),
347                Excluded(endorsement_count),
348            ),
349            block_id_deserializer: BlockIdDeserializer::new(),
350        }
351    }
352}
353
354impl Deserializer<Endorsement> for EndorsementDeserializer {
355    /// ## Example:
356    /// ```rust
357    /// use massa_models::{slot::Slot, block_id::BlockId, endorsement::{Endorsement, EndorsementSerializer, EndorsementDeserializer}};
358    /// use massa_serialization::{Serializer, Deserializer, DeserializeError};
359    /// use massa_hash::Hash;
360    ///
361    /// let endorsement = Endorsement {
362    ///   slot: Slot::new(1, 2),
363    ///   index: 0,
364    ///   endorsed_block: BlockId::generate_from_hash(Hash::compute_from("test".as_bytes()))
365    /// };
366    /// let mut buffer = Vec::new();
367    /// EndorsementSerializer::new().serialize(&endorsement, &mut buffer).unwrap();
368    /// let (rest, deserialized) = EndorsementDeserializer::new(32, 10).deserialize::<DeserializeError>(&buffer).unwrap();
369    /// assert_eq!(rest.len(), 0);
370    /// assert_eq!(deserialized.slot, endorsement.slot);
371    /// assert_eq!(deserialized.index, endorsement.index);
372    /// assert_eq!(deserialized.endorsed_block, endorsement.endorsed_block);
373    /// ```
374    fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
375        &self,
376        buffer: &'a [u8],
377    ) -> IResult<&'a [u8], Endorsement, E> {
378        context(
379            "Failed endorsement deserialization",
380            tuple((
381                context("Failed slot deserialization", |input| {
382                    self.slot_deserializer.deserialize(input)
383                }),
384                context("Failed index deserialization", |input| {
385                    self.index_deserializer.deserialize(input)
386                }),
387                context("Failed endorsed_block deserialization", |input| {
388                    self.block_id_deserializer.deserialize(input)
389                }),
390            )),
391        )
392        .map(|(slot, index, endorsed_block)| Endorsement {
393            slot,
394            index,
395            endorsed_block,
396        })
397        .parse(buffer)
398    }
399}
400
401/// Lightweight Serializer for `Endorsement`
402/// When included in a `BlockHeader`, we want to serialize only the index (optimization)
403pub struct EndorsementSerializerLW {
404    // slot_serializer: SlotSerializer,
405    u32_serializer: U32VarIntSerializer,
406}
407
408impl EndorsementSerializerLW {
409    /// Creates a new `EndorsementSerializerLW`
410    pub fn new() -> Self {
411        EndorsementSerializerLW {
412            u32_serializer: U32VarIntSerializer::new(),
413        }
414    }
415}
416
417impl Default for EndorsementSerializerLW {
418    fn default() -> Self {
419        Self::new()
420    }
421}
422
423impl Serializer<Endorsement> for EndorsementSerializerLW {
424    /// ## Example:
425    /// ```rust
426    /// use massa_models::{slot::Slot, block_id::BlockId, endorsement::{Endorsement, EndorsementSerializerLW}};
427    /// use massa_serialization::Serializer;
428    /// use massa_hash::Hash;
429    ///
430    /// let endorsement = Endorsement {
431    ///   slot: Slot::new(1, 2),
432    ///   index: 0,
433    ///   endorsed_block: BlockId::generate_from_hash(Hash::compute_from("test".as_bytes()))
434    /// };
435    /// let mut buffer = Vec::new();
436    /// EndorsementSerializerLW::new().serialize(&endorsement, &mut buffer).unwrap();
437    /// ```
438    fn serialize(&self, value: &Endorsement, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
439        self.u32_serializer.serialize(&value.index, buffer)?;
440        Ok(())
441    }
442}
443
444/// Lightweight Deserializer for `Endorsement`
445pub struct EndorsementDeserializerLW {
446    index_deserializer: U32VarIntDeserializer,
447    slot: Slot,
448    endorsed_block: BlockId,
449}
450
451impl EndorsementDeserializerLW {
452    /// Creates a new `EndorsementDeserializerLW`
453    pub const fn new(endorsement_count: u32, slot: Slot, endorsed_block: BlockId) -> Self {
454        EndorsementDeserializerLW {
455            index_deserializer: U32VarIntDeserializer::new(
456                Included(0),
457                Excluded(endorsement_count),
458            ),
459            slot,
460            endorsed_block,
461        }
462    }
463}
464
465impl Deserializer<Endorsement> for EndorsementDeserializerLW {
466    /// ## Example:
467    /// ```rust
468    /// use massa_models::{slot::Slot, block_id::BlockId, endorsement::{Endorsement, EndorsementSerializerLW, EndorsementDeserializerLW}};
469    /// use massa_serialization::{Serializer, Deserializer, DeserializeError};
470    /// use massa_hash::Hash;
471    ///
472    /// let slot = Slot::new(1, 2);
473    /// let endorsed_block = BlockId::generate_from_hash(Hash::compute_from("test".as_bytes()));
474    /// let endorsement = Endorsement {
475    ///   slot: slot,
476    ///   index: 0,
477    ///   endorsed_block: endorsed_block
478    /// };
479    /// let mut buffer = Vec::new();
480    /// EndorsementSerializerLW::new().serialize(&endorsement, &mut buffer).unwrap();
481    /// let (rest, deserialized) = EndorsementDeserializerLW::new(10, slot, endorsed_block).deserialize::<DeserializeError>(&buffer).unwrap();
482    /// assert_eq!(rest.len(), 0);
483    /// assert_eq!(deserialized.index, endorsement.index);
484    /// ```
485    fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
486        &self,
487        buffer: &'a [u8],
488    ) -> IResult<&'a [u8], Endorsement, E> {
489        context("Failed index deserialization", |input| {
490            self.index_deserializer.deserialize(input)
491        })
492        .map(|index| Endorsement {
493            slot: self.slot,
494            index,
495            endorsed_block: self.endorsed_block,
496        })
497        .parse(buffer)
498    }
499}
500
501/// A denunciation data for endorsement
502#[derive(Debug)]
503pub struct EndorsementDenunciationData {
504    slot: Slot,
505    index: u32,
506    /// `None` for the legacy (chain-agnostic) signed-hash layout.
507    /// `Some(chain_id)` for the chain-scoped layout (post MIP-0002 `Execution` v2).
508    chain_id: Option<u64>,
509}
510
511impl EndorsementDenunciationData {
512    /// Create a new denunciation data for endorsement. Pass `None` for the legacy
513    /// layout, `Some(chain_id)` for the chain-scoped layout.
514    pub fn new(slot: Slot, index: u32, chain_id: Option<u64>) -> Self {
515        Self {
516            slot,
517            index,
518            chain_id,
519        }
520    }
521
522    /// Legacy layout: bytes are byte-for-byte identical to the pre-fix layout, so
523    /// endorsements signed before MIP-0002 activation keep verifying.
524    /// Equivalent to `Self::new(slot, index, None)`.
525    #[allow(dead_code)]
526    pub fn new_legacy(slot: Slot, index: u32) -> Self {
527        Self {
528            slot,
529            index,
530            chain_id: None,
531        }
532    }
533
534    /// Get byte array
535    pub fn to_bytes(&self) -> Vec<u8> {
536        let mut buf = Vec::new();
537        buf.extend(self.slot.to_bytes_key());
538        buf.extend(self.index.to_le_bytes());
539        if let Some(chain_id) = self.chain_id {
540            buf.extend(chain_id.to_be_bytes());
541        }
542        buf
543    }
544}
545
546#[cfg(test)]
547mod tests {
548    use crate::secure_share::{SecureShareContent, SecureShareDeserializer, SecureShareSerializer};
549    use massa_signature::verify_signature_batch;
550    use serde_json::Value;
551
552    use super::*;
553    use crate::config::CHAINID;
554    use massa_serialization::DeserializeError;
555    use massa_signature::KeyPair;
556    use serial_test::serial;
557
558    #[test]
559    #[serial]
560    fn test_endorsement_serialization() {
561        let sender_keypair = KeyPair::generate(0).unwrap();
562        let content = Endorsement {
563            slot: Slot::new(10, 1),
564            index: 0,
565            endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk".as_bytes())),
566        };
567        let endorsement: SecureShareEndorsement = Endorsement::new_verifiable(
568            content,
569            EndorsementSerializer::new(),
570            &sender_keypair,
571            *CHAINID,
572            Some(*CHAINID),
573        )
574        .unwrap();
575
576        let mut ser_endorsement: Vec<u8> = Vec::new();
577        let serializer = SecureShareSerializer::new();
578        serializer
579            .serialize(&endorsement, &mut ser_endorsement)
580            .unwrap();
581        let (_, res_endorsement): (&[u8], SecureShareEndorsement) =
582            SecureShareDeserializer::new(EndorsementDeserializer::new(32, 1), *CHAINID)
583                .deserialize::<DeserializeError>(&ser_endorsement)
584                .unwrap();
585        assert_eq!(res_endorsement, endorsement);
586    }
587
588    #[test]
589    #[serial]
590    fn test_endorsement_lightweight_serialization() {
591        let sender_keypair = KeyPair::generate(0).unwrap();
592        let content = Endorsement {
593            slot: Slot::new(10, 1),
594            index: 0,
595            endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk".as_bytes())),
596        };
597        let endorsement: SecureShareEndorsement = Endorsement::new_verifiable(
598            content,
599            EndorsementSerializerLW::new(),
600            &sender_keypair,
601            *CHAINID,
602            Some(*CHAINID),
603        )
604        .unwrap();
605
606        let mut ser_endorsement: Vec<u8> = Vec::new();
607        let serializer = SecureShareSerializer::new();
608        serializer
609            .serialize(&endorsement, &mut ser_endorsement)
610            .unwrap();
611
612        let parent = BlockId::generate_from_hash(Hash::compute_from("blk".as_bytes()));
613
614        let (_, res_endorsement): (&[u8], SecureShareEndorsement) = SecureShareDeserializer::new(
615            EndorsementDeserializerLW::new(1, Slot::new(10, 1), parent),
616            *CHAINID,
617        )
618        .deserialize::<DeserializeError>(&ser_endorsement)
619        .unwrap();
620        // Test only endorsement index as with the lw ser. we only process this field
621        assert_eq!(res_endorsement.content.index, endorsement.content.index);
622    }
623
624    #[test]
625    fn test_verify_sig_batch() {
626        // test verify_signature_batch as we override SecureShareEndorsements compute_hash
627
628        let sender_keypair = KeyPair::generate(0).unwrap();
629        let content_1 = Endorsement {
630            slot: Slot::new(10, 1),
631            index: 0,
632            endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk1".as_bytes())),
633        };
634        let s_endorsement_1: SecureShareEndorsement = Endorsement::new_verifiable(
635            content_1,
636            EndorsementSerializer::new(),
637            &sender_keypair,
638            *CHAINID,
639            Some(*CHAINID),
640        )
641        .unwrap();
642        let mut serialized = vec![];
643        SecureShareSerializer::new()
644            .serialize(&s_endorsement_1, &mut serialized)
645            .unwrap();
646        let (_, s_endorsement_1): (&[u8], SecureShare<Endorsement, EndorsementId>) =
647            SecureShareDeserializer::new(EndorsementDeserializer::new(32, 32), *CHAINID)
648                .deserialize::<DeserializeError>(&serialized)
649                .unwrap();
650        let sender_keypair = KeyPair::generate(0).unwrap();
651        let content_2 = Endorsement {
652            slot: Slot::new(2, 5),
653            index: 0,
654            endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk2".as_bytes())),
655        };
656        let s_endorsement_2: SecureShareEndorsement = Endorsement::new_verifiable(
657            content_2,
658            EndorsementSerializerLW::new(),
659            &sender_keypair,
660            *CHAINID,
661            Some(*CHAINID),
662        )
663        .unwrap();
664
665        // Test with batch len == 1 (no // verif)
666        let batch_1 = [(
667            s_endorsement_1.compute_signed_hash(Some(*CHAINID)),
668            s_endorsement_1.signature,
669            s_endorsement_1.content_creator_pub_key,
670        )];
671        verify_signature_batch(&batch_1).unwrap();
672
673        // Test with batch len > 1 (// verif)
674        let batch_2 = [
675            (
676                s_endorsement_1.compute_signed_hash(Some(*CHAINID)),
677                s_endorsement_1.signature,
678                s_endorsement_1.content_creator_pub_key,
679            ),
680            (
681                s_endorsement_2.compute_signed_hash(Some(*CHAINID)),
682                s_endorsement_2.signature,
683                s_endorsement_2.content_creator_pub_key,
684            ),
685        ];
686        verify_signature_batch(&batch_2).unwrap();
687    }
688
689    #[test]
690    #[serial]
691    fn test_endorsement_id() {
692        let expected_endorsement_id =
693            EndorsementId::from_str("E12Uy7hrAUHpmHQTWu68p17v7VtZJ6syBTWEJH6jwMTWJB6fdSc7")
694                .unwrap();
695        let actual_endorsement_id = EndorsementId::new(Hash::compute_from("edm".as_bytes()));
696
697        assert_eq!(actual_endorsement_id, expected_endorsement_id);
698    }
699
700    #[test]
701    #[serial]
702    fn test_endorsement_id_errors() {
703        let actual_error = EndorsementId::from_str("SomeInvalidEndorsementId")
704            .unwrap_err()
705            .to_string();
706        let expected_error = "endorsement id parsing error".to_string();
707
708        assert_eq!(actual_error, expected_error);
709    }
710
711    #[test]
712    #[serial]
713    fn test_endorsement_serde() {
714        let orig_endorsement = Endorsement {
715            slot: Slot::new(10, 1),
716            index: 0,
717            endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk".as_bytes())),
718        };
719        let serialized_endorsement = serde_json::to_string(&orig_endorsement).unwrap();
720        let res_endorsement: Value = serde_json::from_str(&serialized_endorsement).unwrap();
721        // check equality
722        assert_eq!(
723            orig_endorsement.slot.period,
724            res_endorsement["slot"]["period"]
725        );
726        assert_eq!(
727            orig_endorsement.slot.thread,
728            res_endorsement["slot"]["thread"]
729        );
730        assert_eq!(orig_endorsement.index, res_endorsement["index"]);
731        assert_eq!(
732            orig_endorsement.endorsed_block.to_string(),
733            res_endorsement["endorsed_block"]
734        );
735    }
736}