use std::iter::FromIterator;
use std::time::Duration;
use crate::prelude::*;
use holo_hash::AgentPubKey;
use holo_hash::EntryHash;
use holo_hash::HeaderHash;
pub const SESSION_HEADER_TIME_OFFSET: Duration = Duration::from_millis(1000);
pub const SESSION_TIME_FUTURE_MAX: Duration =
Duration::from_millis(5000 + SESSION_HEADER_TIME_OFFSET.as_millis() as u64);
pub const MIN_COUNTERSIGNING_AGENTS: usize = 2;
pub const MAX_COUNTERSIGNING_AGENTS: usize = 8;
#[derive(Debug, thiserror::Error)]
pub enum CounterSigningError {
#[error("Agent index is out of bounds for the signing session.")]
AgentIndexOutOfBounds,
#[error("Attempted to build CounterSigningSessionData with an empty response vector.")]
MissingResponse,
#[error("The countersigning session responses ({0}) did not match the number of signing agents ({1})")]
CounterSigningSessionResponsesLength(usize, usize),
#[error(
"The countersigning session response with agent index {0} was found in index position {1}"
)]
CounterSigningSessionResponsesOrder(u8, usize),
#[error("The enzyme index {1:?} is out of bounds for signing agents list of length {0:?}")]
EnzymeIndex(usize, usize),
#[error("The signing agents list is too long or short {0:?}")]
AgentsLength(usize),
#[error("The signing agents list contains duplicates {0:?}")]
AgentsDupes(Vec<AgentPubKey>),
#[error("The countersigning session times were not valid {0:?}")]
CounterSigningSessionTimes(CounterSigningSessionTimes),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct CounterSigningSessionTimes {
start: Timestamp,
end: Timestamp,
}
impl CounterSigningSessionTimes {
pub fn try_new(start: Timestamp, end: Timestamp) -> Result<Self, CounterSigningError> {
let session_times = Self { start, end };
session_times.check_integrity()?;
Ok(session_times)
}
pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
let times_are_valid = &Timestamp::from_micros(0) < self.start()
&& self.start()
<= &(self.end() - SESSION_HEADER_TIME_OFFSET).map_err(|_| {
CounterSigningError::CounterSigningSessionTimes((*self).clone())
})?;
if times_are_valid {
Ok(())
} else {
Err(CounterSigningError::CounterSigningSessionTimes(
(*self).clone(),
))
}
}
pub fn start(&self) -> &Timestamp {
&self.start
}
#[cfg(feature = "test_utils")]
pub fn start_mut(&mut self) -> &mut Timestamp {
&mut self.start
}
pub fn end(&self) -> &Timestamp {
&self.end
}
#[cfg(feature = "test_utils")]
pub fn end_mut(&mut self) -> &mut Timestamp {
&mut self.end
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct PreflightBytes(#[serde(with = "serde_bytes")] pub Vec<u8>);
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Role(pub u8);
impl Role {
pub fn new(role: u8) -> Self {
Self(role)
}
}
pub type CounterSigningAgents = Vec<(AgentPubKey, Vec<Role>)>;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct PreflightRequest {
app_entry_hash: EntryHash,
signing_agents: CounterSigningAgents,
enzyme_index: Option<u8>,
session_times: CounterSigningSessionTimes,
header_base: HeaderBase,
preflight_bytes: PreflightBytes,
}
impl PreflightRequest {
pub fn try_new(
app_entry_hash: EntryHash,
signing_agents: CounterSigningAgents,
enzyme_index: Option<u8>,
session_times: CounterSigningSessionTimes,
header_base: HeaderBase,
preflight_bytes: PreflightBytes,
) -> Result<Self, CounterSigningError> {
let preflight_request = Self {
app_entry_hash,
signing_agents,
enzyme_index,
session_times,
header_base,
preflight_bytes,
};
preflight_request.check_integrity()?;
Ok(preflight_request)
}
pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
self.check_enzyme_index()?;
self.session_times().check_integrity()?;
self.check_agents()?;
Ok(())
}
pub fn check_agents_dupes(&self) -> Result<(), CounterSigningError> {
let v: Vec<AgentPubKey> = self
.signing_agents()
.iter()
.map(|(agent, _roles)| agent.clone())
.collect();
if std::collections::HashSet::<AgentPubKey>::from_iter(v.clone()).len()
== self.signing_agents().len()
{
Ok(())
} else {
Err(CounterSigningError::AgentsDupes(v))
}
}
pub fn check_agents_len(&self) -> Result<(), CounterSigningError> {
if MIN_COUNTERSIGNING_AGENTS <= self.signing_agents().len()
&& self.signing_agents().len() <= MAX_COUNTERSIGNING_AGENTS
{
Ok(())
} else {
Err(CounterSigningError::AgentsLength(
self.signing_agents().len(),
))
}
}
pub fn check_agents(&self) -> Result<(), CounterSigningError> {
self.check_agents_dupes()?;
self.check_agents_len()?;
Ok(())
}
pub fn check_enzyme_index(&self) -> Result<(), CounterSigningError> {
match self.enzyme_index() {
Some(index) => {
if (*index as usize) < self.signing_agents().len() {
Ok(())
} else {
Err(CounterSigningError::EnzymeIndex(
self.signing_agents().len(),
*index as usize,
))
}
}
None => Ok(()),
}
}
pub fn signing_agents(&self) -> &CounterSigningAgents {
&self.signing_agents
}
#[cfg(feature = "test_utils")]
pub fn signing_agents_mut(&mut self) -> &mut CounterSigningAgents {
&mut self.signing_agents
}
pub fn enzyme_index(&self) -> &Option<u8> {
&self.enzyme_index
}
#[cfg(feature = "test_utils")]
pub fn enzyme_index_mut(&mut self) -> &mut Option<u8> {
&mut self.enzyme_index
}
pub fn session_times(&self) -> &CounterSigningSessionTimes {
&self.session_times
}
#[cfg(feature = "test_utils")]
pub fn session_times_mut(&mut self) -> &mut CounterSigningSessionTimes {
&mut self.session_times
}
pub fn header_base(&self) -> &HeaderBase {
&self.header_base
}
#[cfg(feature = "test_utils")]
pub fn header_base_mut(&mut self) -> &mut HeaderBase {
&mut self.header_base
}
pub fn preflight_bytes(&self) -> &PreflightBytes {
&self.preflight_bytes
}
#[cfg(feature = "test_utils")]
pub fn preflight_bytes_mut(&mut self) -> &mut PreflightBytes {
&mut self.preflight_bytes
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct PreflightResponse {
request: PreflightRequest,
agent_state: CounterSigningAgentState,
signature: Signature,
}
impl PreflightResponse {
pub fn try_new(
request: PreflightRequest,
agent_state: CounterSigningAgentState,
signature: Signature,
) -> Result<Self, CounterSigningError> {
let preflight_response = Self {
request,
agent_state,
signature,
};
preflight_response.check_integrity()?;
Ok(preflight_response)
}
pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
self.request().check_integrity()
}
pub fn encode_fields_for_signature(
request: &PreflightRequest,
agent_state: &CounterSigningAgentState,
) -> Result<Vec<u8>, SerializedBytesError> {
holochain_serialized_bytes::encode(&(request, agent_state))
}
pub fn encode_for_signature(&self) -> Result<Vec<u8>, SerializedBytesError> {
Self::encode_fields_for_signature(&self.request, &self.agent_state)
}
pub fn request(&self) -> &PreflightRequest {
&self.request
}
#[cfg(feature = "test_utils")]
pub fn request_mut(&mut self) -> &mut PreflightRequest {
&mut self.request
}
pub fn agent_state(&self) -> &CounterSigningAgentState {
&self.agent_state
}
#[cfg(feature = "test_utils")]
pub fn agent_state_mut(&mut self) -> &mut CounterSigningAgentState {
&mut self.agent_state
}
pub fn signature(&self) -> &Signature {
&self.signature
}
#[cfg(feature = "test_utils")]
pub fn signature_mut(&mut self) -> &mut Signature {
&mut self.signature
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum PreflightRequestAcceptance {
Accepted(PreflightResponse),
UnacceptableFutureStart,
UnacceptableAgentNotFound,
Invalid(String),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct CounterSigningAgentState {
agent_index: u8,
chain_top: HeaderHash,
header_seq: u32,
}
impl CounterSigningAgentState {
pub fn new(agent_index: u8, chain_top: HeaderHash, header_seq: u32) -> Self {
Self {
agent_index,
chain_top,
header_seq,
}
}
pub fn agent_index(&self) -> &u8 {
&self.agent_index
}
#[cfg(feature = "test_utils")]
pub fn agent_index_mut(&mut self) -> &mut u8 {
&mut self.agent_index
}
pub fn chain_top(&self) -> &HeaderHash {
&self.chain_top
}
#[cfg(feature = "test_utils")]
pub fn chain_top_mut(&mut self) -> &mut HeaderHash {
&mut self.chain_top
}
pub fn header_seq(&self) -> &u32 {
&self.header_seq
}
#[cfg(feature = "test_utils")]
pub fn header_seq_mut(&mut self) -> &mut u32 {
&mut self.header_seq
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum HeaderBase {
Create(CreateBase),
Update(UpdateBase),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct CreateBase {
entry_type: EntryType,
}
impl CreateBase {
pub fn new(entry_type: EntryType) -> Self {
Self { entry_type }
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct UpdateBase {
original_header_address: HeaderHash,
original_entry_address: EntryHash,
entry_type: EntryType,
}
impl Header {
pub fn from_countersigning_data(
entry_hash: EntryHash,
session_data: &CounterSigningSessionData,
author: AgentPubKey,
) -> Result<Self, CounterSigningError> {
let agent_state = session_data.agent_state_for_agent(&author)?;
Ok(match session_data.preflight_request().header_base() {
HeaderBase::Create(create_base) => Header::Create(Create {
author,
timestamp: session_data.to_timestamp(),
header_seq: agent_state.header_seq + 1,
prev_header: agent_state.chain_top.clone(),
entry_type: create_base.entry_type.clone(),
entry_hash,
}),
HeaderBase::Update(update_base) => Header::Update(Update {
author,
timestamp: session_data.to_timestamp(),
header_seq: agent_state.header_seq + 1,
prev_header: agent_state.chain_top.clone(),
original_header_address: update_base.original_header_address.clone(),
original_entry_address: update_base.original_entry_address.clone(),
entry_type: update_base.entry_type.clone(),
entry_hash,
}),
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct CounterSigningSessionData {
preflight_request: PreflightRequest,
responses: Vec<(CounterSigningAgentState, Signature)>,
}
impl CounterSigningSessionData {
pub fn try_from_responses(
responses: Vec<PreflightResponse>,
) -> Result<Self, CounterSigningError> {
let preflight_response = responses
.get(0)
.ok_or(CounterSigningError::MissingResponse)?
.to_owned();
let responses: Vec<(CounterSigningAgentState, Signature)> = responses
.into_iter()
.map(|response| (response.agent_state.clone(), response.signature))
.collect();
Ok(Self {
preflight_request: preflight_response.request,
responses,
})
}
pub fn agent_state_for_agent(
&self,
agent: &AgentPubKey,
) -> Result<&CounterSigningAgentState, CounterSigningError> {
match self
.preflight_request
.signing_agents()
.iter()
.position(|(pubkey, _)| pubkey == agent)
{
Some(agent_index) => match self.responses.get(agent_index as usize) {
Some((agent_state, _)) => Ok(agent_state),
None => Err(CounterSigningError::AgentIndexOutOfBounds),
},
None => Err(CounterSigningError::AgentIndexOutOfBounds),
}
}
pub fn build_header_set(
&self,
entry_hash: EntryHash,
) -> Result<Vec<Header>, CounterSigningError> {
let mut headers = vec![];
for (agent, _role) in self.preflight_request.signing_agents().iter() {
headers.push(Header::from_countersigning_data(
entry_hash.clone(),
self,
agent.clone(),
)?);
}
Ok(headers)
}
pub fn try_new(
preflight_request: PreflightRequest,
responses: Vec<(CounterSigningAgentState, Signature)>,
) -> Result<Self, CounterSigningError> {
let session_data = Self {
preflight_request,
responses,
};
session_data.check_integrity()?;
Ok(session_data)
}
pub fn check_integrity(&self) -> Result<(), CounterSigningError> {
self.check_responses_indexes()
}
pub fn check_responses_indexes(&self) -> Result<(), CounterSigningError> {
if self.preflight_request().signing_agents().len() != self.responses().len() {
Err(CounterSigningError::CounterSigningSessionResponsesLength(
self.responses().len(),
self.preflight_request().signing_agents().len(),
))
} else {
for (i, (response, _response_signature)) in self.responses().iter().enumerate() {
if *response.agent_index() as usize != i {
return Err(CounterSigningError::CounterSigningSessionResponsesOrder(
*response.agent_index(),
i,
));
}
}
Ok(())
}
}
pub fn to_timestamp(&self) -> Timestamp {
(self.preflight_request().session_times().start() + SESSION_HEADER_TIME_OFFSET)
.unwrap_or(Timestamp::MAX)
}
pub fn preflight_request(&self) -> &PreflightRequest {
&self.preflight_request
}
#[cfg(feature = "test_utils")]
pub fn preflight_request_mut(&mut self) -> &mut PreflightRequest {
&mut self.preflight_request
}
pub fn signing_agents(&self) -> impl Iterator<Item = &AgentPubKey> {
self.preflight_request.signing_agents.iter().map(|(a, _)| a)
}
pub fn responses(&self) -> &Vec<(CounterSigningAgentState, Signature)> {
&self.responses
}
#[cfg(feature = "test_utils")]
pub fn responses_mut(&mut self) -> &mut Vec<(CounterSigningAgentState, Signature)> {
&mut self.responses
}
}
#[cfg(test)]
pub mod test {
use crate::CounterSigningAgentState;
use crate::CounterSigningSessionData;
use crate::Signature;
use matches::assert_matches;
use super::CounterSigningError;
use super::CounterSigningSessionTimes;
use super::PreflightRequest;
use super::SESSION_HEADER_TIME_OFFSET;
use crate::AgentPubKeyFixturator;
use crate::Role;
use arbitrary::Arbitrary;
use fixt::fixt;
use fixt::Predictable;
#[test]
pub fn test_check_countersigning_session_times() {
let mut u = arbitrary::Unstructured::new(&[0; 1000]);
let mut session_times = CounterSigningSessionTimes::arbitrary(&mut u).unwrap();
assert_matches!(
session_times.check_integrity(),
Err(CounterSigningError::CounterSigningSessionTimes(_))
);
*session_times.end_mut() =
(session_times.end() + core::time::Duration::from_millis(1)).unwrap();
assert_matches!(
session_times.check_integrity(),
Err(CounterSigningError::CounterSigningSessionTimes(_))
);
*session_times.end_mut() = (session_times.end() + SESSION_HEADER_TIME_OFFSET).unwrap();
assert_matches!(
session_times.check_integrity(),
Err(CounterSigningError::CounterSigningSessionTimes(_))
);
*session_times.start_mut() =
(session_times.start() + core::time::Duration::from_millis(1)).unwrap();
assert_eq!(session_times.check_integrity().unwrap(), (),);
*session_times.start_mut() =
(session_times.start() + core::time::Duration::from_millis(1)).unwrap();
assert_matches!(
session_times.check_integrity(),
Err(CounterSigningError::CounterSigningSessionTimes(_))
);
}
#[test]
pub fn test_check_countersigning_preflight_request_enzyme_index() {
let mut u = arbitrary::Unstructured::new(&[0; 1000]);
let mut preflight_request = PreflightRequest::arbitrary(&mut u).unwrap();
assert_eq!(preflight_request.check_enzyme_index().unwrap(), ());
let alice = fixt!(AgentPubKey, Predictable);
(*preflight_request.signing_agents_mut()).push((alice.clone(), vec![]));
*preflight_request.enzyme_index_mut() = Some(0);
assert_eq!(preflight_request.check_enzyme_index().unwrap(), (),);
*preflight_request.enzyme_index_mut() = Some(1);
assert_matches!(
preflight_request.check_enzyme_index(),
Err(CounterSigningError::EnzymeIndex(_, _))
);
}
#[test]
pub fn test_check_countersigning_preflight_request_agents_len() {
let mut u = arbitrary::Unstructured::new(&[0; 1000]);
let mut preflight_request = PreflightRequest::arbitrary(&mut u).unwrap();
assert_matches!(
preflight_request.check_agents_len(),
Err(CounterSigningError::AgentsLength(_))
);
let alice = fixt!(AgentPubKey, Predictable);
(*preflight_request.signing_agents_mut()).push((alice.clone(), vec![]));
assert_matches!(
preflight_request.check_agents_len(),
Err(CounterSigningError::AgentsLength(_))
);
let bob = fixt!(AgentPubKey, Predictable, 1);
(*preflight_request.signing_agents_mut()).push((bob.clone(), vec![]));
assert_eq!(preflight_request.check_agents_len().unwrap(), (),);
}
#[test]
pub fn test_check_countersigning_preflight_request_agents_dupes() {
let mut u = arbitrary::Unstructured::new(&[0; 1000]);
let mut preflight_request = PreflightRequest::arbitrary(&mut u).unwrap();
let alice = fixt!(AgentPubKey, Predictable);
let bob = fixt!(AgentPubKey, Predictable, 1);
assert_eq!(preflight_request.check_agents_dupes().unwrap(), (),);
(*preflight_request.signing_agents_mut()).push((alice.clone(), vec![]));
assert_eq!(preflight_request.check_agents_dupes().unwrap(), (),);
(*preflight_request.signing_agents_mut()).push((bob.clone(), vec![]));
assert_eq!(preflight_request.check_agents_dupes().unwrap(), (),);
(*preflight_request.signing_agents_mut()).push((alice.clone(), vec![Role::new(0_u8)]));
assert_matches!(
preflight_request.check_agents_dupes(),
Err(CounterSigningError::AgentsDupes(_))
);
}
#[test]
pub fn test_check_countersigning_session_data_responses_indexes() {
let mut u = arbitrary::Unstructured::new(&[0; 1000]);
let mut session_data = CounterSigningSessionData::arbitrary(&mut u).unwrap();
let alice = fixt!(AgentPubKey, Predictable);
let bob = fixt!(AgentPubKey, Predictable, 1);
assert_eq!(session_data.check_responses_indexes().unwrap(), ());
(*session_data.preflight_request_mut().signing_agents_mut()).push((alice.clone(), vec![]));
assert_matches!(
session_data.check_responses_indexes(),
Err(CounterSigningError::CounterSigningSessionResponsesLength(
_,
_
))
);
(*session_data.preflight_request_mut().signing_agents_mut()).push((bob.clone(), vec![]));
let alice_state = CounterSigningAgentState::arbitrary(&mut u).unwrap();
let alice_signature = Signature::arbitrary(&mut u).unwrap();
let mut bob_state = CounterSigningAgentState::arbitrary(&mut u).unwrap();
let bob_signature = Signature::arbitrary(&mut u).unwrap();
(*session_data.responses_mut()).push((alice_state, alice_signature));
(*session_data.responses_mut()).push((bob_state.clone(), bob_signature.clone()));
assert_matches!(
session_data.check_responses_indexes(),
Err(CounterSigningError::CounterSigningSessionResponsesOrder(
_,
_
))
);
*bob_state.agent_index_mut() = 1;
(*session_data.responses_mut()).pop();
(*session_data.responses_mut()).push((bob_state, bob_signature));
assert_eq!(session_data.check_responses_indexes().unwrap(), (),);
}
}