use p256::elliptic_curve::rand_core::OsRng;
use crate::{
builder::VerificationScript,
crypto::{
private_key_from_wif, wif_from_private_key, CryptoError, PublicKeyExtension,
Secp256r1PrivateKey, Secp256r1PublicKey, Secp256r1Signature,
},
neo_types::{ScriptHash, ScriptHashExtension},
};
use p256::elliptic_curve::zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Debug, Clone)]
pub struct KeyPair {
private_key: Option<Secp256r1PrivateKey>,
public_key: Secp256r1PublicKey,
}
impl KeyPair {
fn missing_private_key_error() -> CryptoError {
CryptoError::KeyError("Key pair does not contain a private key".to_string())
}
pub fn new(private_key: Secp256r1PrivateKey, public_key: Secp256r1PublicKey) -> Self {
Self { private_key: Some(private_key), public_key }
}
pub fn private_key(&self) -> Result<Secp256r1PrivateKey, CryptoError> {
self.private_key.as_ref().cloned().ok_or_else(Self::missing_private_key_error)
}
#[inline]
pub fn private_key_ref(&self) -> Result<&Secp256r1PrivateKey, CryptoError> {
self.private_key.as_ref().ok_or_else(Self::missing_private_key_error)
}
pub fn public_key(&self) -> Secp256r1PublicKey {
self.public_key.clone()
}
#[inline]
pub fn public_key_ref(&self) -> &Secp256r1PublicKey {
&self.public_key
}
#[inline]
pub fn has_private_key(&self) -> bool {
self.private_key.is_some()
}
pub fn from_secret_key(private_key: &Secp256r1PrivateKey) -> Self {
let public_key = private_key.clone().to_public_key();
Self::new(private_key.clone(), public_key)
}
pub fn private_key_bytes(&self) -> Result<[u8; 32], CryptoError> {
Ok(self.private_key_ref()?.to_raw_bytes())
}
pub fn public_key_bytes(&self) -> [u8; 64] {
let mut buf = [0u8; 64];
let uncompressed = self.public_key.get_encoded(false);
debug_assert_eq!(
uncompressed.len(),
65,
"Expected an uncompressed secp256r1 public key to be 65 bytes (0x04 || X || Y)"
);
if uncompressed.len() == 65 {
buf.copy_from_slice(&uncompressed[1..]);
} else if uncompressed.len() == 64 {
buf.copy_from_slice(&uncompressed);
}
buf
}
pub fn sign(&self, message: &[u8]) -> Result<Secp256r1Signature, CryptoError> {
self.private_key_ref()?.sign_tx(message)
}
pub fn verify(
&self,
message: &[u8],
signature: &Secp256r1Signature,
) -> Result<bool, CryptoError> {
self.public_key.verify(message, signature).map(|_| true)
}
}
impl KeyPair {
pub fn new_random() -> Self {
let mut rng = OsRng; let secret_key = Secp256r1PrivateKey::random(&mut rng);
Self::from_secret_key(&secret_key)
}
pub fn from_private_key(private_key: &[u8; 32]) -> Result<Self, CryptoError> {
let secret_key = Secp256r1PrivateKey::from_bytes(private_key)?;
Ok(Self::from_secret_key(&secret_key))
}
pub fn from_wif(wif: &str) -> Result<Self, CryptoError> {
let private_key = private_key_from_wif(wif)?;
Ok(Self::from_secret_key(&private_key))
}
pub fn from_public_key(public_key: &[u8; 64]) -> Result<Self, CryptoError> {
let public_key = Secp256r1PublicKey::from_slice(public_key)?;
Ok(Self { private_key: None, public_key })
}
pub fn export_as_wif(&self) -> Result<String, CryptoError> {
Ok(wif_from_private_key(self.private_key_ref()?))
}
pub fn get_script_hash(&self) -> ScriptHash {
let vs = VerificationScript::from_public_key(self.public_key_ref());
vs.hash()
}
pub fn get_address(&self) -> String {
self.get_script_hash().to_address()
}
}
impl PartialEq for KeyPair {
fn eq(&self, other: &Self) -> bool {
self.private_key == other.private_key && self.public_key == other.public_key
}
}
impl Zeroize for KeyPair {
fn zeroize(&mut self) {
if let Some(private_key) = self.private_key.as_mut() {
private_key.zeroize();
}
}
}
impl ZeroizeOnDrop for KeyPair {}
#[cfg(test)]
mod tests {
use crate::{
config::TestConstants,
crypto::{CryptoError, KeyPair},
ScriptHash, ScriptHashExtension,
};
use hex;
#[test]
fn test_public_key_wif() {
let private_key =
hex::decode("c7134d6fd8e73d819e82755c64c93788d8db0961929e025a53363c4cc02a6962")
.unwrap();
let private_key_arr: &[u8; 32] = private_key.as_slice().try_into().unwrap();
let key_pair = KeyPair::from_private_key(private_key_arr).unwrap();
assert_eq!(
key_pair.export_as_wif().unwrap(),
"L3tgppXLgdaeqSGSFw1Go3skBiy8vQAM7YMXvTHsKQtE16PBncSU"
);
}
#[test]
fn test_public_key_only_wif_export_fails() {
let key_pair = KeyPair::new_random();
let public_key = key_pair.public_key_bytes();
let watch_only = KeyPair::from_public_key(&public_key).unwrap();
assert!(!watch_only.has_private_key());
assert!(matches!(watch_only.private_key_ref(), Err(CryptoError::KeyError(_))));
assert!(matches!(watch_only.private_key_bytes(), Err(CryptoError::KeyError(_))));
assert!(matches!(watch_only.sign(b"message"), Err(CryptoError::KeyError(_))));
assert!(matches!(watch_only.export_as_wif(), Err(CryptoError::KeyError(_))));
}
#[test]
fn test_address() {
let private_key = hex::decode(TestConstants::DEFAULT_ACCOUNT_PRIVATE_KEY).unwrap();
let private_key_arr: &[u8; 32] = private_key.as_slice().try_into().unwrap();
let key_pair = KeyPair::from_private_key(private_key_arr).unwrap();
assert_eq!(key_pair.get_address(), TestConstants::DEFAULT_ACCOUNT_ADDRESS);
}
#[test]
fn test_script_hash() {
let private_key = hex::decode(TestConstants::DEFAULT_ACCOUNT_PRIVATE_KEY).unwrap();
let private_key_arr: &[u8; 32] = private_key.as_slice().try_into().unwrap();
let key_pair = KeyPair::from_private_key(private_key_arr).unwrap();
assert_eq!(
key_pair.get_script_hash(),
ScriptHash::from_hex(TestConstants::DEFAULT_ACCOUNT_SCRIPT_HASH).unwrap()
);
}
#[test]
fn test_public_key_bytes_uncompressed_xy() {
let private_key = hex::decode(TestConstants::DEFAULT_ACCOUNT_PRIVATE_KEY).unwrap();
let private_key_arr: &[u8; 32] = private_key.as_slice().try_into().unwrap();
let key_pair = KeyPair::from_private_key(private_key_arr).unwrap();
let bytes = key_pair.public_key_bytes();
let uncompressed = key_pair.public_key().get_encoded(false);
assert_eq!(uncompressed.len(), 65);
assert_eq!(&bytes[..], &uncompressed[1..]);
}
}