use borsh::{BorshDeserialize, BorshSerialize};
use near_crypto::{KeyType, SecretKey, Signature};
use near_primitives::borsh::maybestd::sync::Arc;
use near_primitives::hash::CryptoHash;
use near_primitives::network::PeerId;
use once_cell::sync::Lazy;
use crate::time;
pub const EDGE_MIN_TIMESTAMP_NONCE: Lazy<time::Utc> =
Lazy::new(|| time::Utc::from_unix_timestamp(1660000000).unwrap());
#[cfg_attr(feature = "deepsize_feature", derive(deepsize::DeepSizeOf))]
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Default)]
pub struct PartialEdgeInfo {
pub nonce: u64,
pub signature: Signature,
}
impl PartialEdgeInfo {
pub fn new(peer0: &PeerId, peer1: &PeerId, nonce: u64, secret_key: &SecretKey) -> Self {
let data = if peer0 < peer1 {
Edge::build_hash(peer0, peer1, nonce)
} else {
Edge::build_hash(peer1, peer0, nonce)
};
let signature = secret_key.sign(data.as_ref());
Self { nonce, signature }
}
}
#[derive(thiserror::Error, Debug)]
pub enum InvalidNonceError {
#[error("nonce is overflowing i64: {nonce}")]
NonceOutOfBoundsError { nonce: u64 },
}
#[cfg_attr(feature = "deepsize_feature", derive(deepsize::DeepSizeOf))]
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "test_features", derive(serde::Serialize, serde::Deserialize))]
pub struct Edge(pub Arc<EdgeInner>);
impl Edge {
pub fn new(
peer0: PeerId,
peer1: PeerId,
nonce: u64,
signature0: Signature,
signature1: Signature,
) -> Self {
Edge(Arc::new(EdgeInner::new(peer0, peer1, nonce, signature0, signature1)))
}
pub fn with_removal_info(mut self, ri: Option<(bool, Signature)>) -> Edge {
Arc::make_mut(&mut self.0).removal_info = ri;
self
}
pub fn key(&self) -> &(PeerId, PeerId) {
&self.0.key
}
pub fn nonce(&self) -> u64 {
self.0.nonce
}
pub fn signature0(&self) -> &Signature {
&self.0.signature0
}
pub fn signature1(&self) -> &Signature {
&self.0.signature1
}
pub fn removal_info(&self) -> Option<&(bool, Signature)> {
self.0.removal_info.as_ref()
}
pub fn make_fake_edge(peer0: PeerId, peer1: PeerId, nonce: u64) -> Self {
Self(Arc::new(EdgeInner {
key: (peer0, peer1),
nonce,
signature0: Signature::empty(KeyType::ED25519),
signature1: Signature::empty(KeyType::ED25519),
removal_info: None,
}))
}
pub fn build_with_secret_key(
peer0: PeerId,
peer1: PeerId,
nonce: u64,
secret_key: &SecretKey,
signature1: Signature,
) -> Self {
let hash = if peer0 < peer1 {
Self::build_hash(&peer0, &peer1, nonce)
} else {
Self::build_hash(&peer1, &peer0, nonce)
};
let signature0 = secret_key.sign(hash.as_ref());
Self::new(peer0, peer1, nonce, signature0, signature1)
}
pub fn build_hash(peer0: &PeerId, peer1: &PeerId, nonce: u64) -> CryptoHash {
debug_assert!(peer0 < peer1);
CryptoHash::hash_borsh(&(peer0, peer1, nonce))
}
pub fn make_key(peer0: PeerId, peer1: PeerId) -> (PeerId, PeerId) {
if peer0 < peer1 {
(peer0, peer1)
} else {
(peer1, peer0)
}
}
pub fn partial_verify(peer0: &PeerId, peer1: &PeerId, edge_info: &PartialEdgeInfo) -> bool {
let pk = peer1.public_key();
let data = if peer0 < peer1 {
Edge::build_hash(peer0, peer1, edge_info.nonce)
} else {
Edge::build_hash(peer1, peer0, edge_info.nonce)
};
edge_info.signature.verify(data.as_ref(), pk)
}
pub fn next_nonce(nonce: u64) -> u64 {
if nonce % 2 == 1 {
nonce + 2
} else {
nonce + 1
}
}
pub fn remove_edge(&self, my_peer_id: PeerId, sk: &SecretKey) -> Edge {
assert_eq!(self.edge_type(), EdgeState::Active);
let mut edge = self.0.as_ref().clone();
edge.nonce += 1;
let me = edge.key.0 == my_peer_id;
let hash = edge.hash();
let signature = sk.sign(hash.as_ref());
edge.removal_info = Some((me, signature));
Edge(Arc::new(edge))
}
fn hash(&self) -> CryptoHash {
Edge::build_hash(&self.key().0, &self.key().1, self.nonce())
}
fn prev_hash(&self) -> CryptoHash {
Edge::build_hash(&self.key().0, &self.key().1, self.nonce() - 1)
}
pub fn verify(&self) -> bool {
if self.key().0 > self.key().1 {
return false;
}
match self.edge_type() {
EdgeState::Active => {
let data = self.hash();
self.removal_info().is_none()
&& self.signature0().verify(data.as_ref(), self.key().0.public_key())
&& self.signature1().verify(data.as_ref(), self.key().1.public_key())
}
EdgeState::Removed => {
if self.nonce() == 0 {
return false;
}
let add_hash = self.prev_hash();
if !self.signature0().verify(add_hash.as_ref(), self.key().0.public_key())
|| !self.signature1().verify(add_hash.as_ref(), self.key().1.public_key())
{
return false;
}
if let Some((party, signature)) = self.removal_info() {
let peer = if *party { &self.key().0 } else { &self.key().1 };
let del_hash = self.hash();
signature.verify(del_hash.as_ref(), peer.public_key())
} else {
false
}
}
}
}
pub fn edge_type(&self) -> EdgeState {
if self.nonce() % 2 == 1 {
EdgeState::Active
} else {
EdgeState::Removed
}
}
pub fn next(&self) -> u64 {
Edge::next_nonce(self.nonce())
}
pub fn contains_peer(&self, peer_id: &PeerId) -> bool {
self.key().0 == *peer_id || self.key().1 == *peer_id
}
pub fn other(&self, me: &PeerId) -> Option<&PeerId> {
if self.key().0 == *me {
Some(&self.key().1)
} else if self.key().1 == *me {
Some(&self.key().0)
} else {
None
}
}
pub fn is_edge_older_than(&self, utc_timestamp: time::Utc) -> bool {
Edge::nonce_to_utc(self.nonce()).map_or(false, |maybe_timestamp| {
maybe_timestamp.map_or(false, |nonce_timestamp| nonce_timestamp < utc_timestamp)
})
}
pub fn nonce_to_utc(nonce: u64) -> Result<Option<time::Utc>, InvalidNonceError> {
if let Ok(nonce_as_i64) = i64::try_from(nonce) {
time::Utc::from_unix_timestamp(nonce_as_i64)
.map(
|nonce_ts| {
if nonce_ts > *EDGE_MIN_TIMESTAMP_NONCE {
Some(nonce_ts)
} else {
None
}
},
)
.map_err(|_| InvalidNonceError::NonceOutOfBoundsError { nonce })
} else {
Err(InvalidNonceError::NonceOutOfBoundsError { nonce })
}
}
}
#[cfg_attr(feature = "deepsize_feature", derive(deepsize::DeepSizeOf))]
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "test_features", derive(serde::Serialize, serde::Deserialize))]
pub struct EdgeInner {
key: (PeerId, PeerId),
nonce: u64,
signature0: Signature,
signature1: Signature,
removal_info: Option<(bool, Signature)>,
}
impl EdgeInner {
fn new(
peer0: PeerId,
peer1: PeerId,
nonce: u64,
signature0: Signature,
signature1: Signature,
) -> Self {
let (peer0, signature0, peer1, signature1) = if peer0 < peer1 {
(peer0, signature0, peer1, signature1)
} else {
(peer1, signature1, peer0, signature0)
};
Self { key: (peer0, peer1), nonce, signature0, signature1, removal_info: None }
}
fn hash(&self) -> CryptoHash {
Edge::build_hash(&self.key.0, &self.key.1, self.nonce)
}
}
#[derive(BorshSerialize, BorshDeserialize, Clone, PartialEq, Eq, Debug, Hash)]
pub enum EdgeState {
Active,
Removed,
}