massa_models/
secure_share.rs

1use std::fmt::Display;
2
3use crate::{address::Address, error::ModelsError};
4use massa_hash::Hash;
5use massa_serialization::{Deserializer, SerializeError, Serializer};
6use massa_signature::{
7    KeyPair, PublicKey, PublicKeyDeserializer, Signature, SignatureDeserializer,
8};
9use nom::{
10    error::{context, ContextError, ParseError},
11    sequence::tuple,
12    IResult,
13};
14use serde::{Deserialize, Serialize};
15
16/// Packages type T such that it can be securely sent and received in a trust-free network
17///
18/// If the internal content is mutated, then it must be re-wrapped, as the associated
19/// signature, serialized data, etc. would no longer be in sync
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
21pub struct SecureShare<T, ID>
22where
23    T: Display + SecureShareContent,
24    ID: Id,
25{
26    /// Reference contents. Not required for the security protocols.
27    ///
28    /// Use the Lightweight equivalent structures when you need verifiable
29    /// serialized data, but do not need to read the values directly (such as when sending)
30    pub content: T,
31    #[serde(skip)]
32    /// Content in sharable, deserializable form. Is used in the secure verification protocols.
33    pub serialized_data: Vec<u8>,
34
35    /// A cryptographically generated value using `serialized_data` and a public key.
36    pub signature: Signature,
37    /// The public-key component used in the generation of the signature
38    pub content_creator_pub_key: PublicKey,
39    /// Derived from the same public key used to generate the signature
40    pub content_creator_address: Address,
41    /// A secure hash of the data. See also [massa_hash::Hash]
42    pub id: ID,
43}
44
45/// Used by signed structure
46/// TODO: Make this trait use versions
47pub trait Id {
48    /// New id from hash
49    fn new(hash: Hash) -> Self;
50    /// Get a reference to the underlying hash
51    fn get_hash(&self) -> &Hash;
52}
53
54/// Trait that define a structure that can be signed for secure sharing.
55pub trait SecureShareContent
56where
57    Self: Sized + Display,
58{
59    /// Sign the SecureShare given the content. `sig_chain_id` selects the
60    /// signed-hash layout: `None` for the legacy (chain-agnostic) layout,
61    /// `Some(chain_id)` to fold the chain id into the signed hash. Signing,
62    /// verification and denunciation reconstruction must use the same value
63    /// for a given message (see [`Self::compute_signed_hash`]).
64    fn sign(
65        &self,
66        keypair: &KeyPair,
67        content_hash: &Hash,
68        sig_chain_id: Option<u64>,
69    ) -> Result<Signature, ModelsError> {
70        Ok(keypair.sign(&self.compute_signed_hash(
71            &keypair.get_public_key(),
72            content_hash,
73            sig_chain_id,
74        ))?)
75    }
76
77    /// Verify a signature. `sig_chain_id` selects the signed-hash layout used
78    /// to reconstruct the signature hash (see [`Self::compute_signed_hash`]).
79    fn verify_signature(
80        &self,
81        public_key: &PublicKey,
82        content_hash: &Hash,
83        signature: &Signature,
84        sig_chain_id: Option<u64>,
85    ) -> Result<(), ModelsError> {
86        Ok(public_key.verify_signature(
87            &self.compute_signed_hash(public_key, content_hash, sig_chain_id),
88            signature,
89        )?)
90    }
91
92    /// Using the provided key-pair, applies a cryptographic signature, and packages
93    /// the data required to share and verify the data in a trust-free network of peers.
94    ///
95    /// `chain_id` computes the content id ([`Self::compute_hash`]); `sig_chain_id`
96    /// selects the signed-hash layout (`None` = legacy / chain-agnostic,
97    /// `Some(chain_id)` = chain-scoped). The two must flip together at the
98    /// deterministic MIP-0002 activation slot so all nodes switch simultaneously.
99    fn new_verifiable<Ser: Serializer<Self>, ID: Id>(
100        self,
101        content_serializer: Ser,
102        keypair: &KeyPair,
103        chain_id: u64,
104        sig_chain_id: Option<u64>,
105    ) -> Result<SecureShare<Self, ID>, ModelsError> {
106        let mut content_serialized = Vec::new();
107        content_serializer.serialize(&self, &mut content_serialized)?;
108        let public_key = keypair.get_public_key();
109        let hash = Self::compute_hash(&self, &content_serialized, &public_key, chain_id);
110        let creator_address = Address::from_public_key(&public_key);
111        Ok(SecureShare {
112            signature: self.sign(keypair, &hash, sig_chain_id)?,
113            content_creator_pub_key: public_key,
114            content_creator_address: creator_address,
115            content: self,
116            serialized_data: content_serialized,
117            id: ID::new(hash),
118        })
119    }
120
121    /// Compute hash
122    fn compute_hash(
123        &self,
124        content_serialized: &[u8],
125        content_creator_pub_key: &PublicKey,
126        _chain_id: u64,
127    ) -> Hash {
128        let mut hash_data = Vec::new();
129        hash_data.extend(content_creator_pub_key.to_bytes());
130        hash_data.extend(content_serialized);
131        Hash::compute_from(&hash_data)
132    }
133
134    /// Compute hash used for signature.
135    ///
136    /// `sig_chain_id` selects the layout:
137    /// * `None` -> legacy (chain-agnostic) layout, identical to the pre-fix
138    ///   one. Used for messages whose slot is governed by a network version
139    ///   predating the MIP-0002 `Execution` v2 activation.
140    /// * `Some(chain_id)` -> chain-scoped layout, folding `chain_id` into the
141    ///   signed hash so two signatures produced on different chains for the
142    ///   same (slot, index) cannot be replayed as a denunciation on either
143    ///   chain (F90 / PDF #11).
144    ///
145    /// The default ignores `sig_chain_id` and returns `*content_hash` (the
146    /// legacy layout), which is correct for components that never chain-scope
147    /// their signed hash (e.g. `Operation`, whose `compute_hash` already folds
148    /// in the chain id). `Endorsement` and `BlockHeader` override this to fold
149    /// in `chain_id` when `Some`.
150    fn compute_signed_hash(
151        &self,
152        _public_key: &PublicKey,
153        content_hash: &Hash,
154        _sig_chain_id: Option<u64>,
155    ) -> Hash {
156        *content_hash
157    }
158
159    /// Serialize the secured structure
160    fn serialize(
161        signature: &Signature,
162        creator_public_key: &PublicKey,
163        serialized_content: &[u8],
164        buffer: &mut Vec<u8>,
165    ) -> Result<(), SerializeError> {
166        buffer.extend(signature.to_bytes());
167        buffer.extend(creator_public_key.to_bytes());
168        buffer.extend(serialized_content);
169        Ok(())
170    }
171
172    /// Deserialize the secured structure
173    fn deserialize<
174        'a,
175        E: ParseError<&'a [u8]> + ContextError<&'a [u8]>,
176        Deser: Deserializer<Self>,
177        ID: Id,
178    >(
179        content_serializer: Option<&dyn Serializer<Self>>,
180        signature_deserializer: &SignatureDeserializer,
181        creator_public_key_deserializer: &PublicKeyDeserializer,
182        content_deserializer: &Deser,
183        buffer: &'a [u8],
184        chain_id: u64,
185    ) -> IResult<&'a [u8], SecureShare<Self, ID>, E> {
186        let (serialized_data, (signature, creator_public_key)) = context(
187            "Failed SecureShare deserialization",
188            tuple((
189                context("Failed signature deserialization", |input| {
190                    signature_deserializer.deserialize(input)
191                }),
192                context("Failed public_key deserialization", |input| {
193                    creator_public_key_deserializer.deserialize(input)
194                }),
195            )),
196        )(buffer)?;
197        let (rest, content) = content_deserializer.deserialize(serialized_data)?;
198        let content_serialized = if let Some(content_serializer) = content_serializer {
199            let mut content_buffer = Vec::new();
200            content_serializer
201                .serialize(&content, &mut content_buffer)
202                .map_err(|_| {
203                    nom::Err::Error(ParseError::from_error_kind(
204                        rest,
205                        nom::error::ErrorKind::Fail,
206                    ))
207                })?;
208            content_buffer
209        } else {
210            // Avoid getting the rest of the data in the serialized data
211            serialized_data[..serialized_data.len() - rest.len()].to_vec()
212        };
213        let creator_address = Address::from_public_key(&creator_public_key);
214        let hash = Self::compute_hash(&content, &content_serialized, &creator_public_key, chain_id);
215
216        Ok((
217            rest,
218            SecureShare {
219                content,
220                signature,
221                content_creator_pub_key: creator_public_key,
222                content_creator_address: creator_address,
223                serialized_data: content_serialized.to_vec(),
224                id: ID::new(hash),
225            },
226        ))
227    }
228}
229
230impl<T, ID> Display for SecureShare<T, ID>
231where
232    T: Display + SecureShareContent,
233    ID: Id,
234{
235    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236        writeln!(f, "Signature: {}", self.signature)?;
237        writeln!(f, "Creator pubkey: {}", self.content_creator_pub_key)?;
238        writeln!(f, "Creator address: {}", self.content_creator_address)?;
239        writeln!(f, "Id: {}", self.id.get_hash())?;
240        writeln!(f, "{}", self.content)?;
241        Ok(())
242    }
243}
244
245impl<T, ID> SecureShare<T, ID>
246where
247    T: Display + SecureShareContent,
248    ID: Id,
249{
250    /// Sign the SecureShare given the content
251    pub fn sign(
252        keypair: &KeyPair,
253        content_hash: &Hash,
254        _content: &T,
255    ) -> Result<Signature, ModelsError> {
256        Ok(keypair.sign(content_hash)?)
257    }
258
259    /// check if self has been signed by public key. `sig_chain_id` selects the
260    /// signed-hash layout (`None` = legacy, `Some(chain_id)` = chain-scoped); see
261    /// [`SecureShareContent::compute_signed_hash`].
262    pub fn verify_signature(&self, sig_chain_id: Option<u64>) -> Result<(), ModelsError> {
263        self.content.verify_signature(
264            &self.content_creator_pub_key,
265            self.id.get_hash(),
266            &self.signature,
267            sig_chain_id,
268        )
269    }
270
271    /// Compute the signed hash. `sig_chain_id` selects the signed-hash layout
272    /// (`None` = legacy, `Some(chain_id)` = chain-scoped); see
273    /// [`SecureShareContent::compute_signed_hash`].
274    pub fn compute_signed_hash(&self, sig_chain_id: Option<u64>) -> Hash {
275        self.content.compute_signed_hash(
276            &self.content_creator_pub_key,
277            self.id.get_hash(),
278            sig_chain_id,
279        )
280    }
281
282    /// get full serialized size
283    pub fn serialized_size(&self) -> usize {
284        self.serialized_data
285            .len()
286            .saturating_add(self.signature.get_ser_len())
287            .saturating_add(self.content_creator_pub_key.get_ser_len())
288    }
289}
290
291// NOTE FOR EXPLICATION: No content serializer because serialized data is already here.
292/// Serializer for `SecureShare` structure
293#[derive(Default, Clone)]
294pub struct SecureShareSerializer;
295
296impl SecureShareSerializer {
297    /// Creates a new `SecureShareSerializer`
298    pub const fn new() -> Self {
299        Self
300    }
301
302    /// This method is used to serialize a `SecureShare` structure and use a custom serializer instead of
303    /// using the serialized form of the content stored in `serialized_data`.
304    /// This is useful when the content need to be serialized in a lighter form in specific cases.
305    ///
306    /// # Arguments:
307    /// * `content_serializer`: Custom serializer to be used instead of the data in `serialized_data`
308    /// * `value`: SecureShare structure to be serialized
309    /// * `buffer`: buffer of serialized data to be extend
310    pub fn serialize_with<Ser, T, ID>(
311        &self,
312        content_serializer: &Ser,
313        value: &SecureShare<T, ID>,
314        buffer: &mut Vec<u8>,
315    ) -> Result<(), SerializeError>
316    where
317        Ser: Serializer<T>,
318        T: Display + SecureShareContent,
319        ID: Id,
320    {
321        let mut content_buffer = Vec::new();
322        content_serializer.serialize(&value.content, &mut content_buffer)?;
323        T::serialize(
324            &value.signature,
325            &value.content_creator_pub_key,
326            &content_buffer,
327            buffer,
328        )
329    }
330}
331
332impl<T, ID> Serializer<SecureShare<T, ID>> for SecureShareSerializer
333where
334    T: Display + SecureShareContent,
335    ID: Id,
336{
337    fn serialize(
338        &self,
339        value: &SecureShare<T, ID>,
340        buffer: &mut Vec<u8>,
341    ) -> Result<(), SerializeError> {
342        T::serialize(
343            &value.signature,
344            &value.content_creator_pub_key,
345            &value.serialized_data,
346            buffer,
347        )
348    }
349}
350
351/// Deserializer for SecureShare structure
352pub struct SecureShareDeserializer<T, Deser>
353where
354    T: Display + SecureShareContent,
355    Deser: Deserializer<T>,
356{
357    signature_deserializer: SignatureDeserializer,
358    public_key_deserializer: PublicKeyDeserializer,
359    content_deserializer: Deser,
360    chain_id: u64,
361    marker_t: std::marker::PhantomData<T>,
362}
363
364impl<T, Deser> SecureShareDeserializer<T, Deser>
365where
366    T: Display + SecureShareContent,
367    Deser: Deserializer<T>,
368{
369    /// Creates a new `SecureShareDeserializer`
370    ///
371    /// # Arguments
372    /// * `content_deserializer` - Deserializer for the content
373    pub const fn new(content_deserializer: Deser, chain_id: u64) -> Self {
374        Self {
375            signature_deserializer: SignatureDeserializer::new(),
376            public_key_deserializer: PublicKeyDeserializer::new(),
377            content_deserializer,
378            chain_id,
379            marker_t: std::marker::PhantomData,
380        }
381    }
382
383    /// This method is used to deserialize data that has been serialized in a lightweight form.
384    /// The buffer doesn't have the whole content serialized and so
385    /// this serialized data isn't coherent with the full structure and can't be used to calculate id and signature.
386    /// We pass a serializer to serialize the full structure and retrieve a coherent `serialized_data`
387    /// that can be use for the id and signature computing.
388    ///
389    /// # Arguments:
390    /// * `content_serializer`: Serializer use to compute the `serialized_data` from the content
391    /// * `buffer`: buffer of serialized data to be deserialized
392    ///
393    /// # Returns:
394    /// A rest (data left over from deserialization), an instance of `T`, and the data enabling signature verification
395    pub fn deserialize_with<
396        'a,
397        E: ParseError<&'a [u8]> + ContextError<&'a [u8]>,
398        ID: Id,
399        Ser: Serializer<T>,
400    >(
401        &self,
402        content_serializer: &Ser,
403        buffer: &'a [u8],
404    ) -> IResult<&'a [u8], SecureShare<T, ID>, E> {
405        T::deserialize(
406            Some(content_serializer),
407            &self.signature_deserializer,
408            &self.public_key_deserializer,
409            &self.content_deserializer,
410            buffer,
411            self.chain_id,
412        )
413    }
414}
415
416impl<T, ID, Deser> Deserializer<SecureShare<T, ID>> for SecureShareDeserializer<T, Deser>
417where
418    T: Display + SecureShareContent,
419    ID: Id,
420    Deser: Deserializer<T>,
421{
422    /// ```
423    /// # use massa_models::{endorsement::{Endorsement, EndorsementSerializer, EndorsementDeserializer}, slot::Slot, secure_share::{SecureShare, SecureShareSerializer, SecureShareDeserializer, SecureShareContent}};
424    /// use massa_models::block_id::BlockId;
425    /// # use massa_serialization::{Deserializer, Serializer, DeserializeError, U16VarIntSerializer, U16VarIntDeserializer};
426    /// # use massa_signature::KeyPair;
427    /// # use std::ops::Bound::Included;
428    /// # use massa_hash::Hash;
429    /// use massa_models::config::CHAINID;
430    ///
431    /// let content = Endorsement {
432    ///    slot: Slot::new(10, 1),
433    ///    index: 0,
434    ///    endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk".as_bytes())),
435    /// };
436    /// let keypair = KeyPair::generate(0).unwrap();
437    /// let secured: SecureShare<Endorsement, BlockId> = Endorsement::new_verifiable(
438    ///    content,
439    ///    EndorsementSerializer::new(),
440    ///    &keypair,
441    ///    *CHAINID,
442    ///    None
443    /// ).unwrap();
444    /// let mut serialized_data = Vec::new();
445    /// let serialized = SecureShareSerializer::new().serialize(&secured, &mut serialized_data).unwrap();
446    /// let deserializer = SecureShareDeserializer::new(EndorsementDeserializer::new(32, 1), 77);
447    /// let (rest, deserialized): (&[u8], SecureShare<Endorsement, BlockId>) = deserializer.deserialize::<DeserializeError>(&serialized_data).unwrap();
448    /// assert!(rest.is_empty());
449    /// assert_eq!(secured.id, deserialized.id);
450    /// ```
451    fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
452        &self,
453        buffer: &'a [u8],
454    ) -> IResult<&'a [u8], SecureShare<T, ID>, E> {
455        T::deserialize(
456            None,
457            &self.signature_deserializer,
458            &self.public_key_deserializer,
459            &self.content_deserializer,
460            buffer,
461            self.chain_id,
462        )
463    }
464}