1use std::{fmt, path::Path, str::FromStr};
2
3use base64::Engine;
4use ed25519_dalek::{
5 ed25519::signature::SignerMut, pkcs8::EncodePrivateKey as _, SigningKey as Ed25519SecretKey,
6};
7use openmina_core::{EncryptedSecretKey, EncryptedSecretKeyFile, EncryptionError};
8use rand::{CryptoRng, Rng};
9use serde::{Deserialize, Serialize};
10use zeroize::Zeroizing;
11
12use super::{PublicKey, Signature};
13
14#[derive(Clone)]
15pub struct SecretKey(Ed25519SecretKey);
16
17impl fmt::Debug for SecretKey {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 f.debug_tuple("SecretKey").field(&"***").finish()
20 }
21}
22
23impl SecretKey {
24 const BASE58_CHECK_VERSION: u8 = 0x80;
25
26 pub fn rand() -> Self {
27 Self::rand_with(&mut rand::thread_rng())
28 }
29
30 pub fn rand_with(mut rng: impl Rng) -> Self {
31 Self::from_bytes(rng.gen())
32 }
33
34 pub fn deterministic(i: usize) -> Self {
35 let mut bytes = [0; 32];
36 let bytes_len = bytes.len();
37 let i_bytes = (i + 1).to_be_bytes();
38 let i = bytes_len - i_bytes.len();
39 bytes[i..bytes_len].copy_from_slice(&i_bytes);
40 Self::from_bytes(bytes)
41 }
42
43 pub fn from_bytes(bytes: [u8; 32]) -> Self {
44 Self(Ed25519SecretKey::from_bytes(&bytes))
45 }
46
47 pub fn to_bytes(&self) -> [u8; 32] {
48 self.0.to_bytes()
49 }
50
51 pub fn public_key(&self) -> PublicKey {
52 PublicKey(self.0.verifying_key())
53 }
54
55 pub fn to_x25519(&self) -> x25519_dalek::StaticSecret {
56 self.0.to_scalar_bytes().into()
57 }
58
59 pub fn to_pem(&self) -> Zeroizing<String> {
60 self.0
61 .to_pkcs8_pem(Default::default())
62 .expect("must be valid key")
63 }
64
65 pub fn from_encrypted_file(
66 path: impl AsRef<Path>,
67 password: &str,
68 ) -> Result<Self, EncryptionError> {
69 let encrypted = EncryptedSecretKeyFile::new(path)?;
70 let decrypted = Self::try_decrypt(&encrypted, password)?;
71
72 let keypair_string = String::from_utf8(decrypted.to_vec())
73 .map_err(|e| EncryptionError::Other(e.to_string()))?;
74
75 let parts: Vec<&str> = keypair_string.split(',').collect();
76
77 if parts.len() != 3 {
78 return Err(EncryptionError::Other(
79 "libp2p keypair string must have 3 parts".to_string(),
80 ));
81 }
82
83 let (secret_key_base64, _public_key_base64, _peer_id) = (parts[0], parts[1], parts[2]);
84
85 let key_bytes = base64::engine::general_purpose::STANDARD
86 .decode(secret_key_base64.as_bytes())
87 .map_err(|e| EncryptionError::Other(e.to_string()))?;
88
89 let key_bytes = key_bytes[4..36]
90 .try_into()
91 .map_err(|_| EncryptionError::Other("Invalid secret key length".to_string()))?;
92 Ok(Self::from_bytes(key_bytes))
93 }
94
95 pub fn to_encrypted_file(
96 &self,
97 _password: &str,
98 _path: impl AsRef<Path>,
99 ) -> Result<(), EncryptionError> {
100 todo!()
101 }
102}
103
104impl EncryptedSecretKey for SecretKey {}
105
106use aes_gcm::{
107 aead::{Aead, AeadCore},
108 Aes256Gcm, KeyInit,
109};
110
111#[derive(Debug, Clone)]
113pub struct EncryptError();
114
115impl std::fmt::Display for EncryptError {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 write!(f, "Encryption error occurred")
118 }
119}
120
121impl std::error::Error for EncryptError {}
122
123impl SecretKey {
124 fn shared_key(&self, other_pk: &PublicKey) -> Result<Aes256Gcm, EncryptError> {
125 let key = self.to_x25519().diffie_hellman(&other_pk.to_x25519());
126 if !key.was_contributory() {
127 return Err(EncryptError());
128 }
129 let key = key.to_bytes();
130 let key: &aes_gcm::Key<Aes256Gcm> = (&key).into();
132 Ok(Aes256Gcm::new(key))
133 }
134
135 pub fn encrypt_raw(
136 &self,
137 other_pk: &PublicKey,
138 rng: impl Rng + CryptoRng,
139 data: &[u8],
140 ) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
141 let shared_key = self.shared_key(other_pk)?;
142 let nonce = Aes256Gcm::generate_nonce(rng);
143 let mut buffer = Vec::from(AsRef::<[u8]>::as_ref(&nonce));
144 buffer.extend(
145 shared_key
146 .encrypt(&nonce, data)
147 .or(Err(Box::new(EncryptError())))?,
148 );
149 Ok(buffer)
150 }
151
152 pub fn encrypt<M: EncryptableType>(
153 &self,
154 other_pk: &PublicKey,
155 rng: impl Rng + CryptoRng,
156 data: &M,
157 ) -> Result<M::Encrypted, Box<dyn std::error::Error>> {
158 let data = serde_json::to_vec(data).map_err(|_| EncryptError())?;
159 self.encrypt_raw(other_pk, rng, &data).map(Into::into)
160 }
161
162 pub fn decrypt_raw(
163 &self,
164 other_pk: &PublicKey,
165 ciphertext: &[u8],
166 ) -> Result<Vec<u8>, EncryptError> {
167 let shared_key = self.shared_key(other_pk)?;
168 let (nonce, ciphertext) = ciphertext.split_at_checked(12).ok_or(EncryptError())?;
169 shared_key
170 .decrypt(nonce.into(), ciphertext)
171 .or(Err(EncryptError()))
172 }
173
174 pub fn decrypt<M: EncryptableType>(
175 &self,
176 other_pk: &PublicKey,
177 ciphertext: &M::Encrypted,
178 ) -> Result<M, Box<dyn std::error::Error>> {
179 let data: Vec<u8> = self.decrypt_raw(other_pk, ciphertext.as_ref())?;
180 serde_json::from_slice(&data).map_err(Box::<dyn std::error::Error>::from)
181 }
182
183 pub fn sign(&mut self, data: &[u8]) -> Signature {
184 Signature(self.0.sign(data))
185 }
186
187 pub fn libp2p_pubsub_sign(&mut self, msg: &[u8]) -> Signature {
188 self.sign(&[b"libp2p-pubsub:", msg].concat())
189 }
190
191 pub fn libp2p_pubsub_pb_message_sign(
192 &mut self,
193 msg: &crate::pb::Message,
194 ) -> Result<Signature, prost::EncodeError> {
195 let mut buf = Vec::new();
196 prost::Message::encode(msg, &mut buf)?;
197 Ok(self.libp2p_pubsub_sign(&buf))
198 }
199}
200
201pub trait EncryptableType: Serialize + for<'a> Deserialize<'a> {
202 type Encrypted: From<Vec<u8>> + AsRef<[u8]>;
203}
204
205impl fmt::Display for SecretKey {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 let s = bs58::encode(&self.to_bytes())
208 .with_check_version(Self::BASE58_CHECK_VERSION)
209 .into_string();
210 write!(f, "{}", s)
211 }
212}
213
214#[derive(thiserror::Error, Serialize, Deserialize, Debug, Clone)]
215pub enum SecretKeyFromStrError {
216 #[error("Base58 decode error: {0}")]
217 Bs58(String),
218}
219
220impl FromStr for SecretKey {
221 type Err = SecretKeyFromStrError;
222
223 fn from_str(s: &str) -> Result<Self, Self::Err> {
224 let mut bytes = [0u8; 37];
225 let size = bs58::decode(s)
226 .with_check(Some(Self::BASE58_CHECK_VERSION))
227 .into(&mut bytes)
228 .map_err(|err| SecretKeyFromStrError::Bs58(err.to_string()))?;
229 if size != 33 {
230 return Err(SecretKeyFromStrError::Bs58(
231 bs58::decode::Error::BufferTooSmall.to_string(),
232 ));
233 }
234 Ok(Self::from_bytes(
235 bytes[1..33].try_into().expect("Size checked above"),
236 ))
237 }
238}
239
240#[cfg(feature = "p2p-libp2p")]
241impl TryFrom<SecretKey> for libp2p_identity::Keypair {
242 type Error = libp2p_identity::DecodingError;
243
244 fn try_from(value: SecretKey) -> Result<Self, Self::Error> {
245 Self::ed25519_from_bytes(value.to_bytes())
246 }
247}
248
249#[cfg(feature = "p2p-libp2p")]
250impl TryFrom<libp2p_identity::Keypair> for SecretKey {
251 type Error = ();
252
253 fn try_from(value: libp2p_identity::Keypair) -> Result<Self, Self::Error> {
254 let bytes = value.try_into_ed25519().or(Err(()))?.to_bytes();
255 Ok(Self::from_bytes(bytes[0..32].try_into().or(Err(()))?))
256 }
257}
258
259impl Serialize for SecretKey {
260 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
261 where
262 S: serde::Serializer,
263 {
264 serializer.serialize_str(&self.to_string())
265 }
266}
267
268impl<'de> serde::Deserialize<'de> for SecretKey {
269 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
270 where
271 D: serde::Deserializer<'de>,
272 {
273 let b58: String = Deserialize::deserialize(deserializer)?;
274 b58.parse().map_err(serde::de::Error::custom)
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::SecretKey;
281
282 #[test]
283 fn secret_key_to_string_roundtrip() {
284 let s = "5K9G39rCgREFMCk7S739JZprwpMsiLRQXcELErUSwhHwfdVR8HT";
285 let sk = s.parse::<SecretKey>().expect("should be parseable");
286 let unparsed = sk.to_string();
287 assert_eq!(s, &unparsed);
288 }
289
290 #[test]
291 fn test_libp2p_key_decrypt() {
292 let password = "total-secure-pass";
293 let key_path = "../tests/files/accounts/libp2p-key";
294
295 let expected_peer_id = "12D3KooWDxyuJKSsVEwNR13UVwf4PEfs4yHkk3ecZipBPv3Y3Sac";
296
297 let decrypted = SecretKey::from_encrypted_file(key_path, password)
298 .expect("Failed to decrypt secret key file");
299
300 let peer_id = decrypted.public_key().peer_id().to_libp2p_string();
301 assert_eq!(expected_peer_id, peer_id);
302 }
303}