Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
heartwood crates radicle-crypto src lib.rs
use std::cmp::Ordering;
use std::sync::Arc;
use std::{fmt, ops::Deref, str::FromStr};

use ec25519 as ed25519;
use serde::{Deserialize, Serialize};
use thiserror::Error;

pub use ed25519::{Error, KeyPair, Seed, edwards25519};

pub extern crate signature;

#[cfg(feature = "ssh")]
pub mod ssh;
#[cfg(any(test, feature = "test"))]
pub mod test;

/// Output of a Diffie-Hellman key exchange.
pub type SharedSecret = [u8; 32];

/// Error returned if signing fails, eg. due to an HSM or KMS.
#[derive(Debug, Clone, Error)]
#[error(transparent)]
#[non_exhaustive]
pub struct SignerError {
    #[from]
    source: Arc<dyn std::error::Error + Send + Sync>,
}

impl SignerError {
    pub fn new(source: impl std::error::Error + Send + Sync + 'static) -> Self {
        Self {
            source: Arc::new(source),
        }
    }
}

pub trait Signer: Send + signature::Signer<Signature> {
    /// Return this signer's public/verification key.
    fn public_key(&self) -> &PublicKey;
}

impl<S> Signer for S
where
    S: Send,
    S: signature::Signer<Signature>,
    S: signature::KeypairRef<VerifyingKey = PublicKey>,
{
    fn public_key(&self) -> &PublicKey {
        self.as_ref()
    }
}

/// Cryptographic signature.
#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
pub struct Signature(pub ed25519::Signature);

impl AsRef<[u8]> for Signature {
    fn as_ref(&self) -> &[u8] {
        self.0.as_ref()
    }
}

impl fmt::Display for Signature {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let base = multibase::Base::Base58Btc;
        write!(f, "{}", multibase::encode(base, self.deref()))
    }
}

impl fmt::Debug for Signature {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Signature({self})")
    }
}

#[derive(Error, Debug)]
#[non_exhaustive]
pub enum SignatureError {
    #[error("invalid multibase string: {0}")]
    Multibase(#[from] multibase::Error),
    #[error("invalid signature: {0}")]
    Invalid(#[from] ed25519::Error),
}

impl From<ed25519::Signature> for Signature {
    fn from(other: ed25519::Signature) -> Self {
        Self(other)
    }
}

impl FromStr for Signature {
    type Err = SignatureError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (_, bytes) = multibase::decode(s)?;
        let sig = ed25519::Signature::from_slice(bytes.as_slice())?;

        Ok(Self(sig))
    }
}

impl Deref for Signature {
    type Target = ed25519::Signature;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl From<[u8; 64]> for Signature {
    fn from(bytes: [u8; 64]) -> Self {
        Self(ed25519::Signature::new(bytes))
    }
}

impl TryFrom<&[u8]> for Signature {
    type Error = ed25519::Error;

    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
        ed25519::Signature::from_slice(bytes).map(Self)
    }
}

impl From<Signature> for String {
    fn from(s: Signature) -> Self {
        s.to_string()
    }
}

impl TryFrom<String> for Signature {
    type Error = SignatureError;

    fn try_from(s: String) -> Result<Self, Self::Error> {
        Self::from_str(&s)
    }
}

/// The public/verification key.
#[derive(Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[serde(into = "String", try_from = "String")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(
    feature = "schemars",
    schemars(
        title = "Ed25519",
        description = "An Ed25519 public key in multibase encoding.",
        extend("examples" = [
            "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7",
            "z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C",
            "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
            "z6MkkfM3tPXNPrPevKr3uSiQtHPuwnNhu2yUVjgd2jXVsVz5",
        ]),
    ),
)]
pub struct PublicKey(amplify::Bytes32);

impl PublicKey {
    /// Verify the signature for a given payload.
    pub fn verify(
        &self,
        payload: impl AsRef<[u8]>,
        signature: &ed25519::Signature,
    ) -> Result<(), ed25519::Error> {
        ed25519::PublicKey::new(self.0.to_byte_array()).verify(payload, signature)
    }

    /// Returns a byte array representation of the public key.
    #[inline]
    pub fn to_byte_array(&self) -> [u8; 32] {
        self.0.to_byte_array()
    }
}

impl signature::Verifier<Signature> for PublicKey {
    fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> {
        self.verify(msg, signature)
            .map_err(signature::Error::from_source)
    }
}

#[cfg(feature = "cyphernet")]
impl cyphernet::display::MultiDisplay<cyphernet::display::Encoding> for PublicKey {
    type Display = String;

    fn display_fmt(&self, _: &cyphernet::display::Encoding) -> Self::Display {
        self.to_string()
    }
}

#[cfg(feature = "ssh")]
impl From<PublicKey> for ssh_key::PublicKey {
    fn from(key: PublicKey) -> Self {
        ssh_key::PublicKey::from(ssh_key::public::Ed25519PublicKey(key.to_byte_array()))
    }
}

