1use partial_default::PartialDefault;
7use serde::{Deserialize, Serialize};
8use signal_crypto::Aes256GcmEncryption;
9use subtle::ConstantTimeEq;
10
11use crate::common::constants::*;
12use crate::common::sho::*;
13use crate::common::simple_types::*;
14use crate::{api, crypto};
15
16#[derive(Copy, Clone, Serialize, Deserialize, PartialDefault)]
17pub struct ProfileKey {
18 pub bytes: ProfileKeyBytes,
19}
20
21impl std::fmt::Debug for ProfileKey {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 f.debug_struct("ProfileKey")
24 .field("bytes", &zkcredential::PrintAsHex(self.bytes.as_slice()))
25 .finish()
26 }
27}
28
29impl PartialEq for ProfileKey {
30 fn eq(&self, other: &Self) -> bool {
31 self.bytes.as_slice().ct_eq(other.bytes.as_slice()).into()
32 }
33}
34
35impl ProfileKey {
36 pub fn generate(randomness: RandomnessBytes) -> Self {
37 let mut sho = Sho::new(
38 b"Signal_ZKGroup_20200424_Random_ProfileKey_Generate",
39 &randomness,
40 );
41 let mut bytes = [0u8; PROFILE_KEY_LEN];
42 bytes.copy_from_slice(&sho.squeeze(PROFILE_KEY_LEN)[..]);
43 Self { bytes }
44 }
45
46 pub fn create(bytes: ProfileKeyBytes) -> Self {
47 Self { bytes }
48 }
49
50 pub fn get_bytes(&self) -> ProfileKeyBytes {
51 self.bytes
52 }
53
54 pub fn get_commitment(
55 &self,
56 user_id: libsignal_core::Aci,
57 ) -> api::profiles::ProfileKeyCommitment {
58 let uid_bytes = uuid::Uuid::from(user_id).into_bytes();
59 let profile_key = crypto::profile_key_struct::ProfileKeyStruct::new(self.bytes, uid_bytes);
60 let commitment =
61 crypto::profile_key_commitment::CommitmentWithSecretNonce::new(profile_key, uid_bytes);
62 api::profiles::ProfileKeyCommitment {
63 reserved: Default::default(),
64 commitment: commitment.get_profile_key_commitment(),
65 }
66 }
67
68 pub fn get_profile_key_version(
69 &self,
70 user_id: libsignal_core::Aci,
71 ) -> api::profiles::ProfileKeyVersion {
72 let uid_bytes = uuid::Uuid::from(user_id).into_bytes();
73 let mut combined_array = [0u8; PROFILE_KEY_LEN + UUID_LEN];
74 combined_array[..PROFILE_KEY_LEN].copy_from_slice(&self.bytes);
75 combined_array[PROFILE_KEY_LEN..].copy_from_slice(&uid_bytes);
76 let mut sho = Sho::new(
77 b"Signal_ZKGroup_20200424_ProfileKeyAndUid_ProfileKey_GetProfileKeyVersion",
78 &combined_array,
79 );
80
81 let pkv_hex_string = hex::encode(&sho.squeeze(PROFILE_KEY_VERSION_LEN)[..]);
82 let mut pkv_hex_array: [u8; PROFILE_KEY_VERSION_ENCODED_LEN] =
83 [0u8; PROFILE_KEY_VERSION_ENCODED_LEN];
84 pkv_hex_array.copy_from_slice(pkv_hex_string.as_bytes());
85 api::profiles::ProfileKeyVersion {
86 bytes: pkv_hex_array,
87 }
88 }
89
90 pub fn derive_access_key(&self) -> [u8; ACCESS_KEY_LEN] {
91 let nonce = &[0u8; AESGCM_NONCE_LEN];
92 let mut cipher = Aes256GcmEncryption::new(&self.bytes, nonce, &[]).unwrap();
93 let mut buf = [0u8; ACCESS_KEY_LEN];
94 cipher.encrypt(&mut buf[..]);
95 buf
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn access_key_kat() {
105 let kat = [
107 (
108 [
109 0xb9, 0x50, 0x42, 0xa2, 0xc2, 0xd9, 0xe5, 0xb3, 0xbb, 0x9, 0x30, 0xe, 0xe4,
110 0x8, 0xa1, 0x72, 0xfa, 0xcd, 0x96, 0xe9, 0x1b, 0x50, 0x4e, 0x4, 0x3a, 0x5a,
111 0x2, 0x3d, 0xc4, 0xcf, 0xf3, 0x59,
112 ],
113 [
114 0x24, 0xfb, 0x96, 0xd4, 0xa5, 0xe3, 0x33, 0xe9, 0xd4, 0x45, 0x12, 0x5, 0xb9,
115 0xe2, 0xfa, 0xed,
116 ],
117 ),
118 (
119 [
120 0x26, 0x19, 0x7b, 0x17, 0xe5, 0xa2, 0xc3, 0x6d, 0x8c, 0x95, 0x18, 0xc3, 0x53,
121 0x58, 0xf1, 0x23, 0xc4, 0x76, 0x0, 0xd, 0xb6, 0xda, 0x75, 0x65, 0xc0, 0xd4,
122 0x1f, 0x66, 0x74, 0x46, 0x2c, 0x4d,
123 ],
124 [
125 0xe8, 0x95, 0xc3, 0xc, 0xf7, 0x80, 0x75, 0x7d, 0x22, 0xf7, 0xa1, 0x79, 0x70,
126 0x8b, 0x14, 0xa1,
127 ],
128 ),
129 ];
130
131 for (pk, ak) in kat {
132 let profile_key = ProfileKey::create(pk);
133 let access_key = profile_key.derive_access_key();
134 assert_eq!(ak, access_key);
135 }
136 }
137}