openmina_node_account/
secret_key.rs1use 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 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 bytes.reverse();
67
68 Ok(Self(Keypair::from_bytes(&bytes[..])?))
69 }
70
71 pub fn to_bytes(&self) -> [u8; 32] {
72 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 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 new_key
235 .to_encrypted_file(&tmp_path, password)
236 .expect("Failed to encrypt secret key");
237
238 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}