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