p2p/identity/
secret_key.rs

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// TODO: provide more detailed errors
112#[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        // eprintln!("[shared_key] {} & {} = {}", self.public_key(), other_pk, hex::encode(&key));
131        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}