utility_crypto/
key_file.rs1use crate::{PublicKey, SecretKey};
2use near_account_id::AccountId;
3use std::fs::File;
4use std::io;
5use std::io::{Read, Write};
6use std::path::Path;
7
8#[derive(serde::Serialize, serde::Deserialize)]
9pub struct KeyFile {
10 pub account_id: AccountId,
11 pub public_key: PublicKey,
12 #[serde(alias = "private_key")]
16 pub secret_key: SecretKey,
17}
18
19impl KeyFile {
20 pub fn write_to_file(&self, path: &Path) -> io::Result<()> {
21 let data = serde_json::to_string_pretty(self)?;
22 let mut file = Self::create(path)?;
23 file.write_all(data.as_bytes())
24 }
25
26 #[cfg(unix)]
27 fn create(path: &Path) -> io::Result<File> {
28 use std::os::unix::fs::OpenOptionsExt;
29 std::fs::File::options().mode(0o600).write(true).create(true).open(path)
30 }
31
32 #[cfg(not(unix))]
33 fn create(path: &Path) -> io::Result<File> {
34 std::fs::File::create(path)
35 }
36
37 pub fn from_file(path: &Path) -> io::Result<Self> {
38 let mut file = File::open(path)?;
39 let mut json_config_str = String::new();
40 file.read_to_string(&mut json_config_str)?;
41 let json_str_without_comments: String =
42 near_config_utils::strip_comments_from_json_str(&json_config_str)?;
43
44 Ok(serde_json::from_str(&json_str_without_comments)?)
45 }
46}
47
48#[cfg(test)]
49mod test {
50 use super::*;
51
52 const ACCOUNT_ID: &str = "example";
53 const SECRET_KEY: &str = "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr";
54 const KEY_FILE_CONTENTS: &str = r#"{
55 "account_id": "example",
56 "public_key": "ed25519:6DSjZ8mvsRZDvFqFxo8tCKePG96omXW7eVYVSySmDk8e",
57 "secret_key": "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr"
58}"#;
59
60 #[test]
61 fn test_to_file() {
62 let tmp = tempfile::TempDir::new().unwrap();
63 let path = tmp.path().join("key-file");
64
65 let account_id = ACCOUNT_ID.parse().unwrap();
66 let secret_key: SecretKey = SECRET_KEY.parse().unwrap();
67 let public_key = secret_key.public_key();
68 let key = KeyFile { account_id, public_key, secret_key };
69 key.write_to_file(&path).unwrap();
70
71 assert_eq!(KEY_FILE_CONTENTS, std::fs::read_to_string(&path).unwrap());
72
73 #[cfg(unix)]
74 {
75 use std::os::unix::fs::PermissionsExt;
76 let got = std::fs::metadata(&path).unwrap().permissions().mode();
77 assert_eq!(0o600, got & 0o777);
78 }
79 }
80
81 #[test]
82 fn test_from_file() {
83 fn load(contents: &[u8]) -> io::Result<()> {
84 let tmp = tempfile::NamedTempFile::new().unwrap();
85 tmp.as_file().write_all(contents).unwrap();
86 let result = KeyFile::from_file(tmp.path());
87 tmp.close().unwrap();
88
89 result.map(|key| {
90 assert_eq!(ACCOUNT_ID, key.account_id.to_string());
91 let secret_key: SecretKey = SECRET_KEY.parse().unwrap();
92 assert_eq!(secret_key, key.secret_key);
93 assert_eq!(secret_key.public_key(), key.public_key);
94 })
95 }
96
97 load(KEY_FILE_CONTENTS.as_bytes()).unwrap();
98
99 let contents = KEY_FILE_CONTENTS.replace("secret_key", "private_key");
101 load(contents.as_bytes()).unwrap();
102
103 let err = load(br#"{
105 "account_id": "example",
106 "public_key": "ed25519:6DSjZ8mvsRZDvFqFxo8tCKePG96omXW7eVYVSySmDk8e",
107 "secret_key": "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr",
108 "private_key": "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr"
109 }"#).unwrap_err();
110 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
111 let inner_msg = err.into_inner().unwrap().to_string();
112 assert!(inner_msg.contains("duplicate field"));
113 }
114}