Skip to main content

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