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    /// The public key as a hex string (32 bytes → 64 hex chars).
74    pub fn public_key_hex(&self) -> String {
75        self.address_hex()
76    }
77
78    /// Write to a Solana-compatible JSON keystore file.
79    ///
80    /// Format: a JSON array of 64 integers — the 32-byte secret key followed
81    /// by the 32-byte public key. Compatible with `solana-keygen` and Phantom.
82    pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
83        let secret = self.signing_key.to_bytes();
84        let public = self.signing_key.verifying_key().to_bytes();
85        let mut bytes = [0u8; 64];
86        bytes[..32].copy_from_slice(&secret);
87        bytes[32..].copy_from_slice(&public);
88        let json = serde_json::to_string(&bytes.as_slice())
89            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
90        std::fs::write(path, json)
91    }
92
93    /// Read a Solana-compatible JSON keystore file.
94    ///
95    /// Accepts either a 64-byte array (secret + public) or a 32-byte array
96    /// (secret only). Returns an error if the file is missing or malformed.
97    pub fn read_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
98        let path = path.as_ref();
99        let data = std::fs::read_to_string(path)?;
100        let bytes: Vec<u8> = serde_json::from_str(&data)
101            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
102        if bytes.len() < 32 {
103            return Err(std::io::Error::new(
104                std::io::ErrorKind::InvalidData,
105                format!("keystore too short: {} bytes (need ≥32)", bytes.len()),
106            ));
107        }
108        let mut secret = [0u8; 32];
109        secret.copy_from_slice(&bytes[..32]);
110        Ok(Self::from_bytes(secret))
111    }
112}
113
114impl std::fmt::Debug for Keypair {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        f.debug_struct("Keypair").field("address", &self.address()).finish_non_exhaustive()
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_generate_and_sign() {
126        let keypair = Keypair::generate();
127        let message = b"test message";
128        let signature = keypair.sign(message);
129        assert_eq!(signature.len(), 64); // Ed25519 signatures are 64 bytes
130    }
131
132    #[test]
133    fn test_from_hex() {
134        let hex = "0000000000000000000000000000000000000000000000000000000000000001";
135        let keypair = Keypair::from_hex(hex).unwrap();
136        assert_eq!(keypair.public_key().len(), 32);
137    }
138
139    #[test]
140    fn test_from_hex_with_prefix() {
141        let hex = "0x0000000000000000000000000000000000000000000000000000000000000001";
142        let keypair = Keypair::from_hex(hex).unwrap();
143        assert_eq!(keypair.public_key().len(), 32);
144    }
145}