1
//! Re-exporting Ed25519 implementations, and related utilities.
2
//!
3
//! Here we re-export types from [`ed25519_dalek`] that implement the
4
//! Ed25519 signature algorithm.  (TODO: Eventually, this module
5
//! should probably be replaced with a wrapper that uses the ed25519
6
//! trait and the Signature trait.)
7
//!
8
//! We additionally provide an `Ed25519Identity` type to represent the
9
//! unvalidated Ed25519 "identity keys" that we use throughout the Tor
10
//! protocol to uniquely identify a relay.
11

            
12
use base64ct::{Base64Unpadded, Encoding as _};
13
use curve25519_dalek::Scalar;
14
use derive_deftly::Deftly;
15
use std::fmt::{self, Debug, Display, Formatter};
16
use subtle::{Choice, ConstantTimeEq};
17

            
18
#[cfg(feature = "memquota-memcost")]
19
use tor_memquota_cost::derive_deftly_template_HasMemoryCost;
20

            
21
use ed25519_dalek::hazmat::ExpandedSecretKey;
22
use ed25519_dalek::{Signer as _, Verifier as _};
23

            
24
use crate::util::{
25
    ct::{
26
        CtByteArray, derive_deftly_template_ConstantTimeEq,
27
        derive_deftly_template_PartialEqFromCtEq,
28
    },
29
    rng::RngCompat,
30
};
31

            
32
/// An Ed25519 signature.
33
///
34
/// See [`ed25519_dalek::Signature`] for more information.
35
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36
pub struct Signature(pub(crate) ed25519_dalek::Signature);
37

            
38
/// An Ed25519 keypair.
39
///
40
/// (We do not provide a separate "private key only" type.)
41
///
42
/// See [`ed25519_dalek::SigningKey`] for more information.
43
#[derive(Debug, Deftly)]
44
#[derive_deftly(ConstantTimeEq)]
45
pub struct Keypair(pub(crate) ed25519_dalek::SigningKey);
46

            
47
/// An Ed25519 public key.
48
///
49
/// See [`ed25519_dalek::VerifyingKey`] for more information.
50
#[derive(Clone, Copy, Debug, Eq, Deftly)]
51
#[derive_deftly(PartialEqFromCtEq)]
52
pub struct PublicKey(pub(crate) ed25519_dalek::VerifyingKey);
53

            
54
impl<'a> From<&'a Keypair> for PublicKey {
55
4266
    fn from(value: &'a Keypair) -> Self {
56
4266
        PublicKey((&value.0).into())
57
4266
    }
58
}
59

            
60
impl ConstantTimeEq for PublicKey {
61
32134
    fn ct_eq(&self, other: &Self) -> Choice {
62
32134
        self.as_bytes().ct_eq(other.as_bytes())
63
32134
    }
64
}
65

            
66
impl PublicKey {
67
    /// Construct a public key from its byte representation.
68
81988
    pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, signature::Error> {
69
81988
        Ok(PublicKey(ed25519_dalek::VerifyingKey::from_bytes(bytes)?))
70
81988
    }
71

            
72
    /// Return a reference to the byte representation of this public key.
73
221732
    pub fn as_bytes(&self) -> &[u8; 32] {
74
221732
        self.0.as_bytes()
75
221732
    }
76
    /// Return the byte representation of this public key.
77
15558
    pub fn to_bytes(&self) -> [u8; 32] {
78
15558
        self.0.to_bytes()
79
15558
    }
80
    /// Verify a signature using this public key.
81
    ///
82
    /// See [`ed25519_dalek::VerifyingKey::verify`] for more information.
83
2168
    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), signature::Error> {
84
2168
        self.0.verify(message, &signature.0)
85
2168
    }
