holochain_integrity_types/
countersigning.rs

1//! Countersigned entries involve preflights between many agents to build a session that is part of the entry.
2
3use std::iter::FromIterator;
4use std::time::Duration;
5
6use crate::prelude::*;
7use holo_hash::ActionHash;
8use holo_hash::AgentPubKey;
9use holo_hash::EntryHash;
10use holochain_serialized_bytes::SerializedBytesError;
11use holochain_timestamp::Timestamp;
12
13/// The timestamps on actions for a session use this offset relative to the session start time.
14/// This makes it easier for agents to accept a preflight request with actions that are after their current chain top, after network latency.
15pub const SESSION_ACTION_TIME_OFFSET: Duration = Duration::from_millis(1000);
16
17/// Maximum time in the future the session start can be in the opinion of the participating agent.
18/// As the action will be `SESSION_ACTION_TIME_OFFSET` after the session start we include that here.
19pub const SESSION_TIME_FUTURE_MAX: Duration =
20    Duration::from_millis(5000 + SESSION_ACTION_TIME_OFFSET.as_millis() as u64);
21
22/// Need at least two to countersign.
23pub const MIN_COUNTERSIGNING_AGENTS: usize = 2;
24/// 8 seems like a reasonable limit of agents to countersign.
25pub const MAX_COUNTERSIGNING_AGENTS: usize = 8;
26
27pub use error::CounterSigningError;
28mod error;
29
30/// Every countersigning session must complete a full set of actions between the start and end times to be valid.
31#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
32pub struct CounterSigningSessionTimes {
33    /// The earliest allowable time for countersigning session responses to be valid.
34    pub start: Timestamp,
35    /// The latest allowable time for countersigning session responses to be valid.
36    pub end: Timestamp,
37}
38
39impl CounterSigningSessionTimes {
40    /// Fallible constructor.
41    pub fn try_new(start: Timestamp, end: Timestamp) -> Result<Self, CounterSigningError> {
42        let session_times = Self { start, end };
43        session_times.check_integrity()?;
44        Ok(session_times)
45    }
46
47    /// Verify the difference between the end and start time is larger than the session action time offset.
48    pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
49        let times_are_valid = &Timestamp::from_micros(0) < self.start()
50            && self.start()
51                <= &(self.end() - SESSION_ACTION_TIME_OFFSET).map_err(|_| {
52                    CounterSigningError::CounterSigningSessionTimes((*self).clone())
53                })?;
54        if times_are_valid {
55            Ok(())
56        } else {
57            Err(CounterSigningError::CounterSigningSessionTimes(
58                (*self).clone(),
59            ))
60        }
61    }
62
63    /// Start time accessor.
64    pub fn start(&self) -> &Timestamp {
65        &self.start
66    }
67
68    /// Mutable start time accessor for testing.
69    #[cfg(feature = "test_utils")]
70    pub fn start_mut(&mut self) -> &mut Timestamp {
71        &mut self.start
72    }
73
74    /// End time accessor.
75    pub fn end(&self) -> &Timestamp {
76        &self.end
77    }
78
79    /// Mutable end time accessor for testing.
80    #[cfg(feature = "test_utils")]
81    pub fn end_mut(&mut self) -> &mut Timestamp {
82        &mut self.end
83    }
84}
85
86/// Every preflight request can have optional arbitrary bytes that can be agreed to.
87#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, Hash)]
88pub struct PreflightBytes(#[serde(with = "serde_bytes")] pub Vec<u8>);
89
90/// Agents can have a role specific to each countersigning session.
91/// The role is app defined and opaque to the subconscious.
92#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
93pub struct Role(pub u8);
94
95impl Role {
96    /// Constructor.
97    pub fn new(role: u8) -> Self {
98        Self(role)
99    }
100}
101
102/// Alias for a list of agents and their roles.
103pub type CounterSigningAgents = Vec<(AgentPubKey, Vec<Role>)>;
104
105/// The same PreflightRequest is sent to every agent.
106/// Each agent signs this data as part of their PreflightResponse.
107/// Every preflight must be identical and signed by every agent for a session to be valid.
108#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
109pub struct PreflightRequest {
110    /// The hash of the app entry, as if it were not countersigned.
111    /// The final entry hash will include the countersigning session.
112    pub app_entry_hash: EntryHash,
113    /// The agents that are participating in this countersignature session.
114    pub signing_agents: CounterSigningAgents,
115    /// The optional additional M of N signers.
116    /// If there are additional signers then M MUST be the majority of N.
117    /// If there are additional signers then the enzyme MUST be used and is the
118    /// first signer in BOTH signing_agents and optional_signing_agents.
119    pub optional_signing_agents: CounterSigningAgents,
120    /// The M in the M of N signers.
121    /// M MUST be strictly greater than N / 2 and NOT larger than N.
122    pub minimum_optional_signing_agents: u8,
123    /// The first signing agent (index 0) is acting as an enzyme.
124    /// If true AND optional_signing_agents are set then the first agent MUST
125    /// be the same in both signing_agents and optional_signing_agents.
126    pub enzymatic: bool,
127    /// The session times.
128    /// Session actions must all have the same timestamp, which is the session offset.
129    pub session_times: CounterSigningSessionTimes,
130    /// The action information that is shared by all agents.
131    /// Contents depend on the action type, create, update, etc.
132    pub action_base: ActionBase,
133    /// The preflight bytes for session.
134    pub preflight_bytes: PreflightBytes,
135}
136
137impl PreflightRequest {
138    /// Fallible constructor.
139    #[allow(clippy::too_many_arguments)]
140    pub fn try_new(
141        app_entry_hash: EntryHash,
142        signing_agents: CounterSigningAgents,
143        optional_signing_agents: CounterSigningAgents,
144        minimum_optional_signing_agents: u8,
145        enzymatic: bool,
146        session_times: CounterSigningSessionTimes,
147        action_base: ActionBase,
148        preflight_bytes: PreflightBytes,
149    ) -> Result<Self, CounterSigningError> {
150        let preflight_request = Self {
151            app_entry_hash,
152            signing_agents,
153            optional_signing_agents,
154            minimum_optional_signing_agents,
155            enzymatic,
156            session_times,
157            action_base,
158            preflight_bytes,
159        };
160        preflight_request.check_integrity()?;
161        Ok(preflight_request)
162    }
163    /// Combined integrity checks.
164    pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
165        self.check_enzyme()?;
166        self.session_times.check_integrity()?;
167        self.check_agents()?;
168        Ok(())
169    }
170
171    /// Verify there are no duplicate agents to sign.
172    pub fn check_agents_dupes(&self) -> Result<(), CounterSigningError> {
173        let v: Vec<AgentPubKey> = self
174            .signing_agents
175            .iter()
176            .map(|(agent, _roles)| agent.clone())
177            .collect();
178        if std::collections::HashSet::<AgentPubKey>::from_iter(v.clone()).len()
179            == self.signing_agents.len()
180        {
181            Ok(())
182        } else {
183            Err(CounterSigningError::AgentsDupes(v))
184        }
185    }
186
187    /// Verify the number of signing agents is within the correct range.
188    pub fn check_agents_len(&self) -> Result<(), CounterSigningError> {
189        if MIN_COUNTERSIGNING_AGENTS <= self.signing_agents.len()
190            && self.signing_agents.len() <= MAX_COUNTERSIGNING_AGENTS
191        {
192            Ok(())
193        } else {
194            Err(CounterSigningError::AgentsLength(self.signing_agents.len()))
195        }
196    }
197
198    /// Verify the optional signing agents.
199    pub fn check_agents_optional(&self) -> Result<(), CounterSigningError> {
200        if self.minimum_optional_signing_agents as usize > self.optional_signing_agents.len() {
201            return Err(CounterSigningError::OptionalAgentsLength(
202                self.minimum_optional_signing_agents,
203                self.optional_signing_agents.len(),
204            ));
205        }
206        // Minimum optional signers must be at least half the total signers.
207        if ((self.minimum_optional_signing_agents * 2) as usize)
208            < self.optional_signing_agents.len()
209            && !self.optional_signing_agents.is_empty()
210        {
211            return Err(CounterSigningError::MinOptionalAgents(
212                self.minimum_optional_signing_agents,
213                self.optional_signing_agents.len(),
214            ));
215        }
216        Ok(())
217    }
218
219    /// Verify the preflight request agents.
220    pub fn check_agents(&self) -> Result<(), CounterSigningError> {
221        self.check_agents_dupes()?;
222        self.check_agents_len()?;
223        self.check_agents_optional()?;
224        Ok(())
225    }
226
227    /// Verify everything about the enzyme.
228    pub fn check_enzyme(&self) -> Result<(), CounterSigningError> {
229        // Enzymatic optional signing agents MUST match the first signer in
230        // both the signing agents and optional signing agents.
231        if self.enzymatic
232            && !self.optional_signing_agents.is_empty()
233            && self.signing_agents.first() != self.optional_signing_agents.first()
234        {
235            return Err(CounterSigningError::EnzymeMismatch(
236                self.signing_agents.first().cloned(),
237                self.optional_signing_agents.first().cloned(),
238            ));
239        }
240        if !self.enzymatic && !self.optional_signing_agents.is_empty() {
241            return Err(CounterSigningError::NonEnzymaticOptionalSigners);
242        }
243        Ok(())
244    }
245
246    /// Compute a fingerprint for this preflight request.
247    pub fn fingerprint(&self) -> Result<Vec<u8>, SerializedBytesError> {
248        Ok(holo_hash::encode::blake2b_256(
249            &holochain_serialized_bytes::encode(self)?,
250        ))
251    }
252}
253
254/// Every agent must send back a preflight response.
255/// All the preflight response data is signed by each agent and included in the session data.
256#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
257pub struct PreflightResponse {
258    /// The request this is a response to.
259    pub request: PreflightRequest,
260    /// The agent must provide their current chain state, state their position in the preflight and sign everything.
261    pub agent_state: CounterSigningAgentState,
262    /// The signature of the preflight resonse.
263    pub signature: Signature,
264}
265
266impl PreflightResponse {
267    /// Fallible constructor.
268    pub fn try_new(
269        request: PreflightRequest,
270        agent_state: CounterSigningAgentState,
271        signature: Signature,
272    ) -> Result<Self, CounterSigningError> {
273        let preflight_response = Self {
274            request,
275            agent_state,
276            signature,
277        };
278        preflight_response.check_integrity()?;
279        Ok(preflight_response)
280    }
281
282    /// Combined preflight response validation call.
283    pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
284        self.request().check_integrity()
285    }
286
287    /// Serialization for signing of the signable field data only.
288    pub fn encode_fields_for_signature(
289        request: &PreflightRequest,
290        agent_state: &CounterSigningAgentState,
291    ) -> Result<Vec<u8>, SerializedBytesError> {
292        holochain_serialized_bytes::encode(&(request, agent_state))
293    }
294
295    /// Consistent serialization for the preflight response so it can be signed and the signatures verified.
296    pub fn encode_for_signature(&self) -> Result<Vec<u8>, SerializedBytesError> {
297        Self::encode_fields_for_signature(&self.request, &self.agent_state)
298    }
299
300    /// Request accessor.
301    pub fn request(&self) -> &PreflightRequest {
302        &self.request
303    }
304
305    /// Mutable request accessor for testing.
306    #[cfg(feature = "test_utils")]
307    pub fn request_mut(&mut self) -> &mut PreflightRequest {
308        &mut self.request
309    }
310
311    /// Agent state accessor.
312    pub fn agent_state(&self) -> &CounterSigningAgentState {
313        &self.agent_state
314    }
315
316    /// Mutable agent state accessor for testing.
317    #[cfg(feature = "test_utils")]
318    pub fn agent_state_mut(&mut self) -> &mut CounterSigningAgentState {
319        &mut self.agent_state
320    }
321
322    /// Signature accessor.
323    pub fn signature(&self) -> &Signature {
324        &self.signature
325    }
326
327    /// Mutable signature accessor for testing.
328    #[cfg(feature = "test_utils")]
329    pub fn signature_mut(&mut self) -> &mut Signature {
330        &mut self.signature
331    }
332}
333
334/// A preflight request can be accepted, or invalid, or valid but the local agent cannot accept it.
335#[derive(Debug, serde::Serialize, serde::Deserialize)]
336#[allow(clippy::large_enum_variant)]
337pub enum PreflightRequestAcceptance {
338    /// Preflight request accepted.
339    Accepted(PreflightResponse),
340    /// The preflight request start time is too far in the future for the agent.
341    UnacceptableFutureStart,
342    /// The preflight request does not include the agent.
343    UnacceptableAgentNotFound,
344    /// The preflight hasn't been checked because another session is already in progress.
345    AnotherSessionIsInProgress,
346    /// The preflight request is invalid as it failed some integrity check.
347    Invalid(String),
348}
349
350/// Every countersigning agent must sign against their chain state.
351/// The chain must be frozen until each agent decides to sign or exit the session.
352#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
353pub struct CounterSigningAgentState {
354    /// The index of the agent in the preflight request agent vector.
355    agent_index: u8,
356    /// The current (frozen) top of the agent's local chain.
357    chain_top: ActionHash,
358    /// The action sequence of the agent's chain top.
359    action_seq: u32,
360}
361
362impl CounterSigningAgentState {
363    /// Constructor.
364    pub fn new(agent_index: u8, chain_top: ActionHash, action_seq: u32) -> Self {
365        Self {
366            agent_index,
367            chain_top,
368            action_seq,
369        }
370    }
371
372    /// Agent index accessor.
373    pub fn agent_index(&self) -> &u8 {
374        &self.agent_index
375    }
376
377    /// Mutable agent index accessor for testing.
378    #[cfg(feature = "test_utils")]
379    pub fn agent_index_mut(&mut self) -> &mut u8 {
380        &mut self.agent_index
381    }
382
383    /// Chain top accessor.
384    pub fn chain_top(&self) -> &ActionHash {
385        &self.chain_top
386    }
387
388    /// Mutable chain top accessor for testing.
389    #[cfg(feature = "test_utils")]
390    pub fn chain_top_mut(&mut self) -> &mut ActionHash {
391        &mut self.chain_top
392    }
393
394    /// Action seq accessor.
395    pub fn action_seq(&self) -> &u32 {
396        &self.action_seq
397    }
398
399    /// Mutable action seq accessor for testing.
400    #[cfg(feature = "test_utils")]
401    pub fn action_seq_mut(&mut self) -> &mut u32 {
402        &mut self.action_seq
403    }
404}
405
406/// Enum to mirror Action for all the shared data required to build session actions.
407/// Does NOT hold any agent specific information.
408#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
409pub enum ActionBase {
410    /// Mirrors Action::Create.
411    Create(CreateBase),
412    /// Mirrors Action::Update.
413    Update(UpdateBase),
414    // @todo - These actions don't have entries so there's nowhere obvious to put the CounterSigningSessionData.
415    // Delete(DeleteBase),
416    // DeleteLink(DeleteLinkBase),
417    // CreateLink(CreateLinkBase),
418}
419
420/// Base data for Create actions.
421#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
422pub struct CreateBase {
423    entry_type: EntryType,
424}
425
426impl CreateBase {
427    /// Constructor.
428    pub fn new(entry_type: EntryType) -> Self {
429        Self { entry_type }
430    }
431}
432
433/// Base data for Update actions.
434#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
435pub struct UpdateBase {
436    /// The original action being updated.
437    pub original_action_address: ActionHash,
438    /// The original entry being updated.
439    pub original_entry_address: EntryHash,
440    /// The entry type of the update.
441    pub entry_type: EntryType,
442}
443
444impl Action {
445    /// Construct an Action from the ActionBase and associated session data.
446    pub fn from_countersigning_data(
447        entry_hash: EntryHash,
448        session_data: &CounterSigningSessionData,
449        author: AgentPubKey,
450        weight: EntryRateWeight,
451    ) -> Result<Self, CounterSigningError> {
452        let agent_state = session_data.agent_state_for_agent(&author)?;
453        Ok(match &session_data.preflight_request().action_base {
454            ActionBase::Create(base) => Action::Create(Create {
455                author,
456                timestamp: session_data.to_timestamp(),
457                action_seq: agent_state.action_seq + 1,
458                prev_action: agent_state.chain_top.clone(),
459                entry_type: base.entry_type.clone(),
460                weight,
461                entry_hash,
462            }),
463            ActionBase::Update(base) => Action::Update(Update {
464                author,
465                timestamp: session_data.to_timestamp(),
466                action_seq: agent_state.action_seq + 1,
467                prev_action: agent_state.chain_top.clone(),
468                original_action_address: base.original_action_address.clone(),
469                original_entry_address: base.original_entry_address.clone(),
470                entry_type: base.entry_type.clone(),
471                weight,
472                entry_hash,
473            }),
474        })
475    }
476}
477
478/// All the data required for a countersigning session.
479#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
480pub struct CounterSigningSessionData {
481    /// The preflight request that was agreed upon by all parties for the session.
482    pub preflight_request: PreflightRequest,
483    /// All the required responses from each party.
484    pub responses: Vec<(CounterSigningAgentState, Signature)>,
485    /// Any optional responses if allowed for by the preflight request.
486    pub optional_responses: Vec<(CounterSigningAgentState, Signature)>,
487}
488
489impl CounterSigningSessionData {
490    /// Attempt to build session data from a vector of responses.
491    pub fn try_from_responses(
492        responses: Vec<PreflightResponse>,
493        optional_responses: Vec<PreflightResponse>,
494    ) -> Result<Self, CounterSigningError> {
495        let preflight_request = responses
496            .first()
497            .ok_or(CounterSigningError::MissingResponse)?
498            .to_owned()
499            .request;
500        let convert_responses =
501            |rs: Vec<PreflightResponse>| -> Vec<(CounterSigningAgentState, Signature)> {
502                rs.into_iter()
503                    .map(|response| (response.agent_state.clone(), response.signature))
504                    .collect()
505            };
506        let responses = convert_responses(responses);
507        let optional_responses = convert_responses(optional_responses);
508        Ok(Self {
509            preflight_request,
510            responses,
511            optional_responses,
512        })
513    }
514
515    /// Get the agent state for a specific agent.
516    pub fn agent_state_for_agent(
517        &self,
518        agent: &AgentPubKey,
519    ) -> Result<&CounterSigningAgentState, CounterSigningError> {
520        match self
521            .preflight_request
522            .signing_agents
523            .iter()
524            .position(|(pubkey, _)| pubkey == agent)
525        {
526            Some(agent_index) => match self.responses.get(agent_index) {
527                Some((agent_state, _)) => Ok(agent_state),
528                None => Err(CounterSigningError::AgentIndexOutOfBounds),
529            },
530            None => Err(CounterSigningError::AgentIndexOutOfBounds),
531        }
532    }
533
534    /// Attempt to map countersigning session data to a set of actions.
535    /// A given countersigning session always maps to the same ordered set of actions or an error.
536    /// Note the actions are not signed as the intent is to build actions for other agents without their private keys.
537    pub fn build_action_set(
538        &self,
539        entry_hash: EntryHash,
540        weight: EntryRateWeight,
541    ) -> Result<Vec<Action>, CounterSigningError> {
542        let mut actions = vec![];
543        let mut build_actions = |countersigning_agents: &CounterSigningAgents| -> Result<(), _> {
544            for (agent, _role) in countersigning_agents.iter() {
545                actions.push(Action::from_countersigning_data(
546                    entry_hash.clone(),
547                    self,
548                    agent.clone(),
549                    weight.clone(),
550                )?);
551            }
552            Ok(())
553        };
554        build_actions(&self.preflight_request.signing_agents)?;
555        build_actions(&self.preflight_request.optional_signing_agents)?;
556        Ok(actions)
557    }
558
559    /// Fallible constructor.
560    pub fn try_new(
561        preflight_request: PreflightRequest,
562        responses: Vec<(CounterSigningAgentState, Signature)>,
563        optional_responses: Vec<(CounterSigningAgentState, Signature)>,
564    ) -> Result<Self, CounterSigningError> {
565        let session_data = Self {
566            preflight_request,
567            responses,
568            optional_responses,
569        };
570        session_data.check_integrity()?;
571        Ok(session_data)
572    }
573
574    /// Combines all integrity checks.
575    pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
576        self.check_responses_indexes()
577    }
578
579    /// Check that the countersigning session data responses all have the
580    /// correct indexes.
581    pub fn check_responses_indexes(&self) -> Result<(), CounterSigningError> {
582        if self.preflight_request().signing_agents.len() != self.responses().len() {
583            Err(CounterSigningError::CounterSigningSessionResponsesLength(
584                self.responses().len(),
585                self.preflight_request().signing_agents.len(),
586            ))
587        } else {
588            for (i, (response, _response_signature)) in self.responses().iter().enumerate() {
589                if *response.agent_index() as usize != i {
590                    return Err(CounterSigningError::CounterSigningSessionResponsesOrder(
591                        *response.agent_index(),
592                        i,
593                    ));
594                }
595            }
596            Ok(())
597        }
598    }
599
600    /// Construct a Timestamp from countersigning session data.
601    /// Ostensibly used for the Action because the session itself covers a time range.
602    pub fn to_timestamp(&self) -> Timestamp {
603        (self.preflight_request().session_times.start() + SESSION_ACTION_TIME_OFFSET)
604            .unwrap_or(Timestamp::MAX)
605    }
606
607    /// Accessor to the preflight request.
608    pub fn preflight_request(&self) -> &PreflightRequest {
609        &self.preflight_request
610    }
611
612    /// Mutable preflight_request accessor for testing.
613    #[cfg(feature = "test_utils")]
614    pub fn preflight_request_mut(&mut self) -> &mut PreflightRequest {
615        &mut self.preflight_request
616    }
617
618    /// Get all the agents signing for this session.
619    pub fn signing_agents(&self) -> impl Iterator<Item = &AgentPubKey> {
620        self.preflight_request.signing_agents.iter().map(|(a, _)| a)
621    }
622
623    /// Accessor to responses.
624    pub fn responses(&self) -> &Vec<(CounterSigningAgentState, Signature)> {
625        &self.responses
626    }
627
628    /// Mutable responses accessor for testing.
629    #[cfg(feature = "test_utils")]
630    pub fn responses_mut(&mut self) -> &mut Vec<(CounterSigningAgentState, Signature)> {
631        &mut self.responses
632    }
633}
634
635#[cfg(test)]
636mod test {
637    use super::CounterSigningError;
638    use super::CounterSigningSessionTimes;
639    use super::PreflightRequest;
640    use super::SESSION_ACTION_TIME_OFFSET;
641    use crate::CounterSigningSessionData;
642    use crate::Role;
643    use crate::Signature;
644    use crate::{
645        ActionBase, AppEntryDef, CounterSigningAgentState, CreateBase, EntryType, EntryVisibility,
646        PreflightBytes,
647    };
648    use fixt::*;
649    use holo_hash::fixt::ActionHashFixturator;
650    use holo_hash::fixt::AgentPubKeyFixturator;
651    use holo_hash::fixt::EntryHashFixturator;
652    use holochain_timestamp::Timestamp;
653    use std::time::Duration;
654
655    fn test_preflight_request() -> PreflightRequest {
656        let mut request = PreflightRequest::try_new(
657            fixt!(EntryHash),
658            vec![(fixt!(AgentPubKey), vec![]), (fixt!(AgentPubKey), vec![])],
659            vec![],
660            0,
661            false,
662            CounterSigningSessionTimes::try_new(
663                Timestamp::now(),
664                (Timestamp::now() + Duration::from_secs(30)).unwrap(),
665            )
666            .unwrap(),
667            ActionBase::Create(CreateBase::new(EntryType::App(AppEntryDef {
668                entry_index: 0.into(),
669                zome_index: 0.into(),
670                visibility: EntryVisibility::Public,
671            }))),
672            PreflightBytes(vec![]),
673        )
674        .unwrap();
675        request.signing_agents.clear();
676
677        request
678    }
679
680    #[test]
681    fn test_check_countersigning_session_times() {
682        let mut session_times = CounterSigningSessionTimes {
683            start: Timestamp(0),
684            end: Timestamp(0),
685        };
686
687        // Zero start and end won't pass.
688        assert!(matches!(
689            session_times.check_integrity(),
690            Err(CounterSigningError::CounterSigningSessionTimes(_))
691        ));
692
693        // Shifting the end forward 1 milli won't help.
694        *session_times.end_mut() =
695            (session_times.end() + core::time::Duration::from_millis(1)).unwrap();
696        assert!(matches!(
697            session_times.check_integrity(),
698            Err(CounterSigningError::CounterSigningSessionTimes(_))
699        ));
700
701        // Shifting the end forward by the session offset will _almost_ fix it.
702        *session_times.end_mut() = (session_times.end() + SESSION_ACTION_TIME_OFFSET).unwrap();
703        assert!(matches!(
704            session_times.check_integrity(),
705            Err(CounterSigningError::CounterSigningSessionTimes(_))
706        ));
707
708        // making the start non-zero should fix it.
709        *session_times.start_mut() =
710            (session_times.start() + core::time::Duration::from_millis(1)).unwrap();
711        session_times.check_integrity().unwrap();
712
713        // making the diff between start and end less than the action offset will break it again.
714        *session_times.start_mut() =
715            (session_times.start() + core::time::Duration::from_millis(1)).unwrap();
716        assert!(matches!(
717            session_times.check_integrity(),
718            Err(CounterSigningError::CounterSigningSessionTimes(_))
719        ));
720    }
721
722    #[test]
723    fn test_check_countersigning_preflight_request_optional_agents() {
724        let mut preflight_request = test_preflight_request();
725
726        // Empty optional agents is a pass.
727        preflight_request.check_agents_optional().unwrap();
728
729        // Adding a single agent with a minimum of zero is a fail.
730        let alice = fixt!(AgentPubKey);
731
732        preflight_request
733            .optional_signing_agents
734            .push((alice.clone(), vec![]));
735
736        assert!(matches!(
737            preflight_request.check_agents_optional(),
738            Err(CounterSigningError::MinOptionalAgents(0, 1))
739        ));
740
741        // 1 of 1 is a pass
742
743        preflight_request.minimum_optional_signing_agents = 1;
744
745        preflight_request.check_agents_optional().unwrap();
746
747        // 1 of 2 optional agents is a pass
748        preflight_request
749            .optional_signing_agents
750            .push((alice.clone(), vec![]));
751
752        preflight_request.check_agents_optional().unwrap();
753
754        // 1 of 3 optional agents is a fail
755        preflight_request
756            .optional_signing_agents
757            .push((alice.clone(), vec![]));
758
759        assert!(matches!(
760            preflight_request.check_agents_optional(),
761            Err(CounterSigningError::MinOptionalAgents(1, 3))
762        ));
763
764        // 2 of 3 optional agents is a pass
765        preflight_request.minimum_optional_signing_agents = 2;
766
767        preflight_request.check_agents_optional().unwrap();
768
769        // 4 of 3 optional agents is a fail
770        preflight_request.minimum_optional_signing_agents = 4;
771
772        assert!(matches!(
773            preflight_request.check_agents_optional(),
774            Err(CounterSigningError::OptionalAgentsLength(4, 3))
775        ));
776    }
777
778    #[test]
779    fn test_check_countersigning_preflight_request_enzyme() {
780        let mut preflight_request = test_preflight_request();
781
782        // Non enzymatic with no signers is always pass.
783        preflight_request.check_enzyme().unwrap();
784
785        let alice = fixt!(AgentPubKey);
786        let bob = fixt!(AgentPubKey);
787
788        // Non enzymatic with signers and no optional signers is a pass.
789        preflight_request
790            .signing_agents
791            .push((alice.clone(), vec![]));
792
793        preflight_request.check_enzyme().unwrap();
794
795        // Non enzymatic with optional signers is a fail.
796        preflight_request
797            .optional_signing_agents
798            .push((alice.clone(), vec![]));
799
800        assert!(matches!(
801            preflight_request.check_enzyme(),
802            Err(CounterSigningError::NonEnzymaticOptionalSigners),
803        ));
804
805        // Enzymatic with zero optional signers is a pass.
806        preflight_request.optional_signing_agents = vec![];
807        preflight_request.enzymatic = true;
808
809        preflight_request.check_enzyme().unwrap();
810
811        // Enzymatic with optional signers is a pass.
812        preflight_request.optional_signing_agents = vec![(alice.clone(), vec![])];
813
814        preflight_request.check_enzyme().unwrap();
815
816        // Enzymatic with first signer mismatch is a fail.
817        preflight_request.optional_signing_agents = vec![(bob.clone(), vec![])];
818
819        assert!(matches!(
820            preflight_request.check_enzyme(),
821            Err(CounterSigningError::EnzymeMismatch(_, _)),
822        ));
823    }
824
825    #[test]
826    fn test_check_countersigning_preflight_request_agents_len() {
827        let mut preflight_request = test_preflight_request();
828
829        // Empty is a fail.
830        assert!(matches!(
831            preflight_request.check_agents_len(),
832            Err(CounterSigningError::AgentsLength(_))
833        ));
834
835        // One signer is a fail.
836        let alice = fixt!(AgentPubKey);
837        preflight_request
838            .signing_agents
839            .push((alice.clone(), vec![]));
840
841        assert!(matches!(
842            preflight_request.check_agents_len(),
843            Err(CounterSigningError::AgentsLength(_))
844        ));
845
846        // Two signers is a pass.
847        let bob = fixt!(AgentPubKey);
848        preflight_request.signing_agents.push((bob.clone(), vec![]));
849
850        preflight_request.check_agents_len().unwrap();
851    }
852
853    #[test]
854    fn test_check_countersigning_preflight_request_agents_dupes() {
855        let mut preflight_request = test_preflight_request();
856
857        let alice = fixt!(AgentPubKey);
858        let bob = fixt!(AgentPubKey);
859
860        preflight_request.check_agents_dupes().unwrap();
861
862        preflight_request
863            .signing_agents
864            .push((alice.clone(), vec![]));
865        preflight_request.check_agents_dupes().unwrap();
866
867        preflight_request.signing_agents.push((bob.clone(), vec![]));
868        preflight_request.check_agents_dupes().unwrap();
869
870        // Another alice is a dupe, even if roles are different.
871        preflight_request
872            .signing_agents
873            .push((alice.clone(), vec![Role::new(0_u8)]));
874        assert!(matches!(
875            preflight_request.check_agents_dupes(),
876            Err(CounterSigningError::AgentsDupes(_))
877        ));
878    }
879
880    #[test]
881    pub fn test_check_countersigning_session_data_responses_indexes() {
882        let mut session_data =
883            CounterSigningSessionData::try_new(test_preflight_request(), vec![], vec![]).unwrap();
884
885        let alice = fixt!(AgentPubKey);
886        let bob = fixt!(AgentPubKey);
887
888        // When everything is empty the indexes line up by default.
889        session_data.check_responses_indexes().unwrap();
890
891        // When the signing agents and responses are out of sync it must error.
892        session_data
893            .preflight_request_mut()
894            .signing_agents
895            .push((alice.clone(), vec![]));
896        assert!(matches!(
897            session_data.check_responses_indexes(),
898            Err(CounterSigningError::CounterSigningSessionResponsesLength(
899                _,
900                _
901            ))
902        ));
903
904        // When signing agents indexes are not in the correct order it must error.
905        session_data
906            .preflight_request_mut()
907            .signing_agents
908            .push((bob.clone(), vec![]));
909
910        let alice_state = CounterSigningAgentState::new(0, fixt!(ActionHash), 0);
911        let alice_signature = Signature(vec![0; 64].try_into().unwrap());
912        let mut bob_state = CounterSigningAgentState::new(0, fixt!(ActionHash), 0);
913        let bob_signature = Signature(vec![1; 64].try_into().unwrap());
914
915        (*session_data.responses_mut()).push((alice_state, alice_signature));
916        (*session_data.responses_mut()).push((bob_state.clone(), bob_signature.clone()));
917
918        assert!(
919            matches!(
920                session_data.check_responses_indexes(),
921                Err(CounterSigningError::CounterSigningSessionResponsesOrder(
922                    _,
923                    _
924                ))
925            ),
926            "But got: {:?}",
927            session_data.check_responses_indexes()
928        );
929
930        *bob_state.agent_index_mut() = 1;
931        (*session_data.responses_mut()).pop();
932        (*session_data.responses_mut()).push((bob_state, bob_signature));
933        session_data.check_responses_indexes().unwrap()
934    }
935}