ouisync/crypto/
sign.rs

1use crate::crypto::{Digest, Hashable};
2use ed25519_dalek::{self as ext, Signer, Verifier};
3use rand::{rngs::OsRng, CryptoRng, Rng};
4use serde::{Deserialize, Serialize};
5use sqlx::{
6    sqlite::{SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef},
7    Sqlite,
8};
9use std::{cmp::Ordering, fmt, str::FromStr};
10use thiserror::Error;
11
12#[derive(Serialize, Deserialize)]
13#[repr(transparent)]
14#[serde(transparent)]
15pub struct Keypair(ext::SigningKey);
16
17impl Keypair {
18    pub const SECRET_KEY_SIZE: usize = ext::SECRET_KEY_LENGTH;
19
20    pub fn generate<R: Rng + CryptoRng>(rng: &mut R) -> Self {
21        Self(ext::SigningKey::generate(rng))
22    }
23
24    pub fn random() -> Self {
25        Self::generate(&mut OsRng)
26    }
27
28    pub fn to_bytes(&self) -> [u8; Self::SECRET_KEY_SIZE] {
29        self.0.to_bytes()
30    }
31
32    pub fn public_key(&self) -> PublicKey {
33        PublicKey(self.0.verifying_key())
34    }
35
36    pub fn sign(&self, msg: &[u8]) -> Signature {
37        Signature(self.0.sign(msg))
38    }
39}
40
41impl From<&'_ [u8; Self::SECRET_KEY_SIZE]> for Keypair {
42    fn from(bytes: &'_ [u8; Self::SECRET_KEY_SIZE]) -> Self {
43        Self(ext::SigningKey::from(bytes))
44    }
45}
46
47impl TryFrom<&'_ [u8]> for Keypair {
48    type Error = SignatureError;
49
50    fn try_from(bytes: &'_ [u8]) -> Result<Self, Self::Error> {
51        Ok(Self(ext::SigningKey::try_from(bytes)?))
52    }
53}
54
55impl fmt::Debug for Keypair {
56    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
57        f.debug_struct("Keypair")
58            .field("public_key", &self.public_key())
59            .finish_non_exhaustive()
60    }
61}
62
63#[derive(PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize)]
64#[repr(transparent)]
65#[serde(transparent)]
66pub struct PublicKey(ext::VerifyingKey);
67
68impl PublicKey {
69    pub const SIZE: usize = ext::PUBLIC_KEY_LENGTH;
70
71    #[cfg(test)]
72    pub fn generate<R: Rng + CryptoRng>(rng: &mut R) -> Self {
73        Keypair::generate(rng).public_key()
74    }
75
76    #[cfg(test)]
77    pub fn random() -> Self {
78        Self::generate(&mut OsRng)
79    }
80
81    pub fn verify(&self, msg: &[u8], signature: &Signature) -> bool {
82        self.0.verify(msg, &signature.0).is_ok()
83    }
84
85    pub fn starts_with(&self, needle: &[u8]) -> bool {
86        self.0.as_ref().starts_with(needle)
87    }
88}
89
90impl PartialOrd for PublicKey {
91    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
92        Some(self.cmp(other))
93    }
94}
95
96impl Ord for PublicKey {
97    fn cmp(&self, other: &Self) -> Ordering {
98        self.0.as_bytes().cmp(other.0.as_bytes())
99    }
100}
101
102impl fmt::LowerHex for PublicKey {
103    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104        hex_fmt::HexFmt(self.0.as_bytes()).fmt(f)
105    }
106}
107
108impl Hashable for PublicKey {
109    fn update_hash<S: Digest>(&self, state: &mut S) {
110        self.0.as_bytes().update_hash(state)
111    }
112}
113
114impl AsRef<[u8]> for PublicKey {
115    fn as_ref(&self) -> &[u8] {
116        self.0.as_bytes()
117    }
118}
119
120impl TryFrom<&'_ [u8]> for PublicKey {
121    type Error = ext::SignatureError;
122
123    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
124        Ok(Self(bytes.try_into()?))
125    }
126}
127
128impl From<PublicKey> for [u8; PublicKey::SIZE] {
129    fn from(key: PublicKey) -> Self {
130        key.0.to_bytes()
131    }
132}
133
134impl fmt::Display for PublicKey {
135    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136        write!(f, "{self:x}")
137    }
138}
139
140impl fmt::Debug for PublicKey {
141    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
142        write!(f, "{self:<8x}")
143    }
144}
145
146impl FromStr for PublicKey {
147    type Err = ParseError;
148
149    fn from_str(s: &str) -> Result<Self, Self::Err> {
150        let mut bytes = [0; Self::SIZE];
151        hex::decode_to_slice(s, &mut bytes).map_err(|_| ParseError)?;
152        Self::try_from(&bytes[..]).map_err(|_| ParseError)
153    }
154}
155
156#[derive(Debug, Error)]
157#[error("failed to parse public key")]
158pub struct ParseError;
159
160derive_sqlx_traits_for_byte_array_wrapper!(PublicKey);
161
162#[cfg(test)]
163mod test_utils {
164    use super::{Keypair, PublicKey};
165    use proptest::{
166        arbitrary::{any, Arbitrary},
167        array::UniformArrayStrategy,
168        num,
169        strategy::{Map, NoShrink, Strategy},
170    };
171
172    impl Arbitrary for Keypair {
173        type Parameters = ();
174        type Strategy = Map<
175            NoShrink<UniformArrayStrategy<num::u8::Any, [u8; Keypair::SECRET_KEY_SIZE]>>,
176            fn([u8; Keypair::SECRET_KEY_SIZE]) -> Self,
177        >;
178
179        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
180            any::<[u8; Keypair::SECRET_KEY_SIZE]>()
181                .no_shrink()
182                .prop_map(|array| Keypair::from(&array))
183        }
184    }
185
186    impl Arbitrary for PublicKey {
187        type Parameters = ();
188        type Strategy = Map<<Keypair as Arbitrary>::Strategy, fn(Keypair) -> Self>;
189
190        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
191            any::<Keypair>().prop_map(|keypair| keypair.public_key())
192        }
193    }
194}
195
196#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
197#[repr(transparent)]
198pub struct Signature(ext::Signature);
199
200impl Signature {
201    pub const SIZE: usize = ext::SIGNATURE_LENGTH;
202
203    pub fn to_bytes(&self) -> [u8; Self::SIZE] {
204        self.0.to_bytes()
205    }
206}
207
208impl From<&'_ [u8; Self::SIZE]> for Signature {
209    fn from(bytes: &'_ [u8; Self::SIZE]) -> Self {
210        Self(ext::Signature::from(bytes))
211    }
212}
213
214impl TryFrom<&'_ [u8]> for Signature {
215    type Error = ext::SignatureError;
216
217    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
218        Ok(Signature(ext::Signature::try_from(bytes)?))
219    }
220}
221
222impl fmt::Debug for Signature {
223    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224        write!(f, "Signature(_)")
225    }
226}
227
228impl sqlx::Type<Sqlite> for Signature {
229    fn type_info() -> SqliteTypeInfo {
230        <&[u8] as sqlx::Type<Sqlite>>::type_info()
231    }
232}
233
234impl<'q> sqlx::Encode<'q, Sqlite> for &'q Signature {
235    fn encode_by_ref(
236        &self,
237        args: &mut Vec<SqliteArgumentValue<'q>>,
238    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
239        // It seems there is no way to avoid the allocation here because sqlx doesn't implement
240        // `Encode` for arrays.
241        sqlx::Encode::<Sqlite>::encode(self.to_bytes().to_vec(), args)
242    }
243}
244
245impl<'r> sqlx::Decode<'r, Sqlite> for Signature {
246    fn decode(value: SqliteValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
247        let slice = <&[u8] as sqlx::Decode<Sqlite>>::decode(value)?;
248        Ok(slice.try_into()?)
249    }
250}
251
252pub type SignatureError = ext::SignatureError;
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use rand::{rngs::StdRng, SeedableRng};
258
259    // This test asserts that signatures from the same keys and input are identical between
260    // different versions.
261    #[test]
262    fn compatibility() {
263        let mut rng = StdRng::seed_from_u64(0);
264        let keypair = Keypair::generate(&mut rng);
265
266        assert_eq!(
267            dump_signature(&keypair.sign(b"")),
268            "51ad17bc6bfbeeddd86c2a328d7a9b37197453244f4470a446ac9516acb4f243add7f93a5a6ba44bd21b9ed45c830dbbe28e2c40f7819d4c42c45b844258140a"
269        );
270
271        assert_eq!(
272            dump_signature(&keypair.sign(b"hello world")),
273            "bcbd9b3aee0031f9616ed873106f2a0a136572fb5182c71e8d56c1308098c7c687367608e99bb64ace8de09544e8d87dc46e0cdaa7d188ee78bfbfb7d754a703"
274        );
275
276        assert_eq!(
277            dump_signature(&keypair.sign(&rng.r#gen::<[u8; 32]>())),
278            "3230b7f98529273c71f8af92b1581d290bf424fd7bd5015399c6213cbc461ca79ff932a7fcbb5e19d2ef6efa8ed9b833b6d17431793facf1b810c3b579570d0d"
279        );
280    }
281
282    fn dump_signature(signature: &Signature) -> String {
283        hex::encode(signature.to_bytes())
284    }
285}