Skip to main content

rings_core/message/protocols/
verify.rs

1#![deny(missing_docs)]
2
3//! Implementation of Message Verification.
4
5use 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/// Message Verification is based on session, and sig.
18/// it also included ttl time and created ts.
19#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
20pub struct MessageVerification {
21    /// The [Session] of the [SessionSk]. Used to identify a sender and verify the signature.
22    pub session: Session,
23    /// The time to live of the message in milliseconds.
24    pub ttl_ms: u64,
25    /// The timestamp of the message in milliseconds.
26    pub ts_ms: u128,
27    /// The signature of the message. Signed by [SessionSk]. Can be verified by [Session].
28    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    /// Create a new MessageVerification. Should provide the data and the [SessionSk].
43    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    /// Verify a MessageVerification
57    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    /// Return whether the verification timestamp is outside its accepted lifetime.
69    pub fn is_expired(&self) -> bool {
70        !self.is_live_at(get_epoch_ms())
71    }
72
73    /// Return whether the verification timestamp and TTL describe a currently live proof.
74    ///
75    /// Pre: `now_ms` is the receiver's current wall-clock time.
76    /// Post: `true` implies `ttl_ms <= MAX_TTL_MS`, the timestamp is not beyond the accepted future
77    /// skew, and `now_ms` has not passed `ts_ms + ttl_ms`.
78    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    /// Verify the signature only when the verification timestamp is still live.
85    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
95/// This trait helps a struct with `MessageVerification` field to `verify` itself.
96/// It also provides a `signer` method to let receiver know who sent the message.
97pub trait MessageVerificationExt {
98    /// Give the data to be verified.
99    fn verification_data(&self) -> Result<Vec<u8>>;
100
101    /// Give the verification field for verifying.
102    fn verification(&self) -> &MessageVerification;
103
104    /// Checks whether the message is expired.
105    fn is_expired(&self) -> bool {
106        self.verification().is_expired()
107    }
108
109    /// Verifies that the message is not expired and that the signature is valid.
110    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    /// Get signer did from verification.
125    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}