use thiserror::Error;
pub const PRO_STANDARD_CHARACTER_LIMIT: usize = 2000;
pub const PRO_HIGHER_CHARACTER_LIMIT: usize = 10000;
pub const PRO_STANDARD_PINNED_CONVERSATION_LIMIT: usize = 5;
pub const COMMUNITY_OR_1O1_MSG_PADDING: usize = 160;
pub const PADDING_TERMINATING_BYTE: u8 = 0x80;
pub const GENERATE_PROOF_HASH_PERSONALISATION: &str = "ProGenerateProof";
pub const BUILD_PROOF_HASH_PERSONALISATION: &str = "ProProof________";
pub const ADD_PRO_PAYMENT_HASH_PERSONALISATION: &str = "ProAddPayment___";
pub const SET_PAYMENT_REFUND_REQUESTED_HASH_PERSONALISATION: &str = "ProSetRefundReq_";
pub const GET_PRO_DETAILS_HASH_PERSONALISATION: &str = "ProGetProDetReq_";
pub const PRO_BACKEND_BLAKE2B_PERSONALISATION: &str = "SeshProBackend__";
pub struct ProtocolStrings {
pub build_variant_apk: &'static str,
pub build_variant_fdroid: &'static str,
pub build_variant_huawei: &'static str,
pub build_variant_ipa: &'static str,
pub url_donations: &'static str,
pub url_donations_app: &'static str,
pub url_download: &'static str,
pub url_faq: &'static str,
pub url_feedback: &'static str,
pub url_network: &'static str,
pub url_privacy_policy: &'static str,
pub url_pro_access_not_found: &'static str,
pub url_pro_faq: &'static str,
pub url_pro_page: &'static str,
pub url_pro_privacy_policy: &'static str,
pub url_pro_roadmap: &'static str,
pub url_pro_support: &'static str,
pub url_pro_terms_of_service: &'static str,
pub url_pro_upgrade: &'static str,
pub url_staking: &'static str,
pub url_support: &'static str,
pub url_survey: &'static str,
pub url_terms_of_service: &'static str,
pub url_token: &'static str,
pub url_translate: &'static str,
}
pub static PROTOCOL_STRINGS: ProtocolStrings = ProtocolStrings {
build_variant_apk: "APK",
build_variant_fdroid: "F-Droid Store",
build_variant_huawei: "Huawei App Gallery",
build_variant_ipa: "IPA",
url_donations: "https://getsession.org/donate",
url_donations_app: "https://getsession.org/donate#app",
url_download: "https://getsession.org/download",
url_faq: "https://getsession.org/faq",
url_feedback: "https://getsession.org/feedback",
url_network: "https://docs.getsession.org/session-network",
url_privacy_policy: "https://getsession.org/privacy-policy",
url_pro_access_not_found:
"https://sessionapp.zendesk.com/hc/sections/4416517450649-Support",
url_pro_faq: "https://getsession.org/pro#faq",
url_pro_page: "https://getsession.org/pro",
url_pro_privacy_policy: "https://getsession.org/pro-privacy",
url_pro_roadmap: "https://getsession.org/pro#roadmap",
url_pro_support: "https://getsession.org/pro-support",
url_pro_terms_of_service: "https://getsession.org/pro-terms",
url_pro_upgrade: "https://getsession.org/pro#upgrade",
url_staking: "https://docs.getsession.org/session-network/staking",
url_support: "https://getsession.org/support",
url_survey: "https://getsession.org/survey",
url_terms_of_service: "https://getsession.org/terms-of-service",
url_token: "https://token.getsession.org",
url_translate: "https://getsession.org/translate",
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ProStatus {
InvalidProBackendSig = 1,
InvalidUserSig = 2,
Valid = 3,
Expired = 4,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ProProofVersion {
V0 = 0,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProProof {
pub version: u8,
pub gen_index_hash: [u8; 32],
pub rotating_pubkey: [u8; 32],
pub expiry_unix_ts_ms: u64,
pub sig: [u8; 64],
}
#[derive(Debug, Error)]
pub enum ProProofError {
#[error("Invalid verify_pubkey: must be 32 byte Ed25519 public key (was: {0})")]
InvalidPubkeySize(usize),
#[error("Invalid signature: must be 64 bytes (was: {0})")]
InvalidSignatureSize(usize),
#[error("Crypto error: {0}")]
Crypto(#[from] crate::crypto::types::CryptoError),
}
impl ProProof {
pub fn hash(&self) -> [u8; 32] {
proof_hash_internal(
self.version,
&self.gen_index_hash,
&self.rotating_pubkey,
self.expiry_unix_ts_ms,
)
}
pub fn verify_signature(&self, verify_pubkey: &[u8]) -> Result<bool, ProProofError> {
if verify_pubkey.len() != 32 {
return Err(ProProofError::InvalidPubkeySize(verify_pubkey.len()));
}
let hash_to_sign = self.hash();
let result = crate::crypto::ed25519::verify(&self.sig, verify_pubkey, &hash_to_sign)?;
Ok(result)
}
pub fn verify_message(&self, sig: &[u8], msg: &[u8]) -> Result<bool, ProProofError> {
if sig.len() != 64 {
return Err(ProProofError::InvalidSignatureSize(sig.len()));
}
let result = crate::crypto::ed25519::verify(sig, &self.rotating_pubkey, msg)?;
Ok(result)
}
pub fn is_active(&self, unix_ts_ms: u64) -> bool {
unix_ts_ms <= self.expiry_unix_ts_ms
}
pub fn status(
&self,
verify_pubkey: &[u8],
unix_ts_ms: u64,
signed_msg: Option<ProSignedMessage<'_>>,
) -> Result<ProStatus, ProProofError> {
if !self.verify_signature(verify_pubkey)? {
return Ok(ProStatus::InvalidProBackendSig);
}
if let Some(sm) = signed_msg
&& !self.verify_message(sm.sig, sm.msg)? {
return Ok(ProStatus::InvalidUserSig);
}
if !self.is_active(unix_ts_ms) {
return Ok(ProStatus::Expired);
}
Ok(ProStatus::Valid)
}
}
#[derive(Debug, Clone, Copy)]
pub struct ProSignedMessage<'a> {
pub sig: &'a [u8],
pub msg: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u64)]
pub enum ProProfileFeature {
ProBadge = 0,
AnimatedAvatar = 1,
}
pub const PRO_PROFILE_FEATURES_COUNT: usize = 2;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ProProfileBitset {
pub data: u64,
}
impl ProProfileBitset {
pub fn set(&mut self, feature: ProProfileFeature) {
self.data |= 1u64 << (feature as u64);
}
pub fn unset(&mut self, feature: ProProfileFeature) {
self.data &= !(1u64 << (feature as u64));
}
pub fn is_set(&self, feature: ProProfileFeature) -> bool {
(self.data & (1u64 << (feature as u64))) != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u64)]
pub enum ProMessageFeature {
CharacterLimit10K = 0,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ProMessageBitset {
pub data: u64,
}
impl ProMessageBitset {
pub fn set(&mut self, feature: ProMessageFeature) {
self.data |= 1u64 << (feature as u64);
}
pub fn unset(&mut self, feature: ProMessageFeature) {
self.data &= !(1u64 << (feature as u64));
}
pub fn is_set(&self, feature: ProMessageFeature) -> bool {
(self.data & (1u64 << (feature as u64))) != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ProFeaturesForMsgStatus {
Success = 0,
UtfDecodingError = 1,
ExceedsCharacterLimit = 2,
}
#[derive(Debug, Clone)]
pub struct ProFeaturesForMsg {
pub status: ProFeaturesForMsgStatus,
pub error: Option<String>,
pub bitset: ProMessageBitset,
pub codepoint_count: usize,
}
pub fn pro_features_for_utf8(text: &str) -> ProFeaturesForMsg {
let codepoint_count = text.chars().count();
if codepoint_count > PRO_HIGHER_CHARACTER_LIMIT {
return ProFeaturesForMsg {
status: ProFeaturesForMsgStatus::ExceedsCharacterLimit,
error: Some("Message exceeds the maximum character limit allowed".into()),
bitset: ProMessageBitset::default(),
codepoint_count,
};
}
let mut bitset = ProMessageBitset::default();
if codepoint_count > PRO_STANDARD_CHARACTER_LIMIT {
bitset.set(ProMessageFeature::CharacterLimit10K);
}
ProFeaturesForMsg {
status: ProFeaturesForMsgStatus::Success,
error: None,
bitset,
codepoint_count,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum DestinationType {
SyncOr1o1 = 0,
Group = 1,
CommunityInbox = 2,
Community = 3,
}
pub mod envelope_flags {
pub const SOURCE: u32 = 1 << 0;
pub const SOURCE_DEVICE: u32 = 1 << 1;
pub const SERVER_TIMESTAMP: u32 = 1 << 2;
pub const PRO_SIG: u32 = 1 << 3;
pub const TIMESTAMP: u32 = 1 << 4;
}
pub fn pad_message(payload: &[u8]) -> Vec<u8> {
let padded_content_size = payload.len() + 1; let bytes_for_padding =
COMMUNITY_OR_1O1_MSG_PADDING - (padded_content_size % COMMUNITY_OR_1O1_MSG_PADDING);
let total_size = padded_content_size + bytes_for_padding;
debug_assert!(total_size.is_multiple_of(COMMUNITY_OR_1O1_MSG_PADDING));
let mut result = vec![0u8; total_size];
result[..payload.len()].copy_from_slice(payload);
result[payload.len()] = PADDING_TERMINATING_BYTE;
result
}
pub fn unpad_message(payload: &[u8]) -> &[u8] {
let mut size = payload.len();
while size > 0 {
let ch = payload[size - 1];
if ch != 0 && ch != PADDING_TERMINATING_BYTE {
return payload;
}
size -= 1;
if ch == PADDING_TERMINATING_BYTE {
break;
}
}
&payload[..size]
}
fn proof_hash_internal(
version: u8,
gen_index_hash: &[u8; 32],
rotating_pubkey: &[u8; 32],
expiry_unix_ts_ms: u64,
) -> [u8; 32] {
use blake2b_simd::Params;
let mut params = Params::new();
params.hash_length(32);
debug_assert!(BUILD_PROOF_HASH_PERSONALISATION.len() == 16);
let mut personal = [0u8; 16];
personal.copy_from_slice(BUILD_PROOF_HASH_PERSONALISATION.as_bytes());
params.personal(&personal);
let mut state = params.to_state();
state.update(&[version]);
state.update(gen_index_hash);
state.update(rotating_pubkey);
state.update(&expiry_unix_ts_ms.to_le_bytes());
let hash = state.finalize();
let mut result = [0u8; 32];
result.copy_from_slice(hash.as_bytes());
result
}
pub fn make_blake2b32_hasher(personalisation: &str) -> blake2b_simd::State {
debug_assert!(personalisation.len() == 16);
let mut personal = [0u8; 16];
let len = personalisation.len().min(16);
personal[..len].copy_from_slice(&personalisation.as_bytes()[..len]);
let mut params = blake2b_simd::Params::new();
params.hash_length(32);
params.personal(&personal);
params.to_state()
}
pub const GROUP_MESSAGE_PADDING: usize = 256;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constants() {
assert_eq!(PRO_STANDARD_CHARACTER_LIMIT, 2000);
assert_eq!(PRO_HIGHER_CHARACTER_LIMIT, 10000);
assert_eq!(PRO_STANDARD_PINNED_CONVERSATION_LIMIT, 5);
assert_eq!(COMMUNITY_OR_1O1_MSG_PADDING, 160);
}
#[test]
fn test_personalisation_lengths() {
assert_eq!(GENERATE_PROOF_HASH_PERSONALISATION.len(), 16);
assert_eq!(BUILD_PROOF_HASH_PERSONALISATION.len(), 16);
assert_eq!(ADD_PRO_PAYMENT_HASH_PERSONALISATION.len(), 16);
assert_eq!(SET_PAYMENT_REFUND_REQUESTED_HASH_PERSONALISATION.len(), 16);
assert_eq!(GET_PRO_DETAILS_HASH_PERSONALISATION.len(), 16);
}
#[test]
fn test_protocol_strings() {
assert_eq!(PROTOCOL_STRINGS.build_variant_apk, "APK");
assert_eq!(PROTOCOL_STRINGS.build_variant_fdroid, "F-Droid Store");
assert_eq!(PROTOCOL_STRINGS.build_variant_huawei, "Huawei App Gallery");
assert_eq!(PROTOCOL_STRINGS.build_variant_ipa, "IPA");
assert_eq!(
PROTOCOL_STRINGS.url_donations,
"https://getsession.org/donate"
);
assert_eq!(
PROTOCOL_STRINGS.url_donations_app,
"https://getsession.org/donate#app"
);
assert_eq!(
PROTOCOL_STRINGS.url_download,
"https://getsession.org/download"
);
assert_eq!(PROTOCOL_STRINGS.url_faq, "https://getsession.org/faq");
assert_eq!(
PROTOCOL_STRINGS.url_feedback,
"https://getsession.org/feedback"
);
assert_eq!(
PROTOCOL_STRINGS.url_network,
"https://docs.getsession.org/session-network"
);
assert_eq!(
PROTOCOL_STRINGS.url_privacy_policy,
"https://getsession.org/privacy-policy"
);
assert_eq!(
PROTOCOL_STRINGS.url_pro_access_not_found,
"https://sessionapp.zendesk.com/hc/sections/4416517450649-Support"
);
assert_eq!(
PROTOCOL_STRINGS.url_pro_faq,
"https://getsession.org/pro#faq"
);
assert_eq!(
PROTOCOL_STRINGS.url_pro_page,
"https://getsession.org/pro"
);
assert_eq!(
PROTOCOL_STRINGS.url_pro_privacy_policy,
"https://getsession.org/pro-privacy"
);
assert_eq!(
PROTOCOL_STRINGS.url_pro_roadmap,
"https://getsession.org/pro#roadmap"
);
assert_eq!(
PROTOCOL_STRINGS.url_pro_support,
"https://getsession.org/pro-support"
);
assert_eq!(
PROTOCOL_STRINGS.url_pro_terms_of_service,
"https://getsession.org/pro-terms"
);
assert_eq!(
PROTOCOL_STRINGS.url_pro_upgrade,
"https://getsession.org/pro#upgrade"
);
assert_eq!(
PROTOCOL_STRINGS.url_staking,
"https://docs.getsession.org/session-network/staking"
);
assert_eq!(
PROTOCOL_STRINGS.url_support,
"https://getsession.org/support"
);
assert_eq!(
PROTOCOL_STRINGS.url_survey,
"https://getsession.org/survey"
);
assert_eq!(
PROTOCOL_STRINGS.url_terms_of_service,
"https://getsession.org/terms-of-service"
);
assert_eq!(PROTOCOL_STRINGS.url_token, "https://token.getsession.org");
assert_eq!(
PROTOCOL_STRINGS.url_translate,
"https://getsession.org/translate"
);
}
#[test]
fn test_pro_status_values() {
assert_eq!(ProStatus::InvalidProBackendSig as u8, 1);
assert_eq!(ProStatus::InvalidUserSig as u8, 2);
assert_eq!(ProStatus::Valid as u8, 3);
assert_eq!(ProStatus::Expired as u8, 4);
}
#[test]
fn test_pro_profile_bitset() {
let mut bitset = ProProfileBitset::default();
assert!(!bitset.is_set(ProProfileFeature::ProBadge));
assert!(!bitset.is_set(ProProfileFeature::AnimatedAvatar));
bitset.set(ProProfileFeature::ProBadge);
assert!(bitset.is_set(ProProfileFeature::ProBadge));
assert!(!bitset.is_set(ProProfileFeature::AnimatedAvatar));
assert_eq!(bitset.data, 1);
bitset.set(ProProfileFeature::AnimatedAvatar);
assert!(bitset.is_set(ProProfileFeature::ProBadge));
assert!(bitset.is_set(ProProfileFeature::AnimatedAvatar));
assert_eq!(bitset.data, 3);
bitset.unset(ProProfileFeature::ProBadge);
assert!(!bitset.is_set(ProProfileFeature::ProBadge));
assert!(bitset.is_set(ProProfileFeature::AnimatedAvatar));
assert_eq!(bitset.data, 2);
}
#[test]
fn test_pro_message_bitset() {
let mut bitset = ProMessageBitset::default();
assert!(!bitset.is_set(ProMessageFeature::CharacterLimit10K));
bitset.set(ProMessageFeature::CharacterLimit10K);
assert!(bitset.is_set(ProMessageFeature::CharacterLimit10K));
assert_eq!(bitset.data, 1);
bitset.unset(ProMessageFeature::CharacterLimit10K);
assert!(!bitset.is_set(ProMessageFeature::CharacterLimit10K));
assert_eq!(bitset.data, 0);
}
#[test]
fn test_pro_features_for_utf8_short_message() {
let msg = "Hello, world!";
let result = pro_features_for_utf8(msg);
assert_eq!(result.status, ProFeaturesForMsgStatus::Success);
assert!(result.error.is_none());
assert!(!result.bitset.is_set(ProMessageFeature::CharacterLimit10K));
assert_eq!(result.codepoint_count, 13);
}
#[test]
fn test_pro_features_for_utf8_at_standard_limit() {
let msg: String = "a".repeat(PRO_STANDARD_CHARACTER_LIMIT);
let result = pro_features_for_utf8(&msg);
assert_eq!(result.status, ProFeaturesForMsgStatus::Success);
assert!(!result.bitset.is_set(ProMessageFeature::CharacterLimit10K));
assert_eq!(result.codepoint_count, PRO_STANDARD_CHARACTER_LIMIT);
}
#[test]
fn test_pro_features_for_utf8_above_standard_limit() {
let msg: String = "a".repeat(PRO_STANDARD_CHARACTER_LIMIT + 1);
let result = pro_features_for_utf8(&msg);
assert_eq!(result.status, ProFeaturesForMsgStatus::Success);
assert!(result.bitset.is_set(ProMessageFeature::CharacterLimit10K));
assert_eq!(result.codepoint_count, PRO_STANDARD_CHARACTER_LIMIT + 1);
}
#[test]
fn test_pro_features_for_utf8_at_pro_limit() {
let msg: String = "a".repeat(PRO_HIGHER_CHARACTER_LIMIT);
let result = pro_features_for_utf8(&msg);
assert_eq!(result.status, ProFeaturesForMsgStatus::Success);
assert!(result.bitset.is_set(ProMessageFeature::CharacterLimit10K));
assert_eq!(result.codepoint_count, PRO_HIGHER_CHARACTER_LIMIT);
}
#[test]
fn test_pro_features_for_utf8_above_pro_limit() {
let msg: String = "a".repeat(PRO_HIGHER_CHARACTER_LIMIT + 1);
let result = pro_features_for_utf8(&msg);
assert_eq!(result.status, ProFeaturesForMsgStatus::ExceedsCharacterLimit);
assert!(result.error.is_some());
}
#[test]
fn test_pro_features_for_utf8_multibyte() {
let msg = "\u{1F600}\u{1F601}\u{1F602}"; let result = pro_features_for_utf8(msg);
assert_eq!(result.status, ProFeaturesForMsgStatus::Success);
assert_eq!(result.codepoint_count, 3);
}
#[test]
fn test_pad_message_alignment() {
let payload = b"hello";
let padded = pad_message(payload);
assert_eq!(padded.len() % COMMUNITY_OR_1O1_MSG_PADDING, 0);
assert!(padded.len() >= payload.len() + 1);
assert_eq!(&padded[..payload.len()], payload);
assert_eq!(padded[payload.len()], PADDING_TERMINATING_BYTE);
for &b in &padded[payload.len() + 1..] {
assert_eq!(b, 0);
}
}
#[test]
fn test_pad_message_exact_boundary() {
let payload = vec![0xAA; COMMUNITY_OR_1O1_MSG_PADDING - 1];
let padded = pad_message(&payload);
assert_eq!(padded.len() % COMMUNITY_OR_1O1_MSG_PADDING, 0);
assert_eq!(padded.len(), COMMUNITY_OR_1O1_MSG_PADDING * 2);
}
#[test]
fn test_unpad_message() {
let original = b"hello world";
let padded = pad_message(original);
let unpadded = unpad_message(&padded);
assert_eq!(unpadded, original);
}
#[test]
fn test_unpad_message_empty() {
let unpadded = unpad_message(&[]);
assert_eq!(unpadded, &[] as &[u8]);
}
#[test]
fn test_proof_hash_deterministic() {
let proof = ProProof {
version: 0,
gen_index_hash: [1u8; 32],
rotating_pubkey: [2u8; 32],
expiry_unix_ts_ms: 1000000,
sig: [0u8; 64],
};
let h1 = proof.hash();
let h2 = proof.hash();
assert_eq!(h1, h2);
assert_ne!(h1, [0u8; 32]);
}
#[test]
fn test_proof_hash_different_inputs() {
let proof1 = ProProof {
version: 0,
gen_index_hash: [1u8; 32],
rotating_pubkey: [2u8; 32],
expiry_unix_ts_ms: 1000000,
sig: [0u8; 64],
};
let proof2 = ProProof {
version: 1,
gen_index_hash: [1u8; 32],
rotating_pubkey: [2u8; 32],
expiry_unix_ts_ms: 1000000,
sig: [0u8; 64],
};
assert_ne!(proof1.hash(), proof2.hash());
}
#[test]
fn test_proof_is_active() {
let proof = ProProof {
version: 0,
gen_index_hash: [0u8; 32],
rotating_pubkey: [0u8; 32],
expiry_unix_ts_ms: 5000,
sig: [0u8; 64],
};
assert!(proof.is_active(4999));
assert!(proof.is_active(5000));
assert!(!proof.is_active(5001));
}
#[test]
fn test_proof_verify_signature_invalid_pubkey_size() {
let proof = ProProof {
version: 0,
gen_index_hash: [0u8; 32],
rotating_pubkey: [0u8; 32],
expiry_unix_ts_ms: 5000,
sig: [0u8; 64],
};
let bad_key = [0u8; 16];
let result = proof.verify_signature(&bad_key);
assert!(result.is_err());
}
#[test]
fn test_proof_verify_message_invalid_sig_size() {
let proof = ProProof {
version: 0,
gen_index_hash: [0u8; 32],
rotating_pubkey: [0u8; 32],
expiry_unix_ts_ms: 5000,
sig: [0u8; 64],
};
let bad_sig = [0u8; 32];
let result = proof.verify_message(&bad_sig, b"hello");
assert!(result.is_err());
}
#[test]
fn test_proof_sign_and_verify() {
let (backend_pk, backend_sk) = crate::crypto::ed25519::ed25519_key_pair();
let (rotating_pk, rotating_sk) = crate::crypto::ed25519::ed25519_key_pair();
let mut proof = ProProof {
version: 0,
gen_index_hash: [0xAA; 32],
rotating_pubkey: rotating_pk,
expiry_unix_ts_ms: u64::MAX, sig: [0u8; 64],
};
let hash = proof.hash();
let sig = crate::crypto::ed25519::sign(&backend_sk, &hash).unwrap();
proof.sig = sig;
assert!(proof.verify_signature(&backend_pk).unwrap());
let (wrong_pk, _) = crate::crypto::ed25519::ed25519_key_pair();
assert!(!proof.verify_signature(&wrong_pk).unwrap());
let msg = b"test message";
let msg_sig = crate::crypto::ed25519::sign(&rotating_sk, msg).unwrap();
assert!(proof.verify_message(&msg_sig, msg).unwrap());
let status = proof.status(&backend_pk, 0, None).unwrap();
assert_eq!(status, ProStatus::Valid);
let status = proof
.status(
&backend_pk,
0,
Some(ProSignedMessage {
sig: &msg_sig,
msg,
}),
)
.unwrap();
assert_eq!(status, ProStatus::Valid);
}
#[test]
fn test_proof_status_expired() {
let (backend_pk, backend_sk) = crate::crypto::ed25519::ed25519_key_pair();
let mut proof = ProProof {
version: 0,
gen_index_hash: [0xBB; 32],
rotating_pubkey: [0u8; 32],
expiry_unix_ts_ms: 1000,
sig: [0u8; 64],
};
let hash = proof.hash();
proof.sig = crate::crypto::ed25519::sign(&backend_sk, &hash).unwrap();
let status = proof.status(&backend_pk, 1001, None).unwrap();
assert_eq!(status, ProStatus::Expired);
}
#[test]
fn test_destination_type_values() {
assert_eq!(DestinationType::SyncOr1o1 as u8, 0);
assert_eq!(DestinationType::Group as u8, 1);
assert_eq!(DestinationType::CommunityInbox as u8, 2);
assert_eq!(DestinationType::Community as u8, 3);
}
#[test]
fn test_envelope_flags() {
assert_eq!(envelope_flags::SOURCE, 1);
assert_eq!(envelope_flags::SOURCE_DEVICE, 2);
assert_eq!(envelope_flags::SERVER_TIMESTAMP, 4);
assert_eq!(envelope_flags::PRO_SIG, 8);
assert_eq!(envelope_flags::TIMESTAMP, 16);
}
#[test]
fn test_make_blake2b32_hasher() {
let mut hasher = make_blake2b32_hasher(BUILD_PROOF_HASH_PERSONALISATION);
hasher.update(b"test data");
let result = hasher.finalize();
assert_eq!(result.as_bytes().len(), 32);
}
}