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