Add project files.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// Copyright 2020-2022 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
pub const NUM_AUTH_CRED_ATTRIBUTES: usize = 3;
|
||||
pub const NUM_PROFILE_KEY_CRED_ATTRIBUTES: usize = 4;
|
||||
pub const NUM_RECEIPT_CRED_ATTRIBUTES: usize = 2;
|
||||
|
||||
pub const PRESENTATION_VERSION_1: u8 = 0;
|
||||
pub const PRESENTATION_VERSION_2: u8 = 1;
|
||||
pub const PRESENTATION_VERSION_3: u8 = 2;
|
||||
pub const PRESENTATION_VERSION_4: u8 = 3;
|
||||
|
||||
pub const AES_KEY_LEN: usize = 32;
|
||||
pub const AESGCM_NONCE_LEN: usize = 12;
|
||||
pub const AESGCM_TAG_LEN: usize = 16;
|
||||
pub const GROUP_MASTER_KEY_LEN: usize = 32;
|
||||
pub const GROUP_SECRET_PARAMS_LEN: usize = 289;
|
||||
pub const GROUP_PUBLIC_PARAMS_LEN: usize = 97;
|
||||
pub const GROUP_IDENTIFIER_LEN: usize = 32;
|
||||
pub const AUTH_CREDENTIAL_LEN: usize = 181;
|
||||
pub const AUTH_CREDENTIAL_PRESENTATION_V2_LEN: usize = 461;
|
||||
pub const AUTH_CREDENTIAL_RESPONSE_LEN: usize = 361;
|
||||
pub const AUTH_CREDENTIAL_WITH_PNI_LEN: usize = 265;
|
||||
pub const AUTH_CREDENTIAL_WITH_PNI_RESPONSE_LEN: usize = 425;
|
||||
pub const PROFILE_KEY_LEN: usize = 32;
|
||||
pub const PROFILE_KEY_CIPHERTEXT_LEN: usize = 65;
|
||||
pub const PROFILE_KEY_COMMITMENT_LEN: usize = 97;
|
||||
pub const EXPIRING_PROFILE_KEY_CREDENTIAL_LEN: usize = 153;
|
||||
pub(crate) const PROFILE_KEY_CREDENTIAL_PRESENTATION_V1_LEN: usize = 713;
|
||||
pub const PROFILE_KEY_CREDENTIAL_PRESENTATION_V2_LEN: usize = 713;
|
||||
pub const PROFILE_KEY_CREDENTIAL_REQUEST_LEN: usize = 329;
|
||||
pub const PROFILE_KEY_CREDENTIAL_REQUEST_CONTEXT_LEN: usize = 473;
|
||||
pub const EXPIRING_PROFILE_KEY_CREDENTIAL_RESPONSE_LEN: usize = 497;
|
||||
pub const PROFILE_KEY_VERSION_LEN: usize = 32;
|
||||
pub const PROFILE_KEY_VERSION_ENCODED_LEN: usize = 64;
|
||||
pub const RECEIPT_CREDENTIAL_LEN: usize = 129;
|
||||
pub const RECEIPT_CREDENTIAL_PRESENTATION_LEN: usize = 329;
|
||||
pub const RECEIPT_CREDENTIAL_REQUEST_LEN: usize = 97;
|
||||
pub const RECEIPT_CREDENTIAL_REQUEST_CONTEXT_LEN: usize = 177;
|
||||
pub const RECEIPT_CREDENTIAL_RESPONSE_LEN: usize = 409;
|
||||
pub const RECEIPT_SERIAL_LEN: usize = 16;
|
||||
pub const RESERVED_LEN: usize = 1;
|
||||
pub const SERVER_SECRET_PARAMS_LEN: usize = 2721;
|
||||
pub const SERVER_PUBLIC_PARAMS_LEN: usize = 673;
|
||||
pub const UUID_CIPHERTEXT_LEN: usize = 65;
|
||||
pub const RANDOMNESS_LEN: usize = 32;
|
||||
pub const SIGNATURE_LEN: usize = 64;
|
||||
pub const UUID_LEN: usize = 16;
|
||||
pub const ACCESS_KEY_LEN: usize = 16;
|
||||
|
||||
/// Seconds in a 24-hour cycle (ignoring leap seconds).
|
||||
pub const SECONDS_PER_DAY: u64 = 86400;
|
||||
|
||||
pub const TEST_ARRAY_16: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
|
||||
|
||||
pub const TEST_ARRAY_16_1: [u8; 16] = [
|
||||
100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115,
|
||||
];
|
||||
|
||||
pub const TEST_ARRAY_32: [u8; 32] = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
|
||||
26, 27, 28, 29, 30, 31,
|
||||
];
|
||||
|
||||
pub const TEST_ARRAY_32_1: [u8; 32] = [
|
||||
100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
|
||||
119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131,
|
||||
];
|
||||
|
||||
pub const TEST_ARRAY_32_2: [u8; 32] = [
|
||||
200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218,
|
||||
219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231,
|
||||
];
|
||||
|
||||
pub const TEST_ARRAY_32_3: [u8; 32] = [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
|
||||
27, 28, 29, 30, 31, 32,
|
||||
];
|
||||
|
||||
pub const TEST_ARRAY_32_4: [u8; 32] = [
|
||||
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
|
||||
28, 29, 30, 31, 32, 33,
|
||||
];
|
||||
|
||||
pub const TEST_ARRAY_32_5: [u8; 32] = [
|
||||
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
|
||||
28, 29, 30, 31, 32, 33, 34,
|
||||
];
|
||||
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// Copyright 2023 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use bincode::Options;
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ZkGroupDeserializationFailure;
|
||||
|
||||
fn zkgroup_bincode_options() -> impl bincode::Options {
|
||||
bincode::DefaultOptions::new()
|
||||
.with_fixint_encoding()
|
||||
.with_little_endian()
|
||||
.reject_trailing_bytes()
|
||||
}
|
||||
|
||||
/// Deserializes a type using the standard zkgroup encoding (based on bincode).
|
||||
///
|
||||
/// The type must support [`PartialDefault`] to save on code size.
|
||||
pub fn deserialize<'a, T: Deserialize<'a> + PartialDefault>(
|
||||
bytes: &'a [u8],
|
||||
) -> Result<T, ZkGroupDeserializationFailure> {
|
||||
let mut result = T::partial_default();
|
||||
// Use the same encoding options as plain bincode::deserialize, which we used historically,
|
||||
// but also reject trailing bytes.
|
||||
// See https://docs.rs/bincode/1.3.3/bincode/config/index.html#options-struct-vs-bincode-functions.
|
||||
T::deserialize_in_place(
|
||||
&mut bincode::Deserializer::from_slice(bytes, zkgroup_bincode_options()),
|
||||
&mut result,
|
||||
)
|
||||
.map_err(|_| ZkGroupDeserializationFailure::new::<T>())?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Serializes a type using the standard zkgroup encoding (based on bincode).
|
||||
pub fn serialize<T: Serialize>(value: &T) -> Vec<u8> {
|
||||
zkgroup_bincode_options()
|
||||
.serialize(value)
|
||||
.expect("cannot fail")
|
||||
}
|
||||
|
||||
/// Constant version number `C` as a type.
|
||||
///
|
||||
/// Zero-sized type that converts to and from for the value `C` via `Into`,
|
||||
/// `TryFrom`, [`Serialize`], and [`Deserialize`]. Used for providing a version
|
||||
/// tag at the beginning of serialized structs.
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct VersionByte<const C: u8>;
|
||||
|
||||
impl<const C: u8> From<VersionByte<C>> for u8 {
|
||||
fn from(VersionByte: VersionByte<C>) -> Self {
|
||||
C
|
||||
}
|
||||
}
|
||||
|
||||
/// version byte was {found}, not {EXPECTED:?}
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, displaydoc::Display)]
|
||||
pub struct VersionMismatchError<const EXPECTED: u8> {
|
||||
found: u8,
|
||||
}
|
||||
|
||||
impl<const C: u8> TryFrom<u8> for VersionByte<C> {
|
||||
type Error = VersionMismatchError<C>;
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
(value == C)
|
||||
.then_some(VersionByte::<C>)
|
||||
.ok_or(VersionMismatchError::<C> { found: value })
|
||||
}
|
||||
}
|
||||
|
||||
impl<const C: u8> Serialize for VersionByte<C> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
u8::serialize(&C, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const C: u8> Deserialize<'de> for VersionByte<C> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v = u8::deserialize(deserializer)?;
|
||||
v.try_into().map_err(|_| {
|
||||
<D::Error as serde::de::Error>::invalid_value(
|
||||
serde::de::Unexpected::Unsigned(v.into()),
|
||||
&format!("version `{C}`").as_str(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Value that always serializes to and from `0u8`.
|
||||
pub type ReservedByte = VersionByte<0>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::fmt::Debug;
|
||||
|
||||
use test_case::test_case;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, PartialDefault)]
|
||||
struct WithLeadingByte<T> {
|
||||
leading: T,
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl<T: Default> WithLeadingByte<T> {
|
||||
fn test_value() -> Self {
|
||||
Self {
|
||||
leading: T::default(),
|
||||
string: "a string".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type WithReservedByte = WithLeadingByte<ReservedByte>;
|
||||
type WithVersionByte = WithLeadingByte<VersionByte<42>>;
|
||||
|
||||
#[test_case(WithReservedByte::test_value(), 0)]
|
||||
#[test_case(WithVersionByte::test_value(), 42)]
|
||||
fn round_trip<T: Serialize + for<'a> Deserialize<'a> + PartialEq + PartialDefault + Debug>(
|
||||
test_value: T,
|
||||
expected_first_byte: u8,
|
||||
) {
|
||||
let serialized = crate::serialize(&test_value);
|
||||
|
||||
assert_eq!(serialized[0], expected_first_byte);
|
||||
let deserialized: T = crate::deserialize(&serialized).expect("can deserialize");
|
||||
|
||||
assert_eq!(deserialized, test_value);
|
||||
}
|
||||
|
||||
#[test_case(WithReservedByte::test_value())]
|
||||
#[test_case(WithVersionByte::test_value())]
|
||||
fn version_byte_wrong<
|
||||
T: Serialize + for<'a> Deserialize<'a> + PartialEq + PartialDefault + Debug,
|
||||
>(
|
||||
test_value: T,
|
||||
) {
|
||||
let mut serialized = crate::serialize(&test_value);
|
||||
// perturb the first byte.
|
||||
serialized[0] += 1;
|
||||
crate::deserialize::<T>(&serialized).expect_err("invalid version");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_byte_error_message() {
|
||||
let mut bincode_serialized =
|
||||
bincode::serialize(&WithVersionByte::test_value()).expect("should serialize");
|
||||
bincode_serialized[0] = 41;
|
||||
|
||||
let error_message =
|
||||
bincode::deserialize::<WithVersionByte>(&bincode_serialized).expect_err("should fail");
|
||||
assert_eq!(
|
||||
error_message.to_string(),
|
||||
"invalid value: integer `41`, expected version `42`"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Copyright 2020 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use curve25519_dalek_signal::ristretto::RistrettoPoint;
|
||||
use curve25519_dalek_signal::scalar::Scalar;
|
||||
use poksho::ShoApi;
|
||||
use poksho::shoapi::ShoApiExt as _;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Sho {
|
||||
internal_sho: poksho::ShoHmacSha256,
|
||||
}
|
||||
|
||||
impl Sho {
|
||||
/// Creates a Sho and immediately absorbs `data` and ratchets.
|
||||
///
|
||||
/// This is exactly equivalent to calling [`Sho::new_seed`] followed by
|
||||
/// [`Sho::absorb_and_ratchet`]. Meant for when you're not doing any other absorbing.
|
||||
pub fn new(label: &[u8], data: &[u8]) -> Self {
|
||||
let mut sho = Self::new_seed(label);
|
||||
sho.absorb_and_ratchet(data);
|
||||
sho
|
||||
}
|
||||
|
||||
pub fn new_seed(label: &[u8]) -> Self {
|
||||
let sho = poksho::ShoHmacSha256::new(label);
|
||||
Sho { internal_sho: sho }
|
||||
}
|
||||
|
||||
pub fn absorb_and_ratchet(&mut self, data: &[u8]) {
|
||||
self.internal_sho.absorb_and_ratchet(data)
|
||||
}
|
||||
|
||||
pub fn squeeze(&mut self, outlen: usize) -> Vec<u8> {
|
||||
self.internal_sho.squeeze_and_ratchet(outlen)
|
||||
}
|
||||
|
||||
pub fn squeeze_as_array<const N: usize>(&mut self) -> [u8; N] {
|
||||
self.internal_sho.squeeze_and_ratchet_as_array()
|
||||
}
|
||||
|
||||
pub fn get_point(&mut self) -> RistrettoPoint {
|
||||
RistrettoPoint::from_uniform_bytes(&self.internal_sho.squeeze_and_ratchet_as_array())
|
||||
}
|
||||
|
||||
pub fn get_point_single_elligator(&mut self) -> RistrettoPoint {
|
||||
RistrettoPoint::from_uniform_bytes_single_elligator(
|
||||
&self.internal_sho.squeeze_and_ratchet_as_array(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_scalar(&mut self) -> Scalar {
|
||||
Scalar::from_bytes_mod_order_wide(&self.internal_sho.squeeze_and_ratchet_as_array())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<poksho::ShoHmacSha256> for Sho {
|
||||
fn as_mut(&mut self) -> &mut poksho::ShoHmacSha256 {
|
||||
&mut self.internal_sho
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// Copyright 2020 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use curve25519_dalek_signal::scalar::Scalar;
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zkcredential::attributes::PublicAttribute;
|
||||
|
||||
use crate::common::constants::*;
|
||||
|
||||
pub type AesKeyBytes = [u8; AES_KEY_LEN];
|
||||
pub type GroupMasterKeyBytes = [u8; GROUP_MASTER_KEY_LEN];
|
||||
pub type UidBytes = [u8; UUID_LEN];
|
||||
pub type ProfileKeyBytes = [u8; PROFILE_KEY_LEN];
|
||||
pub type RandomnessBytes = [u8; RANDOMNESS_LEN];
|
||||
pub type SignatureBytes = [u8; SIGNATURE_LEN];
|
||||
pub type NotarySignatureBytes = [u8; SIGNATURE_LEN];
|
||||
pub type GroupIdentifierBytes = [u8; GROUP_IDENTIFIER_LEN];
|
||||
pub type ProfileKeyVersionBytes = [u8; PROFILE_KEY_VERSION_LEN];
|
||||
// TODO: Use ascii::Char when stable (the "encoding" is hex)
|
||||
pub type ProfileKeyVersionEncodedBytes = [u8; PROFILE_KEY_VERSION_ENCODED_LEN];
|
||||
|
||||
// A random UUID that the receipt issuing server will blind authorize to redeem a given receipt
|
||||
// level within a certain time frame.
|
||||
pub type ReceiptSerialBytes = [u8; RECEIPT_SERIAL_LEN];
|
||||
|
||||
/// Timestamp measured in seconds past the epoch.
|
||||
///
|
||||
/// Clients should only accept round multiples of 86400 to avoid fingerprinting by the server.
|
||||
/// For expirations, the timestamp should be within a couple of days into the future;
|
||||
/// for redemption times, it should be within a day of the current date.
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize, PartialDefault,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
#[repr(transparent)]
|
||||
pub struct Timestamp(u64);
|
||||
|
||||
impl Timestamp {
|
||||
#[inline]
|
||||
pub const fn from_epoch_seconds(seconds: u64) -> Self {
|
||||
Self(seconds)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn epoch_seconds(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn add_seconds(&self, seconds: u64) -> Self {
|
||||
Self(self.0 + seconds)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn sub_seconds(&self, seconds: u64) -> Self {
|
||||
Self(self.0 - seconds)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn checked_add_seconds(&self, seconds: u64) -> Option<Self> {
|
||||
self.0.checked_add(seconds).map(Self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn checked_sub_seconds(&self, seconds: u64) -> Option<Self> {
|
||||
self.0.checked_sub(seconds).map(Self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn is_day_aligned(&self) -> bool {
|
||||
self.0 % SECONDS_PER_DAY == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_be_bytes(self) -> [u8; 8] {
|
||||
self.0.to_be_bytes()
|
||||
}
|
||||
|
||||
/// Number of seconds that `self` is after `before`.
|
||||
///
|
||||
/// Returns `0` if `self` is equal to or earlier than `before`.
|
||||
pub(crate) fn saturating_seconds_since(&self, before: Timestamp) -> u64 {
|
||||
self.0.saturating_sub(before.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Timestamp> for std::time::SystemTime {
|
||||
fn from(Timestamp(seconds): Timestamp) -> Self {
|
||||
std::time::UNIX_EPOCH + std::time::Duration::from_secs(seconds)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::time::SystemTime> for Timestamp {
|
||||
fn from(timestamp: std::time::SystemTime) -> Self {
|
||||
Self::from_epoch_seconds(
|
||||
timestamp
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl rand::distr::Distribution<Timestamp> for rand::distr::StandardUniform {
|
||||
fn sample<R: rand::prelude::Rng + ?Sized>(&self, rng: &mut R) -> Timestamp {
|
||||
Timestamp(Self::sample(self, rng))
|
||||
}
|
||||
}
|
||||
|
||||
impl PublicAttribute for Timestamp {
|
||||
fn hash_into(&self, sho: &mut dyn poksho::ShoApi) {
|
||||
self.0.hash_into(sho)
|
||||
}
|
||||
}
|
||||
|
||||
// Used to tell the server handling receipt redemptions what to redeem the receipt for. Clients
|
||||
// should validate this matches their expectations.
|
||||
pub type ReceiptLevel = u64;
|
||||
|
||||
pub fn encode_redemption_time(redemption_time: u32) -> Scalar {
|
||||
let mut scalar_bytes: [u8; 32] = Default::default();
|
||||
scalar_bytes[0..4].copy_from_slice(&redemption_time.to_be_bytes());
|
||||
Scalar::from_bytes_mod_order(scalar_bytes)
|
||||
}
|
||||
|
||||
pub fn encode_receipt_serial_bytes(receipt_serial_bytes: ReceiptSerialBytes) -> Scalar {
|
||||
let mut scalar_bytes: [u8; 32] = Default::default();
|
||||
scalar_bytes[0..16].copy_from_slice(&receipt_serial_bytes[..]);
|
||||
Scalar::from_bytes_mod_order(scalar_bytes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_encode_scalar() {
|
||||
let s_bytes = [0xFF; 32];
|
||||
match bincode::deserialize::<Scalar>(&s_bytes) {
|
||||
Err(_) => (),
|
||||
Ok(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user