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