use crate::{Error, Pubkey, Result};
use ed25519_dalek::{SecretKey, SigningKey, VerifyingKey};
use rand::rngs::OsRng;
#[derive(Clone)]
pub struct Keypair {
signing_key: SigningKey,
}
impl Keypair {
pub fn generate() -> Self {
let signing_key = SigningKey::generate(&mut OsRng);
Self { signing_key }
}
pub fn from_secret_key(secret: &[u8]) -> Result<Self> {
if secret.len() != 32 {
return Err(Error::InvalidKeyLength {
expected: 32,
got: secret.len(),
});
}
let mut bytes = [0u8; 32];
bytes.copy_from_slice(secret);
let signing_key = SigningKey::from_bytes(&bytes);
Ok(Self { signing_key })
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() == 32 {
return Self::from_secret_key(bytes);
}
if bytes.len() != 64 {
return Err(Error::InvalidKeyLength {
expected: 64,
got: bytes.len(),
});
}
let bytes: &[u8; 64] = bytes.try_into().expect("keypair length checked above");
let signing_key =
SigningKey::from_keypair_bytes(bytes).map_err(|_| Error::InvalidKeypair)?;
Ok(Self { signing_key })
}
pub fn from_base58(s: &str) -> Result<Self> {
let bytes = bs58::decode(s)
.into_vec()
.map_err(|e| Error::InvalidBase58(e.to_string()))?;
Self::from_bytes(&bytes)
}
pub fn pubkey(&self) -> Pubkey {
let verifying_key = self.signing_key.verifying_key();
Pubkey::from_bytes(verifying_key.to_bytes())
}
pub fn secret_key(&self) -> &SecretKey {
self.signing_key.as_bytes()
}
pub fn to_bytes(&self) -> [u8; 64] {
let mut bytes = [0u8; 64];
bytes[..32].copy_from_slice(self.signing_key.as_bytes());
bytes[32..].copy_from_slice(self.signing_key.verifying_key().as_bytes());
bytes
}
pub fn to_base58(&self) -> String {
bs58::encode(self.to_bytes()).into_string()
}
pub(crate) fn signing_key(&self) -> &SigningKey {
&self.signing_key
}
pub fn verifying_key(&self) -> VerifyingKey {
self.signing_key.verifying_key()
}
}
impl std::fmt::Debug for Keypair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Keypair")
.field("pubkey", &self.pubkey().to_base58())
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_keypair() {
let keypair = Keypair::generate();
let pubkey = keypair.pubkey();
assert_eq!(pubkey.as_bytes().len(), 32);
let b58 = pubkey.to_base58();
assert!(!b58.is_empty());
}
#[test]
fn test_keypair_roundtrip() {
let keypair = Keypair::generate();
let bytes = keypair.to_bytes();
let restored = Keypair::from_bytes(&bytes).unwrap();
assert_eq!(keypair.pubkey(), restored.pubkey());
}
#[test]
fn test_keypair_from_base58() {
let keypair = Keypair::generate();
let b58 = keypair.to_base58();
let restored = Keypair::from_base58(&b58).unwrap();
assert_eq!(keypair.pubkey(), restored.pubkey());
}
#[test]
fn test_invalid_key_length() {
let result = Keypair::from_bytes(&[0u8; 31]);
assert!(result.is_err());
let result = Keypair::from_bytes(&[0u8; 65]);
assert!(result.is_err());
}
#[test]
fn imported_keypair_rejects_inconsistent_public_key() {
let keypair = Keypair::from_secret_key(&[1; 32]).unwrap();
let mut bytes = keypair.to_bytes();
bytes[63] ^= 1;
assert!(matches!(
Keypair::from_bytes(&bytes),
Err(Error::InvalidKeypair)
));
assert!(matches!(
Keypair::from_base58(&bs58::encode(bytes).into_string()),
Err(Error::InvalidKeypair)
));
}
#[test]
fn imported_seed_and_valid_keypair_sign_identically() {
use ed25519_dalek::Signer;
let keypair = Keypair::from_secret_key(&[1; 32]).unwrap();
for imported in [
Keypair::from_bytes(&[1; 32]).unwrap(),
Keypair::from_bytes(&keypair.to_bytes()).unwrap(),
Keypair::from_base58(&keypair.to_base58()).unwrap(),
] {
assert_eq!(imported.to_bytes(), keypair.to_bytes());
assert_eq!(
imported.signing_key().sign(b"fixture"),
keypair.signing_key().sign(b"fixture")
);
}
}
}