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

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

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

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

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

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

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

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

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

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

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

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

            
365
impl Debug for Ed25519Identity {
366
33858
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
367
33858
        write!(f, "Ed25519Identity {{ {} }}", self)
368
33858
    }
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
81
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
375
81
        write!(
376
81
            f,
377
            "{}…",
378
81
            &Base64Unpadded::encode_string(self.id.as_ref())[..2]
379
        )
380
81
    }
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
18718
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
389
18718
    where
390
18718
        S: serde::Serializer,
391
    {
392
18718
        if serializer.is_human_readable() {
393
18716
            serializer.serialize_str(&Base64Unpadded::encode_string(self.id.as_ref()))
394
        } else {
395
2
            serializer.serialize_bytes(&self.id.as_ref()[..])
396
        }
397
18718
    }
398
}
399

            
400
impl<'de> serde::Deserialize<'de> for Ed25519Identity {
401
578
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
402
578
    where
403
578
        D: serde::Deserializer<'de>,
404
    {
405
578
        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
574
                fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
414
574
                where
415
574
                    E: serde::de::Error,
416
                {
417
574
                    let bytes = Base64Unpadded::decode_vec(s).map_err(E::custom)?;
418
574
                    Ed25519Identity::from_bytes(&bytes)
419
574
                        .ok_or_else(|| E::custom("wrong length for Ed25519 public key"))
420
574
                }
421
            }
422

            
423
574
            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
578
    }
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
34267
    pub fn new(key: PublicKey, sig: Signature, text: &[u8]) -> Self {
473
34267
        ValidatableEd25519Signature {
474
34267
            key,
475
34267
            sig,
476
34267
            entire_text_of_signed_thing: text.into(),
477
34267
        }
478
34267
    }
479

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

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

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

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