radicle_crypto/
lib.rs

1use std::cmp::Ordering;
2use std::sync::Arc;
3use std::{fmt, ops::Deref, str::FromStr};
4
5use ec25519 as ed25519;
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9pub use ed25519::{edwards25519, Error, KeyPair, Seed};
10
11#[cfg(feature = "ssh")]
12pub mod ssh;
13#[cfg(any(test, feature = "test"))]
14pub mod test;
15
16/// Verified (used as type witness).
17#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)]
18pub struct Verified;
19/// Unverified (used as type witness).
20#[derive(Debug, Copy, Clone, PartialEq, Eq)]
21pub struct Unverified;
22
23/// Output of a Diffie-Hellman key exchange.
24pub type SharedSecret = [u8; 32];
25
26/// Error returned if signing fails, eg. due to an HSM or KMS.
27#[derive(Debug, Clone, Error)]
28#[error(transparent)]
29pub struct SignerError {
30    #[from]
31    source: Arc<dyn std::error::Error + Send + Sync>,
32}
33
34impl SignerError {
35    pub fn new(source: impl std::error::Error + Send + Sync + 'static) -> Self {
36        Self {
37            source: Arc::new(source),
38        }
39    }
40}
41
42pub trait Signer: Send + Sync {
43    /// Return this signer's public/verification key.
44    fn public_key(&self) -> &PublicKey;
45    /// Sign a message and return the signature.
46    fn sign(&self, msg: &[u8]) -> Signature;
47    /// Sign a message and return the signature, or fail if the signer was unable
48    /// to produce a signature.
49    fn try_sign(&self, msg: &[u8]) -> Result<Signature, SignerError>;
50}
51
52impl<T> Signer for Box<T>
53where
54    T: Signer + ?Sized,
55{
56    fn public_key(&self) -> &PublicKey {
57        self.deref().public_key()
58    }
59
60    fn sign(&self, msg: &[u8]) -> Signature {
61        self.deref().sign(msg)
62    }
63
64    fn try_sign(&self, msg: &[u8]) -> Result<Signature, SignerError> {
65        self.deref().try_sign(msg)
66    }
67}
68
69/// Cryptographic signature.
70#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
71#[serde(into = "String", try_from = "String")]
72pub struct Signature(pub ed25519::Signature);
73
74impl AsRef<[u8]> for Signature {
75    fn as_ref(&self) -> &[u8] {
76        self.0.as_ref()
77    }
78}
79
80impl fmt::Display for Signature {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        let base = multibase::Base::Base58Btc;
83        write!(f, "{}", multibase::encode(base, self.deref()))
84    }
85}
86
87impl fmt::Debug for Signature {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        write!(f, "Signature({self})")
90    }
91}
92
93#[derive(Error, Debug)]
94pub enum SignatureError {
95    #[error("invalid multibase string: {0}")]
96    Multibase(#[from] multibase::Error),
97    #[error("invalid signature: {0}")]
98    Invalid(#[from] ed25519::Error),
99}
100
101impl From<ed25519::Signature> for Signature {
102    fn from(other: ed25519::Signature) -> Self {
103        Self(other)
104    }
105}
106
107impl FromStr for Signature {
108    type Err = SignatureError;
109
110    fn from_str(s: &str) -> Result<Self, Self::Err> {
111        let (_, bytes) = multibase::decode(s)?;
112        let sig = ed25519::Signature::from_slice(bytes.as_slice())?;
113
114        Ok(Self(sig))
115    }
116}
117
118impl Deref for Signature {
119    type Target = ed25519::Signature;
120
121    fn deref(&self) -> &Self::Target {
122        &self.0
123    }
124}
125
126impl From<[u8; 64]> for Signature {
127    fn from(bytes: [u8; 64]) -> Self {
128        Self(ed25519::Signature::new(bytes))
129    }
130}
131
132impl TryFrom<&[u8]> for Signature {
133    type Error = ed25519::Error;
134
135    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
136        ed25519::Signature::from_slice(bytes).map(Self)
137    }
138}
139
140impl From<Signature> for String {
141    fn from(s: Signature) -> Self {
142        s.to_string()
143    }
144}
145
146impl TryFrom<String> for Signature {
147    type Error = SignatureError;
148
149    fn try_from(s: String) -> Result<Self, Self::Error> {
150        Self::from_str(&s)
151    }
152}
153
154/// The public/verification key.
155#[derive(Hash, Serialize, Deserialize, PartialEq, Eq, Copy, Clone)]
156#[serde(into = "String", try_from = "String")]
157pub struct PublicKey(pub ed25519::PublicKey);
158
159#[cfg(feature = "cyphernet")]
160impl cyphernet::display::MultiDisplay<cyphernet::display::Encoding> for PublicKey {
161    type Display = String;
162
163    fn display_fmt(&self, _: &cyphernet::display::Encoding) -> Self::Display {
164        self.to_string()
165    }
166}
167
168#[cfg(feature = "ssh")]
169impl From<PublicKey> for ssh_key::PublicKey {
170    fn from(key: PublicKey) -> Self {
171        ssh_key::PublicKey::from(ssh_key::public::Ed25519PublicKey(**key))
172    }
173}
174
175#[cfg(feature = "cyphernet")]
176impl cyphernet::EcPk for PublicKey {
177    const COMPRESSED_LEN: usize = 32;
178    const CURVE_NAME: &'static str = "Edwards25519";
179
180    type Compressed = [u8; 32];
181
182    fn base_point() -> Self {
183        unimplemented!()
184    }
185
186    fn to_pk_compressed(&self) -> Self::Compressed {
187        *self.0.deref()
188    }
189
190    fn from_pk_compressed(pk: Self::Compressed) -> Result<Self, cyphernet::EcPkInvalid> {
191        Ok(PublicKey::from(pk))
192    }
193
194    fn from_pk_compressed_slice(slice: &[u8]) -> Result<Self, cyphernet::EcPkInvalid> {
195        ed25519::PublicKey::from_slice(slice)
196            .map_err(|_| cyphernet::EcPkInvalid::default())
197            .map(Self)
198    }
199}
200
201/// The private/signing key.
202#[derive(Clone, Debug, Eq, PartialEq, Hash)]
203pub struct SecretKey(ed25519::SecretKey);
204
205impl SecretKey {
206    /// Elliptic-curve Diffie-Hellman.
207    pub fn ecdh(&self, pk: &PublicKey) -> Result<[u8; 32], ed25519::Error> {
208        let scalar = self.seed().scalar();
209        let ge = edwards25519::GeP3::from_bytes_vartime(pk).ok_or(Error::InvalidPublicKey)?;
210
211        Ok(edwards25519::ge_scalarmult(&scalar, &ge).to_bytes())
212    }
213}
214
215impl PartialOrd for SecretKey {
216    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
217        Some(self.cmp(other))
218    }
219}
220
221impl Ord for SecretKey {
222    fn cmp(&self, other: &Self) -> Ordering {
223        self.0.cmp(&other.0)
224    }
225}
226
227impl zeroize::Zeroize for SecretKey {
228    fn zeroize(&mut self) {
229        self.0.zeroize();
230    }
231}
232
233impl TryFrom<&[u8]> for SecretKey {
234    type Error = ed25519::Error;
235
236    fn try_from(bytes: &[u8]) -> Result<Self, ed25519::Error> {
237        ed25519::SecretKey::from_slice(bytes).map(Self)
238    }
239}
240
241impl AsRef<[u8]> for SecretKey {
242    fn as_ref(&self) -> &[u8] {
243        &*self.0
244    }
245}
246
247impl From<[u8; 64]> for SecretKey {
248    fn from(bytes: [u8; 64]) -> Self {
249        Self(ed25519::SecretKey::new(bytes))
250    }
251}
252
253impl From<ed25519::SecretKey> for SecretKey {
254    fn from(other: ed25519::SecretKey) -> Self {
255        Self(other)
256    }
257}
258
259impl From<SecretKey> for ed25519::SecretKey {
260    fn from(other: SecretKey) -> Self {
261        other.0
262    }
263}
264
265impl Deref for SecretKey {
266    type Target = ed25519::SecretKey;
267
268    fn deref(&self) -> &Self::Target {
269        &self.0
270    }
271}
272
273#[derive(Error, Debug)]
274pub enum PublicKeyError {
275    #[error("invalid length {0}")]
276    InvalidLength(usize),
277    #[error("invalid multibase string: {0}")]
278    Multibase(#[from] multibase::Error),
279    #[error("invalid multicodec prefix, expected {0:?}")]
280    Multicodec([u8; 2]),
281    #[error("invalid key: {0}")]
282    InvalidKey(#[from] ed25519::Error),
283}
284
285impl PartialOrd for PublicKey {
286    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
287        Some(self.cmp(other))
288    }
289}
290
291impl Ord for PublicKey {
292    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
293        self.0.as_ref().cmp(other.as_ref())
294    }
295}
296
297impl fmt::Display for PublicKey {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        write!(f, "{}", self.to_human())
300    }
301}
302
303impl From<PublicKey> for String {
304    fn from(other: PublicKey) -> Self {
305        other.to_human()
306    }
307}
308
309impl fmt::Debug for PublicKey {
310    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311        write!(f, "PublicKey({self})")
312    }
313}
314
315impl From<ed25519::PublicKey> for PublicKey {
316    fn from(other: ed25519::PublicKey) -> Self {
317        Self(other)
318    }
319}
320
321impl From<[u8; 32]> for PublicKey {
322    fn from(other: [u8; 32]) -> Self {
323        Self(ed25519::PublicKey::new(other))
324    }
325}
326
327impl TryFrom<&[u8]> for PublicKey {
328    type Error = ed25519::Error;
329
330    fn try_from(other: &[u8]) -> Result<Self, Self::Error> {
331        ed25519::PublicKey::from_slice(other).map(Self)
332    }
333}
334
335impl PublicKey {
336    /// Multicodec key type for Ed25519 keys.
337    pub const MULTICODEC_TYPE: [u8; 2] = [0xED, 0x1];
338
339    /// Encode public key in human-readable format.
340    ///
341    /// `MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))`
342    ///
343    pub fn to_human(&self) -> String {
344        let mut buf = [0; 2 + ed25519::PublicKey::BYTES];
345        buf[..2].copy_from_slice(&Self::MULTICODEC_TYPE);
346        buf[2..].copy_from_slice(self.0.deref());
347
348        multibase::encode(multibase::Base::Base58Btc, buf)
349    }
350
351    #[cfg(feature = "radicle-git-ext")]
352    pub fn to_namespace(&self) -> radicle_git_ext::ref_format::RefString {
353        use radicle_git_ext::ref_format::{refname, Component};
354        refname!("refs/namespaces").join(Component::from(self))
355    }
356
357    #[cfg(feature = "radicle-git-ext")]
358    pub fn to_component(&self) -> radicle_git_ext::ref_format::Component {
359        radicle_git_ext::ref_format::Component::from(self)
360    }
361
362    #[cfg(feature = "radicle-git-ext")]
363    pub fn from_namespaced(
364        refstr: &radicle_git_ext::ref_format::Namespaced,
365    ) -> Result<Self, PublicKeyError> {
366        let name = refstr.namespace().into_inner();
367
368        Self::from_str(name.deref().as_str())
369    }
370}
371
372impl FromStr for PublicKey {
373    type Err = PublicKeyError;
374
375    fn from_str(s: &str) -> Result<Self, Self::Err> {
376        let (_, bytes) = multibase::decode(s)?;
377
378        if let Some(bytes) = bytes.strip_prefix(&Self::MULTICODEC_TYPE) {
379            let key = ed25519::PublicKey::from_slice(bytes)?;
380
381            Ok(Self(key))
382        } else {
383            Err(PublicKeyError::Multicodec(Self::MULTICODEC_TYPE))
384        }
385    }
386}
387
388impl TryFrom<String> for PublicKey {
389    type Error = PublicKeyError;
390
391    fn try_from(value: String) -> Result<Self, Self::Error> {
392        Self::from_str(&value)
393    }
394}
395
396impl Deref for PublicKey {
397    type Target = ed25519::PublicKey;
398
399    fn deref(&self) -> &Self::Target {
400        &self.0
401    }
402}
403
404#[cfg(feature = "radicle-git-ext")]
405impl<'a> From<&PublicKey> for radicle_git_ext::ref_format::Component<'a> {
406    fn from(id: &PublicKey) -> Self {
407        use radicle_git_ext::ref_format::{Component, RefString};
408        let refstr =
409            RefString::try_from(id.to_string()).expect("encoded public keys are valid ref strings");
410        Component::from_refstr(refstr).expect("encoded public keys are valid refname components")
411    }
412}
413
414#[cfg(feature = "sqlite")]
415impl From<&PublicKey> for sqlite::Value {
416    fn from(pk: &PublicKey) -> Self {
417        sqlite::Value::String(pk.to_human())
418    }
419}
420
421#[cfg(feature = "sqlite")]
422impl TryFrom<&sqlite::Value> for PublicKey {
423    type Error = sqlite::Error;
424
425    fn try_from(value: &sqlite::Value) -> Result<Self, Self::Error> {
426        match value {
427            sqlite::Value::String(s) => Self::from_str(s).map_err(|e| sqlite::Error {
428                code: None,
429                message: Some(e.to_string()),
430            }),
431            _ => Err(sqlite::Error {
432                code: None,
433                message: Some("sql: invalid type for public key".to_owned()),
434            }),
435        }
436    }
437}
438
439#[cfg(feature = "sqlite")]
440impl sqlite::BindableWithIndex for &PublicKey {
441    fn bind<I: sqlite::ParameterIndex>(
442        self,
443        stmt: &mut sqlite::Statement<'_>,
444        i: I,
445    ) -> sqlite::Result<()> {
446        sqlite::Value::from(self).bind(stmt, i)
447    }
448}
449
450#[cfg(feature = "sqlite")]
451impl From<&Signature> for sqlite::Value {
452    fn from(sig: &Signature) -> Self {
453        sqlite::Value::Binary(sig.to_vec())
454    }
455}
456
457#[cfg(feature = "sqlite")]
458impl TryFrom<&sqlite::Value> for Signature {
459    type Error = sqlite::Error;
460
461    fn try_from(value: &sqlite::Value) -> Result<Self, Self::Error> {
462        match value {
463            sqlite::Value::Binary(s) => ed25519::Signature::from_slice(s)
464                .map_err(|e| sqlite::Error {
465                    code: None,
466                    message: Some(e.to_string()),
467                })
468                .map(Self),
469            _ => Err(sqlite::Error {
470                code: None,
471                message: Some("sql: invalid column type for signature".to_owned()),
472            }),
473        }
474    }
475}
476
477#[cfg(feature = "sqlite")]
478impl sqlite::BindableWithIndex for &Signature {
479    fn bind<I: sqlite::ParameterIndex>(
480        self,
481        stmt: &mut sqlite::Statement<'_>,
482        i: I,
483    ) -> sqlite::Result<()> {
484        sqlite::Value::from(self).bind(stmt, i)
485    }
486}
487
488#[cfg(test)]
489mod tests {
490    use super::KeyPair;
491    use crate::{PublicKey, SecretKey};
492    use qcheck_macros::quickcheck;
493    use std::str::FromStr;
494
495    #[test]
496    fn test_e25519_dh() {
497        let kp_a = KeyPair::generate();
498        let kp_b = KeyPair::generate();
499
500        let output_a = SecretKey::from(kp_b.sk).ecdh(&kp_a.pk.into()).unwrap();
501        let output_b = SecretKey::from(kp_a.sk).ecdh(&kp_b.pk.into()).unwrap();
502
503        assert_eq!(output_a, output_b);
504    }
505
506    #[quickcheck]
507    fn prop_encode_decode(input: PublicKey) {
508        let encoded = input.to_string();
509        let decoded = PublicKey::from_str(&encoded).unwrap();
510
511        assert_eq!(input, decoded);
512    }
513
514    #[test]
515    fn test_encode_decode() {
516        let input = "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
517        let key = PublicKey::from_str(input).unwrap();
518
519        assert_eq!(key.to_string(), input);
520    }
521
522    #[quickcheck]
523    fn prop_key_equality(a: PublicKey, b: PublicKey) {
524        use std::collections::HashSet;
525
526        assert_ne!(a, b);
527
528        let mut hm = HashSet::new();
529
530        assert!(hm.insert(a));
531        assert!(hm.insert(b));
532        assert!(!hm.insert(a));
533        assert!(!hm.insert(b));
534    }
535}