hopr_crypto_types/
types.rs

1use std::{
2    cmp::Ordering,
3    fmt::{Debug, Display, Formatter},
4    hash,
5    hash::Hasher,
6    marker::PhantomData,
7    result,
8    str::FromStr,
9};
10
11use cipher::crypto_common::OutputSizeUser;
12use curve25519_dalek::{
13    edwards::{CompressedEdwardsY, EdwardsPoint},
14    montgomery::MontgomeryPoint,
15};
16use digest::Digest;
17use elliptic_curve::NonZeroScalar;
18use generic_array::GenericArray;
19use hopr_crypto_random::Randomizable;
20use hopr_primitive_types::{errors::GeneralError::ParseError, prelude::*};
21use k256::{
22    AffinePoint, Secp256k1,
23    elliptic_curve::{
24        self,
25        point::NonIdentity,
26        sec1::{FromEncodedPoint, ToEncodedPoint},
27    },
28};
29use libp2p_identity::PeerId;
30
31use crate::{
32    errors::{
33        CryptoError::{self, CalculationError, InvalidInputValue},
34        Result,
35    },
36    utils::random_group_element,
37};
38
39pub(crate) fn affine_point_from_bytes(bytes: &[u8]) -> Result<AffinePoint> {
40    let ep = k256::EncodedPoint::from_bytes(bytes).map_err(|_| InvalidInputValue("affine_point_from_bytes"))?;
41    AffinePoint::from_encoded_point(&ep)
42        .into_option()
43        .ok_or(InvalidInputValue("affine_point_from_bytes"))
44}
45
46pub(crate) fn affine_point_to_address(ap: &AffinePoint) -> Address {
47    let serialized = ap.to_encoded_point(false);
48    let hash = Hash::create(&[&serialized.as_ref()[1..]]);
49    Address::new(&hash.as_ref()[12..])
50}
51
52/// Contains the complete Proof-of-Relay challenge is a secp256k1 curve point.
53///
54/// This is the elliptic curve point corresponding to the `Ticket` challenge.
55#[derive(Clone, Copy)]
56pub struct Challenge(NonIdentity<AffinePoint>);
57
58impl Debug for Challenge {
59    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
60        write!(f, "{}", self.0.to_encoded_point(true))
61    }
62}
63
64impl PartialEq for Challenge {
65    fn eq(&self, other: &Self) -> bool {
66        self.0.eq(other.0.as_ref())
67    }
68}
69
70impl Eq for Challenge {}
71
72impl Challenge {
73    /// Converts the PoR challenge to an Ethereum challenge.
74    ///
75    /// This is a one-way (lossy) operation, since the corresponding curve point is hashed
76    /// with the hash value then truncated.
77    pub fn to_ethereum_challenge(&self) -> EthereumChallenge {
78        EthereumChallenge(affine_point_to_address(&self.0))
79    }
80}
81
82impl Challenge {
83    /// Gets the PoR challenge by adding the two EC points represented by the half-key challenges.
84    ///
85    /// Note that this is an expensive operation that involves point decompression of the
86    /// both [`HalfKeyChallenges`](HalfKeyChallenge).
87    pub fn from_hint_and_share(own_share: &HalfKeyChallenge, hint: &HalfKeyChallenge) -> Result<Self> {
88        #[cfg(not(feature = "rust-ecdsa"))]
89        {
90            let own_share = secp256k1::PublicKey::from_byte_array_compressed(own_share.0)
91                .map_err(|_| ParseError("invalid half-key challenge for own share".into()))?;
92
93            let hint = secp256k1::PublicKey::from_byte_array_compressed(hint.0)
94                .map_err(|_| ParseError("invalid half-key challenge for hint".into()))?;
95
96            let res = own_share.combine(&hint).map_err(|_| CalculationError)?;
97
98            affine_point_from_bytes(&res.serialize_uncompressed())
99                .and_then(|p| NonIdentity::new(p).into_option().ok_or(CryptoError::InvalidPublicKey))
100                .map(Self)
101        }
102
103        #[cfg(feature = "rust-ecdsa")]
104        {
105            let own_share: k256::ProjectivePoint = affine_point_from_bytes(own_share.as_ref())?.into();
106
107            let hint: k256::ProjectivePoint = affine_point_from_bytes(hint.as_ref())?.into();
108
109            NonIdentity::new((own_share + hint).to_affine())
110                .into_option()
111                .ok_or(CalculationError)
112                .map(Self)
113        }
114    }
115
116    /// Gets the PoR challenge by converting the given HalfKey into a secp256k1 point and
117    /// adding it with the given HalfKeyChallenge (which already represents a secp256k1 point).
118    ///
119    /// Note that this is an expensive operation that involves point decompression of the
120    /// both [`HalfKeyChallenge`] and scalar multiplication of the [`HalfKey`] with the basepoint.
121    pub fn from_own_share_and_half_key(own_share: &HalfKeyChallenge, half_key: &HalfKey) -> Result<Self> {
122        Self::from_hint_and_share(own_share, &half_key.to_challenge()?)
123    }
124}
125
126/// Represents a half-key used for the Proof-of-Relay.
127///
128/// Half-key is equivalent to a non-zero scalar in the field used by secp256k1, but the type
129/// itself does not validate nor enforce this fact.
130///
131/// The type is internally represented as a byte-array of the secp256k1 field element.
132#[derive(Debug, Copy, Clone, Eq, PartialEq)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
134pub struct HalfKey(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; Self::SIZE]);
135
136#[allow(deprecated)] // Until the dependency updates to newer versions of `generic-array`
137impl Default for HalfKey {
138    fn default() -> Self {
139        let mut ret = Self([0u8; Self::SIZE]);
140
141        ret.0.copy_from_slice(
142            NonZeroScalar::<Secp256k1>::from_uint(1u16.into())
143                .unwrap()
144                .to_bytes()
145                .as_slice(),
146        );
147        ret
148    }
149}
150
151impl HalfKey {
152    /// Converts the non-zero scalar represented by this half-key into the half-key challenge.
153    ///
154    /// Note that this is an expensive operation that involves scalar multiplication.
155    ///
156    /// Returns an error if the instance is a zero scalar.
157    pub fn to_challenge(&self) -> Result<HalfKeyChallenge> {
158        // This may return an error if the instance was deserialized (e.g., via serde) from a zero scalar
159        let pk = PublicKey::from_privkey(&self.0)?;
160        let compressed: &[u8] = pk.as_ref();
161        Ok(compressed.try_into()?)
162    }
163}
164
165impl Randomizable for HalfKey {
166    fn random() -> Self {
167        Self(random_group_element().0)
168    }
169}
170
171impl AsRef<[u8]> for HalfKey {
172    fn as_ref(&self) -> &[u8] {
173        &self.0
174    }
175}
176
177impl TryFrom<&[u8]> for HalfKey {
178    type Error = GeneralError;
179
180    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
181        Ok(Self(value.try_into().map_err(|_| ParseError("HalfKey".into()))?))
182    }
183}
184
185impl BytesRepresentable for HalfKey {
186    /// Size of the secp256k1 secret scalar representing the `HalfKey`.
187    const SIZE: usize = 32;
188}
189
190/// Represents a challenge for the half-key in Proof of Relay.
191///
192/// Half-key challenge is equivalent to a secp256k1 curve point.
193/// Therefore, `HalfKeyChallenge` can be [obtained](HalfKey::to_challenge) from a [`HalfKey`].
194///
195/// The value is internally stored as a compressed point encoded as a byte-array.
196#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
197#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
198pub struct HalfKeyChallenge(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; Self::SIZE]);
199
200impl Display for HalfKeyChallenge {
201    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
202        write!(f, "{}", self.to_hex())
203    }
204}
205
206impl Default for HalfKeyChallenge {
207    fn default() -> Self {
208        // Note that the default HalfKeyChallenge is the identity point on secp256k1, therefore,
209        // will fail all public key checks, which is intended.
210        let mut ret = Self([0u8; Self::SIZE]);
211        ret.0[Self::SIZE - 1] = 1;
212        ret
213    }
214}
215
216impl HalfKeyChallenge {
217    pub fn new(half_key_challenge: &[u8]) -> Self {
218        let mut ret = Self::default();
219        ret.0.copy_from_slice(half_key_challenge);
220        ret
221    }
222}
223
224impl AsRef<[u8]> for HalfKeyChallenge {
225    fn as_ref(&self) -> &[u8] {
226        &self.0
227    }
228}
229
230impl TryFrom<&[u8]> for HalfKeyChallenge {
231    type Error = GeneralError;
232
233    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
234        Ok(Self(
235            value.try_into().map_err(|_| ParseError("HalfKeyChallenge".into()))?,
236        ))
237    }
238}
239
240impl BytesRepresentable for HalfKeyChallenge {
241    /// Size of the compressed secp256k1 point representing the Half Key Challenge.
242    const SIZE: usize = PublicKey::SIZE_COMPRESSED;
243}
244
245impl FromStr for HalfKeyChallenge {
246    type Err = GeneralError;
247
248    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
249        Self::from_hex(s)
250    }
251}
252
253const HASH_BASE_SIZE: usize = 32;
254
255/// Represents a generic 256-bit hash value.
256#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
257pub struct HashBase<H>(
258    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; HASH_BASE_SIZE],
259    #[cfg_attr(feature = "serde", serde(skip))] PhantomData<H>,
260);
261
262impl<H> Clone for HashBase<H> {
263    fn clone(&self) -> Self {
264        *self
265    }
266}
267
268impl<H> Copy for HashBase<H> {}
269
270impl<H> PartialEq for HashBase<H> {
271    fn eq(&self, other: &Self) -> bool {
272        self.0 == other.0
273    }
274}
275
276impl<H> Eq for HashBase<H> {}
277
278impl<H> Default for HashBase<H> {
279    fn default() -> Self {
280        Self([0u8; HASH_BASE_SIZE], PhantomData)
281    }
282}
283
284impl<H> PartialOrd<Self> for HashBase<H> {
285    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
286        Some(self.cmp(other))
287    }
288}
289
290impl<H> Ord for HashBase<H> {
291    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
292        self.0.cmp(&other.0)
293    }
294}
295
296impl<H> std::hash::Hash for HashBase<H> {
297    fn hash<H2: Hasher>(&self, state: &mut H2) {
298        self.0.hash(state);
299    }
300}
301
302impl<H> Debug for HashBase<H> {
303    // Intentionally same as Display
304    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
305        write!(f, "{}", self.to_hex())
306    }
307}
308
309impl<H> Display for HashBase<H> {
310    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
311        write!(f, "{}", self.to_hex())
312    }
313}
314
315impl<H> FromStr for HashBase<H> {
316    type Err = GeneralError;
317
318    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
319        Self::from_hex(s)
320    }
321}
322
323impl<H> HashBase<H>
324where
325    H: OutputSizeUser<OutputSize = typenum::U32> + Digest,
326{
327    /// Convenience method that creates a new hash by hashing this.
328    pub fn hash(&self) -> Self {
329        Self::create(&[&self.0])
330    }
331
332    /// Takes all the byte slices and computes hash of their concatenated value.
333    pub fn create(inputs: &[&[u8]]) -> Self {
334        let mut hash = H::new();
335        inputs.iter().for_each(|v| hash.update(v));
336        Self(hash.finalize().into(), PhantomData)
337    }
338}
339
340impl<H> AsRef<[u8]> for HashBase<H> {
341    fn as_ref(&self) -> &[u8] {
342        &self.0
343    }
344}
345
346impl<H> TryFrom<&[u8]> for HashBase<H> {
347    type Error = GeneralError;
348
349    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
350        Ok(Self(
351            value.try_into().map_err(|_| ParseError("Hash".into()))?,
352            PhantomData,
353        ))
354    }
355}
356
357impl<H> BytesRepresentable for HashBase<H> {
358    /// The size of the digest is 32 bytes.
359    const SIZE: usize = HASH_BASE_SIZE;
360}
361
362impl<H> From<[u8; HASH_BASE_SIZE]> for HashBase<H> {
363    fn from(hash: [u8; HASH_BASE_SIZE]) -> Self {
364        Self(hash, PhantomData)
365    }
366}
367
368impl<H> From<HashBase<H>> for [u8; HASH_BASE_SIZE] {
369    fn from(value: HashBase<H>) -> Self {
370        value.0
371    }
372}
373
374impl<H> From<&HashBase<H>> for [u8; HASH_BASE_SIZE] {
375    fn from(value: &HashBase<H>) -> Self {
376        value.0
377    }
378}
379
380impl<H> From<HashBase<H>> for primitive_types::H256 {
381    fn from(value: HashBase<H>) -> Self {
382        value.0.into()
383    }
384}
385
386impl<H> From<primitive_types::H256> for HashBase<H> {
387    fn from(value: primitive_types::H256) -> Self {
388        Self(value.0, PhantomData)
389    }
390}
391
392/// Represents an Ethereum 256-bit hash value.
393///
394/// This implementation instantiates the hash via Keccak256 digest.
395pub type Hash = HashBase<sha3::Keccak256>;
396
397/// Represents an alternative 256-bit hash value computed via a faster hashing algorithm.
398///
399/// This implementation instantiates the hash via Blake3 digest, which is usually 8-9x faster
400/// than Keccak256.
401pub type HashFast = HashBase<blake3::Hasher>;
402
403/// Represents an Ed25519 public key.
404#[derive(Clone, Copy, Eq)]
405#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
406pub struct OffchainPublicKey {
407    compressed: CompressedEdwardsY,
408    pub(crate) edwards: EdwardsPoint,
409}
410
411impl std::fmt::Debug for OffchainPublicKey {
412    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
413        // Intentionally same as display
414        write!(f, "{}", self.to_hex())
415    }
416}
417
418impl std::hash::Hash for OffchainPublicKey {
419    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
420        self.compressed.hash(state);
421    }
422}
423
424impl PartialEq for OffchainPublicKey {
425    fn eq(&self, other: &Self) -> bool {
426        self.compressed == other.compressed
427    }
428}
429
430impl AsRef<[u8]> for OffchainPublicKey {
431    fn as_ref(&self) -> &[u8] {
432        &self.compressed.0
433    }
434}
435
436impl TryFrom<&[u8]> for OffchainPublicKey {
437    type Error = GeneralError;
438
439    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
440        let compressed = CompressedEdwardsY::from_slice(value).map_err(|_| ParseError("OffchainPublicKey".into()))?;
441        let edwards = compressed
442            .decompress()
443            .ok_or(ParseError("OffchainPublicKey.decompress".into()))?;
444        Ok(Self { compressed, edwards })
445    }
446}
447
448impl BytesRepresentable for OffchainPublicKey {
449    /// Size of the public key (compressed Edwards Y coordinate)
450    const SIZE: usize = 32;
451}
452
453impl TryFrom<[u8; OffchainPublicKey::SIZE]> for OffchainPublicKey {
454    type Error = GeneralError;
455
456    fn try_from(value: [u8; OffchainPublicKey::SIZE]) -> std::result::Result<Self, Self::Error> {
457        let v: &[u8] = &value;
458        v.try_into()
459    }
460}
461
462impl From<OffchainPublicKey> for [u8; OffchainPublicKey::SIZE] {
463    fn from(value: OffchainPublicKey) -> Self {
464        value.compressed.0
465    }
466}
467
468impl From<OffchainPublicKey> for PeerId {
469    fn from(value: OffchainPublicKey) -> Self {
470        let k = libp2p_identity::ed25519::PublicKey::try_from_bytes(value.compressed.as_bytes())
471            .expect("offchain public key is always a valid ed25519 public key");
472        PeerId::from_public_key(&k.into())
473    }
474}
475
476impl From<&OffchainPublicKey> for PeerId {
477    fn from(value: &OffchainPublicKey) -> Self {
478        (*value).into()
479    }
480}
481
482impl Display for OffchainPublicKey {
483    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
484        write!(f, "{}", self.to_hex())
485    }
486}
487
488impl FromStr for OffchainPublicKey {
489    type Err = GeneralError;
490
491    fn from_str(s: &str) -> result::Result<Self, Self::Err> {
492        Self::from_hex(s)
493    }
494}
495
496impl OffchainPublicKey {
497    /// Tries to create the public key from a Ed25519 private key.
498    /// The length must be exactly `ed25519_dalek::SECRET_KEY_LENGTH`.
499    pub fn from_privkey(private_key: &[u8]) -> Result<Self> {
500        let mut pk: [u8; ed25519_dalek::SECRET_KEY_LENGTH] =
501            private_key.try_into().map_err(|_| InvalidInputValue("private_key"))?;
502        let sk = libp2p_identity::ed25519::SecretKey::try_from_bytes(&mut pk)
503            .map_err(|_| InvalidInputValue("private_key"))?;
504        let kp: libp2p_identity::ed25519::Keypair = sk.into();
505        Ok(Self::try_from(kp.public().to_bytes())?)
506    }
507
508    /// Outputs the public key as PeerId represented as Base58 string.
509    pub fn to_peerid_str(&self) -> String {
510        PeerId::from(self).to_base58()
511    }
512
513    /// Tries to convert an Ed25519 `PeerId` to `OffchainPublicKey`.
514    ///
515    /// This is a CPU-intensive operation, as it performs Ed25519 point decompression
516    /// and mapping to the Curve255919 point representation.
517    pub fn from_peerid(peerid: &PeerId) -> std::result::Result<Self, GeneralError> {
518        let mh = peerid.as_ref();
519        if mh.code() == 0 {
520            libp2p_identity::PublicKey::try_decode_protobuf(mh.digest())
521                .map_err(|_| ParseError("invalid ed25519 peer id".into()))
522                .and_then(|pk| {
523                    pk.try_into_ed25519()
524                        .map(|p| p.to_bytes())
525                        .map_err(|_| ParseError("invalid ed25519 peer id".into()))
526                })
527                .and_then(Self::try_from)
528        } else {
529            Err(ParseError("invalid ed25519 peer id".into()))
530        }
531    }
532}
533
534impl From<&OffchainPublicKey> for EdwardsPoint {
535    fn from(value: &OffchainPublicKey) -> Self {
536        value.edwards
537    }
538}
539
540impl<'a> From<&'a OffchainPublicKey> for &'a GenericArray<u8, typenum::U32> {
541    fn from(value: &'a OffchainPublicKey) -> &'a GenericArray<u8, typenum::U32> {
542        GenericArray::from_slice(&value.compressed.0)
543    }
544}
545
546impl From<&OffchainPublicKey> for MontgomeryPoint {
547    fn from(value: &OffchainPublicKey) -> Self {
548        // The Curve25519 computations are mostly not used, so we can do the conversion
549        // here without caching.
550        value.edwards.to_montgomery()
551    }
552}
553
554/// Length of a packet tag
555pub const PACKET_TAG_LENGTH: usize = 16;
556
557/// Represents a fixed size packet verification tag
558pub type PacketTag = [u8; PACKET_TAG_LENGTH];
559
560/// Represents a secp256k1 public key.
561///
562/// The key is internally represented using an `AffinePoint` and the compressed encoding of it.
563///
564/// The `AsRef` implementation will always return the compressed representation.
565/// However, the `TryFrom` byte slice accepts any representation.
566#[derive(Copy, Clone)]
567pub struct PublicKey(NonIdentity<AffinePoint>, [u8; Self::SIZE_COMPRESSED], Address);
568
569impl PublicKey {
570    /// Size of the compressed public key in bytes
571    pub const SIZE_COMPRESSED: usize = 33;
572    /// Size of the uncompressed public key in bytes
573    pub const SIZE_UNCOMPRESSED: usize = 65;
574    pub const SIZE_UNCOMPRESSED_PLAIN: usize = 64;
575
576    /// Computes the public key from the given `private_key`.
577    ///
578    /// The private key must be a big-endian encoding of a non-zero scalar in the field
579    /// of the `secp256k1` curve.
580    pub fn from_privkey(private_key: &[u8]) -> Result<PublicKey> {
581        #[cfg(feature = "rust-ecdsa")]
582        {
583            // This verifies that it is indeed a non-zero scalar, and thus represents a valid public key
584            let secret_scalar = NonZeroScalar::<Secp256k1>::try_from(private_key)
585                .map_err(|_| GeneralError::ParseError("PublicKey".into()))?;
586
587            Ok(
588                elliptic_curve::PublicKey::<Secp256k1>::from_secret_scalar(&secret_scalar)
589                    .to_nonidentity()
590                    .into(),
591            )
592        }
593
594        #[cfg(not(feature = "rust-ecdsa"))]
595        {
596            let sk = secp256k1::SecretKey::from_byte_array(
597                private_key
598                    .try_into()
599                    .map_err(|_| GeneralError::ParseError("private_key.len".into()))?,
600            )
601            .map_err(|_| GeneralError::ParseError("private_key".into()))?;
602
603            let pk = secp256k1::PublicKey::from_secret_key_global(&sk);
604            affine_point_from_bytes(&pk.serialize_uncompressed())
605                .and_then(|p| NonIdentity::new(p).into_option().ok_or(CryptoError::InvalidPublicKey))
606                .map(Self::from)
607        }
608    }
609
610    /// Converts the public key to an Ethereum address
611    pub fn to_address(&self) -> Address {
612        self.2
613    }
614
615    /// Serializes the public key to a binary uncompressed form.
616    pub fn to_uncompressed_bytes(&self) -> Box<[u8]> {
617        self.0.to_encoded_point(false).to_bytes()
618    }
619
620    /// Serializes the public key to a binary uncompressed form and converts it to hexadecimal string representation.
621    pub fn to_uncompressed_hex(&self) -> String {
622        format!("0x{}", hex::encode(self.to_uncompressed_bytes()))
623    }
624}
625
626impl PartialEq for PublicKey {
627    fn eq(&self, other: &Self) -> bool {
628        self.1.eq(&other.1)
629    }
630}
631
632impl Eq for PublicKey {}
633
634impl hash::Hash for PublicKey {
635    fn hash<H: Hasher>(&self, state: &mut H) {
636        self.1.hash(state);
637    }
638}
639
640impl Randomizable for PublicKey {
641    /// Generates a new random public key.
642    /// Because the corresponding private key is discarded, this might be useful only for testing purposes.
643    fn random() -> Self {
644        let (_, cp) = random_group_element();
645        cp.into()
646    }
647}
648
649impl Debug for PublicKey {
650    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
651        write!(f, "{}", self.to_hex())
652    }
653}
654
655impl Display for PublicKey {
656    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
657        write!(f, "{}", self.to_hex())
658    }
659}
660
661impl TryFrom<&[u8]> for PublicKey {
662    type Error = GeneralError;
663
664    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
665        match value.len() {
666            Self::SIZE_UNCOMPRESSED => {
667                // already has 0x04 prefix
668                let key = elliptic_curve::PublicKey::<Secp256k1>::from_sec1_bytes(value)
669                    .map_err(|_| GeneralError::ParseError("invalid secp256k1 point".into()))?;
670
671                Ok(key.to_nonidentity().into())
672            }
673            Self::SIZE_UNCOMPRESSED_PLAIN => {
674                // add 0x04 prefix
675                let key = elliptic_curve::PublicKey::<Secp256k1>::from_sec1_bytes(&[&[4u8], value].concat())
676                    .map_err(|_| GeneralError::ParseError("invalid secp256k1 point".into()))?;
677
678                Ok(key.to_nonidentity().into())
679            }
680            Self::SIZE_COMPRESSED => {
681                // has either 0x02 or 0x03 prefix
682                let key = elliptic_curve::PublicKey::<Secp256k1>::from_sec1_bytes(value)
683                    .map_err(|_| GeneralError::ParseError("invalid secp256k1 point".into()))?;
684
685                Ok(key.to_nonidentity().into())
686            }
687            _ => Err(GeneralError::ParseError("invalid secp256k1 point".into())),
688        }
689    }
690}
691
692impl AsRef<Address> for PublicKey {
693    fn as_ref(&self) -> &Address {
694        &self.2
695    }
696}
697
698impl AsRef<NonIdentity<AffinePoint>> for PublicKey {
699    fn as_ref(&self) -> &NonIdentity<AffinePoint> {
700        &self.0
701    }
702}
703
704impl AsRef<[u8]> for PublicKey {
705    fn as_ref(&self) -> &[u8] {
706        &self.1
707    }
708}
709
710impl BytesRepresentable for PublicKey {
711    const SIZE: usize = PublicKey::SIZE_COMPRESSED;
712}
713
714impl From<NonIdentity<AffinePoint>> for PublicKey {
715    fn from(value: NonIdentity<AffinePoint>) -> Self {
716        let mut compressed = [0u8; PublicKey::SIZE_COMPRESSED];
717        compressed.copy_from_slice(value.to_encoded_point(true).as_bytes());
718        Self(value, compressed, affine_point_to_address(&value))
719    }
720}
721
722impl From<PublicKey> for NonIdentity<AffinePoint> {
723    fn from(value: PublicKey) -> Self {
724        value.0
725    }
726}
727
728impl TryFrom<AffinePoint> for PublicKey {
729    type Error = CryptoError;
730
731    fn try_from(value: AffinePoint) -> std::result::Result<Self, Self::Error> {
732        Ok(NonIdentity::new(value)
733            .into_option()
734            .ok_or(CryptoError::InvalidPublicKey)?
735            .into())
736    }
737}
738
739// TODO: make this `for &k256::ProjectivePoint`
740impl From<&PublicKey> for k256::ProjectivePoint {
741    fn from(value: &PublicKey) -> Self {
742        (*value.0.as_ref()).into()
743    }
744}
745
746impl<'a> From<&'a PublicKey> for &'a GenericArray<u8, typenum::U33> {
747    fn from(value: &'a PublicKey) -> &'a GenericArray<u8, typenum::U33> {
748        GenericArray::from_slice(&value.1)
749    }
750}
751
752/// Contains a response upon ticket acknowledgement
753/// It is equivalent to a non-zero secret scalar on secp256k1 (EC private key).
754#[derive(Clone, Copy, Debug, PartialEq, Eq)]
755#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
756pub struct Response(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; Self::SIZE]);
757
758impl Default for Response {
759    fn default() -> Self {
760        Self(HalfKey::default().0)
761    }
762}
763
764impl Display for Response {
765    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
766        write!(f, "{}", self.to_hex())
767    }
768}
769
770#[allow(deprecated)] // Until the dependency updates to newer versions of `generic-array`
771impl Response {
772    /// Converts this response to the PoR challenge by turning the non-zero scalar
773    /// represented by this response into a secp256k1 curve point (public key).
774    ///
775    /// Note that this is an expensive operation involving scalar multiplication.
776    ///
777    /// Error is returned when this `Response` contains an invalid value.
778    pub fn to_challenge(&self) -> Result<Challenge> {
779        // This may return an error if the instance was deserialized (e.g., via serde) from a zero scalar
780        PublicKey::from_privkey(&self.0).map(|pk| Challenge(pk.into()))
781    }
782
783    /// Derives the response from two half-keys.
784    ///
785    /// This is done by adding together the two non-zero scalars that the given half-keys represent.
786    /// Returns an error if any of the given scalars is zero.
787    pub fn from_half_keys(first: &HalfKey, second: &HalfKey) -> Result<Self> {
788        let first = NonZeroScalar::<Secp256k1>::try_from(first.as_ref()).map_err(|_| InvalidInputValue("first"))?;
789        let second = NonZeroScalar::<Secp256k1>::try_from(second.as_ref()).map_err(|_| InvalidInputValue("second"))?;
790
791        // This addition is modulo order the order of the secp256k1 prime field
792        let res = first.as_ref() + second.as_ref();
793        if res.is_zero().into() {
794            return Err(InvalidInputValue("invalid half-key"));
795        }
796
797        let mut ret = [0u8; Self::SIZE];
798        ret.copy_from_slice(res.to_bytes().as_slice());
799        Ok(Self(ret))
800    }
801}
802
803impl AsRef<[u8]> for Response {
804    fn as_ref(&self) -> &[u8] {
805        &self.0
806    }
807}
808
809impl TryFrom<&[u8]> for Response {
810    type Error = GeneralError;
811
812    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
813        Ok(Self(value.try_into().map_err(|_| ParseError("Response".into()))?))
814    }
815}
816
817impl BytesRepresentable for Response {
818    /// Size of the PoR challenge response.
819    const SIZE: usize = 32;
820}
821
822impl From<[u8; Self::SIZE]> for Response {
823    fn from(value: [u8; Self::SIZE]) -> Self {
824        Self(value)
825    }
826}
827
828/// Pseudonym used to identify the creator of a `SURB`.
829/// This allows indexing `SURB` and `LocalSURBEntry` at both parties.
830///
831/// To maintain anonymity, this must be something else than the sender's
832/// public key or public key identifier.
833pub trait Pseudonym: BytesRepresentable + hash::Hash + Eq + Display + Randomizable {}
834
835/// Represents a simple UUID-like pseudonym consisting of 10 bytes.
836#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
837#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
838pub struct SimplePseudonym(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] pub [u8; Self::SIZE]);
839
840impl Display for SimplePseudonym {
841    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
842        write!(f, "{}", self.to_hex())
843    }
844}
845
846impl Debug for SimplePseudonym {
847    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
848        write!(f, "{}", self.to_hex())
849    }
850}
851
852impl BytesRepresentable for SimplePseudonym {
853    const SIZE: usize = 10;
854}
855
856impl AsRef<[u8]> for SimplePseudonym {
857    fn as_ref(&self) -> &[u8] {
858        &self.0
859    }
860}
861
862impl<'a> TryFrom<&'a [u8]> for SimplePseudonym {
863    type Error = GeneralError;
864
865    fn try_from(value: &'a [u8]) -> result::Result<Self, Self::Error> {
866        value
867            .try_into()
868            .map(Self)
869            .map_err(|_| ParseError("SimplePseudonym".into()))
870    }
871}
872
873impl Randomizable for SimplePseudonym {
874    /// Generates a random pseudonym.
875    fn random() -> Self {
876        let mut data = vec![0u8; Self::SIZE];
877        hopr_crypto_random::random_fill(&mut data);
878        Self::try_from(data.as_slice()).unwrap()
879    }
880}
881
882impl Pseudonym for SimplePseudonym {}
883
884#[cfg(test)]
885mod tests {
886    use std::str::FromStr;
887
888    use hex_literal::hex;
889    use hopr_crypto_random::Randomizable;
890    use hopr_primitive_types::prelude::*;
891    use k256::AffinePoint;
892    use libp2p_identity::PeerId;
893
894    use crate::{
895        keypairs::{Keypair, OffchainKeypair},
896        types::{Challenge, HalfKey, HalfKeyChallenge, Hash, OffchainPublicKey, PublicKey, Response},
897    };
898
899    const PUBLIC_KEY: [u8; 33] = hex!("021464586aeaea0eb5736884ca1bf42d165fc8e2243b1d917130fb9e321d7a93b8");
900    const PUBLIC_KEY_UNCOMPRESSED_PLAIN: [u8; 64] = hex!("1464586aeaea0eb5736884ca1bf42d165fc8e2243b1d917130fb9e321d7a93b8fb0699d4f177f9c84712f6d7c5f6b7f4f6916116047fa25c79ef806fc6c9523e");
901    const PUBLIC_KEY_UNCOMPRESSED: [u8; 65] = hex!("041464586aeaea0eb5736884ca1bf42d165fc8e2243b1d917130fb9e321d7a93b8fb0699d4f177f9c84712f6d7c5f6b7f4f6916116047fa25c79ef806fc6c9523e");
902    const PRIVATE_KEY: [u8; 32] = hex!("e17fe86ce6e99f4806715b0c9412f8dad89334bf07f72d5834207a9d8f19d7f8");
903
904    #[test]
905    fn test_public_key_to_hex() -> anyhow::Result<()> {
906        let pk = PublicKey::from_privkey(&hex!(
907            "492057cf93e99b31d2a85bc5e98a9c3aa0021feec52c227cc8170e8f7d047775"
908        ))?;
909
910        assert_eq!("0x0439d1bc2291826eaed86567d225cf243ebc637275e0a5aedb0d6b1dc82136a38e428804340d4c949a029846f682711d046920b4ca8b8ebeb9d1192b5bdaa54dba",
911            pk.to_uncompressed_hex());
912        assert_eq!(
913            "0x0239d1bc2291826eaed86567d225cf243ebc637275e0a5aedb0d6b1dc82136a38e",
914            pk.to_hex()
915        );
916
917        Ok(())
918    }
919
920    #[test]
921    fn test_public_key_serialize() -> anyhow::Result<()> {
922        let pk1 = PublicKey::try_from(PUBLIC_KEY.as_ref())?;
923        let pk2 = PublicKey::try_from(pk1.as_ref())?;
924        let pk3 = PublicKey::try_from(pk1.to_uncompressed_bytes().as_ref())?;
925
926        assert_eq!(pk1, pk2, "pub keys 1 2 don't match");
927        assert_eq!(pk2, pk3, "pub keys 2 3 don't match");
928
929        let pk1 = PublicKey::try_from(PUBLIC_KEY.as_ref())?;
930        let pk2 = PublicKey::try_from(PUBLIC_KEY_UNCOMPRESSED.as_ref())?;
931        let pk3 = PublicKey::try_from(PUBLIC_KEY_UNCOMPRESSED_PLAIN.as_ref())?;
932
933        assert_eq!(pk1, pk2, "pubkeys don't match");
934        assert_eq!(pk2, pk3, "pubkeys don't match");
935
936        let compressed: &[u8] = pk1.as_ref();
937        assert_eq!(PublicKey::SIZE_COMPRESSED, compressed.len());
938        assert_eq!(PublicKey::SIZE_UNCOMPRESSED, pk1.to_uncompressed_bytes().len());
939
940        let shorter = hex!("f85e38b056284626a7aed0acc5d474605a408e6cccf76d7241ec7b4dedb31929b710e034f4f9a7dba97743b01e1cc35a45a60bebb29642cb0ba6a7fe8433316c");
941        let s1 = PublicKey::try_from(shorter.as_ref())?;
942        let s2 = PublicKey::try_from(s1.to_uncompressed_bytes().as_ref())?;
943        assert_eq!(s1, s2);
944
945        Ok(())
946    }
947
948    #[test]
949    fn test_public_key_should_not_accept_identity() -> anyhow::Result<()> {
950        PublicKey::try_from(AffinePoint::IDENTITY).expect_err("must fail for identity point");
951        Ok(())
952    }
953
954    #[test]
955    fn test_public_key_from_privkey() -> anyhow::Result<()> {
956        let pk1 = PublicKey::from_privkey(&PRIVATE_KEY)?;
957        let pk2 = PublicKey::try_from(PUBLIC_KEY.as_ref())?;
958
959        assert_eq!(pk1, pk2, "failed to match deserialized pub key");
960
961        Ok(())
962    }
963
964    #[test]
965    fn test_offchain_public_key() -> anyhow::Result<()> {
966        let (s, pk1) = OffchainKeypair::random().unzip();
967
968        let pk2 = OffchainPublicKey::from_privkey(s.as_ref())?;
969        assert_eq!(pk1, pk2, "from privkey failed");
970
971        let pk3 = OffchainPublicKey::try_from(pk1.as_ref())?;
972        assert_eq!(pk1, pk3, "from bytes failed");
973
974        Ok(())
975    }
976
977    #[test]
978    fn test_offchain_public_key_peerid() -> anyhow::Result<()> {
979        let valid_peerid = PeerId::from_str("12D3KooWLYKsvDB4xEELYoHXxeStj2gzaDXjra2uGaFLpKCZkJHs")?;
980        let valid = OffchainPublicKey::from_peerid(&valid_peerid)?;
981        assert_eq!(valid_peerid, valid.into(), "must work with ed25519 peer ids");
982
983        let invalid_peerid = PeerId::from_str("16Uiu2HAmPHGyJ7y1Rj3kJ64HxJQgM9rASaeT2bWfXF9EiX3Pbp3K")?;
984        let invalid = OffchainPublicKey::from_peerid(&invalid_peerid);
985        assert!(invalid.is_err(), "must not work with secp256k1 peer ids");
986
987        let invalid_peerid_2 = PeerId::from_str("QmWvEwidPYBbLHfcZN6ATHdm4NPM4KbUx72LZnZRoRNKEN")?;
988        let invalid_2 = OffchainPublicKey::from_peerid(&invalid_peerid_2);
989        assert!(invalid_2.is_err(), "must not work with rsa peer ids");
990
991        Ok(())
992    }
993
994    #[test]
995    pub fn test_response() -> anyhow::Result<()> {
996        let r1 = Response([0u8; Response::SIZE]);
997        let r2 = Response::try_from(r1.as_ref())?;
998        assert_eq!(r1, r2, "deserialized response does not match");
999
1000        Ok(())
1001    }
1002
1003    #[test]
1004    fn test_half_key() -> anyhow::Result<()> {
1005        let hk1 = HalfKey([0u8; HalfKey::SIZE]);
1006        let hk2 = HalfKey::try_from(hk1.as_ref())?;
1007
1008        assert_eq!(hk1, hk2, "failed to match deserialized half-key");
1009
1010        Ok(())
1011    }
1012
1013    #[test]
1014    fn test_half_key_challenge() -> anyhow::Result<()> {
1015        let hkc1 = HalfKeyChallenge::try_from(PUBLIC_KEY.as_ref())?;
1016        let hkc2 = HalfKeyChallenge::try_from(hkc1.as_ref())?;
1017        assert_eq!(hkc1, hkc2, "failed to match deserialized half key challenge");
1018
1019        Ok(())
1020    }
1021
1022    #[test]
1023    fn test_challenge_response_flow() -> anyhow::Result<()> {
1024        let hk1 = HalfKey::random();
1025        let hk2 = HalfKey::random();
1026
1027        let response = Response::from_half_keys(&hk1, &hk2)?;
1028
1029        let half_chal1 = hk1.to_challenge()?;
1030        let half_chal2 = hk2.to_challenge()?;
1031
1032        let challenge1 = Challenge::from_hint_and_share(&half_chal1, &half_chal2)?;
1033        assert_eq!(challenge1, Challenge::from_hint_and_share(&half_chal2, &half_chal1)?);
1034        assert_eq!(challenge1, Challenge::from_own_share_and_half_key(&half_chal1, &hk2)?);
1035
1036        let challenge2 = response.to_challenge()?;
1037        assert_eq!(challenge1, challenge2);
1038        assert_eq!(challenge1.to_ethereum_challenge(), challenge2.to_ethereum_challenge());
1039        Ok(())
1040    }
1041
1042    #[test]
1043    fn test_hash() -> anyhow::Result<()> {
1044        let hash1 = Hash::create(&[b"msg"]);
1045        assert_eq!(
1046            "0x92aef1b955b9de564fc50e31a55b470b0c8cdb931f186485d620729fb03d6f2c",
1047            hash1.to_hex(),
1048            "hash test vector failed to match"
1049        );
1050
1051        let hash2 = Hash::try_from(hash1.as_ref())?;
1052        assert_eq!(hash1, hash2, "failed to match deserialized hash");
1053
1054        assert_eq!(
1055            hash1.hash(),
1056            Hash::try_from(hex!("1c4d8d521eccee7225073ea180e0fa075a6443afb7ca06076a9566b07d29470f").as_ref())?
1057        );
1058
1059        Ok(())
1060    }
1061}