#[cfg(feature = "cyphernet")]
impl cyphernet::EcPk for PublicKey {
    const COMPRESSED_LEN: usize = 32;
    const CURVE_NAME: &'static str = "Edwards25519";

    type Compressed = amplify::Bytes32;

    fn base_point() -> Self {
        unimplemented!()
    }

    fn to_pk_compressed(&self) -> Self::Compressed {
        amplify::Bytes32::from_byte_array(self.to_byte_array())
    }

    fn from_pk_compressed(pk: Self::Compressed) -> Result<Self, cyphernet::EcPkInvalid> {
        Ok(PublicKey::from(pk.to_byte_array()))
    }

    fn from_pk_compressed_slice(slice: &[u8]) -> Result<Self, cyphernet::EcPkInvalid> {
        ed25519::PublicKey::from_slice(slice)
            .map_err(|_| cyphernet::EcPkInvalid::default())
            .map(Self::from)
    }
}

/// The private/signing key.
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct SecretKey(ed25519::SecretKey);

impl SecretKey {
    /// Elliptic-curve Diffie-Hellman.
    pub fn ecdh(&self, pk: &PublicKey) -> Result<[u8; 32], ed25519::Error> {
        let scalar = self.seed().scalar();
        let ge = edwards25519::GeP3::from_bytes_vartime(&pk.to_byte_array())
            .ok_or(Error::InvalidPublicKey)?;

        Ok(edwards25519::ge_scalarmult(&scalar, &ge).to_bytes())
    }
}

impl PartialOrd for SecretKey {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for SecretKey {
    fn cmp(&self, other: &Self) -> Ordering {
        self.0.cmp(&other.0)
    }
}

impl zeroize::Zeroize for SecretKey {
    fn zeroize(&mut self) {
        self.0.zeroize();
    }
}

impl TryFrom<&[u8]> for SecretKey {
    type Error = ed25519::Error;

    fn try_from(bytes: &[u8]) -> Result<Self, ed25519::Error> {
        ed25519::SecretKey::from_slice(bytes).map(Self)
    }
}

impl AsRef<[u8]> for SecretKey {
    fn as_ref(&self) -> &[u8] {
        &*self.0
    }
}

impl From<[u8; 64]> for SecretKey {
    fn from(bytes: [u8; 64]) -> Self {
        Self(ed25519::SecretKey::new(bytes))
    }
}

impl From<ed25519::SecretKey> for SecretKey {
    fn from(other: ed25519::SecretKey) -> Self {
        Self(other)
    }
}

impl From<SecretKey> for ed25519::SecretKey {
    fn from(other: SecretKey) -> Self {
        other.0
    }
}

impl Deref for SecretKey {
    type Target = ed25519::SecretKey;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[derive(Error, Debug)]
#[non_exhaustive]
pub enum PublicKeyError {
    #[error("invalid length {0}")]
    InvalidLength(usize),
    #[error("invalid multibase string: {0}")]
    Multibase(#[from] multibase::Error),
    #[error("invalid multicodec prefix, expected {0:?}")]
    Multicodec([u8; 2]),
    #[error("invalid key: {0}")]
    InvalidKey(#[from] ed25519::Error),
}

impl fmt::Display for PublicKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.to_human())
    }
}

impl From<PublicKey> for String {
    fn from(other: PublicKey) -> Self {
        other.to_human()
    }
}

impl fmt::Debug for PublicKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "PublicKey({self})")
    }
}

impl From<ed25519::PublicKey> for PublicKey {
    fn from(other: ed25519::PublicKey) -> Self {
        Self(amplify::Bytes32::from_byte_array(*other.deref()))
    }
}

impl From<PublicKey> for ed25519::PublicKey {
    fn from(val: PublicKey) -> Self {
        ed25519::PublicKey::new(val.to_byte_array())
    }
}

impl From<[u8; 32]> for PublicKey {
    fn from(other: [u8; 32]) -> Self {
        Self(amplify::Bytes32::from_byte_array(other))
    }
}

impl TryFrom<&[u8]> for PublicKey {
    type Error = ed25519::Error;

    fn try_from(other: &[u8]) -> Result<Self, Self::Error> {
        ed25519::PublicKey::from_slice(other).map(Self::from)
    }
}

impl PublicKey {
    /// Multicodec key type for Ed25519 keys.
    pub const MULTICODEC_TYPE: [u8; 2] = [0xED, 0x1];

    /// Encode public key in human-readable format.
    ///
    /// `MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))`
    ///
    pub fn to_human(&self) -> String {
        let mut buf = [0; 2 + ed25519::PublicKey::BYTES];
        buf[..2].copy_from_slice(&Self::MULTICODEC_TYPE);
        buf[2..].copy_from_slice(self.to_byte_array().as_slice());

        multibase::encode(multibase::Base::Base58Btc, buf)
    }

    #[cfg(feature = "git-ref-format-core")]
    pub fn to_namespace(&self) -> git_ref_format_core::RefString {
        use git_ref_format_core::name::{Component, NAMESPACES, REFS};
        REFS.to_owned().and(NAMESPACES).and(Component::from(self))
    }

