1use crate::client::key_locker::key_locker::KeyLocker;
22use crate::tools::client_id::ClientId;
23use crate::tools::time::TimeMillis;
24use crate::tools::types::{Hash, Id, PQCommitmentBytes, Signature, VerificationKey, VerificationKeyBytes, HASH_BYTES, ID_BYTES, SIGNATURE_BYTES};
25use crate::tools::{compression, encryption, hashing, json, signing};
26use crate::{anyhow_assert_eq, anyhow_assert_ge};
27use bytes::{Buf, BufMut, Bytes, BytesMut};
28use serde::{Deserialize, Serialize};
29use std::fmt::Debug;
30use std::sync::Arc;
31
32#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
33pub struct EncodedPostHeaderSignatureDirectV1 {}
34#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
35pub struct EncodedPostHeaderSignatureEphemeralV1 {
36 }
40
41#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
42pub struct EncodedPostHeaderSignatureDelegationV1 {
43 }
45
46#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
47pub struct EncodedPostHeaderSignatureMechanismV1 {
48 direct: Option<EncodedPostHeaderSignatureDirectV1>,
49 ephemeral: Option<EncodedPostHeaderSignatureEphemeralV1>,
50 delegation: Option<EncodedPostHeaderSignatureDelegationV1>,
51}
52
53#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
54pub struct EncodedPostHeaderV1 {
55 pub verification_key_bytes: VerificationKeyBytes,
56 pub pq_commitment_bytes: PQCommitmentBytes,
57 pub time_millis: TimeMillis,
58 pub post_length: usize,
59 pub linked_base_ids: Vec<Id>,
60 pub signature_mechanism: EncodedPostHeaderSignatureMechanismV1,
61}
62
63impl EncodedPostHeaderV1 {
64 pub fn client_id(&self) -> anyhow::Result<ClientId> {
65 ClientId::new(self.verification_key_bytes, self.pq_commitment_bytes)
66 }
67}
68
69#[derive(Debug, PartialEq, Clone)]
82pub struct EncodedPostV1 {
83 pub post_id: Id, pub signature: Signature,
85 pub header: EncodedPostHeaderV1,
86 pub post: String,
87}
88
89impl EncodedPostV1 {
90 pub fn bytes_without_body(bytes: Bytes) -> anyhow::Result<Bytes> {
94 let fixed_prefix = ID_BYTES + SIGNATURE_BYTES + 1 + HASH_BYTES * 2;
95 anyhow_assert_ge!(bytes.len(), fixed_prefix + 4 + 4, "Bytes too short for header");
96 let header_encrypted_length = u32::from_be_bytes(bytes[fixed_prefix..fixed_prefix + 4].try_into()?) as usize;
97 let length_without_body = fixed_prefix + 4 + 4 + header_encrypted_length;
98 anyhow_assert_ge!(bytes.len(), length_without_body, "Bytes too short for encrypted header");
99 Ok(bytes.slice(..length_without_body))
100 }
101
102 pub fn new(client_id: &ClientId, timestamp: TimeMillis, linked_base_ids: Vec<Id>, post: &str) -> Self {
103 let post = post.to_string();
104 let post_length = post.len();
105
106 Self {
107 post_id: Id::zero(),
108 signature: Signature::zero(),
109 header: EncodedPostHeaderV1 {
110 verification_key_bytes: client_id.verification_key_bytes,
111 pq_commitment_bytes: client_id.pq_commitment_bytes,
112 time_millis: timestamp,
113 post_length,
114 linked_base_ids,
115 signature_mechanism: EncodedPostHeaderSignatureMechanismV1 {
116 direct: None,
117 ephemeral: None,
118 delegation: None,
119 },
120 },
121
122 post,
123 }
124 }
125
126 pub async fn encode_to_bytes_direct(&mut self, key_locker: &Arc<dyn KeyLocker>) -> anyhow::Result<EncodedPostBytesV1> {
127 self.header.signature_mechanism.direct = Some(EncodedPostHeaderSignatureDirectV1 {});
128
129 let mut passwords = Vec::new();
130 {
131 let client_id = ClientId::id_from_parts(&self.header.verification_key_bytes, &self.header.pq_commitment_bytes)?;
132 passwords.push(client_id.as_bytes().to_vec());
133 let mut linked_base_ids = self.header.linked_base_ids.iter().map(|id| id.as_bytes().to_vec()).collect();
134 passwords.append(&mut linked_base_ids);
135 }
136
137 let header = json::struct_to_bytes(&self.header)?;
138 let header_compressed = compression::compress_for_size(&header)?.to_bytes();
139 let header_encrypted = encryption::encrypt_weak(&header_compressed, &passwords)?;
140 let header_encrypted_hash = hashing::hash(&header_encrypted);
141
142 let post_compressed = compression::compress_for_size(self.post.as_bytes())?.to_bytes();
143 let post_encrypted = encryption::encrypt_weak(&post_compressed, &passwords)?;
144 let post_encrypted_hash = hashing::hash(&post_encrypted);
145
146 let hash = hashing::hash_multiple(&[header_encrypted_hash.as_ref(), post_encrypted_hash.as_ref()]);
147 let signature = key_locker.sign(hash.as_ref()).await?;
148 let post_id = Id::from_hash(hashing::hash(signature.as_ref()))?;
149
150 self.signature = signature;
151 self.post_id = post_id;
152
153 let mut bytes = BytesMut::new();
154 bytes.put_slice(post_id.as_ref());
155 bytes.put_slice(signature.as_ref());
156 bytes.put_u8(1u8); bytes.put_slice(header_encrypted_hash.as_ref());
158 bytes.put_slice(post_encrypted_hash.as_ref());
159 bytes.put_u32(header_encrypted.len() as u32);
160 bytes.put_u32(post_encrypted.len() as u32);
161 bytes.put_slice(header_encrypted.as_ref());
162 bytes.put_slice(post_encrypted.as_ref());
163
164 let bytes = bytes.freeze();
165
166 Ok(EncodedPostBytesV1 {
167 length_without_body: bytes.len() - post_encrypted.len(),
168 bytes,
169 })
170 }
171
172 pub fn decode_signature_from_bytes(bytes: &[u8]) -> anyhow::Result<Signature> {
173 anyhow::ensure!(bytes.len() >= SIGNATURE_BYTES, "decode_signature_from_bytes: need {} bytes, got {}", SIGNATURE_BYTES, bytes.len());
174 Signature::from_slice(&bytes[0..SIGNATURE_BYTES])
175 }
176
177 pub fn decode_from_bytes(mut bytes: Bytes, password_base_id: &Id, expect_body: bool, decode_body: bool) -> anyhow::Result<Self> {
178 let password = password_base_id.as_ref();
179
180 anyhow_assert_ge!(bytes.remaining(), ID_BYTES, "Missing post_id");
181 let post_id = Id::from_slice(&bytes.split_to(ID_BYTES))?;
182
183 anyhow_assert_ge!(bytes.remaining(), SIGNATURE_BYTES, "Missing signature");
184 let signature = Signature::from_slice(&bytes.split_to(SIGNATURE_BYTES))?;
185
186 anyhow_assert_ge!(bytes.remaining(), 1, "Missing version");
187 let version = bytes.get_u8();
188 if 1 != version {
189 anyhow::bail!("Invalid buffer: unknown version");
190 }
191
192 anyhow_assert_ge!(bytes.remaining(), HASH_BYTES, "Missing encrypted hashes");
193 let header_encrypted_hash = Hash::from_slice(&bytes.split_to(HASH_BYTES))?;
194 anyhow_assert_ge!(bytes.remaining(), HASH_BYTES, "Missing encrypted hashes");
195 let post_encrypted_hash = Hash::from_slice(&bytes.split_to(HASH_BYTES))?;
196
197 anyhow_assert_ge!(bytes.remaining(), size_of::<u32>(), "Missing encrypted lengths");
198 let header_encrypted_length = bytes.get_u32() as usize;
199 anyhow_assert_ge!(bytes.remaining(), size_of::<u32>(), "Missing encrypted lengths");
200 let post_encrypted_length = bytes.get_u32() as usize;
201
202 anyhow_assert_ge!(bytes.remaining(), header_encrypted_length, "Missing encrypted header");
203 let header_encrypted = bytes.split_to(header_encrypted_length);
204
205 let post_encrypted = match expect_body {
206 true => {
207 anyhow_assert_ge!(bytes.remaining(), post_encrypted_length, "Missing encrypted post");
208 bytes.split_to(post_encrypted_length)
209 }
210 false => Bytes::new(),
211 };
212
213 anyhow_assert_eq!(bytes.remaining(), 0, "Unexpected remaining data");
214
215 let header_compressed = encryption::decrypt(&header_encrypted, password)?;
218 let header_bytes = compression::decompress(&header_compressed)?.to_bytes();
220 let header = json::bytes_to_struct::<EncodedPostHeaderV1>(&header_bytes)?;
222
223 anyhow_assert_eq!(header_encrypted_hash, hashing::hash(&header_encrypted));
225 if expect_body {
226 anyhow_assert_eq!(post_encrypted_hash, hashing::hash(&post_encrypted));
227 }
228
229 {
231 let hash = hashing::hash_multiple(&[header_encrypted_hash.as_ref(), post_encrypted_hash.as_ref()]);
232
233 if header.signature_mechanism.direct.is_some() {
235 let verification_key = VerificationKey::from_bytes(&header.verification_key_bytes)?;
236 signing::verify(&verification_key, &signature, hash.as_ref())?;
237 }
238 else if header.signature_mechanism.ephemeral.is_some() {
240 anyhow::bail!("Signature verification ephemeral not implemented")
241 }
242 else if header.signature_mechanism.delegation.is_some() {
244 anyhow::bail!("Signature verification delegation not implemented")
245 }
246 else {
248 anyhow::bail!("No signature verification mechanisms")
249 }
250 }
251
252 {
254 anyhow_assert_eq!(post_id, Id::from_hash(hashing::hash(signature.as_ref()))?, "post_id is not the has of signature");
255 }
256
257 let post = match expect_body && decode_body {
259 true => {
260 let post_compressed = encryption::decrypt(&post_encrypted, password)?;
262 let post_bytes = compression::decompress(&post_compressed)?.to_bytes();
264 let post = std::str::from_utf8(&post_bytes)?;
266 if post.len() != header.post_length {
267 anyhow::bail!("Post length mismatch: header.post_length={}, actual={}", header.post_length, post.len())
268 }
269 post.to_string()
270 }
271 false => String::new(),
272 };
273
274 Ok(Self { post_id, signature, header, post })
276 }
277}
278
279pub struct EncodedPostBytesV1 {
280 length_without_body: usize,
281 bytes: Bytes,
282}
283
284impl EncodedPostBytesV1 {
285 pub fn bytes_without_body(&self) -> &[u8] {
286 &self.bytes[..self.length_without_body]
287 }
288 pub fn bytes(&self) -> &[u8] {
289 self.bytes.as_ref()
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::client::key_locker::key_locker::{KeyLocker, KeyLockerManager};
297 use crate::client::key_locker::mem_key_locker::MemKeyLockerManager;
298 use crate::tools::time_provider::time_provider::{RealTimeProvider, TimeProvider};
299
300 #[tokio::test]
301 async fn test_post_v1_verification() -> anyhow::Result<()> {
302 let key_locker_manager = MemKeyLockerManager::new().await?;
303 let key_locker: Arc<dyn KeyLocker> = key_locker_manager.create("this is a random keyphrase".to_string()).await?;
304 let time_provider = RealTimeProvider::default();
305 let client_id = key_locker.client_id();
306 let timestamp = time_provider.current_time_millis();
307 let linked_base_ids = vec![client_id.id, Id::random(), Id::random(), Id::random()];
308
309 let password1 = linked_base_ids[0].clone();
310 let password2 = linked_base_ids[1].clone();
311
312 let mut encoded_post = EncodedPostV1::new(client_id, timestamp, linked_base_ids, "this is a test post");
313 let bytes = encoded_post.encode_to_bytes_direct(&key_locker).await?;
314
315 {
317 {
318 let decoded_post = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(&bytes.bytes()), &password1, true, true)?;
319 assert_eq!(encoded_post, decoded_post);
320 }
321
322 {
323 let decoded_post = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(&bytes.bytes()), &password2, true, true)?;
324 assert_eq!(encoded_post, decoded_post);
325 }
326 }
327
328 {
330 let decoded_post = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(&bytes.bytes()), &password2, true, false)?;
331 assert_eq!("", decoded_post.post);
332 }
333
334 {
336 let wrong_password = Id::random();
337 let decoded_post_attempt = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(&bytes.bytes()), &wrong_password, true, true);
338 if decoded_post_attempt.is_ok() {
339 anyhow::bail!("Decoding with wrong password should fail")
340 }
341 }
342
343 {
345 let mut tampered_bytes = Vec::from(bytes.bytes());
346 tampered_bytes[100] = 0u8;
347 let decoded_post_attempt = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(&tampered_bytes), &password1, true, true);
348 if decoded_post_attempt.is_ok() {
349 anyhow::bail!("Decoding tampered bytes should fail")
350 }
351 }
352
353 Ok(())
354 }
355
356 #[tokio::test]
357 async fn test_header_only_verification() -> anyhow::Result<()> {
358 let key_locker_manager = MemKeyLockerManager::new().await?;
359 let key_locker: Arc<dyn KeyLocker> = key_locker_manager.create("this is a random keyphrase".to_string()).await?;
360 let time_provider = RealTimeProvider::default();
361 let client_id = key_locker.client_id();
362 let timestamp = time_provider.current_time_millis();
363 let linked_base_ids = vec![client_id.id, Id::random(), Id::random(), Id::random()];
364
365 let password1 = linked_base_ids[0].clone();
366 let password2 = linked_base_ids[1].clone();
367
368 let mut encoded_post = EncodedPostV1::new(client_id, timestamp, linked_base_ids, "this is a test post");
369 let bytes = encoded_post.encode_to_bytes_direct(&key_locker).await?;
370
371 {
372 let decoded_post = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(&bytes.bytes_without_body()), &password1, false, false)?;
373 assert_eq!(encoded_post.signature, decoded_post.signature);
374 assert_eq!(encoded_post.header, decoded_post.header);
375 assert_eq!(String::new(), decoded_post.post);
376 }
377
378 {
379 let decoded_post = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(&bytes.bytes_without_body()), &password2, false, false)?;
380 assert_eq!(encoded_post.signature, decoded_post.signature);
381 assert_eq!(encoded_post.header, decoded_post.header);
382 assert_eq!(String::new(), decoded_post.post);
383 }
384
385 {
387 let decoded_post_attempt = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(&bytes.bytes()), &password2, false, false);
388 if decoded_post_attempt.is_ok() {
389 anyhow::bail!("Decoding with wrong too many bytes should fail")
390 }
391 }
392
393 {
394 let wrong_password = Id::random();
395 let decoded_post_attempt = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(&bytes.bytes_without_body()), &wrong_password, false, false);
396 if decoded_post_attempt.is_ok() {
397 anyhow::bail!("Decoding with wrong password should fail")
398 }
399 }
400
401 {
402 let mut tampered_bytes = Vec::from(bytes.bytes_without_body());
403 tampered_bytes[100] = 0u8;
404 let decoded_post_attempt = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(&tampered_bytes), &password1, false, false);
405 if decoded_post_attempt.is_ok() {
406 anyhow::bail!("Decoding tampered bytes should fail")
407 }
408 }
409
410 Ok(())
411 }
412
413 #[tokio::test]
414 async fn test_post_with_no_linked_base_ids() -> anyhow::Result<()> {
415 let key_locker_manager = MemKeyLockerManager::new().await?;
416 let key_locker: Arc<dyn KeyLocker> = key_locker_manager.create("this is a random keyphrase".to_string()).await?;
417 let time_provider = RealTimeProvider::default();
418 let client_id = key_locker.client_id();
419 let timestamp = time_provider.current_time_millis();
420
421 let password = client_id.id.clone();
423 let mut encoded_post = EncodedPostV1::new(client_id, timestamp, vec![], "post with no linked ids");
424 let bytes = encoded_post.encode_to_bytes_direct(&key_locker).await?;
425
426 let decoded_post = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(bytes.bytes()), &password, true, true)?;
428 assert_eq!(encoded_post, decoded_post);
429
430 let wrong_password = Id::random();
432 let attempt = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(bytes.bytes()), &wrong_password, true, true);
433 if attempt.is_ok() {
434 anyhow::bail!("Decoding with wrong password should fail");
435 }
436
437 Ok(())
438 }
439
440 #[tokio::test]
441 async fn test_bytes_without_body_round_trip() -> anyhow::Result<()> {
442 let key_locker_manager = MemKeyLockerManager::new().await?;
443 let key_locker: Arc<dyn KeyLocker> = key_locker_manager.create("test keyphrase for bytes_without_body".to_string()).await?;
444 let time_provider = RealTimeProvider::default();
445 let client_id = key_locker.client_id();
446 let timestamp = time_provider.current_time_millis();
447 let linked_base_ids = vec![client_id.id, Id::random(), Id::random()];
448
449 let password = linked_base_ids[0].clone();
450
451 let mut encoded_post = EncodedPostV1::new(client_id, timestamp, linked_base_ids, "test post for bytes_without_body");
452 let bytes = encoded_post.encode_to_bytes_direct(&key_locker).await?;
453
454 let full_bytes = Bytes::copy_from_slice(bytes.bytes());
456 let header_bytes_static = EncodedPostV1::bytes_without_body(full_bytes.clone())?;
457 let header_bytes_original = Bytes::copy_from_slice(bytes.bytes_without_body());
458 assert_eq!(header_bytes_static, header_bytes_original);
459
460 let decoded_header_only = EncodedPostV1::decode_from_bytes(header_bytes_static.clone(), &password, false, false)?;
462 assert_eq!(encoded_post.header, decoded_header_only.header);
463 assert_eq!(encoded_post.signature, decoded_header_only.signature);
464 assert_eq!(encoded_post.post_id, decoded_header_only.post_id);
465 assert_eq!("", decoded_header_only.post);
466
467 let hex_encoded = hex::encode(&header_bytes_static);
469 let hex_decoded = Bytes::from(hex::decode(&hex_encoded)?);
470 let decoded_from_hex = EncodedPostV1::decode_from_bytes(hex_decoded, &password, false, false)?;
471 assert_eq!(encoded_post.header, decoded_from_hex.header);
472
473 Ok(())
474 }
475
476 #[test]
479 fn test_decode_signature_from_bytes_empty() {
480 assert!(EncodedPostV1::decode_signature_from_bytes(&[]).is_err());
481 }
482
483 #[test]
484 fn test_decode_signature_from_bytes_too_short() {
485 assert!(EncodedPostV1::decode_signature_from_bytes(&[0u8; SIGNATURE_BYTES - 1]).is_err());
486 }
487
488 #[test]
489 fn test_decode_signature_from_bytes_exact_length() {
490 assert!(EncodedPostV1::decode_signature_from_bytes(&[0u8; SIGNATURE_BYTES]).is_ok());
492 }
493
494 #[test]
497 fn test_decode_from_bytes_empty() {
498 let password = Id::random();
499 assert!(EncodedPostV1::decode_from_bytes(Bytes::new(), &password, true, true).is_err());
500 }
501
502 #[test]
503 fn test_decode_from_bytes_too_short_for_post_id() {
504 let password = Id::random();
505 let bytes = Bytes::from_static(&[0u8; ID_BYTES - 1]);
506 assert!(EncodedPostV1::decode_from_bytes(bytes, &password, true, true).is_err());
507 }
508
509 #[test]
510 fn test_decode_from_bytes_garbage() {
511 let password = Id::random();
512 let bytes = Bytes::from_static(&[0xff; 256]);
513 assert!(EncodedPostV1::decode_from_bytes(bytes, &password, true, true).is_err());
514 }
515
516 #[test]
519 fn test_bytes_without_body_empty() {
520 assert!(EncodedPostV1::bytes_without_body(Bytes::new()).is_err());
521 }
522
523 #[test]
524 fn test_bytes_without_body_too_short() {
525 let bytes = Bytes::from_static(&[0u8; 10]);
526 assert!(EncodedPostV1::bytes_without_body(bytes).is_err());
527 }
528
529 #[cfg(not(target_arch = "wasm32"))]
530 mod bolero_fuzz {
531 use crate::protocol::posting::encoded_post::EncodedPostV1;
532 use crate::tools::types::Id;
533 use bytes::Bytes;
534
535 #[test]
536 fn fuzz_decode_from_bytes() {
537 bolero::check!().for_each(|data: &[u8]| {
538 let password = Id::zero();
539 let _ = EncodedPostV1::decode_from_bytes(Bytes::copy_from_slice(data), &password, true, true);
540 });
541 }
542
543 #[test]
544 fn fuzz_decode_signature_from_bytes() {
545 bolero::check!().for_each(|data: &[u8]| {
546 let _ = EncodedPostV1::decode_signature_from_bytes(data);
547 });
548 }
549
550 #[test]
551 fn fuzz_bytes_without_body() {
552 bolero::check!().for_each(|data: &[u8]| {
553 let _ = EncodedPostV1::bytes_without_body(Bytes::copy_from_slice(data));
554 });
555 }
556 }
557}