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, } 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 { /// Return this signer's public/verification key. fn public_key(&self) -> &PublicKey; } impl Signer for S where S: Send, S: signature::Signer, S: signature::KeypairRef, { 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 for Signature { fn from(other: ed25519::Signature) -> Self { Self(other) } } impl FromStr for Signature { type Err = SignatureError; fn from_str(s: &str) -> Result { 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 { ed25519::Signature::from_slice(bytes).map(Self) } } impl From for String { fn from(s: Signature) -> Self { s.to_string() } } impl TryFrom for Signature { type Error = SignatureError; fn try_from(s: String) -> Result { 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 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 for PublicKey { type Display = String; fn display_fmt(&self, _: &cyphernet::display::Encoding) -> Self::Display { self.to_string() } } #[cfg(feature = "ssh")] impl From 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 { Ok(PublicKey::from(pk.to_byte_array())) } fn from_pk_compressed_slice(slice: &[u8]) -> Result { 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 { 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 { 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 for SecretKey { fn from(other: ed25519::SecretKey) -> Self { Self(other) } } impl From 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 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 for PublicKey { fn from(other: ed25519::PublicKey) -> Self { Self(amplify::Bytes32::from_byte_array(*other.deref())) } } impl From 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 { 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 { 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 { 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 for PublicKey { type Error = PublicKeyError; fn try_from(value: String) -> Result { 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 { 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( 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 { 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( 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)); } }