mithril_dmq/model/
builder.rs1use std::sync::Arc;
2
3use anyhow::Context;
4use blake2::{Blake2b, Digest, digest::consts::U64};
5use pallas_network::miniprotocols::localmsgsubmission::{
6    DmqMsg, DmqMsgOperationalCertificate, DmqMsgPayload,
7};
8
9use mithril_cardano_node_chain::chain_observer::ChainObserver;
10use mithril_common::{
11    StdResult,
12    crypto_helper::{KesSigner, TryToBytes},
13};
14
15use crate::model::{DmqMessage, SystemUnixTimestampProvider, UnixTimestampProvider};
16
17const DMQ_MESSAGE_TTL_IN_SECONDS: u16 = 1800;
19
20pub struct DmqMessageBuilder {
22    kes_signer: Arc<dyn KesSigner>,
23    chain_observer: Arc<dyn ChainObserver>,
24    timestamp_provider: Arc<dyn UnixTimestampProvider>,
25    ttl_seconds: u16,
26}
27
28impl DmqMessageBuilder {
29    pub fn new(kes_signer: Arc<dyn KesSigner>, chain_observer: Arc<dyn ChainObserver>) -> Self {
31        Self {
32            kes_signer,
33            chain_observer,
34            timestamp_provider: Arc::new(SystemUnixTimestampProvider),
35            ttl_seconds: DMQ_MESSAGE_TTL_IN_SECONDS,
36        }
37    }
38
39    pub fn set_timestamp_provider(
41        mut self,
42        timestamp_provider: Arc<dyn UnixTimestampProvider>,
43    ) -> Self {
44        self.timestamp_provider = timestamp_provider;
45        self
46    }
47
48    pub fn set_ttl(mut self, ttl_seconds: u16) -> Self {
50        self.ttl_seconds = ttl_seconds;
51
52        self
53    }
54
55    fn compute_msg_id(dmq_message_payload: &DmqMsgPayload) -> Vec<u8> {
57        let mut hasher = Blake2b::<U64>::new();
58        hasher.update(&dmq_message_payload.msg_body);
59        hasher.update(dmq_message_payload.kes_period.to_be_bytes());
60        hasher.update(dmq_message_payload.expires_at.to_be_bytes());
61
62        hasher.finalize().to_vec()
63    }
64
65    fn enrich_msg_payload_with_id(dmq_message_payload: DmqMsgPayload) -> DmqMsgPayload {
67        let msg_id = Self::compute_msg_id(&dmq_message_payload);
68        let mut dmq_message_payload_with_id = dmq_message_payload;
69        dmq_message_payload_with_id.msg_id = msg_id;
70
71        dmq_message_payload_with_id
72    }
73
74    pub async fn build(&self, message_bytes: &[u8]) -> StdResult<DmqMessage> {
76        let expires_at: u32 = (self.timestamp_provider.current_timestamp()?
77            + self.ttl_seconds as u64)
78            .try_into()
79            .with_context(|| "Failed to compute expires_at while building DMQ message")?;
80        let kes_period = self
81            .chain_observer
82            .get_current_kes_period()
83            .await
84            .with_context(|| "Failed to get KES period while building DMQ message")?
85            .unwrap_or_default();
86        let dmq_message_payload = Self::enrich_msg_payload_with_id(DmqMsgPayload {
87            msg_id: vec![],
88            msg_body: message_bytes.to_vec(),
89            kes_period: kes_period as u64,
90            expires_at,
91        });
92
93        let (kes_signature, operational_certificate) = self
94            .kes_signer
95            .sign(&dmq_message_payload.bytes_to_sign()?, kes_period)
96            .with_context(|| "Failed to KES sign message while building DMQ message")?;
97        let operational_certificate_without_cold_verification_key =
98            operational_certificate.get_opcert_without_cold_verification_key();
99        let cold_verification_key = operational_certificate.get_cold_verification_key();
100
101        let dmq_message = DmqMsg {
102            msg_payload: dmq_message_payload,
103            kes_signature: kes_signature.to_bytes_vec()?,
104            operational_certificate: DmqMsgOperationalCertificate {
105                kes_vk: operational_certificate_without_cold_verification_key
106                    .kes_vk()
107                    .as_bytes()
108                    .to_vec(),
109                issue_number: operational_certificate_without_cold_verification_key.issue_number(),
110                start_kes_period: operational_certificate_without_cold_verification_key
111                    .start_kes_period(),
112                cert_sig: operational_certificate_without_cold_verification_key
113                    .cert_sig()
114                    .to_bytes()
115                    .to_vec(),
116            },
117            cold_verification_key: cold_verification_key.to_bytes().to_vec(),
118        };
119
120        Ok(dmq_message.into())
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use mithril_cardano_node_chain::test::double::FakeChainObserver;
127    use mithril_common::{
128        crypto_helper::TryToBytes,
129        current_function,
130        entities::{BlockNumber, ChainPoint, TimePoint},
131        test::{crypto_helper::KesSignerFake, double::Dummy},
132    };
133
134    use crate::model::MockUnixTimestampProvider;
135
136    use super::*;
137
138    mod test_utils {
139        use super::*;
140
141        pub(super) struct TestMessage {
142            pub(super) content: Vec<u8>,
143        }
144
145        impl TryToBytes for TestMessage {
146            fn to_bytes_vec(&self) -> StdResult<Vec<u8>> {
147                Ok(self.content.clone())
148            }
149        }
150    }
151
152    #[tokio::test]
153    async fn test_build_dmq_message() {
154        let (kes_signature, operational_certificate) =
155            KesSignerFake::dummy_signature(current_function!());
156        let kes_signer = Arc::new(KesSignerFake::new(vec![Ok((
157            kes_signature,
158            operational_certificate.clone(),
159        ))]));
160        let chain_observer = Arc::new(FakeChainObserver::new(Some(TimePoint {
161            chain_point: ChainPoint {
162                block_number: BlockNumber(123),
163                ..ChainPoint::dummy()
164            },
165            ..TimePoint::dummy()
166        })));
167        let builder = DmqMessageBuilder::new(kes_signer.clone(), chain_observer)
168            .set_ttl(1000)
169            .set_timestamp_provider(Arc::new({
170                let mut mock_timestamp_provider = MockUnixTimestampProvider::new();
171                mock_timestamp_provider
172                    .expect_current_timestamp()
173                    .returning(|| Ok(234));
174
175                mock_timestamp_provider
176            }));
177        let message = test_utils::TestMessage {
178            content: b"test".to_vec(),
179        };
180
181        let dmq_message = builder.build(&message.to_bytes_vec().unwrap()).await.unwrap();
182
183        let expected_msg_payload = DmqMessageBuilder::enrich_msg_payload_with_id(DmqMsgPayload {
184            msg_id: vec![],
185            msg_body: b"test".to_vec(),
186            kes_period: 0,
187            expires_at: 1234,
188        });
189        assert_eq!(
190            DmqMsg {
191                msg_payload: expected_msg_payload.clone(),
192                kes_signature: kes_signature.to_bytes_vec().unwrap(),
193                operational_certificate: DmqMsgOperationalCertificate {
194                    kes_vk: operational_certificate
195                        .get_opcert_without_cold_verification_key()
196                        .kes_vk()
197                        .as_bytes()
198                        .to_vec(),
199                    issue_number: operational_certificate
200                        .get_opcert_without_cold_verification_key()
201                        .issue_number(),
202                    start_kes_period: operational_certificate
203                        .get_opcert_without_cold_verification_key()
204                        .start_kes_period(),
205                    cert_sig: operational_certificate
206                        .get_opcert_without_cold_verification_key()
207                        .cert_sig()
208                        .to_bytes()
209                        .to_vec(),
210                },
211                cold_verification_key: operational_certificate
212                    .get_cold_verification_key()
213                    .to_bytes()
214                    .to_vec(),
215            },
216            dmq_message.clone().into()
217        );
218
219        let signed_messages = kes_signer.get_signed_messages();
220        assert_eq!(
221            vec![expected_msg_payload.bytes_to_sign().unwrap()],
222            signed_messages
223        );
224    }
225}