1use std::convert::TryInto;
3use std::str;
4
5use curve25519_dalek::Scalar;
7use data_encoding::{BASE32_NOPAD, BASE64};
8use data_encoding_macro::new_encoding;
9#[cfg(feature = "legacy-tor-provider")]
10use rand::distributions::Alphanumeric;
11use rand::rngs::OsRng;
12#[cfg(feature = "legacy-tor-provider")]
13use rand::Rng;
14use sha3::{Digest, Sha3_256};
15use static_assertions::const_assert_eq;
16use tor_llcrypto::pk::keymanip::*;
17use tor_llcrypto::*;
18
19#[derive(thiserror::Error, Debug)]
21pub enum Error {
22 #[error("{0}")]
24 ParseError(String),
25
26 #[error("{0}")]
28 ConversionError(String),
29
30 #[error("invalid key")]
32 KeyInvalid,
33}
34
35pub const ED25519_PRIVATE_KEY_SIZE: usize = 64;
38pub const ED25519_PUBLIC_KEY_SIZE: usize = 32;
41pub const ED25519_SIGNATURE_SIZE: usize = 64;
44pub const V3_ONION_SERVICE_ID_STRING_LENGTH: usize = 56;
46pub const V3_ONION_SERVICE_ID_STRING_SIZE: usize = 57;
48const_assert_eq!(
49 V3_ONION_SERVICE_ID_STRING_SIZE,
50 V3_ONION_SERVICE_ID_STRING_LENGTH + 1
51);
52pub const ED25519_PRIVATE_KEYBLOB_BASE64_LENGTH: usize = 88;
54const ED25519_PRIVATE_KEY_KEYBLOB_HEADER: &str = "ED25519-V3:";
56pub const ED25519_PRIVATE_KEY_KEYBLOB_HEADER_LENGTH: usize = 11;
58const_assert_eq!(
59 ED25519_PRIVATE_KEY_KEYBLOB_HEADER_LENGTH,
60 ED25519_PRIVATE_KEY_KEYBLOB_HEADER.len()
61);
62pub const ED25519_PRIVATE_KEY_KEYBLOB_LENGTH: usize = 99;
64const_assert_eq!(
65 ED25519_PRIVATE_KEY_KEYBLOB_LENGTH,
66 ED25519_PRIVATE_KEY_KEYBLOB_HEADER_LENGTH + ED25519_PRIVATE_KEYBLOB_BASE64_LENGTH
67);
68pub const ED25519_PRIVATE_KEY_KEYBLOB_SIZE: usize = 100;
70const_assert_eq!(
71 ED25519_PRIVATE_KEY_KEYBLOB_SIZE,
72 ED25519_PRIVATE_KEY_KEYBLOB_LENGTH + 1
73);
74const V3_ONION_SERVICE_ID_RAW_SIZE: usize = 35;
76const V3_ONION_SERVICE_ID_CHECKSUM_OFFSET: usize = 32;
78const V3_ONION_SERVICE_ID_VERSION_OFFSET: usize = 34;
80const TRUNCATED_CHECKSUM_SIZE: usize = 2;
82pub const X25519_PRIVATE_KEY_SIZE: usize = 32;
85pub const X25519_PUBLIC_KEY_SIZE: usize = 32;
88pub const X25519_PRIVATE_KEY_BASE64_LENGTH: usize = 44;
90pub const X25519_PRIVATE_KEY_BASE64_SIZE: usize = 45;
92const_assert_eq!(
93 X25519_PRIVATE_KEY_BASE64_SIZE,
94 X25519_PRIVATE_KEY_BASE64_LENGTH + 1
95);
96pub const X25519_PUBLIC_KEY_BASE32_LENGTH: usize = 52;
98pub const X25519_PUBLIC_KEY_BASE32_SIZE: usize = 53;
100const_assert_eq!(
101 X25519_PUBLIC_KEY_BASE32_SIZE,
102 X25519_PUBLIC_KEY_BASE32_LENGTH + 1
103);
104
105const ONION_BASE32: data_encoding::Encoding = new_encoding! {
106 symbols: "abcdefghijklmnopqrstuvwxyz234567",
107 padding: '=',
108};
109
110#[cfg(feature = "legacy-tor-provider")]
114pub(crate) fn generate_password(length: usize) -> String {
115 let password: String = std::iter::repeat(())
116 .map(|()| OsRng.sample(Alphanumeric))
117 .map(char::from)
118 .take(length)
119 .collect();
120
121 password
122}
123
124pub struct Ed25519PrivateKey {
130 expanded_keypair: pk::ed25519::ExpandedKeypair,
131}
132
133#[derive(Clone)]
137pub struct Ed25519PublicKey {
138 public_key: pk::ed25519::PublicKey,
139}
140
141#[derive(Clone)]
143pub struct Ed25519Signature {
144 signature: pk::ed25519::Signature,
145}
146
147#[derive(Clone)]
149pub struct X25519PrivateKey {
150 secret_key: pk::curve25519::StaticSecret,
151}
152
153#[derive(Clone, PartialEq, Eq, Hash)]
155pub struct X25519PublicKey {
156 public_key: pk::curve25519::PublicKey,
157}
158
159#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
161pub struct V3OnionServiceId {
162 data: [u8; V3_ONION_SERVICE_ID_STRING_LENGTH],
163}
164
165#[derive(Clone, Copy)]
167pub enum SignBit {
168 Zero,
169 One,
170}
171
172impl From<SignBit> for u8 {
173 fn from(signbit: SignBit) -> Self {
174 match signbit {
175 SignBit::Zero => 0u8,
176 SignBit::One => 1u8,
177 }
178 }
179}
180
181impl From<SignBit> for bool {
182 fn from(signbit: SignBit) -> Self {
183 match signbit {
184 SignBit::Zero => false,
185 SignBit::One => true,
186 }
187 }
188}
189
190impl From<bool> for SignBit {
191 fn from(signbit: bool) -> Self {
192 if signbit {
193 SignBit::One
194 } else {
195 SignBit::Zero
196 }
197 }
198}
199
200enum FromRawValidationMethod {
203 #[cfg(feature = "legacy-tor-provider")]
206 LegacyCTor,
207 Ed25519Dalek,
210}
211
212impl Ed25519PrivateKey {
214 pub fn generate() -> Ed25519PrivateKey {
216 let csprng = &mut OsRng;
217 let keypair = pk::ed25519::Keypair::generate(csprng);
218
219 Ed25519PrivateKey {
220 expanded_keypair: pk::ed25519::ExpandedKeypair::from(&keypair),
221 }
222 }
223
224 fn from_raw_impl(
225 raw: &[u8; ED25519_PRIVATE_KEY_SIZE],
226 method: FromRawValidationMethod,
227 ) -> Result<Ed25519PrivateKey, Error> {
228 match method {
230 #[cfg(feature = "legacy-tor-provider")]
231 FromRawValidationMethod::LegacyCTor => {
232 if !(raw[0] == raw[0] & 248 && raw[31] == (raw[31] & 63) | 64) {
235 return Err(Error::KeyInvalid);
236 }
237 }
238 FromRawValidationMethod::Ed25519Dalek => {
239 let scalar: [u8; 32] = raw[..32].try_into().unwrap();
241 if scalar.iter().all(|&x| x == 0x00u8) {
242 return Err(Error::KeyInvalid);
243 }
244 let reduced_scalar = Scalar::from_bytes_mod_order(scalar).to_bytes();
245 if scalar != reduced_scalar {
246 return Err(Error::KeyInvalid);
247 }
248 }
249 }
250
251 if let Some(expanded_keypair) = pk::ed25519::ExpandedKeypair::from_secret_key_bytes(*raw) {
252 Ok(Ed25519PrivateKey { expanded_keypair })
253 } else {
254 Err(Error::KeyInvalid)
255 }
256 }
257
258 pub fn from_raw(raw: &[u8; ED25519_PRIVATE_KEY_SIZE]) -> Result<Ed25519PrivateKey, Error> {
262 Self::from_raw_impl(raw, FromRawValidationMethod::Ed25519Dalek)
263 }
264
265 fn from_key_blob_impl(
266 key_blob: &str,
267 method: FromRawValidationMethod,
268 ) -> Result<Ed25519PrivateKey, Error> {
269 if key_blob.len() != ED25519_PRIVATE_KEY_KEYBLOB_LENGTH {
270 return Err(Error::ParseError(format!(
271 "expects string of length '{}'; received string with length '{}'",
272 ED25519_PRIVATE_KEY_KEYBLOB_LENGTH,
273 key_blob.len()
274 )));
275 }
276
277 if !key_blob.starts_with(ED25519_PRIVATE_KEY_KEYBLOB_HEADER) {
278 return Err(Error::ParseError(format!(
279 "expects string that begins with '{}'; received '{}'",
280 &ED25519_PRIVATE_KEY_KEYBLOB_HEADER, &key_blob
281 )));
282 }
283
284 let base64_key: &str = &key_blob[ED25519_PRIVATE_KEY_KEYBLOB_HEADER.len()..];
285 let private_key_data = match BASE64.decode(base64_key.as_bytes()) {
286 Ok(private_key_data) => private_key_data,
287 Err(_) => {
288 return Err(Error::ParseError(format!(
289 "could not parse '{}' as base64",
290 base64_key
291 )))
292 }
293 };
294 let private_key_data_len = private_key_data.len();
295 let private_key_data_raw: [u8; ED25519_PRIVATE_KEY_SIZE] = match private_key_data.try_into()
296 {
297 Ok(private_key_data) => private_key_data,
298 Err(_) => {
299 return Err(Error::ParseError(format!(
300 "expects decoded private key length '{}'; actual '{}'",
301 ED25519_PRIVATE_KEY_SIZE, private_key_data_len
302 )))
303 }
304 };
305
306 Ed25519PrivateKey::from_raw_impl(&private_key_data_raw, method)
307 }
308
309 #[cfg(feature = "legacy-tor-provider")]
310 pub(crate) fn from_key_blob_legacy(key_blob: &str) -> Result<Ed25519PrivateKey, Error> {
311 Self::from_key_blob_impl(key_blob, FromRawValidationMethod::LegacyCTor)
312 }
313
314 pub fn from_key_blob(key_blob: &str) -> Result<Ed25519PrivateKey, Error> {
319 Self::from_key_blob_impl(key_blob, FromRawValidationMethod::Ed25519Dalek)
320 }
321
322 pub fn from_private_x25519(
324 x25519_private: &X25519PrivateKey,
325 ) -> Result<(Ed25519PrivateKey, SignBit), Error> {
326 if let Some((result, signbit)) =
327 convert_curve25519_to_ed25519_private(&x25519_private.secret_key)
328 {
329 Ok((
330 Ed25519PrivateKey {
331 expanded_keypair: result,
332 },
333 match signbit {
334 0u8 => SignBit::Zero,
335 1u8 => SignBit::One,
336 invalid_signbit => {
337 return Err(Error::ConversionError(format!(
338 "convert_curve25519_to_ed25519_private() returned invalid signbit: {}",
339 invalid_signbit
340 )))
341 }
342 },
343 ))
344 } else {
345 Err(Error::ConversionError(
346 "could not convert x25519 private key to ed25519 private key".to_string(),
347 ))
348 }
349 }
350
351 pub fn to_key_blob(&self) -> String {
353 let mut key_blob = ED25519_PRIVATE_KEY_KEYBLOB_HEADER.to_string();
354 key_blob.push_str(&BASE64.encode(&self.expanded_keypair.to_secret_key_bytes()));
355
356 key_blob
357 }
358
359 pub fn sign_message(&self, message: &[u8]) -> Ed25519Signature {
363 let signature = self.expanded_keypair.sign(message);
364 Ed25519Signature { signature }
365 }
366
367 pub fn to_bytes(&self) -> [u8; ED25519_PRIVATE_KEY_SIZE] {
369 self.expanded_keypair.to_secret_key_bytes()
370 }
371
372 #[cfg(feature = "arti-client-tor-provider")]
373 pub(crate) fn inner(&self) -> &pk::ed25519::ExpandedKeypair {
374 &self.expanded_keypair
375 }
376}
377
378impl PartialEq for Ed25519PrivateKey {
379 fn eq(&self, other: &Self) -> bool {
380 self.to_bytes().eq(&other.to_bytes())
381 }
382}
383
384impl Clone for Ed25519PrivateKey {
385 fn clone(&self) -> Ed25519PrivateKey {
386 match Ed25519PrivateKey::from_raw(&self.to_bytes()) {
387 Ok(ed25519_private_key) => ed25519_private_key,
388 Err(_) => unreachable!(),
389 }
390 }
391}
392
393impl std::fmt::Debug for Ed25519PrivateKey {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 write!(f, "--- ed25519 private key ---")
396 }
397}
398
399impl Ed25519PublicKey {
401 pub fn from_raw(raw: &[u8; ED25519_PUBLIC_KEY_SIZE]) -> Result<Ed25519PublicKey, Error> {
403 Ok(Ed25519PublicKey {
404 public_key: match pk::ed25519::PublicKey::from_bytes(raw) {
405 Ok(public_key) => public_key,
406 Err(_) => return Err(Error::KeyInvalid),
407 },
408 })
409 }
410
411 pub fn from_service_id(service_id: &V3OnionServiceId) -> Result<Ed25519PublicKey, Error> {
413 let mut decoded_service_id = [0u8; V3_ONION_SERVICE_ID_RAW_SIZE];
415 let decoded_byte_count =
416 match ONION_BASE32.decode_mut(service_id.as_bytes(), &mut decoded_service_id) {
417 Ok(decoded_byte_count) => decoded_byte_count,
418 Err(_) => {
419 return Err(Error::ConversionError(format!(
420 "failed to decode '{}' as V3OnionServiceId",
421 service_id
422 )))
423 }
424 };
425 if decoded_byte_count != V3_ONION_SERVICE_ID_RAW_SIZE {
426 return Err(Error::ConversionError(format!(
427 "decoded byte count is '{}', expected '{}'",
428 decoded_byte_count, V3_ONION_SERVICE_ID_RAW_SIZE
429 )));
430 }
431
432 Ed25519PublicKey::from_raw(
433 decoded_service_id[0..ED25519_PUBLIC_KEY_SIZE]
434 .try_into()
435 .unwrap(),
436 )
437 }
438
439 pub fn from_private_key(private_key: &Ed25519PrivateKey) -> Ed25519PublicKey {
441 Ed25519PublicKey {
442 public_key: *private_key.expanded_keypair.public(),
443 }
444 }
445
446 fn from_public_x25519(
447 public_x25519: &X25519PublicKey,
448 signbit: SignBit,
449 ) -> Result<Ed25519PublicKey, Error> {
450 match convert_curve25519_to_ed25519_public(&public_x25519.public_key, signbit.into()) {
451 Some(public_key) => Ok(Ed25519PublicKey { public_key }),
452 None => Err(Error::ConversionError(
453 "failed to create ed25519 public key from x25519 public key and signbit"
454 .to_string(),
455 )),
456 }
457 }
458
459 pub fn as_bytes(&self) -> &[u8; ED25519_PUBLIC_KEY_SIZE] {
461 self.public_key.as_bytes()
462 }
463}
464
465impl PartialEq for Ed25519PublicKey {
466 fn eq(&self, other: &Self) -> bool {
467 self.public_key.eq(&other.public_key)
468 }
469}
470
471impl std::fmt::Debug for Ed25519PublicKey {
472 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473 self.public_key.fmt(f)
474 }
475}
476
477
478impl Ed25519Signature {
480 pub fn from_raw(raw: &[u8; ED25519_SIGNATURE_SIZE]) -> Result<Ed25519Signature, Error> {
482 Ok(Ed25519Signature {
484 signature: pk::ed25519::Signature::from_bytes(raw),
485 })
486 }
487
488 pub fn verify(&self, message: &[u8], public_key: &Ed25519PublicKey) -> bool {
490 if let Ok(()) = public_key
491 .public_key
492 .verify_strict(message, &self.signature)
493 {
494 return true;
495 }
496 false
497 }
498
499 pub fn verify_x25519(
501 &self,
502 message: &[u8],
503 public_key: &X25519PublicKey,
504 signbit: SignBit,
505 ) -> bool {
506 if let Ok(public_key) = Ed25519PublicKey::from_public_x25519(public_key, signbit) {
507 return self.verify(message, &public_key);
508 }
509 false
510 }
511
512 pub fn to_bytes(&self) -> [u8; ED25519_SIGNATURE_SIZE] {
514 self.signature.to_bytes()
515 }
516}
517
518impl PartialEq for Ed25519Signature {
519 fn eq(&self, other: &Self) -> bool {
520 self.signature.eq(&other.signature)
521 }
522}
523
524impl std::fmt::Debug for Ed25519Signature {
525 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
526 self.signature.fmt(f)
527 }
528}
529
530impl X25519PrivateKey {
532 pub fn generate() -> X25519PrivateKey {
534 let csprng = &mut OsRng;
535 X25519PrivateKey {
536 secret_key: pk::curve25519::StaticSecret::random_from_rng(csprng),
537 }
538 }
539
540 pub fn from_raw(raw: &[u8; X25519_PRIVATE_KEY_SIZE]) -> Result<X25519PrivateKey, Error> {
544 if raw[0] == raw[0] & 240 && raw[31] == (raw[31] & 127) | 64 {
546 Ok(X25519PrivateKey {
547 secret_key: pk::curve25519::StaticSecret::from(*raw),
548 })
549 } else {
550 Err(Error::KeyInvalid)
551 }
552 }
553
554 pub fn from_base64(base64: &str) -> Result<X25519PrivateKey, Error> {
561 if base64.len() != X25519_PRIVATE_KEY_BASE64_LENGTH {
563 return Err(Error::ParseError(format!(
564 "expects string of length '{}'; received string with length '{}'",
565 X25519_PRIVATE_KEY_BASE64_LENGTH,
566 base64.len()
567 )));
568 }
569
570 let private_key_data = match BASE64.decode(base64.as_bytes()) {
571 Ok(private_key_data) => private_key_data,
572 Err(_) => {
573 return Err(Error::ParseError(format!(
574 "could not parse '{}' as base64",
575 base64
576 )))
577 }
578 };
579 let private_key_data_len = private_key_data.len();
580 let private_key_data_raw: [u8; X25519_PRIVATE_KEY_SIZE] = match private_key_data.try_into()
581 {
582 Ok(private_key_data) => private_key_data,
583 Err(_) => {
584 return Err(Error::ParseError(format!(
585 "expects decoded private key length '{}'; actual '{}'",
586 X25519_PRIVATE_KEY_SIZE, private_key_data_len
587 )))
588 }
589 };
590
591 X25519PrivateKey::from_raw(&private_key_data_raw)
592 }
593
594 pub fn sign_message(&self, message: &[u8]) -> Result<(Ed25519Signature, SignBit), Error> {
601 let (ed25519_private, signbit) = Ed25519PrivateKey::from_private_x25519(self)?;
602 Ok((ed25519_private.sign_message(message), signbit))
603 }
604
605 pub fn to_base64(&self) -> String {
607 BASE64.encode(&self.secret_key.to_bytes())
608 }
609
610 pub fn to_bytes(&self) -> [u8; X25519_PRIVATE_KEY_SIZE] {
612 self.secret_key.to_bytes()
613 }
614
615 #[cfg(feature = "arti-client-tor-provider")]
616 pub(crate) fn inner(&self) -> &pk::curve25519::StaticSecret {
617 &self.secret_key
618 }
619}
620
621impl PartialEq for X25519PrivateKey {
622 fn eq(&self, other: &Self) -> bool {
623 self.secret_key.to_bytes() == other.secret_key.to_bytes()
624 }
625}
626
627impl std::fmt::Debug for X25519PrivateKey {
628 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
629 write!(f, "--- x25519 private key ---")
630 }
631}
632
633impl X25519PublicKey {
635 pub fn from_private_key(private_key: &X25519PrivateKey) -> X25519PublicKey {
637 X25519PublicKey {
638 public_key: pk::curve25519::PublicKey::from(&private_key.secret_key),
639 }
640 }
641
642 pub fn from_raw(raw: &[u8; X25519_PUBLIC_KEY_SIZE]) -> X25519PublicKey {
644 X25519PublicKey {
645 public_key: pk::curve25519::PublicKey::from(*raw),
646 }
647 }
648
649 pub fn from_base32(base32: &str) -> Result<X25519PublicKey, Error> {
657 if base32.len() != X25519_PUBLIC_KEY_BASE32_LENGTH {
658 return Err(Error::ParseError(format!(
659 "expects string of length '{}'; received '{}' with length '{}'",
660 X25519_PUBLIC_KEY_BASE32_LENGTH,
661 base32,
662 base32.len()
663 )));
664 }
665
666 let public_key_data = match BASE32_NOPAD.decode(base32.as_bytes()) {
667 Ok(public_key_data) => public_key_data,
668 Err(_) => {
669 return Err(Error::ParseError(format!(
670 "failed to decode '{}' as X25519PublicKey",
671 base32
672 )))
673 }
674 };
675 let public_key_data_len = public_key_data.len();
676 let public_key_data_raw: [u8; X25519_PUBLIC_KEY_SIZE] = match public_key_data.try_into() {
677 Ok(public_key_data) => public_key_data,
678 Err(_) => {
679 return Err(Error::ParseError(format!(
680 "expects decoded public key length '{}'; actual '{}'",
681 X25519_PUBLIC_KEY_SIZE, public_key_data_len
682 )))
683 }
684 };
685
686 Ok(X25519PublicKey::from_raw(&public_key_data_raw))
687 }
688
689 pub fn to_base32(&self) -> String {
691 BASE32_NOPAD.encode(self.public_key.as_bytes())
692 }
693
694 pub fn as_bytes(&self) -> &[u8; X25519_PUBLIC_KEY_SIZE] {
696 self.public_key.as_bytes()
697 }
698
699 #[cfg(feature = "arti-client-tor-provider")]
700 pub(crate) fn inner(&self) -> &pk::curve25519::PublicKey {
701 &self.public_key
702 }
703
704}
705
706impl std::fmt::Debug for X25519PublicKey {
707 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
708 write!(f, "{}", self.to_base32())
709 }
710}
711
712impl V3OnionServiceId {
714 fn calc_truncated_checksum(
716 public_key: &[u8; ED25519_PUBLIC_KEY_SIZE],
717 ) -> [u8; TRUNCATED_CHECKSUM_SIZE] {
718 let mut hasher = Sha3_256::new();
719
720 hasher.update(b".onion checksum");
722 hasher.update(public_key);
723 hasher.update([0x03u8]);
724 let hash_bytes = hasher.finalize();
725
726 [hash_bytes[0], hash_bytes[1]]
727 }
728
729 pub fn from_string(service_id: &str) -> Result<V3OnionServiceId, Error> {
742 if !V3OnionServiceId::is_valid(service_id) {
743 return Err(Error::ParseError(format!(
744 "'{}' is not a valid v3 onion service id",
745 service_id
746 )));
747 }
748 Ok(V3OnionServiceId {
749 data: service_id.as_bytes().try_into().unwrap(),
750 })
751 }
752
753 pub fn from_public_key(public_key: &Ed25519PublicKey) -> V3OnionServiceId {
755 let mut raw_service_id = [0u8; V3_ONION_SERVICE_ID_RAW_SIZE];
756
757 raw_service_id[..ED25519_PUBLIC_KEY_SIZE].copy_from_slice(&public_key.as_bytes()[..]);
758 let truncated_checksum = Self::calc_truncated_checksum(public_key.as_bytes());
759 raw_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET] = truncated_checksum[0];
760 raw_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET + 1] = truncated_checksum[1];
761 raw_service_id[V3_ONION_SERVICE_ID_VERSION_OFFSET] = 0x03u8;
762
763 let mut service_id = [0u8; V3_ONION_SERVICE_ID_STRING_LENGTH];
764 ONION_BASE32.encode_mut(&raw_service_id, &mut service_id);
766
767 V3OnionServiceId { data: service_id }
768 }
769
770 pub fn from_private_key(private_key: &Ed25519PrivateKey) -> V3OnionServiceId {
772 Self::from_public_key(&Ed25519PublicKey::from_private_key(private_key))
773 }
774
775 pub fn is_valid(service_id: &str) -> bool {
777 if service_id.len() != V3_ONION_SERVICE_ID_STRING_LENGTH {
778 return false;
779 }
780
781 let mut decoded_service_id = [0u8; V3_ONION_SERVICE_ID_RAW_SIZE];
782 match ONION_BASE32.decode_mut(service_id.as_bytes(), &mut decoded_service_id) {
783 Ok(decoded_byte_count) => {
784 if decoded_byte_count != V3_ONION_SERVICE_ID_RAW_SIZE {
786 return false;
787 }
788 if decoded_service_id[V3_ONION_SERVICE_ID_VERSION_OFFSET] != 0x03 {
790 return false;
791 }
792 let mut public_key = [0u8; ED25519_PUBLIC_KEY_SIZE];
794 public_key[..].copy_from_slice(&decoded_service_id[..ED25519_PUBLIC_KEY_SIZE]);
795 let truncated_checksum = Self::calc_truncated_checksum(&public_key);
797 if truncated_checksum[0] != decoded_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET]
798 || truncated_checksum[1]
799 != decoded_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET + 1]
800 {
801 return false;
802 }
803 true
804 }
805 Err(_) => false,
806 }
807 }
808
809 pub fn as_bytes(&self) -> &[u8; V3_ONION_SERVICE_ID_STRING_LENGTH] {
811 &self.data
812 }
813}
814
815impl std::fmt::Display for V3OnionServiceId {
816 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
817 unsafe { write!(f, "{}", str::from_utf8_unchecked(&self.data)) }
818 }
819}
820
821impl std::fmt::Debug for V3OnionServiceId {
822 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
823 unsafe { write!(f, "{}", str::from_utf8_unchecked(&self.data)) }
824 }
825}