1use std::fmt::{self, Debug, Display};
8use std::str::FromStr;
9
10use derive_deftly::Deftly;
11use digest::Digest;
12use itertools::{Itertools, chain};
13use safelog::DisplayRedacted;
14use safelog::util::write_end_redacted;
15use subtle::ConstantTimeEq;
16use thiserror::Error;
17use tor_basic_utils::{StrExt as _, impl_debug_hex};
18use tor_key_forge::ToEncodableKey;
19use tor_llcrypto::d::Sha3_256;
20use tor_llcrypto::pk::ed25519::{Ed25519PublicKey, Ed25519SigningKey};
21use tor_llcrypto::pk::{curve25519, ed25519, keymanip};
22use tor_llcrypto::util::ct::CtByteArray;
23use tor_llcrypto::{
24 derive_deftly_template_ConstantTimeEq, derive_deftly_template_PartialEqFromCtEq,
25};
26
27use crate::macros::{define_bytes, define_pk_keypair};
28use crate::time::TimePeriod;
29
30#[allow(deprecated)]
31pub use hs_client_intro_auth::{HsClientIntroAuthKey, HsClientIntroAuthKeypair};
32
33define_bytes! {
34#[derive(Copy, Clone, Eq, PartialEq, Hash)]
46pub struct HsId([u8; 32]);
47}
48
49define_pk_keypair! {
50#[derive(Deftly)]
64#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
65pub struct HsIdKey(ed25519::PublicKey) /
66 #[derive(Deftly)]
80 #[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
81 HsIdKeypair(ed25519::ExpandedKeypair);
82}
83
84impl HsIdKey {
85 pub fn id(&self) -> HsId {
89 HsId(self.0.to_bytes().into())
90 }
91}
92impl TryFrom<HsId> for HsIdKey {
93 type Error = signature::Error;
94
95 fn try_from(value: HsId) -> Result<Self, Self::Error> {
96 ed25519::PublicKey::from_bytes(value.0.as_ref()).map(HsIdKey)
97 }
98}
99impl From<HsIdKey> for HsId {
100 fn from(value: HsIdKey) -> Self {
101 value.id()
102 }
103}
104
105impl From<&HsIdKeypair> for HsIdKey {
106 fn from(value: &HsIdKeypair) -> Self {
107 Self(*value.0.public())
108 }
109}
110
111impl From<HsIdKeypair> for HsIdKey {
112 fn from(value: HsIdKeypair) -> Self {
113 Self(*value.0.public())
114 }
115}
116
117const HSID_ONION_VERSION: u8 = 0x03;
119
120pub const HSID_ONION_SUFFIX: &str = ".onion";
122
123impl safelog::DisplayRedacted for HsId {
124 fn fmt_unredacted(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
125 let checksum = self.onion_checksum();
127 let binary = chain!(self.0.as_ref(), &checksum, &[HSID_ONION_VERSION],)
128 .cloned()
129 .collect_vec();
130 let mut b32 = data_encoding::BASE32_NOPAD.encode(&binary);
131 b32.make_ascii_lowercase();
132 write!(f, "{}{}", b32, HSID_ONION_SUFFIX)
133 }
134
135 fn fmt_redacted(&self, f: &mut fmt::Formatter) -> fmt::Result {
139 let unredacted = self.display_unredacted().to_string();
140 assert!(unredacted.ends_with(HSID_ONION_SUFFIX));
141
142 write_end_redacted(f, &unredacted, 3 + HSID_ONION_SUFFIX.len(), "[…]")
151 }
152}
153
154impl safelog::DebugRedacted for HsId {
155 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 write!(f, "HsId({})", self.display_redacted())
157 }
158
159 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 write!(f, "HsId({})", self.display_unredacted())
161 }
162}
163
164safelog::derive_redacted_debug!(HsId);
165
166impl FromStr for HsId {
167 type Err = HsIdParseError;
168 fn from_str(s: &str) -> Result<Self, HsIdParseError> {
169 use HsIdParseError as PE;
170
171 let s = s
172 .strip_suffix_ignore_ascii_case(HSID_ONION_SUFFIX)
173 .ok_or(PE::NotOnionDomain)?;
174
175 if s.contains('.') {
176 return Err(PE::HsIdContainsSubdomain);
177 }
178
179 let mut s = s.to_owned();
185 s.make_ascii_uppercase();
186
187 let binary = data_encoding::BASE32_NOPAD.decode(s.as_bytes())?;
192 let mut binary = tor_bytes::Reader::from_slice(&binary);
193
194 let pubkey: [u8; 32] = binary.extract()?;
195 let checksum: [u8; 2] = binary.extract()?;
196 let version: u8 = binary.extract()?;
197 let tentative = HsId(pubkey.into());
198
199 if version != HSID_ONION_VERSION {
201 return Err(PE::UnsupportedVersion(version));
202 }
203 if checksum != tentative.onion_checksum() {
204 return Err(PE::WrongChecksum);
205 }
206 Ok(tentative)
207 }
208}
209
210#[derive(Error, Clone, Debug)]
212#[non_exhaustive]
213pub enum HsIdParseError {
214 #[error("Domain name does not end in .onion")]
216 NotOnionDomain,
217
218 #[error("Invalid base32 in .onion address")]
222 InvalidBase32(#[from] data_encoding::DecodeError),
223
224 #[error("Invalid encoded binary data in .onion address")]
226 InvalidData(#[from] tor_bytes::Error),
227
228 #[error("Unsupported .onion address version, v{0}")]
230 UnsupportedVersion(u8),
231
232 #[error("Checksum failed, .onion address corrupted")]
234 WrongChecksum,
235
236 #[error("`.onion` address with subdomain passed where not expected")]
238 HsIdContainsSubdomain,
239}
240
241impl HsId {
242 fn onion_checksum(&self) -> [u8; 2] {
244 let mut h = Sha3_256::new();
245 h.update(b".onion checksum");
246 h.update(self.0.as_ref());
247 h.update([HSID_ONION_VERSION]);
248 h.finalize()[..2]
249 .try_into()
250 .expect("slice of fixed size wasn't that size")
251 }
252}
253
254impl HsIdKey {
255 pub fn compute_blinded_key(
257 &self,
258 cur_period: TimePeriod,
259 ) -> Result<(HsBlindIdKey, crate::Subcredential), keymanip::BlindingError> {
260 let secret = b"";
268 let h = self.blinding_factor(secret, cur_period);
269
270 let blinded_key = keymanip::blind_pubkey(&self.0, h)?.into();
271 let subcredential = self.compute_subcredential(&blinded_key, cur_period);
273
274 Ok((blinded_key, subcredential))
275 }
276
277 pub fn compute_subcredential(
279 &self,
280 blinded_key: &HsBlindIdKey,
281 cur_period: TimePeriod,
282 ) -> crate::Subcredential {
283 let subcredential_bytes: [u8; 32] = {
285 let n_hs_cred: [u8; 32] = {
289 let mut h = Sha3_256::new();
290 h.update(b"credential");
291 h.update(self.0.as_bytes());
292 h.finalize().into()
293 };
294 let mut h = Sha3_256::new();
295 h.update(b"subcredential");
296 h.update(n_hs_cred);
297 h.update(blinded_key.as_bytes());
298 h.finalize().into()
299 };
300
301 subcredential_bytes.into()
302 }
303
304 fn blinding_factor(&self, secret: &[u8], cur_period: TimePeriod) -> [u8; 32] {
309 const BLIND_STRING: &[u8] = b"Derive temporary signing key\0";
322 const ED25519_BASEPOINT: &[u8] =
324 b"(15112221349535400772501151409588531511454012693041857206046113283949847762202, \
325 46316835694926478169428394003475163141307993866256225615783033603165251855960)";
326
327 let mut h = Sha3_256::new();
328 h.update(BLIND_STRING);
329 h.update(self.0.as_bytes());
330 h.update(secret);
331 h.update(ED25519_BASEPOINT);
332 h.update(b"key-blind");
333 h.update(cur_period.interval_num.to_be_bytes());
334 h.update((u64::from(cur_period.length.as_minutes())).to_be_bytes());
335
336 h.finalize().into()
337 }
338}
339
340impl HsIdKeypair {
341 pub fn compute_blinded_key(
343 &self,
344 cur_period: TimePeriod,
345 ) -> Result<(HsBlindIdKey, HsBlindIdKeypair, crate::Subcredential), keymanip::BlindingError>
346 {
347 let secret = b"";
351
352 let public_key = HsIdKey(*self.0.public());
353
354 let (blinded_public_key, subcredential) = public_key.compute_blinded_key(cur_period)?;
359
360 let h = public_key.blinding_factor(secret, cur_period);
361
362 let blinded_keypair = keymanip::blind_keypair(&self.0, h)?;
363
364 Ok((blinded_public_key, blinded_keypair.into(), subcredential))
365 }
366}
367
368define_pk_keypair! {
369#[derive(Deftly)]
380#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
381pub struct HsBlindIdKey(ed25519::PublicKey) /
382
383#[derive(Deftly)]
384#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
385HsBlindIdKeypair(ed25519::ExpandedKeypair);
386}
387
388impl From<HsBlindIdKeypair> for HsBlindIdKey {
389 fn from(kp: HsBlindIdKeypair) -> HsBlindIdKey {
390 HsBlindIdKey(kp.0.into())
391 }
392}
393
394define_bytes! {
395#[derive(Copy, Clone, Eq, PartialEq, Hash)]
400pub struct HsBlindId([u8; 32]);
401}
402impl_debug_hex! { HsBlindId .0 }
403
404impl HsBlindIdKey {
405 pub fn id(&self) -> HsBlindId {
409 HsBlindId(self.0.to_bytes().into())
410 }
411}
412impl TryFrom<HsBlindId> for HsBlindIdKey {
413 type Error = signature::Error;
414
415 fn try_from(value: HsBlindId) -> Result<Self, Self::Error> {
416 ed25519::PublicKey::from_bytes(value.0.as_ref()).map(HsBlindIdKey)
417 }
418}
419
420impl From<&HsBlindIdKeypair> for HsBlindIdKey {
421 fn from(value: &HsBlindIdKeypair) -> Self {
422 HsBlindIdKey(*value.0.public())
423 }
424}
425
426impl From<HsBlindIdKey> for HsBlindId {
427 fn from(value: HsBlindIdKey) -> Self {
428 value.id()
429 }
430}
431impl From<ed25519::Ed25519Identity> for HsBlindId {
432 fn from(value: ed25519::Ed25519Identity) -> Self {
433 Self(CtByteArray::from(<[u8; 32]>::from(value)))
434 }
435}
436
437impl Ed25519SigningKey for HsBlindIdKeypair {
438 fn sign(&self, message: &[u8]) -> ed25519::Signature {
439 self.0.sign(message)
440 }
441}
442
443impl Ed25519PublicKey for HsBlindIdKeypair {
444 fn public_key(&self) -> ed25519::PublicKey {
445 *self.0.public()
446 }
447}
448
449define_pk_keypair! {
450#[derive(Deftly)]
464#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
465pub struct HsDescSigningKey(ed25519::PublicKey) /
466
467#[derive(Deftly)]
468#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
469HsDescSigningKeypair(ed25519::Keypair);
470}
471
472define_pk_keypair! {
473#[derive(Deftly)]
481#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
482pub struct HsIntroPtSessionIdKey(ed25519::PublicKey) /
483
484#[derive(Deftly)]
485#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
486HsIntroPtSessionIdKeypair(ed25519::Keypair);
487}
488
489define_pk_keypair! {
490#[derive(Deftly)]
497#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
498pub struct HsSvcNtorKey(curve25519::PublicKey) /
499
500#[derive(Deftly)]
501#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
502HsSvcNtorSecretKey(curve25519::StaticSecret);
503
504#[derive(Deftly)]
505#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
506curve25519_pair as HsSvcNtorKeypair;
507}
508
509mod hs_client_intro_auth {
510 #![allow(deprecated)]
511 use subtle::ConstantTimeEq;
514 use tor_llcrypto::pk::ed25519;
515 use tor_llcrypto::{
516 derive_deftly::Deftly, derive_deftly_template_ConstantTimeEq,
517 derive_deftly_template_PartialEqFromCtEq,
518 };
519
520 use crate::macros::define_pk_keypair;
521
522 define_pk_keypair! {
523 #[deprecated(note = "This key type is not used in the protocol implemented today.")]
529 #[derive(Deftly)]
530 #[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
531 pub struct HsClientIntroAuthKey(ed25519::PublicKey) /
532
533 #[deprecated(note = "This key type is not used in the protocol implemented today.")]
534 #[derive(Deftly)]
535 #[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
536 HsClientIntroAuthKeypair(ed25519::Keypair);
537 }
538}
539
540define_pk_keypair! {
541#[derive(Deftly)]
572#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
573pub struct HsClientDescEncKey(curve25519::PublicKey) /
574
575#[derive(Deftly)]
576#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
577HsClientDescEncSecretKey(curve25519::StaticSecret);
578
579#[derive(Deftly)]
580#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
581curve25519_pair as HsClientDescEncKeypair;
582}
583
584impl Eq for HsClientDescEncKey {}
585
586impl Display for HsClientDescEncKey {
587 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
588 let x25519_pk = data_encoding::BASE32_NOPAD.encode(&self.0.to_bytes());
589 write!(f, "descriptor:x25519:{}", x25519_pk)
590 }
591}
592
593impl FromStr for HsClientDescEncKey {
594 type Err = HsClientDescEncKeyParseError;
595
596 fn from_str(key: &str) -> Result<Self, HsClientDescEncKeyParseError> {
597 let (auth_type, key_type, encoded_key) = key
598 .split(':')
599 .collect_tuple()
600 .ok_or(HsClientDescEncKeyParseError::InvalidFormat)?;
601
602 if auth_type != "descriptor" {
603 return Err(HsClientDescEncKeyParseError::InvalidAuthType(
604 auth_type.into(),
605 ));
606 }
607
608 if key_type != "x25519" {
609 return Err(HsClientDescEncKeyParseError::InvalidKeyType(
610 key_type.into(),
611 ));
612 }
613
614 let encoded_key = encoded_key.to_uppercase();
620 let x25519_pk = data_encoding::BASE32_NOPAD.decode(encoded_key.as_bytes())?;
621 let x25519_pk: [u8; 32] = x25519_pk
622 .try_into()
623 .map_err(|_| HsClientDescEncKeyParseError::InvalidKeyMaterial)?;
624
625 Ok(Self(curve25519::PublicKey::from(x25519_pk)))
626 }
627}
628
629#[derive(Error, Clone, Debug, PartialEq)]
631#[non_exhaustive]
632pub enum HsClientDescEncKeyParseError {
633 #[error("Invalid auth type {0}")]
635 InvalidAuthType(String),
636
637 #[error("Invalid key type {0}")]
639 InvalidKeyType(String),
640
641 #[error("Invalid key format")]
643 InvalidFormat,
644
645 #[error("Invalid key material")]
647 InvalidKeyMaterial,
648
649 #[error("Invalid base32 in client key")]
651 InvalidBase32(#[from] data_encoding::DecodeError),
652}
653
654define_pk_keypair! {
655#[derive(Deftly)]
660#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
661pub struct HsSvcDescEncKey(curve25519::PublicKey) /
662
663#[derive(Deftly)]
664#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
665HsSvcDescEncSecretKey(curve25519::StaticSecret);
666}
667
668impl From<&HsClientDescEncSecretKey> for HsClientDescEncKey {
669 fn from(ks: &HsClientDescEncSecretKey) -> Self {
670 Self(curve25519::PublicKey::from(&ks.0))
671 }
672}
673
674impl From<&HsClientDescEncKeypair> for HsClientDescEncKey {
675 fn from(ks: &HsClientDescEncKeypair) -> Self {
676 Self(**ks.public())
677 }
678}
679
680#[allow(clippy::exhaustive_structs)]
683#[derive(Debug, Deftly)]
684#[derive_deftly(ConstantTimeEq, PartialEqFromCtEq)]
685pub struct HsSvcDescEncKeypair {
686 pub public: HsSvcDescEncKey,
688 pub secret: HsSvcDescEncSecretKey,
690}
691
692impl ToEncodableKey for HsClientDescEncKeypair {
699 type Key = curve25519::StaticKeypair;
700 type KeyPair = HsClientDescEncKeypair;
701
702 fn to_encodable_key(self) -> Self::Key {
703 self.into()
704 }
705
706 fn from_encodable_key(key: Self::Key) -> Self {
707 HsClientDescEncKeypair::new(key.public.into(), key.secret.into())
708 }
709}
710
711impl ToEncodableKey for HsBlindIdKeypair {
712 type Key = ed25519::ExpandedKeypair;
713 type KeyPair = HsBlindIdKeypair;
714
715 fn to_encodable_key(self) -> Self::Key {
716 self.into()
717 }
718
719 fn from_encodable_key(key: Self::Key) -> Self {
720 HsBlindIdKeypair::from(key)
721 }
722}
723
724impl ToEncodableKey for HsBlindIdKey {
725 type Key = ed25519::PublicKey;
726 type KeyPair = HsBlindIdKeypair;
727
728 fn to_encodable_key(self) -> Self::Key {
729 self.into()
730 }
731
732 fn from_encodable_key(key: Self::Key) -> Self {
733 HsBlindIdKey::from(key)
734 }
735}
736
737impl ToEncodableKey for HsIdKeypair {
738 type Key = ed25519::ExpandedKeypair;
739 type KeyPair = HsIdKeypair;
740
741 fn to_encodable_key(self) -> Self::Key {
742 self.into()
743 }
744
745 fn from_encodable_key(key: Self::Key) -> Self {
746 HsIdKeypair::from(key)
747 }
748}
749
750impl ToEncodableKey for HsIdKey {
751 type Key = ed25519::PublicKey;
752 type KeyPair = HsIdKeypair;
753
754 fn to_encodable_key(self) -> Self::Key {
755 self.into()
756 }
757
758 fn from_encodable_key(key: Self::Key) -> Self {
759 HsIdKey::from(key)
760 }
761}
762
763impl ToEncodableKey for HsDescSigningKeypair {
764 type Key = ed25519::Keypair;
765 type KeyPair = HsDescSigningKeypair;
766
767 fn to_encodable_key(self) -> Self::Key {
768 self.into()
769 }
770
771 fn from_encodable_key(key: Self::Key) -> Self {
772 HsDescSigningKeypair::from(key)
773 }
774}
775
776impl ToEncodableKey for HsIntroPtSessionIdKeypair {
777 type Key = ed25519::Keypair;
778 type KeyPair = HsIntroPtSessionIdKeypair;
779
780 fn to_encodable_key(self) -> Self::Key {
781 self.into()
782 }
783
784 fn from_encodable_key(key: Self::Key) -> Self {
785 key.into()
786 }
787}
788
789impl ToEncodableKey for HsSvcNtorKeypair {
790 type Key = curve25519::StaticKeypair;
791 type KeyPair = HsSvcNtorKeypair;
792
793 fn to_encodable_key(self) -> Self::Key {
794 self.into()
795 }
796
797 fn from_encodable_key(key: Self::Key) -> Self {
798 key.into()
799 }
800}
801
802#[cfg(test)]
803mod test {
804 #![allow(clippy::bool_assert_comparison)]
806 #![allow(clippy::clone_on_copy)]
807 #![allow(clippy::dbg_macro)]
808 #![allow(clippy::mixed_attributes_style)]
809 #![allow(clippy::print_stderr)]
810 #![allow(clippy::print_stdout)]
811 #![allow(clippy::single_char_pattern)]
812 #![allow(clippy::unwrap_used)]
813 #![allow(clippy::unchecked_time_subtraction)]
814 #![allow(clippy::useless_vec)]
815 #![allow(clippy::needless_pass_by_value)]
816 #![allow(clippy::string_slice)] use hex_literal::hex;
820 use itertools::izip;
821 use tor_basic_utils::test_rng::testing_rng;
822 use web_time_compat::{Duration, SystemTime, SystemTimeExt};
823
824 use super::*;
825
826 #[test]
827 fn hsid_strings() {
828 use HsIdParseError as PE;
829
830 let hex = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a";
832 let b32 = "25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid";
833
834 let hsid: [u8; 32] = hex::decode(hex).unwrap().try_into().unwrap();
835 let hsid = HsId::from(hsid);
836 let onion = format!("{}.onion", b32);
837
838 assert_eq!(onion.parse::<HsId>().unwrap(), hsid);
839 assert_eq!(hsid.display_unredacted().to_string(), onion);
840
841 let weird_case: String = izip!(onion.chars(), [false, true].iter().cloned().cycle(),)
842 .map(|(c, swap)| if swap { c.to_ascii_uppercase() } else { c })
843 .collect();
844 dbg!(&weird_case);
845 assert_eq!(weird_case.parse::<HsId>().unwrap(), hsid);
846
847 macro_rules! chk_err { { $s:expr, $($pat:tt)* } => {
848 let e = $s.parse::<HsId>();
849 assert!(matches!(e, Err($($pat)*)), "{:?}", &e);
850 } }
851 let edited = |i, c| {
852 let mut s = b32.to_owned().into_bytes();
853 s[i] = c;
854 format!("{}.onion", String::from_utf8(s).unwrap())
855 };
856
857 chk_err!("wrong", PE::NotOnionDomain);
858 chk_err!("@.onion", PE::InvalidBase32(..));
859 chk_err!("aaaaaaaa.onion", PE::InvalidData(..));
860 chk_err!(edited(55, b'E'), PE::UnsupportedVersion(4));
861 chk_err!(edited(53, b'X'), PE::WrongChecksum);
862 chk_err!(&format!("www.{}", &onion), PE::HsIdContainsSubdomain);
863
864 safelog::with_safe_logging_suppressed(|| {
865 assert_eq!(format!("{:?}", &hsid), format!("HsId({})", onion));
866 });
867
868 assert_eq!(format!("{}", hsid.display_redacted()), "[…]sid.onion");
869 }
870
871 #[test]
872 fn key_blinding_blackbox() {
873 let mut rng = testing_rng();
874 let offset = Duration::new(12 * 60 * 60, 0);
875 let when = TimePeriod::new(Duration::from_secs(3600), SystemTime::get(), offset).unwrap();
876 let keypair = ed25519::Keypair::generate(&mut rng);
877 let id_pub = HsIdKey::from(keypair.verifying_key());
878 let id_keypair = HsIdKeypair::from(ed25519::ExpandedKeypair::from(&keypair));
879
880 let (blinded_pub, subcred1) = id_pub.compute_blinded_key(when).unwrap();
881 let (blinded_pub2, blinded_keypair, subcred2) =
882 id_keypair.compute_blinded_key(when).unwrap();
883
884 assert_eq!(subcred1.as_ref(), subcred2.as_ref());
885 assert_eq!(blinded_pub.0.to_bytes(), blinded_pub2.0.to_bytes());
886 assert_eq!(blinded_pub.id(), blinded_pub2.id());
887
888 let message = b"Here is a terribly important string to authenticate.";
889 let other_message = b"Hey, that is not what I signed!";
890 let sign = blinded_keypair.sign(message);
891
892 assert!(blinded_pub.as_ref().verify(message, &sign).is_ok());
893 assert!(blinded_pub.as_ref().verify(other_message, &sign).is_err());
894 }
895
896 #[test]
897 fn key_blinding_testvec() {
898 let id = HsId::from(hex!(
900 "833990B085C1A688C1D4C8B1F6B56AFAF5A2ECA674449E1D704F83765CCB7BC6"
901 ));
902 let id_pubkey = HsIdKey::try_from(id).unwrap();
903 let id_seckey = HsIdKeypair::from(
904 ed25519::ExpandedKeypair::from_secret_key_bytes(hex!(
905 "D8C7FF0E31295B66540D789AF3E3DF992038A9592EEA01D8B7CBA06D6E66D159
906 4D6167696320576F7264733A20737065697373636F62616C742062697669756D"
907 ))
908 .unwrap(),
909 );
910 let time_period = TimePeriod::new(
911 humantime::parse_duration("1 day").unwrap(),
912 humantime::parse_rfc3339("1973-05-20T01:50:33Z").unwrap(),
913 humantime::parse_duration("12 hours").unwrap(),
914 )
915 .unwrap();
916 assert_eq!(time_period.interval_num, 1234);
917
918 let h = id_pubkey.blinding_factor(b"", time_period);
919 assert_eq!(
920 h,
921 hex!("379E50DB31FEE6775ABD0AF6FB7C371E060308F4F847DB09FE4CFE13AF602287")
922 );
923
924 let (blinded_pub1, subcred1) = id_pubkey.compute_blinded_key(time_period).unwrap();
925 assert_eq!(
926 blinded_pub1.0.to_bytes(),
927 hex!("3A50BF210E8F9EE955AE0014F7A6917FB65EBF098A86305ABB508D1A7291B6D5")
928 );
929 assert_eq!(
930 subcred1.as_ref(),
931 &hex!("635D55907816E8D76398A675A50B1C2F3E36B42A5CA77BA3A0441285161AE07D")
932 );
933
934 let (blinded_pub2, blinded_sec, subcred2) =
935 id_seckey.compute_blinded_key(time_period).unwrap();
936 assert_eq!(blinded_pub1.0.to_bytes(), blinded_pub2.0.to_bytes());
937 assert_eq!(subcred1.as_ref(), subcred2.as_ref());
938 assert_eq!(
939 blinded_sec.0.to_secret_key_bytes(),
940 hex!(
941 "A958DC83AC885F6814C67035DE817A2C604D5D2F715282079448F789B656350B
942 4540FE1F80AA3F7E91306B7BF7A8E367293352B14A29FDCC8C19F3558075524B"
943 )
944 );
945 }
946
947 #[test]
948 fn parse_client_desc_enc_key() {
949 use HsClientDescEncKeyParseError::*;
950
951 const VALID_KEY_BASE32: &str = "dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja";
953
954 const WRONG_FORMAT: &[&str] = &["a:b:c:d:e", "descriptor:", "descriptor:x25519", ""];
956
957 for key in WRONG_FORMAT {
958 let err = HsClientDescEncKey::from_str(key).unwrap_err();
959
960 assert_eq!(err, InvalidFormat);
961 }
962
963 let err =
964 HsClientDescEncKey::from_str(&format!("foo:descriptor:x25519:{VALID_KEY_BASE32}"))
965 .unwrap_err();
966
967 assert_eq!(err, InvalidFormat);
968
969 let err = HsClientDescEncKey::from_str("bar:x25519:aa==").unwrap_err();
971 assert_eq!(err, InvalidAuthType("bar".into()));
972
973 let err = HsClientDescEncKey::from_str("descriptor:not-x25519:aa==").unwrap_err();
975 assert_eq!(err, InvalidKeyType("not-x25519".into()));
976
977 let err = HsClientDescEncKey::from_str("descriptor:x25519:aa==").unwrap_err();
979 assert!(matches!(err, InvalidBase32(_)));
980
981 let _key =
983 HsClientDescEncKey::from_str(&format!("descriptor:x25519:{VALID_KEY_BASE32}")).unwrap();
984
985 let desc_enc_key = HsClientDescEncKey::from(curve25519::PublicKey::from(
987 &curve25519::StaticSecret::random_from_rng(testing_rng()),
988 ));
989
990 assert_eq!(
991 desc_enc_key,
992 HsClientDescEncKey::from_str(&desc_enc_key.to_string()).unwrap()
993 );
994 }
995}