nkeys/
lib.rs

1//! # nkeys
2//!
3//! The `nkeys` is a Rust port of the official NATS [Go](https://github.com/nats-io/nkeys) nkeys implementation.
4//!
5//! Nkeys provides library functions to create ed25519 keys using the special prefix encoding system used by
6//! NATS 2.0+ security.
7//!
8//! # Examples
9//! ```
10//! use nkeys::KeyPair;
11//!
12//! // Create a user key pair
13//! let user = KeyPair::new_user();
14//!
15//! // Sign some data with the user's full key pair
16//! let msg = "this is super secret".as_bytes();
17//! let sig = user.sign(&msg).unwrap();
18//! let res = user.verify(msg, sig.as_slice());
19//! assert!(res.is_ok());
20//!
21//! // Access the encoded seed (the information that needs to be kept safe/secret)
22//! let seed = user.seed().unwrap();
23//! // Access the public key, which can be safely shared
24//! let pk = user.public_key();
25//!
26//! // Create a full User who can sign and verify from a private seed.
27//! let user = KeyPair::from_seed(&seed);
28//!
29//! // Create a user that can only verify and not sign
30//! let user = KeyPair::from_public_key(&pk).unwrap();
31//! assert!(user.seed().is_err());
32//! ```
33//!
34//! # Notes
35//! The following is a list of the valid prefixes / key pair types available. Note that there are more
36//! key pair types available in this crate than there are in the original Go implementation for NATS.
37//! * **N** - Server
38//! * **C** - Cluster
39//! * **O** - Operator
40//! * **A** - Account
41//! * **U** - User
42//! * **M** - Module
43//! * **V** - Service / Service Provider
44//! * **P** - Private Key
45//! * **X** - Curve Key (X25519)
46
47#![allow(dead_code)]
48
49use std::fmt::{self, Debug};
50
51use crc::{extract_crc, push_crc, valid_checksum};
52use ed25519_dalek::{SecretKey, Signer, SigningKey, Verifier, VerifyingKey};
53use rand::prelude::*;
54
55#[cfg(feature = "xkeys")]
56mod xkeys;
57
58#[cfg(feature = "xkeys")]
59pub use xkeys::XKey;
60
61const ENCODED_SEED_LENGTH: usize = 58;
62const ENCODED_PUBKEY_LENGTH: usize = 56;
63
64const PREFIX_BYTE_SEED: u8 = 18 << 3;
65const PREFIX_BYTE_PRIVATE: u8 = 15 << 3;
66const PREFIX_BYTE_SERVER: u8 = 13 << 3;
67const PREFIX_BYTE_CLUSTER: u8 = 2 << 3;
68const PREFIX_BYTE_OPERATOR: u8 = 14 << 3;
69const PREFIX_BYTE_MODULE: u8 = 12 << 3;
70const PREFIX_BYTE_ACCOUNT: u8 = 0;
71const PREFIX_BYTE_USER: u8 = 20 << 3;
72const PREFIX_BYTE_SERVICE: u8 = 21 << 3;
73const PREFIX_BYTE_CURVE: u8 = 23 << 3;
74const PREFIX_BYTE_UNKNOWN: u8 = 25 << 3;
75
76const PUBLIC_KEY_PREFIXES: [u8; 8] = [
77    PREFIX_BYTE_ACCOUNT,
78    PREFIX_BYTE_CLUSTER,
79    PREFIX_BYTE_OPERATOR,
80    PREFIX_BYTE_SERVER,
81    PREFIX_BYTE_USER,
82    PREFIX_BYTE_MODULE,
83    PREFIX_BYTE_SERVICE,
84    PREFIX_BYTE_CURVE,
85];
86
87type Result<T> = std::result::Result<T, crate::error::Error>;
88
89/// The main interface used for reading and writing _nkey-encoded_ key pairs, including
90/// seeds and public keys.
91#[derive(Clone)]
92pub struct KeyPair {
93    kp_type: KeyPairType,
94    sk: Option<SecretKey>, //rawkey_kind: RawKeyKind,
95    signing_key: Option<SigningKey>,
96    pk: VerifyingKey,
97}
98
99impl Debug for KeyPair {
100    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101        write!(f, "KeyPair ({:?})", self.kp_type)
102    }
103}
104
105/// The authoritative list of valid key pair types that are used for cryptographically secure
106/// identities
107#[derive(Debug, Clone, PartialEq)]
108pub enum KeyPairType {
109    /// A server identity
110    Server,
111    /// A cluster (group of servers) identity
112    Cluster,
113    /// An operator (vouches for accounts) identity
114    Operator,
115    /// An account (vouches for users) identity
116    Account,
117    /// A user identity
118    User,
119    /// A module identity - can represent an opaque component, etc.
120    Module,
121    /// A service / service provider identity
122    Service,
123    /// CurveKeys (X25519)
124    Curve,
125}
126
127impl std::str::FromStr for KeyPairType {
128    type Err = crate::error::Error;
129
130    fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
131        let tgt = s.to_uppercase();
132
133        match tgt.as_ref() {
134            "SERVER" => Ok(KeyPairType::Server),
135            "CLUSTER" => Ok(KeyPairType::Cluster),
136            "OPERATOR" => Ok(KeyPairType::Operator),
137            "ACCOUNT" => Ok(KeyPairType::Account),
138            "USER" => Ok(KeyPairType::User),
139            "SERVICE" => Ok(KeyPairType::Service),
140            "MODULE" => Ok(KeyPairType::Module),
141            "CURVE" => Ok(KeyPairType::Curve),
142            _ => Ok(KeyPairType::Module), // Do not crash the app if user input was wrong
143        }
144    }
145}
146
147impl From<u8> for KeyPairType {
148    fn from(prefix_byte: u8) -> KeyPairType {
149        match prefix_byte {
150            PREFIX_BYTE_SERVER => KeyPairType::Server,
151            PREFIX_BYTE_CLUSTER => KeyPairType::Cluster,
152            PREFIX_BYTE_OPERATOR => KeyPairType::Operator,
153            PREFIX_BYTE_ACCOUNT => KeyPairType::Account,
154            PREFIX_BYTE_USER => KeyPairType::User,
155            PREFIX_BYTE_MODULE => KeyPairType::Module,
156            PREFIX_BYTE_SERVICE => KeyPairType::Service,
157            PREFIX_BYTE_CURVE => KeyPairType::Curve,
158            _ => KeyPairType::Operator,
159        }
160    }
161}
162
163impl KeyPair {
164    /// Creates a new key pair of the given type.
165    ///
166    /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of
167    /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead
168    #[cfg(not(target_arch = "wasm32"))]
169    pub fn new(kp_type: KeyPairType) -> KeyPair {
170        // If this unwrap fails, then the library is invalid, so the unwrap is OK here
171        Self::new_from_raw(kp_type, generate_seed_rand()).unwrap()
172    }
173
174    /// Create a new keypair using a pre-existing set of random bytes.
175    ///
176    /// Returns an error if there is an issue using the bytes to generate the key
177    /// NOTE: These bytes should be generated from a cryptographically secure random source.
178    pub fn new_from_raw(kp_type: KeyPairType, random_bytes: [u8; 32]) -> Result<KeyPair> {
179        let signing_key = SigningKey::from_bytes(&random_bytes);
180        Ok(KeyPair {
181            kp_type,
182            pk: signing_key.verifying_key(),
183            signing_key: Some(signing_key),
184            sk: Some(random_bytes),
185        })
186    }
187
188    /// Creates a new user key pair with a seed that has a **U** prefix
189    ///
190    /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of
191    /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead
192    #[cfg(not(target_arch = "wasm32"))]
193    pub fn new_user() -> KeyPair {
194        Self::new(KeyPairType::User)
195    }
196
197    /// Creates a new account key pair with a seed that has an **A** prefix
198    ///
199    /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of
200    /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead
201    #[cfg(not(target_arch = "wasm32"))]
202    pub fn new_account() -> KeyPair {
203        Self::new(KeyPairType::Account)
204    }
205
206    /// Creates a new operator key pair with a seed that has an **O** prefix
207    ///
208    /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of
209    /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead
210    #[cfg(not(target_arch = "wasm32"))]
211    pub fn new_operator() -> KeyPair {
212        Self::new(KeyPairType::Operator)
213    }
214
215    /// Creates a new cluster key pair with a seed that has the **C** prefix
216    ///
217    /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of
218    /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead
219    #[cfg(not(target_arch = "wasm32"))]
220    pub fn new_cluster() -> KeyPair {
221        Self::new(KeyPairType::Cluster)
222    }
223
224    /// Creates a new server key pair with a seed that has the **N** prefix
225    ///
226    /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of
227    /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead
228    #[cfg(not(target_arch = "wasm32"))]
229    pub fn new_server() -> KeyPair {
230        Self::new(KeyPairType::Server)
231    }
232
233    /// Creates a new module (e.g. WebAssembly) key pair with a seed that has the **M** prefix
234    ///
235    /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of
236    /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead
237    #[cfg(not(target_arch = "wasm32"))]
238    pub fn new_module() -> KeyPair {
239        Self::new(KeyPairType::Module)
240    }
241
242    /// Creates a new service / service provider key pair with a seed that has the **V** prefix
243    ///
244    /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of
245    /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead
246    #[cfg(not(target_arch = "wasm32"))]
247    pub fn new_service() -> KeyPair {
248        Self::new(KeyPairType::Service)
249    }
250
251    /// Returns the encoded, human-readable public key of this key pair
252    pub fn public_key(&self) -> String {
253        encode(&self.kp_type, self.pk.as_bytes())
254    }
255
256    /// Attempts to sign the given input with the key pair's seed
257    pub fn sign(&self, input: &[u8]) -> Result<Vec<u8>> {
258        if let Some(ref seed) = self.signing_key {
259            let sig = seed.sign(input);
260            Ok(sig.to_bytes().to_vec())
261        } else {
262            Err(err!(SignatureError, "Cannot sign without a seed key"))
263        }
264    }
265
266    /// Attempts to verify that the given signature is valid for the given input
267    pub fn verify(&self, input: &[u8], sig: &[u8]) -> Result<()> {
268        if sig.len() != ed25519::Signature::BYTE_SIZE {
269            return Err(err!(
270                InvalidSignatureLength,
271                "Signature did not match expected length"
272            ));
273        }
274
275        let mut fixedsig = [0; ed25519::Signature::BYTE_SIZE];
276        fixedsig.copy_from_slice(sig);
277        let insig = ed25519::Signature::from_bytes(&fixedsig);
278
279        match self.pk.verify(input, &insig) {
280            Ok(()) => Ok(()),
281            Err(e) => Err(e.into()),
282        }
283    }
284
285    /// Attempts to return the encoded, human-readable string for this key pair's seed.
286    /// Remember that this value should be treated as a secret. Do not store it for
287    /// any longer than necessary
288    pub fn seed(&self) -> Result<String> {
289        if let Some(ref seed) = self.sk {
290            Ok(encode_seed(&self.kp_type, seed))
291        } else {
292            Err(err!(IncorrectKeyType, "This keypair has no seed"))
293        }
294    }
295
296    /// Attempts to produce a public-only key pair from the given encoded public key string
297    pub fn from_public_key(source: &str) -> Result<KeyPair> {
298        let (prefix, bytes) = from_public_key(source)?;
299
300        let pk = VerifyingKey::from_bytes(&bytes)
301            .map_err(|_| err!(VerifyError, "Could not read public key"))?;
302
303        Ok(KeyPair {
304            kp_type: KeyPairType::from(prefix),
305            pk,
306            sk: None,
307            signing_key: None,
308        })
309    }
310
311    /// Attempts to produce a full key pair from the given encoded seed string
312    pub fn from_seed(source: &str) -> Result<KeyPair> {
313        let (ty, seed) = decode_seed(source)?;
314
315        let signing_key = SigningKey::from_bytes(&seed);
316
317        Ok(KeyPair {
318            kp_type: KeyPairType::from(ty),
319            pk: signing_key.verifying_key(),
320            sk: Some(seed),
321            signing_key: Some(signing_key),
322        })
323    }
324
325    /// Returns the type of this key pair.
326    pub fn key_pair_type(&self) -> KeyPairType {
327        self.kp_type.clone()
328    }
329}
330
331fn decode_raw(raw: &[u8]) -> Result<Vec<u8>> {
332    let mut b32_decoded = data_encoding::BASE32_NOPAD.decode(raw)?;
333
334    let checksum = extract_crc(&mut b32_decoded)?;
335    let v_checksum = valid_checksum(&b32_decoded, checksum);
336    if !v_checksum {
337        Err(err!(ChecksumFailure, "Checksum mismatch"))
338    } else {
339        Ok(b32_decoded)
340    }
341}
342
343/// Returns the prefix byte and the underlying public key bytes
344/// NOTE: This is considered an advanced use case, it's generally recommended to stick with [`KeyPair::from_public_key`] instead.
345pub fn from_public_key(source: &str) -> Result<(u8, [u8; 32])> {
346    if source.len() != ENCODED_PUBKEY_LENGTH {
347        let l = source.len();
348        return Err(err!(InvalidKeyLength, "Bad key length: {}", l));
349    }
350
351    let source_bytes = source.as_bytes();
352    let mut raw = decode_raw(source_bytes)?;
353
354    let prefix = raw[0];
355    if !valid_public_key_prefix(prefix) {
356        return Err(err!(
357            InvalidPrefix,
358            "Not a valid public key prefix: {}",
359            raw[0]
360        ));
361    }
362    raw.remove(0);
363
364    let mut public_key = [0u8; 32];
365    public_key.copy_from_slice(&raw[..]);
366
367    Ok((prefix, public_key))
368}
369
370/// Attempts to decode the provided base32 encoded string into a valid prefix byte and the private key seed bytes.
371/// NOTE: This is considered an advanced use case, it's generally recommended to stick with [`KeyPair::from_seed`] instead.
372pub fn decode_seed(source: &str) -> Result<(u8, [u8; 32])> {
373    if source.len() != ENCODED_SEED_LENGTH {
374        let l = source.len();
375        return Err(err!(InvalidKeyLength, "Bad seed length: {}", l));
376    }
377
378    let source_bytes = source.as_bytes();
379    let raw = decode_raw(source_bytes)?;
380
381    let b1 = raw[0] & 248;
382    if b1 != PREFIX_BYTE_SEED {
383        return Err(err!(
384            InvalidPrefix,
385            "Incorrect byte prefix: {}",
386            source.chars().next().unwrap()
387        ));
388    }
389
390    let b2 = (raw[0] & 7) << 5 | ((raw[1] & 248) >> 3);
391
392    let mut seed = [0u8; 32];
393    seed.copy_from_slice(&raw[2..]);
394
395    Ok((b2, seed))
396}
397
398fn generate_seed_rand() -> [u8; 32] {
399    let mut rng = rand::thread_rng();
400    rng.gen::<[u8; 32]>()
401}
402
403fn get_prefix_byte(kp_type: &KeyPairType) -> u8 {
404    match kp_type {
405        KeyPairType::Server => PREFIX_BYTE_SERVER,
406        KeyPairType::Account => PREFIX_BYTE_ACCOUNT,
407        KeyPairType::Cluster => PREFIX_BYTE_CLUSTER,
408        KeyPairType::Operator => PREFIX_BYTE_OPERATOR,
409        KeyPairType::User => PREFIX_BYTE_USER,
410        KeyPairType::Module => PREFIX_BYTE_MODULE,
411        KeyPairType::Service => PREFIX_BYTE_SERVICE,
412        KeyPairType::Curve => PREFIX_BYTE_CURVE,
413    }
414}
415
416fn valid_public_key_prefix(prefix: u8) -> bool {
417    PUBLIC_KEY_PREFIXES.to_vec().contains(&prefix)
418}
419
420fn encode_seed(ty: &KeyPairType, seed: &[u8]) -> String {
421    let prefix_byte = get_prefix_byte(ty);
422
423    let b1 = PREFIX_BYTE_SEED | prefix_byte >> 5;
424    let b2 = (prefix_byte & 31) << 3;
425
426    encode_prefix(&[b1, b2], seed)
427}
428
429fn encode(ty: &KeyPairType, key: &[u8]) -> String {
430    let prefix_byte = get_prefix_byte(ty);
431    encode_prefix(&[prefix_byte], key)
432}
433
434fn encode_prefix(prefix: &[u8], key: &[u8]) -> String {
435    let mut raw = Vec::with_capacity(prefix.len() + key.len() + 2);
436    raw.extend_from_slice(prefix);
437    raw.extend_from_slice(key);
438    push_crc(&mut raw);
439
440    data_encoding::BASE32_NOPAD.encode(&raw[..])
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446    use crate::error::ErrorKind;
447
448    #[test]
449    fn validate_decode_seed() {
450        let input_bytes = generate_seed_rand();
451        let seed = encode_seed(&KeyPairType::User, input_bytes.as_slice());
452
453        let (prefix, decoded_bytes) = decode_seed(&seed).unwrap();
454
455        assert_eq!(prefix, PREFIX_BYTE_USER);
456        assert_eq!(decoded_bytes, input_bytes);
457    }
458
459    #[test]
460    fn validate_from_public_key() {
461        let input_bytes = generate_seed_rand();
462        let public_key = encode(&KeyPairType::User, input_bytes.as_slice());
463
464        let (prefix, decoded_bytes) = from_public_key(&public_key).unwrap();
465
466        assert_eq!(prefix, PREFIX_BYTE_USER);
467        assert_eq!(decoded_bytes, input_bytes);
468    }
469
470    #[test]
471    fn seed_encode_decode_round_trip() {
472        let pair = KeyPair::new_user();
473        let s = pair.seed().unwrap();
474        let p = pair.public_key();
475
476        let pair2 = KeyPair::from_seed(s.as_str()).unwrap();
477        let s2 = pair2.seed().unwrap();
478
479        assert_eq!(s, s2);
480        assert_eq!(p, pair2.public_key());
481    }
482
483    #[test]
484    fn roundtrip_encoding_go_compat() {
485        // Seed and Public Key pair generated by Go nkeys library
486        let seed = "SAAPN4W3EG6KCJGUQTKTJ5GSB5NHK5CHAJL4DBGFUM3HHROI4XUEP4OBK4";
487        let pk = "ACODERUVFFAWZQDSS6SBIACUA5O6SXF7HJ3YTYXBALHZP3P7R4BUO4J2";
488
489        let pair = KeyPair::from_seed(seed).unwrap();
490
491        assert_eq!(pair.seed().unwrap(), seed);
492        assert_eq!(pair.public_key(), pk);
493    }
494
495    #[test]
496    fn from_seed_rejects_bad_prefix() {
497        let seed = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
498        let pair = KeyPair::from_seed(seed);
499        assert!(pair.is_err());
500        if let Err(e) = pair {
501            assert_eq!(e.kind(), ErrorKind::InvalidPrefix);
502        }
503    }
504
505    #[test]
506    fn from_seed_rejects_bad_checksum() {
507        let seed = "FAAPN4W3EG6KCJGUQTKTJ5GSB5NHK5CHAJL4DBGFUM3HHROI4XUEP4OBK4";
508        let pair = KeyPair::from_seed(seed);
509        assert!(pair.is_err());
510        if let Err(e) = pair {
511            assert_eq!(e.kind(), ErrorKind::ChecksumFailure);
512        }
513    }
514
515    #[test]
516    fn from_seed_rejects_bad_length() {
517        let seed = "SAAPN4W3EG6KCJGUQTKTJ5GSB5NHK5CHAJL4DBGFUM3SAAPN4W3EG6KCJGUQTKTJ5GSB5NHK5";
518        let pair = KeyPair::from_seed(seed);
519        assert!(pair.is_err());
520        if let Err(e) = pair {
521            assert_eq!(e.kind(), ErrorKind::InvalidKeyLength);
522        }
523    }
524
525    #[test]
526    fn from_seed_rejects_invalid_encoding() {
527        let badseed = "SAAPN4W3EG6KCJGUQTKTJ5!#B5NHK5CHAJL4DBGFUM3HHROI4XUEP4OBK4";
528        let pair = KeyPair::from_seed(badseed);
529        assert!(pair.is_err());
530        if let Err(e) = pair {
531            assert_eq!(e.kind(), ErrorKind::CodecFailure);
532        }
533    }
534
535    #[test]
536    fn sign_and_verify() {
537        let user = KeyPair::new_user();
538        let msg = b"this is super secret";
539
540        let sig = user.sign(msg).unwrap();
541
542        let res = user.verify(msg, sig.as_slice());
543        assert!(res.is_ok());
544    }
545
546    #[test]
547    fn sign_and_verify_rejects_mismatched_sig() {
548        let user = KeyPair::new_user();
549        let msg = b"this is super secret";
550
551        let sig = user.sign(msg).unwrap();
552        let res = user.verify(b"this doesn't match the message", sig.as_slice());
553        assert!(res.is_err());
554    }
555
556    #[test]
557    fn sign_and_verify_rejects_invalid_signature_length() {
558        let kp = KeyPair::new_user();
559        let res = kp.verify(&[], &[]);
560        assert!(res.is_err());
561        if let Err(e) = res {
562            assert_eq!(e.kind(), ErrorKind::InvalidSignatureLength);
563        }
564    }
565
566    #[test]
567    fn from_public_key_rejects_bad_length() {
568        let public_key = "ACARVGW77LDNWYXBAH62YKKQRVHYOTKKDDVVJVOISOU75WQPXOO7N3";
569        let pair = KeyPair::from_public_key(public_key);
570        assert!(pair.is_err());
571        if let Err(e) = pair {
572            assert_eq!(e.kind(), ErrorKind::InvalidKeyLength);
573        }
574    }
575
576    #[test]
577    fn from_public_key_rejects_bad_prefix() {
578        let public_key = "ZCO4XYNKEN7ZFQ42BHYCBYI3K7USOGG43C2DIJZYWSQ2YEMBOZWN6PYH";
579        let pair = KeyPair::from_public_key(public_key);
580        assert!(pair.is_err());
581        if let Err(e) = pair {
582            assert_eq!(e.kind(), ErrorKind::InvalidPrefix);
583        }
584    }
585
586    #[test]
587    fn public_key_round_trip() {
588        let account =
589            KeyPair::from_public_key("ACODERUVFFAWZQDSS6SBIACUA5O6SXF7HJ3YTYXBALHZP3P7R4BUO4J2")
590                .unwrap();
591        let pk = account.public_key();
592        assert_eq!(
593            pk,
594            "ACODERUVFFAWZQDSS6SBIACUA5O6SXF7HJ3YTYXBALHZP3P7R4BUO4J2"
595        );
596    }
597
598    #[test]
599    fn module_has_proper_prefix() {
600        let module = KeyPair::new_module();
601        assert!(module.seed().unwrap().starts_with("SM"));
602        assert!(module.public_key().starts_with('M'));
603    }
604
605    #[test]
606    fn service_has_proper_prefix() {
607        let service = KeyPair::new_service();
608        assert!(service.seed().unwrap().starts_with("SV"));
609        assert!(service.public_key().starts_with('V'));
610    }
611
612    #[test]
613    fn can_get_key_type() {
614        let from_pub =
615            KeyPair::from_public_key("UBCXCMGAZQZN55X5TTTWMB5CZNZIKJHEDZJOJ3TV63NKPJ6FRXSR2ZO4")
616                .unwrap();
617        let from_seed =
618            KeyPair::from_seed("SCANU5JGFEPJ2XNFQ6YMDRHMNFAL6ZT3DCU3ZMMHHML7GLFE3YIH5TBM6E")
619                .unwrap();
620
621        assert!(
622            matches!(from_pub.key_pair_type(), KeyPairType::User),
623            "Expected the key type to be {:?}, found {:?}",
624            KeyPairType::User,
625            from_pub.key_pair_type()
626        );
627        assert!(
628            matches!(from_seed.key_pair_type(), KeyPairType::Cluster),
629            "Expected the key type to be {:?}, found {:?}",
630            KeyPairType::Cluster,
631            from_seed.key_pair_type()
632        );
633    }
634}
635
636mod crc;
637pub mod error;