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(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/// Represents various errors that can occur in the tor_crypto module.
20#[derive(thiserror::Error, Debug)]
21pub enum Error {
22    /// A error encountered converting a String to a tor_crypto type
23    #[error("{0}")]
24    ParseError(String),
25
26    /// An error encountered converting between tor_crypto types
27    #[error("{0}")]
28    ConversionError(String),
29
30    /// An error encountered converting from a raw byte representation
31    #[error("invalid key")]
32    KeyInvalid,
33}
34
35/// The number of bytes in an ed25519 secret key
36/// cbindgen:ignore
37pub const ED25519_PRIVATE_KEY_SIZE: usize = 64;
38/// The number of bytes in an ed25519 public key
39/// cbindgen:ignore
40pub const ED25519_PUBLIC_KEY_SIZE: usize = 32;
41/// The number of bytes in an ed25519 signature
42/// cbindgen:ignore
43pub const ED25519_SIGNATURE_SIZE: usize = 64;
44/// The number of bytes needed to store onion service id as an ASCII c-string (not including null-terminator)
45pub const V3_ONION_SERVICE_ID_STRING_LENGTH: usize = 56;
46/// The number of bytes needed to store onion service id as an ASCII c-string (including null-terminator)
47pub 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);
52/// The number of bytes needed to store base64 encoded ed25519 private key as an ASCII c-string (not including null-terminator)
53pub const ED25519_PRIVATE_KEYBLOB_BASE64_LENGTH: usize = 88;
54/// key klob header string
55const ED25519_PRIVATE_KEY_KEYBLOB_HEADER: &str = "ED25519-V3:";
56/// The number of bytes needed to store the keyblob header
57pub 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);
62/// The number of bytes needed to store ed25519 private keyblob as an ASCII c-string (not including a null terminator)
63pub 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);
68/// The number of bytes needed to store ed25519 private keyblob as an ASCII c-string (including a null terminator)
69pub 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);
74// number of bytes in an onion service id after base32 decode
75const V3_ONION_SERVICE_ID_RAW_SIZE: usize = 35;
76// byte index of the start of the public key checksum
77const V3_ONION_SERVICE_ID_CHECKSUM_OFFSET: usize = 32;
78// byte index of the v3 onion service version
79const V3_ONION_SERVICE_ID_VERSION_OFFSET: usize = 34;
80/// The number of bytes in a v3 service id's truncated checksum
81const TRUNCATED_CHECKSUM_SIZE: usize = 2;
82/// The number of bytes in an x25519 private key
83/// cbindgen:ignore
84pub const X25519_PRIVATE_KEY_SIZE: usize = 32;
85/// The number of bytes in an x25519 publickey
86/// cbindgen:ignore
87pub const X25519_PUBLIC_KEY_SIZE: usize = 32;
88/// The number of bytes needed to store base64 encoded x25519 private key as an ASCII c-string (not including null-terminator)
89pub const X25519_PRIVATE_KEY_BASE64_LENGTH: usize = 44;
90/// The number of bytes needed to store base64 encoded x25519 private key as an ASCII c-string (including a null terminator)
91pub 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);
96/// The number of bytes needed to store base32 encoded x25519 public key as an ASCII c-string (not including null-terminator)
97pub const X25519_PUBLIC_KEY_BASE32_LENGTH: usize = 52;
98/// The number of bytes needed to store base32 encoded x25519 public key as an ASCII c-string (including a null terminator)
99pub 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// Free functions
111
112// securely generate password using OsRng
113#[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
124// Struct deinitions
125
126/// An ed25519 private key.
127///
128/// 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`].
129pub struct Ed25519PrivateKey {
130    expanded_keypair: pk::ed25519::ExpandedKeypair,
131}
132
133/// An ed25519 public key.
134///
135/// This key type is derived from [`Ed25519PrivateKey`] and can be converted to a [`V3OnionServiceId`]. It can also be used to verify a [`Ed25519Signature`].
136#[derive(Clone)]
137pub struct Ed25519PublicKey {
138    public_key: pk::ed25519::PublicKey,
139}
140
141/// An ed25519 cryptographic signature
142#[derive(Clone)]
143pub struct Ed25519Signature {
144    signature: pk::ed25519::Signature,
145}
146
147/// An x25519 private key
148#[derive(Clone)]
149pub struct X25519PrivateKey {
150    secret_key: pk::curve25519::StaticSecret,
151}
152
153/// An x25519 public key
154#[derive(Clone, PartialEq, Eq, Hash)]
155pub struct X25519PublicKey {
156    public_key: pk::curve25519::PublicKey,
157}
158
159/// A v3 onion-service id
160#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
161pub struct V3OnionServiceId {
162    data: [u8; V3_ONION_SERVICE_ID_STRING_LENGTH],
163}
164
165/// An enum representing a single bit
166#[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
200// which validation method to use when constructing an ed25519 expanded key from
201// a byte array
202enum FromRawValidationMethod {
203    // expanded ed25519 keys coming from legacy c-tor daemon; the scalar portion
204    // is clamped, but not reduced
205    #[cfg(feature = "legacy-tor-provider")]
206    LegacyCTor,
207    // expanded ed25519 keys coming from ed25519-dalek crate; the scalar portion
208    // has been clamped AND reduced
209    Ed25519Dalek,
210}
211
212/// A wrapper around `tor_llcrypto::pk::ed25519::ExpandedKeypair`.
213impl Ed25519PrivateKey {
214    /// Securely generate a new `Ed25519PrivateKey`.
215    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        // see: https://gitlab.torproject.org/tpo/core/arti/-/issues/1343
229        match method {
230            #[cfg(feature = "legacy-tor-provider")]
231            FromRawValidationMethod::LegacyCTor => {
232                // Verify the scalar portion of the expanded key has been clamped
233                // see: https://gitlab.torproject.org/tpo/core/arti/-/issues/1021
234                if !(raw[0] == raw[0] & 248 && raw[31] == (raw[31] & 63) | 64) {
235                    return Err(Error::KeyInvalid);
236                }
237            }
238            FromRawValidationMethod::Ed25519Dalek => {
239                // Verify the scalar is non-zero and it has been reduced
240                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    /// 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.
259    ///
260    /// To securely generate a valid `Ed25519PrivateKey`, use [`Ed25519PrivateKey::generate()`].
261    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    /// 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):
315    /// > 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.
316    ///
317    /// Only key blob strings derived from [`Ed25519PrivateKey::to_key_blob()`] are required to convert correctly.
318    pub fn from_key_blob(key_blob: &str) -> Result<Ed25519PrivateKey, Error> {
319        Self::from_key_blob_impl(key_blob, FromRawValidationMethod::Ed25519Dalek)
320    }
321
322    /// Construct an `Ed25519PrivateKEy` from an [`X25519PrivateKey`].
323    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    /// Write `Ed25519PrivateKey` to a c-tor key blob formatted [`String`].
352    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    /// Sign the provided message and return an [`Ed25519Signature`].
360    /// ## ⚠ Warning ⚠
361    ///Only ever sign messages the private key owner controls the contents of!
362    pub fn sign_message(&self, message: &[u8]) -> Ed25519Signature {
363        let signature = self.expanded_keypair.sign(message);
364        Ed25519Signature { signature }
365    }
366
367    /// Convert this private key to an array of bytes.
368    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
399/// A wrapper around `tor_llcrypto::pk::ed25519::PublicKey`
400impl Ed25519PublicKey {
401    /// 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.
402    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    /// Construct an `Ed25519PublicKey` from a [`V3OnionServiceId`].
412    pub fn from_service_id(service_id: &V3OnionServiceId) -> Result<Ed25519PublicKey, Error> {
413        // decode base32 encoded service id
414        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    /// Construct an `Ed25519PublicKey` from an [`Ed25519PrivateKey`].
440    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    /// View this public key as an array of bytes
460    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
478/// A wrapper around `tor_llcrypto::pk::ed25519::Signature`
479impl Ed25519Signature {
480    /// Construct an `Ed25519Signature` from an array of bytes.
481    pub fn from_raw(raw: &[u8; ED25519_SIGNATURE_SIZE]) -> Result<Ed25519Signature, Error> {
482        // todo: message cannot fail so should not return a Result<>
483        Ok(Ed25519Signature {
484            signature: pk::ed25519::Signature::from_bytes(raw),
485        })
486    }
487
488    /// Verify this `Ed25519Signature` for the given message and [`Ed25519PublicKey`].
489    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    /// 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 OsRng;
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}