Skip to main content

hopr_crypto_packet/sphinx/
ec_groups.rs

1use hopr_types::crypto::errors::Result;
2#[cfg(feature = "x25519")]
3use hopr_types::crypto::primitives::Curve25519MontgomeryPoint;
4#[cfg(any(feature = "x25519", feature = "ed25519"))]
5use hopr_types::crypto::primitives::{Curve25519CompressedPoint, Curve25519Point, Curve25519Scalar, IsIdentity};
6#[cfg(feature = "secp256k1")]
7use hopr_types::crypto::{
8    crypto_traits::elliptic_curve::{
9        self, Group,
10        field::FieldBytes,
11        sec1::{FromSec1Point, Sec1Point, ToSec1Point},
12    },
13    keypairs::ChainKeypair,
14    prelude::{CryptoError, Secp256k1},
15    types::PublicKey,
16};
17
18use super::shared_keys::{Alpha, GroupElement, Scalar, SphinxSuite};
19
20/// Newtype for secp256k1 scalars — avoids Rust 2024 coherence issues
21/// that arise when using transparent type aliases to `elliptic_curve::Scalar<Secp256k1>`.
22#[cfg(feature = "secp256k1")]
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
24pub struct Secp256k1Scalar(pub elliptic_curve::Scalar<Secp256k1>);
25
26/// Newtype for secp256k1 projective points — avoids Rust 2024 coherence issues
27/// that arise when using transparent type aliases to `elliptic_curve::ProjectivePoint<Secp256k1>`.
28#[cfg(feature = "secp256k1")]
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub struct Secp256k1Point(pub elliptic_curve::ProjectivePoint<Secp256k1>);
31
32#[cfg(feature = "secp256k1")]
33impl Default for Secp256k1Point {
34    fn default() -> Self {
35        Self(elliptic_curve::ProjectivePoint::<Secp256k1>::IDENTITY)
36    }
37}
38
39// Forwarding impls needed by SphinxSuite trait bounds
40#[cfg(feature = "secp256k1")]
41impl std::ops::Mul for Secp256k1Scalar {
42    type Output = Self;
43
44    fn mul(self, rhs: Self) -> Self::Output {
45        Self(self.0 * rhs.0)
46    }
47}
48
49#[cfg(feature = "secp256k1")]
50impl<'a> std::ops::Mul<&'a Secp256k1Scalar> for Secp256k1Point {
51    type Output = Self;
52
53    fn mul(self, rhs: &'a Secp256k1Scalar) -> Self::Output {
54        Self(self.0 * rhs.0)
55    }
56}
57
58#[cfg(feature = "secp256k1")]
59impl From<&ChainKeypair> for Secp256k1Scalar {
60    fn from(value: &ChainKeypair) -> Self {
61        Self(elliptic_curve::Scalar::<Secp256k1>::from(value))
62    }
63}
64
65#[cfg(feature = "secp256k1")]
66impl From<&PublicKey> for Secp256k1Point {
67    fn from(value: &PublicKey) -> Self {
68        Self(elliptic_curve::ProjectivePoint::<Secp256k1>::from(value))
69    }
70}
71
72#[cfg(any(feature = "x25519", feature = "ed25519"))]
73impl Scalar for Curve25519Scalar {
74    fn random() -> Self {
75        let bytes = hopr_types::crypto_random::random_bytes::<32>();
76        Self::from_bytes(&bytes).unwrap()
77    }
78
79    fn from_bytes(bytes: &[u8]) -> Result<Self> {
80        hopr_types::crypto::utils::x25519_scalar_from_bytes(bytes)
81    }
82}
83
84#[cfg(feature = "secp256k1")]
85impl Scalar for Secp256k1Scalar {
86    fn random() -> Self {
87        // Beware, this is not constant-time
88        let mut rng = hopr_types::crypto_random::rng();
89        let mut bytes = FieldBytes::<Secp256k1>::default();
90        use elliptic_curve::PrimeField;
91        use hopr_types::crypto_random::Rng;
92        // Needs manual implementation due to incompatible rand crates
93        // elliptic_curve::Scalar::<Secp256k1>::generate_vartime(&mut hopr_types::crypto_random::rng())
94
95        loop {
96            rng.fill_bytes(&mut bytes);
97            if let Some(scalar) = elliptic_curve::Scalar::<Secp256k1>::from_repr(bytes).into() {
98                return Secp256k1Scalar(scalar);
99            }
100        }
101    }
102
103    fn from_bytes(bytes: &[u8]) -> Result<Self> {
104        Ok(Secp256k1Scalar(hopr_types::crypto::utils::k256_scalar_from_bytes(
105            bytes,
106        )?))
107    }
108}
109
110#[cfg(feature = "x25519")]
111impl GroupElement<Curve25519Scalar> for Curve25519MontgomeryPoint {
112    type AlphaLen = hopr_types::primitive::typenum::U32;
113
114    fn to_alpha(&self) -> Alpha<hopr_types::primitive::typenum::U32> {
115        self.0.into()
116    }
117
118    fn from_alpha(alpha: Alpha<hopr_types::primitive::typenum::U32>) -> Result<Self> {
119        Ok(Curve25519MontgomeryPoint(alpha.into()))
120    }
121
122    fn generate(scalar: &Curve25519Scalar) -> Self {
123        Curve25519Point::mul_base(scalar).to_montgomery()
124    }
125
126    fn is_valid(&self) -> bool {
127        use IsIdentity;
128        !self.is_identity()
129    }
130}
131
132#[cfg(feature = "ed25519")]
133impl GroupElement<Curve25519Scalar> for Curve25519Point {
134    type AlphaLen = hopr_types::primitive::typenum::U32;
135
136    fn to_alpha(&self) -> Alpha<hopr_types::primitive::typenum::U32> {
137        self.compress().0.into()
138    }
139
140    fn from_alpha(alpha: Alpha<hopr_types::primitive::typenum::U32>) -> Result<Self> {
141        Curve25519CompressedPoint(alpha.into())
142            .decompress()
143            .ok_or(hopr_types::crypto::errors::CryptoError::InvalidInputValue("alpha"))
144    }
145
146    fn generate(scalar: &Curve25519Scalar) -> Self {
147        Curve25519Point::mul_base(scalar)
148    }
149
150    fn is_valid(&self) -> bool {
151        // Ed25519 scalars always come clamped (pre-multiplied by the curve's co-factor)
152        // and therefore cannot result into points of small order.
153        // See `x25519_scalar_from_bytes` for more details.
154        use IsIdentity;
155        !self.is_identity()
156    }
157}
158
159#[cfg(feature = "secp256k1")]
160impl GroupElement<Secp256k1Scalar> for Secp256k1Point {
161    type AlphaLen = hopr_types::primitive::typenum::U33;
162
163    fn to_alpha(&self) -> Alpha<hopr_types::primitive::typenum::U33> {
164        debug_assert!(self.is_valid(), "to_alpha called on identity point");
165        let mut ret = Alpha::<hopr_types::primitive::typenum::U33>::default();
166        // Copy only if the point is not the identity, we do not care here about constant-time here.
167        if !bool::from(self.0.is_identity()) {
168            ret.copy_from_slice(self.0.to_affine().to_sec1_point(true).as_ref());
169        }
170        ret
171    }
172
173    fn from_alpha(alpha: Alpha<hopr_types::primitive::typenum::U33>) -> Result<Self> {
174        Sec1Point::<Secp256k1>::from_bytes(alpha)
175            .map_err(|_| CryptoError::InvalidInputValue("alpha"))
176            .and_then(|ep| {
177                <Secp256k1 as elliptic_curve::CurveArithmetic>::AffinePoint::from_sec1_point(&ep)
178                    .into_option()
179                    .ok_or(CryptoError::InvalidInputValue("alpha"))
180            })
181            .map(|ap| Secp256k1Point(elliptic_curve::ProjectivePoint::<Secp256k1>::from(ap)))
182    }
183
184    fn generate(scalar: &Secp256k1Scalar) -> Self {
185        Secp256k1Point(elliptic_curve::ProjectivePoint::<Secp256k1>::mul_by_generator(
186            &scalar.0,
187        ))
188    }
189
190    fn is_valid(&self) -> bool {
191        !bool::from(self.0.is_identity())
192    }
193}
194
195// TODO: invert this, so that each SphinxSuite takes this as a type argument
196/// Default packet block size for the Sphinx protocol.
197///
198/// Currently, 1040 bytes.
199pub type DefaultSphinxPacketSize = hopr_types::primitive::hybrid_array::sizes::U1040;
200
201pub use hopr_types::primitive::typenum::Unsigned;
202
203/// Represents an instantiation of the Sphinx protocol using secp256k1 elliptic curve and `ChainKeypair`
204#[cfg(feature = "secp256k1")]
205#[derive(Clone, Copy, Debug, PartialEq, Eq)]
206#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
207pub struct Secp256k1Suite;
208
209#[cfg(feature = "secp256k1")]
210impl SphinxSuite for Secp256k1Suite {
211    type E = Secp256k1Scalar;
212    type G = Secp256k1Point;
213    type P = hopr_types::crypto::keypairs::ChainKeypair;
214    type PRP = hopr_types::crypto::lioness::LionessBlake3ChaCha20<DefaultSphinxPacketSize>;
215}
216
217/// Represents an instantiation of the Sphinx protocol using the ed25519 curve and `OffchainKeypair`
218#[cfg(feature = "ed25519")]
219#[derive(Clone, Copy, Debug, PartialEq, Eq)]
220#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
221pub struct Ed25519Suite;
222
223#[cfg(feature = "ed25519")]
224impl SphinxSuite for Ed25519Suite {
225    type E = Curve25519Scalar;
226    type G = Curve25519Point;
227    type P = hopr_types::crypto::keypairs::OffchainKeypair;
228    type PRP = hopr_types::crypto::lioness::LionessBlake3ChaCha20<DefaultSphinxPacketSize>;
229}
230
231/// Represents an instantiation of the Sphinx protocol using the Curve25519 curve and `OffchainKeypair`
232#[cfg(feature = "x25519")]
233#[derive(Clone, Copy, Debug, PartialEq, Eq)]
234#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
235pub struct X25519Suite;
236
237#[cfg(feature = "x25519")]
238impl SphinxSuite for X25519Suite {
239    type E = Curve25519Scalar;
240    type G = Curve25519MontgomeryPoint;
241    type P = hopr_types::crypto::keypairs::OffchainKeypair;
242    type PRP = hopr_types::crypto::lioness::LionessBlake3ChaCha20<DefaultSphinxPacketSize>;
243}
244
245#[cfg(test)]
246mod tests {
247    use parameterized::parameterized;
248
249    use super::super::shared_keys::tests::generic_sphinx_suite_test;
250
251    #[cfg(feature = "secp256k1")]
252    #[test]
253    fn test_extract_key_from_group_element() {
254        use hopr_types::crypto::{crypto_traits::elliptic_curve, prelude::Secp256k1};
255
256        use super::{super::shared_keys::GroupElement, Secp256k1Point};
257
258        let salt = [0xde, 0xad, 0xbe, 0xef];
259        let pt = Secp256k1Point(elliptic_curve::ProjectivePoint::<Secp256k1>::GENERATOR);
260
261        let key = pt.extract_key("test", &salt);
262        assert_eq!(
263            "08112a22609819a4c698d6c92f404628ca925f3d731d53594126ffdf19ef6fa9",
264            const_hex::encode(key)
265        );
266    }
267
268    #[cfg(feature = "secp256k1")]
269    #[test]
270    fn test_expand_key_from_group_element() {
271        use hopr_types::crypto::{crypto_traits::elliptic_curve, prelude::Secp256k1};
272
273        use super::{super::shared_keys::GroupElement, Secp256k1Point};
274
275        let salt = [0xde, 0xad, 0xbe, 0xef];
276        let pt = Secp256k1Point(elliptic_curve::ProjectivePoint::<Secp256k1>::GENERATOR);
277
278        let key = pt.extract_key("test", &salt);
279
280        assert_eq!(
281            "08112a22609819a4c698d6c92f404628ca925f3d731d53594126ffdf19ef6fa9",
282            const_hex::encode(key)
283        );
284    }
285
286    #[cfg(feature = "secp256k1")]
287    #[parameterized(nodes = {4, 3, 2, 1})]
288    fn test_secp256k1_suite(nodes: usize) {
289        generic_sphinx_suite_test::<super::Secp256k1Suite>(nodes)
290    }
291
292    #[cfg(feature = "ed25519")]
293    #[parameterized(nodes = {4, 3, 2, 1})]
294    fn test_ed25519_shared_keys(nodes: usize) {
295        generic_sphinx_suite_test::<crate::Ed25519Suite>(nodes)
296    }
297
298    #[cfg(feature = "x25519")]
299    #[parameterized(nodes = {4, 3, 2, 1})]
300    fn test_montgomery_shared_keys(nodes: usize) {
301        generic_sphinx_suite_test::<super::X25519Suite>(nodes)
302    }
303}