86
}
87
impl Keypair {
88
    /// Generate a new random ed25519 keypair.
89
4176
    pub fn generate<R: rand_core::RngCore + rand_core::CryptoRng>(csprng: &mut R) -> Self {
90
4176
        Self(ed25519_dalek::SigningKey::generate(&mut RngCompat::new(
91
4176
            csprng,
92
4176
        )))
93
4176
    }
94
    /// Construct an ed25519 keypair from the byte representation of its secret key.
95
82
    pub fn from_bytes(bytes: &[u8; 32]) -> Self {
96
82
        Self(ed25519_dalek::SigningKey::from_bytes(bytes))
97
82
    }
98
    /// Return a reference to the byte representation of the secret key in this keypair.
99
9022
    pub fn as_bytes(&self) -> &[u8; 32] {
100
9022
        self.0.as_bytes()
101
9022
    }
102
    /// Return to the byte representation of the secret key in this keypair.
103
330
    pub fn to_bytes(&self) -> [u8; 32] {
104
330
        self.0.to_bytes()
105
330
    }
106
    /// Return the public key in this keypair.
107
87904
    pub fn verifying_key(&self) -> PublicKey {
108
87904
        PublicKey(*self.0.as_ref())
109
87904
    }
110
    /// Verify a signature generated with this keypair.
111
246
    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), signature::Error> {
112
246
        self.0.verify(message, &signature.0)
113
246
    }
114
    /// Sign a message using this keypair.
115
41902
    pub fn sign(&self, message: &[u8]) -> Signature {
116
41902
        Signature(self.0.sign(message))
117
41902
    }
118
}
119
impl Signature {
120
    /// Construct this signature from its byte representation.
121
26486
    pub fn from_bytes(bytes: &[u8; 64]) -> Self {
122
26486
        Self(ed25519_dalek::Signature::from_bytes(bytes))
123
26486
    }
124
    /// Return the byte representation of this signature.
125
47642
    pub fn to_bytes(&self) -> [u8; 64] {
126
47642
        self.0.to_bytes()
127
47642
    }
128
}
129
impl<'a> TryFrom<&'a [u8]> for PublicKey {
130
    type Error = signature::Error;
131

            
132
23780
    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
133
23780
        Ok(Self(ed25519_dalek::VerifyingKey::try_from(value)?))
134
23780
    }
