hopr_crypto_types/
keypairs.rs

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