hopr_crypto_types/
keypairs.rs1use 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
16pub trait Keypair: ConstantTimeEq + ZeroizeOnDrop + Sized {
20 type SecretLen: ArrayLength<u8>;
22
23 type Public: BytesRepresentable + Clone + PartialEq;
25
26 fn random() -> Self;
28
29 fn from_secret(bytes: &[u8]) -> errors::Result<Self>;
31
32 fn secret(&self) -> &SecretValue<Self::SecretLen>;
34
35 fn public(&self) -> &Self::Public;
37
38 fn unzip(self) -> (SecretValue<Self::SecretLen>, Self::Public) {
40 (self.secret().clone(), self.public().clone())
41 }
42}
43
44#[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 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 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() }
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 }
105}
106
107impl From<&OffchainKeypair> for libp2p_identity::PeerId {
108 fn from(value: &OffchainKeypair) -> Self {
109 value.1.into()
110 }
111}
112
113#[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 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() }
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}