openmina_node_account/
secret_key.rs

1use std::{fmt, fs, io, path::Path, str::FromStr};
2
3use mina_p2p_messages::{bigint::BigInt, v2::SignatureLibPrivateKeyStableV1};
4use mina_signer::{keypair::KeypairError, seckey::SecKeyError, CompressedPubKey, Keypair};
5use openmina_core::{
6    constants::GENESIS_PRODUCER_SK, EncryptedSecretKey, EncryptedSecretKeyFile, EncryptionError,
7};
8use rand::{rngs::StdRng, CryptoRng, Rng, SeedableRng};
9use serde::{Deserialize, Serialize};
10
11use super::AccountPublicKey;
12
13#[derive(Clone)]
14pub struct AccountSecretKey(Keypair);
15
16impl std::fmt::Debug for AccountSecretKey {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        f.debug_tuple("AccountSecretKey").field(&"***").finish()
19    }
20}
21
22lazy_static::lazy_static! {
23    // TODO(binier): better way.
24    static ref GENERATED_DETERMINISTIC: Vec<AccountSecretKey> = {
25        let mut rng = StdRng::seed_from_u64(0);
26        (0..10000)
27            .map(|_| AccountSecretKey::rand_with(&mut rng))
28            .collect()
29    };
30}
31
32impl AccountSecretKey {
33    const BASE58_CHECK_VERSION: u8 = 90;
34
35    pub fn genesis_producer() -> Self {
36        Self::from_str(GENESIS_PRODUCER_SK).unwrap()
37    }
38
39    pub fn deterministic(i: u64) -> Self {
40        GENERATED_DETERMINISTIC[i as usize].clone()
41    }
42
43    pub fn deterministic_iter() -> impl Iterator<Item = &'static AccountSecretKey> {
44        GENERATED_DETERMINISTIC.iter()
45    }
46
47    pub fn max_deterministic_count() -> usize {
48        GENERATED_DETERMINISTIC.len()
49    }
50
51    pub fn rand() -> Self {
52        Self::rand_with(rand::thread_rng())
53    }
54
55    pub fn rand_with(mut rng: impl Rng + CryptoRng) -> Self {
56        Self(Keypair::rand(&mut rng).unwrap())
57    }
58
59    pub fn from_bytes(bytes: &[u8]) -> Result<Self, KeypairError> {
60        let mut bytes: [u8; 32] = match bytes.try_into() {
61            Ok(bytes) => bytes,
62            Err(_) => return Err(KeypairError::SecretKey(SecKeyError::SecretKeyLength)),
63        };
64
65        // For some reason, `mina_signer::SecKey::from_bytes` reverse the bytes
66        bytes.reverse();
67
68        Ok(Self(Keypair::from_bytes(&bytes[..])?))
69    }
70
71    pub fn to_bytes(&self) -> [u8; 32] {
72        // TODO(binier): refactor
73        let mut bytes = hex::decode(self.0.to_hex()).unwrap();
74        bytes.reverse();
75        bytes.try_into().unwrap()
76    }
77
78    pub fn public_key(&self) -> AccountPublicKey {
79        self.0.public.clone().into()
80    }
81
82    pub fn public_key_compressed(&self) -> CompressedPubKey {
83        self.0.public.clone().into_compressed()
84    }
85
86    pub fn from_encrypted_file(
87        path: impl AsRef<Path>,
88        password: &str,
89    ) -> Result<Self, EncryptionError> {
90        Self::from_encrypted_reader(fs::File::open(path)?, password)
91    }
92
93    pub fn from_encrypted_reader(
94        reader: impl io::Read,
95        password: &str,
96    ) -> Result<Self, EncryptionError> {
97        let encrypted: EncryptedSecretKeyFile = serde_json::from_reader(reader)?;
98        Self::from_encrypted(&encrypted, password)
99    }
100
101    pub fn from_encrypted(
102        encrypted: &EncryptedSecretKeyFile,
103        password: &str,
104    ) -> Result<Self, EncryptionError> {
105        let decrypted: Vec<u8> = Self::try_decrypt(encrypted, password)?;
106        AccountSecretKey::from_bytes(&decrypted[1..])
107            .map_err(|err| EncryptionError::Other(err.to_string()))
108    }
109
110    pub fn to_encrypted_file(
111        &self,
112        path: impl AsRef<Path>,
113        password: &str,
114    ) -> Result<(), EncryptionError> {
115        if path.as_ref().exists() {
116            panic!("File {} already exists", path.as_ref().display())
117        }
118
119        let f = fs::File::create(path)?;
120        let encrypted = Self::try_encrypt(&self.to_bytes(), password)?;
121
122        serde_json::to_writer(f, &encrypted)?;
123        Ok(())
124    }
125}
126
127impl EncryptedSecretKey for AccountSecretKey {}
128
129impl From<AccountSecretKey> for Keypair {
130    fn from(value: AccountSecretKey) -> Self {
131        value.0
132    }
133}
134
135impl From<&AccountSecretKey> for SignatureLibPrivateKeyStableV1 {
136    fn from(value: &AccountSecretKey) -> Self {
137        Self(BigInt::from_bytes(value.to_bytes()))
138    }
139}
140
141impl From<AccountSecretKey> for SignatureLibPrivateKeyStableV1 {
142    fn from(value: AccountSecretKey) -> Self {
143        Self(BigInt::from_bytes(value.to_bytes()))
144    }
145}
146
147impl FromStr for AccountSecretKey {
148    type Err = anyhow::Error;
149
150    fn from_str(s: &str) -> Result<Self, Self::Err> {
151        let mut bytes = [0u8; 38];
152
153        let size = bs58::decode(s)
154            .with_check(Some(Self::BASE58_CHECK_VERSION))
155            .into(&mut bytes)?;
156        if size != 34 {
157            return Err(bs58::decode::Error::BufferTooSmall.into());
158        }
159
160        Ok(Self::from_bytes(&bytes[2..34])?)
161    }
162}
163
164impl fmt::Display for AccountSecretKey {
165    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
166        // TODO: implement to_bytes for Keypair, and remove this ugly workaround
167        let hex = self.0.to_hex();
168        let mut bytes = hex::decode(hex).expect("to_hex should return hex string");
169        bytes.reverse();
170        bytes.insert(0, 1);
171        let s = bs58::encode(&bytes)
172            .with_check_version(Self::BASE58_CHECK_VERSION)
173            .into_string();
174        f.write_str(&s)
175    }
176}
177
178impl Serialize for AccountSecretKey {
179    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
180    where
181        S: serde::Serializer,
182    {
183        serializer.serialize_str(&self.to_string())
184    }
185}
186
187impl<'de> serde::Deserialize<'de> for AccountSecretKey {
188    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189    where
190        D: serde::Deserializer<'de>,
191    {
192        let b58: String = Deserialize::deserialize(deserializer)?;
193        b58.parse().map_err(serde::de::Error::custom)
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use std::env;
200
201    use super::*;
202
203    #[test]
204    fn test_account_secret_key_bs58check_decode() {
205        let parsed: AccountSecretKey = "EKFWgzXsoMYcP1Hnj7dBhsefxNucZ6wyz676Qg5uMFNzytXAi2Ww"
206            .parse()
207            .unwrap();
208        assert_eq!(
209            parsed.0.get_address(),
210            "B62qjVQLxt9nYMWGn45mkgwYfcz8e8jvjNCBo11VKJb7vxDNwv5QLPS"
211        );
212    }
213
214    #[test]
215    fn test_account_secret_key_display() {
216        let parsed: AccountSecretKey = "EKFWgzXsoMYcP1Hnj7dBhsefxNucZ6wyz676Qg5uMFNzytXAi2Ww"
217            .parse()
218            .unwrap();
219        assert_eq!(
220            &parsed.to_string(),
221            "EKFWgzXsoMYcP1Hnj7dBhsefxNucZ6wyz676Qg5uMFNzytXAi2Ww"
222        );
223    }
224
225    #[test]
226    fn test_encrypt_decrypt() {
227        let password = "not-very-secure-pass";
228
229        let new_key = AccountSecretKey::rand();
230        let tmp_dir = env::temp_dir();
231        let tmp_path = format!("{}/{}-key", tmp_dir.display(), new_key.public_key());
232
233        // dump encrypted file
234        new_key
235            .to_encrypted_file(&tmp_path, password)
236            .expect("Failed to encrypt secret key");
237
238        // load and decrypt
239        let decrypted = AccountSecretKey::from_encrypted_file(&tmp_path, password)
240            .expect("Failed to decrypt secret key file");
241
242        assert_eq!(
243            new_key.public_key(),
244            decrypted.public_key(),
245            "Encrypted and decrypted public keys do not match"
246        );
247    }
248
249    #[test]
250    fn test_ocaml_key_decrypt() {
251        let password = "not-very-secure-pass";
252        let key_path = "../tests/files/accounts/test-key-1";
253        let expected_public_key = "B62qmg7n4XqU3SFwx9KD9B7gxsKwxJP5GmxtBpHp1uxyN3grujii9a1";
254        let decrypted = AccountSecretKey::from_encrypted_file(key_path, password)
255            .expect("Failed to decrypt secret key file");
256
257        assert_eq!(
258            expected_public_key.to_string(),
259            decrypted.public_key().to_string()
260        )
261    }
262}