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

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

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

            
72
    /// Return a reference to the byte representation of this public key.
73
276836
    pub fn as_bytes(&self) -> &[u8; 32] {
74
276836
        self.0.as_bytes()
75
276836
    }
76
    /// Return the byte representation of this public key.
77
16870
    pub fn to_bytes(&self) -> [u8; 32] {
78
16870
        self.0.to_bytes()
79
16870
    }
80
    /// Verify a signature using this public key.
81
    ///
82
    /// See [`ed25519_dalek::VerifyingKey::verify`] for more information.
83
3808
    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), signature::Error> {
84
3808
        self.0.verify(message, &signature.0)
85
3808
    }
86
}
87
impl Keypair {
88
    /// Generate a new random ed25519 keypair.
89
5363
    pub fn generate<R: rand_core::Rng + rand_core::CryptoRng>(csprng: &mut R) -> Self {
90
5363
        Self(ed25519_dalek::SigningKey::generate(&mut RngCompat::new(
91
5363
            csprng,
92
5363
        )))
93
5363
    }
94
    /// Construct an ed25519 keypair from the byte representation of its secret key.
95
164
    pub fn from_bytes(bytes: &[u8; 32]) -> Self {
96
164
        Self(ed25519_dalek::SigningKey::from_bytes(bytes))
97
164
    }
98
    /// Return a reference to the byte representation of the secret key in this keypair.
99
10334
    pub fn as_bytes(&self) -> &[u8; 32] {
100
10334
        self.0.as_bytes()
101
10334
    }
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
102172
    pub fn verifying_key(&self) -> PublicKey {
108
102172
        PublicKey(*self.0.as_ref())
109
102172
    }
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
48216
    pub fn sign(&self, message: &[u8]) -> Signature {
116
48216
        Signature(self.0.sign(message))
117
48216
    }
118
}
119
impl Signature {
120
    /// Construct this signature from its byte representation.
121
33374
    pub fn from_bytes(bytes: &[u8; 64]) -> Self {
122
33374
        Self(ed25519_dalek::Signature::from_bytes(bytes))
123
33374
    }
124
    /// Return the byte representation of this signature.
125
54858
    pub fn to_bytes(&self) -> [u8; 64] {
126
54858
        self.0.to_bytes()
127
54858
    }
128
}
129
impl<'a> TryFrom<&'a [u8]> for PublicKey {
130
    type Error = signature::Error;
131

            
132
26404
    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
133
26404
        Ok(Self(ed25519_dalek::VerifyingKey::try_from(value)?))
134
26404
    }
135
}
136
impl<'a> From<&'a [u8; 32]> for Keypair {
137
10168
    fn from(value: &'a [u8; 32]) -> Self {
138
10168
        Self(ed25519_dalek::SigningKey::from(value))
139
10168
    }
140
}
141
impl From<[u8; 64]> for Signature {
142
6316
    fn from(value: [u8; 64]) -> Self {
143
6316
        Signature(value.into())
144
6316
    }
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
56108
    pub fn public(&self) -> &PublicKey {
183
56108
        &self.public
184
56108
    }
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
6340
    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
6340
        Signature(ed25519_dalek::hazmat::raw_sign::<Sha512>(
195
6340
            &self.secret,
196
6340
            message,
197
6340
            &self.public.0,
198
6340
        ))
199
6340
    }
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
7748
    pub fn to_secret_key_bytes(&self) -> [u8; 64] {
206
7748
        let mut output = [0_u8; 64];
207
7748
        output[0..32].copy_from_slice(&self.secret.scalar.to_bytes());
208
7748
        output[32..64].copy_from_slice(&self.secret.hash_prefix);
209
7748
        output
210
7748
    }
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
27162
    pub fn from_secret_key_bytes(bytes: [u8; 64]) -> Option<Self> {
219
27162
        let scalar = Option::from(Scalar::from_bytes_mod_order(
220
27162
            bytes[0..32].try_into().expect("wrong length on slice"),
221
        ))?;
222
27162
        let hash_prefix = bytes[32..64].try_into().expect("wrong length on slice");
223
27162
        let secret = ExpandedSecretKey {
224
27162
            scalar,
225
27162
            hash_prefix,
226
27162
        };
227
27162
        let public = PublicKey((&secret).into());
228
27162
        Some(Self { secret, public })
229
27162
    }
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
4112550
    pub fn new(id: [u8; 32]) -> Self {
293
4112550
        Ed25519Identity { id: id.into() }
294
4112550
    }
295
    /// If `id` is of the correct length, wrap it in an Ed25519Identity.
296
3245480
    pub fn from_bytes(id: &[u8]) -> Option<Self> {
297
3245480
        Some(Ed25519Identity::new(id.try_into().ok()?))
298
3245480
    }
299
    /// Return a reference to the bytes in this key.
300
309632
    pub fn as_bytes(&self) -> &[u8] {
301
309632
        &self.id.as_ref()[..]
302
309632
    }
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
166
    pub fn from_base64(s: &str) -> Option<Self> {
308
166
        let bytes = Base64Unpadded::decode_vec(s).ok()?;
309
166
        Ed25519Identity::from_bytes(&bytes)
310
166
    }
311
}
312

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

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

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

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

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

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

            
365
impl Debug for Ed25519Identity {
366
35588
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
367
35588
        write!(f, "Ed25519Identity {{ {} }}", self)
368
35588
    }
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
    #[allow(clippy::string_slice)] // TODO
375
82
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376
82
        write!(
377
82
            f,
378
            "{}…",
379
82
            &Base64Unpadded::encode_string(self.id.as_ref())[..2]
380
        )
381
82
    }
382

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
584
    use super::*;
585

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

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