Skip to main content

radicle_crypto/
lib.rs

1#![no_std]
2
3#[cfg(any(test, feature = "alloc"))]
4extern crate alloc;
5
6#[cfg(any(test, feature = "alloc"))]
7#[allow(unused_imports)]
8use alloc::{
9    string::{String, ToString as _},
10    vec::Vec,
11};
12
13#[cfg(feature = "std")]
14extern crate std;
15
16/// References to dalek cryptography crates (see <https://dalek.rs/>)
17/// that this crate depends on. Since both are related to Curve25519
18/// in some way, the "25519" suffix is omitted from the name of the re-export.
19mod dalek {
20    pub(crate) extern crate curve25519_dalek as curve;
21    pub(crate) extern crate ed25519_dalek as ed;
22}
23
24/// Re-exports of the `signature` crate and `ed25519::Signature`
25/// as re-exported by the `ed25519_dalek` crate.
26pub use dalek::ed::ed25519::{Signature, signature};
27
28#[cfg(all(feature = "ssh", feature = "alloc"))]
29pub mod ssh;
30
31mod seed;
32pub use seed::Seed;
33
34/// Output of a Diffie-Hellman key exchange.
35pub type SharedSecret = [u8; 32];
36
37/// A super-trait that requires:
38///   - [`signature::Signer`] to produce the exported [`Signature`] type,
39///   - [`signature::Keypair`] where the associated
40///     [`signature::Keypair::VerifyingKey`] is the
41///     [`VerifyingKey`] defined in this crate, and
42///   - [`AsRef<PublicKey>`] to obtain a reference to the corresponding
43///     [`PublicKey`].
44///
45/// A blanket implementation is provided for all types that satisfy the trait
46/// bounds.
47pub trait Signer
48where
49    Self: signature::Signer<Signature>,
50    Self: signature::Keypair<VerifyingKey = VerifyingKey>,
51    Self: AsRef<PublicKey>,
52{
53    /// Return a reference to the [`PublicKey`].
54    ///
55    /// This is generally satisfied by the [`AsRef<PublicKey>`] instance.
56    fn public_key(&self) -> &PublicKey {
57        self.as_ref()
58    }
59}
60
61impl<T: ?Sized> Signer for T
62where
63    Self: signature::Signer<Signature>,
64    Self: signature::Keypair<VerifyingKey = VerifyingKey>,
65    Self: AsRef<PublicKey>,
66{
67}
68
69/// This module contains compile-time checks to ensure the following:
70///  1. [`Signer`] is compatible with `dyn` usage.
71///  2. [`SigningKey`] and other well-known implementations of signers
72///     implement the trait.
73///
74/// As long as this module compiles, we have reasonable confidence that we
75/// can generalize to `dyn` in the future without breaking existing code.
76///
77/// Note that this module is "dead code" in the sense that it serves no
78/// purpose at runtime, but it is useful at compile-time!
79#[allow(dead_code)]
80mod future {
81    use super::*;
82
83    /// Witnesses that [`Signer`] is `dyn`-compatible.
84    const fn r#dyn(_: &dyn Signer) {}
85
86    /// Witnesses that the generic argument implements [`Signer`].
87    const fn r#impl<Witness: Signer>() {}
88
89    /// Witnesses that [`SigningKey`] implements [`Signer`].
90    const IMPL_SECRET_KEY: () = r#impl::<SigningKey>();
91
92    /// Witnesses that [`ssh::agent::AgentSigner`] implements [`Signer`].
93    #[cfg(all(feature = "ssh", feature = "std"))]
94    const IMPL_AGENT_SIGNER: () = r#impl::<ssh::agent::AgentSigner>();
95}
96
97/// Multicodec key type for Ed25519 keys.
98#[cfg(feature = "multibase")]
99pub const MULTICODEC_TYPE: [u8; 2] = [0xED, 0x01];
100
101pub type PublicKeyBytes = [u8; dalek::ed::PUBLIC_KEY_LENGTH];
102
103/// Bytes that are intended/thought to correspond to a point on the Edwards25519
104/// curve (but not on its twist).
105///
106/// This is more compact than [`VerifyingKey`] in memory, and easier to handle,
107/// but it is not guaranteed to be a valid point on the curve, so cannot be used
108/// for actual cryptographic operations such as signature verification or
109/// Diffie-Hellman key exchange.
110#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug, PartialOrd, Ord)]
111#[cfg_attr(
112    all(feature = "serde", feature = "alloc", feature = "multibase"),
113    derive(serde::Serialize, serde::Deserialize),
114    serde(into = "String", try_from = "String")
115)]
116#[cfg_attr(
117    all(feature = "schemars", feature = "serde", feature = "alloc", feature = "multibase"),
118    derive(schemars::JsonSchema),
119    schemars(
120        title = "Ed25519",
121        description = "An Ed25519 public key in multibase encoding.",
122        extend("examples" = [
123            "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7",
124            "z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C",
125            "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
126            "z6MkkfM3tPXNPrPevKr3uSiQtHPuwnNhu2yUVjgd2jXVsVz5",
127        ]),
128    ),
129)]
130#[repr(transparent)]
131pub struct PublicKey(PublicKeyBytes);
132
133impl PublicKey {
134    pub const fn from_bytes(bytes: PublicKeyBytes) -> Self {
135        Self(bytes)
136    }
137
138    pub fn into_inner(self) -> PublicKeyBytes {
139        self.0
140    }
141}
142
143impl<'a> From<&'a PublicKeyBytes> for &'a PublicKey {
144    fn from(other: &'a PublicKeyBytes) -> Self {
145        let ptr = std::ptr::from_ref(other).cast::<PublicKey>();
146        // SAFETY: `PublicKey` is `#[repr(transparent)]` over the same array type,
147        // so the cast preserves layout and alignment, and every byte pattern is valid.
148        unsafe { &*ptr }
149    }
150}
151
152impl From<PublicKeyBytes> for PublicKey {
153    fn from(bytes: PublicKeyBytes) -> Self {
154        Self(bytes)
155    }
156}
157
158#[cfg(feature = "alloc")]
159impl alloc::borrow::Borrow<PublicKeyBytes> for PublicKey {
160    fn borrow(&self) -> &PublicKeyBytes {
161        &self.0
162    }
163}
164
165#[cfg(all(feature = "alloc", feature = "multibase"))]
166impl alloc::fmt::Display for PublicKey {
167    fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
168        write!(f, "{}", self.to_human())
169    }
170}
171
172#[cfg(feature = "ssh")]
173impl From<PublicKey> for ssh_key::public::Ed25519PublicKey {
174    fn from(key: PublicKey) -> Self {
175        ssh_key::public::Ed25519PublicKey(key.0)
176    }
177}
178
179#[cfg(feature = "ssh")]
180impl From<ssh_key::public::Ed25519PublicKey> for PublicKey {
181    fn from(key: ssh_key::public::Ed25519PublicKey) -> Self {
182        Self(key.0)
183    }
184}
185
186#[cfg(all(feature = "alloc", feature = "multibase"))]
187#[derive(thiserror::Error, Debug)]
188#[non_exhaustive]
189pub enum PublicKeyError {
190    #[error("invalid length {0}")]
191    InvalidLength(usize),
192    #[error("invalid multibase string: {0}")]
193    Multibase(#[cfg_attr(feature = "std", source)] multibase::Error),
194    #[error("invalid multicodec prefix, expected {0:?}")]
195    Multicodec([u8; 2]),
196    #[error("invalid public key")]
197    Invalid(#[cfg_attr(feature = "std", source)] signature::Error),
198}
199
200#[cfg(all(feature = "alloc", feature = "multibase"))]
201impl From<PublicKey> for String {
202    fn from(other: PublicKey) -> Self {
203        other.to_human()
204    }
205}
206
207impl PublicKey {
208    /// Encode public key in human-readable format.
209    ///
210    /// `MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))`
211    ///
212    #[cfg(all(feature = "alloc", feature = "multibase"))]
213    pub fn to_human(&self) -> String {
214        let mut buf = [0; 2 + dalek::ed::PUBLIC_KEY_LENGTH];
215        buf[..2].copy_from_slice(&MULTICODEC_TYPE);
216        buf[2..].copy_from_slice(&self.0);
217
218        multibase::encode(multibase::Base::Base58Btc, buf)
219    }
220
221    /// Encode the public key to a Git reference string:
222    ///
223    /// `refs/namespaces/<public-key>`
224    ///
225    /// and `<public-key>` is encoded in human-readable format
226    /// ([`PublicKey::to_human`]).
227    #[cfg(all(
228        feature = "git-ref-format-core",
229        feature = "alloc",
230        feature = "multibase"
231    ))]
232    pub fn to_namespace(&self) -> git_ref_format_core::RefString {
233        use alloc::borrow::ToOwned as _;
234        use git_ref_format_core::name::{NAMESPACES, REFS};
235        REFS.to_owned().and(NAMESPACES).and(self.to_component())
236    }
237
238    /// Encode the public key a Git reference component, which is equivalent to
239    /// the human-readable format ([`PublicKey::to_human`]).
240    #[cfg(all(
241        feature = "git-ref-format-core",
242        feature = "alloc",
243        feature = "multibase"
244    ))]
245    pub fn to_component(&self) -> git_ref_format_core::Component<'_> {
246        git_ref_format_core::Component::from(self)
247    }
248
249    /// Decode a [`PublicKey`] from a namespaced Git reference, expected to be
250    /// in the format:
251    ///
252    /// `refs/namespaces/<public-key>/…`
253    ///
254    /// The `<public-key>` is decoded from the human-readable format
255    /// ([`PublicKey::to_human`]).
256    #[cfg(all(
257        feature = "git-ref-format-core",
258        feature = "alloc",
259        feature = "multibase"
260    ))]
261    pub fn from_namespaced(
262        refstr: &git_ref_format_core::Namespaced,
263    ) -> Result<Self, PublicKeyError> {
264        use alloc::str::FromStr as _;
265
266        let name = refstr.namespace().into_inner();
267        Self::from_str(name.as_str())
268    }
269}
270
271#[cfg(all(feature = "alloc", feature = "multibase"))]
272impl alloc::str::FromStr for PublicKey {
273    type Err = PublicKeyError;
274
275    fn from_str(s: &str) -> Result<Self, Self::Err> {
276        let (_, bytes) = multibase::decode(s).map_err(PublicKeyError::Multibase)?;
277
278        if bytes.len() < 2 {
279            return Err(PublicKeyError::InvalidLength(bytes.len()));
280        }
281
282        if bytes[..MULTICODEC_TYPE.len()] != MULTICODEC_TYPE {
283            return Err(PublicKeyError::Multicodec(MULTICODEC_TYPE));
284        }
285
286        Ok(PublicKey(
287            bytes[MULTICODEC_TYPE.len()..]
288                .try_into()
289                .map_err(|_| PublicKeyError::InvalidLength(bytes.len()))?,
290        ))
291    }
292}
293
294#[cfg(all(
295    feature = "git-ref-format-core",
296    feature = "alloc",
297    feature = "multibase"
298))]
299impl From<&PublicKey> for git_ref_format_core::Component<'_> {
300    fn from(id: &PublicKey) -> Self {
301        use git_ref_format_core::{Component, RefString};
302        let refstr =
303            RefString::try_from(id.to_string()).expect("encoded public keys are valid ref strings");
304        Component::from_refstr(refstr).expect("encoded public keys are valid refname components")
305    }
306}
307
308#[cfg(all(feature = "sqlite", feature = "alloc", feature = "multibase"))]
309impl TryFrom<&sqlite::Value> for PublicKey {
310    type Error = sqlite::Error;
311
312    fn try_from(value: &sqlite::Value) -> Result<Self, Self::Error> {
313        use alloc::str::FromStr as _;
314
315        match value {
316            sqlite::Value::String(s) => Self::from_str(s).map_err(|e| sqlite::Error {
317                code: None,
318                message: Some(e.to_string()),
319            }),
320            _ => Err(sqlite::Error {
321                code: None,
322                message: Some(String::from("sql: invalid type for public key")),
323            }),
324        }
325    }
326}
327
328#[cfg(all(feature = "sqlite", feature = "alloc", feature = "multibase"))]
329impl sqlite::BindableWithIndex for &PublicKey {
330    fn bind<I: sqlite::ParameterIndex>(
331        self,
332        stmt: &mut sqlite::Statement<'_>,
333        i: I,
334    ) -> sqlite::Result<()> {
335        sqlite::Value::from(self).bind(stmt, i)
336    }
337}
338
339#[cfg(all(feature = "sqlite", feature = "alloc", feature = "multibase"))]
340impl From<&PublicKey> for sqlite::Value {
341    fn from(pk: &PublicKey) -> Self {
342        sqlite::Value::String(pk.to_human())
343    }
344}
345
346#[cfg(feature = "cyphernet")]
347impl AsRef<[u8]> for PublicKey {
348    fn as_ref(&self) -> &[u8] {
349        &self.0
350    }
351}
352
353#[cfg(all(feature = "cyphernet", feature = "alloc", feature = "multibase"))]
354impl cyphernet::display::MultiDisplay<cyphernet::display::Encoding> for PublicKey {
355    type Display = String;
356
357    fn display_fmt(&self, encoding: &cyphernet::display::Encoding) -> Self::Display {
358        match encoding {
359            cyphernet::display::Encoding::Base58
360            | cyphernet::display::Encoding::Multibase(multibase::Base::Base58Btc) => {
361                self.to_string()
362            }
363            _ => unimplemented!(),
364        }
365    }
366}
367
368#[cfg(all(feature = "cyphernet", feature = "alloc"))]
369impl cyphernet::EcPk for PublicKey {
370    const COMPRESSED_LEN: usize = dalek::ed::PUBLIC_KEY_LENGTH;
371    const CURVE_NAME: &'static str = "Edwards25519";
372
373    type Compressed = PublicKey;
374
375    fn base_point() -> Self {
376        unimplemented!()
377    }
378
379    fn to_pk_compressed(&self) -> Self::Compressed {
380        *self
381    }
382
383    fn from_pk_compressed(pk: Self::Compressed) -> Result<Self, cyphernet::EcPkInvalid> {
384        Ok(pk)
385    }
386
387    fn from_pk_compressed_slice(pk: &[u8]) -> Result<Self, cyphernet::EcPkInvalid> {
388        Ok(PublicKey(
389            PublicKeyBytes::try_from(pk).map_err(|_| cyphernet::EcPkInvalid::default())?,
390        ))
391    }
392}
393
394/// A (decompressed) point on the Edwards25519 curve (but not on its twist) that
395/// may be used to verify signatures.
396///
397/// It is not as compact as a [`PublicKey`] in memory and requires more costly
398/// verification/initialization, but directly corresponds to one.
399#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
400pub struct VerifyingKey(dalek::ed::VerifyingKey);
401
402impl VerifyingKey {
403    #[allow(clippy::wrong_self_convention)] // Name copied from dalek.
404    #[inline]
405    #[cfg(any(feature = "diffie-hellman", feature = "ssh"))]
406    pub(crate) fn to_bytes(&self) -> PublicKeyBytes {
407        self.0.to_bytes()
408    }
409}
410
411impl TryFrom<&PublicKey> for VerifyingKey {
412    type Error = signature::Error;
413
414    fn try_from(key: &PublicKey) -> Result<Self, Self::Error> {
415        dalek::ed::VerifyingKey::from_bytes(&key.0).map(Self)
416    }
417}
418
419impl<'a> VerifyingKey {
420    pub fn public_key(&'a self) -> &'a PublicKey {
421        self.0.as_bytes().into()
422    }
423}
424
425impl AsRef<PublicKeyBytes> for VerifyingKey {
426    fn as_ref(&self) -> &PublicKeyBytes {
427        self.0.as_bytes()
428    }
429}
430
431impl From<dalek::ed::VerifyingKey> for VerifyingKey {
432    fn from(other: dalek::ed::VerifyingKey) -> Self {
433        Self(other)
434    }
435}
436
437impl signature::Verifier<Signature> for VerifyingKey {
438    fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> {
439        self.0.verify(msg, signature)
440    }
441}
442
443#[cfg(all(feature = "alloc", feature = "multibase"))]
444impl alloc::fmt::Display for VerifyingKey {
445    fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
446        self.public_key().fmt(f)
447    }
448}
449
450#[cfg(all(feature = "cyphernet", feature = "alloc", feature = "multibase"))]
451impl cyphernet::display::MultiDisplay<cyphernet::display::Encoding> for VerifyingKey {
452    type Display = String;
453
454    fn display_fmt(&self, encoding: &cyphernet::display::Encoding) -> Self::Display {
455        self.public_key().display_fmt(encoding)
456    }
457}
458
459#[cfg(feature = "cyphernet")]
460impl From<&cyphernet::ed25519::PublicKey> for PublicKey {
461    fn from(value: &cyphernet::ed25519::PublicKey) -> Self {
462        use core::ops::Deref as _;
463        Self(*value.deref().deref())
464    }
465}
466
467#[cfg(feature = "cyphernet")]
468impl From<PublicKey> for cyphernet::ed25519::PublicKey {
469    fn from(value: PublicKey) -> Self {
470        use cyphernet::EcPk as _;
471        cyphernet::ed25519::PublicKey::from_pk_compressed(value.into_inner().into())
472            .expect("implementation is infallible")
473    }
474}
475
476#[cfg(all(feature = "cyphernet", feature = "alloc"))]
477impl cyphernet::EcPk for VerifyingKey {
478    const COMPRESSED_LEN: usize = dalek::ed::PUBLIC_KEY_LENGTH;
479    const CURVE_NAME: &'static str = "Edwards25519";
480
481    type Compressed = PublicKey;
482
483    fn base_point() -> Self {
484        unimplemented!()
485    }
486
487    fn to_pk_compressed(&self) -> Self::Compressed {
488        *self.public_key()
489    }
490
491    fn from_pk_compressed(pk: Self::Compressed) -> Result<Self, cyphernet::EcPkInvalid> {
492        dalek::ed::VerifyingKey::from_bytes(&pk.0)
493            .map_err(|_| cyphernet::EcPkInvalid::default())
494            .map(Self)
495    }
496
497    fn from_pk_compressed_slice(slice: &[u8]) -> Result<Self, cyphernet::EcPkInvalid> {
498        Self::from_pk_compressed(PublicKey(
499            slice
500                .try_into()
501                .map_err(|_| cyphernet::EcPkInvalid::default())?,
502        ))
503    }
504}
505
506#[cfg(all(feature = "ssh", feature = "std"))]
507#[derive(thiserror::Error, Debug)]
508#[non_exhaustive]
509pub enum LoadError {
510    #[error(transparent)]
511    Keystore(#[from] ssh::keystore::Error),
512    #[error("key not found in '{0}'")]
513    NotFound(std::path::PathBuf),
514    #[error("invalid passphrase")]
515    InvalidPassphrase,
516    #[error("secret key '{secret}' and public key '{public}' do not match")]
517    KeyMismatch {
518        secret: std::path::PathBuf,
519        public: std::path::PathBuf,
520    },
521}
522
523/// A (decompressed) point on the Edwards25519 curve (but not on its twist) that
524/// may be used to sign data.
525#[derive(Clone, Debug, Eq, PartialEq)]
526pub struct SigningKey(dalek::ed::SigningKey);
527
528impl SigningKey {
529    fn public_key(&self) -> &PublicKey {
530        self.0.as_ref().as_bytes().into()
531    }
532
533    /// Construct a new [`SigningKey`] from the provided [`Seed`] by "expanding"
534    /// `seed`. This involves hashing `seed` with SHA-512 and clamping the
535    /// resulting 32-byte digest to produce a valid key.
536    ///
537    /// See also `secret_expand` in [RFC 8032, Sec. 6].
538    ///
539    /// [RFC 8032, Sec. 6]: https://datatracker.ietf.org/doc/html/rfc8032#section-6
540    pub fn from_seed(seed: Seed) -> Self {
541        Self(dalek::ed::SigningKey::from_bytes(seed.as_ref()))
542    }
543
544    #[cfg(any(test, all(feature = "test", feature = "alloc")))]
545    pub fn mock(id: usize) -> Self {
546        Self::from_seed(Seed::mock(id))
547    }
548
549    /// Convert this [`SigningKey`] to a 64-byte keypair.
550    pub fn to_keypair_bytes(&self) -> [u8; dalek::ed::KEYPAIR_LENGTH] {
551        self.0.to_keypair_bytes()
552    }
553
554    /// Convert this [`SigningKey`] into a reference to its 32-byte
555    /// representation.
556    pub fn as_bytes(&self) -> &[u8; dalek::ed::SECRET_KEY_LENGTH] {
557        self.0.as_bytes()
558    }
559
560    /// Load this signer from a keystore, given a secret key passphrase.
561    #[cfg(all(feature = "ssh", feature = "std"))]
562    pub fn load(
563        keystore: &ssh::Keystore,
564        passphrase: Option<ssh::Passphrase>,
565    ) -> Result<Self, LoadError> {
566        let secret = keystore
567            .secret_key(passphrase)
568            .map_err(|e| {
569                if e.is_crypto_err() {
570                    LoadError::InvalidPassphrase
571                } else {
572                    e.into()
573                }
574            })?
575            .ok_or_else(|| LoadError::NotFound(keystore.secret_key_path().to_path_buf()))?;
576
577        let Some(public_path) = keystore.public_key_path() else {
578            // There is no public key in the key store, so there's nothing
579            // to validate. Derive it from the secret key.
580            return Ok(secret);
581        };
582
583        let public = keystore
584            .public_key()?
585            .ok_or_else(|| LoadError::NotFound(public_path.to_path_buf()))?;
586
587        if secret.public_key() != &public {
588            return Err(LoadError::KeyMismatch {
589                secret: keystore.secret_key_path().to_path_buf(),
590                public: public_path.to_path_buf(),
591            });
592        }
593
594        Ok(secret)
595    }
596
597    /// Elliptic-curve Diffie-Hellman.
598    #[cfg(feature = "diffie-hellman")]
599    pub fn diffie_hellman(&self, their_public: &VerifyingKey) -> Option<SharedSecret> {
600        let scalar = self.0.to_scalar();
601
602        dalek::curve::edwards::CompressedEdwardsY(their_public.to_bytes())
603            .decompress()
604            .map(|point| (scalar * point).compress().to_bytes())
605    }
606}
607
608impl AsRef<PublicKey> for SigningKey {
609    fn as_ref(&self) -> &PublicKey {
610        self.public_key()
611    }
612}
613
614impl PartialOrd for SigningKey {
615    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
616        Some(self.cmp(other))
617    }
618}
619
620impl Ord for SigningKey {
621    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
622        self.0.as_bytes().cmp(other.0.as_bytes())
623    }
624}
625
626impl TryFrom<[u8; dalek::ed::KEYPAIR_LENGTH]> for SigningKey {
627    type Error = signature::Error;
628
629    fn try_from(bytes: [u8; dalek::ed::KEYPAIR_LENGTH]) -> Result<Self, Self::Error> {
630        dalek::ed::SigningKey::from_keypair_bytes(&bytes).map(Self)
631    }
632}
633
634impl From<dalek::ed::SigningKey> for SigningKey {
635    fn from(other: dalek::ed::SigningKey) -> Self {
636        Self(other)
637    }
638}
639
640impl From<SigningKey> for dalek::ed::SigningKey {
641    fn from(other: SigningKey) -> Self {
642        other.0
643    }
644}
645
646impl signature::Signer<Signature> for SigningKey {
647    fn try_sign(&self, msg: &[u8]) -> Result<Signature, signature::Error> {
648        self.0.try_sign(msg)
649    }
650}
651
652impl signature::Keypair for SigningKey {
653    type VerifyingKey = VerifyingKey;
654
655    fn verifying_key(&self) -> Self::VerifyingKey {
656        VerifyingKey(self.0.verifying_key())
657    }
658}
659
660#[cfg(all(feature = "cyphernet", feature = "alloc"))]
661impl cyphernet::EcSk for SigningKey {
662    type Pk = VerifyingKey;
663
664    fn generate_keypair() -> (Self, Self::Pk)
665    where
666        Self: Sized,
667    {
668        let signing_key = dalek::ed::SigningKey::from_bytes(&[154; 32]);
669        let verifying_key = signature::Keypair::verifying_key(&signing_key);
670        (SigningKey(signing_key), VerifyingKey(verifying_key))
671    }
672
673    fn to_pk(&self) -> Result<Self::Pk, cyphernet::EcSkInvalid> {
674        use signature::Keypair as _;
675
676        Ok(self.verifying_key())
677    }
678}
679
680#[cfg(all(feature = "cyphernet", feature = "diffie-hellman"))]
681impl cyphernet::Ecdh for SigningKey {
682    type SharedSecret = SharedSecret;
683
684    fn ecdh(&self, pk: &Self::Pk) -> Result<Self::SharedSecret, cyphernet::EcdhError> {
685        self.diffie_hellman(pk)
686            .ok_or(cyphernet::EcdhError::InvalidPk(
687                cyphernet::EcPkInvalid::default(),
688            ))
689    }
690}
691
692#[cfg(feature = "qcheck")]
693impl qcheck::Arbitrary for SigningKey {
694    fn arbitrary(g: &mut qcheck::Gen) -> Self {
695        SigningKey::mock(usize::arbitrary(g))
696    }
697}
698
699#[cfg(all(feature = "alloc", feature = "multibase", feature = "cyphernet"))]
700impl alloc::str::FromStr for VerifyingKey {
701    type Err = PublicKeyError;
702
703    fn from_str(s: &str) -> Result<Self, Self::Err> {
704        let pk = PublicKey::from_str(s)?;
705        dalek::ed::VerifyingKey::from_bytes(&pk.0)
706            .map(Self)
707            .map_err(PublicKeyError::Invalid)
708    }
709}
710
711#[cfg(all(feature = "alloc", feature = "multibase"))]
712impl TryFrom<String> for PublicKey {
713    type Error = PublicKeyError;
714
715    fn try_from(value: String) -> Result<Self, Self::Error> {
716        use alloc::str::FromStr as _;
717
718        Self::from_str(&value)
719    }
720}
721
722#[cfg(all(feature = "alloc", feature = "multibase", feature = "cyphernet"))]
723impl TryFrom<String> for VerifyingKey {
724    type Error = PublicKeyError;
725
726    fn try_from(value: String) -> Result<Self, Self::Error> {
727        use alloc::str::FromStr as _;
728
729        Self::from_str(&value)
730    }
731}
732
733#[cfg(feature = "qcheck")]
734impl qcheck::Arbitrary for PublicKey {
735    fn arbitrary(g: &mut qcheck::Gen) -> Self {
736        *SigningKey::from_seed(Seed::arbitrary(g)).public_key()
737    }
738}
739
740/// An extended signature carries the key that may be used to verify the
741/// signature along with the signature itself.
742#[derive(Debug, Clone, PartialEq, Eq)]
743pub struct ExtendedSignature<PublicKey = crate::PublicKey, Signature = crate::Signature> {
744    key: PublicKey,
745    sig: Signature,
746}
747
748impl ExtendedSignature {
749    pub fn try_sign(signer: &impl Signer, payload: &[u8]) -> Result<Self, signature::Error> {
750        Ok(Self {
751            key: *signer.public_key(),
752            sig: signer.try_sign(payload)?,
753        })
754    }
755}
756
757impl<VerifyingKey, Signature> ExtendedSignature<VerifyingKey, Signature>
758where
759    VerifyingKey: signature::Verifier<Signature>,
760{
761    /// Verify the signature for a given payload.
762    pub fn verify(&self, msg: &[u8]) -> Result<(), signature::Error> {
763        self.key.verify(msg, &self.sig)
764    }
765}
766
767impl<VerifyingKey, Signature> ExtendedSignature<VerifyingKey, Signature> {
768    /// Create a new extended signature.
769    pub fn new(key: VerifyingKey, sig: Signature) -> Self {
770        Self { key, sig }
771    }
772
773    pub fn key(&self) -> &VerifyingKey {
774        &self.key
775    }
776
777    pub fn sig(&self) -> &Signature {
778        &self.sig
779    }
780
781    pub fn into_pair(self) -> (VerifyingKey, Signature) {
782        (self.key, self.sig)
783    }
784}
785
786#[cfg(test)]
787mod tests {
788    use super::*;
789
790    use qcheck_macros::quickcheck;
791
792    use crate::SigningKey;
793
794    /// See <https://w3c-ccg.github.io/did-key-spec/#example-a-simple-ed25519-did-key-value>.
795    const DID_KEY_SAMPLE: &str = "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
796
797    #[cfg(feature = "diffie-hellman")]
798    #[quickcheck]
799    fn diffie_hellman(sk_a: SigningKey, sk_b: SigningKey) {
800        use signature::Keypair as _;
801
802        let output_a = sk_b.diffie_hellman(&sk_a.verifying_key()).unwrap();
803        let output_b = sk_a.diffie_hellman(&sk_b.verifying_key()).unwrap();
804
805        assert_eq!(output_a, output_b);
806    }
807
808    #[cfg(feature = "alloc")]
809    #[quickcheck]
810    fn prop_encode_decode(input: PublicKey) {
811        use alloc::str::FromStr as _;
812
813        let encoded = input.to_string();
814        let decoded = PublicKey::from_str(&encoded).unwrap();
815
816        assert_eq!(input, decoded);
817    }
818
819    #[cfg(feature = "alloc")]
820    #[test]
821    fn did_key_sample() {
822        use alloc::str::FromStr as _;
823
824        let key = PublicKey::from_str(DID_KEY_SAMPLE).unwrap();
825
826        assert_eq!(key.to_string(), DID_KEY_SAMPLE);
827    }
828
829    #[cfg(feature = "std")]
830    #[quickcheck]
831    fn prop_key_equality(a: PublicKey, b: PublicKey) {
832        if a == b {
833            return;
834        }
835
836        let mut hm = std::collections::HashSet::new();
837
838        assert!(hm.insert(a));
839        assert!(hm.insert(b));
840        assert!(!hm.insert(a));
841        assert!(!hm.insert(b));
842    }
843
844    #[cfg(feature = "diffie-hellman")]
845    #[test]
846    fn diffie_hellman_fixture() {
847        let sk_a: [u8; 32] = [
848            92, 136, 18, 88, 112, 205, 201, 68, 109, 197, 130, 211, 179, 138, 197, 113, 120, 55,
849            104, 139, 208, 184, 178, 157, 120, 11, 60, 13, 91, 30, 213, 38,
850        ];
851        let sk_b: [u8; 32] = [
852            202, 152, 225, 201, 169, 81, 217, 16, 235, 104, 91, 252, 52, 113, 81, 190, 68, 250, 86,
853            21, 202, 228, 123, 193, 140, 252, 63, 72, 5, 137, 36, 245,
854        ];
855
856        let kp_a = dalek::ed::SigningKey::from_bytes(&sk_a);
857        let kp_b = dalek::ed::SigningKey::from_bytes(&sk_b);
858
859        let output_a = SigningKey::from(kp_b.clone())
860            .diffie_hellman(&kp_a.verifying_key().into())
861            .unwrap();
862        let output_b = SigningKey::from(kp_a)
863            .diffie_hellman(&kp_b.verifying_key().into())
864            .unwrap();
865
866        assert_eq!(output_a, output_b);
867
868        assert_eq!(
869            output_a,
870            [
871                159, 131, 169, 27, 132, 202, 47, 250, 112, 247, 176, 222, 213, 220, 147, 216, 53,
872                7, 33, 232, 232, 77, 254, 105, 125, 237, 61, 243, 209, 172, 93, 100
873            ]
874        )
875    }
876}