1pub mod messages;
5use {
6 bip39::{Error as Bip39Error, Language, Mnemonic},
7 crypto::error::CryptoError,
8 messages::*,
9 std::{num::ParseIntError, str::Utf8Error},
10 zeroize::ZeroizeOnDrop,
11};
12
13pub const MAX_LOGIN_ATTEMPTS: u32 = 10;
14pub const MIN_PIN_LENGTH: usize = 6;
15
16pub const DEV_FIDO_ATTESTATION_PRIVATE_KEY: [u8; 32] = [
20 0xbc, 0x2a, 0x1b, 0xfb, 0xce, 0xf4, 0xf7, 0x53, 0xb8, 0x6e, 0xbe, 0x13, 0x02, 0x13, 0x33, 0xc9, 0xbe,
21 0x7e, 0x4c, 0xd0, 0x7b, 0x2a, 0xb9, 0x94, 0xb4, 0xcf, 0x23, 0x36, 0x4b, 0x6f, 0x3c, 0x33,
22];
23
24#[derive(Default)]
25pub struct Security<P: server::CheckedPermissions> {
26 conn: server::CheckedConn<P>,
27}
28
29#[derive(Debug, Clone, ZeroizeOnDrop, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
30pub struct Pin(pub [u8; 32]);
31
32#[derive(Debug, Clone, ZeroizeOnDrop, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
33pub enum Seed {
34 Twelve([u8; 16]),
36 TwentyFour([u8; 32]),
38}
39
40impl Seed {
41 pub fn from_bytes(seed: &[u8]) -> Self {
48 match seed.len() {
49 16 => Seed::Twelve(seed.try_into().unwrap()),
50 32 => Seed::TwentyFour(seed.try_into().unwrap()),
51 _ => panic!("Invalid seed length: expected 16 or 32 bytes, got {}", seed.len()),
52 }
53 }
54
55 pub fn bytes(&self) -> &[u8] {
56 match self {
57 Seed::Twelve(bytes) => bytes,
58 Seed::TwentyFour(bytes) => bytes,
59 }
60 }
61
62 pub fn to_vec(&self) -> Vec<u8> { self.bytes().to_vec() }
63
64 pub fn from_mnemonic(mnemonic: &Mnemonic) -> Self {
65 let entropy = mnemonic.to_entropy();
66 Self::from_bytes(&entropy)
67 }
68
69 pub fn to_mnemonic(&self) -> Result<Mnemonic, Bip39Error> { Mnemonic::from_entropy(self.bytes()) }
70
71 pub fn to_mnemonic_words(&self) -> Result<Vec<String>, Bip39Error> {
72 let mnemonic = self.to_mnemonic()?;
73 Ok(mnemonic.words().map(str::to_string).collect())
74 }
75
76 pub fn to_standard_seed_qr_data(&self) -> Result<Vec<u8>, Bip39Error> {
77 let mnemonic = self.to_mnemonic()?;
78 let indices: String = mnemonic.word_indices().map(|idx| format!("{idx:04}")).collect();
79 Ok(indices.into_bytes())
80 }
81
82 pub fn to_compact_seed_qr_data(&self) -> Result<Vec<u8>, Bip39Error> {
83 let mnemonic = self.to_mnemonic()?;
84 Ok(mnemonic.to_entropy())
85 }
86}
87
88impl Default for Seed {
89 fn default() -> Self { Seed::TwentyFour([0; 32]) }
90}
91
92#[derive(Clone, ZeroizeOnDrop, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
99pub struct AppSeed([u8; 32]);
100
101impl AppSeed {
102 pub fn new(bytes: [u8; 32]) -> Self { Self(bytes) }
103
104 pub fn as_bytes(&self) -> &[u8; 32] { &self.0 }
105}
106
107#[derive(Clone, Debug, thiserror::Error)]
108pub enum ParseSeedQrError {
109 #[error("Invalid UTF-8 in word index: {0}")]
110 InvalidUtf8(#[from] Utf8Error),
111
112 #[error("Failed to parse word index: {0}")]
113 InvalidWordIndex(#[from] ParseIntError),
114
115 #[error("Word index {0} out of range")]
116 WordIndexOutOfRange(usize),
117
118 #[error("Invalid mnemonic: {0}")]
119 InvalidMnemonic(#[from] Bip39Error),
120}
121
122pub fn parse_seedqr(qr_data: &[u8]) -> Result<Mnemonic, ParseSeedQrError> {
125 if qr_data.len() == 48 || qr_data.len() == 96 {
127 let words = qr_data
128 .chunks(4)
129 .map(|index| -> Result<&'static str, ParseSeedQrError> {
130 let index_str = std::str::from_utf8(index)?;
131 let index: usize = index_str.parse()?;
132 let word = Language::English
133 .word_list()
134 .get(index)
135 .copied()
136 .ok_or(ParseSeedQrError::WordIndexOutOfRange(index))?;
137 Ok(word)
138 })
139 .collect::<Result<Vec<&'static str>, _>>()?
140 .join(" ");
141
142 return Mnemonic::parse(words.as_str()).map_err(Into::into);
143 }
144
145 if let Ok(text) = std::str::from_utf8(qr_data) {
146 if let Ok(mnemonic) = Mnemonic::parse_normalized(text) {
147 return Ok(mnemonic);
148 }
149 }
150
151 Mnemonic::from_entropy(qr_data).map_err(Into::into)
152}
153
154#[derive(Debug, Default, Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, PartialEq, Eq)]
155pub enum PinEntryMode {
156 #[default]
157 Pin = 0,
158 Passphrase = 1,
159}
160
161impl From<u8> for PinEntryMode {
162 fn from(value: u8) -> Self {
163 match value {
164 0 => PinEntryMode::Pin,
165 1 => PinEntryMode::Passphrase,
166 _ => PinEntryMode::Pin,
167 }
168 }
169}
170
171impl From<PinEntryMode> for u8 {
172 fn from(mode: PinEntryMode) -> u8 { mode as u8 }
173}
174
175#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, PartialEq, Eq)]
178pub struct LockoutOptions {
179 pub seed_fingerprint: bool,
180 pub aes_keys: bool,
181}
182
183impl LockoutOptions {
184 pub const fn erase_seed_only() -> Self { Self { seed_fingerprint: false, aes_keys: false } }
185
186 pub const fn erase_all() -> Self { Self { seed_fingerprint: true, aes_keys: true } }
187
188 pub const fn erase_aes_keys() -> Self { Self { seed_fingerprint: false, aes_keys: true } }
189}
190
191#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, PartialEq, Eq)]
192pub struct FirmwareTimestamp(pub [u8; 4]);
193
194impl From<FirmwareTimestamp> for u32 {
195 fn from(ts: FirmwareTimestamp) -> u32 { u32::from_le_bytes(ts.0) }
196}
197
198impl From<u32> for FirmwareTimestamp {
199 fn from(ts: u32) -> FirmwareTimestamp { FirmwareTimestamp(ts.to_le_bytes()) }
200}
201
202impl Default for FirmwareTimestamp {
203 fn default() -> Self { 0u32.into() }
204}
205
206pub struct LastSuccess {
207 pub num_fails: u32,
208 pub attempts_left: u32,
209}
210
211#[macro_export]
212macro_rules! use_api {
213 () => {
214 mod security_permissions {
215 use security::messages::*;
216 #[derive(Clone, Default, server::Permissions)]
217 #[server_name = "os/security"]
218 pub struct SecurityPermissions;
219 }
220 type Security = security::Security<security_permissions::SecurityPermissions>;
221 };
222}
223
224impl<P: server::CheckedPermissions> Security<P> {
225 pub fn set_seed_and_pin(&self, seed: Seed, pin: String, pin_entry: PinEntryMode) -> Result<(), PinError>
228 where
229 P: server::MessageAllowed<SetSeedAndPin>,
230 {
231 self.conn.send_blocking_archive(SetSeedAndPin { seed, pin: RawPin(pin), pin_entry })
232 }
233
234 pub fn change_pin(
236 &self,
237 raw_pin: String,
238 seed: Option<Seed>,
239 pin_entry: PinEntryMode,
240 ) -> Result<(), PinError>
241 where
242 P: server::MessageAllowed<ChangePin>,
243 {
244 self.conn.send_blocking_archive(ChangePin { pin: RawPin(raw_pin), seed, pin_entry })
245 }
246
247 pub fn is_pin_set(&self) -> Result<bool, AccessDenied>
248 where
249 P: server::MessageAllowed<IsPinSet>,
250 {
251 self.conn.send_blocking_archive(IsPinSet)
252 }
253
254 pub fn get_pin_entry_mode(&self) -> PinEntryMode
255 where
256 P: server::MessageAllowed<GetPinEntryMode>,
257 {
258 self.conn.send_blocking_archive(GetPinEntryMode)
259 }
260
261 pub fn log_in(&self, pin: String) -> Result<(), LoginFailed>
262 where
263 P: server::MessageAllowed<Login>,
264 {
265 self.conn.send_blocking_archive(Login { pin: RawPin(pin) })
266 }
267
268 pub fn log_out(&self)
269 where
270 P: server::MessageAllowed<Logout>,
271 {
272 self.conn.send_blocking_scalar(Logout)
273 }
274
275 pub fn logged_in(&self) -> bool
276 where
277 P: server::MessageAllowed<LoggedIn>,
278 {
279 self.conn.send_blocking_scalar(LoggedIn)
280 }
281
282 pub fn attempts_remaining(&self) -> Result<u32, AccessDenied>
283 where
284 P: server::MessageAllowed<GetAttemptsRemaining>,
285 {
286 self.conn.send_blocking_archive(GetAttemptsRemaining)
287 }
288
289 pub fn factory_reset_counter(&self) -> Result<u32, AccessDenied>
290 where
291 P: server::MessageAllowed<GetFactoryResetCounter>,
292 {
293 self.conn.send_blocking_archive(GetFactoryResetCounter)
294 }
295
296 pub fn seed(&self) -> Result<Option<Seed>, AccessDenied>
303 where
304 P: server::MessageAllowed<GetSeed>,
305 {
306 self.conn.send_blocking_archive(GetSeed)
307 }
308
309 pub fn set_seed(&self, seed: Seed) -> Result<(), AccessDenied>
314 where
315 P: server::MessageAllowed<SetSeed>,
316 {
317 self.conn.send_blocking_archive(SetSeed(seed))
318 }
319
320 pub fn app_seed(&self) -> Result<AppSeed, AccessDenied>
321 where
322 P: server::MessageAllowed<GetAppSeed>,
323 {
324 self.conn.send_blocking_archive(GetAppSeed)
325 }
326
327 pub fn lockout(&self, lockout_options: LockoutOptions) -> Result<(), AccessDenied>
328 where
329 P: server::MessageAllowed<Lockout>,
330 {
331 self.conn.send_blocking_archive(Lockout { lockout_options, reboot: true })
332 }
333
334 pub fn sign_with_security_check_key(&self, data: [u8; 32]) -> Result<[u8; 64], AccessDenied>
335 where
336 P: server::MessageAllowed<SignWithSecurityCheckKey>,
337 {
338 self.conn.send_blocking_archive(SignWithSecurityCheckKey(data))
339 }
340
341 pub fn sign_with_fido_key(&self, data: [u8; 32]) -> Result<[u8; 64], AccessDenied>
342 where
343 P: server::MessageAllowed<SignWithFidoKey>,
344 {
345 self.conn.send_blocking_archive(SignWithFidoKey(data))
346 }
347
348 pub fn get_fido_pubkey(&self) -> Result<[u8; 64], AccessDenied>
349 where
350 P: server::MessageAllowed<GetFidoPubkey>,
351 {
352 self.conn.send_blocking_archive(GetFidoPubkey)
353 }
354
355 pub fn security_words(&self, pin_prefix: &str) -> Result<[SecurityWord; 2], AccessDenied>
356 where
357 P: server::MessageAllowed<GetSecurityWords>,
358 {
359 self.conn.send_blocking_archive(GetSecurityWords { pin_prefix: pin_prefix.as_bytes().to_vec() })
360 }
361
362 pub fn firmware_timestamp(&self) -> Result<FirmwareTimestamp, AccessDenied>
363 where
364 P: server::MessageAllowed<GetFirmwareTimestamp>,
365 {
366 self.conn.send_blocking_archive(GetFirmwareTimestamp)
367 }
368
369 pub fn set_firmware_timestamp(&self, timestamp: FirmwareTimestamp) -> Result<(), AccessDenied>
370 where
371 P: server::MessageAllowed<SetFirmwareTimestamp>,
372 {
373 self.conn.send_blocking_archive(SetFirmwareTimestamp(timestamp))
374 }
375
376 pub fn seed_fingerprint(&self) -> Result<[u8; 32], AccessDenied>
377 where
378 P: server::MessageAllowed<GetSeedFingerprint>,
379 {
380 self.conn.send_blocking_archive(GetSeedFingerprint)
381 }
382
383 pub fn fingerprint(&self, seed: &Seed) -> Result<[u8; 32], AccessDenied>
384 where
385 P: server::MessageAllowed<ComputeSeedFingerprint>,
386 {
387 self.conn.send_blocking_archive(ComputeSeedFingerprint(seed.clone()))
388 }
389
390 pub fn os_version_info(&self) -> Result<Option<OsVersionInfo>, AccessDenied>
391 where
392 P: server::MessageAllowed<GetOsVersionInfo>,
393 {
394 self.conn.send_blocking_archive(GetOsVersionInfo)
395 }
396
397 pub fn bootloader_build_date(&self) -> Result<Option<u64>, AccessDenied>
398 where
399 P: server::MessageAllowed<GetBootloaderBuildDate>,
400 {
401 self.conn.send_blocking_archive(GetBootloaderBuildDate)
402 }
403
404 pub fn sc_challenge(&self, challenge: [u8; ScChallenge::SIZE]) -> Result<ScProof, ScChallengeError>
405 where
406 P: server::MessageAllowed<ScChallenge>,
407 {
408 self.conn.send_blocking_archive(ScChallenge(challenge))
409 }
410
411 pub fn device_id(&self) -> Result<DeviceId, GetDeviceIdError>
412 where
413 P: server::MessageAllowed<GetDeviceId>,
414 {
415 self.conn.send_blocking_archive(GetDeviceId)
416 }
417
418 pub fn get_random(&self) -> Result<[u8; 32], AccessDenied>
419 where
420 P: server::MessageAllowed<GetRandom>,
421 {
422 self.conn.send_blocking_archive(GetRandom)
423 }
424
425 pub fn keycard_authenticity_mac(&self, msg: [u8; 32]) -> Result<[u8; 32], AccessDenied>
426 where
427 P: server::MessageAllowed<KeycardAuthenticityMac>,
428 {
429 self.conn.send_blocking_archive(KeycardAuthenticityMac(msg))
430 }
431
432 #[cfg(not(keyos))]
433 pub fn get_pin(&self) -> String
434 where
435 P: server::MessageAllowed<GetPin>,
436 {
437 self.conn.send_blocking_archive(GetPin)
438 }
439
440 #[cfg(not(keyos))]
441 pub fn set_attempts_remaining(&self, attempts: u32) -> Result<(), SecurityError>
442 where
443 P: server::MessageAllowed<SetAttempts>,
444 {
445 if attempts > MAX_LOGIN_ATTEMPTS {
446 return Err(SecurityError::AttemptsOutOfBounds(attempts));
447 }
448
449 self.conn.send_blocking_archive(SetAttempts(MAX_LOGIN_ATTEMPTS - attempts));
450 Ok(())
451 }
452
453 pub fn bluetooth_challenge_secret(&self) -> BluetoothChallengeSecret
455 where
456 P: server::MessageAllowed<GetBluetoothChallengeSecret>,
457 {
458 self.conn.send_blocking_archive(GetBluetoothChallengeSecret)
459 }
460
461 pub fn set_bluetooth_challenge_secret_sent(&self)
462 where
463 P: server::MessageAllowed<SetBluetoothCheckSecretSent>,
464 {
465 self.conn.send_blocking_scalar(SetBluetoothCheckSecretSent)
466 }
467
468 pub fn set_bluetooth_device_id(&self, device_id: [u8; 8])
469 where
470 P: server::MessageAllowed<SetBluetoothDeviceId>,
471 {
472 self.conn.send_blocking_archive(SetBluetoothDeviceId(device_id))
473 }
474
475 pub fn master_key_state(&self) -> MasterKeyState
476 where
477 P: server::MessageAllowed<GetMasterKeyState>,
478 {
479 self.conn.send_blocking_scalar(GetMasterKeyState)
480 }
481
482 pub fn subscribe_disk_encryption_keys_ready<SR>(&self, context: &mut server::ServerContext<SR>)
486 where
487 P: server::MessageAllowed<SubscribeDiskEncryptionKeysReady>,
488 SR: server::ScalarEventHandler<DiskEncryptionKeysReady>,
489 {
490 self.conn.subscribe_scalar_infallible(SubscribeDiskEncryptionKeysReady, context)
491 }
492}
493
494#[derive(Debug, Copy, Clone)]
496pub enum MasterKeyState {
497 Onboarding,
498 Erased,
499 Normal,
500 Unknown,
501}
502
503#[cfg(not(keyos))]
504#[derive(Debug, thiserror::Error)]
505pub enum SecurityError {
506 #[error("Attempts remaining must not be greater than max attempts of {}: {0:?}", MAX_LOGIN_ATTEMPTS)]
507 AttemptsOutOfBounds(u32),
508}
509
510#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
511pub struct SecurityWord(pub usize);
512
513impl std::fmt::Display for SecurityWord {
514 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
515 bip39::Language::English.word_list()[self.0].fmt(f)
516 }
517}
518
519#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, thiserror::Error)]
520pub struct AccessDenied;
521
522impl std::fmt::Display for AccessDenied {
523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Access denied") }
524}
525
526#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, thiserror::Error)]
527pub enum PinError {
528 #[error("Access denied")]
529 AccessDenied,
530 #[error("PIN too short")]
531 TooShort,
532}
533
534impl From<AccessDenied> for PinError {
535 fn from(_: AccessDenied) -> Self { PinError::AccessDenied }
536}
537
538#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
539pub struct LoginFailed {
540 pub attempts_left: u32,
541}
542
543impl std::fmt::Display for LoginFailed {
544 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Login failed") }
545}
546
547#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
548pub struct OsVersionInfo {
549 pub bootloader_version: [u8; 8],
550 pub keyos_version: [u8; 20],
551}
552
553#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
562pub struct ScProof(pub [u8; Self::SIZE]);
563
564impl ScProof {
565 pub const SIZE: usize = 189;
566}
567
568#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
569#[repr(u8)]
570pub enum ScError {
571 Ok = 0,
572 InvalidMessageLength = 1,
573 InvalidSignature = 3,
574 DeadlineExpired = 4,
575 UnknownChallenge = 6,
576 InvalidBootloaderVersion = 7,
577}
578
579#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
580pub enum ScChallengeError {
581 Sc(ScError),
582 CryptoAuthLib(i32),
583 Crypto(CryptoError),
584 AccessDenied,
585 Internal(String),
586}
587
588#[derive(Debug, thiserror::Error, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
589pub enum GetDeviceIdError {
590 #[error("crypto auth lib error: {0}")]
591 CryptoAuthLib(i32),
592 #[error(transparent)]
593 Crypto(CryptoError),
594 #[error("no bluetooth serial yet")]
595 NoBluetoothSerialYet,
596}
597
598#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
599pub struct DeviceId(pub [u8; 32]);
600
601impl std::fmt::Display for DeviceId {
602 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
603 write!(
604 f,
605 "{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}",
606 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7]
607 )
608 }
609}
610
611#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
612pub struct BluetoothChallengeSecret {
613 pub secret: [u8; 32],
614 pub sent: bool,
615}
616
617#[cfg(test)]
618mod tests {
619 use super::*;
620
621 #[test]
622 fn test_seed_mnemonic_roundtrip() {
623 let seed = Seed::Twelve([0x7Au8; 16]);
624 let mnemonic = seed.to_mnemonic().unwrap();
625 let recovered_seed = Seed::from_mnemonic(&mnemonic).to_vec();
626
627 assert_eq!(
628 &seed.bytes()[..mnemonic.to_entropy().len()],
629 &recovered_seed[..mnemonic.to_entropy().len()]
630 );
631 }
632
633 #[test]
634 fn test_parse_seedqr_standard_12_word() {
635 let qr_data = b"192402220235174306311124037817700641198012901210";
637
638 let result = parse_seedqr(qr_data).unwrap().word_indices().collect::<Vec<_>>();
639
640 let expected = vec![1924, 222, 235, 1743, 631, 1124, 378, 1770, 641, 1980, 1290, 1210];
641 assert_eq!(result, expected, "Word indices should match expected values");
642 }
643
644 #[test]
645 fn test_parse_seedqr_standard_24_word() {
646 let entropy = [0x35u8; 32];
647 let mnemonic = Mnemonic::from_entropy(&entropy).unwrap();
648
649 let indices: String = mnemonic.word_indices().map(|idx| format!("{idx:04}")).collect();
650 let qr_data = indices.as_bytes();
651 let result = parse_seedqr(qr_data).unwrap();
652
653 assert_eq!(result, mnemonic);
654 assert_eq!(result.word_count(), 24);
655 }
656
657 #[test]
658 fn test_parse_seedqr_compact() {
659 fn test(entropy: &[u8]) {
660 let mnemonic = Mnemonic::from_entropy(entropy).unwrap();
661 let result = parse_seedqr(entropy).unwrap();
662 assert_eq!(result, mnemonic);
663 }
664
665 test(&[0x11u8; 16]);
666 test(&[0x22u8; 32]);
667 }
668
669 #[test]
670 fn test_parse_seedqr_plaintext() {
671 let mnemonic = Mnemonic::from_entropy(&[0x5Au8; 16]).unwrap();
672 let qr_data = mnemonic.to_string();
673 let result = parse_seedqr(qr_data.as_bytes()).unwrap();
674
675 assert_eq!(result, mnemonic);
676 }
677
678 #[test]
679 fn test_parse_seedqr_plaintext_with_extra_whitespace() {
680 let mnemonic = Mnemonic::from_entropy(&[0xA5u8; 32]).unwrap();
681 let words = mnemonic.words().collect::<Vec<_>>();
682 let qr_data = format!(" {}\n{}\n ", words[..12].join(" "), words[12..].join("\n"));
683
684 let result = parse_seedqr(qr_data.as_bytes()).unwrap();
685 assert_eq!(result, mnemonic);
686 }
687
688 #[test]
689 fn test_seedqr_generation_roundtrip() {
690 let seed = Seed::Twelve([0x6Cu8; 16]);
691
692 let standard_data = seed.to_standard_seed_qr_data().unwrap();
693 let parsed_standard = parse_seedqr(&standard_data).unwrap();
694 let recovered_seed = Seed::from_mnemonic(&parsed_standard).to_vec();
695 assert_eq!(
696 &seed.bytes()[..parsed_standard.to_entropy().len()],
697 &recovered_seed[..parsed_standard.to_entropy().len()]
698 );
699
700 let compact_data = seed.to_compact_seed_qr_data().unwrap();
701 let parsed_compact = parse_seedqr(&compact_data).unwrap();
702 let recovered_seed = Seed::from_mnemonic(&parsed_compact).to_vec();
703 assert_eq!(
704 &seed.bytes()[..parsed_compact.to_entropy().len()],
705 &recovered_seed[..parsed_compact.to_entropy().len()]
706 );
707 }
708
709 #[test]
710 fn test_parse_seedqr_errors() {
711 let invalid_utf8 = vec![0xFF; 48];
713 assert!(matches!(parse_seedqr(&invalid_utf8), Err(ParseSeedQrError::InvalidUtf8(_))));
714
715 let invalid_number = b"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"; assert!(matches!(parse_seedqr(invalid_number), Err(ParseSeedQrError::InvalidWordIndex(_))));
718
719 let out_of_range = b"999999999999999999999999999999999999999999999999"; assert!(matches!(parse_seedqr(out_of_range), Err(ParseSeedQrError::WordIndexOutOfRange(9999))));
722
723 let invalid_compact = b"invalid"; assert!(matches!(parse_seedqr(invalid_compact), Err(ParseSeedQrError::InvalidMnemonic(_))));
726 }
727}