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