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.into(),
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 .into(),
113 cert_sig: operational_certificate_without_cold_verification_key
114 .cert_sig()
115 .to_bytes()
116 .to_vec(),
117 },
118 cold_verification_key: cold_verification_key.to_bytes().to_vec(),
119 };
120
121 Ok(dmq_message.into())
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use mithril_cardano_node_chain::test::double::FakeChainObserver;
128 use mithril_common::{
129 crypto_helper::TryToBytes,
130 current_function,
131 entities::{BlockNumber, ChainPoint, TimePoint},
132 test::{crypto_helper::KesSignerFake, double::Dummy},
133 };
134
135 use crate::model::MockUnixTimestampProvider;
136
137 use super::*;
138
139 mod test_utils {
140 use super::*;
141
142 pub(super) struct TestMessage {
143 pub(super) content: Vec<u8>,
144 }
145
146 impl TryToBytes for TestMessage {
147 fn to_bytes_vec(&self) -> StdResult<Vec<u8>> {
148 Ok(self.content.clone())
149 }
150 }
151 }
152
153 #[tokio::test]
154 async fn test_build_dmq_message() {
155 let (kes_signature, operational_certificate) =
156 KesSignerFake::dummy_signature(current_function!());
157 let kes_signer = Arc::new(KesSignerFake::new(vec![Ok((
158 kes_signature,
159 operational_certificate.clone(),
160 ))]));
161 let chain_observer = Arc::new(FakeChainObserver::new(Some(TimePoint {
162 chain_point: ChainPoint {
163 block_number: BlockNumber(123),
164 ..ChainPoint::dummy()
165 },
166 ..TimePoint::dummy()
167 })));
168 let builder = DmqMessageBuilder::new(kes_signer.clone(), chain_observer)
169 .set_ttl(1000)
170 .set_timestamp_provider(Arc::new({
171 let mut mock_timestamp_provider = MockUnixTimestampProvider::new();
172 mock_timestamp_provider
173 .expect_current_timestamp()
174 .returning(|| Ok(234));
175
176 mock_timestamp_provider
177 }));
178 let message = test_utils::TestMessage {
179 content: b"test".to_vec(),
180 };
181
182 let dmq_message = builder.build(&message.to_bytes_vec().unwrap()).await.unwrap();
183
184 let expected_msg_payload = DmqMessageBuilder::enrich_msg_payload_with_id(DmqMsgPayload {
185 msg_id: vec![],
186 msg_body: b"test".to_vec(),
187 kes_period: 0,
188 expires_at: 1234,
189 });
190 assert_eq!(
191 DmqMsg {
192 msg_payload: expected_msg_payload.clone(),
193 kes_signature: kes_signature.to_bytes_vec().unwrap(),
194 operational_certificate: DmqMsgOperationalCertificate {
195 kes_vk: operational_certificate
196 .get_opcert_without_cold_verification_key()
197 .kes_vk()
198 .as_bytes()
199 .to_vec(),
200 issue_number: operational_certificate
201 .get_opcert_without_cold_verification_key()
202 .issue_number(),
203 start_kes_period: operational_certificate
204 .get_opcert_without_cold_verification_key()
205 .start_kes_period()
206 .into(),
207 cert_sig: operational_certificate
208 .get_opcert_without_cold_verification_key()
209 .cert_sig()
210 .to_bytes()
211 .to_vec(),
212 },
213 cold_verification_key: operational_certificate
214 .get_cold_verification_key()
215 .to_bytes()
216 .to_vec(),
217 },
218 dmq_message.clone().into()
219 );
220
221 let signed_messages = kes_signer.get_signed_messages();
222 assert_eq!(
223 vec![expected_msg_payload.bytes_to_sign().unwrap()],
224 signed_messages
225 );
226 }
227}