1#![cfg_attr(docsrs, feature(doc_cfg))]
3use {
4 num_enum::{IntoPrimitive, TryFromPrimitive},
5 solana_hash::Hash,
6 solana_sanitize::SanitizeError,
7 solana_signature::Signature,
8 solana_signer::Signer,
9};
10
11#[cfg(test)]
12static_assertions::const_assert_eq!(OffchainMessage::HEADER_LEN, 17);
13#[cfg(test)]
14static_assertions::const_assert_eq!(v0::OffchainMessage::MAX_LEN, 65515);
15#[cfg(test)]
16static_assertions::const_assert_eq!(v0::OffchainMessage::MAX_LEN_LEDGER, 1212);
17
18pub fn is_printable_ascii(data: &[u8]) -> bool {
20 for &char in data {
21 if !(0x20..=0x7e).contains(&char) {
22 return false;
23 }
24 }
25 true
26}
27
28pub fn is_utf8(data: &[u8]) -> bool {
30 std::str::from_utf8(data).is_ok()
31}
32
33#[repr(u8)]
34#[derive(Debug, PartialEq, Eq, Copy, Clone, TryFromPrimitive, IntoPrimitive)]
35pub enum MessageFormat {
36 RestrictedAscii,
37 LimitedUtf8,
38 ExtendedUtf8,
39}
40
41#[allow(clippy::arithmetic_side_effects)]
42pub mod v0 {
43 use {
44 super::{is_printable_ascii, is_utf8, MessageFormat, OffchainMessage as Base},
45 solana_hash::Hash,
46 solana_packet::PACKET_DATA_SIZE,
47 solana_sanitize::SanitizeError,
48 solana_sha256_hasher::Hasher,
49 };
50
51 #[derive(Debug, PartialEq, Eq, Clone)]
54 pub struct OffchainMessage {
55 format: MessageFormat,
56 message: Vec<u8>,
57 }
58
59 impl OffchainMessage {
60 pub const HEADER_LEN: usize = 3;
62 pub const MAX_LEN: usize = u16::MAX as usize - Base::HEADER_LEN - Self::HEADER_LEN;
64 pub const MAX_LEN_LEDGER: usize = PACKET_DATA_SIZE - Base::HEADER_LEN - Self::HEADER_LEN;
66
67 pub fn new(message: &[u8]) -> Result<Self, SanitizeError> {
69 let format = if message.is_empty() {
70 return Err(SanitizeError::InvalidValue);
71 } else if message.len() <= OffchainMessage::MAX_LEN_LEDGER {
72 if is_printable_ascii(message) {
73 MessageFormat::RestrictedAscii
74 } else if is_utf8(message) {
75 MessageFormat::LimitedUtf8
76 } else {
77 return Err(SanitizeError::InvalidValue);
78 }
79 } else if message.len() <= OffchainMessage::MAX_LEN {
80 if is_utf8(message) {
81 MessageFormat::ExtendedUtf8
82 } else {
83 return Err(SanitizeError::InvalidValue);
84 }
85 } else {
86 return Err(SanitizeError::ValueOutOfBounds);
87 };
88 Ok(Self {
89 format,
90 message: message.to_vec(),
91 })
92 }
93
94 pub fn serialize(&self, data: &mut Vec<u8>) -> Result<(), SanitizeError> {
96 assert!(!self.message.is_empty() && self.message.len() <= Self::MAX_LEN);
98 data.reserve(Self::HEADER_LEN.saturating_add(self.message.len()));
99 data.push(self.format.into());
101 data.extend_from_slice(&(self.message.len() as u16).to_le_bytes());
103 data.extend_from_slice(&self.message);
105 Ok(())
106 }
107
108 pub fn deserialize(data: &[u8]) -> Result<Self, SanitizeError> {
110 if data.len() <= Self::HEADER_LEN || data.len() > Self::HEADER_LEN + Self::MAX_LEN {
112 return Err(SanitizeError::ValueOutOfBounds);
113 }
114 let format =
116 MessageFormat::try_from(data[0]).map_err(|_| SanitizeError::InvalidValue)?;
117 let message_len = u16::from_le_bytes([data[1], data[2]]) as usize;
118 if Self::HEADER_LEN.saturating_add(message_len) != data.len() {
120 return Err(SanitizeError::InvalidValue);
121 }
122 let message = &data[Self::HEADER_LEN..];
123 let is_valid = match format {
125 MessageFormat::RestrictedAscii => {
126 (message.len() <= Self::MAX_LEN_LEDGER) && is_printable_ascii(message)
127 }
128 MessageFormat::LimitedUtf8 => {
129 (message.len() <= Self::MAX_LEN_LEDGER) && is_utf8(message)
130 }
131 MessageFormat::ExtendedUtf8 => (message.len() <= Self::MAX_LEN) && is_utf8(message),
132 };
133
134 if is_valid {
135 Ok(Self {
136 format,
137 message: message.to_vec(),
138 })
139 } else {
140 Err(SanitizeError::InvalidValue)
141 }
142 }
143
144 pub fn hash(serialized_message: &[u8]) -> Result<Hash, SanitizeError> {
146 let mut hasher = Hasher::default();
147 hasher.hash(serialized_message);
148 Ok(hasher.result())
149 }
150
151 pub fn get_format(&self) -> MessageFormat {
152 self.format
153 }
154
155 pub fn get_message(&self) -> &Vec<u8> {
156 &self.message
157 }
158 }
159}
160
161#[derive(Debug, PartialEq, Eq, Clone)]
162pub enum OffchainMessage {
163 V0(v0::OffchainMessage),
164}
165
166impl OffchainMessage {
167 pub const SIGNING_DOMAIN: &'static [u8] = b"\xffsolana offchain";
168 pub const HEADER_LEN: usize = Self::SIGNING_DOMAIN.len() + 1;
170
171 pub fn new(version: u8, message: &[u8]) -> Result<Self, SanitizeError> {
173 match version {
174 0 => Ok(Self::V0(v0::OffchainMessage::new(message)?)),
175 _ => Err(SanitizeError::ValueOutOfBounds),
176 }
177 }
178
179 pub fn serialize(&self) -> Result<Vec<u8>, SanitizeError> {
181 let mut data = Self::SIGNING_DOMAIN.to_vec();
183
184 match self {
186 Self::V0(msg) => {
187 data.push(0);
188 msg.serialize(&mut data)?;
189 }
190 }
191 Ok(data)
192 }
193
194 pub fn deserialize(data: &[u8]) -> Result<Self, SanitizeError> {
196 if data.len() <= Self::HEADER_LEN {
197 return Err(SanitizeError::ValueOutOfBounds);
198 }
199 if !data.starts_with(Self::SIGNING_DOMAIN) {
200 return Err(SanitizeError::InvalidValue);
201 }
202 let version = data[Self::SIGNING_DOMAIN.len()];
203 let data = &data[Self::SIGNING_DOMAIN.len().saturating_add(1)..];
204 match version {
205 0 => Ok(Self::V0(v0::OffchainMessage::deserialize(data)?)),
206 _ => Err(SanitizeError::ValueOutOfBounds),
207 }
208 }
209
210 pub fn hash(&self) -> Result<Hash, SanitizeError> {
212 match self {
213 Self::V0(_) => v0::OffchainMessage::hash(&self.serialize()?),
214 }
215 }
216
217 pub fn get_version(&self) -> u8 {
218 match self {
219 Self::V0(_) => 0,
220 }
221 }
222
223 pub fn get_format(&self) -> MessageFormat {
224 match self {
225 Self::V0(msg) => msg.get_format(),
226 }
227 }
228
229 pub fn get_message(&self) -> &Vec<u8> {
230 match self {
231 Self::V0(msg) => msg.get_message(),
232 }
233 }
234
235 pub fn sign(&self, signer: &dyn Signer) -> Result<Signature, SanitizeError> {
237 Ok(signer.sign_message(&self.serialize()?))
238 }
239
240 #[cfg(feature = "verify")]
241 pub fn verify(
243 &self,
244 signer: &solana_pubkey::Pubkey,
245 signature: &Signature,
246 ) -> Result<bool, SanitizeError> {
247 Ok(signature.verify(signer.as_ref(), &self.serialize()?))
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use {super::*, solana_keypair::Keypair, std::str::FromStr};
254
255 #[test]
256 fn test_offchain_message_rejects_invalid_signing_domain() {
257 let message = OffchainMessage::new(0, b"Test Message").unwrap();
258 let serialized = message.serialize().unwrap();
259
260 for index in 0..OffchainMessage::SIGNING_DOMAIN.len() {
261 let mut altered = serialized.clone();
262 altered[index] ^= 1;
263
264 assert_eq!(
265 OffchainMessage::deserialize(&altered),
266 Err(SanitizeError::InvalidValue),
267 );
268 }
269 }
270
271 #[test]
272 fn test_offchain_message_ascii() {
273 let message = OffchainMessage::new(0, b"Test Message").unwrap();
274 assert_eq!(message.get_version(), 0);
275 assert_eq!(message.get_format(), MessageFormat::RestrictedAscii);
276 assert_eq!(message.get_message().as_slice(), b"Test Message");
277 assert!(
278 matches!(message, OffchainMessage::V0(ref msg) if msg.get_format() == MessageFormat::RestrictedAscii)
279 );
280 let serialized = [
281 255, 115, 111, 108, 97, 110, 97, 32, 111, 102, 102, 99, 104, 97, 105, 110, 0, 0, 12, 0,
282 84, 101, 115, 116, 32, 77, 101, 115, 115, 97, 103, 101,
283 ];
284 let hash = Hash::from_str("HG5JydBGjtjTfD3sSn21ys5NTWPpXzmqifiGC2BVUjkD").unwrap();
285 assert_eq!(message.serialize().unwrap(), serialized);
286 assert_eq!(message.hash().unwrap(), hash);
287 assert_eq!(message, OffchainMessage::deserialize(&serialized).unwrap());
288 }
289
290 #[test]
291 fn test_offchain_message_utf8() {
292 let message = OffchainMessage::new(0, "Тестовое сообщение".as_bytes()).unwrap();
293 assert_eq!(message.get_version(), 0);
294 assert_eq!(message.get_format(), MessageFormat::LimitedUtf8);
295 assert_eq!(
296 message.get_message().as_slice(),
297 "Тестовое сообщение".as_bytes()
298 );
299 assert!(
300 matches!(message, OffchainMessage::V0(ref msg) if msg.get_format() == MessageFormat::LimitedUtf8)
301 );
302 let serialized = [
303 255, 115, 111, 108, 97, 110, 97, 32, 111, 102, 102, 99, 104, 97, 105, 110, 0, 1, 35, 0,
304 208, 162, 208, 181, 209, 129, 209, 130, 208, 190, 208, 178, 208, 190, 208, 181, 32,
305 209, 129, 208, 190, 208, 190, 208, 177, 209, 137, 208, 181, 208, 189, 208, 184, 208,
306 181,
307 ];
308 let hash = Hash::from_str("6GXTveatZQLexkX4WeTpJ3E7uk1UojRXpKp43c4ArSun").unwrap();
309 assert_eq!(message.serialize().unwrap(), serialized);
310 assert_eq!(message.hash().unwrap(), hash);
311 assert_eq!(message, OffchainMessage::deserialize(&serialized).unwrap());
312 }
313
314 #[test]
315 fn test_offchain_message_sign_and_verify() {
316 let message = OffchainMessage::new(0, b"Test Message").unwrap();
317 let keypair = Keypair::new();
318 let signature = message.sign(&keypair).unwrap();
319 assert!(message.verify(&keypair.pubkey(), &signature).unwrap());
320 }
321}