use crate::utils::as_array_32;
use core::fmt;
use core::mem::size_of;
#[cfg(feature = "dalek")]
use rand::{CryptoRng, RngCore};
use zerocopy::{AsBytes, FromBytes};
use zeroize::Zeroize;
#[cfg(all(feature = "dalek", not(feature = "force_sodium")))]
use crate::dalek::sign;
#[cfg(all(
feature = "sodium",
any(feature = "force_sodium", not(feature = "dalek"))
))]
use crate::sodium::sign;
/// A public/secret long-term key pair.
///
/// This is an [Ed25519](https://en.wikipedia.org/wiki/EdDSA) key pair.
#[derive(Clone, Debug, AsBytes, FromBytes)]
#[repr(C)]
pub struct Keypair {
/// The secret half of the key pair. Keep private.
pub secret: SecretKey,
/// The public half of the key pair. Feel free to share.
pub public: PublicKey,
}
impl Keypair {
/// Size of a key pair, in bytes (== 64).
pub const SIZE: usize = size_of::<Self>();
/// Deserialize a keypair from a byte slice.
///
/// The slice length must be 64; where the first 32 bytes
/// are the secret key, and the second 32 bytes are the public key
/// (libsodium's standard layout).
pub fn from_slice(s: &[u8]) -> Option<Self> {
if s.len() == Self::SIZE {
Some(Keypair {
secret: SecretKey(as_array_32(&s[..32])),
public: PublicKey(as_array_32(&s[32..])),
})
} else {
None
}
}
/// Deserialize from the bas64 representation. Ignores optional .ed25519 suffix.
///
/// # Example
/// ```rust
/// let s = "R6DKoOCt1Cj/IB2/ocvj2Eyp8AgmFdoJ9hH2TO4Tl8Yfapd5Lmw4pSpoY0WBEnqpHjz6UB4/QL2Wr0hWVAyi1w==.ed25519";
/// if let Some(keypair) = ssb_crypto::Keypair::from_base64(s) {
/// // let auth = keypair.sign("hello".to_bytes());
/// // ...
/// } else {
/// panic!()
/// }
/// ```
#[cfg(feature = "b64")]
pub fn from_base64(s: &str) -> Option<Self> {
let mut buf = [0; 64];
if crate::b64::decode(s, &mut buf, Some(".ed25519")) {
Self::from_slice(&buf)
} else {
None
}
}
/// Does not include ".ed25519" suffix or a sigil prefix.
///
/// # Example
/// ```rust
/// let s = "R6DKoOCt1Cj/IB2/ocvj2Eyp8AgmFdoJ9hH2TO4Tl8Yfapd5Lmw4pSpoY0WBEnqpHjz6UB4/QL2Wr0hWVAyi1w==";
/// let kp = ssb_crypto::Keypair::from_base64(s).unwrap();
/// assert_eq!(kp.as_base64(), s);
/// ```
#[cfg(feature = "alloc")]
pub fn as_base64(&self) -> alloc::string::String {
let mut buf = [0; 64];
let (s, p) = buf.split_at_mut(32);
s.copy_from_slice(&self.secret.0);
p.copy_from_slice(&self.public.0);
base64::encode_config(&buf[..], base64::STANDARD)
}
/// Generate a new random keypair.
#[cfg(any(feature = "sodium", all(feature = "dalek", feature = "getrandom")))]
pub fn generate() -> Keypair {
sign::generate_keypair()
}
/// Create a keypair from the given seed bytes. Slice length must be 32.
#[cfg(any(feature = "sodium", feature = "dalek"))]
pub fn from_seed(seed: &[u8]) -> Option<Keypair> {
sign::keypair_from_seed(seed)
}
/// Generate a new random keypair using the given cryptographically-secure
/// random number generator.
#[cfg(feature = "dalek")]
pub fn generate_with_rng<R>(r: &mut R) -> Keypair
where
R: CryptoRng + RngCore,
{
crate::dalek::sign::generate_keypair_with_rng(r)
}
/// Generate a signature for a given byte slice.
#[cfg(any(feature = "sodium", feature = "dalek"))]
pub fn sign(&self, b: &[u8]) -> Signature {
sign::sign(self, b)
}
}
/// The secret half of a [`Keypair`].
///
/// Note that a libsodium "secret key" is actually a pair of secret and public keys,
/// in the same buffer. This is only the secret half, which isn't much use on its own.
/// For signing, and deserializing a libsodium secretkey encoded in base64
/// (as in the ~/.ssb/secret file), see [`Keypair`].
///
/// The underlying memory is zeroed on drop.
///
/// [`Keypair`]: ./struct.Keypair.html
#[derive(Clone, Debug, AsBytes, FromBytes, Zeroize)]
#[zeroize(drop)]
#[repr(C)]
pub struct SecretKey(pub [u8; 32]);
impl SecretKey {
/// Size of a secret key, in bytes (32).
pub const SIZE: usize = size_of::<Self>();
}
/// The public half of a [`Keypair`].
///
/// [`Keypair`]: ./struct.Keypair.html
#[derive(AsBytes, FromBytes, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(C)]
pub struct PublicKey(pub [u8; 32]);
impl PublicKey {
/// Size of a public key, in bytes (== 32).
pub const SIZE: usize = size_of::<Self>();
/// Deserialize a public key from a byte slice. The slice length must be 32.
pub fn from_slice(s: &[u8]) -> Option<Self> {
if s.len() == Self::SIZE {
let mut out = Self([0; Self::SIZE]);
out.0.copy_from_slice(s);
Some(out)
} else {
None
}
}
/// Deserialize from the base-64 representation. Ignores optional leading '@' sigil and '.ed25519' suffix.
///
/// # Example
/// ```rust
/// use ssb_crypto::PublicKey;
/// let author = "@H2qXeS5sOKUqaGNFgRJ6qR48+lAeP0C9lq9IVlQMotc=.ed25519";
/// let pk = PublicKey::from_base64(author).unwrap();
/// ```
#[cfg(feature = "b64")]
pub fn from_base64(mut s: &str) -> Option<Self> {
if s.starts_with('@') {
s = &s[1..];
}
let mut buf = [0; Self::SIZE];
if crate::b64::decode(s, &mut buf, Some(".ed25519")) {
Some(Self(buf))
} else {
None
}
}
/// Does not include ".ed25519" suffix or a prefix sigil.
///
/// # Example
/// ```rust
/// let s = "H2qXeS5sOKUqaGNFgRJ6qR48+lAeP0C9lq9IVlQMotc=";
/// let pk = ssb_crypto::PublicKey::from_base64(s).unwrap();
/// assert_eq!(pk.as_base64(), s);
/// ```
#[cfg(feature = "alloc")]
pub fn as_base64(&self) -> alloc::string::String {
base64::encode_config(&self.0, base64::STANDARD)
}
/// Verify that a signature was generated by this key's secret half for the given
/// bytes.
#[cfg(any(feature = "sodium", feature = "dalek"))]
pub fn verify(&self, sig: &Signature, b: &[u8]) -> bool {
sign::verify(self, sig, b)
}
}
/// A cryptographic signature of some content, generated by [`Keypair::sign`]
/// and verified by [`PublicKey::verify`].
///
/// [`Keypair::sign`]: ./struct.Keypair.html#method.sign
/// [`PublicKey::verify`]: ./struct.PublicKey.html#method.verify
#[derive(AsBytes, FromBytes, Copy, Clone)]
#[repr(C)]
pub struct Signature(pub [u8; 64]);
impl Signature {
/// Size of a signature, in bytes (== 64).
pub const SIZE: usize = size_of::<Self>();
/// Deserialize a signature from a byte slice. The slice length must be 64.
pub fn from_slice(s: &[u8]) -> Option<Self> {
if s.len() == Self::SIZE {
let mut out = Self([0; Self::SIZE]);
out.0.copy_from_slice(s);
Some(out)
} else {
None
}
}
/// Deserialize a signature from a base-64 encoded string. Ignores optional .sig.ed25519 suffix.
///
/// # Example
/// ```rust
/// use ssb_crypto::Signature;
/// let s = "QTsCZ+INzDENs1dAdej14Lsp1v2UCXUtRZBv4HlDGo6WZn29ZYM5lZtxnyNC53LxX0ucY1x8NlC1A1RjY7FHBA==.sig.ed25519";
/// let sig = Signature::from_base64(s).unwrap();
/// ```
#[cfg(feature = "b64")]
pub fn from_base64(s: &str) -> Option<Self> {
let mut buf = [0; Self::SIZE];
if crate::b64::decode(s, &mut buf, Some(".sig.ed25519")) {
Some(Self(buf))
} else {
None
}
}
/// Does not include ".sig.ed25519" suffix or a prefix sigil.
///
/// # Example
/// ```rust
/// use ssb_crypto::Signature;
/// let s = "QTsCZ+INzDENs1dAdej14Lsp1v2UCXUtRZBv4HlDGo6WZn29ZYM5lZtxnyNC53LxX0ucY1x8NlC1A1RjY7FHBA==";
/// let sig = Signature::from_base64(s).unwrap();
/// assert_eq!(sig.as_base64(), s);
/// ```
#[cfg(feature = "alloc")]
pub fn as_base64(&self) -> alloc::string::String {
base64::encode_config(&self.0[..], base64::STANDARD)
}
}
impl fmt::Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Signature({:?})", &self.0[..])
}
}
impl Eq for Signature {}
impl PartialEq for Signature {
fn eq(&self, other: &Self) -> bool {
self.0.as_ref().eq(other.0.as_ref())
}
}