1use alloc::vec::Vec;
2use rand_core::{CryptoRng, RngCore};
3
4use crate::VerifyingKey;
5use crate::api::Client;
6use crate::client::plaintext::Plaintext;
7use crate::keys::*;
8use crate::message::{
9 MessageKeyPair, MessagePrivateKey, MessagePublicKey, keygen as message_keygen,
10};
11use crate::metadata::{MetadataKeyPair, MetadataPublicKey, keygen as metadata_keygen};
12use crate::primitives::dh_akem::{DhAkemPrivateKey, DhAkemPublicKey};
13use crate::primitives::mlkem::{MLKEM768PrivateKey, MLKEM768PublicKey};
14use crate::primitives::provider;
15use crate::primitives::ristretto255::{DHPrivateKey, DHPublicKey, generate_dh_keypair};
16use crate::primitives::xwing::{XWingPrivateKey, XWingPublicKey};
17use crate::sign::{JournalistEphemeralKey, JournalistLongTermKey, Signature, SigningKey};
18use crate::traits::{Enrollable, JournalistPublic, RestrictedApi, UserPublic, UserSecret};
19use crate::wire::bundle::{SignedKeyBundlePublic, SignedLongtermKeyBundle};
20use crate::wire::enrollment::Enrollment;
21
22use crate::sealed;
24
25#[cfg(not(hax))]
26impl sealed::Sealed for Journalist {}
27
28#[cfg(not(hax))]
29impl RestrictedApi for Journalist {}
30
31pub struct Journalist {
35 signing_key: SigningKey,
36 fetch_key: DhFetchKeyPair,
37 message_keys: Vec<MessageKeyBundle>,
38 reply_apke: MessageKeyPair,
40 session_storage: SessionStorage,
41}
42
43#[cfg_attr(not(hax), derive(serde::Serialize, serde::Deserialize))]
46pub struct JournalistPublicView {
47 vk: VerifyingKey,
48 signed_longterm_key_bundle: SignedLongtermKeyBundle,
49 kb: SignedKeyBundlePublic,
50}
51
52impl JournalistPublicView {
53 pub fn new(
54 vk: VerifyingKey,
55 signed_longterm_key_bundle: SignedLongtermKeyBundle,
56 kb: SignedKeyBundlePublic,
57 ) -> Self {
58 Self {
59 vk,
60 signed_longterm_key_bundle,
61 kb,
62 }
63 }
64}
65
66impl UserPublic for JournalistPublicView {
67 fn fetch_pk(&self) -> &DHPublicKey {
68 &self.signed_longterm_key_bundle.bundle.fetch_pk
69 }
70
71 fn message_auth_pk(&self) -> &MessagePublicKey {
72 &self.signed_longterm_key_bundle.bundle.apke
73 }
74
75 fn message_metadata_pk(&self) -> &MetadataPublicKey {
76 &self.kb.0.metadata_pk
77 }
78
79 fn message_enc_pk(&self) -> &MessagePublicKey {
80 &self.kb.0.apke_pk
81 }
82}
83
84impl JournalistPublic for JournalistPublicView {
85 fn verifying_key(&self) -> &VerifyingKey {
86 &self.vk
87 }
88
89 fn self_signature(&self) -> &Signature<JournalistLongTermKey> {
90 &self.signed_longterm_key_bundle.selfsig
91 }
92
93 fn signed_keybytes(&self) -> &SignedLongtermKeyBundle {
94 &self.signed_longterm_key_bundle
95 }
96
97 fn ephemeral_bundle(&self) -> &KeyBundlePublic {
98 &self.kb.0
99 }
100
101 fn ephemeral_signature(&self) -> &Signature<JournalistEphemeralKey> {
102 &self.kb.1
103 }
104}
105
106impl Client for Journalist {
107 fn newsroom_verifying_key(&self) -> Option<&VerifyingKey> {
108 self.session_storage.nr_key.as_ref()
109 }
110
111 fn set_newsroom_verifying_key(&mut self, key: VerifyingKey) {
112 self.session_storage.nr_key = Some(key);
113 }
114}
115
116#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
117fn signed_keybundle_publics(
118 message_keys: &[MessageKeyBundle],
119 signing_key: &SigningKey,
120) -> Vec<SignedKeyBundlePublic> {
121 let mut out = Vec::new();
122 for bundle in message_keys.iter() {
123 out.push(sign_bundle(bundle, signing_key));
124 }
125 out
126}
127
128impl UserSecret for Journalist {
130 fn num_bundles(&self) -> usize {
131 self.message_keys.len()
132 }
133
134 fn fetch_keypair(&self) -> (&DHPrivateKey, &DHPublicKey) {
135 (&self.fetch_key.sk, &self.fetch_key.pk)
136 }
137
138 fn message_auth_keypair(&self) -> &MessageKeyPair {
139 &self.reply_apke
140 }
141
142 fn build_message(&self, message: Vec<u8>) -> Plaintext {
143 Plaintext {
148 sender_fetch_key: crate::primitives::ristretto255::placeholder_public_key(),
149 sender_reply_pubkey_hybrid: MetadataPublicKey::from_bytes(
150 &[0u8; MetadataPublicKey::LEN],
151 )
152 .expect("zero placeholder has the right length"),
153 msg: message,
154 }
155 }
156
157 fn keybundles(&self) -> Vec<&MessageKeyBundle> {
158 self.message_keys.iter().collect()
159 }
160}
161
162impl Enrollable for Journalist {
163 fn enroll(&self) -> Enrollment {
164 Enrollment {
165 bundle: self.signed_longterm_key_bundle(),
166 verification_key: self.signing_key.vk,
167 }
168 }
169
170 fn signed_keybundles(&self) -> Vec<SignedKeyBundlePublic> {
171 signed_keybundle_publics(&self.message_keys, &self.signing_key)
172 }
173
174 fn signing_key(&self) -> &VerifyingKey {
175 &self.signing_key.vk
176 }
177}
178
179#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
181fn make_key_bundle<R: RngCore + CryptoRng>(rng: &mut R) -> MessageKeyBundle {
182 let apke_kp = message_keygen(rng).expect("SD-APKE ephemeral keygen failed");
183 let metadata_kp = metadata_keygen(rng).expect("Failed to generate metadata keys");
184
185 MessageKeyBundle::new(apke_kp, metadata_kp)
186}
187
188fn sign_bundle(bundle: &MessageKeyBundle, signing_key: &SigningKey) -> SignedKeyBundlePublic {
190 let public = bundle.public();
191 let selfsig: Signature<JournalistEphemeralKey> = signing_key.sign(&public.as_bytes());
192 (public, selfsig)
193}
194
195impl Journalist {
196 #[cfg_attr(hax, hax_lib::opaque)]
197 pub fn new<R: RngCore + CryptoRng>(rng: &mut R, num_keybundles: usize) -> Self {
198 let mut key_bundles: Vec<MessageKeyBundle> = Vec::with_capacity(num_keybundles);
199
200 let signing_key = SigningKey::new(rng).expect("Signing keygen failed");
201
202 let (sk_fetch, pk_fetch) = generate_dh_keypair(rng);
203
204 let reply_apke = message_keygen(rng).expect("SD-APKE Keygen (Reply) failed");
205
206 for _ in 0..num_keybundles {
208 key_bundles.push(make_key_bundle(rng));
209 }
210 assert_eq!(key_bundles.len(), num_keybundles);
211
212 let session_storage = SessionStorage {
213 fpf_key: None,
214 nr_key: None,
215 fpf_signature: None,
216 };
217
218 Self {
219 signing_key,
220 fetch_key: KeyPair {
221 sk: sk_fetch,
222 pk: pk_fetch,
223 },
224 reply_apke,
225 message_keys: key_bundles,
226 session_storage,
227 }
228 }
229
230 #[cfg_attr(hax, hax_lib::opaque)]
231 pub fn public(&self, idx: usize) -> JournalistPublicView {
232 let kb = self.message_keys.get(idx).expect("Bad index");
233 JournalistPublicView::new(
234 self.signing_key.vk,
235 self.signed_longterm_key_bundle(),
236 sign_bundle(kb, &self.signing_key),
237 )
238 }
239
240 fn signed_longterm_key_bundle(&self) -> SignedLongtermKeyBundle {
242 let longterm_bundle =
243 LongtermKeyBundle::new(self.reply_apke.public_key().clone(), self.fetch_key.pk);
244 let self_signature: Signature<JournalistLongTermKey> =
245 self.signing_key.sign(&longterm_bundle.as_bytes());
246 SignedLongtermKeyBundle::new(longterm_bundle, self_signature)
247 }
248
249 pub fn long_term_bytes(&self) -> JournalistLongTermBytes {
253 JournalistLongTermBytes {
254 sig_seed: self.signing_key.as_bytes(),
255 fetch_sk: self.fetch_key.sk.to_bytes(),
256 apke_dhakem_sk: *self.reply_apke.private_key().dhakem.as_bytes(),
257 apke_mlkem_sk: *self.reply_apke.private_key().mlkem.as_bytes(),
258 apke_mlkem_pk: *self.reply_apke.public_key().mlkem.as_bytes(),
259 }
260 }
261
262 #[cfg_attr(hax, hax_lib::opaque)]
264 pub fn from_long_term_bytes(parts: JournalistLongTermBytes) -> Result<Self, anyhow::Error> {
265 use crate::message::{MessagePrivateKey, MessagePublicKey};
266 use crate::primitives::dh_akem::{DhAkemPrivateKey, DhAkemPublicKey};
267 use crate::primitives::mlkem::{MLKEM768PrivateKey, MLKEM768PublicKey};
268 use crate::primitives::provider;
269
270 let signing_key = SigningKey::from_seed(parts.sig_seed);
271 let sk_fetch = DHPrivateKey::decode(parts.fetch_sk)?;
272 let pk_fetch = sk_fetch.public_key();
273
274 let mut apke_dhakem_pk_bytes = [0u8; DhAkemPublicKey::LEN];
275 provider::curve25519::secret_to_public(&mut apke_dhakem_pk_bytes, &parts.apke_dhakem_sk);
276 let apke_dhakem_sk = DhAkemPrivateKey::from_bytes(parts.apke_dhakem_sk);
277 let apke_dhakem_pk = DhAkemPublicKey::from_bytes(apke_dhakem_pk_bytes);
278 let apke_mlkem_sk = MLKEM768PrivateKey::from_bytes(parts.apke_mlkem_sk);
279 let apke_mlkem_pk = MLKEM768PublicKey::from_bytes(parts.apke_mlkem_pk);
280
281 let reply_apke = MessageKeyPair::new(
282 MessagePrivateKey {
283 dhakem: apke_dhakem_sk,
284 mlkem: apke_mlkem_sk,
285 },
286 MessagePublicKey {
287 dhakem: apke_dhakem_pk,
288 mlkem: apke_mlkem_pk,
289 },
290 );
291
292 Ok(Self {
293 signing_key,
294 fetch_key: KeyPair {
295 sk: sk_fetch,
296 pk: pk_fetch,
297 },
298 reply_apke,
299 message_keys: Vec::new(),
300 session_storage: SessionStorage {
301 fpf_key: None,
302 nr_key: None,
303 fpf_signature: None,
304 },
305 })
306 }
307
308 #[cfg_attr(hax, hax_lib::opaque)]
315 pub fn generate_ephemeral_bundles<R: RngCore + CryptoRng>(&mut self, rng: &mut R, n: usize) {
316 for _ in 0..n {
317 self.message_keys.push(make_key_bundle(rng));
318 }
319 }
320
321 #[cfg_attr(hax, hax_lib::opaque)]
326 pub fn ephemeral_bundle_bytes(&self) -> Vec<EphemeralBundleBytes> {
327 self.message_keys
328 .iter()
329 .map(EphemeralBundleBytes::from_bundle)
330 .collect()
331 }
332
333 #[cfg_attr(hax, hax_lib::opaque)]
337 pub fn load_ephemeral_bundles(&mut self, bundles: Vec<EphemeralBundleBytes>) {
338 for bytes in bundles {
339 let bundle = bytes.into_bundle();
340 self.message_keys.push(bundle);
341 }
342 }
343}
344
345pub struct JournalistLongTermBytes {
349 pub sig_seed: [u8; SigningKey::SEED_LEN],
350 pub fetch_sk: [u8; DHPrivateKey::LEN],
351 pub apke_dhakem_sk: [u8; DhAkemPrivateKey::LEN],
352 pub apke_mlkem_sk: [u8; MLKEM768PrivateKey::LEN],
353 pub apke_mlkem_pk: [u8; MLKEM768PublicKey::LEN],
354}
355
356impl JournalistLongTermBytes {
357 pub const LEN: usize = SigningKey::SEED_LEN
359 + DHPrivateKey::LEN
360 + DhAkemPrivateKey::LEN
361 + MLKEM768PrivateKey::LEN
362 + MLKEM768PublicKey::LEN;
363
364 pub fn as_bytes(&self) -> Vec<u8> {
366 let mut out = Vec::with_capacity(Self::LEN);
367 out.extend_from_slice(&self.sig_seed);
368 out.extend_from_slice(&self.fetch_sk);
369 out.extend_from_slice(&self.apke_dhakem_sk);
370 out.extend_from_slice(&self.apke_mlkem_sk);
371 out.extend_from_slice(&self.apke_mlkem_pk);
372 out
373 }
374
375 pub fn from_bytes(bytes: &[u8]) -> Result<Self, anyhow::Error> {
381 if bytes.len() != Self::LEN {
382 return Err(anyhow::anyhow!(
383 "Invalid JournalistLongTermBytes length: expected {}, got {}",
384 Self::LEN,
385 bytes.len()
386 ));
387 }
388
389 let (sig_seed, rest) = bytes.split_at(SigningKey::SEED_LEN);
390 let (fetch_sk, rest) = rest.split_at(DHPrivateKey::LEN);
391 let (apke_dhakem_sk, rest) = rest.split_at(DhAkemPrivateKey::LEN);
392 let (apke_mlkem_sk, apke_mlkem_pk) = rest.split_at(MLKEM768PrivateKey::LEN);
393
394 Ok(Self {
396 sig_seed: sig_seed.try_into().expect("wrong checked length"),
397 fetch_sk: fetch_sk.try_into().expect("wrong checked length"),
398 apke_dhakem_sk: apke_dhakem_sk.try_into().expect("wrong checked length"),
399 apke_mlkem_sk: apke_mlkem_sk.try_into().expect("wrong checked length"),
400 apke_mlkem_pk: apke_mlkem_pk.try_into().expect("wrong checked length"),
401 })
402 }
403}
404
405pub struct EphemeralBundleBytes {
407 pub apke_dhakem_sk: [u8; DhAkemPrivateKey::LEN],
408 pub apke_mlkem_sk: [u8; MLKEM768PrivateKey::LEN],
409 pub apke_mlkem_pk: [u8; MLKEM768PublicKey::LEN],
410 pub metadata_sk: [u8; XWingPrivateKey::LEN],
411 pub metadata_pk: [u8; XWingPublicKey::LEN],
412}
413
414impl EphemeralBundleBytes {
415 pub const LEN: usize = DhAkemPrivateKey::LEN
418 + MLKEM768PrivateKey::LEN
419 + MLKEM768PublicKey::LEN
420 + XWingPrivateKey::LEN
421 + XWingPublicKey::LEN;
422
423 fn from_bundle(bundle: &MessageKeyBundle) -> Self {
424 Self {
425 apke_dhakem_sk: *bundle.apke.private_key().dhakem.as_bytes(),
426 apke_mlkem_sk: *bundle.apke.private_key().mlkem.as_bytes(),
427 apke_mlkem_pk: *bundle.apke.public_key().mlkem.as_bytes(),
428 metadata_sk: *bundle.metadata_kp.secret_bytes(),
429 metadata_pk: *bundle.metadata_kp.public_bytes(),
430 }
431 }
432
433 #[cfg_attr(hax, hax_lib::opaque)]
434 fn into_bundle(self) -> MessageKeyBundle {
435 let mut apke_dhakem_pk_bytes = [0u8; DhAkemPublicKey::LEN];
436 provider::curve25519::secret_to_public(&mut apke_dhakem_pk_bytes, &self.apke_dhakem_sk);
437
438 let apke = MessageKeyPair::new(
439 MessagePrivateKey {
440 dhakem: DhAkemPrivateKey::from_bytes(self.apke_dhakem_sk),
441 mlkem: MLKEM768PrivateKey::from_bytes(self.apke_mlkem_sk),
442 },
443 MessagePublicKey {
444 dhakem: DhAkemPublicKey::from_bytes(apke_dhakem_pk_bytes),
445 mlkem: MLKEM768PublicKey::from_bytes(self.apke_mlkem_pk),
446 },
447 );
448 let metadata_kp = MetadataKeyPair::from_key_bytes(self.metadata_sk, self.metadata_pk);
449
450 MessageKeyBundle::new(apke, metadata_kp)
451 }
452
453 pub fn as_bytes(&self) -> Vec<u8> {
456 let mut out = Vec::with_capacity(Self::LEN);
457 out.extend_from_slice(&self.apke_dhakem_sk);
458 out.extend_from_slice(&self.apke_mlkem_sk);
459 out.extend_from_slice(&self.apke_mlkem_pk);
460 out.extend_from_slice(&self.metadata_sk);
461 out.extend_from_slice(&self.metadata_pk);
462 out
463 }
464
465 pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, anyhow::Error> {
472 let bytes: [u8; Self::LEN] = match bytes.try_into() {
473 Ok(bytes) => bytes,
474 Err(_) => {
475 return Err(anyhow::anyhow!(
476 "Invalid EphemeralBundleBytes length: expected {}, got {}",
477 Self::LEN,
478 bytes.len()
479 ));
480 }
481 };
482
483 Ok(Self::from_bytes(&bytes))
484 }
485
486 pub fn from_bytes(bytes: &[u8; Self::LEN]) -> Self {
489 let (apke_dhakem_sk, rest) = bytes.split_at(DhAkemPrivateKey::LEN);
490 let (apke_mlkem_sk, rest) = rest.split_at(MLKEM768PrivateKey::LEN);
491 let (apke_mlkem_pk, rest) = rest.split_at(MLKEM768PublicKey::LEN);
492 let (metadata_sk, metadata_pk) = rest.split_at(XWingPrivateKey::LEN);
493
494 Self {
496 apke_dhakem_sk: apke_dhakem_sk.try_into().expect("wrong checked length"),
497 apke_mlkem_sk: apke_mlkem_sk.try_into().expect("wrong checked length"),
498 apke_mlkem_pk: apke_mlkem_pk.try_into().expect("wrong checked length"),
499 metadata_sk: metadata_sk.try_into().expect("wrong checked length"),
500 metadata_pk: metadata_pk.try_into().expect("wrong checked length"),
501 }
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508 use crate::Enrollable;
509 use crate::api::JournalistApi;
510 use crate::wire::setup::{JournalistEphemeralKeyRequest, JournalistSetupRequest};
511 use rand_chacha::ChaCha20Rng;
512 use rand_core::SeedableRng;
513
514 #[test]
515 fn test_journalist_setup_request_serde_roundtrip() {
516 let mut rng = ChaCha20Rng::seed_from_u64(7);
517 let journalist = Journalist::new(&mut rng, 0);
518 let req = JournalistSetupRequest {
519 enrollment: journalist.enroll(),
520 };
521 let json = serde_json::to_string(&req).expect("serialize");
522 let restored: JournalistSetupRequest = serde_json::from_str(&json).expect("deserialize");
523 assert_eq!(
524 req.enrollment.bundle.as_bytes(),
525 restored.enrollment.bundle.as_bytes()
526 );
527 assert_eq!(
528 req.enrollment.bundle.selfsig.as_bytes(),
529 restored.enrollment.bundle.selfsig.as_bytes()
530 );
531 assert_eq!(
532 req.enrollment.verification_key.into_bytes(),
533 restored.enrollment.verification_key.into_bytes()
534 );
535 }
536
537 #[test]
538 fn test_journalist_setup() {
539 let mut rng = ChaCha20Rng::seed_from_u64(666);
540
541 let journalist = Journalist::new(&mut rng, 5);
542 assert_eq!(journalist.message_keys.len(), 5);
543 let skb: Vec<SignedKeyBundlePublic> = journalist.signed_keybundles();
544 assert_eq!(journalist.message_keys.len(), skb.len());
545
546 let kbs: Vec<&MessageKeyBundle> = journalist.keybundles();
547 assert_eq!(kbs.len(), journalist.message_keys.len());
548
549 for i in 0..kbs.len() {
550 assert_eq!(
551 journalist.message_keys[i].apke.public_key().as_bytes(),
552 kbs[i].apke.public_key().as_bytes()
553 );
554 assert_eq!(
555 journalist.message_keys[i]
556 .metadata_kp
557 .private_key()
558 .as_bytes(),
559 kbs[i].metadata_kp.private_key().as_bytes()
560 );
561 assert_eq!(
562 journalist.message_keys[i]
563 .metadata_kp
564 .public_key()
565 .as_bytes(),
566 kbs[i].metadata_kp.public_key().as_bytes()
567 );
568 }
569 }
570
571 #[test]
572 fn test_journalist_enroll_selfsig() {
573 let mut rng = ChaCha20Rng::seed_from_u64(666);
574
575 let journalist = Journalist::new(&mut rng, 5);
576 let e = journalist.enroll();
577
578 journalist
579 .signing_key()
580 .verify(&e.bundle.bundle_bytes(), &e.bundle.selfsig)
581 .expect("Need correct enrollment sig");
582 }
583
584 use proptest::prelude::*;
585
586 proptest! {
587 #[test]
588 fn test_journalist_long_term_bytes_roundtrip(rng_seed: u64) {
589 let mut rng = ChaCha20Rng::seed_from_u64(rng_seed);
590 let original = Journalist::new(&mut rng, 0);
591 let parts = original.long_term_bytes();
592 let restored =
593 Journalist::from_long_term_bytes(parts).expect("valid long-term bytes");
594
595 prop_assert_eq!(
597 original.signing_key.vk.into_bytes(),
598 restored.signing_key.vk.into_bytes()
599 );
600 prop_assert_eq!(
601 original.signed_longterm_key_bundle().as_bytes(),
602 restored.signed_longterm_key_bundle().as_bytes()
603 );
604 prop_assert!(restored.message_keys.is_empty());
605 }
606
607 #[test]
608 fn test_journalist_long_term_bytes_serde_roundtrip(rng_seed: u64) {
609 let mut rng = ChaCha20Rng::seed_from_u64(rng_seed);
610 let parts = Journalist::new(&mut rng, 0).long_term_bytes();
611
612 let bytes = parts.as_bytes();
613 prop_assert_eq!(bytes.len(), JournalistLongTermBytes::LEN);
614
615 let restored = JournalistLongTermBytes::from_bytes(&bytes).expect("valid length");
616 prop_assert_eq!(restored.sig_seed, parts.sig_seed);
617 prop_assert_eq!(restored.fetch_sk, parts.fetch_sk);
618 prop_assert_eq!(restored.apke_dhakem_sk, parts.apke_dhakem_sk);
619 prop_assert_eq!(restored.apke_mlkem_sk, parts.apke_mlkem_sk);
620 prop_assert_eq!(restored.apke_mlkem_pk, parts.apke_mlkem_pk);
621 }
622
623 #[test]
624 fn test_ephemeral_bundle_bytes_roundtrip(rng_seed: u64, n in 0usize..4) {
625 let mut rng = ChaCha20Rng::seed_from_u64(rng_seed);
626 let mut original = Journalist::new(&mut rng, 0);
627 original.generate_ephemeral_bundles(&mut rng, n);
628
629 let persisted: Vec<EphemeralBundleBytes> = original
630 .ephemeral_bundle_bytes()
631 .into_iter()
632 .map(|b| {
633 let bytes = b.as_bytes();
634 prop_assert_eq!(bytes.len(), EphemeralBundleBytes::LEN);
635 Ok(EphemeralBundleBytes::try_from_bytes(&bytes).expect("valid length"))
636 })
637 .collect::<Result<_, TestCaseError>>()?;
638
639 let mut restored = Journalist::from_long_term_bytes(original.long_term_bytes())
640 .expect("valid long-term bytes");
641 restored.load_ephemeral_bundles(persisted);
642
643 let orig_pub = original.signed_keybundles();
644 let restored_pub = restored.signed_keybundles();
645 prop_assert_eq!(orig_pub.len(), n);
646 prop_assert_eq!(restored_pub.len(), n);
647 for (a, b) in orig_pub.iter().zip(restored_pub.iter()) {
648 prop_assert_eq!(a.0.as_bytes(), b.0.as_bytes());
649 prop_assert_eq!(a.1.as_bytes(), b.1.as_bytes());
650 }
651 }
652
653 #[test]
654 fn test_journalist_ephemeral_key_request_serde_roundtrip(rng_seed: u64, n in 1usize..4) {
655 let mut rng = ChaCha20Rng::seed_from_u64(rng_seed);
656 let mut journalist = Journalist::new(&mut rng, 0);
657 journalist.generate_ephemeral_bundles(&mut rng, n);
658
659 let req = journalist.create_ephemeral_key_request();
660 let json = serde_json::to_string(&req).expect("serialize");
661 let restored: JournalistEphemeralKeyRequest =
662 serde_json::from_str(&json).expect("deserialize");
663
664 prop_assert_eq!(
665 req.verifying_key.into_bytes(),
666 restored.verifying_key.into_bytes()
667 );
668 prop_assert_eq!(req.bundles.len(), n);
669 prop_assert_eq!(restored.bundles.len(), n);
670 for (a, b) in req.bundles.iter().zip(restored.bundles.iter()) {
671 prop_assert_eq!(a.0.as_bytes(), b.0.as_bytes());
672 prop_assert_eq!(a.1.as_bytes(), b.1.as_bytes());
673 }
674 }
675 }
676
677 #[test]
678 fn test_journalist_long_term_bytes_from_bytes_rejects_wrong_length() {
679 assert!(JournalistLongTermBytes::from_bytes(&[]).is_err());
680 assert!(
681 JournalistLongTermBytes::from_bytes(&[0u8; JournalistLongTermBytes::LEN - 1]).is_err()
682 );
683 assert!(
684 JournalistLongTermBytes::from_bytes(&[0u8; JournalistLongTermBytes::LEN + 1]).is_err()
685 );
686 }
687}