xmtp_cryptography/
basic_credential.rs1use ed25519_dalek::SigningKey;
2use openmls::prelude::SignaturePublicKey;
3use openmls_basic_credential::SignatureKeyPair;
4use openmls_traits::signatures::Signer;
5use openmls_traits::{signatures, types::SignatureScheme};
6use serde::de::Error;
7use std::io::BufReader;
8use tls_codec::SecretTlsVecU8;
9use zeroize::Zeroizing;
10
11#[derive(thiserror::Error, Debug)]
13pub struct SignerError {
14 inner: signatures::SignerError,
15}
16
17impl From<signatures::SignerError> for SignerError {
18 fn from(err: signatures::SignerError) -> SignerError {
19 SignerError { inner: err }
20 }
21}
22
23impl std::fmt::Display for SignerError {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 use signatures::SignerError::*;
26 match self.inner {
27 SigningError => write!(f, "signing error"),
28 InvalidSignature => write!(f, "invalid signature"),
29 CryptoError(c) => write!(f, "{c}"),
30 }
31 }
32}
33
34mod private {
35
36 pub struct NotSpecialized;
42}
43
44pub trait CredentialSign<SP = private::NotSpecialized> {
46 type Error;
48
49 fn credential_sign<T: SigningContextProvider>(
50 &self,
51 text: impl AsRef<str>,
52 ) -> Result<Vec<u8>, Self::Error>;
53}
54
55pub trait SigningContextProvider {
56 fn context() -> &'static [u8];
57}
58
59pub trait CredentialVerify<SP = private::NotSpecialized> {
61 type Error;
62
63 fn credential_verify<T: SigningContextProvider>(
64 &self,
65 signature_text: impl AsRef<str>,
66 signature_bytes: &[u8; 64],
67 ) -> Result<(), Self::Error>;
68}
69
70#[derive(Debug, Clone)]
76pub struct XmtpInstallationCredential(Box<SigningKey>);
77
78impl Default for XmtpInstallationCredential {
79 fn default() -> Self {
80 Self(Box::new(SigningKey::generate(&mut crate::rand::rng())))
81 }
82}
83
84impl XmtpInstallationCredential {
85 pub fn new() -> Self {
87 Self(Box::new(SigningKey::generate(&mut crate::rand::rng())))
88 }
89
90 pub fn verifying_key(&self) -> ed25519_dalek::VerifyingKey {
93 self.0.verifying_key()
94 }
95
96 pub fn public_bytes(&self) -> &[u8; 32] {
98 self.0.as_ref().as_ref().as_bytes()
99 }
100
101 pub fn public_slice(&self) -> &[u8] {
103 self.0.as_ref().as_ref().as_ref()
104 }
105
106 fn scheme(&self) -> SignatureScheme {
108 SignatureScheme::ED25519
109 }
110
111 pub fn with_context<'k, 'v>(
112 &'k self,
113 context: &'v [u8],
114 ) -> Result<ed25519_dalek::Context<'k, 'v, SigningKey>, ed25519_dalek::SignatureError> {
115 self.0.with_context(context)
116 }
117
118 fn from_raw(private: &[u8], public: &[u8]) -> Result<Self, ed25519_dalek::SignatureError> {
121 let keypair = Zeroizing::new({
122 let mut keypair = [0u8; 64];
123 keypair[0..32].copy_from_slice(private);
124 keypair[32..].copy_from_slice(public);
125 keypair
126 });
127
128 let signing_key = SigningKey::from_keypair_bytes(&keypair)?;
129 Ok(Self(Box::new(signing_key)))
130 }
131
132 pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, ed25519_dalek::SignatureError> {
134 let key = SigningKey::from_bytes(bytes);
135 Ok(Self(Box::new(key)))
136 }
137
138 #[cfg(feature = "exposed-keys")]
140 pub fn private_bytes(&self) -> [u8; 32] {
141 self.0.to_bytes()
142 }
143}
144
145impl From<XmtpInstallationCredential> for SignaturePublicKey {
146 fn from(value: XmtpInstallationCredential) -> Self {
147 SignaturePublicKey::from(value.verifying_key().as_ref())
148 }
149}
150
151impl Signer for XmtpInstallationCredential {
153 fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, signatures::SignerError> {
154 SignatureKeyPair::from(self).sign(payload)
155 }
156
157 fn signature_scheme(&self) -> SignatureScheme {
158 self.scheme()
159 }
160}
161
162impl Signer for &XmtpInstallationCredential {
164 fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, signatures::SignerError> {
165 SignatureKeyPair::from(*self).sign(payload)
166 }
167
168 fn signature_scheme(&self) -> SignatureScheme {
169 self.scheme()
170 }
171}
172
173impl tls_codec::Deserialize for XmtpInstallationCredential {
174 fn tls_deserialize<R: std::io::Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
175 where
176 Self: Sized,
177 {
178 let mut buf = BufReader::new(bytes);
180 let private = SecretTlsVecU8::tls_deserialize(&mut buf)?;
181 let public = SecretTlsVecU8::tls_deserialize(&mut buf)?;
182 let scheme = SignatureScheme::tls_deserialize(&mut buf)?;
183 if scheme != SignatureScheme::ED25519 {
184 return Err(tls_codec::Error::DecodingError(
185 "XMTP InstallationCredential must be Ed25519".into(),
186 ));
187 }
188
189 Self::from_raw(private.as_slice(), public.as_slice())
190 .map_err(|e| tls_codec::Error::DecodingError(e.to_string()))
191 }
192}
193
194impl From<XmtpInstallationCredential> for SignatureKeyPair {
195 fn from(key: XmtpInstallationCredential) -> SignatureKeyPair {
196 SignatureKeyPair::from_raw(
197 key.signature_scheme(),
198 key.0.to_bytes().into(),
199 key.0.verifying_key().to_bytes().into(),
200 )
201 }
202}
203
204impl<'a> From<&'a XmtpInstallationCredential> for SignatureKeyPair {
205 fn from(key: &'a XmtpInstallationCredential) -> SignatureKeyPair {
206 SignatureKeyPair::from_raw(
207 key.signature_scheme(),
208 key.0.to_bytes().into(),
209 key.0.verifying_key().to_bytes().into(),
210 )
211 }
212}
213
214impl From<SigningKey> for XmtpInstallationCredential {
215 fn from(signing_key: SigningKey) -> Self {
216 Self(Box::new(signing_key))
217 }
218}
219
220impl<'a> From<&'a SigningKey> for XmtpInstallationCredential {
221 fn from(signing_key: &'a SigningKey) -> Self {
222 Self(Box::new(signing_key.clone()))
223 }
224}
225
226impl tls_codec::Serialize for XmtpInstallationCredential {
227 fn tls_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
228 SignatureKeyPair::from(self).tls_serialize(writer)
229 }
230}
231
232impl tls_codec::Size for XmtpInstallationCredential {
233 fn tls_serialized_len(&self) -> usize {
234 SignatureKeyPair::from(self).tls_serialized_len()
235 }
236}
237
238impl serde::Serialize for XmtpInstallationCredential {
239 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
240 where
241 S: serde::Serializer,
242 {
243 SignatureKeyPair::from(self).serialize(serializer)
244 }
245}
246
247impl<'de> serde::Deserialize<'de> for XmtpInstallationCredential {
248 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
249 where
250 D: serde::Deserializer<'de>,
251 {
252 #[derive(serde::Deserialize, zeroize::ZeroizeOnDrop)]
253 struct SignatureKeyPairRemote {
254 private: Vec<u8>,
255 public: Vec<u8>,
256 #[allow(dead_code)]
257 #[zeroize(skip)]
258 _signature_scheme: SignatureScheme,
259 }
260
261 let SignatureKeyPairRemote {
262 ref private,
263 ref public,
264 ..
265 } = SignatureKeyPairRemote::deserialize(deserializer)?;
266
267 Self::from_raw(private.as_slice(), public.as_slice())
268 .map_err(|e| <D as serde::Deserializer<'_>>::Error::custom(format!("{e}")))
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use tls_codec::{Deserialize as _, Serialize as _};
276
277 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
278 #[cfg_attr(not(target_arch = "wasm32"), test)]
279 fn test_is_binary_compatible_with_mls_deser() {
280 let keypair = SignatureKeyPair::new(SignatureScheme::ED25519).unwrap();
283 let mut serialized: Vec<u8> = Vec::new();
284
285 keypair.tls_serialize(&mut serialized).unwrap();
286 let x_kp = XmtpInstallationCredential::tls_deserialize(&mut serialized.as_slice()).unwrap();
287 assert_eq!(keypair.private(), &x_kp.0.to_bytes());
288 assert_eq!(keypair.public(), &x_kp.0.verifying_key().to_bytes());
289 }
290
291 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
292 #[cfg_attr(not(target_arch = "wasm32"), test)]
293 fn test_is_binary_compatible_with_mls_ser() {
294 let keypair = XmtpInstallationCredential::new();
295 let mut serialized: Vec<u8> = Vec::new();
296
297 keypair.tls_serialize(&mut serialized).unwrap();
298 let mls_kp = SignatureKeyPair::tls_deserialize(&mut serialized.as_slice()).unwrap();
299 assert_eq!(mls_kp.private(), &keypair.0.to_bytes());
300 assert_eq!(mls_kp.public(), &keypair.0.verifying_key().to_bytes());
301 assert_eq!(mls_kp.signature_scheme(), keypair.scheme());
302 }
303
304 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
305 #[cfg_attr(not(target_arch = "wasm32"), test)]
306 fn test_is_binary_compatible_with_mls_deser_serde() {
307 let keypair = SignatureKeyPair::new(SignatureScheme::ED25519).unwrap();
310 let serialized: Vec<u8> = bincode::serialize(&keypair).unwrap();
311
312 let x_kp: XmtpInstallationCredential = bincode::deserialize(serialized.as_slice()).unwrap();
313 assert_eq!(keypair.private(), &x_kp.0.to_bytes());
314 assert_eq!(keypair.public(), &x_kp.0.verifying_key().to_bytes());
315 }
316
317 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
318 #[cfg_attr(not(target_arch = "wasm32"), test)]
319 fn test_is_binary_compatible_with_mls_ser_serde() {
320 let keypair = XmtpInstallationCredential::new();
321 let serialized: Vec<u8> = bincode::serialize(&keypair).unwrap();
322
323 let mls_kp: SignatureKeyPair = bincode::deserialize(serialized.as_slice()).unwrap();
324 assert_eq!(mls_kp.private(), &keypair.0.to_bytes());
325 assert_eq!(mls_kp.public(), &keypair.0.verifying_key().to_bytes());
326 assert_eq!(mls_kp.signature_scheme(), keypair.scheme());
327 }
328
329 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
330 #[cfg_attr(not(target_arch = "wasm32"), test)]
331 fn secret_key_can_not_be_exposed() {
332 let keypair = XmtpInstallationCredential::new();
333 let secret = keypair.0.as_ref();
334
335 assert_ne!(keypair.public_bytes(), secret.as_bytes());
336 assert_ne!(keypair.public_slice(), secret.as_bytes());
337 assert_ne!(keypair.verifying_key().as_bytes(), &secret.to_bytes());
338 }
339}