    #[cfg(feature = "git-ref-format-core")]
    pub fn to_component(&self) -> git_ref_format_core::Component<'_> {
        git_ref_format_core::Component::from(self)
    }

    #[cfg(feature = "git-ref-format-core")]
    pub fn from_namespaced(
        refstr: &git_ref_format_core::Namespaced,
    ) -> Result<Self, PublicKeyError> {
        let name = refstr.namespace().into_inner();

        Self::from_str(name.deref().as_str())
    }
}

impl FromStr for PublicKey {
    type Err = PublicKeyError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (_, bytes) = multibase::decode(s)?;

        if let Some(bytes) = bytes.strip_prefix(&Self::MULTICODEC_TYPE) {
            let key = ed25519::PublicKey::from_slice(bytes)?;

            Ok(key.into())
        } else {
            Err(PublicKeyError::Multicodec(Self::MULTICODEC_TYPE))
        }
    }
}

impl TryFrom<String> for PublicKey {
    type Error = PublicKeyError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        Self::from_str(&value)
    }
}

#[cfg(feature = "git-ref-format-core")]
impl From<&PublicKey> for git_ref_format_core::Component<'_> {
    fn from(id: &PublicKey) -> Self {
        use git_ref_format_core::{Component, RefString};
        let refstr =
            RefString::try_from(id.to_string()).expect("encoded public keys are valid ref strings");
        Component::from_refstr(refstr).expect("encoded public keys are valid refname components")
    }
}

#[cfg(feature = "sqlite")]
impl From<&PublicKey> for sqlite::Value {
    fn from(pk: &PublicKey) -> Self {
        sqlite::Value::String(pk.to_human())
    }
}

#[cfg(feature = "sqlite")]
impl TryFrom<&sqlite::Value> for PublicKey {
    type Error = sqlite::Error;

    fn try_from(value: &sqlite::Value) -> Result<Self, Self::Error> {
        match value {
            sqlite::Value::String(s) => Self::from_str(s).map_err(|e| sqlite::Error {
                code: None,
                message: Some(e.to_string()),
            }),
            _ => Err(sqlite::Error {
                code: None,
                message: Some("sql: invalid type for public key".to_owned()),
            }),
        }
    }
}

#[cfg(feature = "sqlite")]
impl sqlite::BindableWithIndex for &PublicKey {
    fn bind<I: sqlite::ParameterIndex>(
        self,
        stmt: &mut sqlite::Statement<'_>,
        i: I,
    ) -> sqlite::Result<()> {
        sqlite::Value::from(self).bind(stmt, i)
    }
}

#[cfg(feature = "sqlite")]
impl From<&Signature> for sqlite::Value {
    fn from(sig: &Signature) -> Self {
        sqlite::Value::Binary(sig.to_vec())
    }
}

#[cfg(feature = "sqlite")]
impl TryFrom<&sqlite::Value> for Signature {
    type Error = sqlite::Error;

    fn try_from(value: &sqlite::Value) -> Result<Self, Self::Error> {
        match value {
            sqlite::Value::Binary(s) => ed25519::Signature::from_slice(s)
                .map_err(|e| sqlite::Error {
                    code: None,
                    message: Some(e.to_string()),
                })
                .map(Self),
            _ => Err(sqlite::Error {
                code: None,
                message: Some("sql: invalid column type for signature".to_owned()),
            }),
        }
    }
}

#[cfg(feature = "sqlite")]
impl sqlite::BindableWithIndex for &Signature {
    fn bind<I: sqlite::ParameterIndex>(
        self,
        stmt: &mut sqlite::Statement<'_>,
        i: I,
    ) -> sqlite::Result<()> {
        sqlite::Value::from(self).bind(stmt, i)
    }
}

#[cfg(test)]
mod tests {
    use super::KeyPair;
    use crate::{PublicKey, SecretKey};
    use qcheck_macros::quickcheck;
    use std::str::FromStr;

    #[test]
    fn test_e25519_dh() {
        let kp_a = KeyPair::generate();
        let kp_b = KeyPair::generate();

        let output_a = SecretKey::from(kp_b.sk).ecdh(&kp_a.pk.into()).unwrap();
        let output_b = SecretKey::from(kp_a.sk).ecdh(&kp_b.pk.into()).unwrap();

        assert_eq!(output_a, output_b);
    }

    #[quickcheck]
    fn prop_encode_decode(input: PublicKey) {
        let encoded = input.to_string();
        let decoded = PublicKey::from_str(&encoded).unwrap();

        assert_eq!(input, decoded);
    }

    #[test]
    fn test_encode_decode() {
        let input = "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
        let key = PublicKey::from_str(input).unwrap();

        assert_eq!(key.to_string(), input);
    }

    #[quickcheck]
    fn prop_key_equality(a: PublicKey, b: PublicKey) {
        use std::collections::HashSet;

        assert_ne!(a, b);

        let mut hm = HashSet::new();

        assert!(hm.insert(a));
        assert!(hm.insert(b));
        assert!(!hm.insert(a));
        assert!(!hm.insert(b));
    }
}