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(any(feature = "legacy-tor-provider", feature = "arti-tor-provider"))]
10use rand::distr::Alphanumeric;
11#[cfg(any(feature = "legacy-tor-provider", feature = "arti-tor-provider"))]
12use rand::Rng;
13use sha3::{Digest, Sha3_256};
14use static_assertions::const_assert_eq;
15use tor_llcrypto::pk::keymanip::*;
16use tor_llcrypto::*;
17
18#[derive(thiserror::Error, Debug)]
20pub enum Error {
21 #[error("{0}")]
23 ParseError(String),
24
25 #[error("{0}")]
27 ConversionError(String),
28
29 #[error("invalid key")]
31 KeyInvalid,
32}
33
34pub const ED25519_PRIVATE_KEY_SIZE: usize = 64;
37pub const ED25519_PUBLIC_KEY_SIZE: usize = 32;
40pub const ED25519_SIGNATURE_SIZE: usize = 64;
43pub const V3_ONION_SERVICE_ID_STRING_LENGTH: usize = 56;
45pub const V3_ONION_SERVICE_ID_STRING_SIZE: usize = 57;
47const_assert_eq!(
48 V3_ONION_SERVICE_ID_STRING_SIZE,
49 V3_ONION_SERVICE_ID_STRING_LENGTH + 1
50);
51pub const ED25519_PRIVATE_KEYBLOB_BASE64_LENGTH: usize = 88;
53const ED25519_PRIVATE_KEY_KEYBLOB_HEADER: &str = "ED25519-V3:";
55pub const ED25519_PRIVATE_KEY_KEYBLOB_HEADER_LENGTH: usize = 11;
57const_assert_eq!(
58 ED25519_PRIVATE_KEY_KEYBLOB_HEADER_LENGTH,
59 ED25519_PRIVATE_KEY_KEYBLOB_HEADER.len()
60);
61pub const ED25519_PRIVATE_KEY_KEYBLOB_LENGTH: usize = 99;
63const_assert_eq!(
64 ED25519_PRIVATE_KEY_KEYBLOB_LENGTH,
65 ED25519_PRIVATE_KEY_KEYBLOB_HEADER_LENGTH + ED25519_PRIVATE_KEYBLOB_BASE64_LENGTH
66);
67pub const ED25519_PRIVATE_KEY_KEYBLOB_SIZE: usize = 100;
69const_assert_eq!(
70 ED25519_PRIVATE_KEY_KEYBLOB_SIZE,
71 ED25519_PRIVATE_KEY_KEYBLOB_LENGTH + 1
72);
73const V3_ONION_SERVICE_ID_RAW_SIZE: usize = 35;
75const V3_ONION_SERVICE_ID_CHECKSUM_OFFSET: usize = 32;
77const V3_ONION_SERVICE_ID_VERSION_OFFSET: usize = 34;
79const TRUNCATED_CHECKSUM_SIZE: usize = 2;
81pub const X25519_PRIVATE_KEY_SIZE: usize = 32;
84pub const X25519_PUBLIC_KEY_SIZE: usize = 32;
87pub const X25519_PRIVATE_KEY_BASE64_LENGTH: usize = 44;
89pub const X25519_PRIVATE_KEY_BASE64_SIZE: usize = 45;
91const_assert_eq!(
92 X25519_PRIVATE_KEY_BASE64_SIZE,
93 X25519_PRIVATE_KEY_BASE64_LENGTH + 1
94);
95pub const X25519_PUBLIC_KEY_BASE32_LENGTH: usize = 52;
97pub const X25519_PUBLIC_KEY_BASE32_SIZE: usize = 53;
99const_assert_eq!(
100 X25519_PUBLIC_KEY_BASE32_SIZE,
101 X25519_PUBLIC_KEY_BASE32_LENGTH + 1
102);
103
104const ONION_BASE32: data_encoding::Encoding = new_encoding! {
105 symbols: "abcdefghijklmnopqrstuvwxyz234567",
106 padding: '=',
107};
108
109#[cfg(any(feature = "legacy-tor-provider", feature = "arti-tor-provider"))]
113pub(crate) fn generate_password(length: usize) -> String {
114 let password: String = std::iter::repeat(())
115 .map(|()| tor_llcrypto::rng::CautiousRng.sample(Alphanumeric))
116 .map(char::from)
117 .take(length)
118 .collect();
119
120 password
121}
122
123pub struct Ed25519PrivateKey {
129 expanded_keypair: pk::ed25519::ExpandedKeypair,
130}
131
132#[derive(Clone, Eq, PartialEq)]
136pub struct Ed25519PublicKey {
137 public_key: pk::ed25519::PublicKey,
138}
139
140#[derive(Clone)]
142pub struct Ed25519Signature {
143 signature: pk::ed25519::Signature,
144}
145
146#[derive(Clone)]
148pub struct X25519PrivateKey {
149 secret_key: pk::curve25519::StaticSecret,
150}
151
152#[derive(Clone, Eq, PartialEq)]
154pub struct X25519PublicKey {
155 public_key: pk::curve25519::PublicKey,
156}
157
158#[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
160pub struct V3OnionServiceId {
161 data: [u8; V3_ONION_SERVICE_ID_STRING_LENGTH],
162}
163
164#[derive(Clone, Copy)]
166pub enum SignBit {
167 Zero,
168 One,
169}
170
171impl From<SignBit> for u8 {
172 fn from(signbit: SignBit) -> Self {
173 match signbit {
174 SignBit::Zero => 0u8,
175 SignBit::One => 1u8,
176 }
177 }
178}
179
180impl From<SignBit> for bool {
181 fn from(signbit: SignBit) -> Self {
182 match signbit {
183 SignBit::Zero => false,
184 SignBit::One => true,
185 }
186 }
187}
188
189impl From<bool> for SignBit {
190 fn from(signbit: bool) -> Self {
191 if signbit {
192 SignBit::One
193 } else {
194 SignBit::Zero
195 }
196 }
197}
198
199enum FromRawValidationMethod {
202 #[cfg(feature = "legacy-tor-provider")]
205 LegacyCTor,
206 Ed25519Dalek,
209}
210
211impl Ed25519PrivateKey {
213 pub fn generate() -> Ed25519PrivateKey {
215 let csprng = &mut tor_llcrypto::rng::CautiousRng;
216 let keypair = pk::ed25519::Keypair::generate(csprng);
217
218 Ed25519PrivateKey {
219 expanded_keypair: pk::ed25519::ExpandedKeypair::from(&keypair),
220 }
221 }
222
223 fn from_raw_impl(
224 raw: &[u8; ED25519_PRIVATE_KEY_SIZE],
225 method: FromRawValidationMethod,
226 ) -> Result<Ed25519PrivateKey, Error> {
227 match method {
229 #[cfg(feature = "legacy-tor-provider")]
230 FromRawValidationMethod::LegacyCTor => {
231 if !(raw[0] == raw[0] & 248 && raw[31] == (raw[31] & 63) | 64) {
234 return Err(Error::KeyInvalid);
235 }
236 }
237 FromRawValidationMethod::Ed25519Dalek => {
238 let scalar: [u8; 32] = raw[..32].try_into().unwrap();
240 if scalar.iter().all(|&x| x == 0x00u8) {
241 return Err(Error::KeyInvalid);
242 }
243 let reduced_scalar = Scalar::from_bytes_mod_order(scalar).to_bytes();
244 if scalar != reduced_scalar {
245 return Err(Error::KeyInvalid);
246 }
247 }
248 }
249
250 if let Some(expanded_keypair) = pk::ed25519::ExpandedKeypair::from_secret_key_bytes(*raw) {
251 Ok(Ed25519PrivateKey { expanded_keypair })
252 } else {
253 Err(Error::KeyInvalid)
254 }
255 }
256
257 pub fn from_raw(raw: &[u8; ED25519_PRIVATE_KEY_SIZE]) -> Result<Ed25519PrivateKey, Error> {
261 Self::from_raw_impl(raw, FromRawValidationMethod::Ed25519Dalek)
262 }
263
264 fn from_key_blob_impl(
265 key_blob: &str,
266 method: FromRawValidationMethod,
267 ) -> Result<Ed25519PrivateKey, Error> {
268 if key_blob.len() != ED25519_PRIVATE_KEY_KEYBLOB_LENGTH {
269 return Err(Error::ParseError(format!(
270 "expects string of length '{}'; received string with length '{}'",
271 ED25519_PRIVATE_KEY_KEYBLOB_LENGTH,
272 key_blob.len()
273 )));
274 }
275
276 if !key_blob.starts_with(ED25519_PRIVATE_KEY_KEYBLOB_HEADER) {
277 return Err(Error::ParseError(format!(
278 "expects string that begins with '{}'; received '{}'",
279 &ED25519_PRIVATE_KEY_KEYBLOB_HEADER, &key_blob
280 )));
281 }
282
283 let base64_key: &str = &key_blob[ED25519_PRIVATE_KEY_KEYBLOB_HEADER.len()..];
284 let private_key_data = match BASE64.decode(base64_key.as_bytes()) {
285 Ok(private_key_data) => private_key_data,
286 Err(_) => {
287 return Err(Error::ParseError(format!(
288 "could not parse '{}' as base64",
289 base64_key
290 )))
291 }
292 };
293 let private_key_data_len = private_key_data.len();
294 let private_key_data_raw: [u8; ED25519_PRIVATE_KEY_SIZE] = match private_key_data.try_into()
295 {
296 Ok(private_key_data) => private_key_data,
297 Err(_) => {
298 return Err(Error::ParseError(format!(
299 "expects decoded private key length '{}'; actual '{}'",
300 ED25519_PRIVATE_KEY_SIZE, private_key_data_len
301 )))
302 }
303 };
304
305 Ed25519PrivateKey::from_raw_impl(&private_key_data_raw, method)
306 }
307
308 #[cfg(feature = "legacy-tor-provider")]
313 pub fn from_key_blob_legacy(key_blob: &str) -> Result<Ed25519PrivateKey, Error> {
314 Self::from_key_blob_impl(key_blob, FromRawValidationMethod::LegacyCTor)
315 }
316
317 pub fn from_key_blob(key_blob: &str) -> Result<Ed25519PrivateKey, Error> {
322 Self::from_key_blob_impl(key_blob, FromRawValidationMethod::Ed25519Dalek)
323 }
324
325 pub fn from_private_x25519(
327 x25519_private: &X25519PrivateKey,
328 ) -> Result<(Ed25519PrivateKey, SignBit), Error> {
329 if let Some((result, signbit)) =
330 convert_curve25519_to_ed25519_private(&x25519_private.secret_key)
331 {
332 Ok((
333 Ed25519PrivateKey {
334 expanded_keypair: result,
335 },
336 match signbit {
337 0u8 => SignBit::Zero,
338 1u8 => SignBit::One,
339 invalid_signbit => {
340 return Err(Error::ConversionError(format!(
341 "convert_curve25519_to_ed25519_private() returned invalid signbit: {}",
342 invalid_signbit
343 )))
344 }
345 },
346 ))
347 } else {
348 Err(Error::ConversionError(
349 "could not convert x25519 private key to ed25519 private key".to_string(),
350 ))
351 }
352 }
353
354 pub fn to_key_blob(&self) -> String {
356 let mut key_blob = ED25519_PRIVATE_KEY_KEYBLOB_HEADER.to_string();
357 key_blob.push_str(&BASE64.encode(&self.expanded_keypair.to_secret_key_bytes()));
358
359 key_blob
360 }
361
362 pub fn sign_message(&self, message: &[u8]) -> Ed25519Signature {
366 let signature = self.expanded_keypair.sign(message);
367 Ed25519Signature { signature }
368 }
369
370 pub fn to_bytes(&self) -> [u8; ED25519_PRIVATE_KEY_SIZE] {
372 self.expanded_keypair.to_secret_key_bytes()
373 }
374
375 #[cfg(feature = "arti-client-tor-provider")]
376 pub(crate) fn inner(&self) -> &pk::ed25519::ExpandedKeypair {
377 &self.expanded_keypair
378 }
379}
380
381impl PartialEq for Ed25519PrivateKey {
382 fn eq(&self, other: &Self) -> bool {
383 self.to_bytes().eq(&other.to_bytes())
384 }
385}
386
387impl Clone for Ed25519PrivateKey {
388 fn clone(&self) -> Ed25519PrivateKey {
389 match Ed25519PrivateKey::from_raw(&self.to_bytes()) {
390 Ok(ed25519_private_key) => ed25519_private_key,
391 Err(_) => unreachable!(),
392 }
393 }
394}
395
396impl std::fmt::Debug for Ed25519PrivateKey {
397 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
398 write!(f, "--- ed25519 private key ---")
399 }
400}
401
402impl Ed25519PublicKey {
404 pub fn from_raw(raw: &[u8; ED25519_PUBLIC_KEY_SIZE]) -> Result<Ed25519PublicKey, Error> {
406 Ok(Ed25519PublicKey {
407 public_key: match pk::ed25519::PublicKey::from_bytes(raw) {
408 Ok(public_key) => public_key,
409 Err(_) => return Err(Error::KeyInvalid),
410 },
411 })
412 }
413
414 pub fn from_service_id(service_id: &V3OnionServiceId) -> Result<Ed25519PublicKey, Error> {
416 let mut decoded_service_id = [0u8; V3_ONION_SERVICE_ID_RAW_SIZE];
418 let decoded_byte_count =
419 match ONION_BASE32.decode_mut(service_id.as_bytes(), &mut decoded_service_id) {
420 Ok(decoded_byte_count) => decoded_byte_count,
421 Err(_) => {
422 return Err(Error::ConversionError(format!(
423 "failed to decode '{}' as V3OnionServiceId",
424 service_id
425 )))
426 }
427 };
428 if decoded_byte_count != V3_ONION_SERVICE_ID_RAW_SIZE {
429 return Err(Error::ConversionError(format!(
430 "decoded byte count is '{}', expected '{}'",
431 decoded_byte_count, V3_ONION_SERVICE_ID_RAW_SIZE
432 )));
433 }
434
435 Ed25519PublicKey::from_raw(
436 decoded_service_id[0..ED25519_PUBLIC_KEY_SIZE]
437 .try_into()
438 .unwrap(),
439 )
440 }
441
442 pub fn from_private_key(private_key: &Ed25519PrivateKey) -> Ed25519PublicKey {
444 Ed25519PublicKey {
445 public_key: *private_key.expanded_keypair.public(),
446 }
447 }
448
449 fn from_public_x25519(
450 public_x25519: &X25519PublicKey,
451 signbit: SignBit,
452 ) -> Result<Ed25519PublicKey, Error> {
453 match convert_curve25519_to_ed25519_public(&public_x25519.public_key, signbit.into()) {
454 Some(public_key) => Ok(Ed25519PublicKey { public_key }),
455 None => Err(Error::ConversionError(
456 "failed to create ed25519 public key from x25519 public key and signbit"
457 .to_string(),
458 )),
459 }
460 }
461
462 pub fn as_bytes(&self) -> &[u8; ED25519_PUBLIC_KEY_SIZE] {
464 self.public_key.as_bytes()
465 }
466}
467
468impl PartialOrd for Ed25519PublicKey {
469 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
470 self.as_bytes().partial_cmp(other.as_bytes())
471 }
472}
473
474impl Ord for Ed25519PublicKey {
475 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
476 self.as_bytes().cmp(other.as_bytes())
477 }
478}
479
480impl std::fmt::Debug for Ed25519PublicKey {
481 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
482 self.public_key.fmt(f)
483 }
484}
485
486impl Ed25519Signature {
488 pub fn from_raw(raw: &[u8; ED25519_SIGNATURE_SIZE]) -> Result<Ed25519Signature, Error> {
490 Ok(Ed25519Signature {
492 signature: pk::ed25519::Signature::from_bytes(raw),
493 })
494 }
495
496 pub fn verify(&self, message: &[u8], public_key: &Ed25519PublicKey) -> bool {
498 public_key
499 .public_key
500 .verify(message, &self.signature)
501 .is_ok()
502 }
503
504 pub fn verify_x25519(
506 &self,
507 message: &[u8],
508 public_key: &X25519PublicKey,
509 signbit: SignBit,
510 ) -> bool {
511 if let Ok(public_key) = Ed25519PublicKey::from_public_x25519(public_key, signbit) {
512 return self.verify(message, &public_key);
513 }
514 false
515 }
516
517 pub fn to_bytes(&self) -> [u8; ED25519_SIGNATURE_SIZE] {
519 self.signature.to_bytes()
520 }
521}
522
523impl PartialEq for Ed25519Signature {
524 fn eq(&self, other: &Self) -> bool {
525 self.signature.eq(&other.signature)
526 }
527}
528
529impl std::fmt::Debug for Ed25519Signature {
530 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531 self.signature.fmt(f)
532 }
533}
534
535impl X25519PrivateKey {
537 pub fn generate() -> X25519PrivateKey {
539 let csprng = &mut tor_llcrypto::rng::CautiousRng;
540 X25519PrivateKey {
541 secret_key: pk::curve25519::StaticSecret::random_from_rng(csprng),
542 }
543 }
544
545 pub fn from_raw(raw: &[u8; X25519_PRIVATE_KEY_SIZE]) -> Result<X25519PrivateKey, Error> {
549 Ok(X25519PrivateKey {
550 secret_key: pk::curve25519::StaticSecret::from(*raw),
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
705impl std::fmt::Debug for X25519PublicKey {
706 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
707 write!(f, "{}", self.to_base32())
708 }
709}
710
711impl V3OnionServiceId {
713 fn calc_truncated_checksum(
715 public_key: &[u8; ED25519_PUBLIC_KEY_SIZE],
716 ) -> [u8; TRUNCATED_CHECKSUM_SIZE] {
717 let mut hasher = Sha3_256::new();
718
719 hasher.update(b".onion checksum");
721 hasher.update(public_key);
722 hasher.update([0x03u8]);
723 let hash_bytes = hasher.finalize();
724
725 [hash_bytes[0], hash_bytes[1]]
726 }
727
728 pub fn from_string(service_id: &str) -> Result<V3OnionServiceId, Error> {
741 if !V3OnionServiceId::is_valid(service_id) {
742 return Err(Error::ParseError(format!(
743 "'{}' is not a valid v3 onion service id",
744 service_id
745 )));
746 }
747 Ok(V3OnionServiceId {
748 data: service_id.as_bytes().try_into().unwrap(),
749 })
750 }
751
752 pub fn from_public_key(public_key: &Ed25519PublicKey) -> V3OnionServiceId {
754 let mut raw_service_id = [0u8; V3_ONION_SERVICE_ID_RAW_SIZE];
755
756 raw_service_id[..ED25519_PUBLIC_KEY_SIZE].copy_from_slice(&public_key.as_bytes()[..]);
757 let truncated_checksum = Self::calc_truncated_checksum(public_key.as_bytes());
758 raw_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET] = truncated_checksum[0];
759 raw_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET + 1] = truncated_checksum[1];
760 raw_service_id[V3_ONION_SERVICE_ID_VERSION_OFFSET] = 0x03u8;
761
762 let mut service_id = [0u8; V3_ONION_SERVICE_ID_STRING_LENGTH];
763 ONION_BASE32.encode_mut(&raw_service_id, &mut service_id);
765
766 V3OnionServiceId { data: service_id }
767 }
768
769 pub fn from_private_key(private_key: &Ed25519PrivateKey) -> V3OnionServiceId {
771 Self::from_public_key(&Ed25519PublicKey::from_private_key(private_key))
772 }
773
774 pub fn is_valid(service_id: &str) -> bool {
776 if service_id.len() != V3_ONION_SERVICE_ID_STRING_LENGTH {
777 return false;
778 }
779
780 let mut decoded_service_id = [0u8; V3_ONION_SERVICE_ID_RAW_SIZE];
781 match ONION_BASE32.decode_mut(service_id.as_bytes(), &mut decoded_service_id) {
782 Ok(decoded_byte_count) => {
783 if decoded_byte_count != V3_ONION_SERVICE_ID_RAW_SIZE {
785 return false;
786 }
787 if decoded_service_id[V3_ONION_SERVICE_ID_VERSION_OFFSET] != 0x03 {
789 return false;
790 }
791 let mut public_key = [0u8; ED25519_PUBLIC_KEY_SIZE];
793 public_key[..].copy_from_slice(&decoded_service_id[..ED25519_PUBLIC_KEY_SIZE]);
794 let truncated_checksum = Self::calc_truncated_checksum(&public_key);
796 if truncated_checksum[0] != decoded_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET]
797 || truncated_checksum[1]
798 != decoded_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET + 1]
799 {
800 return false;
801 }
802 true
803 }
804 Err(_) => false,
805 }
806 }
807
808 pub fn as_bytes(&self) -> &[u8; V3_ONION_SERVICE_ID_STRING_LENGTH] {
810 &self.data
811 }
812}
813
814impl std::fmt::Display for V3OnionServiceId {
815 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
816 unsafe { write!(f, "{}", str::from_utf8_unchecked(&self.data)) }
817 }
818}
819
820impl std::fmt::Debug for V3OnionServiceId {
821 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
822 unsafe { write!(f, "{}", str::from_utf8_unchecked(&self.data)) }
823 }
824}