135
}
136
impl<'a> From<&'a [u8; 32]> for Keypair {
137
8774
    fn from(value: &'a [u8; 32]) -> Self {
138
8774
        Self(ed25519_dalek::SigningKey::from(value))
139
8774
    }
140
}
141
impl From<[u8; 64]> for Signature {
142
5660
    fn from(value: [u8; 64]) -> Self {
143
5660
        Signature(value.into())
144
5660
    }
145
}
146
impl<'a> From<&'a [u8; 64]> for Signature {
147
    fn from(value: &'a [u8; 64]) -> Self {
148
        Signature(value.into())
149
    }
150
}
151

            
152
/// The length of an ED25519 identity, in bytes.
153
pub const ED25519_ID_LEN: usize = 32;
154

            
155
/// The length of an ED25519 signature, in bytes.
156
pub const ED25519_SIGNATURE_LEN: usize = 64;
157

            
158
/// A variant of [`Keypair`] containing an [`ExpandedSecretKey`].
159
///
160
/// In the Tor protocol, we use this type for blinded onion service identity keys
161
/// (KS_hs_blind_id).  Since their scalar values are computed, rather than taken
162
/// directly from a
163
/// SHA-512 transformation of a SecretKey, we cannot use the regular `Keypair`
164
/// type.
165
#[allow(clippy::exhaustive_structs)]
166
#[derive(Deftly)]
167
#[derive_deftly(ConstantTimeEq)]
168
pub struct ExpandedKeypair {
169
    /// The secret part of the key.
170
    pub(crate) secret: ExpandedSecretKey,
171
    /// The public part of this key.
172
    ///
173
    /// NOTE: As with [`ed25519_dalek::SigningKey`], this public key _must_ be
174
    /// the public key matching `secret`.  Putting a different public key in
175
    /// here would enable a class of attacks against ed25519 and enable secret
176
    /// key recovery.
177
    pub(crate) public: PublicKey,
178
}
179

            
180
impl ExpandedKeypair {
181
    /// Return the public part of this expanded keypair.
182
50860
    pub fn public(&self) -> &PublicKey {
183
50860
        &self.public
184
50860
    }
185

            
186
    // NOTE: There is deliberately no secret() function.  If we had one, we
187
    // would be exposing an unescorted secret key, which is part of
188
    // ed25519::hazmat.
189

            
190
    /// Compute a signature over a message using this keypair.
191
5684
    pub fn sign(&self, message: &[u8]) -> Signature {
192
        use sha2::Sha512;
193
        // See notes on ExpandedKeypair about why this hazmat is okay to use.
194
5684
        Signature(ed25519_dalek::hazmat::raw_sign::<Sha512>(
195
5684
            &self.secret,
196
5684
            message,
197
5684
            &self.public.0,
198
5684
        ))
199
5684
    }
200

            
201
    /// Return a representation of the secret key in this keypair.
202
    ///
203
    /// (Since it is an expanded secret key, we represent it as its scalar part
204
    /// followed by its hash_prefix.)
205
7092
    pub fn to_secret_key_bytes(&self) -> [u8; 64] {
206
7092
        let mut output = [0_u8; 64];
207
7092
        output[0..32].copy_from_slice(&self.secret.scalar.to_bytes());
208
7092
        output[32..64].copy_from_slice(&self.secret.hash_prefix);
209
7092
        output
210
7092
    }
211

            
212
    /// Reconstruct a key from its byte representation as returned by
213
    /// `to_secret_key_bytes()`.
214
    ///
215
    /// Return None if the input cannot be the output of `to_secret_key_bytes()`.
216
    //
217
    // NOTE: Returning None is a bit silly, but that's what Dalek does.
218
24538
    pub fn from_secret_key_bytes(bytes: [u8; 64]) -> Option<Self> {
219
24538
        let scalar = Option::from(Scalar::from_bytes_mod_order(
220
24538
            bytes[0..32].try_into().expect("wrong length on slice"),
221
        ))?;
222
24538
        let hash_prefix = bytes[32..64].try_into().expect("wrong length on slice");
223
24538
        let secret = ExpandedSecretKey {
224
24538
            scalar,
225
24538
            hash_prefix,
226
24538
        };
227
24538
        let public = PublicKey((&secret).into());
228
24538
        Some(Self { secret, public })
229
24538
    }
230

            
231
    // NOTE: There is deliberately no constructor here that takes a (secret,
232
    // public) pair.  If there were, you could construct a pair with a
233
    // mismatched public key.
234
}
235

            
236
impl<'a> From<&'a Keypair> for ExpandedKeypair {
237
822
    fn from(kp: &'a Keypair) -> ExpandedKeypair {
238
822
        ExpandedKeypair {
239
822
            secret: kp.as_bytes().into(),
240
822
            public: kp.into(),
241
822
        }
242
822
    }
243
}
244

            
245
impl From<ExpandedKeypair> for PublicKey {
246
328
    fn from(ekp: ExpandedKeypair) -> PublicKey {
247
328
        ekp.public
248
328
    }
249
}
250

            
251
/// An unchecked, unvalidated Ed25519 key.
252
///
253
/// This key is an "identity" in the sense that it identifies (up to) one
254
/// Ed25519 key.  It may also represent the identity for a particular entity,
255
/// such as a relay or an onion service.
256
///
257
/// This type is distinct from an Ed25519 [`PublicKey`] for several reasons:
258
///  * We're storing it in a compact format, whereas the public key
259
///    implementation might want an expanded form for more efficient key
260
///    validation.
261
///  * This type hasn't checked whether the bytes here actually _are_ a valid
262
///    Ed25519 public key.
263
#[derive(Clone, Copy, Hash, PartialOrd, Ord, Eq, PartialEq)]
264
#[cfg_attr(
265
    feature = "memquota-memcost",
266
    derive(Deftly),
267
    derive_deftly(HasMemoryCost)
