tor_interface/
tor_crypto.rs

1// standard
2use std::convert::TryInto;
3use std::str;
4
5// extern crates
6use 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/// Represents various errors that can occur in the tor_crypto module.
19#[derive(thiserror::Error, Debug)]
20pub enum Error {
21    /// A error encountered converting a String to a tor_crypto type
22    #[error("{0}")]
23    ParseError(String),
24
25    /// An error encountered converting between tor_crypto types
26    #[error("{0}")]
27    ConversionError(String),
28
29    /// An error encountered converting from a raw byte representation
30    #[error("invalid key")]
31    KeyInvalid,
32}
33
34/// The number of bytes in an ed25519 secret key
35/// cbindgen:ignore
36pub const ED25519_PRIVATE_KEY_SIZE: usize = 64;
37/// The number of bytes in an ed25519 public key
38/// cbindgen:ignore
39pub const ED25519_PUBLIC_KEY_SIZE: usize = 32;
40/// The number of bytes in an ed25519 signature
41/// cbindgen:ignore
42pub const ED25519_SIGNATURE_SIZE: usize = 64;
43/// The number of bytes needed to store onion service id as an ASCII c-string (not including null-terminator)
44pub const V3_ONION_SERVICE_ID_STRING_LENGTH: usize = 56;
45/// The number of bytes needed to store onion service id as an ASCII c-string (including null-terminator)
46pub 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);
51/// The number of bytes needed to store base64 encoded ed25519 private key as an ASCII c-string (not including null-terminator)
52pub const ED25519_PRIVATE_KEYBLOB_BASE64_LENGTH: usize = 88;
53/// key klob header string
54const ED25519_PRIVATE_KEY_KEYBLOB_HEADER: &str = "ED25519-V3:";
55/// The number of bytes needed to store the keyblob header
56pub 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);
61/// The number of bytes needed to store ed25519 private keyblob as an ASCII c-string (not including a null terminator)
62pub 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);
67/// The number of bytes needed to store ed25519 private keyblob as an ASCII c-string (including a null terminator)
68pub 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);
73// number of bytes in an onion service id after base32 decode
74const V3_ONION_SERVICE_ID_RAW_SIZE: usize = 35;
75// byte index of the start of the public key checksum
76const V3_ONION_SERVICE_ID_CHECKSUM_OFFSET: usize = 32;
77// byte index of the v3 onion service version
78const V3_ONION_SERVICE_ID_VERSION_OFFSET: usize = 34;
79/// The number of bytes in a v3 service id's truncated checksum
80const TRUNCATED_CHECKSUM_SIZE: usize = 2;
81/// The number of bytes in an x25519 private key
82/// cbindgen:ignore
83pub const X25519_PRIVATE_KEY_SIZE: usize = 32;
84/// The number of bytes in an x25519 publickey
85/// cbindgen:ignore
86pub const X25519_PUBLIC_KEY_SIZE: usize = 32;
87/// The number of bytes needed to store base64 encoded x25519 private key as an ASCII c-string (not including null-terminator)
88pub const X25519_PRIVATE_KEY_BASE64_LENGTH: usize = 44;
89/// The number of bytes needed to store base64 encoded x25519 private key as an ASCII c-string (including a null terminator)
90pub 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);
95/// The number of bytes needed to store base32 encoded x25519 public key as an ASCII c-string (not including null-terminator)
96pub const X25519_PUBLIC_KEY_BASE32_LENGTH: usize = 52;
97/// The number of bytes needed to store base32 encoded x25519 public key as an ASCII c-string (including a null terminator)
98pub 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// Free functions
110
111// securely generate password using CautionsRng
112#[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
123// Struct deinitions
124
125/// An ed25519 private key.
126///
127/// This key type is used with [`crate::tor_provider::TorProvider`] trait for hosting onion-services and can be convertd to an [`Ed25519PublicKey`]. It can also be used to sign messages and create an [`Ed25519Signature`].
128pub struct Ed25519PrivateKey {
129    expanded_keypair: pk::ed25519::ExpandedKeypair,
130}
131
132/// An ed25519 public key.
133///
134/// This key type is derived from [`Ed25519PrivateKey`] and can be converted to a [`V3OnionServiceId`]. It can also be used to verify a [`Ed25519Signature`].
135#[derive(Clone)]
136pub struct Ed25519PublicKey {
137    public_key: pk::ed25519::PublicKey,
138}
139
140/// An ed25519 cryptographic signature
141#[derive(Clone)]
142pub struct Ed25519Signature {
143    signature: pk::ed25519::Signature,
144}
145
146/// An x25519 private key
147#[derive(Clone)]
148pub struct X25519PrivateKey {
149    secret_key: pk::curve25519::StaticSecret,
150}
151
152/// An x25519 public key
153#[derive(Clone, PartialEq, Eq)]
154pub struct X25519PublicKey {
155    public_key: pk::curve25519::PublicKey,
156}
157
158/// A v3 onion-service id
159#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
160pub struct V3OnionServiceId {
161    data: [u8; V3_ONION_SERVICE_ID_STRING_LENGTH],
162}
163
164/// An enum representing a single bit
165#[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
199// which validation method to use when constructing an ed25519 expanded key from
200// a byte array
201enum FromRawValidationMethod {
202    // expanded ed25519 keys coming from legacy c-tor daemon; the scalar portion
203    // is clamped, but not reduced
204    #[cfg(feature = "legacy-tor-provider")]
205    LegacyCTor,
206    // expanded ed25519 keys coming from ed25519-dalek crate; the scalar portion
207    // has been clamped AND reduced
208    Ed25519Dalek,
209}
210
211/// A wrapper around `tor_llcrypto::pk::ed25519::ExpandedKeypair`.
212impl Ed25519PrivateKey {
213    /// Securely generate a new `Ed25519PrivateKey`.
214    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        // see: https://gitlab.torproject.org/tpo/core/arti/-/issues/1343
228        match method {
229            #[cfg(feature = "legacy-tor-provider")]
230            FromRawValidationMethod::LegacyCTor => {
231                // Verify the scalar portion of the expanded key has been clamped
232                // see: https://gitlab.torproject.org/tpo/core/arti/-/issues/1021
233                if !(raw[0] == raw[0] & 248 && raw[31] == (raw[31] & 63) | 64) {
234                    return Err(Error::KeyInvalid);
235                }
236            }
237            FromRawValidationMethod::Ed25519Dalek => {
238                // Verify the scalar is non-zero and it has been reduced
239                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    /// Attempt to create an `Ed25519PrivateKey` from an array of bytes. Not all byte buffers of the required size can create a valid `Ed25519PrivateKey`. Only buffers derived from [`Ed25519PrivateKey::to_bytes()`] are required to convert correctly.
258    ///
259    /// To securely generate a valid `Ed25519PrivateKey`, use [`Ed25519PrivateKey::generate()`].
260    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    /// Create an `Ed25519PrivateKey` from a [`String`] in the legacy c-tor daemon key blob format used in the `ADD_ONION` control-port command. From the c-tor control [specification](https://spec.torproject.org/control-spec/commands.html#add_onion):
309    /// > For a "ED25519-V3" key is the Base64 encoding of the concatenation of the 32-byte ed25519 secret scalar in little-endian and the 32-byte ed25519 PRF secret.
310    ///
311    /// Only key blob strings created by the legacy c-tor daemon are required to convert correctly using this function. An `Ed25519PrivateKey` created using this method will seriaise to a different (but cryptographically equivalent) scalar-reduced key blob string using the [`Ed25519PrivateKey::to_key_blob()`] function.
312    #[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    /// Create an `Ed25519PrivateKey` from a [`String`] in the legacy c-tor daemon key blob format used in the `ADD_ONION` control-port command. From the c-tor control [specification](https://spec.torproject.org/control-spec/commands.html#add_onion):
318    /// > For a "ED25519-V3" key is the Base64 encoding of the concatenation of the 32-byte ed25519 secret scalar in little-endian and the 32-byte ed25519 PRF secret.
319    ///
320    /// Only key blob strings derived from [`Ed25519PrivateKey::to_key_blob()`] are required to convert correctly using this function.
321    pub fn from_key_blob(key_blob: &str) -> Result<Ed25519PrivateKey, Error> {
322        Self::from_key_blob_impl(key_blob, FromRawValidationMethod::Ed25519Dalek)
323    }
324
325    /// Construct an `Ed25519PrivateKEy` from an [`X25519PrivateKey`].
326    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    /// Write `Ed25519PrivateKey` to a c-tor key blob formatted [`String`].
355    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    /// Sign the provided message and return an [`Ed25519Signature`].
363    /// ## ⚠ Warning ⚠
364    ///Only ever sign messages the private key owner controls the contents of!
365    pub fn sign_message(&self, message: &[u8]) -> Ed25519Signature {
366        let signature = self.expanded_keypair.sign(message);
367        Ed25519Signature { signature }
368    }
369
370    /// Convert this private key to an array of bytes.
371    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
402/// A wrapper around `tor_llcrypto::pk::ed25519::PublicKey`
403impl Ed25519PublicKey {
404    /// Construct an `Ed25519PublicKey` from an array of bytes. Not all byte buffers of the required size can create a valid `Ed25519PublicKey`. Only buffers derived from [`Ed25519PublicKey::as_bytes()`] are required to convert correctly.
405    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    /// Construct an `Ed25519PublicKey` from a [`V3OnionServiceId`].
415    pub fn from_service_id(service_id: &V3OnionServiceId) -> Result<Ed25519PublicKey, Error> {
416        // decode base32 encoded service id
417        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    /// Construct an `Ed25519PublicKey` from an [`Ed25519PrivateKey`].
443    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    /// View this public key as an array of bytes
463    pub fn as_bytes(&self) -> &[u8; ED25519_PUBLIC_KEY_SIZE] {
464        self.public_key.as_bytes()
465    }
466}
467
468impl PartialEq for Ed25519PublicKey {
469    fn eq(&self, other: &Self) -> bool {
470        self.public_key.eq(&other.public_key)
471    }
472}
473
474impl std::fmt::Debug for Ed25519PublicKey {
475    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
476        self.public_key.fmt(f)
477    }
478}
479
480
481/// A wrapper around `tor_llcrypto::pk::ed25519::Signature`
482impl Ed25519Signature {
483    /// Construct an `Ed25519Signature` from an array of bytes.
484    pub fn from_raw(raw: &[u8; ED25519_SIGNATURE_SIZE]) -> Result<Ed25519Signature, Error> {
485        // todo: message cannot fail so should not return a Result<>
486        Ok(Ed25519Signature {
487            signature: pk::ed25519::Signature::from_bytes(raw),
488        })
489    }
490
491    /// Verify this `Ed25519Signature` for the given message and [`Ed25519PublicKey`].
492    pub fn verify(&self, message: &[u8], public_key: &Ed25519PublicKey) -> bool {
493        public_key
494            .public_key
495            .verify(message, &self.signature)
496            .is_ok()
497    }
498
499    /// Verify this `Ed25519Signature` for the given message, [`X25519PublicKey`], and [`SignBit`]. This signature must have been created by first converting an [`X25519PrivateKey`] to a [`Ed25519PrivateKey`] and [`SignBit`], and then signing the message using this [`Ed25519PrivateKey`]. This method verifies the signature using the [`Ed25519PublicKey`] derived from the provided  [`X25519PublicKey`] and [`SignBit`].
500    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    /// Convert this signature to an array of bytes
513    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
530/// A wrapper around `tor_llcrypto::pk::curve25519::StaticSecret`
531impl X25519PrivateKey {
532    /// Securely generate a new `X25519PrivateKey`
533    pub fn generate() -> X25519PrivateKey {
534        let csprng = &mut tor_llcrypto::rng::CautiousRng;
535        X25519PrivateKey {
536            secret_key: pk::curve25519::StaticSecret::random_from_rng(csprng),
537        }
538    }
539
540    /// Attempt to create an `X25519PrivateKey` from an array of bytes. Not all byte buffers of the required size can create a valid `X25519PrivateKey`. Only buffers derived from [`X25519PrivateKey::to_bytes()`] are required to convert correctly.
541    ///
542    /// To securely generate a valid `X25519PrivateKey`, use [`X25519PrivateKey::generate()`].
543    pub fn from_raw(raw: &[u8; X25519_PRIVATE_KEY_SIZE]) -> Result<X25519PrivateKey, Error> {
544        // see: https://docs.rs/x25519-dalek/2.0.0-pre.1/src/x25519_dalek/x25519.rs.html#197
545        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    /// Create an `X25519PrivateKey` from a [`String`] in the legacy c-tor daemon key blob format used in the `ONION_CLIENT_AUTH_ADD` control-port command. From the c-tor control [specification](https://spec.torproject.org/control-spec/commands.html#onion_client_auth_add):
555    /// > ```text
556    /// > PrivateKeyBlob = base64 encoding of x25519 key
557    /// > ```
558    ///
559    /// Only key blob strings derived from [`X25519PrivateKey::to_base64()`] are required to convert correctly.
560    pub fn from_base64(base64: &str) -> Result<X25519PrivateKey, Error> {
561        // todo: see if this should be from/to key blob like with ed25519 rather than base64
562        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    /// Sign the provided message and return an [`Ed25519Signature`] and [`SignBit`].
595    ///
596    /// This method first converts this `X25519PrivateKey` to an [`Ed25519PrivateKey`] and [`SignBit`]. Then, the message is signed using the derived [`Ed25519PrivateKey`]. To verify the signature, both the [`X25519PublicKey`] and this calculated [`SignBit`] are required.
597    ///
598    /// ## ⚠ Warning ⚠
599    ///Only ever sign messages the private key owner controls the contents of!
600    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    /// Write `X25519PrivateKey` to a base64 encocded [`String`].
606    pub fn to_base64(&self) -> String {
607        BASE64.encode(&self.secret_key.to_bytes())
608    }
609
610    /// Convert this private key to an array of bytes.
611    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
633/// A wrapper around `tor_llcrypto::pk::curve25519::PublicKey`
634impl X25519PublicKey {
635    /// Construct an `X25519PublicKey` from an [`X25519PrivateKey`].
636    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    /// Construct an `X25519PublicKey` from an array of bytes.
643    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    /// Create an `X25519PublicKey` from a [`String`] in the legacy c-tor daemon key base32 format used in the `ADD_ONION` control-port command. From the c-tor control [specification](https://spec.torproject.org/control-spec/commands.html#add_onion):
650    /// > ```text
651    /// > V3Key = The client's base32-encoded x25519 public key, using only the key
652    /// >         part of rend-spec-v3.txt section G.1.2 (v3 only).
653    /// > ```
654    ///
655    /// Only key base32 strings derived from [`X25519PublicKey::to_base32()`] are required to convert correctly.
656    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    /// Write `X25519PublicKey` to a base32 encocded [`String`].
690    pub fn to_base32(&self) -> String {
691        BASE32_NOPAD.encode(self.public_key.as_bytes())
692    }
693
694    /// View this public key as an array of bytes
695    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
712/// Strongly-typed representation of a v3 onion-service id
713impl V3OnionServiceId {
714    // see https://github.com/torproject/torspec/blob/main/rend-spec-v3.txt#L2143
715    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        // calculate checksum
721        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    /// Create a `V3OnionServiceId` from a [`String`] in the version 3 onion service digest format. From the tor address [specification](https://spec.torproject.org/address-spec.html#onion):
730    /// > ```text
731    /// > onion_address = base32(PUBKEY | CHECKSUM | VERSION)
732    /// > CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2]
733    /// >
734    /// > where:
735    /// > - PUBKEY is the 32 bytes ed25519 master pubkey of the onion service.
736    /// > - VERSION is a one byte version field (default value '\x03')
737    /// > - ".onion checksum" is a constant string
738    /// > - H is SHA3-256
739    /// > - CHECKSUM is truncated to two bytes before inserting it in onion_address
740    /// > ```
741    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    /// Create a `V3OnionServiceId` from an [`Ed25519PublicKey`].
754    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        // panics on wrong buffer size, but given our constant buffer sizes should be fine
765        ONION_BASE32.encode_mut(&raw_service_id, &mut service_id);
766
767        V3OnionServiceId { data: service_id }
768    }
769
770    /// Create a `V3OnionServiceId` from an [`Ed25519PrivateKey`].
771    pub fn from_private_key(private_key: &Ed25519PrivateKey) -> V3OnionServiceId {
772        Self::from_public_key(&Ed25519PublicKey::from_private_key(private_key))
773    }
774
775    /// Determine if the provided string is a valid representation of a `V3OnionServiceId`
776    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                // ensure right size
785                if decoded_byte_count != V3_ONION_SERVICE_ID_RAW_SIZE {
786                    return false;
787                }
788                // ensure correct version
789                if decoded_service_id[V3_ONION_SERVICE_ID_VERSION_OFFSET] != 0x03 {
790                    return false;
791                }
792                // copy public key into own buffer
793                let mut public_key = [0u8; ED25519_PUBLIC_KEY_SIZE];
794                public_key[..].copy_from_slice(&decoded_service_id[..ED25519_PUBLIC_KEY_SIZE]);
795                // ensure checksum is correct
796                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    /// View this service id as an array of bytes
810    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}