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