1use hex::{FromHex, ToHex};
2use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize};
3use uuid::Uuid;
4
5#[cfg(feature = "geth-compat")]
6use ethereum_types::H160 as Address;
7
8#[derive(Debug, Deserialize, Serialize)]
9pub struct EthKeystore {
12 #[cfg(feature = "geth-compat")]
13 pub address: Address,
14
15 pub crypto: CryptoJson,
16 pub id: Uuid,
17 pub version: u8,
18}
19
20#[derive(Debug, Deserialize, Serialize)]
21pub struct CryptoJson {
23 pub cipher: String,
24 pub cipherparams: CipherparamsJson,
25 #[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
26 pub ciphertext: Vec<u8>,
27 pub kdf: KdfType,
28 pub kdfparams: KdfparamsType,
29 #[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
30 pub mac: Vec<u8>,
31}
32
33#[derive(Debug, Deserialize, Serialize)]
34pub struct CipherparamsJson {
36 #[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
37 pub iv: Vec<u8>,
38}
39
40#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
41#[serde(rename_all = "lowercase")]
42pub enum KdfType {
44 Pbkdf2,
45 Scrypt,
46}
47
48#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
49#[serde(untagged)]
50pub enum KdfparamsType {
52 Pbkdf2 {
53 c: u32,
54 dklen: u8,
55 prf: String,
56 #[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
57 salt: Vec<u8>,
58 },
59 Scrypt {
60 dklen: u8,
61 n: u32,
62 p: u32,
63 r: u32,
64 #[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
65 salt: Vec<u8>,
66 },
67}
68
69fn buffer_to_hex<T, S>(buffer: &T, serializer: S) -> Result<S::Ok, S::Error>
70where
71 T: AsRef<[u8]>,
72 S: Serializer,
73{
74 serializer.serialize_str(&buffer.encode_hex::<String>())
75}
76
77fn hex_to_buffer<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
78where
79 D: Deserializer<'de>,
80{
81 use serde::de::Error;
82 String::deserialize(deserializer)
83 .and_then(|string| Vec::from_hex(&string).map_err(|err| Error::custom(err.to_string())))
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[cfg(feature = "geth-compat")]
91 #[test]
92 fn deserialize_geth_compat_keystore() {
93 let data = r#"
94 {
95 "address": "00000398232e2064f896018496b4b44b3d62751f",
96 "crypto": {
97 "cipher": "aes-128-ctr",
98 "ciphertext": "4f784cd629a7caf34b488e36fb96aad8a8f943a6ce31c7deab950c5e3a5b1c43",
99 "cipherparams": {
100 "iv": "76f07196b3c94f25b8f34d869493f640"
101 },
102 "kdf": "scrypt",
103 "kdfparams": {
104 "dklen": 32,
105 "n": 262144,
106 "p": 1,
107 "r": 8,
108 "salt": "1e7be4ce8351dd1710b0885438414b1748a81f1af510eda11e4d1f99c8d43975"
109 },
110 "mac": "5b5433575a2418c1c813337a88b4099baa2f534e5dabeba86979d538c1f594d8"
111 },
112 "id": "6c4485f3-3cc0-4081-848e-8bf489f2c262",
113 "version": 3
114 }"#;
115 let keystore: EthKeystore = serde_json::from_str(data).unwrap();
116 assert_eq!(
117 keystore.address.as_bytes().to_vec(),
118 hex::decode("00000398232e2064f896018496b4b44b3d62751f").unwrap()
119 );
120 }
121
122 #[cfg(not(feature = "geth-compat"))]
123 #[test]
124 fn test_deserialize_pbkdf2() {
125 let data = r#"
126 {
127 "crypto" : {
128 "cipher" : "aes-128-ctr",
129 "cipherparams" : {
130 "iv" : "6087dab2f9fdbbfaddc31a909735c1e6"
131 },
132 "ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
133 "kdf" : "pbkdf2",
134 "kdfparams" : {
135 "c" : 262144,
136 "dklen" : 32,
137 "prf" : "hmac-sha256",
138 "salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
139 },
140 "mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"
141 },
142 "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
143 "version" : 3
144 }"#;
145 let keystore: EthKeystore = serde_json::from_str(data).unwrap();
146 assert_eq!(keystore.version, 3);
147 assert_eq!(
148 keystore.id,
149 Uuid::parse_str("3198bc9c-6672-5ab3-d995-4942343ae5b6").unwrap()
150 );
151 assert_eq!(keystore.crypto.cipher, "aes-128-ctr");
152 assert_eq!(
153 keystore.crypto.cipherparams.iv,
154 Vec::from_hex("6087dab2f9fdbbfaddc31a909735c1e6").unwrap()
155 );
156 assert_eq!(
157 keystore.crypto.ciphertext,
158 Vec::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46")
159 .unwrap()
160 );
161 assert_eq!(keystore.crypto.kdf, KdfType::Pbkdf2);
162 assert_eq!(
163 keystore.crypto.kdfparams,
164 KdfparamsType::Pbkdf2 {
165 c: 262144,
166 dklen: 32,
167 prf: String::from("hmac-sha256"),
168 salt: Vec::from_hex(
169 "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
170 )
171 .unwrap(),
172 }
173 );
174 assert_eq!(
175 keystore.crypto.mac,
176 Vec::from_hex("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2")
177 .unwrap()
178 );
179 }
180
181 #[cfg(not(feature = "geth-compat"))]
182 #[test]
183 fn test_deserialize_scrypt() {
184 let data = r#"
185 {
186 "crypto" : {
187 "cipher" : "aes-128-ctr",
188 "cipherparams" : {
189 "iv" : "83dbcc02d8ccb40e466191a123791e0e"
190 },
191 "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
192 "kdf" : "scrypt",
193 "kdfparams" : {
194 "dklen" : 32,
195 "n" : 262144,
196 "p" : 8,
197 "r" : 1,
198 "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
199 },
200 "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
201 },
202 "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
203 "version" : 3
204 }"#;
205 let keystore: EthKeystore = serde_json::from_str(data).unwrap();
206 assert_eq!(keystore.version, 3);
207 assert_eq!(
208 keystore.id,
209 Uuid::parse_str("3198bc9c-6672-5ab3-d995-4942343ae5b6").unwrap()
210 );
211 assert_eq!(keystore.crypto.cipher, "aes-128-ctr");
212 assert_eq!(
213 keystore.crypto.cipherparams.iv,
214 Vec::from_hex("83dbcc02d8ccb40e466191a123791e0e").unwrap()
215 );
216 assert_eq!(
217 keystore.crypto.ciphertext,
218 Vec::from_hex("d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c")
219 .unwrap()
220 );
221 assert_eq!(keystore.crypto.kdf, KdfType::Scrypt);
222 assert_eq!(
223 keystore.crypto.kdfparams,
224 KdfparamsType::Scrypt {
225 dklen: 32,
226 n: 262144,
227 p: 8,
228 r: 1,
229 salt: Vec::from_hex(
230 "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
231 )
232 .unwrap(),
233 }
234 );
235 assert_eq!(
236 keystore.crypto.mac,
237 Vec::from_hex("2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097")
238 .unwrap()
239 );
240 }
241}