268
)]
269
pub struct Ed25519Identity {
270
    /// A raw unchecked Ed25519 public key.
271
    id: CtByteArray<ED25519_ID_LEN>,
272
}
273

            
274
impl Ed25519Identity {
275
    /// Construct a new Ed25519 identity from a 32-byte sequence.
276
    ///
277
    /// This might or might not actually be a valid Ed25519 public key.
278
    ///
279
    /// ```
280
    /// use tor_llcrypto::pk::ed25519::{Ed25519Identity, PublicKey};
281
    ///
282
    /// let bytes = b"klsadjfkladsfjklsdafkljasdfsdsd!";
283
    /// let id = Ed25519Identity::new(*bytes);
284
    /// let pk: Result<PublicKey,_> = (&id).try_into();
285
    /// assert!(pk.is_ok());
286
    ///
287
    /// let bytes = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
288
    /// let id = Ed25519Identity::new(*bytes);
289
    /// let pk: Result<PublicKey,_> = (&id).try_into();
290
    /// assert!(pk.is_err());
291
    /// ```
292
3928706
    pub fn new(id: [u8; 32]) -> Self {
293
3928706
        Ed25519Identity { id: id.into() }
294
3928706
    }
295
    /// If `id` is of the correct length, wrap it in an Ed25519Identity.
296
3089516
    pub fn from_bytes(id: &[u8]) -> Option<Self> {
297
3089516
        Some(Ed25519Identity::new(id.try_into().ok()?))
298
3089516
    }
299
    /// Return a reference to the bytes in this key.
300
289214
    pub fn as_bytes(&self) -> &[u8] {
301
289214
        &self.id.as_ref()[..]
302
289214
    }
303
    /// Decode an `Ed25519Identity` from a base64-encoded string.
304
    ///
305
    /// The string must have no padding, spaces, or any extra characters.
306
    // We decode without padding to match the serde deserialize impl.
307
84
    pub fn from_base64(s: &str) -> Option<Self> {
308
84
        let bytes = Base64Unpadded::decode_vec(s).ok()?;
309
84
        Ed25519Identity::from_bytes(&bytes)
310
84
    }
311
}
312

            
313
impl From<[u8; ED25519_ID_LEN]> for Ed25519Identity {
314
810162
    fn from(id: [u8; ED25519_ID_LEN]) -> Self {
315
810162
        Ed25519Identity::new(id)
316
810162
    }
317
}
318

            
319
impl From<Ed25519Identity> for [u8; ED25519_ID_LEN] {
320
15006
    fn from(value: Ed25519Identity) -> Self {
321
15006
        value.id.into()
322
15006
    }
323
}
324

            
325
impl From<PublicKey> for Ed25519Identity {
326
98154
    fn from(pk: PublicKey) -> Self {
327
98154
        (&pk).into()
328
98154
    }
329
}
330

            
331
impl From<&PublicKey> for Ed25519Identity {
332
140056
    fn from(pk: &PublicKey) -> Self {
333
        // This unwrap is safe because the public key is always 32 bytes
334
        // long.
335
140056
        Ed25519Identity::from_bytes(pk.as_bytes()).expect("Ed25519 public key had wrong length?")
336
140056
    }
337
}
338

            
339
impl TryFrom<&Ed25519Identity> for PublicKey {
340
    type Error = ed25519_dalek::SignatureError;
341
44854
    fn try_from(id: &Ed25519Identity) -> Result<PublicKey, Self::Error> {
342
44854
        PublicKey::from_bytes(id.id.as_ref())
343
44854
    }
344
}
345

            
346
impl TryFrom<Ed25519Identity> for PublicKey {
347
    type Error = ed25519_dalek::SignatureError;
348
28864
    fn try_from(id: Ed25519Identity) -> Result<PublicKey, Self::Error> {
349
28864
        (&id).try_into()
350
28864
    }
351
}
352

            
353
impl ConstantTimeEq for Ed25519Identity {
354
    fn ct_eq(&self, other: &Self) -> Choice {
355
        self.id.ct_eq(&other.id)
356
    }
357
}
358

            
359
impl Display for Ed25519Identity {
360
45920
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
361
45920
        write!(f, "{}", Base64Unpadded::encode_string(self.id.as_ref()))
362
45920
    }
363
}
364

            
365
impl Debug for Ed25519Identity {
366
34276
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
367
34276
        write!(f, "Ed25519Identity {{ {} }}", self)
368
34276
    }
369
}
370

            
371
impl safelog::Redactable for Ed25519Identity {
372
    /// Warning: This displays 12 bits of the ed25519 identity, which is
373
    /// enough to narrow down a public relay by a great deal.
374
82
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
375
82
        write!(
376
82
            f,
377
82
            "{}…",
378
82
            &Base64Unpadded::encode_string(self.id.as_ref())[..2]
379
        )
380
82
    }
381

            
382
    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383
        write!(f, "Ed25519Identity {{ {} }}", self.redacted())
384
    }
