hopr_crypto_types/
keypairs.rs

1use digest::Digest;
2use generic_array::{ArrayLength, GenericArray};
3use hopr_primitive_types::prelude::*;
4use sha2::Sha512;
5use std::fmt::Debug;
6use subtle::{Choice, ConstantTimeEq};
7
8use hopr_crypto_random::random_bytes;
9
10use crate::errors;
11use crate::errors::CryptoError::InvalidInputValue;
12use crate::types::{CompressedPublicKey, OffchainPublicKey, PublicKey};
13use crate::utils::{k256_scalar_from_bytes, random_group_element, x25519_scalar_from_bytes, SecretValue};
14
15/// Represents a generic key pair
16/// The keypair contains a private key and public key.
17/// Must be comparable in constant time and zeroized on drop.
18pub trait Keypair: ConstantTimeEq + Sized {
19    /// Represents the type of the private (secret) key
20    type SecretLen: ArrayLength;
21
22    /// Represents the type of the public key
23    type Public: BytesRepresentable + Clone + PartialEq;
24
25    /// Generates a new random keypair.
26    fn random() -> Self;
27
28    /// Creates a keypair from the given secret key.
29    fn from_secret(bytes: &[u8]) -> errors::Result<Self>;
30
31    /// Returns the private (secret) part of the keypair
32    fn secret(&self) -> &SecretValue<Self::SecretLen>;
33
34    /// Returns the public part of the keypair
35    fn public(&self) -> &Self::Public;
36
37    /// Consumes the instance and produces separated private and public parts
38    fn unzip(self) -> (SecretValue<Self::SecretLen>, Self::Public) {
39        (self.secret().clone(), self.public().clone())
40    }
41}
42
43/// Represents a keypair consisting of an Ed25519 private and public key
44#[derive(Clone)]
45pub struct OffchainKeypair(SecretValue<typenum::U32>, OffchainPublicKey);
46
47impl Keypair for OffchainKeypair {
48    type SecretLen = typenum::U32;
49    type Public = OffchainPublicKey;
50
51    fn random() -> Self {
52        // Safe to unwrap here, as the random bytes length is exact
53        Self::from_secret(&random_bytes::<{ ed25519_dalek::SECRET_KEY_LENGTH }>()).unwrap()
54    }
55
56    fn from_secret(bytes: &[u8]) -> errors::Result<Self> {
57        Ok(Self(
58            bytes.try_into().map_err(|_| InvalidInputValue("bytes"))?,
59            OffchainPublicKey::from_privkey(bytes)?,
60        ))
61    }
62
63    fn secret(&self) -> &SecretValue<typenum::U32> {
64        &self.0
65    }
66
67    fn public(&self) -> &Self::Public {
68        &self.1
69    }
70}
71
72impl Debug for OffchainKeypair {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        f.debug_tuple("OffchainKeypair").field(&self.1).finish()
75    }
76}
77
78impl ConstantTimeEq for OffchainKeypair {
79    fn ct_eq(&self, other: &Self) -> Choice {
80        self.secret().ct_eq(other.secret())
81    }
82}
83
84impl From<&OffchainKeypair> for curve25519_dalek::scalar::Scalar {
85    /// Transforms the secret to be equivalent with the EdDSA public key used for signing.
86    /// This is required so that the secret keys used to generate Sphinx shared secrets
87    /// correspond to the public keys we get from the Ed25519 peer ids.
88    fn from(value: &OffchainKeypair) -> Self {
89        let mut h: Sha512 = Sha512::default();
90        h.update(&value.0);
91        let hash = h.finalize();
92
93        let mut ret = [0u8; ed25519_dalek::SECRET_KEY_LENGTH];
94        ret.copy_from_slice(&hash[..32]);
95        x25519_scalar_from_bytes(&ret).unwrap() // cannot happen, secret always represents a valid scalar
96    }
97}
98
99impl From<&OffchainKeypair> for libp2p_identity::Keypair {
100    fn from(value: &OffchainKeypair) -> Self {
101        libp2p_identity::Keypair::ed25519_from_bytes(value.0.clone()).expect("invalid offchain keypair")
102        // must not happen
103    }
104}
105
106impl From<&OffchainKeypair> for libp2p_identity::PeerId {
107    fn from(value: &OffchainKeypair) -> Self {
108        value.1.into()
109    }
110}
111
112/// Represents a keypair consisting of a secp256k1 private and public key
113#[derive(Clone)]
114pub struct ChainKeypair(SecretValue<typenum::U32>, CompressedPublicKey);
115
116impl Keypair for ChainKeypair {
117    type SecretLen = typenum::U32;
118    type Public = CompressedPublicKey;
119
120    fn random() -> Self {
121        let (secret, public) = random_group_element();
122        Self(
123            GenericArray::from(secret).into(),
124            CompressedPublicKey(public.try_into().unwrap()),
125        )
126    }
127
128    fn from_secret(bytes: &[u8]) -> errors::Result<Self> {
129        let compressed = PublicKey::from_privkey(bytes).map(CompressedPublicKey)?;
130
131        Ok(Self(
132            bytes.try_into().map_err(|_| InvalidInputValue("bytes"))?,
133            compressed,
134        ))
135    }
136
137    fn secret(&self) -> &SecretValue<typenum::U32> {
138        &self.0
139    }
140
141    fn public(&self) -> &Self::Public {
142        &self.1
143    }
144}
145
146impl Debug for ChainKeypair {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        // Do not expose the private key
149        f.debug_tuple("ChainKeypair").field(&self.1).finish()
150    }
151}
152
153impl ConstantTimeEq for ChainKeypair {
154    fn ct_eq(&self, other: &Self) -> Choice {
155        self.secret().ct_eq(other.secret())
156    }
157}
158
159impl From<&ChainKeypair> for k256::Scalar {
160    fn from(value: &ChainKeypair) -> Self {
161        k256_scalar_from_bytes(value.0.as_ref()).unwrap() // cannot happen, secret always represents a valid scalar
162    }
163}
164
165impl From<&ChainKeypair> for Address {
166    fn from(value: &ChainKeypair) -> Self {
167        value.public().to_address()
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use libp2p_identity::PeerId;
175    use subtle::ConstantTimeEq;
176
177    #[test]
178    fn test_offchain_keypair() {
179        let kp_1 = OffchainKeypair::random();
180
181        let public = OffchainPublicKey::from_privkey(kp_1.secret().as_ref()).unwrap();
182        assert_eq!(&public, kp_1.public(), "secret keys must yield compatible public keys");
183
184        let kp_2 = OffchainKeypair::from_secret(kp_1.secret().as_ref()).unwrap();
185        assert_eq!(
186            kp_1.ct_eq(&kp_2).unwrap_u8(),
187            1,
188            "keypairs generated from secrets must be equal"
189        );
190        assert_eq!(&public, kp_2.public(), "secret keys must yield compatible public keys");
191        assert_eq!(kp_1.public(), kp_2.public(), "keypair public keys must be equal");
192
193        let (s1, p1) = kp_1.unzip();
194        let (s2, p2) = kp_2.unzip();
195
196        assert_eq!(s1.ct_eq(&s2).unwrap_u8(), 1);
197        assert_eq!(p1, p2);
198    }
199
200    #[test]
201    fn test_offchain_keypair_libp2p_compatibility() {
202        let kp_1 = OffchainKeypair::random();
203
204        let p2p_kp: libp2p_identity::Keypair = (&kp_1).into();
205
206        let p1: PeerId = (*kp_1.public()).into();
207        let p2: PeerId = p2p_kp.public().into();
208        assert_eq!(p1, p2, "peer ids must be equal");
209    }
210
211    #[test]
212    fn test_chain_keypair() {
213        let kp_1 = ChainKeypair::random();
214
215        let public = CompressedPublicKey(PublicKey::from_privkey(kp_1.secret().as_ref()).unwrap());
216        assert_eq!(&public, kp_1.public(), "secret keys must yield compatible public keys");
217
218        let kp_2 = ChainKeypair::from_secret(kp_1.secret().as_ref()).unwrap();
219        assert_eq!(
220            kp_1.ct_eq(&kp_2).unwrap_u8(),
221            1,
222            "keypairs generated from secrets must be equal"
223        );
224        assert_eq!(&public, kp_2.public(), "secret keys must yield compatible public keys");
225        assert_eq!(kp_1.public(), kp_2.public(), "keypair public keys must be equal");
226
227        let (s1, p1) = kp_1.unzip();
228        let (s2, p2) = kp_2.unzip();
229
230        assert_eq!(s1.ct_eq(&s2).unwrap_u8(), 1);
231        assert_eq!(p1, p2);
232    }
233}