hopr_crypto_types/
keypairs.rs1use 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::{OffchainPublicKey, PublicKey},
14 utils::{SecretValue, k256_scalar_from_bytes, random_group_element, x25519_scalar_from_bytes},
15};
16
17pub trait Keypair: ConstantTimeEq + Sized {
21 type SecretLen: ArrayLength;
23
24 type Public: BytesRepresentable + Clone + PartialEq;
26
27 fn random() -> Self;
29
30 fn from_secret(bytes: &[u8]) -> errors::Result<Self>;
32
33 fn secret(&self) -> &SecretValue<Self::SecretLen>;
35
36 fn public(&self) -> &Self::Public;
38
39 fn unzip(self) -> (SecretValue<Self::SecretLen>, Self::Public) {
41 (self.secret().clone(), self.public().clone())
42 }
43}
44
45#[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 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 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() }
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 }
106}
107
108impl From<&OffchainKeypair> for libp2p_identity::PeerId {
109 fn from(value: &OffchainKeypair) -> Self {
110 value.1.into()
111 }
112}
113
114#[derive(Clone)]
116pub struct ChainKeypair(SecretValue<typenum::U32>, PublicKey);
117
118impl Keypair for ChainKeypair {
119 type Public = PublicKey;
120 type SecretLen = typenum::U32;
121
122 fn random() -> Self {
123 let (secret, public) = random_group_element();
124 Self(GenericArray::from(secret).into(), public.into())
125 }
126
127 fn from_secret(bytes: &[u8]) -> errors::Result<Self> {
128 let compressed = PublicKey::from_privkey(bytes)?;
129
130 Ok(Self(
131 bytes.try_into().map_err(|_| InvalidInputValue("bytes"))?,
132 compressed,
133 ))
134 }
135
136 fn secret(&self) -> &SecretValue<typenum::U32> {
137 &self.0
138 }
139
140 fn public(&self) -> &Self::Public {
141 &self.1
142 }
143}
144
145impl Debug for ChainKeypair {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 f.debug_tuple("ChainKeypair").field(&self.1).finish()
149 }
150}
151
152impl ConstantTimeEq for ChainKeypair {
153 fn ct_eq(&self, other: &Self) -> Choice {
154 self.secret().ct_eq(other.secret())
155 }
156}
157
158impl From<&ChainKeypair> for k256::Scalar {
159 fn from(value: &ChainKeypair) -> Self {
160 k256_scalar_from_bytes(value.0.as_ref()).unwrap() }
162}
163
164impl From<&ChainKeypair> for Address {
165 fn from(value: &ChainKeypair) -> Self {
166 value.public().to_address()
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use libp2p_identity::PeerId;
173 use subtle::ConstantTimeEq;
174
175 use super::*;
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 = 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}