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
1234
    fn from(value: &'a Keypair) -> Self {
56
1234
        PublicKey((&value.0).into())
57
1234
    }
58
}
59

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

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

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

            
132
27632
    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
133
27632
        Ok(Self(ed25519_dalek::VerifyingKey::try_from(value)?))
134
27632
    }
135
}
136
impl<'a> From<&'a [u8; 32]> for Keypair {
137
8536
    fn from(value: &'a [u8; 32]) -> Self {
138
8536
        Self(ed25519_dalek::SigningKey::from(value))
139
8536
    }
140
}
141
impl From<[u8; 64]> for Signature {
142
6074
    fn from(value: [u8; 64]) -> Self {
143
6074
        Signature(value.into())
144
6074
    }
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
59156
    pub fn public(&self) -> &PublicKey {
183
59156
        &self.public
184
59156
    }
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
6802
    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
6802
        Signature(ed25519_dalek::hazmat::raw_sign::<Sha512>(
195
6802
            &self.secret,
196
6802
            message,
197
6802
            &self.public.0,
198
6802
        ))
199
6802
    }
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
8312
    pub fn to_secret_key_bytes(&self) -> [u8; 64] {
206
8312
        let mut output = [0_u8; 64];
207
8312
        output[0..32].copy_from_slice(&self.secret.scalar.to_bytes());
208
8312
        output[32..64].copy_from_slice(&self.secret.hash_prefix);
209
8312
        output
210
8312
    }
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
28444
    pub fn from_secret_key_bytes(bytes: [u8; 64]) -> Option<Self> {
219
28444
        let scalar = Option::from(Scalar::from_bytes_mod_order(
220
28444
            bytes[0..32].try_into().expect("wrong length on slice"),
221
        ))?;
222
28444
        let hash_prefix = bytes[32..64].try_into().expect("wrong length on slice");
223
28444
        let secret = ExpandedSecretKey {
224
28444
            scalar,
225
28444
            hash_prefix,
226
28444
        };
227
28444
        let public = PublicKey((&secret).into());
228
28444
        Some(Self { secret, public })
229
28444
    }
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
882
    fn from(kp: &'a Keypair) -> ExpandedKeypair {
238
882
        ExpandedKeypair {
239
882
            secret: kp.as_bytes().into(),
240
882
            public: kp.into(),
241
882
        }
242
882
    }
243
}
244

            
245
impl From<ExpandedKeypair> for PublicKey {
246
    fn from(ekp: ExpandedKeypair) -> PublicKey {
247
        ekp.public
248
    }
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
4223212
    pub fn new(id: [u8; 32]) -> Self {
293
4223212
        Ed25519Identity { id: id.into() }
294
4223212
    }
295
    /// If `id` is of the correct length, wrap it in an Ed25519Identity.
296
3324202
    pub fn from_bytes(id: &[u8]) -> Option<Self> {
297
3324202
        Some(Ed25519Identity::new(id.try_into().ok()?))
298
3324202
    }
299
    /// Return a reference to the bytes in this key.
300
315128
    pub fn as_bytes(&self) -> &[u8] {
301
315128
        &self.id.as_ref()[..]
302
315128
    }
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
90
    pub fn from_base64(s: &str) -> Option<Self> {
308
90
        let bytes = Base64Unpadded::decode_vec(s).ok()?;
309
90
        Ed25519Identity::from_bytes(&bytes)
310
90
    }
311
}
312

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

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

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

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

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

            
346
impl TryFrom<Ed25519Identity> for PublicKey {
347
    type Error = ed25519_dalek::SignatureError;
348
31328
    fn try_from(id: Ed25519Identity) -> Result<PublicKey, Self::Error> {
349
31328
        (&id).try_into()
350
31328
    }
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
50248
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
361
50248
        write!(f, "{}", Base64Unpadded::encode_string(self.id.as_ref()))
362
50248
    }
363
}
364

            
365
impl Debug for Ed25519Identity {
366
36784
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
367
36784
        write!(f, "Ed25519Identity {{ {} }}", self)
368
36784
    }
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
88
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
375
88
        write!(
376
88
            f,
377
88
            "{}…",
378
88
            &Base64Unpadded::encode_string(self.id.as_ref())[..2]
379
        )
380
88
    }
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
33356
    pub fn new(key: PublicKey, sig: Signature, text: &[u8]) -> Self {
473
33356
        ValidatableEd25519Signature {
474
33356
            key,
475
33356
            sig,
476
33356
            entire_text_of_signed_thing: text.into(),
477
33356
        }
478
33356
    }
479

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

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

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

            
498
16984
    fn as_ed25519(&self) -> Option<&ValidatableEd25519Signature> {
499
16984
        Some(self)
500
16984
    }
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
7920
pub fn validate_batch(sigs: &[&ValidatableEd25519Signature]) -> bool {
515
    use crate::pk::ValidatableSignature;
516
7920
    if sigs.is_empty() {
517
        // ed25519_dalek has nonzero cost for a batch-verification of
518
        // zero sigs.
519
1672
        true
520
6248
    } else if sigs.len() == 1 {
521
        // Validating one signature in the traditional way is faster.
522
88
        sigs[0].is_valid()
523
    } else {
524
6160
        let mut ed_msgs = Vec::new();
525
6160
        let mut ed_sigs = Vec::new();
526
6160
        let mut ed_pks = Vec::new();
527
25344
        for ed_sig in sigs {
528
19184
            let (pk, sig, msg) = ed_sig.as_parts();
529
19184
            ed_sigs.push(sig.0);
530
19184
            ed_pks.push(pk.0);
531
19184
            ed_msgs.push(msg);
532
19184
        }
533
6160
        ed25519_dalek::verify_batch(&ed_msgs[..], &ed_sigs[..], &ed_pks[..]).is_ok()
534
    }
535
7920
}
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
40656
    fn public_key(&self) -> PublicKey {
545
40656
        Keypair::verifying_key(self)
546
40656
    }
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
39952
    fn sign(&self, message: &[u8]) -> Signature {
557
39952
        Keypair::sign(self, message)
558
39952
    }
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
}