1use 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::new()
81 }
82}
83
84impl XmtpInstallationCredential {
85 pub fn new() -> Self {
87 let seed = Zeroizing::new(crate::rand::rand_array());
88 Self(Box::new(SigningKey::from_bytes(&seed)))
89 }
90
91 pub fn verifying_key(&self) -> ed25519_dalek::VerifyingKey {
94 self.0.verifying_key()
95 }
96
97 pub fn public_bytes(&self) -> &[u8; 32] {
99 self.0.as_ref().as_ref().as_bytes()
100 }
101
102 pub fn public_slice(&self) -> &[u8] {
104 self.0.as_ref().as_ref().as_ref()
105 }
106
107 fn scheme(&self) -> SignatureScheme {
109 SignatureScheme::ED25519
110 }
111
112 pub fn with_context<'k, 'v>(
113 &'k self,
114 context: &'v [u8],
115 ) -> Result<ed25519_dalek::Context<'k, 'v, SigningKey>, ed25519_dalek::SignatureError> {
116 self.0.with_context(context)
117 }
118
119 fn from_raw(private: &[u8], public: &[u8]) -> Result<Self, ed25519_dalek::SignatureError> {
122 const KEY_LENGTH: usize = 32;
123 if private.len() != KEY_LENGTH || public.len() != KEY_LENGTH {
124 return Err(ed25519_dalek::SignatureError::new());
125 }
126 let keypair = Zeroizing::new({
127 let mut keypair = [0u8; 64];
128 keypair[0..32].copy_from_slice(private);
129 keypair[32..].copy_from_slice(public);
130 keypair
131 });
132
133 let signing_key = SigningKey::from_keypair_bytes(&keypair)?;
134 Ok(Self(Box::new(signing_key)))
135 }
136
137 pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, ed25519_dalek::SignatureError> {
139 let key = SigningKey::from_bytes(bytes);
140 Ok(Self(Box::new(key)))
141 }
142
143 #[cfg(feature = "exposed-keys")]
145 pub fn private_bytes(&self) -> Zeroizing<[u8; 32]> {
146 Zeroizing::new(*self.0.as_bytes())
147 }
148}
149
150impl From<XmtpInstallationCredential> for SignaturePublicKey {
151 fn from(value: XmtpInstallationCredential) -> Self {
152 SignaturePublicKey::from(value.verifying_key().as_ref())
153 }
154}
155
156impl Signer for XmtpInstallationCredential {
159 fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, signatures::SignerError> {
160 Ok(ed25519_dalek::Signer::sign(&*self.0, payload)
161 .to_bytes()
162 .to_vec())
163 }
164
165 fn signature_scheme(&self) -> SignatureScheme {
166 self.scheme()
167 }
168}
169
170impl Signer for &XmtpInstallationCredential {
172 fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, signatures::SignerError> {
173 <XmtpInstallationCredential as Signer>::sign(self, payload)
174 }
175
176 fn signature_scheme(&self) -> SignatureScheme {
177 self.scheme()
178 }
179}
180
181impl tls_codec::Deserialize for XmtpInstallationCredential {
182 fn tls_deserialize<R: std::io::Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
183 where
184 Self: Sized,
185 {
186 let mut buf = BufReader::new(bytes);
188 let private = SecretTlsVecU8::tls_deserialize(&mut buf)?;
189 let public = SecretTlsVecU8::tls_deserialize(&mut buf)?;
190 let scheme = SignatureScheme::tls_deserialize(&mut buf)?;
191 if scheme != SignatureScheme::ED25519 {
192 return Err(tls_codec::Error::DecodingError(
193 "XMTP InstallationCredential must be Ed25519".into(),
194 ));
195 }
196
197 Self::from_raw(private.as_slice(), public.as_slice())
198 .map_err(|e| tls_codec::Error::DecodingError(e.to_string()))
199 }
200}
201
202impl From<XmtpInstallationCredential> for SignatureKeyPair {
203 fn from(key: XmtpInstallationCredential) -> SignatureKeyPair {
204 SignatureKeyPair::from_raw(
205 key.signature_scheme(),
206 key.0.as_bytes().to_vec(),
207 key.0.verifying_key().to_bytes().into(),
208 )
209 }
210}
211
212impl<'a> From<&'a XmtpInstallationCredential> for SignatureKeyPair {
213 fn from(key: &'a XmtpInstallationCredential) -> SignatureKeyPair {
214 SignatureKeyPair::from_raw(
215 key.signature_scheme(),
216 key.0.as_bytes().to_vec(),
217 key.0.verifying_key().to_bytes().into(),
218 )
219 }
220}
221
222impl From<SigningKey> for XmtpInstallationCredential {
223 fn from(signing_key: SigningKey) -> Self {
224 Self(Box::new(signing_key))
225 }
226}
227
228impl<'a> From<&'a SigningKey> for XmtpInstallationCredential {
229 fn from(signing_key: &'a SigningKey) -> Self {
230 Self(Box::new(signing_key.clone()))
231 }
232}
233
234impl tls_codec::Serialize for XmtpInstallationCredential {
235 fn tls_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
236 SignatureKeyPair::from(self).tls_serialize(writer)
237 }
238}
239
240impl tls_codec::Size for XmtpInstallationCredential {
241 fn tls_serialized_len(&self) -> usize {
242 SignatureKeyPair::from(self).tls_serialized_len()
243 }
244}
245
246impl serde::Serialize for XmtpInstallationCredential {
247 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
248 where
249 S: serde::Serializer,
250 {
251 SignatureKeyPair::from(self).serialize(serializer)
252 }
253}
254
255impl<'de> serde::Deserialize<'de> for XmtpInstallationCredential {
256 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
257 where
258 D: serde::Deserializer<'de>,
259 {
260 #[derive(serde::Deserialize, zeroize::ZeroizeOnDrop)]
265 struct SignatureKeyPairRemote {
266 #[serde(deserialize_with = "crate::secret::deserialize_seq")]
267 private: crate::Secret,
268 public: Vec<u8>,
269 #[allow(dead_code)]
270 #[zeroize(skip)]
271 #[serde(rename = "signature_scheme")]
274 _signature_scheme: SignatureScheme,
275 }
276
277 let SignatureKeyPairRemote {
278 ref private,
279 ref public,
280 ..
281 } = SignatureKeyPairRemote::deserialize(deserializer)?;
282
283 Self::from_raw(private.as_slice(), public.as_slice())
284 .map_err(|e| <D as serde::Deserializer<'_>>::Error::custom(format!("{e}")))
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use tls_codec::{Deserialize as _, Serialize as _};
292
293 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
294 #[cfg_attr(not(target_arch = "wasm32"), test)]
295 fn test_is_binary_compatible_with_mls_deser() {
296 let keypair = SignatureKeyPair::new(SignatureScheme::ED25519).unwrap();
299 let mut serialized: Vec<u8> = Vec::new();
300
301 keypair.tls_serialize(&mut serialized).unwrap();
302 let x_kp = XmtpInstallationCredential::tls_deserialize(&mut serialized.as_slice()).unwrap();
303 assert_eq!(keypair.private(), x_kp.0.as_bytes());
304 assert_eq!(keypair.public(), &x_kp.0.verifying_key().to_bytes());
305 }
306
307 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
308 #[cfg_attr(not(target_arch = "wasm32"), test)]
309 fn test_is_binary_compatible_with_mls_ser() {
310 let keypair = XmtpInstallationCredential::new();
311 let mut serialized: Vec<u8> = Vec::new();
312
313 keypair.tls_serialize(&mut serialized).unwrap();
314 let mls_kp = SignatureKeyPair::tls_deserialize(&mut serialized.as_slice()).unwrap();
315 assert_eq!(mls_kp.private(), keypair.0.as_bytes());
316 assert_eq!(mls_kp.public(), &keypair.0.verifying_key().to_bytes());
317 assert_eq!(mls_kp.signature_scheme(), keypair.scheme());
318 }
319
320 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
323 #[cfg_attr(not(target_arch = "wasm32"), test)]
324 fn test_is_binary_compatible_with_mls_deser_serde() {
325 let keypair = SignatureKeyPair::new(SignatureScheme::ED25519).unwrap();
328 let serialized: Vec<u8> = cbor4ii::serde::to_vec(Vec::new(), &keypair).unwrap();
329
330 let x_kp: XmtpInstallationCredential =
331 cbor4ii::serde::from_slice(serialized.as_slice()).unwrap();
332 assert_eq!(keypair.private(), x_kp.0.as_bytes());
333 assert_eq!(keypair.public(), &x_kp.0.verifying_key().to_bytes());
334 }
335
336 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
337 #[cfg_attr(not(target_arch = "wasm32"), test)]
338 fn test_is_binary_compatible_with_mls_ser_serde() {
339 let keypair = XmtpInstallationCredential::new();
340 let serialized: Vec<u8> = cbor4ii::serde::to_vec(Vec::new(), &keypair).unwrap();
341
342 let mls_kp: SignatureKeyPair = cbor4ii::serde::from_slice(serialized.as_slice()).unwrap();
343 assert_eq!(mls_kp.private(), keypair.0.as_bytes());
344 assert_eq!(mls_kp.public(), &keypair.0.verifying_key().to_bytes());
345 assert_eq!(mls_kp.signature_scheme(), keypair.scheme());
346 }
347
348 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
351 #[cfg_attr(not(target_arch = "wasm32"), test)]
352 fn wrong_key_lengths_are_decode_errors() {
353 for (private, public) in [
354 (vec![1; 31], vec![2; 32]),
355 (vec![1; 33], vec![2; 32]),
356 (vec![1; 32], vec![2; 31]),
357 (vec![1; 32], vec![2; 33]),
358 ] {
359 let keypair = SignatureKeyPair::from_raw(SignatureScheme::ED25519, private, public);
360 let bytes = cbor4ii::serde::to_vec(Vec::new(), &keypair).unwrap();
361 assert!(cbor4ii::serde::from_slice::<XmtpInstallationCredential>(&bytes).is_err());
362 }
363 }
364
365 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
366 #[cfg_attr(not(target_arch = "wasm32"), test)]
367 fn signs_like_signature_key_pair() {
368 let payloads: [&[u8]; 3] = [b"", b"an mls payload", &[0xa5; 1024]];
369 for seed in [[0x01u8; 32], [0x7f; 32], [0xc3; 32]] {
370 let credential = XmtpInstallationCredential::from_bytes(&seed).unwrap();
371 let key_pair = SignatureKeyPair::from(&credential);
372 let owned = SignatureKeyPair::from(credential.clone());
373 for pair in [&key_pair, &owned] {
374 assert_eq!(pair.private(), seed);
375 assert_eq!(pair.public(), credential.public_slice());
376 }
377 for payload in payloads {
378 let expected = key_pair.sign(payload).unwrap();
379 assert_eq!(Signer::sign(&credential, payload).unwrap(), expected);
380 assert_eq!(Signer::sign(&&credential, payload).unwrap(), expected);
381 }
382 }
383 }
384
385 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
386 #[cfg_attr(not(target_arch = "wasm32"), test)]
387 fn secret_key_can_not_be_exposed() {
388 let keypair = XmtpInstallationCredential::new();
389 let secret = keypair.0.as_ref();
390
391 assert_ne!(keypair.public_bytes(), secret.as_bytes());
392 assert_ne!(keypair.public_slice(), secret.as_bytes());
393 assert_ne!(keypair.verifying_key().as_bytes(), secret.as_bytes());
394 }
395}