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}