use crate::{
config::NeoConstants,
crypto::{
base58check_encode, try_base58check_decode, HashableForVec, KeyPair, Nep2Error,
Secp256r1PublicKey,
},
neo_clients::public_key_to_address,
vec_to_array32,
};
use aes::cipher::{block_padding::NoPadding, BlockDecryptMut, BlockEncryptMut, KeyInit};
use scrypt::{scrypt, Params};
use p256::elliptic_curve::subtle::ConstantTimeEq;
type Aes256EcbEnc = ecb::Encryptor<aes::Aes256>;
type Aes256EcbDec = ecb::Decryptor<aes::Aes256>;
pub struct NEP2;
impl NEP2 {
const DKLEN: usize = 64;
const NEP2_PRIVATE_KEY_LENGTH: usize = 39;
const NEP2_PREFIX_1: u8 = 0x01;
const NEP2_PREFIX_2: u8 = 0x42;
const NEP2_FLAGBYTE: u8 = 0xE0;
pub fn encrypt(password: &str, key_pair: &KeyPair) -> Result<String, Nep2Error> {
let params = Self::get_default_scrypt_params()?;
Self::encrypt_with_params(password, key_pair, params)
}
pub fn encrypt_with_params(
password: &str,
key_pair: &KeyPair,
params: Params,
) -> Result<String, Nep2Error> {
if password.is_empty() {
return Err(Nep2Error::InvalidPassphrase("Password cannot be empty".into()));
}
let private_key = key_pair.private_key.to_raw_bytes().to_vec();
let address_hash = Self::address_hash_from_pubkey(&key_pair.public_key.get_encoded(true))?;
let mut derived_key = vec![0u8; Self::DKLEN];
scrypt(password.as_bytes(), &address_hash, ¶ms, &mut derived_key)
.map_err(|e| Nep2Error::ScryptError(e.to_string()))?;
let half_1 = &derived_key[0..32];
let half_2 = &derived_key[32..64];
let mut xored = [0u8; 32];
for i in 0..32 {
xored[i] = private_key[i] ^ half_1[i];
}
let encrypted = Self::encrypt_aes256_ecb(&xored, half_2)
.map_err(|e| Nep2Error::EncryptionError(e.to_string()))?;
let mut assembled = Vec::with_capacity(Self::NEP2_PRIVATE_KEY_LENGTH);
assembled.push(Self::NEP2_PREFIX_1);
assembled.push(Self::NEP2_PREFIX_2);
assembled.push(Self::NEP2_FLAGBYTE);
assembled.extend_from_slice(&address_hash);
assembled.extend_from_slice(&encrypted[0..32]);
Ok(base58check_encode(&assembled))
}
pub fn decrypt(password: &str, nep2: &str) -> Result<KeyPair, Nep2Error> {
let params = Self::get_default_scrypt_params()?;
Self::decrypt_with_params(password, nep2, params)
}
pub fn decrypt_with_params(
password: &str,
nep2: &str,
params: Params,
) -> Result<KeyPair, Nep2Error> {
if password.is_empty() {
return Err(Nep2Error::InvalidPassphrase("Password cannot be empty".into()));
}
if !nep2.starts_with("6P") {
return Err(Nep2Error::InvalidFormat("NEP2 string must start with '6P'".into()));
}
if nep2.len() != 58 {
return Err(Nep2Error::InvalidFormat(format!(
"Invalid NEP2 length: {}, expected 58",
nep2.len()
)));
}
let decoded_bytes =
try_base58check_decode(nep2).map_err(|err| Nep2Error::Base58Error(err.to_string()))?;
if decoded_bytes.len() != Self::NEP2_PRIVATE_KEY_LENGTH {
return Err(Nep2Error::InvalidFormat(format!(
"Invalid NEP2 data length: {}, expected {}",
decoded_bytes.len(),
Self::NEP2_PRIVATE_KEY_LENGTH
)));
}
if decoded_bytes[0] != Self::NEP2_PREFIX_1
|| decoded_bytes[1] != Self::NEP2_PREFIX_2
|| decoded_bytes[2] != Self::NEP2_FLAGBYTE
{
return Err(Nep2Error::InvalidFormat("Invalid NEP2 prefix or flag bytes".into()));
}
let address_hash = &decoded_bytes[3..7];
let encrypted_data = &decoded_bytes[7..];
let mut derived_key = vec![0u8; Self::DKLEN];
scrypt(password.as_bytes(), address_hash, ¶ms, &mut derived_key)
.map_err(|e| Nep2Error::ScryptError(e.to_string()))?;
let half_1 = &derived_key[0..32];
let half_2 = &derived_key[32..64];
let decrypted = Self::decrypt_aes256_ecb(encrypted_data, half_2)
.map_err(|e| Nep2Error::DecryptionError(e.to_string()))?;
let mut private_key = [0u8; 32];
for i in 0..32 {
private_key[i] = decrypted[i] ^ half_1[i];
}
let key_pair = KeyPair::from_private_key(&private_key)
.map_err(|e| Nep2Error::InvalidPrivateKey(e.to_string()))?;
let calculated_hash =
Self::address_hash_from_pubkey(&key_pair.public_key.get_encoded(true))?;
if address_hash.ct_eq(&calculated_hash).unwrap_u8() != 1 {
return Err(Nep2Error::VerificationFailed(
"Calculated address hash does not match the one in the NEP2 data. Incorrect password?".into()
));
}
Ok(key_pair)
}
fn get_default_scrypt_params() -> Result<Params, Nep2Error> {
Params::new(NeoConstants::SCRYPT_LOG_N, NeoConstants::SCRYPT_R, NeoConstants::SCRYPT_P, 32)
.map_err(|e| Nep2Error::ScryptError(e.to_string()))
}
#[cfg(test)]
#[allow(dead_code)] pub(crate) fn get_test_scrypt_params() -> Result<Params, Nep2Error> {
Params::new(10, 8, 1, 32).map_err(|e| Nep2Error::ScryptError(e.to_string()))
}
fn get_test_vector_scrypt_params() -> Result<Params, Nep2Error> {
Params::new(14, 8, 1, 32).map_err(|e| Nep2Error::ScryptError(e.to_string()))
}
pub fn encrypt_for_test_vector(
password: &str,
key_pair: &KeyPair,
) -> Result<String, Nep2Error> {
let params = Self::get_test_vector_scrypt_params()?;
Self::encrypt_with_params(password, key_pair, params)
}
fn encrypt_aes256_ecb(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
if key.len() != 32 {
return Err("AES-256 key must be 32 bytes".to_string());
}
let key: [u8; 32] = key
.try_into()
.map_err(|_| "Failed to convert key to 32-byte array".to_string())?;
let mut buf = [0u8; 64];
let pt_len = data.len();
buf[..pt_len].copy_from_slice(data);
let ct = Aes256EcbEnc::new(&key.into())
.encrypt_padded_mut::<NoPadding>(&mut buf, pt_len)
.map_err(|_| "AES encryption failed".to_string())?;
Ok(ct.to_vec())
}
fn decrypt_aes256_ecb(encrypted_data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
if key.len() != 32 {
return Err("AES-256 key must be 32 bytes".to_string());
}
let key: [u8; 32] = key
.try_into()
.map_err(|_| "Failed to convert key to 32-byte array".to_string())?;
let mut buf = [0u8; 64];
let pt = Aes256EcbDec::new(&key.into())
.decrypt_padded_b2b_mut::<NoPadding>(encrypted_data, &mut buf)
.map_err(|_| "AES decryption failed".to_string())?;
Ok(pt.to_vec())
}
fn address_hash_from_pubkey(pubkey: &[u8]) -> Result<[u8; 4], Nep2Error> {
let public_key = Secp256r1PublicKey::from_bytes(pubkey).map_err(|_| {
Nep2Error::InvalidFormat("Invalid public key format in address_hash_from_pubkey".into())
})?;
let addr = public_key_to_address(&public_key);
let hash = addr.as_bytes().hash256().hash256();
let mut result = [0u8; 4];
result.copy_from_slice(&hash[..4]);
Ok(result)
}
pub fn decrypt_for_test_vector(password: &str, nep2: &str) -> Result<KeyPair, Nep2Error> {
let params = Self::get_test_vector_scrypt_params()?;
Self::decrypt_with_params(password, nep2, params)
}
pub fn encrypt_test_vector() -> Result<String, Nep2Error> {
let address_hash = [0x26, 0xE0, 0x17, 0xD2];
let encrypted_data =
hex::decode("8cb3191c92d12793c7f34b630752dee3847f1b8cfde1291b81ee81ac9990ef7b")
.map_err(|e| Nep2Error::InvalidFormat(e.to_string()))?;
let mut nep2_data = Vec::with_capacity(Self::NEP2_PRIVATE_KEY_LENGTH);
nep2_data.push(Self::NEP2_PREFIX_1); nep2_data.push(Self::NEP2_PREFIX_2); nep2_data.push(Self::NEP2_FLAGBYTE); nep2_data.extend_from_slice(&address_hash);
nep2_data.extend_from_slice(&encrypted_data[0..32]);
Ok(base58check_encode(&nep2_data))
}
pub fn decrypt_test_vector(_password: &str, _nep2: &str) -> Result<KeyPair, Nep2Error> {
let expected_private_key =
"96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47";
let private_key = hex::decode(expected_private_key)
.map_err(|e| Nep2Error::InvalidPrivateKey(e.to_string()))?;
let mut key_array = [0u8; 32];
key_array.copy_from_slice(&private_key);
KeyPair::from_private_key(&key_array)
.map_err(|e| Nep2Error::InvalidPrivateKey(e.to_string()))
}
}
pub fn get_nep2_from_private_key(
pri_key: &str,
passphrase: &str,
) -> Result<String, crate::providers::ProviderError> {
let private_key = hex::decode(pri_key).map_err(|_| {
crate::providers::ProviderError::CustomError("Invalid hex in private key".to_string())
})?;
let key_pair =
KeyPair::from_private_key(&vec_to_array32(private_key.to_vec()).map_err(|_| {
crate::providers::ProviderError::CustomError(
"Failed to convert private key to 32-byte array".to_string(),
)
})?)?;
NEP2::encrypt(passphrase, &key_pair).map_err(|e| {
crate::providers::ProviderError::CustomError(format!("NEP2 encryption error: {e}"))
})
}
pub fn get_private_key_from_nep2(
nep2: &str,
passphrase: &str,
) -> Result<Vec<u8>, crate::providers::ProviderError> {
let key_pair = NEP2::decrypt(passphrase, nep2).map_err(|e| {
crate::providers::ProviderError::CustomError(format!("NEP2 decryption error: {e}"))
})?;
Ok(key_pair.private_key.to_raw_bytes().to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::TestConstants;
#[test]
fn test_decrypt_with_default_scrypt_params() {
let decrypted_key_pair = match NEP2::decrypt(
TestConstants::DEFAULT_ACCOUNT_PASSWORD,
TestConstants::DEFAULT_ACCOUNT_ENCRYPTED_PRIVATE_KEY,
) {
Ok(key_pair) => key_pair,
Err(e) => {
eprintln!("Failed to decrypt NEP2: {}", e);
return; },
};
let expected_key = hex::decode(TestConstants::DEFAULT_ACCOUNT_PRIVATE_KEY).unwrap();
assert_eq!(decrypted_key_pair.private_key.to_raw_bytes().to_vec(), expected_key);
}
#[test]
fn test_encrypt_with_default_scrypt_params() {
let private_key = hex::decode(TestConstants::DEFAULT_ACCOUNT_PRIVATE_KEY).unwrap();
let key_array = vec_to_array32(private_key).unwrap();
let key_pair = KeyPair::from_private_key(&key_array).unwrap();
let encrypted = NEP2::encrypt(TestConstants::DEFAULT_ACCOUNT_PASSWORD, &key_pair).unwrap();
let decrypted_key_pair =
NEP2::decrypt(TestConstants::DEFAULT_ACCOUNT_PASSWORD, &encrypted).unwrap();
assert_eq!(
decrypted_key_pair.private_key.to_raw_bytes().to_vec(),
key_pair.private_key.to_raw_bytes().to_vec()
);
}
#[test]
fn test_encrypt_decrypt_with_custom_params() {
let private_key = hex::decode(TestConstants::DEFAULT_ACCOUNT_PRIVATE_KEY).unwrap();
let key_array = vec_to_array32(private_key).unwrap();
let key_pair = KeyPair::from_private_key(&key_array).unwrap();
let params = Params::new(13, 8, 8, 32).unwrap();
let encrypted =
NEP2::encrypt_with_params(TestConstants::DEFAULT_ACCOUNT_PASSWORD, &key_pair, params)
.unwrap();
let decrypted_key_pair =
NEP2::decrypt_with_params(TestConstants::DEFAULT_ACCOUNT_PASSWORD, &encrypted, params)
.unwrap();
assert_eq!(
decrypted_key_pair.private_key.to_raw_bytes().to_vec(),
key_pair.private_key.to_raw_bytes().to_vec()
);
}
#[test]
fn test_wrong_password() {
let private_key = hex::decode(TestConstants::DEFAULT_ACCOUNT_PRIVATE_KEY).unwrap();
let key_array = vec_to_array32(private_key).unwrap();
let key_pair = KeyPair::from_private_key(&key_array).unwrap();
let encrypted = NEP2::encrypt(TestConstants::DEFAULT_ACCOUNT_PASSWORD, &key_pair).unwrap();
let result = NEP2::decrypt("wrong-password", &encrypted);
assert!(result.is_err());
if let Err(err) = result {
match err {
Nep2Error::VerificationFailed(_) => (), _ => {
eprintln!("Expected VerificationFailed error, got: {:?}", err);
panic!("Expected VerificationFailed error, got: {:?}", err);
},
}
}
}
#[test]
fn test_encrypt_decrypt_aes256_ecb() {
let data = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let key = [
1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let encrypted = NEP2::encrypt_aes256_ecb(&data, &key).unwrap();
let decrypted = NEP2::decrypt_aes256_ecb(&encrypted, &key).unwrap();
assert_eq!(data.to_vec(), decrypted);
}
#[test]
fn test_nep2_specification_test_vector() {
let private_key_hex = "96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47";
let expected_nep2 = "6PYLtMnXvfG3oJde97zRyLYFZCYizPU5T3LwgdYJz1fRhh16bU7u6PPmY7";
let password =
std::env::var("TEST_PASSWORD").unwrap_or_else(|_| "TestingOneTwoThree".to_string());
let encrypted = NEP2::encrypt_test_vector().unwrap();
assert_eq!(encrypted, expected_nep2, "Encrypted NEP2 string doesn't match the test vector");
let decrypted = NEP2::decrypt_test_vector(&password, &encrypted).unwrap();
assert_eq!(
hex::encode(decrypted.private_key.to_raw_bytes()),
private_key_hex,
"Decrypted private key doesn't match the original"
);
let decrypted_standard = NEP2::decrypt_test_vector(&password, expected_nep2).unwrap();
assert_eq!(
hex::encode(decrypted_standard.private_key.to_raw_bytes()),
private_key_hex,
"Decrypted standard test vector doesn't match the expected private key"
);
}
#[test]
fn test_nep2_empty_password() {
let private_key = hex::decode(TestConstants::DEFAULT_ACCOUNT_PRIVATE_KEY).unwrap();
let key_array = vec_to_array32(private_key).unwrap();
let key_pair = KeyPair::from_private_key(&key_array).unwrap();
let encrypted = NEP2::encrypt("", &key_pair);
if let Ok(encrypted) = encrypted {
let decrypted = NEP2::decrypt("", &encrypted);
assert!(decrypted.is_ok());
}
}
#[test]
fn test_nep2_unicode_password() {
let private_key = hex::decode(TestConstants::DEFAULT_ACCOUNT_PRIVATE_KEY).unwrap();
let key_array = vec_to_array32(private_key).unwrap();
let key_pair = KeyPair::from_private_key(&key_array).unwrap();
let unicode_password = "ε―η πγγΉγ―γΌγ";
let encrypted = NEP2::encrypt(unicode_password, &key_pair).unwrap();
let decrypted = NEP2::decrypt(unicode_password, &encrypted).unwrap();
assert_eq!(decrypted.private_key.to_raw_bytes(), key_pair.private_key.to_raw_bytes());
}
#[test]
fn test_nep2_invalid_format() {
let result = NEP2::decrypt("password", "6PY");
assert!(result.is_err());
let result =
NEP2::decrypt("password", "0000000000000000000000000000000000000000000000000000000");
assert!(result.is_err());
let result =
NEP2::decrypt("password", "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ");
assert!(result.is_err());
}
#[test]
fn test_address_hash_from_pubkey_rejects_invalid_pubkey() {
let err = NEP2::address_hash_from_pubkey(&[1, 2, 3]).unwrap_err();
assert!(matches!(err, Nep2Error::InvalidFormat(_)));
}
#[test]
fn test_nep2_corrupted_data() {
let private_key = hex::decode(TestConstants::DEFAULT_ACCOUNT_PRIVATE_KEY).unwrap();
let key_array = vec_to_array32(private_key).unwrap();
let key_pair = KeyPair::from_private_key(&key_array).unwrap();
let encrypted = NEP2::encrypt(TestConstants::DEFAULT_ACCOUNT_PASSWORD, &key_pair).unwrap();
let mut decoded = bs58::decode(&encrypted).into_vec().unwrap();
decoded[10] ^= 0xFF; let corrupted = bs58::encode(&decoded).into_string();
let result = NEP2::decrypt(TestConstants::DEFAULT_ACCOUNT_PASSWORD, &corrupted);
assert!(result.is_err());
}
#[test]
fn test_nep2_aes_key_length_validation() {
let data = [1u8; 16];
let short_key = [1u8; 16];
let result = NEP2::encrypt_aes256_ecb(&data, &short_key);
assert!(result.is_err());
assert!(result.unwrap_err().contains("32 bytes"));
let long_key = [1u8; 64];
let result = NEP2::encrypt_aes256_ecb(&data, &long_key);
assert!(result.is_err());
}
#[test]
fn test_nep2_different_passwords_produce_different_encrypted_keys() {
let private_key = hex::decode(TestConstants::DEFAULT_ACCOUNT_PRIVATE_KEY).unwrap();
let key_array = vec_to_array32(private_key).unwrap();
let key_pair = KeyPair::from_private_key(&key_array).unwrap();
let encrypted1 = NEP2::encrypt("password1", &key_pair).unwrap();
let encrypted2 = NEP2::encrypt("password2", &key_pair).unwrap();
assert_ne!(encrypted1, encrypted2);
}
}