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}