bee_block/payload/milestone/
mod.rs

1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Module describing the milestone payload.
5
6mod essence;
7mod index;
8mod merkle;
9mod milestone_id;
10
11///
12pub mod option;
13
14use alloc::{string::String, vec::Vec};
15use core::{fmt::Debug, ops::RangeInclusive};
16
17use crypto::{signatures::ed25519, Error as CryptoError};
18use iterator_sorted::is_unique_sorted;
19pub(crate) use option::{MilestoneOptionCount, ReceiptFundsCount};
20use packable::{bounded::BoundedU8, prefix::VecPrefix, Packable};
21
22pub use self::{
23    essence::MilestoneEssence,
24    index::MilestoneIndex,
25    merkle::MerkleRoot,
26    milestone_id::MilestoneId,
27    option::{MilestoneOption, MilestoneOptions, ParametersMilestoneOption, ReceiptMilestoneOption},
28};
29pub(crate) use self::{essence::MilestoneMetadataLength, option::BinaryParametersLength};
30use crate::{protocol::ProtocolParameters, signature::Signature, Error};
31
32#[derive(Debug)]
33#[allow(missing_docs)]
34pub enum MilestoneValidationError {
35    InvalidMinThreshold,
36    TooFewSignatures(usize, usize),
37    InsufficientApplicablePublicKeys(usize, usize),
38    UnapplicablePublicKey(String),
39    InvalidSignature(usize, String),
40    Crypto(CryptoError),
41}
42
43impl From<CryptoError> for MilestoneValidationError {
44    fn from(error: CryptoError) -> Self {
45        MilestoneValidationError::Crypto(error)
46    }
47}
48
49pub(crate) type SignatureCount =
50    BoundedU8<{ *MilestonePayload::SIGNATURE_COUNT_RANGE.start() }, { *MilestonePayload::SIGNATURE_COUNT_RANGE.end() }>;
51
52/// A payload which defines the inclusion set of other blocks in the Tangle.
53#[derive(Clone, Debug, Eq, PartialEq, Packable)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55#[packable(unpack_error = Error)]
56#[packable(unpack_visitor = ProtocolParameters)]
57pub struct MilestonePayload {
58    essence: MilestoneEssence,
59    #[packable(verify_with = verify_signatures_packable)]
60    #[packable(unpack_error_with = |e| e.unwrap_item_err_or_else(|p| Error::MilestoneInvalidSignatureCount(p.into())))]
61    signatures: VecPrefix<Signature, SignatureCount>,
62}
63
64impl MilestonePayload {
65    /// The payload kind of a [`MilestonePayload`].
66    pub const KIND: u32 = 7;
67    /// Range of allowed milestones signatures key numbers.
68    pub const SIGNATURE_COUNT_RANGE: RangeInclusive<u8> = 1..=255;
69    /// Length of a milestone signature.
70    pub const SIGNATURE_LENGTH: usize = 64;
71
72    /// Creates a new [`MilestonePayload`].
73    pub fn new(essence: MilestoneEssence, signatures: Vec<Signature>) -> Result<Self, Error> {
74        let signatures = VecPrefix::<Signature, SignatureCount>::try_from(signatures)
75            .map_err(Error::MilestoneInvalidSignatureCount)?;
76
77        Ok(Self { essence, signatures })
78    }
79
80    /// Returns the essence of a [`MilestonePayload`].
81    pub fn essence(&self) -> &MilestoneEssence {
82        &self.essence
83    }
84
85    /// Returns the signatures of a [`MilestonePayload`].
86    pub fn signatures(&self) -> &[Signature] {
87        &self.signatures
88    }
89
90    /// Computes the identifier of a [`MilestonePayload`].
91    pub fn id(&self) -> MilestoneId {
92        MilestoneId::new(self.essence().hash())
93    }
94
95    /// Semantically validate a [`MilestonePayload`].
96    pub fn validate(
97        &self,
98        applicable_public_keys: &[String],
99        min_threshold: usize,
100    ) -> Result<(), MilestoneValidationError> {
101        if min_threshold == 0 {
102            return Err(MilestoneValidationError::InvalidMinThreshold);
103        }
104
105        if applicable_public_keys.len() < min_threshold {
106            return Err(MilestoneValidationError::InsufficientApplicablePublicKeys(
107                applicable_public_keys.len(),
108                min_threshold,
109            ));
110        }
111
112        if self.signatures.len() < min_threshold {
113            return Err(MilestoneValidationError::TooFewSignatures(
114                min_threshold,
115                self.signatures.len(),
116            ));
117        }
118
119        let essence_hash = self.essence().hash();
120
121        for (index, signature) in self.signatures().iter().enumerate() {
122            let Signature::Ed25519(signature) = signature;
123
124            if !applicable_public_keys.contains(&hex::encode(signature.public_key())) {
125                return Err(MilestoneValidationError::UnapplicablePublicKey(prefix_hex::encode(
126                    *signature.public_key(),
127                )));
128            }
129
130            let ed25519_public_key = ed25519::PublicKey::try_from_bytes(*signature.public_key())
131                .map_err(MilestoneValidationError::Crypto)?;
132            let ed25519_signature = ed25519::Signature::from_bytes(*signature.signature());
133
134            if !ed25519_public_key.verify(&ed25519_signature, &essence_hash) {
135                return Err(MilestoneValidationError::InvalidSignature(
136                    index,
137                    prefix_hex::encode(signature.public_key()),
138                ));
139            }
140        }
141
142        Ok(())
143    }
144}
145
146fn verify_signatures<const VERIFY: bool>(signatures: &[Signature]) -> Result<(), Error> {
147    if VERIFY
148        && !is_unique_sorted(signatures.iter().map(|signature| {
149            let Signature::Ed25519(signature) = signature;
150            signature.public_key()
151        }))
152    {
153        Err(Error::MilestoneSignaturesNotUniqueSorted)
154    } else {
155        Ok(())
156    }
157}
158
159fn verify_signatures_packable<const VERIFY: bool>(
160    signatures: &[Signature],
161    _visitor: &ProtocolParameters,
162) -> Result<(), Error> {
163    verify_signatures::<VERIFY>(signatures)
164}
165
166#[cfg(feature = "dto")]
167#[allow(missing_docs)]
168pub mod dto {
169    use core::str::FromStr;
170
171    use serde::{Deserialize, Serialize};
172
173    use self::option::dto::MilestoneOptionDto;
174    use super::*;
175    use crate::{
176        error::dto::DtoError, parent::Parents, payload::milestone::MilestoneIndex, signature::dto::SignatureDto,
177        BlockId,
178    };
179
180    /// The payload type to define a milestone.
181    #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
182    pub struct MilestonePayloadDto {
183        #[serde(rename = "type")]
184        pub kind: u32,
185        pub index: u32,
186        pub timestamp: u32,
187        #[serde(rename = "protocolVersion")]
188        pub protocol_version: u8,
189        #[serde(rename = "previousMilestoneId")]
190        pub previous_milestone_id: String,
191        pub parents: Vec<String>,
192        #[serde(rename = "inclusionMerkleRoot")]
193        pub inclusion_merkle_root: String,
194        #[serde(rename = "appliedMerkleRoot")]
195        pub applied_merkle_root: String,
196        #[serde(skip_serializing_if = "Vec::is_empty", default)]
197        pub options: Vec<MilestoneOptionDto>,
198        #[serde(skip_serializing_if = "String::is_empty", default)]
199        pub metadata: String,
200        pub signatures: Vec<SignatureDto>,
201    }
202
203    impl From<&MilestonePayload> for MilestonePayloadDto {
204        fn from(value: &MilestonePayload) -> Self {
205            MilestonePayloadDto {
206                kind: MilestonePayload::KIND,
207                index: *value.essence().index(),
208                timestamp: value.essence().timestamp(),
209                protocol_version: value.essence().protocol_version(),
210                previous_milestone_id: value.essence().previous_milestone_id().to_string(),
211                parents: value.essence().parents().iter().map(|p| p.to_string()).collect(),
212                inclusion_merkle_root: value.essence().inclusion_merkle_root().to_string(),
213                applied_merkle_root: value.essence().applied_merkle_root().to_string(),
214                metadata: prefix_hex::encode(value.essence().metadata()),
215                options: value.essence().options().iter().map(Into::into).collect::<_>(),
216                signatures: value.signatures().iter().map(From::from).collect(),
217            }
218        }
219    }
220
221    impl MilestonePayload {
222        pub fn try_from_dto(
223            value: &MilestonePayloadDto,
224            protocol_parameters: &ProtocolParameters,
225        ) -> Result<MilestonePayload, DtoError> {
226            let essence = {
227                let index = value.index;
228                let timestamp = value.timestamp;
229                let previous_milestone_id = MilestoneId::from_str(&value.previous_milestone_id)
230                    .map_err(|_| DtoError::InvalidField("previousMilestoneId"))?;
231                let mut parent_ids = Vec::new();
232
233                for block_id in &value.parents {
234                    parent_ids.push(
235                        block_id
236                            .parse::<BlockId>()
237                            .map_err(|_| DtoError::InvalidField("parents"))?,
238                    );
239                }
240
241                let inclusion_merkle_root = MerkleRoot::from_str(&value.inclusion_merkle_root)
242                    .map_err(|_| DtoError::InvalidField("inclusionMerkleRoot"))?;
243                let applied_merkle_root = MerkleRoot::from_str(&value.applied_merkle_root)
244                    .map_err(|_| DtoError::InvalidField("appliedMerkleRoot"))?;
245                let options = MilestoneOptions::try_from(
246                    value
247                        .options
248                        .iter()
249                        .map(|o| MilestoneOption::try_from_dto(o, protocol_parameters.token_supply()))
250                        .collect::<Result<Vec<_>, _>>()?,
251                )?;
252                let metadata = if !value.metadata.is_empty() {
253                    prefix_hex::decode(&value.metadata).map_err(|_| DtoError::InvalidField("metadata"))?
254                } else {
255                    Vec::new()
256                };
257
258                MilestoneEssence::new(
259                    MilestoneIndex(index),
260                    timestamp,
261                    protocol_parameters.protocol_version(),
262                    previous_milestone_id,
263                    Parents::new(parent_ids)?,
264                    inclusion_merkle_root,
265                    applied_merkle_root,
266                    metadata,
267                    options,
268                )?
269            };
270
271            let mut signatures = Vec::new();
272            for v in &value.signatures {
273                signatures.push(v.try_into().map_err(|_| DtoError::InvalidField("signatures"))?)
274            }
275
276            Ok(MilestonePayload::new(essence, signatures)?)
277        }
278    }
279}
280
281#[cfg(feature = "inx")]
282#[allow(missing_docs)]
283pub mod inx {
284    use packable::PackableExt;
285
286    use super::*;
287    use crate::payload::Payload;
288
289    impl From<MilestonePayload> for ::inx::proto::RawMilestone {
290        fn from(value: MilestonePayload) -> Self {
291            Self {
292                data: Payload::from(value).pack_to_vec(),
293            }
294        }
295    }
296}