385
}
386

            
387
impl serde::Serialize for Ed25519Identity {
388
18308
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
389
18308
    where
390
18308
        S: serde::Serializer,
391
    {
392
18308
        if serializer.is_human_readable() {
393
18306
            serializer.serialize_str(&Base64Unpadded::encode_string(self.id.as_ref()))
394
        } else {
395
2
            serializer.serialize_bytes(&self.id.as_ref()[..])
396
        }
397
18308
    }
398
}
399

            
400
impl<'de> serde::Deserialize<'de> for Ed25519Identity {
401
572
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
402
572
    where
403
572
        D: serde::Deserializer<'de>,
404
    {
405
572
        if deserializer.is_human_readable() {
406
            /// Helper for deserialization
407
            struct EdIdentityVisitor;
408
            impl<'de> serde::de::Visitor<'de> for EdIdentityVisitor {
409
                type Value = Ed25519Identity;
410
                fn expecting(&self, fmt: &mut std::fmt::Formatter<'_>) -> fmt::Result {
411
                    fmt.write_str("base64-encoded Ed25519 public key")
412
                }
413
568
                fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
414
568
                where
415
568
                    E: serde::de::Error,
416
                {
417
568
                    let bytes = Base64Unpadded::decode_vec(s).map_err(E::custom)?;
418
568
                    Ed25519Identity::from_bytes(&bytes)
419
568
                        .ok_or_else(|| E::custom("wrong length for Ed25519 public key"))
420
568
                }
421
            }
422

            
423
568
            deserializer.deserialize_str(EdIdentityVisitor)
424
        } else {
425
            /// Helper for deserialization
426
            struct EdIdentityVisitor;
427
            impl<'de> serde::de::Visitor<'de> for EdIdentityVisitor {
428
                type Value = Ed25519Identity;
429
                fn expecting(&self, fmt: &mut std::fmt::Formatter<'_>) -> fmt::Result {
430
                    fmt.write_str("ed25519 public key")
431
                }
432
4
                fn visit_bytes<E>(self, bytes: &[u8]) -> Result<Self::Value, E>
433
4
                where
434
4
                    E: serde::de::Error,
435
                {
436
4
                    Ed25519Identity::from_bytes(bytes)
437
4
                        .ok_or_else(|| E::custom("wrong length for ed25519 public key"))
438
4
                }
439
            }
440
4
            deserializer.deserialize_bytes(EdIdentityVisitor)
441
        }
442
572
    }
443
}
444

            
445
/// An ed25519 signature, plus the document that it signs and its
446
/// public key.
447
#[derive(Clone, Debug)]
448
#[cfg_attr(
449
    feature = "memquota-memcost",
450
    derive(Deftly),
451
    derive_deftly(HasMemoryCost)
