ssb_crypto/
sign.rs

1use crate::utils::as_array_32;
2use core::fmt;
3use core::mem::size_of;
4#[cfg(feature = "dalek")]
5use rand::{CryptoRng, RngCore};
6use zerocopy::{AsBytes, FromBytes};
7use zeroize::Zeroize;
8
9#[cfg(all(feature = "dalek", not(feature = "force_sodium")))]
10use crate::dalek::sign;
11#[cfg(all(
12    feature = "sodium",
13    any(feature = "force_sodium", not(feature = "dalek"))
14))]
15use crate::sodium::sign;
16
17/// A public/secret long-term key pair.
18///
19/// This is an [Ed25519](https://en.wikipedia.org/wiki/EdDSA) key pair.
20#[derive(Clone, Debug, AsBytes, FromBytes)]
21#[repr(C)]
22pub struct Keypair {
23    /// The secret half of the key pair. Keep private.
24    pub secret: SecretKey,
25
26    /// The public half of the key pair. Feel free to share.
27    pub public: PublicKey,
28}
29
30impl Keypair {
31    /// Size of a key pair, in bytes (== 64).
32    pub const SIZE: usize = size_of::<Self>();
33
34    /// Deserialize a keypair from a byte slice.
35    ///
36    /// The slice length must be 64; where the first 32 bytes
37    /// are the secret key, and the second 32 bytes are the public key
38    /// (libsodium's standard layout).
39    pub fn from_slice(s: &[u8]) -> Option<Self> {
40        if s.len() == Self::SIZE {
41            Some(Keypair {
42                secret: SecretKey(as_array_32(&s[..32])),
43                public: PublicKey(as_array_32(&s[32..])),
44            })
45        } else {
46            None
47        }
48    }
49
50    /// Deserialize from the bas64 representation. Ignores optional .ed25519 suffix.
51    ///
52    /// # Example
53    /// ```rust
54    /// let s = "R6DKoOCt1Cj/IB2/ocvj2Eyp8AgmFdoJ9hH2TO4Tl8Yfapd5Lmw4pSpoY0WBEnqpHjz6UB4/QL2Wr0hWVAyi1w==.ed25519";
55    /// if let Some(keypair) = ssb_crypto::Keypair::from_base64(s) {
56    ///   // let auth = keypair.sign("hello".to_bytes());
57    ///   // ...
58    /// } else {
59    ///     panic!()
60    /// }
61    /// ```
62    #[cfg(feature = "b64")]
63    pub fn from_base64(s: &str) -> Option<Self> {
64        let mut buf = [0; 64];
65        if crate::b64::decode(s, &mut buf, Some(".ed25519")) {
66            Self::from_slice(&buf)
67        } else {
68            None
69        }
70    }
71
72    /// Does not include ".ed25519" suffix or a sigil prefix.
73    ///
74    /// # Example
75    /// ```rust
76    /// let s = "R6DKoOCt1Cj/IB2/ocvj2Eyp8AgmFdoJ9hH2TO4Tl8Yfapd5Lmw4pSpoY0WBEnqpHjz6UB4/QL2Wr0hWVAyi1w==";
77    /// let kp = ssb_crypto::Keypair::from_base64(s).unwrap();
78    /// assert_eq!(kp.as_base64(), s);
79    /// ```
80    #[cfg(feature = "alloc")]
81    pub fn as_base64(&self) -> alloc::string::String {
82        let mut buf = [0; 64];
83        let (s, p) = buf.split_at_mut(32);
84        s.copy_from_slice(&self.secret.0);
85        p.copy_from_slice(&self.public.0);
86        base64::encode_config(&buf[..], base64::STANDARD)
87    }
88
89    /// Generate a new random keypair.
90    #[cfg(any(feature = "sodium", all(feature = "dalek", feature = "getrandom")))]
91    pub fn generate() -> Keypair {
92        sign::generate_keypair()
93    }
94
95    /// Create a keypair from the given seed bytes. Slice length must be 32.
96    #[cfg(any(feature = "sodium", feature = "dalek"))]
97    pub fn from_seed(seed: &[u8]) -> Option<Keypair> {
98        sign::keypair_from_seed(seed)
99    }
100
101    /// Generate a new random keypair using the given cryptographically-secure
102    /// random number generator.
103    #[cfg(feature = "dalek")]
104    pub fn generate_with_rng<R>(r: &mut R) -> Keypair
105    where
106        R: CryptoRng + RngCore,
107    {
108        crate::dalek::sign::generate_keypair_with_rng(r)
109    }
110
111    /// Generate a signature for a given byte slice.
112    #[cfg(any(feature = "sodium", feature = "dalek"))]
113    pub fn sign(&self, b: &[u8]) -> Signature {
114        sign::sign(self, b)
115    }
116}
117
118/// The secret half of a [`Keypair`].
119///
120/// Note that a libsodium "secret key" is actually a pair of secret and public keys,
121/// in the same buffer. This is only the secret half, which isn't much use on its own.
122/// For signing, and deserializing a libsodium secretkey encoded in base64
123/// (as in the ~/.ssb/secret file), see [`Keypair`].
124///
125/// The underlying memory is zeroed on drop.
126///
127/// [`Keypair`]: ./struct.Keypair.html
128#[derive(Clone, Debug, AsBytes, FromBytes, Zeroize)]
129#[zeroize(drop)]
130#[repr(C)]
131pub struct SecretKey(pub [u8; 32]);
132impl SecretKey {
133    /// Size of a secret key, in bytes (32).
134    pub const SIZE: usize = size_of::<Self>();
135}
136
137/// The public half of a [`Keypair`].
138///
139/// [`Keypair`]: ./struct.Keypair.html
140#[derive(AsBytes, FromBytes, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
141#[repr(C)]
142pub struct PublicKey(pub [u8; 32]);
143impl PublicKey {
144    /// Size of a public key, in bytes (== 32).
145    pub const SIZE: usize = size_of::<Self>();
146
147    /// Deserialize a public key from a byte slice. The slice length must be 32.
148    pub fn from_slice(s: &[u8]) -> Option<Self> {
149        if s.len() == Self::SIZE {
150            let mut out = Self([0; Self::SIZE]);
151            out.0.copy_from_slice(s);
152            Some(out)
153        } else {
154            None
155        }
156    }
157
158    /// Deserialize from the base-64 representation. Ignores optional leading '@' sigil and '.ed25519' suffix.
159    ///
160    /// # Example
161    /// ```rust
162    /// use ssb_crypto::PublicKey;
163    /// let author = "@H2qXeS5sOKUqaGNFgRJ6qR48+lAeP0C9lq9IVlQMotc=.ed25519";
164    /// let pk = PublicKey::from_base64(author).unwrap();
165    /// ```
166    #[cfg(feature = "b64")]
167    pub fn from_base64(mut s: &str) -> Option<Self> {
168        if s.starts_with('@') {
169            s = &s[1..];
170        }
171        let mut buf = [0; Self::SIZE];
172        if crate::b64::decode(s, &mut buf, Some(".ed25519")) {
173            Some(Self(buf))
174        } else {
175            None
176        }
177    }
178
179    /// Does not include ".ed25519" suffix or a prefix sigil.
180    ///
181    /// # Example
182    /// ```rust
183    /// let s = "H2qXeS5sOKUqaGNFgRJ6qR48+lAeP0C9lq9IVlQMotc=";
184    /// let pk = ssb_crypto::PublicKey::from_base64(s).unwrap();
185    /// assert_eq!(pk.as_base64(), s);
186    /// ```
187    #[cfg(feature = "alloc")]
188    pub fn as_base64(&self) -> alloc::string::String {
189        base64::encode_config(&self.0, base64::STANDARD)
190    }
191
192    /// Verify that a signature was generated by this key's secret half for the given
193    /// bytes.
194    #[cfg(any(feature = "sodium", feature = "dalek"))]
195    pub fn verify(&self, sig: &Signature, b: &[u8]) -> bool {
196        sign::verify(self, sig, b)
197    }
198}
199
200/// A cryptographic signature of some content, generated by [`Keypair::sign`]
201/// and verified by [`PublicKey::verify`].
202///
203/// [`Keypair::sign`]: ./struct.Keypair.html#method.sign
204/// [`PublicKey::verify`]: ./struct.PublicKey.html#method.verify
205#[derive(AsBytes, FromBytes, Copy, Clone)]
206#[repr(C)]
207pub struct Signature(pub [u8; 64]);
208impl Signature {
209    /// Size of a signature, in bytes (== 64).
210    pub const SIZE: usize = size_of::<Self>();
211
212    /// Deserialize a signature from a byte slice. The slice length must be 64.
213    pub fn from_slice(s: &[u8]) -> Option<Self> {
214        if s.len() == Self::SIZE {
215            let mut out = Self([0; Self::SIZE]);
216            out.0.copy_from_slice(s);
217            Some(out)
218        } else {
219            None
220        }
221    }
222
223    /// Deserialize a signature from a base-64 encoded string. Ignores optional .sig.ed25519 suffix.
224    ///
225    /// # Example
226    /// ```rust
227    /// use ssb_crypto::Signature;
228    /// let s = "QTsCZ+INzDENs1dAdej14Lsp1v2UCXUtRZBv4HlDGo6WZn29ZYM5lZtxnyNC53LxX0ucY1x8NlC1A1RjY7FHBA==.sig.ed25519";
229    /// let sig = Signature::from_base64(s).unwrap();
230    /// ```
231    #[cfg(feature = "b64")]
232    pub fn from_base64(s: &str) -> Option<Self> {
233        let mut buf = [0; Self::SIZE];
234        if crate::b64::decode(s, &mut buf, Some(".sig.ed25519")) {
235            Some(Self(buf))
236        } else {
237            None
238        }
239    }
240
241    /// Does not include ".sig.ed25519" suffix or a prefix sigil.
242    ///
243    /// # Example
244    /// ```rust
245    /// use ssb_crypto::Signature;
246    /// let s = "QTsCZ+INzDENs1dAdej14Lsp1v2UCXUtRZBv4HlDGo6WZn29ZYM5lZtxnyNC53LxX0ucY1x8NlC1A1RjY7FHBA==";
247    /// let sig = Signature::from_base64(s).unwrap();
248    /// assert_eq!(sig.as_base64(), s);
249    /// ```
250    #[cfg(feature = "alloc")]
251    pub fn as_base64(&self) -> alloc::string::String {
252        base64::encode_config(&self.0[..], base64::STANDARD)
253    }
254}
255
256impl fmt::Debug for Signature {
257    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258        write!(f, "Signature({:?})", &self.0[..])
259    }
260}
261impl Eq for Signature {}
262impl PartialEq for Signature {
263    fn eq(&self, other: &Self) -> bool {
264        self.0.as_ref().eq(other.0.as_ref())
265    }
266}