Skip to main content

wacore_libsignal/protocol/
sender_keys.rs

1//
2// Copyright 2020-2021 Signal Messenger, LLC.
3// SPDX-License-Identifier: AGPL-3.0-only
4//
5
6use std::collections::VecDeque;
7
8use buffa::{Message, MessageField};
9
10use hmac::{HmacReset, KeyInit, Mac};
11use sha2::Sha256;
12
13use crate::protocol::counter_lease::CounterLease;
14use crate::protocol::crypto::hmac_sha256;
15use crate::protocol::record_components::{
16    SenderKeyRecordComponents, sender_state_components_from_structure,
17    sender_state_structure_from_components,
18};
19use crate::protocol::stores::{
20    SenderKeyRecordStructure, SenderKeyStateStructure, sender_key_state_structure,
21};
22use crate::protocol::{PrivateKey, PublicKey, SignalProtocolError, consts};
23use subtle::ConstantTimeEq;
24
25/// A distinct error type to keep from accidentally propagating deserialization errors.
26#[derive(Debug)]
27pub struct InvalidSenderKeySessionError(&'static str);
28
29impl std::fmt::Display for InvalidSenderKeySessionError {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        self.0.fmt(f)
32    }
33}
34
35#[derive(Debug, Clone)]
36pub struct SenderMessageKey {
37    iteration: u32,
38    iv: [u8; 16],
39    cipher_key: [u8; 32],
40    seed: [u8; 32],
41}
42
43impl SenderMessageKey {
44    pub fn new(iteration: u32, seed: [u8; 32]) -> Self {
45        let mut derived = [0u8; 48];
46        hkdf::Hkdf::<Sha256>::new(None, &seed)
47            .expand(b"WhisperGroup", &mut derived)
48            .expect("valid output length");
49        Self {
50            iteration,
51            seed,
52            iv: derived[0..16].try_into().expect("correct iv length"),
53            cipher_key: derived[16..48]
54                .try_into()
55                .expect("correct cipher_key length"),
56        }
57    }
58
59    pub fn iteration(&self) -> u32 {
60        self.iteration
61    }
62
63    pub fn iv(&self) -> &[u8] {
64        &self.iv
65    }
66
67    pub fn cipher_key(&self) -> &[u8] {
68        &self.cipher_key
69    }
70}
71
72/// Backlog entry for a skipped message key: only the (iteration, seed) pair the
73/// full [`SenderMessageKey`] is re-derived from on removal. `Copy`, so the
74/// `Arc::make_mut` copy-on-write of the backlog is one flat memcpy — with the
75/// protobuf element type, the first COW after a cold load promoted one `Bytes`
76/// seed (a shared-control-block malloc) per cached key.
77#[derive(Debug, Clone, Copy)]
78struct StoredMessageKey {
79    iteration: u32,
80    seed: [u8; 32],
81}
82
83impl StoredMessageKey {
84    fn from_protobuf(smk: &sender_key_state_structure::SenderMessageKey) -> Self {
85        // Seed is validated at deserialization time; fall back to zeroes on corrupt in-memory data.
86        Self {
87            iteration: smk.iteration.unwrap_or_default(),
88            seed: smk
89                .seed
90                .as_deref()
91                .and_then(|b| b.try_into().ok())
92                .unwrap_or_default(),
93        }
94    }
95
96    fn as_protobuf(&self) -> sender_key_state_structure::SenderMessageKey {
97        sender_key_state_structure::SenderMessageKey {
98            iteration: Some(self.iteration),
99            seed: Some(bytes::Bytes::copy_from_slice(&self.seed)),
100        }
101    }
102}
103
104fn seed_to_array(seed: Option<&bytes::Bytes>) -> Result<[u8; 32], SignalProtocolError> {
105    let Some(seed) = seed else {
106        return Err(SignalProtocolError::InvalidProtobufEncoding);
107    };
108    seed.as_ref()
109        .try_into()
110        .map_err(|_| SignalProtocolError::InvalidProtobufEncoding)
111}
112
113#[derive(Debug, Clone, Copy)]
114pub struct SenderChainKey {
115    iteration: u32,
116    chain_key: [u8; 32],
117}
118
119impl SenderChainKey {
120    const MESSAGE_KEY_SEED: u8 = 0x01;
121    const CHAIN_KEY_SEED: u8 = 0x02;
122
123    pub(crate) fn new(iteration: u32, chain_key: [u8; 32]) -> Self {
124        Self {
125            iteration,
126            chain_key,
127        }
128    }
129
130    pub fn iteration(&self) -> u32 {
131        self.iteration
132    }
133
134    pub fn seed(&self) -> &[u8; 32] {
135        &self.chain_key
136    }
137
138    pub fn next(&self) -> Result<SenderChainKey, SignalProtocolError> {
139        let new_iteration = self.iteration.checked_add(1).ok_or_else(|| {
140            SignalProtocolError::InvalidState(
141                "sender_chain_key_next",
142                "Sender chain is too long".into(),
143            )
144        })?;
145
146        Ok(SenderChainKey::new(
147            new_iteration,
148            self.get_derivative(Self::CHAIN_KEY_SEED),
149        ))
150    }
151
152    pub fn sender_message_key(&self) -> SenderMessageKey {
153        SenderMessageKey::new(self.iteration, self.get_derivative(Self::MESSAGE_KEY_SEED))
154    }
155
156    /// Compute both sender message key and next chain key in one call, reusing HMAC key setup.
157    #[inline]
158    pub fn step_with_message_key(&self) -> Result<(SenderMessageKey, Self), SignalProtocolError> {
159        let new_iteration = self.iteration.checked_add(1).ok_or_else(|| {
160            SignalProtocolError::InvalidState(
161                "sender_chain_key_step",
162                "Sender chain is too long".into(),
163            )
164        })?;
165
166        let mut hmac = HmacReset::<Sha256>::new_from_slice(&self.chain_key)
167            .expect("HMAC-SHA256 should accept any size key");
168
169        hmac.update(&[Self::MESSAGE_KEY_SEED]);
170        let message_key_seed: [u8; 32] = hmac.finalize_reset().into_bytes().into();
171
172        hmac.update(&[Self::CHAIN_KEY_SEED]);
173        let next_chain_key: [u8; 32] = hmac.finalize().into_bytes().into();
174
175        let message_key = SenderMessageKey::new(self.iteration, message_key_seed);
176        let next_chain = Self {
177            iteration: new_iteration,
178            chain_key: next_chain_key,
179        };
180
181        Ok((message_key, next_chain))
182    }
183
184    #[inline]
185    fn get_derivative(&self, label: u8) -> [u8; 32] {
186        let label = [label];
187        hmac_sha256(&self.chain_key, &label)
188    }
189
190    pub(crate) fn as_protobuf(&self) -> sender_key_state_structure::SenderChainKey {
191        use bytes::Bytes;
192        sender_key_state_structure::SenderChainKey {
193            iteration: Some(self.iteration),
194            seed: Some(Bytes::copy_from_slice(&self.chain_key)),
195        }
196    }
197}
198
199#[derive(Clone)]
200pub struct SenderKeyState {
201    state: SenderKeyStateStructure,
202    /// The cached out-of-order message keys, held behind an `Arc` so cloning the
203    /// state (and thus the whole `SenderKeyRecord` on every group load) is a
204    /// refcount bump instead of a deep copy of up to `MAX_MESSAGE_KEYS` keys.
205    /// The in-order decrypt path never touches it, so a load there clones nothing
206    /// even when a prior out-of-order burst left a large backlog; a mutation
207    /// (skip-ahead caching or an out-of-order removal) pays one copy-on-write via
208    /// `Arc::make_mut`, leaving any sharing clone (the cache's copy) intact.
209    /// `state.sender_message_keys` is kept empty in memory; this is the source of
210    /// truth, reassembled into the protobuf only at `as_protobuf` (serialization).
211    message_keys: std::sync::Arc<Vec<StoredMessageKey>>,
212    /// The current sender chain key, held as a `Copy` value instead of in the
213    /// protobuf. The chain seed is a `Bytes` in the generated structure, so
214    /// keeping it there made every record clone (and the copy-on-write on every
215    /// encrypt/decrypt advance) promote that `Bytes` to a shared allocation.
216    /// Same source-of-truth-outside-the-protobuf trick as `message_keys`:
217    /// `state.sender_chain_key` stays empty in memory, reassembled only at
218    /// `as_protobuf`. `None` only for a structurally invalid state.
219    sender_chain: Option<SenderChainKey>,
220    /// Parsed signing key with its XEdDSA cache pre-derived, memoized so the
221    /// per-send signature skips a basepoint multiplication. Clones carry the
222    /// warm value, and a record cache stores this object back after every
223    /// send, so the memo persists for the cache lifetime. Never persisted;
224    /// rebuilt lazily after a cold load. If a signing-key setter is ever
225    /// added, it must reset this memo.
226    ///
227    /// The payoff is the caller's to collect, in one of two ways: keep the
228    /// record, or keep the derivation and hand it back through
229    /// [`SenderKeyState::prewarm_signing_key`]. A caller that does neither, as
230    /// one persisting through `into_components` and rebuilding per operation
231    /// does, re-derives per message.
232    /// `benches/sender_key_derivation_benchmark.rs` in `wacore` measures all
233    /// three shapes.
234    signing_key_memo: std::sync::OnceLock<PrivateKey>,
235    /// Receive-side mirror of `signing_key_memo`: cached verifier whose
236    /// Edwards derivations are reused across every incoming message under
237    /// this sender key. Same lifecycle rules as above.
238    verifying_key_memo: std::sync::OnceLock<crate::core::curve::PreparedVerifyingKey>,
239}
240
241// Manual impl with the signing key REDACTED: the protobuf state embeds the
242// serialized private signing key, and the previous derive printed it raw
243// into any `{:?}` log or panic message.
244impl std::fmt::Debug for SenderKeyState {
245    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246        f.debug_struct("SenderKeyState")
247            .field("chain_id", &self.chain_id())
248            .field(
249                "chain_iteration",
250                &self.sender_chain_key().map(|c| c.iteration()),
251            )
252            .field("message_keys", &self.message_keys.len())
253            .field("signing_key", &"<redacted>")
254            .finish_non_exhaustive()
255    }
256}
257
258impl SenderKeyState {
259    pub fn new(
260        _message_version: u8,
261        chain_id: u32,
262        iteration: u32,
263        chain_key: &[u8],
264        signature_key: PublicKey,
265        signature_private_key: Option<PrivateKey>,
266    ) -> Result<SenderKeyState, SignalProtocolError> {
267        use bytes::Bytes;
268        let chain_key_arr: [u8; 32] = chain_key
269            .try_into()
270            .map_err(|_| SignalProtocolError::InvalidProtobufEncoding)?;
271        let sender_chain = Some(SenderChainKey::new(iteration, chain_key_arr));
272        let state = SenderKeyStateStructure {
273            sender_key_id: Some(chain_id),
274            // Source of truth is `sender_chain`; the protobuf field stays empty
275            // in memory and is reassembled at as_protobuf.
276            sender_chain_key: MessageField::none(),
277            sender_signing_key: MessageField::some(sender_key_state_structure::SenderSigningKey {
278                public: Some(Bytes::copy_from_slice(&signature_key.serialize())),
279                private: signature_private_key
280                    .as_ref()
281                    .map(|k| Bytes::copy_from_slice(k.serialize().as_ref())),
282            }),
283            sender_message_keys: vec![],
284        };
285
286        let signing_key_memo = std::sync::OnceLock::new();
287        if let Some(key) = signature_private_key {
288            key.precompute_signing_cache();
289            let _ = signing_key_memo.set(key);
290        }
291        let verifying_key_memo = std::sync::OnceLock::new();
292        if signing_key_memo.get().is_none() {
293            // Receive-side state (no private key): build the verifier and derive its
294            // Edwards entries here, at SKDM processing, once per sender rotation.
295            // Send-side states skip the allocation; it builds lazily if ever asked.
296            let verifier = crate::core::curve::PreparedVerifyingKey::new(&signature_key);
297            verifier.precompute();
298            let _ = verifying_key_memo.set(verifier);
299        }
300        Ok(Self {
301            state,
302            message_keys: std::sync::Arc::new(Vec::new()),
303            sender_chain,
304            signing_key_memo,
305            verifying_key_memo,
306        })
307    }
308
309    pub(crate) fn from_protobuf(mut state: SenderKeyStateStructure) -> Self {
310        // Move the backlog out of the protobuf into the shared Arc so the
311        // in-memory `state` stays empty; see `message_keys` field docs.
312        let message_keys = std::sync::Arc::new(
313            std::mem::take(&mut state.sender_message_keys)
314                .iter()
315                .map(StoredMessageKey::from_protobuf)
316                .collect::<Vec<_>>(),
317        );
318        // Likewise move the chain key out into the Copy field; the seed was
319        // validated at deserialize before this runs.
320        let sender_chain = state.sender_chain_key.take().and_then(|sc| {
321            let seed: [u8; 32] = sc.seed.as_deref()?.try_into().ok()?;
322            Some(SenderChainKey::new(sc.iteration.unwrap_or_default(), seed))
323        });
324        Self {
325            state,
326            message_keys,
327            sender_chain,
328            signing_key_memo: std::sync::OnceLock::new(),
329            verifying_key_memo: std::sync::OnceLock::new(),
330        }
331    }
332
333    pub fn message_version(&self) -> u32 {
334        3
335    }
336
337    pub fn chain_id(&self) -> u32 {
338        self.state.sender_key_id.unwrap_or_default()
339    }
340
341    pub fn sender_chain_key(&self) -> Option<SenderChainKey> {
342        self.sender_chain
343    }
344
345    pub fn set_sender_chain_key(&mut self, chain_key: SenderChainKey) {
346        self.sender_chain = Some(chain_key);
347    }
348
349    /// Advance the sender chain up to a reload's reserved iteration ceiling so no
350    /// possibly-spent iteration below it stays derivable. Bounded by
351    /// `MAX_RESERVATION_FAST_FORWARD`; a target past that is a corrupt
352    /// reservation and errors rather than looping the KDF unboundedly. Mirrors
353    /// `SessionRecord::fast_forward_sender_chain`.
354    pub(crate) fn fast_forward_sender_chain(
355        &mut self,
356        target: u32,
357    ) -> Result<(), SignalProtocolError> {
358        let Some(mut chain_key) = self.sender_chain_key() else {
359            return Ok(());
360        };
361        if target.saturating_sub(chain_key.iteration()) > consts::MAX_RESERVATION_FAST_FORWARD {
362            return Err(SignalProtocolError::InvalidState(
363                "fast_forward_sender_chain",
364                "reserved sender-key iteration implausibly far ahead".into(),
365            ));
366        }
367        if chain_key.iteration() >= target {
368            return Ok(());
369        }
370        while chain_key.iteration() < target {
371            chain_key = chain_key.next()?;
372        }
373        self.set_sender_chain_key(chain_key);
374        Ok(())
375    }
376
377    pub fn signing_key_public(&self) -> Result<PublicKey, InvalidSenderKeySessionError> {
378        if let Some(signing_key) = self.state.sender_signing_key.as_option() {
379            let public = signing_key
380                .public
381                .as_ref()
382                .ok_or(InvalidSenderKeySessionError("missing public key bytes"))?;
383            PublicKey::try_from(&public[..])
384                .map_err(|_| InvalidSenderKeySessionError("invalid public signing key"))
385        } else {
386            Err(InvalidSenderKeySessionError("missing signing key"))
387        }
388    }
389
390    /// Cached verifier for this sender's signing key; the Edwards
391    /// derivations warm on first use and persist with the in-memory state.
392    pub fn signing_key_verifier(
393        &self,
394    ) -> Result<&crate::core::curve::PreparedVerifyingKey, InvalidSenderKeySessionError> {
395        if let Some(verifier) = self.verifying_key_memo.get() {
396            return Ok(verifier);
397        }
398        let verifier = crate::core::curve::PreparedVerifyingKey::new(&self.signing_key_public()?);
399        // Benign race: concurrent firsts compute the same value.
400        let _ = self.verifying_key_memo.set(verifier);
401        Ok(self
402            .verifying_key_memo
403            .get()
404            .expect("set on the line above"))
405    }
406
407    /// Hand this state a signing key, so it skips the basepoint multiplication
408    /// the lazy path would pay.
409    ///
410    /// For a caller that rebuilds the record per operation, the memo never
411    /// survives to be reused; this lets it keep the derivation instead of the
412    /// record. Doing so makes the caller the owner of that cache, of how long
413    /// it lives, and of the private material in it, which the state would
414    /// otherwise hold only for its own lifetime.
415    ///
416    /// **Hold the key warm to collect anything.** A cold one is warmed here
417    /// rather than refused, because every read of the memo hands out a clone
418    /// and clones of a cold key each re-derive; but warming it costs the
419    /// derivation this call exists to skip, once per handover. Warm it once
420    /// when it enters your cache, with [`PrivateKey::precompute_signing_cache`].
421    ///
422    /// The key must be this state's own; one that is not is rejected and the
423    /// state keeps deriving for itself. A memo already populated is left alone,
424    /// since it holds the same derivation.
425    pub fn prewarm_signing_key(&self, key: PrivateKey) -> Result<(), InvalidSenderKeySessionError> {
426        if !bool::from(self.signing_key_bytes()?.ct_eq(key.serialize())) {
427            return Err(InvalidSenderKeySessionError(
428                "prewarmed signing key belongs to another state",
429            ));
430        }
431        // Check the slot before deriving: whatever is already there is warm,
432        // since the lazy path warms before it memoizes, and deriving first
433        // would spend a basepoint multiplication only to find `set` refuse it.
434        if self.signing_key_memo.get().is_some() {
435            return Ok(());
436        }
437        // Every read of the memo hands out a clone, and clones of a cold key
438        // each re-derive, so accepting one as passed would cost a derivation
439        // per message rather than none.
440        key.precompute_signing_cache();
441        let _ = self.signing_key_memo.set(key);
442        Ok(())
443    }
444
445    /// Receive-side counterpart of [`Self::prewarm_signing_key`], carrying the
446    /// verifier's Edwards derivations. The verifier holds only public material,
447    /// so the caller takes on its lifetime and nothing else.
448    ///
449    /// Same rule about holding it warm, with one difference in the caller's
450    /// favour: a verifier's entries sit behind a shared handle, so warming a
451    /// cold one warms every clone of it, including the copy in your cache.
452    pub fn prewarm_verifying_key(
453        &self,
454        verifier: crate::core::curve::PreparedVerifyingKey,
455    ) -> Result<(), InvalidSenderKeySessionError> {
456        if !verifier.is_for(&self.signing_key_public()?) {
457            return Err(InvalidSenderKeySessionError(
458                "prewarmed verifier belongs to another state",
459            ));
460        }
461        // Warm whichever instance is retained, not the one passed in: a lazy
462        // first use installs a verifier without deriving its entries, and that
463        // one stays when this `set` finds the memo already populated. The
464        // signing side needs no such care, since its lazy path warms before it
465        // memoizes.
466        let _ = self.verifying_key_memo.set(verifier);
467        if let Some(retained) = self.verifying_key_memo.get() {
468            retained.precompute();
469        }
470        Ok(())
471    }
472
473    /// This state's private signing key in the clamped form `PrivateKey` uses,
474    /// so a caller's key compares equal to it without either side deriving.
475    fn signing_key_bytes(&self) -> Result<[u8; 32], InvalidSenderKeySessionError> {
476        let signing_key = self
477            .state
478            .sender_signing_key
479            .as_option()
480            .ok_or(InvalidSenderKeySessionError("missing signing key"))?;
481        let private = signing_key
482            .private
483            .as_ref()
484            .ok_or(InvalidSenderKeySessionError("missing private key bytes"))?;
485        Ok(*PrivateKey::deserialize(private)
486            .map_err(|_| InvalidSenderKeySessionError("invalid private signing key"))?
487            .serialize())
488    }
489
490    pub fn signing_key_private(&self) -> Result<PrivateKey, InvalidSenderKeySessionError> {
491        if let Some(key) = self.signing_key_memo.get() {
492            return Ok(key.clone());
493        }
494        if let Some(signing_key) = self.state.sender_signing_key.as_option() {
495            let private = signing_key
496                .private
497                .as_ref()
498                .ok_or(InvalidSenderKeySessionError("missing private key bytes"))?;
499            let key = PrivateKey::deserialize(private)
500                .map_err(|_| InvalidSenderKeySessionError("invalid private signing key"))?;
501            // Warm BEFORE memoizing: the caller gets a clone, and clones of a
502            // cold key would each re-derive the cache; clones of a warm one
503            // carry it. Benign race: concurrent firsts compute equal values.
504            key.precompute_signing_cache();
505            let _ = self.signing_key_memo.set(key.clone());
506            Ok(key)
507        } else {
508            Err(InvalidSenderKeySessionError("missing signing key"))
509        }
510    }
511
512    /// Test-only: whether the signing-key memo is populated.
513    #[cfg(test)]
514    pub(crate) fn signing_key_memo_initialized(&self) -> bool {
515        self.signing_key_memo.get().is_some()
516    }
517
518    pub(crate) fn as_protobuf(&self) -> SenderKeyStateStructure {
519        debug_assert!(
520            self.state.sender_message_keys.is_empty()
521                && self.state.sender_chain_key.as_option().is_none(),
522            "backlog and chain key must live only in their Copy/Arc fields; the protobuf copies stay empty"
523        );
524        let mut state = self.state.clone();
525        state.sender_message_keys = self
526            .message_keys
527            .iter()
528            .map(StoredMessageKey::as_protobuf)
529            .collect();
530        state.sender_chain_key = self
531            .sender_chain
532            .as_ref()
533            .map_or_else(MessageField::none, |c| MessageField::some(c.as_protobuf()));
534        state
535    }
536
537    fn into_protobuf(mut self) -> SenderKeyStateStructure {
538        debug_assert!(
539            self.state.sender_message_keys.is_empty()
540                && self.state.sender_chain_key.as_option().is_none(),
541            "backlog and chain key must have a single in-memory owner"
542        );
543        let message_keys = std::sync::Arc::try_unwrap(self.message_keys)
544            .unwrap_or_else(|shared| shared.as_ref().clone());
545        self.state.sender_message_keys = message_keys
546            .iter()
547            .map(StoredMessageKey::as_protobuf)
548            .collect();
549        self.state.sender_chain_key = self
550            .sender_chain
551            .as_ref()
552            .map_or_else(MessageField::none, |chain| {
553                MessageField::some(chain.as_protobuf())
554            });
555        self.state
556    }
557
558    pub fn add_sender_message_key(&mut self, sender_message_key: &SenderMessageKey) {
559        let keys = std::sync::Arc::make_mut(&mut self.message_keys);
560        keys.push(StoredMessageKey {
561            iteration: sender_message_key.iteration,
562            seed: sender_message_key.seed,
563        });
564        // AMORTIZED EVICTION: Only prune when exceeding MAX + threshold.
565        // This reduces O(n) drain() calls from every insert to once every PRUNE_THRESHOLD inserts.
566        let len = keys.len();
567        if len > consts::MAX_MESSAGE_KEYS + consts::MESSAGE_KEY_PRUNE_THRESHOLD {
568            let excess = len - consts::MAX_MESSAGE_KEYS;
569            keys.drain(..excess);
570        }
571    }
572
573    pub(crate) fn remove_sender_message_key(&mut self, iteration: u32) -> Option<SenderMessageKey> {
574        // Find first so a miss (e.g. a duplicate message) returns without the
575        // copy-on-write clone that `make_mut` would force.
576        let index = self
577            .message_keys
578            .iter()
579            .position(|x| x.iteration == iteration)?;
580        let smk = std::sync::Arc::make_mut(&mut self.message_keys).remove(index);
581        Some(SenderMessageKey::new(smk.iteration, smk.seed))
582    }
583}
584
585#[derive(Debug, Clone)]
586pub struct SenderKeyRecord {
587    states: VecDeque<SenderKeyState>,
588    /// Durability lease over sender-chain iterations, mirroring
589    /// `SessionRecord`'s for DM, or the consumer's declaration that it needs
590    /// none. Iterations below the reserved ceiling ride the coalesced
591    /// write-behind; only the send that raises it gates the wire, and a reload
592    /// fast-forwards past it so no possibly-spent iteration is re-derivable.
593    /// Reset to 0 on any state change (rotation/promotion), which forces the
594    /// next send to re-reserve and gate. The wire-gate flag it carries is
595    /// transient and never serialized; the store layer converts it into flush
596    /// gating.
597    lease: CounterLease,
598}
599
600/// Local-only field appended to the serialized record for `reserved_iteration`.
601/// The vendored `SenderKeyRecordStructure` proto is untouched; the generated
602/// decoder skips this unknown top-level field and `deserialize` scans it out.
603/// Matches the field-number scheme `SessionRecord` uses for its DM counterpart.
604const RESERVED_ITERATION_FIELD: u32 = super::local_field::COUNTER_RESERVATION_FIELD;
605
606impl SenderKeyRecord {
607    pub fn new_empty() -> Self {
608        Self {
609            states: VecDeque::with_capacity(consts::MAX_SENDER_KEY_STATES),
610            lease: CounterLease::default(),
611        }
612    }
613
614    /// Builds a record from validated protocol components.
615    ///
616    /// Components do not carry process-local durability metadata, so the
617    /// imported current chain starts a fresh reservation lifecycle. Historical
618    /// states are bounded to the same limit enforced by record mutation and
619    /// deserialization.
620    pub fn from_components(value: SenderKeyRecordComponents) -> Result<Self, SignalProtocolError> {
621        let states = value
622            .states
623            .into_iter()
624            .take(consts::MAX_SENDER_KEY_STATES)
625            .map(sender_state_structure_from_components)
626            .map(|state| state.map(SenderKeyState::from_protobuf))
627            .collect::<Result<VecDeque<_>, _>>()?;
628
629        Ok(Self {
630            states,
631            lease: CounterLease::default(),
632        })
633    }
634
635    /// Consumes the record and projects its protocol components.
636    ///
637    /// Any durably reserved sender range is advanced to its exclusive ceiling
638    /// before export so rebuilding the record cannot derive a possibly spent
639    /// message key again.
640    pub fn into_components(mut self) -> Result<SenderKeyRecordComponents, SignalProtocolError> {
641        let reserved_iteration = self.lease.ceiling();
642        if reserved_iteration > 0
643            && let Some(state) = self.states.front_mut()
644        {
645            state.fast_forward_sender_chain(reserved_iteration)?;
646        }
647        let states = self
648            .states
649            .into_iter()
650            .map(SenderKeyState::into_protobuf)
651            .map(sender_state_components_from_structure)
652            .collect::<Result<Vec<_>, _>>()?;
653        Ok(SenderKeyRecordComponents { states })
654    }
655
656    pub fn is_empty(&self) -> bool {
657        self.states.is_empty()
658    }
659
660    /// Iterations strictly below this ceiling are covered by a durable
661    /// reservation and their sends need no synchronous flush.
662    pub fn reserved_iteration(&self) -> u32 {
663        self.lease.ceiling()
664    }
665
666    /// Waive counter leasing on this record.
667    ///
668    /// The group counterpart of
669    /// [`SessionRecord::waive_counter_lease`](crate::protocol::SessionRecord::waive_counter_lease),
670    /// including the guarantee it gives up.
671    pub fn waive_counter_lease(&mut self) -> Result<(), SignalProtocolError> {
672        // Materialize before dropping the ceiling: a chain too stale to advance
673        // leaves the record on its lease rather than free to reissue the
674        // iterations that ceiling covers.
675        let ceiling = self.lease.ceiling();
676        if ceiling > 0
677            && let Some(state) = self.states.front_mut()
678        {
679            state.fast_forward_sender_chain(ceiling)?;
680        }
681        self.lease.waive();
682        Ok(())
683    }
684
685    /// Lease a fresh batch of iterations after `spent_iteration` reached the
686    /// current ceiling. Marks the record wire-gated: the caller's ciphertext
687    /// must not hit the wire until a flush persists the raised ceiling. Mirrors
688    /// `SessionRecord::reserve_sender_chain_counters`.
689    pub fn reserve_iterations(&mut self, spent_iteration: u32) {
690        self.lease.reserve(spent_iteration);
691    }
692
693    pub fn deserialize(buf: &[u8]) -> Result<SenderKeyRecord, SignalProtocolError> {
694        Self::deserialize_inner(buf, None)
695    }
696
697    /// A matching live cache proves this snapshot was not recovered after a crash.
698    #[doc(hidden)]
699    pub fn deserialize_for_store(
700        buf: &[u8],
701        incarnation: &[u8; 16],
702    ) -> Result<SenderKeyRecord, SignalProtocolError> {
703        Self::deserialize_inner(buf, Some(incarnation))
704    }
705
706    fn deserialize_inner(
707        buf: &[u8],
708        incarnation: Option<&[u8; 16]>,
709    ) -> Result<SenderKeyRecord, SignalProtocolError> {
710        let skr = waproto::codec::sender_key_record_decode(buf)
711            .map_err(|_| SignalProtocolError::InvalidProtobufEncoding)?;
712
713        let mut states = VecDeque::with_capacity(
714            skr.sender_key_states
715                .len()
716                .min(consts::MAX_SENDER_KEY_STATES),
717        );
718        for state in skr
719            .sender_key_states
720            .into_iter()
721            .take(consts::MAX_SENDER_KEY_STATES)
722        {
723            // Validate seeds eagerly so callers get a clear error on corrupt data.
724            if let Some(sender_chain) = state.sender_chain_key.as_option() {
725                let _ = seed_to_array(sender_chain.seed.as_ref())?;
726            }
727            for smk in &state.sender_message_keys {
728                let _ = seed_to_array(smk.seed.as_ref())?;
729            }
730            states.push_back(SenderKeyState::from_protobuf(state));
731        }
732
733        let local_fields =
734            super::local_field::decode_local_record_fields(buf, RESERVED_ITERATION_FIELD, || {
735                SignalProtocolError::InvalidProtobufEncoding
736            })?;
737        let reserved_iteration = local_fields.reservation;
738        let trusted_reload =
739            incarnation.is_some_and(|current| local_fields.incarnation == Some(*current));
740
741        // Only an untrusted snapshot may have spent its still-reserved range.
742        if !trusted_reload
743            && reserved_iteration > 0
744            && let Some(state) = states.front_mut()
745        {
746            state.fast_forward_sender_chain(reserved_iteration)?;
747        }
748
749        Ok(Self {
750            states,
751            lease: CounterLease::from_persisted_ceiling(reserved_iteration),
752        })
753    }
754
755    /// Flag an outbound chain advance; cleared by the store layer once it
756    /// owns the durability gate.
757    pub fn mark_wire_gated(&mut self) {
758        self.lease.set_pending_flush(true);
759    }
760
761    pub fn is_wire_gated(&self) -> bool {
762        self.lease.is_pending_flush()
763    }
764
765    pub fn clear_wire_gated(&mut self) {
766        self.lease.set_pending_flush(false);
767    }
768
769    pub fn sender_key_state(&self) -> Result<&SenderKeyState, InvalidSenderKeySessionError> {
770        if !self.states.is_empty() {
771            return Ok(&self.states[0]);
772        }
773        Err(InvalidSenderKeySessionError("empty sender key state"))
774    }
775
776    pub fn sender_key_state_mut(
777        &mut self,
778    ) -> Result<&mut SenderKeyState, InvalidSenderKeySessionError> {
779        if !self.states.is_empty() {
780            return Ok(&mut self.states[0]);
781        }
782        Err(InvalidSenderKeySessionError("empty sender key state"))
783    }
784
785    pub(crate) fn sender_key_state_for_chain_id(
786        &mut self,
787        chain_id: u32,
788    ) -> Option<&mut SenderKeyState> {
789        for i in 0..self.states.len() {
790            if self.states[i].chain_id() == chain_id {
791                return Some(&mut self.states[i]);
792            }
793        }
794        None
795    }
796
797    pub(crate) fn chain_ids_for_logging(&self) -> impl ExactSizeIterator<Item = u32> + '_ {
798        self.states.iter().map(|state| state.chain_id())
799    }
800
801    pub fn add_sender_key_state(
802        &mut self,
803        message_version: u8,
804        chain_id: u32,
805        iteration: u32,
806        chain_key: &[u8],
807        signature_key: PublicKey,
808        signature_private_key: Option<PrivateKey>,
809    ) -> Result<(), SignalProtocolError> {
810        let existing_state = self.remove_state(chain_id, signature_key);
811
812        if self.remove_states_with_chain_id(chain_id) > 0 {
813            log::warn!(
814                "Removed a matching chain_id ({chain_id}) found with a different public key"
815            );
816        }
817
818        let state = match existing_state {
819            None => SenderKeyState::new(
820                message_version,
821                chain_id,
822                iteration,
823                chain_key,
824                signature_key,
825                signature_private_key,
826            )?,
827            Some(state) => state,
828        };
829
830        while self.states.len() >= consts::MAX_SENDER_KEY_STATES {
831            self.states.pop_back();
832        }
833
834        self.states.push_front(state);
835        // Reset the reservation unconditionally. It is a record-level ceiling for
836        // whatever chain is current, and this call may have replaced or reordered
837        // the current chain. Resetting is always safe (the next send re-reserves
838        // and re-gates); keeping a stale ceiling across a chain change is not,
839        // since a lower-iteration chain would treat already-covered iterations as
840        // durable and re-derive a spent (key, IV). In practice this is a no-op:
841        // the sending record only reaches here on first creation (reservation
842        // already 0, warm sends reuse the record without re-adding), and receiver
843        // records never carry a reservation.
844        self.lease.clear_reservation();
845        Ok(())
846    }
847
848    /// Remove the state with the matching `chain_id` and `signature_key`.
849    ///
850    /// Skips any bad protobufs.
851    fn remove_state(&mut self, chain_id: u32, signature_key: PublicKey) -> Option<SenderKeyState> {
852        let (index, _state) = self.states.iter().enumerate().find(|(_, state)| {
853            state.chain_id() == chain_id && state.signing_key_public().ok() == Some(signature_key)
854        })?;
855
856        self.states.remove(index)
857    }
858
859    /// Returns the number of removed states.
860    ///
861    /// Skips any bad protobufs.
862    fn remove_states_with_chain_id(&mut self, chain_id: u32) -> usize {
863        let initial_length = self.states.len();
864        self.states.retain(|state| state.chain_id() != chain_id);
865        initial_length - self.states.len()
866    }
867
868    pub(crate) fn as_protobuf(&self) -> SenderKeyRecordStructure {
869        let mut states = Vec::with_capacity(self.states.len());
870        for state in &self.states {
871            states.push(state.as_protobuf());
872        }
873
874        SenderKeyRecordStructure {
875            sender_key_states: states,
876        }
877    }
878
879    pub fn serialize(&self) -> Result<Vec<u8>, SignalProtocolError> {
880        self.serialize_inner(None)
881    }
882
883    /// The incarnation prevents clean reloads from looking like crashes.
884    #[doc(hidden)]
885    pub fn serialize_for_store(
886        &self,
887        incarnation: &[u8; 16],
888    ) -> Result<Vec<u8>, SignalProtocolError> {
889        self.serialize_inner(Some(incarnation))
890    }
891
892    fn serialize_inner(
893        &self,
894        incarnation: Option<&[u8; 16]>,
895    ) -> Result<Vec<u8>, SignalProtocolError> {
896        use buffa::encoding::{Tag, WireType, encode_varint, varint_len};
897
898        let mut buf = waproto::codec::sender_key_record_to_vec(&self.as_protobuf());
899        let reserved_iteration = self.lease.ceiling();
900        let incarnation = incarnation.filter(|_| reserved_iteration > 0);
901        let reservation_len = if reserved_iteration > 0 {
902            2 + varint_len(reserved_iteration as u64)
903        } else {
904            0
905        };
906        let incarnation_len = incarnation
907            .map(|_| super::local_field::STORE_INCARNATION_ENCODED_LEN)
908            .unwrap_or(0);
909        buf.reserve(reservation_len + incarnation_len);
910        // Append the local-only reservation as a top-level field the generated
911        // decoder skips. Emitted only when non-zero, so legacy/unreserved records
912        // stay byte-identical. Mirrors SessionRecord::serialize_into.
913        if reserved_iteration > 0 {
914            Tag::new(RESERVED_ITERATION_FIELD, WireType::Varint).encode(&mut buf);
915            encode_varint(reserved_iteration as u64, &mut buf);
916        }
917        if let Some(incarnation) = incarnation {
918            super::local_field::encode_store_incarnation(&mut buf, incarnation);
919        }
920        Ok(buf)
921    }
922
923    /// Estimated in-memory footprint proxy: encoded size of each state's
924    /// structure plus the out-of-order message-key backlog (held outside the
925    /// protobuf in memory). Size computation only — nothing is cloned or
926    /// encoded. Used by per-session memory reports.
927    pub fn estimated_size(&self) -> usize {
928        let mut cache = buffa::SizeCache::new();
929        self.states
930            .iter()
931            .map(|s| {
932                s.state.compute_size(&mut cache) as usize
933                    + s.message_keys.len() * size_of::<StoredMessageKey>()
934                    + s.sender_chain.map_or(0, |_| size_of::<SenderChainKey>())
935            })
936            .sum()
937    }
938}
939
940#[cfg(test)]
941#[allow(clippy::disallowed_methods)]
942mod tests {
943    use super::*;
944    use crate::protocol::KeyPair;
945
946    /// An injected derivation has to be indistinguishable from the one the
947    /// state would have produced, or the API trades correctness for speed.
948    #[test]
949    fn a_prewarmed_state_signs_and_verifies_exactly_like_a_cold_one() {
950        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
951        let signing = KeyPair::generate(&mut rng);
952        let private_bytes = *signing.private_key.serialize();
953        let fresh = || {
954            let key = PrivateKey::deserialize(&private_bytes).expect("key");
955            let state = SenderKeyState::new(3, 1, 0, &[7u8; 32], signing.public_key, Some(key))
956                .expect("valid inputs");
957            SenderKeyState::from_protobuf(state.as_protobuf())
958        };
959
960        // Cold: derives for itself on first use.
961        let lazy = fresh();
962        assert!(!lazy.signing_key_memo_initialized());
963
964        // Prewarmed: same key, derived outside and handed in.
965        let prewarmed = fresh();
966        let derived = PrivateKey::deserialize(&private_bytes).expect("key");
967        derived.precompute_signing_cache();
968        prewarmed
969            .prewarm_signing_key(derived)
970            .expect("own key is accepted");
971        assert!(prewarmed.signing_key_memo_initialized());
972        prewarmed
973            .prewarm_verifying_key(crate::core::curve::PreparedVerifyingKey::new(
974                &signing.public_key,
975            ))
976            .expect("own verifier is accepted");
977
978        let message = b"skmsg";
979        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
980        let from_lazy = lazy
981            .signing_key_private()
982            .expect("lazy key")
983            .calculate_signature(message, &mut rng)
984            .expect("sign");
985        let from_prewarmed = prewarmed
986            .signing_key_private()
987            .expect("prewarmed key")
988            .calculate_signature(message, &mut rng)
989            .expect("sign");
990
991        // Signatures are randomized, so each verifier checks both.
992        for signature in [&from_lazy, &from_prewarmed] {
993            assert!(
994                lazy.signing_key_verifier()
995                    .expect("lazy verifier")
996                    .verify_signature(message, signature)
997            );
998            assert!(
999                prewarmed
1000                    .signing_key_verifier()
1001                    .expect("prewarmed verifier")
1002                    .verify_signature(message, signature)
1003            );
1004        }
1005    }
1006
1007    /// Accepting a cold key as passed would be worse than not injecting at all:
1008    /// every read of the memo hands out a clone, and clones of a cold key each
1009    /// re-derive, so the caller would pay a derivation per message instead of
1010    /// none. The setter warms what it is given.
1011    #[test]
1012    fn prewarming_with_cold_material_still_leaves_the_memos_warm() {
1013        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1014        let signing = KeyPair::generate(&mut rng);
1015        let private_bytes = *signing.private_key.serialize();
1016        let state = SenderKeyState::new(
1017            3,
1018            1,
1019            0,
1020            &[7u8; 32],
1021            signing.public_key,
1022            Some(PrivateKey::deserialize(&private_bytes).expect("key")),
1023        )
1024        .expect("valid inputs");
1025        let state = SenderKeyState::from_protobuf(state.as_protobuf());
1026
1027        // Deliberately not precomputed, the way a caller reconstructing from
1028        // stored bytes would hand it over.
1029        let cold = PrivateKey::deserialize(&private_bytes).expect("key");
1030        assert!(!cold.has_warm_signing_cache());
1031        state.prewarm_signing_key(cold).expect("own key");
1032
1033        assert!(
1034            state
1035                .signing_key_private()
1036                .expect("memo key")
1037                .has_warm_signing_cache(),
1038            "a clone taken from the memo must carry the warm cache"
1039        );
1040
1041        // The verifier is cheaper to get wrong, since clones share its entries,
1042        // but the contract is the same: warm on the way out.
1043        let cold = crate::core::curve::PreparedVerifyingKey::new(&signing.public_key);
1044        assert!(!cold.is_precomputed());
1045        state.prewarm_verifying_key(cold).expect("own verifier");
1046        assert!(
1047            state
1048                .signing_key_verifier()
1049                .expect("memo verifier")
1050                .is_precomputed(),
1051            "the memoized verifier must have its entries derived"
1052        );
1053    }
1054
1055    /// Material from another key must not be installed, and refusing it must
1056    /// leave the state able to derive for itself.
1057    #[test]
1058    fn prewarming_with_another_states_material_is_refused() {
1059        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1060        let mine = KeyPair::generate(&mut rng);
1061        let theirs = KeyPair::generate(&mut rng);
1062        let mine_bytes = *mine.private_key.serialize();
1063        let state = SenderKeyState::new(
1064            3,
1065            1,
1066            0,
1067            &[7u8; 32],
1068            mine.public_key,
1069            Some(PrivateKey::deserialize(&mine_bytes).expect("key")),
1070        )
1071        .expect("valid inputs");
1072        let state = SenderKeyState::from_protobuf(state.as_protobuf());
1073
1074        assert!(state.prewarm_signing_key(theirs.private_key).is_err());
1075        assert!(
1076            state
1077                .prewarm_verifying_key(crate::core::curve::PreparedVerifyingKey::new(
1078                    &theirs.public_key
1079                ))
1080                .is_err()
1081        );
1082
1083        // Still cold, and still able to get there on its own.
1084        assert!(!state.signing_key_memo_initialized());
1085        assert_eq!(
1086            *state.signing_key_private().expect("own key").serialize(),
1087            mine_bytes
1088        );
1089        assert!(
1090            state
1091                .signing_key_verifier()
1092                .expect("own verifier")
1093                .is_for(&mine.public_key)
1094        );
1095    }
1096
1097    /// Injecting over a memo that already warmed by use is a no-op: the value
1098    /// in place is the same derivation.
1099    #[test]
1100    fn prewarming_an_already_warm_state_is_inert() {
1101        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1102        let signing = KeyPair::generate(&mut rng);
1103        let private_bytes = *signing.private_key.serialize();
1104        let state = SenderKeyState::new(
1105            3,
1106            1,
1107            0,
1108            &[7u8; 32],
1109            signing.public_key,
1110            Some(PrivateKey::deserialize(&private_bytes).expect("key")),
1111        )
1112        .expect("valid inputs");
1113        let state = SenderKeyState::from_protobuf(state.as_protobuf());
1114
1115        // Warm it the lazy way first. The lazy verifier is installed without
1116        // its entries derived, so the inert path still has to leave the
1117        // retained one warm.
1118        let _ = state.signing_key_private().expect("lazy warm");
1119        assert!(
1120            !state
1121                .signing_key_verifier()
1122                .expect("lazy verifier")
1123                .is_precomputed()
1124        );
1125
1126        let derived = PrivateKey::deserialize(&private_bytes).expect("key");
1127        derived.precompute_signing_cache();
1128        state
1129            .prewarm_signing_key(derived)
1130            .expect("no-op, not error");
1131        state
1132            .prewarm_verifying_key(crate::core::curve::PreparedVerifyingKey::new(
1133                &signing.public_key,
1134            ))
1135            .expect("no-op, not error");
1136
1137        assert!(
1138            state
1139                .signing_key_verifier()
1140                .expect("verifier")
1141                .is_precomputed(),
1142            "the retained verifier must be warm even when the set was inert"
1143        );
1144        assert_eq!(
1145            *state.signing_key_private().expect("key").serialize(),
1146            private_bytes
1147        );
1148    }
1149
1150    /// Test SenderMessageKey derivation is deterministic
1151    #[test]
1152    fn test_sender_message_key_derivation() {
1153        let seed = [0x42u8; 32];
1154        let iteration = 10;
1155
1156        let smk1 = SenderMessageKey::new(iteration, seed);
1157        let smk2 = SenderMessageKey::new(iteration, seed);
1158
1159        // Same seed and iteration should produce same keys
1160        assert_eq!(smk1.iteration(), smk2.iteration());
1161        assert_eq!(smk1.iv(), smk2.iv());
1162        assert_eq!(smk1.cipher_key(), smk2.cipher_key());
1163    }
1164
1165    /// Test SenderMessageKey produces different keys for different seeds
1166    #[test]
1167    fn test_sender_message_key_different_seeds() {
1168        let seed1 = [0x42u8; 32];
1169        let seed2 = [0x43u8; 32];
1170
1171        let smk1 = SenderMessageKey::new(0, seed1);
1172        let smk2 = SenderMessageKey::new(0, seed2);
1173
1174        assert_ne!(smk1.iv(), smk2.iv());
1175        assert_ne!(smk1.cipher_key(), smk2.cipher_key());
1176    }
1177
1178    /// Test SenderChainKey iteration and stepping
1179    #[test]
1180    fn test_sender_chain_key_stepping() {
1181        let initial_chain = [0x55u8; 32];
1182        let sck = SenderChainKey::new(0, initial_chain);
1183
1184        let sck1 = sck
1185            .next()
1186            .expect("sender chain key iteration should succeed");
1187        let sck2 = sck1
1188            .next()
1189            .expect("sender chain key iteration should succeed");
1190        let sck3 = sck2
1191            .next()
1192            .expect("sender chain key iteration should succeed");
1193
1194        // Verify iteration increments
1195        assert_eq!(sck.iteration(), 0);
1196        assert_eq!(sck1.iteration(), 1);
1197        assert_eq!(sck2.iteration(), 2);
1198        assert_eq!(sck3.iteration(), 3);
1199
1200        // Verify seeds change at each step
1201        assert_ne!(sck.seed(), sck1.seed());
1202        assert_ne!(sck1.seed(), sck2.seed());
1203        assert_ne!(sck2.seed(), sck3.seed());
1204    }
1205
1206    /// Test SenderChainKey produces correct message keys
1207    #[test]
1208    fn test_sender_chain_key_message_key() {
1209        let chain = [0x55u8; 32];
1210        let sck = SenderChainKey::new(5, chain);
1211
1212        let smk = sck.sender_message_key();
1213
1214        assert_eq!(smk.iteration(), 5);
1215        assert_eq!(smk.iv().len(), 16);
1216        assert_eq!(smk.cipher_key().len(), 32);
1217    }
1218
1219    /// Test SenderChainKey stepping is deterministic
1220    #[test]
1221    fn test_sender_chain_key_determinism() {
1222        let chain = [0x77u8; 32];
1223
1224        let sck1 = SenderChainKey::new(0, chain);
1225        let sck2 = SenderChainKey::new(0, chain);
1226
1227        let next1 = sck1
1228            .next()
1229            .expect("sender chain key iteration should succeed");
1230        let next2 = sck2
1231            .next()
1232            .expect("sender chain key iteration should succeed");
1233
1234        assert_eq!(next1.seed(), next2.seed());
1235        assert_eq!(next1.iteration(), next2.iteration());
1236    }
1237
1238    /// Test SenderKeyState basic operations
1239    #[test]
1240    fn test_sender_key_state_basic() {
1241        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1242        let keypair = KeyPair::generate(&mut rng);
1243        let chain_key = [0x42u8; 32];
1244
1245        let state = SenderKeyState::new(3, 12345, 0, &chain_key, keypair.public_key, None)
1246            .expect("sender key state should be valid");
1247
1248        assert_eq!(state.chain_id(), 12345);
1249        assert_eq!(state.message_version(), 3);
1250        assert!(state.sender_chain_key().is_some());
1251        assert!(state.signing_key_public().is_ok());
1252        // Private key was not provided
1253        assert!(state.signing_key_private().is_err());
1254    }
1255
1256    #[test]
1257    fn test_sender_key_state_rejects_invalid_chain_key_length() {
1258        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1259        let keypair = KeyPair::generate(&mut rng);
1260
1261        let err = SenderKeyState::new(3, 12345, 0, &[0x42u8; 31], keypair.public_key, None)
1262            .expect_err("invalid chain key length should fail");
1263
1264        assert!(matches!(err, SignalProtocolError::InvalidProtobufEncoding));
1265    }
1266
1267    /// Test SenderKeyState with private signing key
1268    #[test]
1269    fn test_sender_key_state_with_private_key() {
1270        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1271        let keypair = KeyPair::generate(&mut rng);
1272        let chain_key = [0x42u8; 32];
1273
1274        let state = SenderKeyState::new(
1275            3,
1276            12345,
1277            0,
1278            &chain_key,
1279            keypair.public_key,
1280            Some(keypair.private_key),
1281        )
1282        .expect("sender key state should be valid");
1283
1284        assert!(state.signing_key_public().is_ok());
1285        assert!(state.signing_key_private().is_ok());
1286    }
1287
1288    #[test]
1289    fn signing_key_memo_warms_on_first_use_and_survives_clone() {
1290        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1291        let signing = KeyPair::generate(&mut rng);
1292        let chain_key = [7u8; 32];
1293        let state = SenderKeyState::new(
1294            3,
1295            1,
1296            0,
1297            &chain_key,
1298            signing.public_key,
1299            Some(signing.private_key),
1300        )
1301        .expect("valid inputs");
1302
1303        // new() received the parsed key: memo pre-populated and pre-warmed.
1304        assert!(state.signing_key_memo_initialized());
1305        assert!(
1306            state
1307                .signing_key_private()
1308                .expect("memo key")
1309                .has_warm_signing_cache()
1310        );
1311
1312        // A cold load (protobuf roundtrip) drops the memo; the first
1313        // signing_key_private() call rebuilds AND warms it, and the clone
1314        // handed back carries the warm cache.
1315        let reloaded = SenderKeyState::from_protobuf(state.as_protobuf());
1316        assert!(!reloaded.signing_key_memo_initialized());
1317        let key = reloaded.signing_key_private().expect("reloaded key");
1318        assert!(key.has_warm_signing_cache());
1319        assert!(reloaded.signing_key_memo_initialized());
1320
1321        // Clones of the state (the per-send record clone) carry the memo.
1322        let cloned = reloaded.clone();
1323        assert!(cloned.signing_key_memo_initialized());
1324        assert!(
1325            cloned
1326                .signing_key_private()
1327                .expect("cloned key")
1328                .has_warm_signing_cache()
1329        );
1330
1331        // Verifier memo: send-side states (private key present) skip even
1332        // the allocation; it builds lazily if asked, is seeded eagerly only
1333        // on receive-side creation, rebuilds after a cold load, and clones
1334        // carry it.
1335        assert!(state.verifying_key_memo.get().is_none());
1336        let _ = state.signing_key_verifier().expect("lazy build works");
1337        assert!(state.verifying_key_memo.get().is_some());
1338        let cold = SenderKeyState::from_protobuf(state.as_protobuf());
1339        assert!(cold.verifying_key_memo.get().is_none());
1340        let _ = cold.signing_key_verifier().expect("verifier");
1341        assert!(cold.verifying_key_memo.get().is_some());
1342        assert!(cold.clone().verifying_key_memo.get().is_some());
1343
1344        // The memoized key still signs correctly.
1345        let msg = b"skmsg";
1346        let sig = key.calculate_signature(msg, &mut rng).expect("sign");
1347        let public = reloaded.signing_key_public().expect("public key");
1348        assert!(public.verify_signature(msg, &sig));
1349    }
1350
1351    /// Test SenderKeyState chain key operations
1352    #[test]
1353    fn test_sender_key_state_chain_key_update() {
1354        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1355        let keypair = KeyPair::generate(&mut rng);
1356        let chain_key = [0x42u8; 32];
1357
1358        let mut state = SenderKeyState::new(
1359            3,
1360            12345,
1361            0,
1362            &chain_key,
1363            keypair.public_key,
1364            Some(keypair.private_key),
1365        )
1366        .expect("sender key state should be valid");
1367
1368        let initial_sck = state
1369            .sender_chain_key()
1370            .expect("sender chain key should exist");
1371        let next_sck = initial_sck
1372            .next()
1373            .expect("sender chain key iteration should succeed");
1374
1375        state.set_sender_chain_key(next_sck);
1376
1377        let updated_sck = state
1378            .sender_chain_key()
1379            .expect("sender chain key should exist");
1380        assert_eq!(updated_sck.iteration(), 1);
1381    }
1382
1383    /// Test SenderKeyState message key storage
1384    #[test]
1385    fn test_sender_key_state_message_key_storage() {
1386        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1387        let keypair = KeyPair::generate(&mut rng);
1388        let chain_key = [0x42u8; 32];
1389
1390        let mut state = SenderKeyState::new(
1391            3,
1392            12345,
1393            0,
1394            &chain_key,
1395            keypair.public_key,
1396            Some(keypair.private_key),
1397        )
1398        .expect("sender key state should be valid");
1399
1400        let smk = SenderMessageKey::new(5, [0xAA; 32]);
1401        state.add_sender_message_key(&smk);
1402
1403        // Should be able to retrieve it
1404        let retrieved = state.remove_sender_message_key(5);
1405        assert!(retrieved.is_some());
1406        assert_eq!(retrieved.expect("message key should exist").iteration(), 5);
1407
1408        // Should not find it again
1409        let not_found = state.remove_sender_message_key(5);
1410        assert!(not_found.is_none());
1411    }
1412
1413    /// Test SenderKeyState message key limit
1414    #[test]
1415    fn test_sender_key_state_message_key_limit() {
1416        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1417        let keypair = KeyPair::generate(&mut rng);
1418        let chain_key = [0x42u8; 32];
1419
1420        let mut state = SenderKeyState::new(
1421            3,
1422            12345,
1423            0,
1424            &chain_key,
1425            keypair.public_key,
1426            Some(keypair.private_key),
1427        )
1428        .expect("sender key state should be valid");
1429
1430        // Amortized eviction uses MESSAGE_KEY_PRUNE_THRESHOLD.
1431        // Eviction triggers when len > MAX_MESSAGE_KEYS + MESSAGE_KEY_PRUNE_THRESHOLD.
1432        // Add MAX_MESSAGE_KEYS + 100 keys to ensure eviction happens.
1433        let total_keys = consts::MAX_MESSAGE_KEYS + 100;
1434        for i in 0..total_keys {
1435            let smk = SenderMessageKey::new(i as u32, [0xBB; 32]);
1436            state.add_sender_message_key(&smk);
1437        }
1438
1439        // After adding 2100 keys:
1440        // - At 2051: prune to 2000 (removes first 51 keys: 0-50)
1441        // - Continue adding keys 2051-2099 (49 more)
1442        // - Final len = 2049, no second prune since 2049 <= 2050
1443        // So keys 0-50 (51 keys) should be evicted.
1444        let evicted_count = consts::MESSAGE_KEY_PRUNE_THRESHOLD + 1; // 51
1445        for i in 0..evicted_count {
1446            let not_found = state.remove_sender_message_key(i as u32);
1447            assert!(
1448                not_found.is_none(),
1449                "Key at iteration {} should have been evicted",
1450                i
1451            );
1452        }
1453    }
1454
1455    /// The backlog lives in a separate `Arc` while the protobuf copy stays
1456    /// empty; a serialize/deserialize roundtrip must still carry every key.
1457    #[test]
1458    fn serialize_roundtrip_preserves_message_key_backlog() {
1459        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1460        let keypair = KeyPair::generate(&mut rng);
1461        let chain_key = [0x42u8; 32];
1462
1463        let mut record = SenderKeyRecord::new_empty();
1464        record
1465            .add_sender_key_state(
1466                3,
1467                12345,
1468                0,
1469                &chain_key,
1470                keypair.public_key,
1471                Some(keypair.private_key),
1472            )
1473            .expect("add_sender_key_state should succeed");
1474
1475        {
1476            let state = record.sender_key_state_mut().expect("state exists");
1477            for i in 0..5u32 {
1478                state.add_sender_message_key(&SenderMessageKey::new(i, [i as u8; 32]));
1479            }
1480            // The protobuf copy must stay empty in memory.
1481            assert!(state.state.sender_message_keys.is_empty());
1482        }
1483
1484        let serialized = record.serialize().expect("serialize");
1485        let mut deserialized = SenderKeyRecord::deserialize(&serialized).expect("deserialize");
1486
1487        let state = deserialized.sender_key_state_mut().expect("state exists");
1488        // After a cold load the backlog lives in the Arc, the protobuf stays empty.
1489        assert!(state.state.sender_message_keys.is_empty());
1490        for i in 0..5u32 {
1491            let smk = state
1492                .remove_sender_message_key(i)
1493                .unwrap_or_else(|| panic!("key {i} should survive the roundtrip"));
1494            assert_eq!(smk.iteration(), i);
1495        }
1496    }
1497
1498    /// Cloning a state is a refcount bump; a later mutation must copy-on-write so
1499    /// the clone (the in-cache record) is never touched through the loaded copy.
1500    /// This is the invariant the whole `Arc`-backed backlog relies on.
1501    #[test]
1502    fn backlog_mutation_after_clone_is_isolated() {
1503        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1504        let keypair = KeyPair::generate(&mut rng);
1505        let chain_key = [0x42u8; 32];
1506
1507        let mut original =
1508            SenderKeyState::new(3, 1, 0, &chain_key, keypair.public_key, None).expect("valid");
1509        original.add_sender_message_key(&SenderMessageKey::new(7, [7u8; 32]));
1510
1511        // Clone shares the backlog Arc (mirrors the cache keeping its copy while
1512        // the loaded record is handed out).
1513        let mut loaded = original.clone();
1514
1515        // Adding through the loaded copy must not appear in the original.
1516        loaded.add_sender_message_key(&SenderMessageKey::new(8, [8u8; 32]));
1517        assert!(original.remove_sender_message_key(8).is_none());
1518
1519        // Removing through the loaded copy must not drop it from the original.
1520        assert!(loaded.remove_sender_message_key(7).is_some());
1521        assert!(
1522            original.remove_sender_message_key(7).is_some(),
1523            "the cache's copy must keep its key after the loaded copy removed it"
1524        );
1525    }
1526
1527    /// Test SenderKeyRecord basic operations
1528    #[test]
1529    fn test_sender_key_record_basic() {
1530        let record = SenderKeyRecord::new_empty();
1531        assert!(record.sender_key_state().is_err());
1532    }
1533
1534    /// Test SenderKeyRecord add and retrieve state
1535    #[test]
1536    fn test_sender_key_record_add_state() {
1537        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1538        let keypair = KeyPair::generate(&mut rng);
1539        let chain_key = [0x42u8; 32];
1540
1541        let mut record = SenderKeyRecord::new_empty();
1542        record
1543            .add_sender_key_state(
1544                3,
1545                12345,
1546                0,
1547                &chain_key,
1548                keypair.public_key,
1549                Some(keypair.private_key),
1550            )
1551            .expect("sender key state should be valid");
1552
1553        let state = record
1554            .sender_key_state()
1555            .expect("sender key state should exist");
1556        assert_eq!(state.chain_id(), 12345);
1557    }
1558
1559    fn record_with_state(chain_id: u32, seed: u8) -> SenderKeyRecord {
1560        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1561        let keypair = KeyPair::generate(&mut rng);
1562        let mut record = SenderKeyRecord::new_empty();
1563        record
1564            .add_sender_key_state(
1565                3,
1566                chain_id,
1567                0,
1568                &[seed; 32],
1569                keypair.public_key,
1570                Some(keypair.private_key),
1571            )
1572            .expect("state should be valid");
1573        record
1574    }
1575
1576    fn current_iteration(record: &SenderKeyRecord) -> u32 {
1577        record
1578            .sender_key_state()
1579            .expect("test")
1580            .sender_chain_key()
1581            .expect("test")
1582            .iteration()
1583    }
1584
1585    /// A reservation survives a serialize/deserialize round-trip, and the reload
1586    /// fast-forwards the current chain past the reserved ceiling so no
1587    /// possibly-spent iteration below it stays derivable.
1588    #[test]
1589    fn reservation_survives_roundtrip_and_reload_fast_forwards() {
1590        let mut record = record_with_state(12345, 0x42);
1591        record.reserve_iterations(0);
1592        assert_eq!(
1593            record.reserved_iteration(),
1594            consts::SENDER_CHAIN_RESERVATION_BATCH
1595        );
1596        assert_eq!(
1597            current_iteration(&record),
1598            0,
1599            "reserving does not advance the chain"
1600        );
1601
1602        let reloaded =
1603            SenderKeyRecord::deserialize(&record.serialize().expect("test")).expect("test");
1604        assert_eq!(
1605            reloaded.reserved_iteration(),
1606            consts::SENDER_CHAIN_RESERVATION_BATCH
1607        );
1608        assert_eq!(
1609            current_iteration(&reloaded),
1610            consts::SENDER_CHAIN_RESERVATION_BATCH,
1611            "reload must burn the reserved iterations"
1612        );
1613    }
1614
1615    #[test]
1616    fn cache_incarnation_separates_clean_reload_from_recovery() {
1617        let mut record = record_with_state(12345, 0x42);
1618        record.reserve_iterations(0);
1619        let incarnation = [0xA1; 16];
1620        let replacement = [0xB2; 16];
1621        let bytes = record.serialize_for_store(&incarnation).expect("test");
1622
1623        let clean = SenderKeyRecord::deserialize_for_store(&bytes, &incarnation).expect("test");
1624        assert_eq!(current_iteration(&clean), 0);
1625
1626        let recovered = SenderKeyRecord::deserialize_for_store(&bytes, &replacement).expect("test");
1627        assert_eq!(
1628            current_iteration(&recovered),
1629            consts::SENDER_CHAIN_RESERVATION_BATCH
1630        );
1631
1632        let conservative = SenderKeyRecord::deserialize(&bytes).expect("test");
1633        assert_eq!(
1634            current_iteration(&conservative),
1635            consts::SENDER_CHAIN_RESERVATION_BATCH
1636        );
1637
1638        let legacy = record.serialize().expect("test");
1639        let migrated = SenderKeyRecord::deserialize_for_store(&legacy, &incarnation).expect("test");
1640        assert_eq!(
1641            current_iteration(&migrated),
1642            consts::SENDER_CHAIN_RESERVATION_BATCH
1643        );
1644
1645        let mut duplicated = bytes;
1646        crate::protocol::local_field::encode_store_incarnation(&mut duplicated, &incarnation);
1647        let duplicate_recovery =
1648            SenderKeyRecord::deserialize_for_store(&duplicated, &incarnation).expect("test");
1649        assert_eq!(
1650            current_iteration(&duplicate_recovery),
1651            consts::SENDER_CHAIN_RESERVATION_BATCH
1652        );
1653    }
1654
1655    /// Rotating the current sender-key state (a fresh chain) resets the lease so
1656    /// the next send re-reserves against the new chain instead of treating stale
1657    /// iterations as durable.
1658    #[test]
1659    fn rotation_resets_the_lease() {
1660        let mut record = record_with_state(111, 0x42);
1661        record.reserve_iterations(0);
1662        assert_eq!(
1663            record.reserved_iteration(),
1664            consts::SENDER_CHAIN_RESERVATION_BATCH
1665        );
1666
1667        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1668        let kp = KeyPair::generate(&mut rng);
1669        record
1670            .add_sender_key_state(
1671                3,
1672                222,
1673                0,
1674                &[0x43u8; 32],
1675                kp.public_key,
1676                Some(kp.private_key),
1677            )
1678            .expect("test");
1679        assert_eq!(
1680            record.reserved_iteration(),
1681            0,
1682            "rotation must reset the lease"
1683        );
1684    }
1685
1686    /// A record written without the local-only field (an older lib, or an
1687    /// unreserved record) is byte-identical to the plain generated encoding and
1688    /// loads with reservation 0; the first send re-reserves.
1689    #[test]
1690    fn legacy_record_without_field_loads_with_zero() {
1691        let record = record_with_state(12345, 0x42);
1692        let bytes = record.serialize().expect("test");
1693        assert_eq!(
1694            bytes,
1695            record.as_protobuf().encode_to_vec(),
1696            "an unreserved record appends no field"
1697        );
1698        let loaded = SenderKeyRecord::deserialize(&bytes).expect("test");
1699        assert_eq!(loaded.reserved_iteration(), 0);
1700        assert_eq!(
1701            current_iteration(&loaded),
1702            0,
1703            "no reservation, no fast-forward"
1704        );
1705    }
1706
1707    /// A reservation implausibly far past the current chain is a corrupt record
1708    /// and must fail closed at load rather than looping the KDF unboundedly.
1709    #[test]
1710    fn corrupt_reservation_is_rejected() {
1711        use buffa::encoding::{Tag, WireType, encode_varint};
1712
1713        let record = record_with_state(12345, 0x42);
1714        let mut bytes = record.serialize().expect("test");
1715        let corrupt = consts::MAX_RESERVATION_FAST_FORWARD + 1000;
1716        Tag::new(RESERVED_ITERATION_FIELD, WireType::Varint).encode(&mut bytes);
1717        encode_varint(corrupt as u64, &mut bytes);
1718        assert!(
1719            SenderKeyRecord::deserialize(&bytes).is_err(),
1720            "an implausibly-far reservation must fail closed"
1721        );
1722    }
1723
1724    /// Test SenderKeyRecord state limit
1725    #[test]
1726    fn test_sender_key_record_state_limit() {
1727        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1728        let chain_key = [0x42u8; 32];
1729
1730        let mut record = SenderKeyRecord::new_empty();
1731
1732        // Add more than MAX_SENDER_KEY_STATES
1733        for i in 0..(consts::MAX_SENDER_KEY_STATES + 5) {
1734            let keypair = KeyPair::generate(&mut rng);
1735            record
1736                .add_sender_key_state(
1737                    3,
1738                    i as u32,
1739                    0,
1740                    &chain_key,
1741                    keypair.public_key,
1742                    Some(keypair.private_key),
1743                )
1744                .expect("sender key state should be valid");
1745        }
1746
1747        // Should not have more than MAX_SENDER_KEY_STATES
1748        let chain_ids: Vec<u32> = record.chain_ids_for_logging().collect();
1749        assert!(chain_ids.len() <= consts::MAX_SENDER_KEY_STATES);
1750    }
1751
1752    #[test]
1753    fn test_sender_key_record_deserialize_bounds_state_history() {
1754        let mut state = record_with_state(12345, 0x42).as_protobuf();
1755        let state = state.sender_key_states.pop().expect("test state");
1756        let encoded = SenderKeyRecordStructure {
1757            sender_key_states: vec![state; consts::MAX_SENDER_KEY_STATES + 1],
1758        }
1759        .encode_to_vec();
1760
1761        let record = SenderKeyRecord::deserialize(&encoded).expect("valid record");
1762
1763        assert_eq!(
1764            record.chain_ids_for_logging().len(),
1765            consts::MAX_SENDER_KEY_STATES
1766        );
1767    }
1768
1769    /// Test SenderKeyRecord chain ID lookup
1770    #[test]
1771    fn test_sender_key_record_chain_id_lookup() {
1772        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1773        let keypair1 = KeyPair::generate(&mut rng);
1774        let keypair2 = KeyPair::generate(&mut rng);
1775        let chain_key = [0x42u8; 32];
1776
1777        let mut record = SenderKeyRecord::new_empty();
1778        record
1779            .add_sender_key_state(
1780                3,
1781                111,
1782                0,
1783                &chain_key,
1784                keypair1.public_key,
1785                Some(keypair1.private_key),
1786            )
1787            .expect("sender key state should be valid");
1788        record
1789            .add_sender_key_state(
1790                3,
1791                222,
1792                0,
1793                &chain_key,
1794                keypair2.public_key,
1795                Some(keypair2.private_key),
1796            )
1797            .expect("sender key state should be valid");
1798
1799        // Should find chain 222 (most recent is at front)
1800        let state = record.sender_key_state_for_chain_id(222);
1801        assert!(state.is_some());
1802        assert_eq!(state.expect("state should exist").chain_id(), 222);
1803
1804        // Should find chain 111
1805        let state = record.sender_key_state_for_chain_id(111);
1806        assert!(state.is_some());
1807        assert_eq!(state.expect("state should exist").chain_id(), 111);
1808
1809        // Should not find non-existent chain
1810        let state = record.sender_key_state_for_chain_id(333);
1811        assert!(state.is_none());
1812    }
1813
1814    /// Test SenderKeyRecord serialization roundtrip
1815    #[test]
1816    fn test_sender_key_record_serialization() {
1817        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1818        let keypair = KeyPair::generate(&mut rng);
1819        let chain_key = [0x42u8; 32];
1820
1821        let mut record = SenderKeyRecord::new_empty();
1822        record
1823            .add_sender_key_state(
1824                3,
1825                12345,
1826                5,
1827                &chain_key,
1828                keypair.public_key,
1829                Some(keypair.private_key),
1830            )
1831            .expect("sender key state should be valid");
1832
1833        let serialized = record.serialize().expect("serialization should succeed");
1834        let deserialized =
1835            SenderKeyRecord::deserialize(&serialized).expect("deserialization should succeed");
1836
1837        let state = deserialized
1838            .sender_key_state()
1839            .expect("sender key state should exist");
1840        assert_eq!(state.chain_id(), 12345);
1841        assert!(state.sender_chain_key().is_some());
1842    }
1843
1844    #[test]
1845    fn test_sender_key_record_deserialize_rejects_invalid_chain_seed() {
1846        let record = SenderKeyRecordStructure {
1847            sender_key_states: vec![SenderKeyStateStructure {
1848                sender_key_id: Some(12345),
1849                sender_chain_key: MessageField::some(sender_key_state_structure::SenderChainKey {
1850                    iteration: Some(0),
1851                    seed: Some(bytes::Bytes::copy_from_slice(&[0x42; 31])),
1852                }),
1853                ..Default::default()
1854            }],
1855        };
1856
1857        let err = SenderKeyRecord::deserialize(&record.encode_to_vec())
1858            .expect_err("invalid sender chain seed should fail");
1859
1860        assert!(matches!(err, SignalProtocolError::InvalidProtobufEncoding));
1861    }
1862
1863    #[test]
1864    fn test_sender_key_record_deserialize_rejects_invalid_message_seed() {
1865        let record = SenderKeyRecordStructure {
1866            sender_key_states: vec![SenderKeyStateStructure {
1867                sender_key_id: Some(12345),
1868                sender_chain_key: MessageField::some(sender_key_state_structure::SenderChainKey {
1869                    iteration: Some(0),
1870                    seed: Some(bytes::Bytes::copy_from_slice(&[0x42; 32])),
1871                }),
1872                sender_message_keys: vec![sender_key_state_structure::SenderMessageKey {
1873                    iteration: Some(1),
1874                    seed: Some(bytes::Bytes::copy_from_slice(&[0x43; 31])),
1875                }],
1876                ..Default::default()
1877            }],
1878        };
1879
1880        let err = SenderKeyRecord::deserialize(&record.encode_to_vec())
1881            .expect_err("invalid sender message seed should fail");
1882
1883        assert!(matches!(err, SignalProtocolError::InvalidProtobufEncoding));
1884    }
1885
1886    /// Test that step_with_message_key produces the same results as
1887    /// calling sender_message_key() and next() separately
1888    #[test]
1889    fn test_step_with_message_key_equivalence() {
1890        let chain = [0x99u8; 32];
1891        let sck = SenderChainKey::new(5, chain);
1892
1893        // Get results using separate calls
1894        let msg_key_separate = sck.sender_message_key();
1895        let next_chain_separate = sck.next().expect("next should succeed");
1896
1897        // Get results using optimized combined call
1898        let (msg_key_combined, next_chain_combined) = sck
1899            .step_with_message_key()
1900            .expect("step_with_message_key should succeed");
1901
1902        // Verify message keys are identical
1903        assert_eq!(msg_key_separate.iteration(), msg_key_combined.iteration());
1904        assert_eq!(msg_key_separate.iv(), msg_key_combined.iv());
1905        assert_eq!(msg_key_separate.cipher_key(), msg_key_combined.cipher_key());
1906
1907        // Verify next chain key is identical
1908        assert_eq!(next_chain_separate.seed(), next_chain_combined.seed());
1909        assert_eq!(
1910            next_chain_separate.iteration(),
1911            next_chain_combined.iteration()
1912        );
1913    }
1914
1915    /// Test step_with_message_key over multiple iterations
1916    #[test]
1917    fn test_step_with_message_key_chain() {
1918        let initial_chain = [0xBBu8; 32];
1919        let mut chain_separate = SenderChainKey::new(0, initial_chain);
1920        let mut chain_combined = SenderChainKey::new(0, initial_chain);
1921
1922        // Step both chains 10 times and verify they stay in sync
1923        for i in 0..10 {
1924            let msg_key_sep = chain_separate.sender_message_key();
1925            chain_separate = chain_separate.next().expect("next should succeed");
1926
1927            let (msg_key_comb, next_chain) = chain_combined
1928                .step_with_message_key()
1929                .expect("step_with_message_key should succeed");
1930            chain_combined = next_chain;
1931
1932            // Verify message keys match
1933            assert_eq!(
1934                msg_key_sep.cipher_key(),
1935                msg_key_comb.cipher_key(),
1936                "cipher_key mismatch at iteration {i}"
1937            );
1938
1939            // Verify chain keys match
1940            assert_eq!(
1941                chain_separate.seed(),
1942                chain_combined.seed(),
1943                "chain key mismatch at iteration {i}"
1944            );
1945            assert_eq!(chain_separate.iteration(), chain_combined.iteration());
1946        }
1947    }
1948}