use crate::{Error, Id, PublicKey, Signature};
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
use base64::Engine;
use bech32::{FromBase32, ToBase32};
use chacha20poly1305::{
aead::{Aead, AeadCore, KeyInit, Payload},
XChaCha20Poly1305,
};
use derive_more::Display;
use hmac::Hmac;
use k256::ecdh::SharedSecret;
use k256::ecdsa::signature::Signer;
use k256::schnorr::signature::hazmat::PrehashSigner;
use k256::schnorr::SigningKey;
use pbkdf2::pbkdf2;
use rand_core::{OsRng, RngCore};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use std::convert::TryFrom;
use std::ops::Deref;
use zeroize::Zeroize;
const V1_CHECK_VALUE: [u8; 11] = [15, 91, 241, 148, 90, 143, 101, 12, 172, 255, 103];
const V1_HMAC_ROUNDS: u32 = 100_000;
#[derive(Clone, Debug, Display, Serialize, Deserialize)]
pub struct EncryptedPrivateKey(pub String);
impl Deref for EncryptedPrivateKey {
type Target = String;
fn deref(&self) -> &String {
&self.0
}
}
impl EncryptedPrivateKey {
pub fn decrypt(&self, password: &str) -> Result<PrivateKey, Error> {
PrivateKey::import_encrypted(self, password)
}
pub fn version(&self) -> Result<i8, Error> {
if self.0.starts_with("ncryptsec1") {
let data = bech32::decode(&self.0)?;
if data.0 != "ncryptsec" {
return Err(Error::WrongBech32("ncryptsec".to_string(), data.0));
}
let data = Vec::<u8>::from_base32(&data.1)?;
Ok(data[0] as i8)
} else if self.0.len() == 64 {
Ok(-1)
} else {
Ok(0) }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum KeySecurity {
Weak = 0,
Medium = 1,
}
impl TryFrom<u8> for KeySecurity {
type Error = Error;
fn try_from(i: u8) -> Result<KeySecurity, Error> {
if i == 0 {
Ok(KeySecurity::Weak)
} else if i == 1 {
Ok(KeySecurity::Medium)
} else {
Err(Error::UnknownKeySecurity(i))
}
}
}
#[allow(missing_debug_implementations)]
pub struct PrivateKey(SigningKey, KeySecurity);
impl PrivateKey {
pub fn generate() -> PrivateKey {
let signing_key = SigningKey::random(&mut OsRng);
PrivateKey(signing_key, KeySecurity::Medium)
}
pub fn public_key(&self) -> PublicKey {
PublicKey(self.0.verifying_key().to_owned())
}
pub fn key_security(&self) -> KeySecurity {
self.1
}
pub fn as_hex_string(&mut self) -> String {
self.1 = KeySecurity::Weak;
hex::encode(self.0.to_bytes())
}
pub fn try_from_hex_string(v: &str) -> Result<PrivateKey, Error> {
let vec: Vec<u8> = hex::decode(v)?;
Ok(PrivateKey(SigningKey::from_bytes(&vec)?, KeySecurity::Weak))
}
pub fn try_as_bech32_string(&mut self) -> Result<String, Error> {
self.1 = KeySecurity::Weak;
Ok(bech32::encode(
"nsec",
self.0.to_bytes().to_vec().to_base32(),
bech32::Variant::Bech32,
)?)
}
pub fn try_from_bech32_string(s: &str) -> Result<PrivateKey, Error> {
let data = bech32::decode(s)?;
if data.0 != "nsec" {
Err(Error::WrongBech32("nsec".to_string(), data.0))
} else {
let decoded = Vec::<u8>::from_base32(&data.1)?;
Ok(PrivateKey(
SigningKey::from_bytes(&decoded)?,
KeySecurity::Weak,
))
}
}
pub fn sign_id(&self, id: Id) -> Result<Signature, Error> {
let signature = self.0.sign_prehash(&id.0)?;
Ok(Signature(signature))
}
pub fn sign(&self, message: &[u8]) -> Result<Signature, Error> {
let signature = self.0.try_sign(message)?;
Ok(Signature(signature))
}
fn shared_secret(&self, other: &PublicKey) -> SharedSecret {
k256::ecdh::diffie_hellman(self.0.as_nonzero_scalar(), other.0.as_affine())
}
pub fn nip04_encrypt(
&self,
other: &PublicKey,
plaintext: &[u8],
) -> Result<([u8; 16], Vec<u8>), Error> {
let shared_secret = self.shared_secret(other);
let raw_shared_secret_bytes = shared_secret.raw_secret_bytes();
let iv = {
let mut iv: [u8; 16] = [0; 16];
OsRng.fill_bytes(&mut iv);
iv
};
let ciphertext = cbc::Encryptor::<aes::Aes256>::new(raw_shared_secret_bytes, &iv.into())
.encrypt_padded_vec_mut::<Pkcs7>(plaintext);
Ok((iv, ciphertext))
}
pub fn nip04_decrypt(
&self,
other: &PublicKey,
ciphertext: &[u8],
iv: [u8; 16],
) -> Result<Vec<u8>, Error> {
let shared_secret = self.shared_secret(other);
let raw_shared_secret_bytes = shared_secret.raw_secret_bytes();
Ok(
cbc::Decryptor::<aes::Aes256>::new(raw_shared_secret_bytes, &iv.into())
.decrypt_padded_vec_mut::<Pkcs7>(ciphertext)?,
)
}
pub fn export_encrypted(
&self,
password: &str,
log2_rounds: u8,
) -> Result<EncryptedPrivateKey, Error> {
let salt = {
let mut salt: [u8; 16] = [0; 16];
OsRng.fill_bytes(&mut salt);
salt
};
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
let associated_data: Vec<u8> = {
let key_security: u8 = match self.1 {
KeySecurity::Weak => 0,
KeySecurity::Medium => 1,
};
vec![key_security]
};
let ciphertext = {
let cipher = {
let symmetric_key = Self::password_to_key_v2(password, &salt, log2_rounds)?;
XChaCha20Poly1305::new((&symmetric_key).into())
};
let mut inner_secret: Vec<u8> = self.0.to_bytes().to_vec();
let payload = Payload {
msg: &inner_secret,
aad: &associated_data,
};
let ciphertext = match cipher.encrypt(&nonce, payload) {
Ok(c) => c,
Err(_) => return Err(Error::Encryption),
};
inner_secret.zeroize();
ciphertext
};
let mut concatenation: Vec<u8> = Vec::new();
concatenation.push(0x2); concatenation.push(log2_rounds); concatenation.extend(salt); concatenation.extend(nonce); concatenation.extend(associated_data); concatenation.extend(ciphertext);
Ok(EncryptedPrivateKey(bech32::encode(
"ncryptsec",
concatenation.to_base32(),
bech32::Variant::Bech32,
)?))
}
pub fn import_encrypted(
encrypted: &EncryptedPrivateKey,
password: &str,
) -> Result<PrivateKey, Error> {
if encrypted.0.starts_with("ncryptsec1") {
Self::import_encrypted_bech32(encrypted, password)
} else {
Self::import_encrypted_base64(encrypted, password)
}
}
fn import_encrypted_bech32(
encrypted: &EncryptedPrivateKey,
password: &str,
) -> Result<PrivateKey, Error> {
let data = bech32::decode(&encrypted.0)?;
if data.0 != "ncryptsec" {
return Err(Error::WrongBech32("ncryptsec".to_string(), data.0));
}
let data = Vec::<u8>::from_base32(&data.1)?;
match data[0] {
1 => Self::import_encrypted_v1(data, password),
2 => Self::import_encrypted_v2(data, password),
_ => Err(Error::InvalidEncryptedPrivateKey),
}
}
fn import_encrypted_v2(concatenation: Vec<u8>, password: &str) -> Result<PrivateKey, Error> {
if concatenation.len() < 91 {
return Err(Error::InvalidEncryptedPrivateKey);
}
let version: u8 = concatenation[0];
assert_eq!(version, 2);
let log2_rounds: u8 = concatenation[1];
let salt: [u8; 16] = concatenation[2..2 + 16].try_into()?;
let nonce = &concatenation[2 + 16..2 + 16 + 24];
let associated_data = &concatenation[2 + 16 + 24..2 + 16 + 24 + 1];
let ciphertext = &concatenation[2 + 16 + 24 + 1..];
let cipher = {
let symmetric_key = Self::password_to_key_v2(password, &salt, log2_rounds)?;
XChaCha20Poly1305::new((&symmetric_key).into())
};
let payload = Payload {
msg: ciphertext,
aad: associated_data,
};
let mut inner_secret = match cipher.decrypt(nonce.into(), payload) {
Ok(is) => is,
Err(_) => return Err(Error::Encryption),
};
if associated_data.is_empty() {
return Err(Error::InvalidEncryptedPrivateKey);
}
let key_security = match associated_data[0] {
0 => KeySecurity::Weak,
1 => KeySecurity::Medium,
_ => return Err(Error::InvalidEncryptedPrivateKey),
};
let signing_key = SigningKey::from_bytes(&inner_secret)?;
inner_secret.zeroize();
Ok(PrivateKey(signing_key, key_security))
}
fn import_encrypted_base64(
encrypted: &EncryptedPrivateKey,
password: &str,
) -> Result<PrivateKey, Error> {
let concatenation = base64::engine::general_purpose::STANDARD.decode(&encrypted.0)?; if concatenation.len() == 64 {
Self::import_encrypted_pre_v1(concatenation, password)
} else if concatenation.len() == 80 {
Self::import_encrypted_v1(concatenation, password)
} else {
Err(Error::InvalidEncryptedPrivateKey)
}
}
fn import_encrypted_v1(concatenation: Vec<u8>, password: &str) -> Result<PrivateKey, Error> {
let salt: [u8; 16] = concatenation[..16].try_into()?;
let iv: [u8; 16] = concatenation[16..32].try_into()?;
let ciphertext = &concatenation[32..];
let key = Self::password_to_key_v1(password, &salt, V1_HMAC_ROUNDS)?;
let mut plaintext = cbc::Decryptor::<aes::Aes256>::new(&key.into(), &iv.into())
.decrypt_padded_vec_mut::<Pkcs7>(ciphertext)?; if plaintext.len() != 44 {
return Err(Error::InvalidEncryptedPrivateKey);
}
if plaintext[plaintext.len() - 12..plaintext.len() - 1] != V1_CHECK_VALUE {
return Err(Error::WrongDecryptionPassword);
}
let ks = KeySecurity::try_from(plaintext[plaintext.len() - 1])?;
let output = PrivateKey(
SigningKey::from_bytes(&plaintext[..plaintext.len() - 12])?,
ks,
);
plaintext.zeroize();
Ok(output)
}
fn import_encrypted_pre_v1(
iv_plus_ciphertext: Vec<u8>,
password: &str,
) -> Result<PrivateKey, Error> {
let key = Self::password_to_key_v1(password, b"nostr", 4096)?;
if iv_plus_ciphertext.len() < 48 {
return Err(Error::InvalidEncryptedPrivateKey);
}
let iv: [u8; 16] = iv_plus_ciphertext[..16].try_into()?;
let ciphertext = &iv_plus_ciphertext[16..];
let mut pt = cbc::Decryptor::<aes::Aes256>::new(&key.into(), &iv.into())
.decrypt_padded_vec_mut::<Pkcs7>(ciphertext)?;
if pt[pt.len() - 12..pt.len() - 1] != V1_CHECK_VALUE {
return Err(Error::WrongDecryptionPassword);
}
let ks = KeySecurity::try_from(pt[pt.len() - 1])?;
let output = PrivateKey(SigningKey::from_bytes(&pt[..pt.len() - 12])?, ks);
pt.zeroize();
Ok(output)
}
fn password_to_key_v1(password: &str, salt: &[u8], rounds: u32) -> Result<[u8; 32], Error> {
let mut key: [u8; 32] = [0; 32];
pbkdf2::<Hmac<Sha256>>(password.as_bytes(), salt, rounds, &mut key)?;
Ok(key)
}
fn password_to_key_v2(password: &str, salt: &[u8; 16], log_n: u8) -> Result<[u8; 32], Error> {
let params = match scrypt::Params::new(log_n, 8, 1, 32) {
Ok(p) => p,
Err(_) => return Err(Error::Scrypt),
};
let mut key: [u8; 32] = [0; 32];
if scrypt::scrypt(password.as_bytes(), salt, ¶ms, &mut key).is_err() {
return Err(Error::Scrypt);
}
Ok(key)
}
#[allow(dead_code)]
pub(crate) fn mock() -> PrivateKey {
PrivateKey::generate()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_export_import() {
let pk = PrivateKey::generate();
let exported = pk.export_encrypted("secret", 13).unwrap();
println!("{}", exported);
let imported_pk = PrivateKey::import_encrypted(&exported, "secret").unwrap();
assert_eq!(pk.public_key(), imported_pk.public_key());
assert_eq!(pk.key_security(), KeySecurity::Medium)
}
#[test]
fn test_import_old_formats() {
let decrypted = "a28129ab0b70c8d5e75aaf510ec00bff47fde7ca4ab9e3d9315c77edc86f037f";
let encrypted = EncryptedPrivateKey("F+VYIvTCtIZn4c6owPMZyu4Zn5DH9T5XcgZWmFG/3ma4C3PazTTQxQcIF+G+daeFlkqsZiNIh9bcmZ5pfdRPyg==".to_owned());
assert_eq!(
encrypted.decrypt("nostr").unwrap().as_hex_string(),
decrypted
);
let encrypted = EncryptedPrivateKey("AZQYNwAGULWyKweTtw6WCljV+1cil8IMRxfZ7Rs3nCfwbVQBV56U6eV9ps3S1wU7ieCx6EraY9Uqdsw71TY5Yv/Ep6yGcy9m1h4YozuxWQE=".to_owned());
assert_eq!(
encrypted.decrypt("nostr").unwrap().as_hex_string(),
decrypted
);
let decrypted = "3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683";
let encrypted = EncryptedPrivateKey("KlmfCiO+Tf8A/8bm/t+sXWdb1Op4IORdghC7n/9uk/vgJXIcyW7PBAx1/K834azuVmQnCzGq1pmFMF9rNPWQ9Q==".to_owned());
assert_eq!(
encrypted.decrypt("nostr").unwrap().as_hex_string(),
decrypted
);
let encrypted = EncryptedPrivateKey("AZ/2MU2igqP0keoW08Z/rxm+/3QYcZn3oNbVhY6DSUxSDkibNp+bFN/WsRQxP7yBKwyEJVu/YSBtm2PI9DawbYOfXDqfmpA3NTPavgXwUrw=".to_owned());
assert_eq!(
encrypted.decrypt("nostr").unwrap().as_hex_string(),
decrypted
);
let encrypted = EncryptedPrivateKey("ncryptsec1q9hnc06cs5tuk7znrxmetj4q9q2mjtccg995kp86jf3dsp3jykv4fhak730wds4s0mja6c9v2fvdr5dhzrstds8yks5j9ukvh25ydg6xtve6qvp90j0c8a2s5tv4xn7kvulg88".to_owned());
assert_eq!(
encrypted.decrypt("nostr").unwrap().as_hex_string(),
decrypted
);
let encrypted = EncryptedPrivateKey("ncryptsec1qgg9947rlpvqu76pj5ecreduf9jxhselq2nae2kghhvd5g7dgjtcxfqtd67p9m0w57lspw8gsq6yphnm8623nsl8xn9j4jdzz84zm3frztj3z7s35vpzmqf6ksu8r89qk5z2zxfmu5gv8th8wclt0h4p".to_owned());
assert_eq!(
encrypted.decrypt("nostr").unwrap().as_hex_string(),
decrypted
);
}
#[test]
fn test_privkey_bech32() {
let mut pk = PrivateKey::mock();
let encoded = pk.try_as_bech32_string().unwrap();
println!("bech32: {}", encoded);
let decoded = PrivateKey::try_from_bech32_string(&encoded).unwrap();
assert_eq!(pk.0.to_bytes(), decoded.0.to_bytes());
assert_eq!(decoded.1, KeySecurity::Weak);
}
#[test]
fn test_privkey_nip04() {
let private_key = PrivateKey::mock();
let other_public_key = PublicKey::mock();
let message = "hello world, this should come out just dandy.".as_bytes();
let (iv, encrypted) = private_key
.nip04_encrypt(&other_public_key, &message)
.unwrap();
let decrypted = private_key
.nip04_decrypt(&other_public_key, &encrypted, iv)
.unwrap();
assert_eq!(message, decrypted);
}
}