Skip to main content

bullet_rust_sdk/
keypair.rs

1//! Keypair functionality for the Trading SDK.
2
3use crate::errors::{SDKError, SDKResult};
4
5/// An Ed25519 keypair for signing transactions.
6///
7/// This is a lightweight wrapper around ed25519_dalek::SigningKey that provides
8/// convenient methods for creating keypairs and signing messages.
9///
10/// # Security Note
11/// This stores the private key in memory. For production use with significant funds,
12/// consider using a hardware wallet or external signing service.
13#[derive(Clone)]
14pub struct Keypair {
15    signing_key: ed25519_dalek::SigningKey,
16}
17
18impl Keypair {
19    /// Create a keypair from a 32-byte secret key.
20    pub fn from_bytes(secret_key: [u8; 32]) -> Self {
21        let signing_key = ed25519_dalek::SigningKey::from_bytes(&secret_key);
22        Self { signing_key }
23    }
24
25    /// Create a keypair from a hex-encoded secret key.
26    ///
27    /// Accepts keys with or without "0x" prefix.
28    pub fn from_hex(hex: &str) -> SDKResult<Self> {
29        let hex = hex.strip_prefix("0x").unwrap_or(hex);
30        let bytes: [u8; 32] = hex::decode(hex)
31            .map_err(|e| SDKError::InvalidPrivateKey(e.to_string()))?
32            .try_into()
33            .map_err(|_| SDKError::InvalidPrivateKey("Expected 32 bytes".into()))?;
34        Ok(Self::from_bytes(bytes))
35    }
36
37    /// Generate a new random keypair.
38    ///
39    /// Uses the OS random number generator.
40    pub fn generate() -> Self {
41        use ed25519_dalek::SigningKey;
42        use rand::rngs::OsRng;
43        let signing_key = SigningKey::generate(&mut OsRng);
44        Self { signing_key }
45    }
46
47    /// Sign a message and return the 64-byte signature.
48    pub fn sign(&self, message: &[u8]) -> Vec<u8> {
49        use ed25519_dalek::Signer;
50        let signature = self.signing_key.sign(message);
51        signature.to_bytes().to_vec()
52    }
53
54    /// Get the 32-byte public key.
55    pub fn public_key(&self) -> Vec<u8> {
56        self.signing_key.verifying_key().as_bytes().to_vec()
57    }
58
59    /// The on-chain address (base58-encoded public key).
60    ///
61    /// This is the canonical address format used by the Bullet exchange.
62    /// For the hex-encoded raw public key, see [`address_hex`](Self::address_hex).
63    pub fn address(&self) -> String {
64        let pk_bytes: [u8; 32] = self.signing_key.verifying_key().to_bytes();
65        bullet_exchange_interface::address::Address(pk_bytes).to_string()
66    }
67
68    /// The public key as a hex string (32 bytes → 64 hex chars).
69    pub fn address_hex(&self) -> String {
70        hex::encode(self.public_key())
71    }
72
73    /// Write to a Solana-compatible JSON keystore file.
74    ///
75    /// Format: a JSON array of 64 integers — the 32-byte secret key followed
76    /// by the 32-byte public key. Compatible with `solana-keygen` and Phantom.
77    pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
78        let secret = self.signing_key.to_bytes();
79        let public = self.signing_key.verifying_key().to_bytes();
80        let mut bytes = [0u8; 64];
81        bytes[..32].copy_from_slice(&secret);
82        bytes[32..].copy_from_slice(&public);
83        let json = serde_json::to_string(&bytes.as_slice())
84            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
85        std::fs::write(path, json)
86    }
87
88    /// Read a Solana-compatible JSON keystore file.
89    ///
90    /// Accepts either a 64-byte array (secret + public) or a 32-byte array
91    /// (secret only). Returns an error if the file is missing or malformed.
92    pub fn read_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
93        let path = path.as_ref();
94        let data = std::fs::read_to_string(path)?;
95        let bytes: Vec<u8> = serde_json::from_str(&data)
96            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
97        if bytes.len() < 32 {
98            return Err(std::io::Error::new(
99                std::io::ErrorKind::InvalidData,
100                format!("keystore too short: {} bytes (need ≥32)", bytes.len()),
101            ));
102        }
103        let mut secret = [0u8; 32];
104        secret.copy_from_slice(&bytes[..32]);
105        Ok(Self::from_bytes(secret))
106    }
107}
108
109impl std::fmt::Debug for Keypair {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        f.debug_struct("Keypair").field("address", &self.address()).finish_non_exhaustive()
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_generate_and_sign() {
121        let keypair = Keypair::generate();
122        let message = b"test message";
123        let signature = keypair.sign(message);
124        assert_eq!(signature.len(), 64); // Ed25519 signatures are 64 bytes
125    }
126
127    #[test]
128    fn test_from_hex() {
129        let hex = "0000000000000000000000000000000000000000000000000000000000000001";
130        let keypair = Keypair::from_hex(hex).unwrap();
131        assert_eq!(keypair.public_key().len(), 32);
132    }
133
134    #[test]
135    fn test_from_hex_with_prefix() {
136        let hex = "0x0000000000000000000000000000000000000000000000000000000000000001";
137        let keypair = Keypair::from_hex(hex).unwrap();
138        assert_eq!(keypair.public_key().len(), 32);
139    }
140}