Add project files.
This commit is contained in:
@@ -0,0 +1,509 @@
|
||||
//
|
||||
// Copyright 2020-2022 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use const_str::hex;
|
||||
use curve25519_dalek_signal::constants::RISTRETTO_BASEPOINT_POINT;
|
||||
use curve25519_dalek_signal::ristretto::RistrettoPoint;
|
||||
use curve25519_dalek_signal::scalar::Scalar;
|
||||
use derive_where::derive_where;
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::common::array_utils::{ArrayLike, OneBased};
|
||||
use crate::common::sho::*;
|
||||
use crate::common::simple_types::*;
|
||||
use crate::crypto::receipt_struct::ReceiptStruct;
|
||||
use crate::crypto::timestamp_struct::TimestampStruct;
|
||||
use crate::crypto::{
|
||||
profile_key_credential_request, receipt_credential_request, receipt_struct, uid_struct,
|
||||
};
|
||||
use crate::{
|
||||
NUM_AUTH_CRED_ATTRIBUTES, NUM_PROFILE_KEY_CRED_ATTRIBUTES, NUM_RECEIPT_CRED_ATTRIBUTES,
|
||||
};
|
||||
|
||||
static SYSTEM_PARAMS: LazyLock<SystemParams> = LazyLock::new(|| {
|
||||
crate::deserialize(SystemParams::SYSTEM_HARDCODED).expect("valid hardcoded params")
|
||||
});
|
||||
|
||||
const NUM_SUPPORTED_ATTRS: usize = 6;
|
||||
#[derive(Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SystemParams {
|
||||
pub(crate) G_w: RistrettoPoint,
|
||||
pub(crate) G_wprime: RistrettoPoint,
|
||||
pub(crate) G_x0: RistrettoPoint,
|
||||
pub(crate) G_x1: RistrettoPoint,
|
||||
pub(crate) G_y: OneBased<[RistrettoPoint; NUM_SUPPORTED_ATTRS]>,
|
||||
pub(crate) G_m1: RistrettoPoint,
|
||||
pub(crate) G_m2: RistrettoPoint,
|
||||
pub(crate) G_m3: RistrettoPoint,
|
||||
pub(crate) G_m4: RistrettoPoint,
|
||||
pub(crate) G_m5: RistrettoPoint,
|
||||
pub(crate) G_V: RistrettoPoint,
|
||||
pub(crate) G_z: RistrettoPoint,
|
||||
}
|
||||
|
||||
/// Used to specialize a [`KeyPair<S>`] to support a certain number of attributes.
|
||||
///
|
||||
/// The only required member is `Storage`, which should be a fixed-size array of [`Scalar`], one for
|
||||
/// each attribute. However, for backwards compatibility some systems support fewer attributes than
|
||||
/// are actually stored, and in this case the `NUM_ATTRS` member can be set to a custom value. Note
|
||||
/// that `NUM_ATTRS` must always be less than or equal to the number of elements in `Storage`.
|
||||
pub trait AttrScalars {
|
||||
/// The storage (should be a fixed-size array of Scalar).
|
||||
type Storage: ArrayLike<Scalar> + Copy + Eq + Serialize + for<'a> Deserialize<'a>;
|
||||
|
||||
/// The number of attributes supported in this system.
|
||||
///
|
||||
/// Defaults to the full set stored in `Self::Storage`.
|
||||
const NUM_ATTRS: usize = Self::Storage::LEN;
|
||||
}
|
||||
|
||||
impl AttrScalars for AuthCredential {
|
||||
// Store four scalars for backwards compatibility.
|
||||
type Storage = [Scalar; 4];
|
||||
const NUM_ATTRS: usize = NUM_AUTH_CRED_ATTRIBUTES;
|
||||
}
|
||||
impl AttrScalars for AuthCredentialWithPni {
|
||||
type Storage = [Scalar; 5];
|
||||
}
|
||||
impl AttrScalars for ProfileKeyCredential {
|
||||
// Store four scalars for backwards compatibility.
|
||||
type Storage = [Scalar; 4];
|
||||
const NUM_ATTRS: usize = NUM_PROFILE_KEY_CRED_ATTRIBUTES;
|
||||
}
|
||||
impl AttrScalars for ExpiringProfileKeyCredential {
|
||||
type Storage = [Scalar; 5];
|
||||
}
|
||||
impl AttrScalars for ReceiptCredential {
|
||||
// Store four scalars for backwards compatibility.
|
||||
type Storage = [Scalar; 4];
|
||||
const NUM_ATTRS: usize = NUM_RECEIPT_CRED_ATTRIBUTES;
|
||||
}
|
||||
impl AttrScalars for PniCredential {
|
||||
type Storage = [Scalar; 6];
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialDefault)]
|
||||
#[partial_default(bound = "S::Storage: Default")]
|
||||
#[derive_where(Clone, Copy, PartialEq, Eq; S: AttrScalars)]
|
||||
pub struct KeyPair<S: AttrScalars> {
|
||||
// private
|
||||
pub(crate) w: Scalar,
|
||||
pub(crate) wprime: Scalar,
|
||||
pub(crate) W: RistrettoPoint,
|
||||
pub(crate) x0: Scalar,
|
||||
pub(crate) x1: Scalar,
|
||||
pub(crate) y: OneBased<S::Storage>,
|
||||
|
||||
// public
|
||||
pub(crate) C_W: RistrettoPoint,
|
||||
pub(crate) I: RistrettoPoint,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct PublicKey {
|
||||
pub(crate) C_W: RistrettoPoint,
|
||||
pub(crate) I: RistrettoPoint,
|
||||
}
|
||||
|
||||
/// Unused, kept only because ServerSecretParams contains a `KeyPair<AuthCredential>`.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
|
||||
pub(crate) struct AuthCredential {
|
||||
pub(crate) t: Scalar,
|
||||
pub(crate) U: RistrettoPoint,
|
||||
pub(crate) V: RistrettoPoint,
|
||||
}
|
||||
|
||||
/// Unused, kept only because ServerSecretParams contains a `KeyPair<AuthCredentialWithPni>`.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
|
||||
pub(crate) struct AuthCredentialWithPni {
|
||||
pub(crate) t: Scalar,
|
||||
pub(crate) U: RistrettoPoint,
|
||||
pub(crate) V: RistrettoPoint,
|
||||
}
|
||||
|
||||
/// Unused, kept only because ServerSecretParams contains a `KeyPair<ProfileKeyCredential>`.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProfileKeyCredential {
|
||||
pub(crate) t: Scalar,
|
||||
pub(crate) U: RistrettoPoint,
|
||||
pub(crate) V: RistrettoPoint,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct ExpiringProfileKeyCredential {
|
||||
pub(crate) t: Scalar,
|
||||
pub(crate) U: RistrettoPoint,
|
||||
pub(crate) V: RistrettoPoint,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BlindedExpiringProfileKeyCredentialWithSecretNonce {
|
||||
pub(crate) rprime: Scalar,
|
||||
pub(crate) t: Scalar,
|
||||
pub(crate) U: RistrettoPoint,
|
||||
pub(crate) S1: RistrettoPoint,
|
||||
pub(crate) S2: RistrettoPoint,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct BlindedExpiringProfileKeyCredential {
|
||||
pub(crate) t: Scalar,
|
||||
pub(crate) U: RistrettoPoint,
|
||||
pub(crate) S1: RistrettoPoint,
|
||||
pub(crate) S2: RistrettoPoint,
|
||||
}
|
||||
|
||||
/// Unused, kept only because ServerSecretParams contains a `KeyPair<PniCredential>`.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PniCredential {
|
||||
pub(crate) t: Scalar,
|
||||
pub(crate) U: RistrettoPoint,
|
||||
pub(crate) V: RistrettoPoint,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct ReceiptCredential {
|
||||
pub(crate) t: Scalar,
|
||||
pub(crate) U: RistrettoPoint,
|
||||
pub(crate) V: RistrettoPoint,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BlindedReceiptCredentialWithSecretNonce {
|
||||
pub(crate) rprime: Scalar,
|
||||
pub(crate) t: Scalar,
|
||||
pub(crate) U: RistrettoPoint,
|
||||
pub(crate) S1: RistrettoPoint,
|
||||
pub(crate) S2: RistrettoPoint,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct BlindedReceiptCredential {
|
||||
pub(crate) t: Scalar,
|
||||
pub(crate) U: RistrettoPoint,
|
||||
pub(crate) S1: RistrettoPoint,
|
||||
pub(crate) S2: RistrettoPoint,
|
||||
}
|
||||
|
||||
pub(crate) fn convert_to_points_receipt_struct(
|
||||
receipt: receipt_struct::ReceiptStruct,
|
||||
) -> Vec<RistrettoPoint> {
|
||||
let system = SystemParams::get_hardcoded();
|
||||
let m1 = receipt.calc_m1();
|
||||
let receipt_serial_scalar = encode_receipt_serial_bytes(receipt.receipt_serial_bytes);
|
||||
vec![m1 * system.G_m1, receipt_serial_scalar * system.G_m2]
|
||||
}
|
||||
|
||||
pub(crate) fn convert_to_point_M2_receipt_serial_bytes(
|
||||
receipt_serial_bytes: ReceiptSerialBytes,
|
||||
) -> RistrettoPoint {
|
||||
let system = SystemParams::get_hardcoded();
|
||||
let receipt_serial_scalar = encode_receipt_serial_bytes(receipt_serial_bytes);
|
||||
receipt_serial_scalar * system.G_m2
|
||||
}
|
||||
|
||||
impl SystemParams {
|
||||
#[cfg(test)]
|
||||
fn generate() -> Self {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20200424_Constant_Credentials_SystemParams_Generate",
|
||||
b"",
|
||||
);
|
||||
let G_w = sho.get_point();
|
||||
let G_wprime = sho.get_point();
|
||||
|
||||
let G_x0 = sho.get_point();
|
||||
let G_x1 = sho.get_point();
|
||||
|
||||
let G_y1 = sho.get_point();
|
||||
let G_y2 = sho.get_point();
|
||||
let G_y3 = sho.get_point();
|
||||
let G_y4 = sho.get_point();
|
||||
|
||||
let G_m1 = sho.get_point();
|
||||
let G_m2 = sho.get_point();
|
||||
let G_m3 = sho.get_point();
|
||||
let G_m4 = sho.get_point();
|
||||
|
||||
let G_V = sho.get_point();
|
||||
let G_z = sho.get_point();
|
||||
|
||||
// We don't ever want to use existing generator points in new ways,
|
||||
// so new points have to be added at the end.
|
||||
let G_y5 = sho.get_point();
|
||||
let G_y6 = sho.get_point();
|
||||
|
||||
let G_m5 = sho.get_point();
|
||||
|
||||
SystemParams {
|
||||
G_w,
|
||||
G_wprime,
|
||||
G_x0,
|
||||
G_x1,
|
||||
G_y: OneBased([G_y1, G_y2, G_y3, G_y4, G_y5, G_y6]),
|
||||
G_m1,
|
||||
G_m2,
|
||||
G_m3,
|
||||
G_m4,
|
||||
G_m5,
|
||||
G_V,
|
||||
G_z,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_hardcoded() -> SystemParams {
|
||||
*SYSTEM_PARAMS
|
||||
}
|
||||
|
||||
const SYSTEM_HARDCODED: &'static [u8] = &hex!(
|
||||
"9ae7c8e5ed779b114ae7708aa2f794670adda324987b659913122c35505b105e6ca31025d2d76be7fd34944f98f7fa0e37babb2c8b98bbbdbd3dd1bf130cca2c8a9a3bdfaaa2b6b322d46b93eca7b0d51c86a3c839e11466358258a6c10c577fc2bffd34cd99164c9a6cd29fab55d91ff9269322ec3458603cc96a0d47f704058288f62ee0acedb8aa23242121d98965a9bb2991250c11758095ece0fd2b33285286fe1fcb056103b6081744b975f550d08521568dd3d8618f25c140375a0f4024c3aa23bdfffb27fbd982208d3ecd1fd3bcb7ac0c3a14b109804fc748d7fa456cffb4934f980b6e09a248a60f44a6150ae6c13d7e3c06261d7e4eed37f39f60b04dd9d607fd357012274d3c63dbb38e7378599c9e97dfbb28842694891d5f0ddc729919b798b4131503408cc57a9c532f4427632c88f54cea53861a5bc44c61cc6037dc31c2e8d4474fb519587a448693182ad9d6d86b535957858f547b9340127da75f8074caee944ac36c0ac662d38c9b3ccce03a093fcd9644047398b86b6e83372ff14fb8bb0dea65531252ac70d58a4a0810d682a0e709c9227b30ef6c8e17c5915d527221bb00da8175cd6489aa8aa492a500f9abee5690b9dfca8855dc0bd02a7f277add240f639ac16801e81574afb4683edff63b9a01e93dbd867a04b616c706c80c756c11a3016bbfb60977f4648b5f2395a4b428b7211940813e3afde2b87aa9c2c37bf716e2578f95656df12c2fb6f5d0631f6f71e2c3193f6d"
|
||||
);
|
||||
}
|
||||
|
||||
impl<S: AttrScalars> KeyPair<S> {
|
||||
pub fn generate(sho: &mut Sho) -> Self {
|
||||
assert!(S::NUM_ATTRS >= 1, "at least one attribute required");
|
||||
assert!(
|
||||
S::NUM_ATTRS <= NUM_SUPPORTED_ATTRS,
|
||||
"more than {NUM_SUPPORTED_ATTRS} attributes not supported"
|
||||
);
|
||||
assert!(
|
||||
S::NUM_ATTRS <= S::Storage::LEN,
|
||||
"more attributes than storage",
|
||||
);
|
||||
|
||||
let system = SystemParams::get_hardcoded();
|
||||
let w = sho.get_scalar();
|
||||
let W = w * system.G_w;
|
||||
let wprime = sho.get_scalar();
|
||||
let x0 = sho.get_scalar();
|
||||
let x1 = sho.get_scalar();
|
||||
|
||||
let y = OneBased::<S::Storage>::create(|| sho.get_scalar());
|
||||
|
||||
let C_W = (w * system.G_w) + (wprime * system.G_wprime);
|
||||
let mut I = system.G_V - (x0 * system.G_x0) - (x1 * system.G_x1);
|
||||
|
||||
for (yn, G_yn) in y.iter().zip(system.G_y.iter()).take(S::NUM_ATTRS) {
|
||||
I -= yn * G_yn;
|
||||
}
|
||||
|
||||
KeyPair {
|
||||
w,
|
||||
wprime,
|
||||
W,
|
||||
x0,
|
||||
x1,
|
||||
y,
|
||||
C_W,
|
||||
I,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_public_key(&self) -> PublicKey {
|
||||
PublicKey {
|
||||
C_W: self.C_W,
|
||||
I: self.I,
|
||||
}
|
||||
}
|
||||
|
||||
fn credential_core(
|
||||
&self,
|
||||
M: &[RistrettoPoint],
|
||||
sho: &mut Sho,
|
||||
) -> (Scalar, RistrettoPoint, RistrettoPoint) {
|
||||
assert!(
|
||||
M.len() <= S::NUM_ATTRS,
|
||||
"more than {} attributes not supported",
|
||||
S::NUM_ATTRS
|
||||
);
|
||||
let t = sho.get_scalar();
|
||||
let U = sho.get_point();
|
||||
|
||||
let mut V = self.W + (self.x0 + self.x1 * t) * U;
|
||||
for (yn, Mn) in self.y.iter().zip(M) {
|
||||
V += yn * Mn;
|
||||
}
|
||||
(t, U, V)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPair<ExpiringProfileKeyCredential> {
|
||||
pub fn create_blinded_expiring_profile_key_credential(
|
||||
&self,
|
||||
uid: uid_struct::UidStruct,
|
||||
public_key: profile_key_credential_request::PublicKey,
|
||||
ciphertext: profile_key_credential_request::Ciphertext,
|
||||
credential_expiration_time: Timestamp,
|
||||
sho: &mut Sho,
|
||||
) -> BlindedExpiringProfileKeyCredentialWithSecretNonce {
|
||||
let M = [uid.M1, uid.M2];
|
||||
|
||||
let (t, U, Vprime) = self.credential_core(&M, sho);
|
||||
|
||||
let params = SystemParams::get_hardcoded();
|
||||
let m5 = TimestampStruct::calc_m_from(credential_expiration_time);
|
||||
let M5 = m5 * params.G_m5;
|
||||
let Vprime_with_expiration = Vprime + (self.y[5] * M5);
|
||||
|
||||
let rprime = sho.get_scalar();
|
||||
let R1 = rprime * RISTRETTO_BASEPOINT_POINT;
|
||||
let R2 = rprime * public_key.Y + Vprime_with_expiration;
|
||||
let S1 = R1 + (self.y[3] * ciphertext.D1) + (self.y[4] * ciphertext.E1);
|
||||
let S2 = R2 + (self.y[3] * ciphertext.D2) + (self.y[4] * ciphertext.E2);
|
||||
BlindedExpiringProfileKeyCredentialWithSecretNonce {
|
||||
rprime,
|
||||
t,
|
||||
U,
|
||||
S1,
|
||||
S2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPair<ReceiptCredential> {
|
||||
pub fn create_blinded_receipt_credential(
|
||||
&self,
|
||||
public_key: receipt_credential_request::PublicKey,
|
||||
ciphertext: receipt_credential_request::Ciphertext,
|
||||
receipt_expiration_time: Timestamp,
|
||||
receipt_level: ReceiptLevel,
|
||||
sho: &mut Sho,
|
||||
) -> BlindedReceiptCredentialWithSecretNonce {
|
||||
let params = SystemParams::get_hardcoded();
|
||||
let m1 = ReceiptStruct::calc_m1_from(receipt_expiration_time, receipt_level);
|
||||
let M = [m1 * params.G_m1];
|
||||
|
||||
let (t, U, Vprime) = self.credential_core(&M, sho);
|
||||
let rprime = sho.get_scalar();
|
||||
let R1 = rprime * RISTRETTO_BASEPOINT_POINT;
|
||||
let R2 = rprime * public_key.Y + Vprime;
|
||||
let S1 = self.y[2] * ciphertext.D1 + R1;
|
||||
let S2 = self.y[2] * ciphertext.D2 + R2;
|
||||
BlindedReceiptCredentialWithSecretNonce {
|
||||
rprime,
|
||||
t,
|
||||
U,
|
||||
S1,
|
||||
S2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlindedExpiringProfileKeyCredentialWithSecretNonce {
|
||||
pub fn get_blinded_expiring_profile_key_credential(
|
||||
&self,
|
||||
) -> BlindedExpiringProfileKeyCredential {
|
||||
BlindedExpiringProfileKeyCredential {
|
||||
t: self.t,
|
||||
U: self.U,
|
||||
S1: self.S1,
|
||||
S2: self.S2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlindedReceiptCredentialWithSecretNonce {
|
||||
pub fn get_blinded_receipt_credential(&self) -> BlindedReceiptCredential {
|
||||
BlindedReceiptCredential {
|
||||
t: self.t,
|
||||
U: self.U,
|
||||
S1: self.S1,
|
||||
S2: self.S2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::common::constants::*;
|
||||
use crate::crypto::proofs;
|
||||
|
||||
#[test]
|
||||
fn test_system() {
|
||||
let params = SystemParams::generate();
|
||||
println!("PARAMS = {:#x?}", bincode::serialize(¶ms));
|
||||
assert!(SystemParams::generate() == SystemParams::get_hardcoded());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mac() {
|
||||
// It doesn't really matter *which* credential we test here, we just want to generally know
|
||||
// we've set things up correctly. (Also, the credentials hardcoded here in zkgroup may
|
||||
// eventually all be superseded by implementations using zkcredential, at which point this
|
||||
// test can be deleted.)
|
||||
let mut sho = Sho::new(b"Test_Credentials", b"");
|
||||
let keypair = KeyPair::<ExpiringProfileKeyCredential>::generate(&mut sho);
|
||||
|
||||
let uid_bytes = TEST_ARRAY_16;
|
||||
let redemption_time = Timestamp::from_epoch_seconds(37 * SECONDS_PER_DAY);
|
||||
let aci = libsignal_core::Aci::from_uuid_bytes(uid_bytes);
|
||||
let aci_struct = uid_struct::UidStruct::from_service_id(aci.into());
|
||||
let profile_key_struct = crate::crypto::profile_key_struct::ProfileKeyStruct::new(
|
||||
[1; PROFILE_KEY_LEN],
|
||||
uid_bytes,
|
||||
);
|
||||
let request_key_pair =
|
||||
crate::crypto::profile_key_credential_request::KeyPair::generate(&mut sho);
|
||||
let ciphertext = request_key_pair
|
||||
.encrypt(profile_key_struct, &mut sho)
|
||||
.get_ciphertext();
|
||||
let credential = keypair.create_blinded_expiring_profile_key_credential(
|
||||
aci_struct,
|
||||
request_key_pair.get_public_key(),
|
||||
ciphertext,
|
||||
redemption_time,
|
||||
&mut sho,
|
||||
);
|
||||
let proof = proofs::ExpiringProfileKeyCredentialIssuanceProof::new(
|
||||
keypair,
|
||||
request_key_pair.get_public_key(),
|
||||
ciphertext,
|
||||
credential,
|
||||
aci_struct,
|
||||
redemption_time,
|
||||
&mut sho,
|
||||
);
|
||||
|
||||
let public_key = keypair.get_public_key();
|
||||
proof
|
||||
.verify(
|
||||
public_key,
|
||||
request_key_pair.get_public_key(),
|
||||
uid_bytes,
|
||||
ciphertext,
|
||||
credential.get_blinded_expiring_profile_key_credential(),
|
||||
redemption_time,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let keypair_bytes = bincode::serialize(&keypair).unwrap();
|
||||
let keypair2 = bincode::deserialize(&keypair_bytes).unwrap();
|
||||
assert!(keypair == keypair2);
|
||||
|
||||
let public_key_bytes = bincode::serialize(&public_key).unwrap();
|
||||
let public_key2 = bincode::deserialize(&public_key_bytes).unwrap();
|
||||
assert!(public_key == public_key2);
|
||||
|
||||
let mac_bytes = bincode::serialize(&credential).unwrap();
|
||||
|
||||
println!("mac_bytes = {}", hex::encode(&mac_bytes));
|
||||
assert_eq!(
|
||||
mac_bytes,
|
||||
hex!(
|
||||
"ef47110715831160100f14d1936f4349c45b80ccaacd4edd9f949375d2d90a090888d81f8b0ed313
|
||||
808b5ff7ec1957ed4e8b3d9c195b3a5abdbdd3d972c29809100a7f8dc2354be7a1d44452cbadd87e
|
||||
4851ae05ebeb2586b856d35af765883a94473ad855df8583be2930e4e1d5756175a9091f2be1d8d0
|
||||
21280446a7611841d6b4f2eb165267a9d1d7a800f19c2077a4ef7df721b160fe200181be3c455f1c"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
//
|
||||
// Copyright 2020 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use curve25519_dalek_signal::ristretto::RistrettoPoint;
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
|
||||
use zkcredential::attributes::Attribute;
|
||||
|
||||
use crate::common::errors::*;
|
||||
use crate::common::sho::*;
|
||||
use crate::common::simple_types::*;
|
||||
use crate::crypto::profile_key_struct;
|
||||
|
||||
static SYSTEM_PARAMS: LazyLock<SystemParams> = LazyLock::new(|| {
|
||||
crate::deserialize(&SystemParams::SYSTEM_HARDCODED).expect("valid hardcoded params")
|
||||
});
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct SystemParams {
|
||||
pub(crate) G_b1: RistrettoPoint,
|
||||
pub(crate) G_b2: RistrettoPoint,
|
||||
}
|
||||
|
||||
pub type KeyPair = zkcredential::attributes::KeyPair<ProfileKeyEncryptionDomain>;
|
||||
pub type PublicKey = zkcredential::attributes::PublicKey<ProfileKeyEncryptionDomain>;
|
||||
pub type Ciphertext = zkcredential::attributes::Ciphertext<ProfileKeyEncryptionDomain>;
|
||||
|
||||
impl SystemParams {
|
||||
pub fn generate() -> Self {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20200424_Constant_ProfileKeyEncryption_SystemParams_Generate",
|
||||
b"",
|
||||
);
|
||||
let G_b1 = sho.get_point();
|
||||
let G_b2 = sho.get_point();
|
||||
SystemParams { G_b1, G_b2 }
|
||||
}
|
||||
|
||||
pub fn get_hardcoded() -> SystemParams {
|
||||
*SYSTEM_PARAMS
|
||||
}
|
||||
|
||||
const SYSTEM_HARDCODED: [u8; 64] = [
|
||||
0xf6, 0xba, 0xa3, 0x17, 0xce, 0x18, 0x39, 0xc9, 0x3d, 0x61, 0x7e, 0xc, 0xd8, 0x37, 0xd1,
|
||||
0x9d, 0xa9, 0xc8, 0xa4, 0xc5, 0x20, 0xbf, 0x7c, 0x51, 0xb1, 0xe6, 0xc2, 0xcb, 0x2a, 0x4,
|
||||
0x9c, 0x61, 0x2e, 0x1, 0x75, 0x89, 0x4c, 0x87, 0x30, 0xb2, 0x3, 0xab, 0x3b, 0xd9, 0x8e,
|
||||
0xcb, 0x2d, 0x81, 0xab, 0xac, 0xb6, 0x5f, 0x8a, 0x61, 0x24, 0xf4, 0x97, 0x71, 0xd1, 0x4a,
|
||||
0x98, 0x52, 0x12, 0xc,
|
||||
];
|
||||
}
|
||||
|
||||
pub struct ProfileKeyEncryptionDomain;
|
||||
impl zkcredential::attributes::Domain for ProfileKeyEncryptionDomain {
|
||||
type Attribute = profile_key_struct::ProfileKeyStruct;
|
||||
|
||||
const ID: &'static str = "Signal_ZKGroup_20231011_ProfileKeyEncryption";
|
||||
|
||||
fn G_a() -> [RistrettoPoint; 2] {
|
||||
let system = SystemParams::get_hardcoded();
|
||||
[system.G_b1, system.G_b2]
|
||||
}
|
||||
}
|
||||
|
||||
impl ProfileKeyEncryptionDomain {
|
||||
pub(crate) fn decrypt(
|
||||
key_pair: &KeyPair,
|
||||
ciphertext: &Ciphertext,
|
||||
uid_bytes: UidBytes,
|
||||
) -> Result<profile_key_struct::ProfileKeyStruct, ZkGroupVerificationFailure> {
|
||||
let M4 = key_pair
|
||||
.decrypt_to_second_point(ciphertext)
|
||||
.map_err(|_| ZkGroupVerificationFailure)?;
|
||||
let (mask, candidates) = M4.decode_253_bits();
|
||||
|
||||
let target_M3 = key_pair.a1.invert() * ciphertext.as_points()[0];
|
||||
let seed_sho = profile_key_struct::ProfileKeyStruct::seed_M3();
|
||||
|
||||
let mut retval: profile_key_struct::ProfileKeyStruct = PartialDefault::partial_default();
|
||||
let mut n_found = 0;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..8 {
|
||||
let is_valid_fe = Choice::from((mask >> i) & 1);
|
||||
let profile_key_bytes: ProfileKeyBytes = candidates[i];
|
||||
for j in 0..8 {
|
||||
let mut pk = profile_key_bytes;
|
||||
if ((j >> 2) & 1) == 1 {
|
||||
pk[0] |= 0x01;
|
||||
}
|
||||
if ((j >> 1) & 1) == 1 {
|
||||
pk[31] |= 0x80;
|
||||
}
|
||||
if (j & 1) == 1 {
|
||||
pk[31] |= 0x40;
|
||||
}
|
||||
let M3 =
|
||||
profile_key_struct::ProfileKeyStruct::calc_M3(seed_sho.clone(), pk, uid_bytes);
|
||||
let candidate_retval = profile_key_struct::ProfileKeyStruct { bytes: pk, M3, M4 };
|
||||
let found = M3.ct_eq(&target_M3) & is_valid_fe;
|
||||
retval.conditional_assign(&candidate_retval, found);
|
||||
n_found += found.unwrap_u8();
|
||||
}
|
||||
}
|
||||
if n_found == 1 {
|
||||
Ok(retval)
|
||||
} else {
|
||||
Err(ZkGroupVerificationFailure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::common::constants::*;
|
||||
|
||||
#[test]
|
||||
fn test_profile_key_encryption() {
|
||||
let master_key = TEST_ARRAY_32_1;
|
||||
let mut sho = Sho::new(b"Test_Profile_Key_Encryption", &master_key);
|
||||
|
||||
//let system = SystemParams::generate();
|
||||
//println!("PARAMS = {:#x?}", bincode::serialize(&system));
|
||||
assert!(SystemParams::generate() == SystemParams::get_hardcoded());
|
||||
|
||||
let key_pair = KeyPair::derive_from(sho.as_mut());
|
||||
|
||||
// Test serialize of key_pair
|
||||
let key_pair_bytes = bincode::serialize(&key_pair).unwrap();
|
||||
match bincode::deserialize::<KeyPair>(&key_pair_bytes[0..key_pair_bytes.len() - 1]) {
|
||||
Err(_) => (),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let key_pair2: KeyPair = bincode::deserialize(&key_pair_bytes).unwrap();
|
||||
assert!(key_pair == key_pair2);
|
||||
|
||||
let profile_key_bytes = TEST_ARRAY_32_1;
|
||||
let uid_bytes = TEST_ARRAY_16_1;
|
||||
let profile_key = profile_key_struct::ProfileKeyStruct::new(profile_key_bytes, uid_bytes);
|
||||
let ciphertext = key_pair.encrypt(&profile_key);
|
||||
|
||||
// Test serialize / deserialize of Ciphertext
|
||||
let ciphertext_bytes = bincode::serialize(&ciphertext).unwrap();
|
||||
assert!(ciphertext_bytes.len() == 64);
|
||||
let ciphertext2: Ciphertext = bincode::deserialize(&ciphertext_bytes).unwrap();
|
||||
assert!(ciphertext == ciphertext2);
|
||||
println!("ciphertext_bytes = {ciphertext_bytes:#x?}");
|
||||
assert!(
|
||||
ciphertext_bytes
|
||||
== vec![
|
||||
0x56, 0x18, 0xcb, 0x4c, 0x7d, 0x72, 0x1e, 0x1, 0x2b, 0x22, 0xf0, 0x77, 0xef,
|
||||
0x12, 0x64, 0xf6, 0xb1, 0x43, 0xbb, 0x59, 0x7a, 0x1d, 0x66, 0x5a, 0x70, 0xaa,
|
||||
0x84, 0x24, 0x5f, 0x24, 0x6d, 0x20, 0xba, 0xdb, 0x97, 0x47, 0x4a, 0x56, 0xf4,
|
||||
0xb5, 0x36, 0x1a, 0xec, 0xa9, 0xd1, 0x18, 0xb7, 0x0, 0x4e, 0x14, 0x9, 0x71,
|
||||
0x99, 0xa, 0xab, 0x2a, 0xf2, 0x43, 0x2d, 0x3f, 0x8f, 0x7d, 0x21, 0x3a,
|
||||
]
|
||||
);
|
||||
|
||||
let plaintext =
|
||||
ProfileKeyEncryptionDomain::decrypt(&key_pair, &ciphertext2, uid_bytes).unwrap();
|
||||
assert!(plaintext == profile_key);
|
||||
|
||||
let mut sho = Sho::new(b"Test_Repeated_ProfileKeyEnc/Dec", b"seed");
|
||||
for _ in 0..100 {
|
||||
let uid_bytes: UidBytes = sho.squeeze_as_array();
|
||||
let profile_key_bytes: ProfileKeyBytes = sho.squeeze_as_array();
|
||||
|
||||
let profile_key =
|
||||
profile_key_struct::ProfileKeyStruct::new(profile_key_bytes, uid_bytes);
|
||||
let ciphertext = key_pair.encrypt(&profile_key);
|
||||
assert!(
|
||||
ProfileKeyEncryptionDomain::decrypt(&key_pair, &ciphertext, uid_bytes).unwrap()
|
||||
== profile_key
|
||||
);
|
||||
}
|
||||
|
||||
let uid_bytes = TEST_ARRAY_16;
|
||||
let profile_key = profile_key_struct::ProfileKeyStruct::new(TEST_ARRAY_32, TEST_ARRAY_16);
|
||||
let ciphertext = key_pair.encrypt(&profile_key);
|
||||
assert!(
|
||||
ProfileKeyEncryptionDomain::decrypt(&key_pair, &ciphertext, uid_bytes).unwrap()
|
||||
== profile_key
|
||||
);
|
||||
|
||||
let uid_bytes = TEST_ARRAY_16;
|
||||
let profile_key = profile_key_struct::ProfileKeyStruct::new(TEST_ARRAY_32_2, TEST_ARRAY_16);
|
||||
let ciphertext = key_pair.encrypt(&profile_key);
|
||||
assert!(
|
||||
ProfileKeyEncryptionDomain::decrypt(&key_pair, &ciphertext, uid_bytes).unwrap()
|
||||
== profile_key
|
||||
);
|
||||
|
||||
let uid_bytes = TEST_ARRAY_16;
|
||||
let profile_key = profile_key_struct::ProfileKeyStruct::new(TEST_ARRAY_32_3, TEST_ARRAY_16);
|
||||
let ciphertext = key_pair.encrypt(&profile_key);
|
||||
assert!(
|
||||
ProfileKeyEncryptionDomain::decrypt(&key_pair, &ciphertext, uid_bytes).unwrap()
|
||||
== profile_key
|
||||
);
|
||||
|
||||
let uid_bytes = TEST_ARRAY_16;
|
||||
let profile_key = profile_key_struct::ProfileKeyStruct::new(TEST_ARRAY_32_4, TEST_ARRAY_16);
|
||||
let ciphertext = key_pair.encrypt(&profile_key);
|
||||
assert!(
|
||||
ProfileKeyEncryptionDomain::decrypt(&key_pair, &ciphertext, uid_bytes).unwrap()
|
||||
== profile_key
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright 2020 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use curve25519_dalek_signal::ristretto::RistrettoPoint;
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use subtle::{Choice, ConditionallySelectable};
|
||||
|
||||
use crate::common::constants::*;
|
||||
use crate::common::sho::*;
|
||||
use crate::common::simple_types::*;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct ProfileKeyStruct {
|
||||
pub(crate) bytes: ProfileKeyBytes,
|
||||
pub(crate) M3: RistrettoPoint,
|
||||
pub(crate) M4: RistrettoPoint,
|
||||
}
|
||||
|
||||
impl ProfileKeyStruct {
|
||||
pub fn new(profile_key_bytes: ProfileKeyBytes, uid_bytes: UidBytes) -> Self {
|
||||
let mut encoded_profile_key = profile_key_bytes;
|
||||
encoded_profile_key[0] &= 254;
|
||||
encoded_profile_key[31] &= 63;
|
||||
let M3 = Self::calc_M3(Self::seed_M3(), profile_key_bytes, uid_bytes);
|
||||
let M4 = RistrettoPoint::from_uniform_bytes_single_elligator(&encoded_profile_key);
|
||||
|
||||
ProfileKeyStruct {
|
||||
bytes: profile_key_bytes,
|
||||
M3,
|
||||
M4,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn seed_M3() -> Sho {
|
||||
Sho::new_seed(b"Signal_ZKGroup_20200424_ProfileKeyAndUid_ProfileKey_CalcM3")
|
||||
}
|
||||
|
||||
pub(crate) fn calc_M3(
|
||||
mut seed: Sho,
|
||||
profile_key_bytes: ProfileKeyBytes,
|
||||
uid_bytes: UidBytes,
|
||||
) -> RistrettoPoint {
|
||||
let mut combined_array = [0u8; PROFILE_KEY_LEN + UUID_LEN];
|
||||
combined_array[..PROFILE_KEY_LEN].copy_from_slice(&profile_key_bytes);
|
||||
combined_array[PROFILE_KEY_LEN..].copy_from_slice(&uid_bytes);
|
||||
seed.absorb_and_ratchet(&combined_array);
|
||||
seed.get_point_single_elligator()
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> ProfileKeyBytes {
|
||||
self.bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl ConditionallySelectable for ProfileKeyStruct {
|
||||
#[expect(
|
||||
clippy::needless_range_loop,
|
||||
reason = "an explicit loop makes it more clear that this runs in constant time"
|
||||
)]
|
||||
fn conditional_select(
|
||||
a: &ProfileKeyStruct,
|
||||
b: &ProfileKeyStruct,
|
||||
choice: Choice,
|
||||
) -> ProfileKeyStruct {
|
||||
let mut bytes: ProfileKeyBytes = [0u8; PROFILE_KEY_LEN];
|
||||
for i in 0..PROFILE_KEY_LEN {
|
||||
bytes[i] = u8::conditional_select(&a.bytes[i], &b.bytes[i], choice);
|
||||
}
|
||||
|
||||
ProfileKeyStruct {
|
||||
bytes,
|
||||
M3: RistrettoPoint::conditional_select(&a.M3, &b.M3, choice),
|
||||
M4: RistrettoPoint::conditional_select(&a.M4, &b.M4, choice),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl zkcredential::attributes::Attribute for ProfileKeyStruct {
|
||||
fn as_points(&self) -> [RistrettoPoint; 2] {
|
||||
[self.M3, self.M4]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// Copyright 2020 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use curve25519_dalek_signal::ristretto::RistrettoPoint;
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use subtle::{ConditionallySelectable, ConstantTimeEq};
|
||||
use zkcredential::attributes::Attribute;
|
||||
|
||||
use crate::common::errors::*;
|
||||
use crate::common::sho::*;
|
||||
use crate::crypto::uid_struct;
|
||||
|
||||
static SYSTEM_PARAMS: LazyLock<SystemParams> = LazyLock::new(|| {
|
||||
crate::deserialize(&SystemParams::SYSTEM_HARDCODED).expect("valid hardcoded params")
|
||||
});
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct SystemParams {
|
||||
pub(crate) G_a1: RistrettoPoint,
|
||||
pub(crate) G_a2: RistrettoPoint,
|
||||
}
|
||||
|
||||
pub type KeyPair = zkcredential::attributes::KeyPair<UidEncryptionDomain>;
|
||||
pub type PublicKey = zkcredential::attributes::PublicKey<UidEncryptionDomain>;
|
||||
pub type Ciphertext = zkcredential::attributes::Ciphertext<UidEncryptionDomain>;
|
||||
|
||||
impl SystemParams {
|
||||
pub fn generate() -> Self {
|
||||
let mut sho = Sho::new(
|
||||
b"Signal_ZKGroup_20200424_Constant_UidEncryption_SystemParams_Generate",
|
||||
b"",
|
||||
);
|
||||
let G_a1 = sho.get_point();
|
||||
let G_a2 = sho.get_point();
|
||||
SystemParams { G_a1, G_a2 }
|
||||
}
|
||||
|
||||
pub fn get_hardcoded() -> SystemParams {
|
||||
*SYSTEM_PARAMS
|
||||
}
|
||||
|
||||
const SYSTEM_HARDCODED: [u8; 64] = [
|
||||
0xa6, 0x32, 0x4c, 0x36, 0x8d, 0xf7, 0x34, 0x69, 0x11, 0x47, 0x98, 0x13, 0x48, 0xb6, 0xe7,
|
||||
0xeb, 0x42, 0xc3, 0x30, 0x7e, 0x71, 0x1b, 0x6c, 0x7e, 0xcc, 0xd3, 0x3, 0x2d, 0x45, 0x69,
|
||||
0x3f, 0x5a, 0x4, 0x80, 0x13, 0x52, 0x5b, 0x76, 0x12, 0x4b, 0xf2, 0x64, 0xc, 0x5e, 0x93,
|
||||
0x69, 0xc7, 0x6e, 0xfb, 0xe8, 0xa, 0xba, 0x2a, 0x24, 0xaa, 0x5d, 0x8e, 0x18, 0xa9, 0x8e,
|
||||
0xba, 0x14, 0xf8, 0x37,
|
||||
];
|
||||
}
|
||||
|
||||
pub struct UidEncryptionDomain;
|
||||
impl zkcredential::attributes::Domain for UidEncryptionDomain {
|
||||
type Attribute = uid_struct::UidStruct;
|
||||
|
||||
const ID: &'static str = "Signal_ZKGroup_20230419_UidEncryption";
|
||||
|
||||
fn G_a() -> [RistrettoPoint; 2] {
|
||||
let system = SystemParams::get_hardcoded();
|
||||
[system.G_a1, system.G_a2]
|
||||
}
|
||||
}
|
||||
|
||||
impl UidEncryptionDomain {
|
||||
pub(crate) fn decrypt(
|
||||
key_pair: &KeyPair,
|
||||
ciphertext: &Ciphertext,
|
||||
) -> Result<libsignal_core::ServiceId, ZkGroupVerificationFailure> {
|
||||
let M2 = key_pair
|
||||
.decrypt_to_second_point(ciphertext)
|
||||
.map_err(|_| ZkGroupVerificationFailure)?;
|
||||
match M2.lizard_decode::<sha2::Sha256>() {
|
||||
None => Err(ZkGroupVerificationFailure),
|
||||
Some(bytes) => {
|
||||
// We want to do a constant-time choice between the ACI and the PNI possibilities.
|
||||
// Only at the end do we do a normal branch to see if decryption succeeded,
|
||||
// and even then we don't want to expose whether we picked the ACI or the PNI.
|
||||
// So we store them both in an array, and index into it at the very end.
|
||||
// This isn't fully "data-oblivious"; only one service ID gets loaded from memory at
|
||||
// the end, and which one is data-dependent. But it is constant-time.
|
||||
let decoded_uuid = uuid::Uuid::from_bytes(bytes);
|
||||
let decoded_service_ids = [
|
||||
libsignal_core::Aci::from(decoded_uuid).into(),
|
||||
libsignal_core::Pni::from(decoded_uuid).into(),
|
||||
];
|
||||
let decoded_aci = &decoded_service_ids[0];
|
||||
let decoded_pni = &decoded_service_ids[1];
|
||||
let sho_seed = uid_struct::UidStruct::seed_M1();
|
||||
let aci_M1 = uid_struct::UidStruct::calc_M1(sho_seed.clone(), *decoded_aci);
|
||||
let pni_M1 = uid_struct::UidStruct::calc_M1(sho_seed, *decoded_pni);
|
||||
debug_assert!(aci_M1 != pni_M1);
|
||||
let decrypted_M1 = key_pair.a1.invert() * ciphertext.as_points()[0];
|
||||
let mut index = u8::MAX;
|
||||
index.conditional_assign(&0, decrypted_M1.ct_eq(&aci_M1));
|
||||
index.conditional_assign(&1, decrypted_M1.ct_eq(&pni_M1));
|
||||
decoded_service_ids
|
||||
.get(index as usize)
|
||||
.copied()
|
||||
.ok_or(ZkGroupVerificationFailure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::common::constants::*;
|
||||
|
||||
#[test]
|
||||
fn test_uid_encryption() {
|
||||
let master_key = TEST_ARRAY_32;
|
||||
let mut sho = Sho::new(b"Test_Uid_Encryption", &master_key);
|
||||
|
||||
//let system = SystemParams::generate();
|
||||
//println!("PARAMS = {:#x?}", bincode::serialize(&system));
|
||||
assert!(SystemParams::generate() == SystemParams::get_hardcoded());
|
||||
|
||||
let key_pair = KeyPair::derive_from(sho.as_mut());
|
||||
|
||||
// Test serialize of key_pair
|
||||
let key_pair_bytes = bincode::serialize(&key_pair).unwrap();
|
||||
match bincode::deserialize::<KeyPair>(&key_pair_bytes[0..key_pair_bytes.len() - 1]) {
|
||||
Err(_) => (),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let key_pair2: KeyPair = bincode::deserialize(&key_pair_bytes).unwrap();
|
||||
assert!(key_pair == key_pair2);
|
||||
|
||||
let aci = libsignal_core::Aci::from_uuid_bytes(TEST_ARRAY_16);
|
||||
let uid = uid_struct::UidStruct::from_service_id(aci.into());
|
||||
let ciphertext = key_pair.encrypt(&uid);
|
||||
|
||||
// Test serialize / deserialize of Ciphertext
|
||||
let ciphertext_bytes = bincode::serialize(&ciphertext).unwrap();
|
||||
assert!(ciphertext_bytes.len() == 64);
|
||||
let ciphertext2: Ciphertext = bincode::deserialize(&ciphertext_bytes).unwrap();
|
||||
assert!(ciphertext == ciphertext2);
|
||||
//println!("ciphertext_bytes = {:#x?}", ciphertext_bytes);
|
||||
assert!(
|
||||
ciphertext_bytes
|
||||
== vec![
|
||||
0xf8, 0x9e, 0xe7, 0x70, 0x5a, 0x66, 0x3, 0x6b, 0x90, 0x8d, 0xb8, 0x84, 0x21,
|
||||
0x1b, 0x77, 0x3a, 0xc5, 0x43, 0xee, 0x35, 0xc4, 0xa3, 0x8, 0x62, 0x20, 0xfc,
|
||||
0x3e, 0x1e, 0x35, 0xb4, 0x23, 0x4c, 0xfa, 0x1d, 0x2e, 0xea, 0x2c, 0xc2, 0xf4,
|
||||
0xb4, 0xc4, 0x2c, 0xff, 0x39, 0xa9, 0xdc, 0xeb, 0x57, 0x29, 0x3b, 0x5f, 0x87,
|
||||
0x70, 0xca, 0x60, 0xf9, 0xe9, 0xb7, 0x44, 0x47, 0xbf, 0xd3, 0xbd, 0x3d,
|
||||
]
|
||||
);
|
||||
|
||||
let plaintext = UidEncryptionDomain::decrypt(&key_pair, &ciphertext2).unwrap();
|
||||
assert!(matches!(plaintext, libsignal_core::ServiceId::Aci(_)));
|
||||
assert!(uid_struct::UidStruct::from_service_id(plaintext) == uid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pni_encryption() {
|
||||
let mut sho = Sho::new(b"Test_Pni_Encryption", &[]);
|
||||
let key_pair = KeyPair::derive_from(sho.as_mut());
|
||||
|
||||
let pni = libsignal_core::Pni::from_uuid_bytes(TEST_ARRAY_16);
|
||||
let uid = uid_struct::UidStruct::from_service_id(pni.into());
|
||||
let ciphertext = key_pair.encrypt(&uid);
|
||||
|
||||
// Test serialize / deserialize of Ciphertext
|
||||
let ciphertext_bytes = bincode::serialize(&ciphertext).unwrap();
|
||||
assert!(ciphertext_bytes.len() == 64);
|
||||
let ciphertext2: Ciphertext = bincode::deserialize(&ciphertext_bytes).unwrap();
|
||||
assert!(ciphertext == ciphertext2);
|
||||
|
||||
let plaintext = UidEncryptionDomain::decrypt(&key_pair, &ciphertext2).unwrap();
|
||||
assert!(matches!(plaintext, libsignal_core::ServiceId::Pni(_)));
|
||||
assert!(uid_struct::UidStruct::from_service_id(plaintext) == uid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Copyright 2020 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use curve25519_dalek_signal::ristretto::RistrettoPoint;
|
||||
use libsignal_core::ServiceId;
|
||||
use partial_default::PartialDefault;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::common::sho::*;
|
||||
use crate::common::simple_types::*;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
|
||||
pub struct UidStruct {
|
||||
// Currently unused. It would be possible to convert this back to the correct kind of ServiceId
|
||||
// using the same technique as decryption: comparing possible M1 points and seeing which one
|
||||
// matches. But we don't have a need for that, and therefore it's better if that operation
|
||||
// remains part of decryption, so that you're guaranteed to get a valid result or an error in
|
||||
// one step.
|
||||
//
|
||||
// At the same time, we can't just remove the field: it's serialized as part of AuthCredential
|
||||
// and AuthCredentialWithPni, which clients store locally.
|
||||
#[serde(rename = "bytes")]
|
||||
raw_uuid_bytes: UidBytes,
|
||||
pub(crate) M1: RistrettoPoint,
|
||||
pub(crate) M2: RistrettoPoint,
|
||||
}
|
||||
|
||||
impl UidStruct {
|
||||
pub fn from_service_id(service_id: ServiceId) -> Self {
|
||||
let M1 = Self::calc_M1(Self::seed_M1(), service_id);
|
||||
let raw_uuid_bytes = service_id.raw_uuid().into_bytes();
|
||||
let M2 = RistrettoPoint::lizard_encode::<Sha256>(&raw_uuid_bytes);
|
||||
UidStruct {
|
||||
raw_uuid_bytes,
|
||||
M1,
|
||||
M2,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn seed_M1() -> Sho {
|
||||
Sho::new_seed(b"Signal_ZKGroup_20200424_UID_CalcM1")
|
||||
}
|
||||
|
||||
pub(crate) fn calc_M1(mut seed: Sho, service_id: ServiceId) -> RistrettoPoint {
|
||||
seed.absorb_and_ratchet(&service_id.service_id_binary());
|
||||
seed.get_point()
|
||||
}
|
||||
}
|
||||
|
||||
impl zkcredential::attributes::Attribute for UidStruct {
|
||||
fn as_points(&self) -> [RistrettoPoint; 2] {
|
||||
[self.M1, self.M2]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
//
|
||||
// Copyright 2023 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
//! Examples of using zkcredential with existing zkgroup types.
|
||||
//!
|
||||
//! Has to live in zkgroup because they implement zkcredential traits on zkgroup types.
|
||||
|
||||
use curve25519_dalek_signal::ristretto::RistrettoPoint;
|
||||
use poksho::shoapi::ShoApiExt as _;
|
||||
use poksho::{ShoApi, ShoSha256};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zkcredential::attributes::{Attribute, Domain, RevealedAttribute};
|
||||
use zkcredential::credentials::CredentialKeyPair;
|
||||
use zkcredential::issuance::IssuanceProofBuilder;
|
||||
use zkcredential::issuance::blind::{
|
||||
BlindedAttribute, BlindedPoint, BlindingKeyPair, BlindingPublicKey, WithoutNonce,
|
||||
};
|
||||
use zkcredential::presentation::{
|
||||
PresentationProof, PresentationProofBuilder, PresentationProofVerifier,
|
||||
};
|
||||
use zkcredential::sho::ShoExt;
|
||||
|
||||
use crate::common::sho::*;
|
||||
use crate::crypto::profile_key_struct::ProfileKeyStruct;
|
||||
use crate::crypto::uid_struct::UidStruct;
|
||||
use crate::crypto::{profile_key_encryption, uid_encryption};
|
||||
use crate::{RANDOMNESS_LEN, TEST_ARRAY_16, TEST_ARRAY_32};
|
||||
|
||||
#[test]
|
||||
fn test_mac_generic() {
|
||||
let mut sho = ShoSha256::new(b"Test_Credentials");
|
||||
let keypair = CredentialKeyPair::generate(sho.squeeze_and_ratchet_as_array());
|
||||
|
||||
let label = b"20221221_AuthCredentialLike";
|
||||
|
||||
let uid_bytes = TEST_ARRAY_16;
|
||||
let aci = libsignal_core::Aci::from_uuid_bytes(uid_bytes);
|
||||
let uid = UidStruct::from_service_id(aci.into());
|
||||
|
||||
let proof = IssuanceProofBuilder::new(label)
|
||||
.add_attribute(&uid)
|
||||
.add_public_attribute(&[1, 2, 3])
|
||||
.issue(&keypair, sho.squeeze_and_ratchet_as_array());
|
||||
|
||||
let credential = IssuanceProofBuilder::new(label)
|
||||
.add_attribute(&uid)
|
||||
.add_public_attribute(&[1, 2, 3])
|
||||
.verify(keypair.public_key(), proof)
|
||||
.unwrap();
|
||||
|
||||
let uid_encryption_key = uid_encryption::KeyPair::derive_from(Sho::new(b"test", b"").as_mut());
|
||||
let uid_encryption_public_key = uid_encryption_key.public_key;
|
||||
|
||||
let proof = PresentationProofBuilder::new(label)
|
||||
.add_attribute(&uid, &uid_encryption_key)
|
||||
.present(
|
||||
keypair.public_key(),
|
||||
&credential,
|
||||
sho.squeeze_and_ratchet_as_array(),
|
||||
);
|
||||
|
||||
PresentationProofVerifier::new(label)
|
||||
.add_public_attribute(&[1, 2, 3])
|
||||
.add_attribute(
|
||||
&uid_encryption_key.encrypt(&uid),
|
||||
&uid_encryption_public_key,
|
||||
)
|
||||
.verify(&keypair, &proof)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mac_generic_without_verifying_encryption_key() {
|
||||
let mut sho = ShoSha256::new(b"Test_Credentials");
|
||||
let keypair = CredentialKeyPair::generate(sho.squeeze_and_ratchet_as_array());
|
||||
|
||||
let label = b"20221221_AuthCredentialLike";
|
||||
|
||||
let uid_bytes = TEST_ARRAY_16;
|
||||
let aci = libsignal_core::Aci::from_uuid_bytes(uid_bytes);
|
||||
let uid = UidStruct::from_service_id(aci.into());
|
||||
|
||||
let proof = IssuanceProofBuilder::new(label)
|
||||
.add_attribute(&uid)
|
||||
.add_public_attribute(&[1, 2, 3])
|
||||
.issue(&keypair, sho.squeeze_and_ratchet_as_array());
|
||||
|
||||
let credential = IssuanceProofBuilder::new(label)
|
||||
.add_attribute(&uid)
|
||||
.add_public_attribute(&[1, 2, 3])
|
||||
.verify(keypair.public_key(), proof)
|
||||
.unwrap();
|
||||
|
||||
let uid_encryption_key = uid_encryption::KeyPair::derive_from(Sho::new(b"test", b"").as_mut());
|
||||
|
||||
let proof = PresentationProofBuilder::new(label)
|
||||
.add_attribute_without_verified_key(&uid, &uid_encryption_key)
|
||||
.present(
|
||||
keypair.public_key(),
|
||||
&credential,
|
||||
sho.squeeze_and_ratchet_as_array(),
|
||||
);
|
||||
|
||||
PresentationProofVerifier::new(label)
|
||||
.add_public_attribute(&[1, 2, 3])
|
||||
.add_attribute_without_verified_key(
|
||||
&uid_encryption_key.encrypt(&uid),
|
||||
uid_encryption::UidEncryptionDomain::ID,
|
||||
)
|
||||
.verify(&keypair, &proof)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_profile_key_credential() {
|
||||
let mut sho = ShoSha256::new(b"Test_Credentials");
|
||||
let keypair = CredentialKeyPair::generate(sho.squeeze_and_ratchet_as_array());
|
||||
let blinding_keypair = BlindingKeyPair::generate(&mut sho);
|
||||
|
||||
let label = b"20221221_ProfileKeyCredentialLike";
|
||||
|
||||
let aci = libsignal_core::Aci::from_uuid_bytes(TEST_ARRAY_16);
|
||||
let uid = UidStruct::from_service_id(aci.into());
|
||||
let profile_key = ProfileKeyStruct::new(TEST_ARRAY_32, TEST_ARRAY_16);
|
||||
let encrypted_profile_key = blinding_keypair.encrypt(&profile_key, &mut sho).into();
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Request {
|
||||
uid: UidStruct,
|
||||
encrypted_profile_key: BlindedAttribute,
|
||||
blinding_public_key: BlindingPublicKey,
|
||||
}
|
||||
|
||||
// Client
|
||||
let request_serialized = bincode::serialize(&Request {
|
||||
uid,
|
||||
encrypted_profile_key,
|
||||
blinding_public_key: *blinding_keypair.public_key(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Issuing server
|
||||
let request: Request = bincode::deserialize(&request_serialized).unwrap();
|
||||
|
||||
let proof = IssuanceProofBuilder::with_authenticated_message(label, b"abc")
|
||||
.add_attribute(&request.uid)
|
||||
.add_blinded_attribute(&request.encrypted_profile_key)
|
||||
.issue(
|
||||
&keypair,
|
||||
&request.blinding_public_key,
|
||||
sho.squeeze_and_ratchet_as_array(),
|
||||
);
|
||||
|
||||
let proof_serialized = bincode::serialize(&proof).unwrap();
|
||||
|
||||
// Client
|
||||
let credential = IssuanceProofBuilder::with_authenticated_message(label, b"abc")
|
||||
.add_attribute(&uid)
|
||||
.add_blinded_attribute(&encrypted_profile_key)
|
||||
.verify(
|
||||
keypair.public_key(),
|
||||
&blinding_keypair,
|
||||
bincode::deserialize(&proof_serialized).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut zkgroup_sho = Sho::new(b"test", b"");
|
||||
let uid_encryption_key = uid_encryption::KeyPair::derive_from(zkgroup_sho.as_mut());
|
||||
let profile_key_encryption_key =
|
||||
profile_key_encryption::KeyPair::derive_from(zkgroup_sho.as_mut());
|
||||
|
||||
let proof = PresentationProofBuilder::with_authenticated_message(label, b"v1")
|
||||
.add_attribute(&uid, &uid_encryption_key)
|
||||
.add_attribute(&profile_key, &profile_key_encryption_key)
|
||||
.present(
|
||||
keypair.public_key(),
|
||||
&credential,
|
||||
sho.squeeze_and_ratchet_as_array(),
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Presentation {
|
||||
proof: PresentationProof,
|
||||
encrypted_uid: uid_encryption::Ciphertext,
|
||||
encrypted_profile_key: profile_key_encryption::Ciphertext,
|
||||
uid_encryption_public_key: uid_encryption::PublicKey,
|
||||
profile_key_encryption_public_key: profile_key_encryption::PublicKey,
|
||||
}
|
||||
|
||||
let presentation_serialized = bincode::serialize(&Presentation {
|
||||
proof,
|
||||
encrypted_uid: uid_encryption_key.encrypt(&uid),
|
||||
encrypted_profile_key: profile_key_encryption_key.encrypt(&profile_key),
|
||||
uid_encryption_public_key: uid_encryption_key.public_key,
|
||||
profile_key_encryption_public_key: profile_key_encryption_key.public_key,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Verifying server
|
||||
let presentation: Presentation = bincode::deserialize(&presentation_serialized).unwrap();
|
||||
PresentationProofVerifier::with_authenticated_message(label, b"v1")
|
||||
.add_attribute(
|
||||
&presentation.encrypted_uid,
|
||||
&presentation.uid_encryption_public_key,
|
||||
)
|
||||
.add_attribute(
|
||||
&presentation.encrypted_profile_key,
|
||||
&presentation.profile_key_encryption_public_key,
|
||||
)
|
||||
.verify(&keypair, &presentation.proof)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_profile_key_credential_only_verifying_one_encryption_key() {
|
||||
let mut sho = ShoSha256::new(b"Test_Credentials");
|
||||
let keypair = CredentialKeyPair::generate(sho.squeeze_and_ratchet_as_array());
|
||||
let blinding_keypair = BlindingKeyPair::generate(&mut sho);
|
||||
|
||||
let label = b"20221221_ProfileKeyCredentialLike";
|
||||
|
||||
let aci = libsignal_core::Aci::from_uuid_bytes(TEST_ARRAY_16);
|
||||
let uid = UidStruct::from_service_id(aci.into());
|
||||
let profile_key = ProfileKeyStruct::new(TEST_ARRAY_32, TEST_ARRAY_16);
|
||||
let encrypted_profile_key = blinding_keypair.encrypt(&profile_key, &mut sho).into();
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Request {
|
||||
uid: UidStruct,
|
||||
encrypted_profile_key: BlindedAttribute,
|
||||
blinding_public_key: BlindingPublicKey,
|
||||
}
|
||||
|
||||
// Client
|
||||
let request_serialized = bincode::serialize(&Request {
|
||||
uid,
|
||||
encrypted_profile_key,
|
||||
blinding_public_key: *blinding_keypair.public_key(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Issuing server
|
||||
let request: Request = bincode::deserialize(&request_serialized).unwrap();
|
||||
|
||||
let proof = IssuanceProofBuilder::with_authenticated_message(label, b"abc")
|
||||
.add_attribute(&request.uid)
|
||||
.add_blinded_attribute(&request.encrypted_profile_key)
|
||||
.issue(
|
||||
&keypair,
|
||||
&request.blinding_public_key,
|
||||
sho.squeeze_and_ratchet_as_array(),
|
||||
);
|
||||
|
||||
let proof_serialized = bincode::serialize(&proof).unwrap();
|
||||
|
||||
// Client
|
||||
let credential = IssuanceProofBuilder::with_authenticated_message(label, b"abc")
|
||||
.add_attribute(&uid)
|
||||
.add_blinded_attribute(&encrypted_profile_key)
|
||||
.verify(
|
||||
keypair.public_key(),
|
||||
&blinding_keypair,
|
||||
bincode::deserialize(&proof_serialized).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut zkgroup_sho = Sho::new(b"test", b"");
|
||||
let uid_encryption_key = uid_encryption::KeyPair::derive_from(zkgroup_sho.as_mut());
|
||||
let profile_key_encryption_key =
|
||||
profile_key_encryption::KeyPair::derive_from(zkgroup_sho.as_mut());
|
||||
|
||||
let proof = PresentationProofBuilder::with_authenticated_message(label, b"v1")
|
||||
.add_attribute_without_verified_key(&uid, &uid_encryption_key)
|
||||
.add_attribute(&profile_key, &profile_key_encryption_key)
|
||||
.present(
|
||||
keypair.public_key(),
|
||||
&credential,
|
||||
sho.squeeze_and_ratchet_as_array(),
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Presentation {
|
||||
proof: PresentationProof,
|
||||
encrypted_uid: uid_encryption::Ciphertext,
|
||||
encrypted_profile_key: profile_key_encryption::Ciphertext,
|
||||
uid_encryption_public_key: uid_encryption::PublicKey,
|
||||
profile_key_encryption_public_key: profile_key_encryption::PublicKey,
|
||||
}
|
||||
|
||||
let presentation_serialized = bincode::serialize(&Presentation {
|
||||
proof,
|
||||
encrypted_uid: uid_encryption_key.encrypt(&uid),
|
||||
encrypted_profile_key: profile_key_encryption_key.encrypt(&profile_key),
|
||||
uid_encryption_public_key: uid_encryption_key.public_key,
|
||||
profile_key_encryption_public_key: profile_key_encryption_key.public_key,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Verifying server
|
||||
let presentation: Presentation = bincode::deserialize(&presentation_serialized).unwrap();
|
||||
PresentationProofVerifier::with_authenticated_message(label, b"v1")
|
||||
.add_attribute_without_verified_key(
|
||||
&presentation.encrypted_uid,
|
||||
uid_encryption::UidEncryptionDomain::ID,
|
||||
)
|
||||
.add_attribute(
|
||||
&presentation.encrypted_profile_key,
|
||||
&presentation.profile_key_encryption_public_key,
|
||||
)
|
||||
.verify(&keypair, &presentation.proof)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_room_credential() {
|
||||
let mut sho = ShoSha256::new(b"RoomCredential");
|
||||
let keypair = CredentialKeyPair::generate(sho.squeeze_and_ratchet_as_array());
|
||||
let blinding_keypair = BlindingKeyPair::generate(&mut sho);
|
||||
|
||||
let label = b"20230330_RoomCredential";
|
||||
let request_label = b"20230330_RoomCredential_Request";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RoomId {
|
||||
opaque_id: RistrettoPoint,
|
||||
}
|
||||
impl RevealedAttribute for RoomId {
|
||||
fn as_point(&self) -> RistrettoPoint {
|
||||
self.opaque_id
|
||||
}
|
||||
}
|
||||
let room_id = RoomId {
|
||||
opaque_id: sho.get_point(),
|
||||
};
|
||||
let blinded_room_id = blinding_keypair.blind(&room_id, &mut sho);
|
||||
|
||||
// Generate a request proof.
|
||||
let mut request_proof_statement = poksho::Statement::new();
|
||||
request_proof_statement.add("Y", &[("y", "G")]);
|
||||
request_proof_statement.add("D1", &[("r1", "G")]);
|
||||
// For most credentials we'd want to constraint D2 as well, but room IDs are unconstrained.
|
||||
// So we leave it out; if the client passes a wild D2, well, they won't get a credential back.
|
||||
let mut request_scalar_args = poksho::ScalarArgs::new();
|
||||
request_scalar_args.add("y", blinding_keypair.private_key().y);
|
||||
request_scalar_args.add("r1", blinded_room_id.r.0);
|
||||
let mut request_point_args = poksho::PointArgs::new();
|
||||
request_point_args.add("Y", blinding_keypair.public_key().Y);
|
||||
request_point_args.add("D1", blinded_room_id.D1);
|
||||
let proof = request_proof_statement
|
||||
.prove(
|
||||
&request_scalar_args,
|
||||
&request_point_args,
|
||||
request_label,
|
||||
&sho.squeeze_and_ratchet_as_array::<RANDOMNESS_LEN>(),
|
||||
)
|
||||
.expect("valid");
|
||||
|
||||
let blinded_room_id: BlindedPoint<WithoutNonce> = blinded_room_id.into();
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Request {
|
||||
blinded_room_id: BlindedPoint,
|
||||
blinding_public_key: BlindingPublicKey,
|
||||
proof: Vec<u8>,
|
||||
}
|
||||
|
||||
// Client
|
||||
let request_serialized = bincode::serialize(&Request {
|
||||
blinded_room_id,
|
||||
blinding_public_key: *blinding_keypair.public_key(),
|
||||
proof,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Issuing server
|
||||
let request: Request = bincode::deserialize(&request_serialized).unwrap();
|
||||
|
||||
let mut request_verifying_point_args = poksho::PointArgs::new();
|
||||
request_verifying_point_args.add("Y", request.blinding_public_key.Y);
|
||||
request_verifying_point_args.add("D1", request.blinded_room_id.D1);
|
||||
request_proof_statement
|
||||
.verify_proof(&request.proof, &request_verifying_point_args, request_label)
|
||||
.expect("valid");
|
||||
|
||||
let expiration = 1680220000u32;
|
||||
|
||||
let proof = IssuanceProofBuilder::new(label)
|
||||
.add_public_attribute(&expiration)
|
||||
.add_blinded_revealed_attribute(&request.blinded_room_id)
|
||||
.issue(
|
||||
&keypair,
|
||||
&request.blinding_public_key,
|
||||
sho.squeeze_and_ratchet_as_array(),
|
||||
);
|
||||
|
||||
let proof_serialized = bincode::serialize(&proof).unwrap();
|
||||
|
||||
// Client
|
||||
let credential = IssuanceProofBuilder::new(label)
|
||||
.add_public_attribute(&expiration)
|
||||
.add_blinded_revealed_attribute(&blinded_room_id)
|
||||
.verify(
|
||||
keypair.public_key(),
|
||||
&blinding_keypair,
|
||||
bincode::deserialize(&proof_serialized).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let proof = PresentationProofBuilder::new(label)
|
||||
.add_revealed_attribute(&room_id)
|
||||
.present(
|
||||
keypair.public_key(),
|
||||
&credential,
|
||||
sho.squeeze_and_ratchet_as_array(),
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Presentation {
|
||||
proof: PresentationProof,
|
||||
room_id: RoomId,
|
||||
expiration: u32,
|
||||
}
|
||||
|
||||
let presentation_serialized = bincode::serialize(&Presentation {
|
||||
proof,
|
||||
room_id,
|
||||
expiration,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Verifying server
|
||||
let presentation: Presentation = bincode::deserialize(&presentation_serialized).unwrap();
|
||||
PresentationProofVerifier::new(label)
|
||||
.add_public_attribute(&presentation.expiration)
|
||||
.add_revealed_attribute(&presentation.room_id)
|
||||
.verify(&keypair, &presentation.proof)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
struct InverseUidDecryptionKey;
|
||||
impl zkcredential::attributes::Domain for InverseUidDecryptionKey {
|
||||
type Attribute = uid_encryption::Ciphertext;
|
||||
const ID: &'static str = "InverseUidEncryptionDomain_20231011";
|
||||
fn G_a() -> [curve25519_dalek_signal::RistrettoPoint; 2] {
|
||||
static STORAGE: std::sync::OnceLock<[curve25519_dalek_signal::RistrettoPoint; 2]> =
|
||||
std::sync::OnceLock::new();
|
||||
*zkcredential::attributes::derive_default_generator_points::<Self>(&STORAGE)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inverse_key() {
|
||||
let aci = libsignal_core::Aci::from_uuid_bytes(TEST_ARRAY_16);
|
||||
let uid = UidStruct::from_service_id(aci.into());
|
||||
|
||||
let mut sho = Sho::new(b"test_inverse_key", b"");
|
||||
let uid_encryption_key = uid_encryption::KeyPair::derive_from(sho.as_mut());
|
||||
|
||||
let encrypted = uid_encryption_key.encrypt(&uid);
|
||||
|
||||
let inverse = zkcredential::attributes::KeyPair::<InverseUidDecryptionKey>::inverse_of(
|
||||
&uid_encryption_key,
|
||||
);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let [E_A1_prime, E_A2_prime] = inverse.encrypt(&encrypted).as_points();
|
||||
|
||||
assert_eq!(uid.M1, E_A1_prime);
|
||||
assert_eq!(uid.M2, E_A2_prime);
|
||||
}
|
||||
Reference in New Issue
Block a user