zkgroup/common/
simple_types.rs

1//
2// Copyright 2020 Signal Messenger, LLC.
3// SPDX-License-Identifier: AGPL-3.0-only
4//
5
6use curve25519_dalek_signal::scalar::Scalar;
7use partial_default::PartialDefault;
8use serde::{Deserialize, Serialize};
9use zkcredential::attributes::PublicAttribute;
10
11use crate::common::constants::*;
12
13pub type AesKeyBytes = [u8; AES_KEY_LEN];
14pub type GroupMasterKeyBytes = [u8; GROUP_MASTER_KEY_LEN];
15pub type UidBytes = [u8; UUID_LEN];
16pub type ProfileKeyBytes = [u8; PROFILE_KEY_LEN];
17pub type RandomnessBytes = [u8; RANDOMNESS_LEN];
18pub type SignatureBytes = [u8; SIGNATURE_LEN];
19pub type NotarySignatureBytes = [u8; SIGNATURE_LEN];
20pub type GroupIdentifierBytes = [u8; GROUP_IDENTIFIER_LEN];
21pub type ProfileKeyVersionBytes = [u8; PROFILE_KEY_VERSION_LEN];
22// TODO: Use ascii::Char when stable (the "encoding" is hex)
23pub type ProfileKeyVersionEncodedBytes = [u8; PROFILE_KEY_VERSION_ENCODED_LEN];
24
25// A random UUID that the receipt issuing server will blind authorize to redeem a given receipt
26// level within a certain time frame.
27pub type ReceiptSerialBytes = [u8; RECEIPT_SERIAL_LEN];
28
29/// Timestamp measured in seconds past the epoch.
30///
31/// Clients should only accept round multiples of 86400 to avoid fingerprinting by the server.
32/// For expirations, the timestamp should be within a couple of days into the future;
33/// for redemption times, it should be within a day of the current date.
34#[derive(
35    Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize, PartialDefault,
36)]
37#[serde(transparent)]
38#[repr(transparent)]
39pub struct Timestamp(u64);
40
41impl Timestamp {
42    #[inline]
43    pub const fn from_epoch_seconds(seconds: u64) -> Self {
44        Self(seconds)
45    }
46
47    #[inline]
48    pub const fn epoch_seconds(&self) -> u64 {
49        self.0
50    }
51
52    #[inline]
53    pub const fn add_seconds(&self, seconds: u64) -> Self {
54        Self(self.0 + seconds)
55    }
56
57    #[inline]
58    pub const fn sub_seconds(&self, seconds: u64) -> Self {
59        Self(self.0 - seconds)
60    }
61
62    #[inline]
63    pub fn checked_add_seconds(&self, seconds: u64) -> Option<Self> {
64        self.0.checked_add(seconds).map(Self)
65    }
66
67    #[inline]
68    pub fn checked_sub_seconds(&self, seconds: u64) -> Option<Self> {
69        self.0.checked_sub(seconds).map(Self)
70    }
71
72    #[inline]
73    pub const fn is_day_aligned(&self) -> bool {
74        self.0 % SECONDS_PER_DAY == 0
75    }
76
77    #[inline]
78    pub fn to_be_bytes(self) -> [u8; 8] {
79        self.0.to_be_bytes()
80    }
81
82    /// Number of seconds that `self` is after `before`.
83    ///
84    /// Returns `0` if `self` is equal to or earlier than `before`.
85    pub(crate) fn saturating_seconds_since(&self, before: Timestamp) -> u64 {
86        self.0.saturating_sub(before.0)
87    }
88}
89
90impl From<Timestamp> for std::time::SystemTime {
91    fn from(Timestamp(seconds): Timestamp) -> Self {
92        std::time::UNIX_EPOCH + std::time::Duration::from_secs(seconds)
93    }
94}
95
96impl From<std::time::SystemTime> for Timestamp {
97    fn from(timestamp: std::time::SystemTime) -> Self {
98        Self::from_epoch_seconds(
99            timestamp
100                .duration_since(std::time::UNIX_EPOCH)
101                .unwrap_or_default()
102                .as_secs(),
103        )
104    }
105}
106
107impl rand::distr::Distribution<Timestamp> for rand::distr::StandardUniform {
108    fn sample<R: rand::prelude::Rng + ?Sized>(&self, rng: &mut R) -> Timestamp {
109        Timestamp(Self::sample(self, rng))
110    }
111}
112
113impl PublicAttribute for Timestamp {
114    fn hash_into(&self, sho: &mut dyn poksho::ShoApi) {
115        self.0.hash_into(sho)
116    }
117}
118
119// Used to tell the server handling receipt redemptions what to redeem the receipt for. Clients
120// should validate this matches their expectations.
121pub type ReceiptLevel = u64;
122
123pub fn encode_redemption_time(redemption_time: u32) -> Scalar {
124    let mut scalar_bytes: [u8; 32] = Default::default();
125    scalar_bytes[0..4].copy_from_slice(&redemption_time.to_be_bytes());
126    Scalar::from_bytes_mod_order(scalar_bytes)
127}
128
129pub fn encode_receipt_serial_bytes(receipt_serial_bytes: ReceiptSerialBytes) -> Scalar {
130    let mut scalar_bytes: [u8; 32] = Default::default();
131    scalar_bytes[0..16].copy_from_slice(&receipt_serial_bytes[..]);
132    Scalar::from_bytes_mod_order(scalar_bytes)
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_encode_scalar() {
141        let s_bytes = [0xFF; 32];
142        match bincode::deserialize::<Scalar>(&s_bytes) {
143            Err(_) => (),
144            Ok(_) => unreachable!(),
145        }
146    }
147}