rings_core/message/protocols/
verify.rs1#![deny(missing_docs)]
2
3use serde::Deserialize;
6use serde::Serialize;
7
8use crate::consts::DEFAULT_TTL_MS;
9use crate::consts::MAX_TTL_MS;
10use crate::consts::TS_OFFSET_TOLERANCE_MS;
11use crate::dht::Did;
12use crate::error::Result;
13use crate::session::Session;
14use crate::session::SessionSk;
15use crate::utils::get_epoch_ms;
16
17#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
20pub struct MessageVerification {
21 pub session: Session,
23 pub ttl_ms: u64,
25 pub ts_ms: u128,
27 pub sig: Vec<u8>,
29}
30
31fn pack_msg(data: &[u8], ts_ms: u128, ttl_ms: u64) -> Vec<u8> {
32 let mut msg = vec![];
33
34 msg.extend_from_slice(&ts_ms.to_be_bytes());
35 msg.extend_from_slice(&ttl_ms.to_be_bytes());
36 msg.extend_from_slice(data);
37
38 msg
39}
40
41impl MessageVerification {
42 pub fn new(data: &[u8], session_sk: &SessionSk) -> Result<Self> {
44 let ts_ms = get_epoch_ms();
45 let ttl_ms = DEFAULT_TTL_MS;
46 let msg = pack_msg(data, ts_ms, ttl_ms);
47 let verification = MessageVerification {
48 session: session_sk.session(),
49 sig: session_sk.sign(&msg)?,
50 ttl_ms,
51 ts_ms,
52 };
53 Ok(verification)
54 }
55
56 pub fn verify(&self, data: &[u8]) -> bool {
58 let msg = pack_msg(data, self.ts_ms, self.ttl_ms);
59
60 self.session
61 .verify(&msg, &self.sig)
62 .map_err(|e| {
63 tracing::warn!("MessageVerification verify failed: {:?}", e);
64 })
65 .is_ok()
66 }
67
68 pub fn is_expired(&self) -> bool {
70 !self.is_live_at(get_epoch_ms())
71 }
72
73 pub fn is_live_at(&self, now_ms: u128) -> bool {
79 self.ttl_ms <= MAX_TTL_MS
80 && self.ts_ms.saturating_sub(TS_OFFSET_TOLERANCE_MS) <= now_ms
81 && now_ms <= self.ts_ms.saturating_add(self.ttl_ms as u128)
82 }
83
84 pub fn verify_unexpired(&self, data: &[u8]) -> bool {
86 if self.is_expired() {
87 tracing::warn!("message expired");
88 return false;
89 }
90
91 self.verify(data)
92 }
93}
94
95pub trait MessageVerificationExt {
98 fn verification_data(&self) -> Result<Vec<u8>>;
100
101 fn verification(&self) -> &MessageVerification;
103
104 fn is_expired(&self) -> bool {
106 self.verification().is_expired()
107 }
108
109 fn verify(&self) -> bool {
111 if self.is_expired() {
112 tracing::warn!("message expired");
113 return false;
114 }
115
116 let Ok(data) = self.verification_data() else {
117 tracing::warn!("MessageVerificationExt verify get verification_data failed");
118 return false;
119 };
120
121 self.verification().verify_unexpired(&data)
122 }
123
124 fn signer(&self) -> Did {
126 self.verification().session.account_did()
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::ecc::SecretKey;
134
135 struct VerifiedFixture {
136 verification: MessageVerification,
137 }
138
139 impl MessageVerificationExt for VerifiedFixture {
140 fn verification_data(&self) -> Result<Vec<u8>> {
141 Ok(Vec::new())
142 }
143
144 fn verification(&self) -> &MessageVerification {
145 &self.verification
146 }
147 }
148
149 #[test]
150 fn test_expiration_handles_timestamp_below_tolerance_without_underflow() -> Result<()> {
151 let key = SecretKey::random();
152 let session_sk = SessionSk::new_with_seckey(&key)?;
153 let mut verification = MessageVerification::new(&[], &session_sk)?;
154 verification.ts_ms = 0;
155 let fixture = VerifiedFixture { verification };
156
157 assert!(fixture.is_expired());
158 Ok(())
159 }
160
161 fn signed_verification(
162 data: &[u8],
163 session_sk: &SessionSk,
164 ts_ms: u128,
165 ttl_ms: u64,
166 ) -> Result<MessageVerification> {
167 let msg = pack_msg(data, ts_ms, ttl_ms);
168 Ok(MessageVerification {
169 session: session_sk.session(),
170 ttl_ms,
171 ts_ms,
172 sig: session_sk.sign(&msg)?,
173 })
174 }
175
176 #[test]
177 fn test_verify_unexpired_rejects_ttl_above_max() -> Result<()> {
178 let key = SecretKey::random();
179 let session_sk = SessionSk::new_with_seckey(&key)?;
180 let proof = signed_verification(&[], &session_sk, get_epoch_ms(), MAX_TTL_MS + 1)?;
181
182 assert!(proof.is_expired());
183 assert!(!proof.verify_unexpired(&[]));
184 Ok(())
185 }
186
187 #[test]
188 fn test_verify_unexpired_rejects_timestamp_beyond_future_tolerance() -> Result<()> {
189 let key = SecretKey::random();
190 let session_sk = SessionSk::new_with_seckey(&key)?;
191 let proof = signed_verification(
192 &[],
193 &session_sk,
194 get_epoch_ms() + TS_OFFSET_TOLERANCE_MS + 60_000,
195 1_000,
196 )?;
197
198 assert!(proof.is_expired());
199 assert!(!proof.verify_unexpired(&[]));
200 Ok(())
201 }
202}