use hex::{FromHex, ToHex};
use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Deserialize, Serialize)]
pub struct PrivateKeys {
pub version: u32,
#[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
pub chain_key: Vec<u8>,
#[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
pub packet_key: Vec<u8>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct EthKeystore {
pub crypto: CryptoJson,
pub id: Uuid,
pub version: u8,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct CryptoJson {
pub cipher: String,
pub cipherparams: CipherparamsJson,
#[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
pub ciphertext: Vec<u8>,
pub kdf: KdfType,
pub kdfparams: KdfparamsType,
#[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
pub mac: Vec<u8>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct CipherparamsJson {
#[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
pub iv: Vec<u8>,
}
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum KdfType {
Pbkdf2,
Scrypt,
}
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum KdfparamsType {
Pbkdf2 {
c: u32,
dklen: u8,
prf: String,
#[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
salt: Vec<u8>,
},
Scrypt {
dklen: u8,
n: u32,
p: u32,
r: u32,
#[serde(serialize_with = "buffer_to_hex", deserialize_with = "hex_to_buffer")]
salt: Vec<u8>,
},
}
fn buffer_to_hex<T, S>(buffer: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: AsRef<[u8]>,
S: Serializer,
{
serializer.serialize_str(&buffer.encode_hex::<String>())
}
fn hex_to_buffer<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
String::deserialize(deserializer)
.and_then(|string| Vec::from_hex(string).map_err(|err| Error::custom(err.to_string())))
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "geth-compat")]
#[test]
fn deserialize_geth_compat_keystore() {
let data = r#"
{
"address": "00000398232e2064f896018496b4b44b3d62751f",
"crypto": {
"cipher": "aes-128-ctr",
"ciphertext": "4f784cd629a7caf34b488e36fb96aad8a8f943a6ce31c7deab950c5e3a5b1c43",
"cipherparams": {
"iv": "76f07196b3c94f25b8f34d869493f640"
},
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 262144,
"p": 1,
"r": 8,
"salt": "1e7be4ce8351dd1710b0885438414b1748a81f1af510eda11e4d1f99c8d43975"
},
"mac": "5b5433575a2418c1c813337a88b4099baa2f534e5dabeba86979d538c1f594d8"
},
"id": "6c4485f3-3cc0-4081-848e-8bf489f2c262",
"version": 3
}"#;
let keystore: EthKeystore = serde_json::from_str(data).unwrap();
assert_eq!(
keystore.address.as_bytes().to_vec(),
hex::decode("00000398232e2064f896018496b4b44b3d62751f").unwrap()
);
}
#[cfg(not(feature = "geth-compat"))]
#[test]
fn test_deserialize_pbkdf2() -> anyhow::Result<()> {
let data = r#"
{
"crypto" : {
"cipher" : "aes-128-ctr",
"cipherparams" : {
"iv" : "6087dab2f9fdbbfaddc31a909735c1e6"
},
"ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
"kdf" : "pbkdf2",
"kdfparams" : {
"c" : 262144,
"dklen" : 32,
"prf" : "hmac-sha256",
"salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
},
"mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"
},
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
"version" : 3
}"#;
let keystore: EthKeystore = serde_json::from_str(data)?;
assert_eq!(keystore.version, 3);
assert_eq!(keystore.id, Uuid::parse_str("3198bc9c-6672-5ab3-d995-4942343ae5b6")?);
assert_eq!(keystore.crypto.cipher, "aes-128-ctr");
assert_eq!(
keystore.crypto.cipherparams.iv,
Vec::from_hex("6087dab2f9fdbbfaddc31a909735c1e6")?
);
assert_eq!(
keystore.crypto.ciphertext,
Vec::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46")?
);
assert_eq!(keystore.crypto.kdf, KdfType::Pbkdf2);
assert_eq!(
keystore.crypto.kdfparams,
KdfparamsType::Pbkdf2 {
c: 262144,
dklen: 32,
prf: String::from("hmac-sha256"),
salt: Vec::from_hex("ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd")?,
}
);
assert_eq!(
keystore.crypto.mac,
Vec::from_hex("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2")?
);
Ok(())
}
#[cfg(not(feature = "geth-compat"))]
#[test]
fn test_deserialize_scrypt() -> anyhow::Result<()> {
let data = r#"
{
"crypto" : {
"cipher" : "aes-128-ctr",
"cipherparams" : {
"iv" : "83dbcc02d8ccb40e466191a123791e0e"
},
"ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
"kdf" : "scrypt",
"kdfparams" : {
"dklen" : 32,
"n" : 262144,
"p" : 8,
"r" : 1,
"salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
},
"mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
},
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
"version" : 3
}"#;
let keystore: EthKeystore = serde_json::from_str(data)?;
assert_eq!(keystore.version, 3);
assert_eq!(keystore.id, Uuid::parse_str("3198bc9c-6672-5ab3-d995-4942343ae5b6")?);
assert_eq!(keystore.crypto.cipher, "aes-128-ctr");
assert_eq!(
keystore.crypto.cipherparams.iv,
Vec::from_hex("83dbcc02d8ccb40e466191a123791e0e")?
);
assert_eq!(
keystore.crypto.ciphertext,
Vec::from_hex("d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c")?
);
assert_eq!(keystore.crypto.kdf, KdfType::Scrypt);
assert_eq!(
keystore.crypto.kdfparams,
KdfparamsType::Scrypt {
dklen: 32,
n: 262144,
p: 8,
r: 1,
salt: Vec::from_hex("ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19")?,
}
);
assert_eq!(
keystore.crypto.mac,
Vec::from_hex("2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097")?
);
Ok(())
}
}