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