452
)]
453
pub struct ValidatableEd25519Signature {
454
    /// The key that allegedly produced the signature
455
    #[cfg_attr(feature = "memquota-memcost", deftly(has_memory_cost(copy)))]
456
    key: PublicKey,
457
    /// The alleged signature
458
    #[cfg_attr(feature = "memquota-memcost", deftly(has_memory_cost(copy)))]
459
    sig: Signature,
460
    /// The entire body of text that is allegedly signed here.
461
    ///
462
    /// TODO: It's not so good to have this included here; it
463
    /// would be better to have a patch to ed25519_dalek to allow
464
    /// us to pre-hash the signed thing, and just store a digest.
465
    /// We can't use that with the 'prehash' variant of ed25519,
466
    /// since that has different constants.
467
    entire_text_of_signed_thing: Vec<u8>,
468
}
469

            
470
impl ValidatableEd25519Signature {
471
    /// Create a new ValidatableEd25519Signature
472
30754
    pub fn new(key: PublicKey, sig: Signature, text: &[u8]) -> Self {
473
30754
        ValidatableEd25519Signature {
474
30754
            key,
475
30754
            sig,
476
30754
            entire_text_of_signed_thing: text.into(),
477
30754
        }
478
30754
    }
479

            
480
    /// View the interior of this signature object.
481
16564
    pub(crate) fn as_parts(&self) -> (&PublicKey, &Signature, &[u8]) {
482
16564
        (&self.key, &self.sig, &self.entire_text_of_signed_thing[..])
483
16564
    }
484

            
485
    /// Return a reference to the underlying Signature.
486
492
    pub fn signature(&self) -> &Signature {
487
492
        &self.sig
488
492
    }
489
}
490

            
491
impl super::ValidatableSignature for ValidatableEd25519Signature {
492
1562
    fn is_valid(&self) -> bool {
493
1562
        self.key
494
1562
            .verify(&self.entire_text_of_signed_thing[..], &self.sig)
495
1562
            .is_ok()
496
1562
    }
497

            
498
15826
    fn as_ed25519(&self) -> Option<&ValidatableEd25519Signature> {
499
15826
        Some(self)
500
15826
    }
501
}
502

            
503
/// Perform a batch verification operation on the provided signatures
504
///
505
/// Return `true` if _every_ signature is valid; otherwise return `false`.
506
///
507
/// Note that the mathematics for batch validation are slightly
508
/// different than those for normal one-signature validation.  Because
509
/// of this, it is possible for an ostensible signature that passes
510
/// one validation algorithm might fail the other.  (Well-formed
511
/// signatures generated by a correct Ed25519 implementation will
512
/// always pass both kinds of validation, and an attacker should not
513
/// be able to forge a signature that passes either kind.)
514
6724
pub fn validate_batch(sigs: &[&ValidatableEd25519Signature]) -> bool {
515
    use crate::pk::ValidatableSignature;
516
6724
    if sigs.is_empty() {
517
        // ed25519_dalek has nonzero cost for a batch-verification of
518
        // zero sigs.
519
1558
        true
520
5166
    } else if sigs.len() == 1 {
521
        // Validating one signature in the traditional way is faster.
522
82
        sigs[0].is_valid()
523
    } else {
524
5084
        let mut ed_msgs = Vec::new();
525
5084
        let mut ed_sigs = Vec::new();
526
5084
        let mut ed_pks = Vec::new();
527
21648
        for ed_sig in sigs {
528
16564
            let (pk, sig, msg) = ed_sig.as_parts();
529
16564
            ed_sigs.push(sig.0);
530
16564
            ed_pks.push(pk.0);
531
16564
            ed_msgs.push(msg);
532
16564
        }
533
5084
        ed25519_dalek::verify_batch(&ed_msgs[..], &ed_sigs[..], &ed_pks[..]).is_ok()
534
    }
535
6724
}
536

            
537
/// An object that has an Ed25519 [`PublicKey`].
538
pub trait Ed25519PublicKey {
539
    /// Get the Ed25519 [`PublicKey`].
540
    fn public_key(&self) -> PublicKey;
541
}
542

            
543
impl Ed25519PublicKey for Keypair {
544
37884
    fn public_key(&self) -> PublicKey {
545
37884
        Keypair::verifying_key(self)
546
37884
    }
547
}
548

            
549
/// An object that can generate Ed25519 signatures.
550
pub trait Ed25519SigningKey {
551
    /// Sign a message with this key.
552
    fn sign(&self, message: &[u8]) -> Signature;
553
}
554

            
555
impl Ed25519SigningKey for Keypair {
556
34112
    fn sign(&self, message: &[u8]) -> Signature {
557
34112
        Keypair::sign(self, message)
558
34112
    }
559
}
560
impl Ed25519SigningKey for ExpandedKeypair {
561
    fn sign(&self, message: &[u8]) -> Signature {
562
        ExpandedKeypair::sign(self, message)
563
    }
564
}
565

            
566
#[cfg(test)]
567
mod test {
568
    // @@ begin test lint list maintained by maint/add_warning @@
569
    #![allow(clippy::bool_assert_comparison)]
570
    #![allow(clippy::clone_on_copy)]
571
    #![allow(clippy::dbg_macro)]
572
    #![allow(clippy::mixed_attributes_style)]
573
    #![allow(clippy::print_stderr)]
574
    #![allow(clippy::print_stdout)]
575
    #![allow(clippy::single_char_pattern)]
576
    #![allow(clippy::unwrap_used)]
577
    #![allow(clippy::unchecked_time_subtraction)]
578
    #![allow(clippy::useless_vec)]
579
    #![allow(clippy::needless_pass_by_value)]
580
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
581

            
582
    use super::*;
583

            
584
    #[test]
585
    fn ed_from_base64() {
586
        let id =
587
            Ed25519Identity::from_base64("qpL/LxLYVEXghU76iG3LsSI/UW7MBpIROZK0AB18560").unwrap();
588

            
589
        assert_eq!(
590
            id,
591
            Ed25519Identity::from([
592
                0xaa, 0x92, 0xff, 0x2f, 0x12, 0xd8, 0x54, 0x45, 0xe0, 0x85, 0x4e, 0xfa, 0x88, 0x6d,
593
                0xcb, 0xb1, 0x22, 0x3f, 0x51, 0x6e, 0xcc, 0x06, 0x92, 0x11, 0x39, 0x92, 0xb4, 0x00,
594
                0x1d, 0x7c, 0xe7, 0xad
595
            ]),
596
        );
597
    }
598
}