1pub mod dex;
7pub mod governance;
8pub mod jupiter_dca;
9pub mod metaplex;
10pub mod programs;
11pub mod staking;
12pub mod token_extensions;
13pub mod transaction;
14
15use crate::error::SignerError;
16use crate::traits;
17use ed25519_dalek::Signer as DalekSigner;
18use ed25519_dalek::Verifier as DalekVerifier;
19use sha2::{Digest, Sha512};
20use zeroize::Zeroizing;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[must_use]
26pub struct SolanaSignature {
27 #[cfg_attr(feature = "serde", serde(with = "crate::hex_bytes"))]
29 pub bytes: [u8; 64],
30}
31
32impl SolanaSignature {
33 pub fn to_bytes(&self) -> [u8; 64] {
35 self.bytes
36 }
37
38 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignerError> {
40 if bytes.len() != 64 {
41 return Err(SignerError::InvalidSignature(format!(
42 "expected 64 bytes, got {}",
43 bytes.len()
44 )));
45 }
46 let mut out = [0u8; 64];
47 out.copy_from_slice(bytes);
48 Ok(Self { bytes: out })
49 }
50}
51
52impl core::fmt::Display for SolanaSignature {
53 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54 for byte in &self.bytes {
55 write!(f, "{byte:02x}")?;
56 }
57 Ok(())
58 }
59}
60
61pub struct SolanaSigner {
63 pub(crate) signing_key: ed25519_dalek::SigningKey,
64}
65
66impl SolanaSigner {
67 pub fn address(&self) -> String {
71 bs58::encode(self.signing_key.verifying_key().as_bytes()).into_string()
72 }
73
74 #[must_use]
76 pub fn public_key_bytes_32(&self) -> [u8; 32] {
77 *self.signing_key.verifying_key().as_bytes()
78 }
79}
80
81pub fn validate_address(address: &str) -> bool {
85 match bs58::decode(address).into_vec() {
86 Ok(bytes) => bytes.len() == 32,
87 Err(_) => false,
88 }
89}
90
91impl Drop for SolanaSigner {
92 fn drop(&mut self) {
93 }
95}
96
97impl traits::Signer for SolanaSigner {
98 type Signature = SolanaSignature;
99 type Error = SignerError;
100
101 fn sign(&self, message: &[u8]) -> Result<SolanaSignature, SignerError> {
102 let sig = DalekSigner::sign(&self.signing_key, message);
103 Ok(SolanaSignature {
104 bytes: sig.to_bytes(),
105 })
106 }
107
108 fn sign_prehashed(&self, digest: &[u8]) -> Result<SolanaSignature, SignerError> {
117 self.sign(digest)
118 }
119
120 fn public_key_bytes(&self) -> Vec<u8> {
121 self.signing_key.verifying_key().as_bytes().to_vec()
122 }
123
124 fn public_key_bytes_uncompressed(&self) -> Vec<u8> {
125 self.public_key_bytes()
127 }
128}
129
130impl traits::KeyPair for SolanaSigner {
131 fn generate() -> Result<Self, SignerError> {
132 let mut key_bytes = zeroize::Zeroizing::new([0u8; 32]);
133 crate::security::secure_random(&mut *key_bytes)?;
134 let signing_key = ed25519_dalek::SigningKey::from_bytes(&key_bytes);
135 Ok(Self { signing_key })
136 }
137
138 fn from_bytes(private_key: &[u8]) -> Result<Self, SignerError> {
139 if private_key.len() != 32 {
140 return Err(SignerError::InvalidPrivateKey(format!(
141 "expected 32 bytes (Ed25519 seed), got {}",
142 private_key.len()
143 )));
144 }
145 let mut bytes = [0u8; 32];
146 bytes.copy_from_slice(private_key);
147 let signing_key = ed25519_dalek::SigningKey::from_bytes(&bytes);
148 Ok(Self { signing_key })
149 }
150
151 fn private_key_bytes(&self) -> Zeroizing<Vec<u8>> {
152 Zeroizing::new(self.signing_key.to_bytes().to_vec())
153 }
154
155 fn from_keypair_bytes(keypair: &[u8]) -> Result<Self, SignerError> {
159 use subtle::ConstantTimeEq;
160 if keypair.len() != 64 {
161 return Err(SignerError::InvalidPrivateKey(format!(
162 "expected 64-byte keypair, got {}",
163 keypair.len()
164 )));
165 }
166 let signer = Self::from_bytes(&keypair[..32])?;
167 let derived_pk = signer.signing_key.verifying_key().as_bytes().to_vec();
168 if derived_pk.ct_eq(&keypair[32..]).into() {
169 Ok(signer)
170 } else {
171 Err(SignerError::InvalidPrivateKey(
172 "pubkey in keypair does not match derived pubkey".into(),
173 ))
174 }
175 }
176
177 fn keypair_bytes(&self) -> Zeroizing<Vec<u8>> {
179 let mut kp = Vec::with_capacity(64);
180 kp.extend_from_slice(&self.signing_key.to_bytes());
181 kp.extend_from_slice(self.signing_key.verifying_key().as_bytes());
182 Zeroizing::new(kp)
183 }
184}
185
186impl SolanaSigner {
187 pub fn scalar_bytes(&self) -> Zeroizing<Vec<u8>> {
192 let expanded = Sha512::digest(self.signing_key.to_bytes());
193 let mut scalar = [0u8; 32];
194 scalar.copy_from_slice(&expanded[..32]);
195 scalar[0] &= 248;
197 scalar[31] &= 127;
198 scalar[31] |= 64;
199 Zeroizing::new(scalar.to_vec())
200 }
201}
202
203pub struct SolanaVerifier {
205 verifying_key: ed25519_dalek::VerifyingKey,
206}
207
208impl SolanaVerifier {
209 pub fn from_public_key_bytes(bytes: &[u8]) -> Result<Self, SignerError> {
211 if bytes.len() != 32 {
212 return Err(SignerError::InvalidPublicKey(format!(
213 "expected 32 bytes, got {}",
214 bytes.len()
215 )));
216 }
217 let mut pk_bytes = [0u8; 32];
218 pk_bytes.copy_from_slice(bytes);
219 let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&pk_bytes)
220 .map_err(|e| SignerError::InvalidPublicKey(e.to_string()))?;
221 Ok(Self { verifying_key })
222 }
223}
224
225impl traits::Verifier for SolanaVerifier {
226 type Signature = SolanaSignature;
227 type Error = SignerError;
228
229 fn verify(&self, message: &[u8], signature: &SolanaSignature) -> Result<bool, SignerError> {
230 let sig = ed25519_dalek::Signature::from_bytes(&signature.bytes);
231 match DalekVerifier::verify(&self.verifying_key, message, &sig) {
232 Ok(()) => Ok(true),
233 Err(_) => Ok(false),
234 }
235 }
236
237 fn verify_prehashed(
238 &self,
239 digest: &[u8],
240 signature: &SolanaSignature,
241 ) -> Result<bool, SignerError> {
242 self.verify(digest, signature)
243 }
244}
245
246#[cfg(test)]
247#[allow(clippy::unwrap_used, clippy::expect_used)]
248mod tests {
249 use super::*;
250 use crate::traits::{KeyPair, Signer, Verifier};
251
252 #[test]
253 fn test_generate_keypair() {
254 let signer = SolanaSigner::generate().unwrap();
255 assert_eq!(signer.public_key_bytes().len(), 32);
256 }
257
258 #[test]
259 fn test_from_bytes_roundtrip() {
260 let signer = SolanaSigner::generate().unwrap();
261 let restored = SolanaSigner::from_bytes(&signer.private_key_bytes()).unwrap();
262 assert_eq!(signer.public_key_bytes(), restored.public_key_bytes());
263 }
264
265 #[test]
266 fn test_sign_verify_roundtrip() {
267 let signer = SolanaSigner::generate().unwrap();
268 let sig = signer.sign(b"hello solana").unwrap();
269 let verifier = SolanaVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
270 assert!(verifier.verify(b"hello solana", &sig).unwrap());
271 }
272
273 #[test]
274 fn test_64_byte_signature() {
275 let signer = SolanaSigner::generate().unwrap();
276 let sig = signer.sign(b"test").unwrap();
277 assert_eq!(sig.bytes.len(), 64);
278 }
279
280 #[test]
282 fn test_rfc8032_vector1_empty() {
283 let sk = hex::decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
284 .unwrap();
285 let expected_sig = hex::decode(
286 "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"
287 ).unwrap();
288
289 let signer = SolanaSigner::from_bytes(&sk).unwrap();
290 let sig = signer.sign(b"").unwrap();
291 assert_eq!(sig.bytes.to_vec(), expected_sig);
292
293 let verifier = SolanaVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
295 assert!(verifier.verify(b"", &sig).unwrap());
296 }
297
298 #[test]
300 fn test_rfc8032_vector2_single_byte() {
301 let sk = hex::decode("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb")
302 .unwrap();
303 let expected_pk =
304 hex::decode("3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c")
305 .unwrap();
306 let msg = hex::decode("72").unwrap();
307 let expected_sig = hex::decode(
308 "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00"
309 ).unwrap();
310
311 let signer = SolanaSigner::from_bytes(&sk).unwrap();
312 assert_eq!(signer.public_key_bytes(), expected_pk);
313
314 let sig = signer.sign(&msg).unwrap();
315 assert_eq!(sig.bytes.to_vec(), expected_sig);
316 }
317
318 #[test]
320 fn test_rfc8032_vector3_two_bytes() {
321 let sk = hex::decode("c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7")
322 .unwrap();
323 let expected_pk =
324 hex::decode("fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025")
325 .unwrap();
326 let msg = hex::decode("af82").unwrap();
327 let expected_sig = hex::decode(
328 "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a"
329 ).unwrap();
330
331 let signer = SolanaSigner::from_bytes(&sk).unwrap();
332 assert_eq!(signer.public_key_bytes(), expected_pk);
333
334 let sig = signer.sign(&msg).unwrap();
335 assert_eq!(sig.bytes.to_vec(), expected_sig);
336 }
337
338 #[test]
339 fn test_invalid_key_rejected() {
340 assert!(SolanaSigner::from_bytes(&[1u8; 31]).is_err());
341 assert!(SolanaSigner::from_bytes(&[1u8; 33]).is_err());
342 }
343
344 #[test]
345 fn test_tampered_sig_fails() {
346 let signer = SolanaSigner::generate().unwrap();
347 let sig = signer.sign(b"tamper").unwrap();
348 let verifier = SolanaVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
349 let mut tampered = sig.clone();
350 tampered.bytes[0] ^= 0xff;
351 assert!(!verifier.verify(b"tamper", &tampered).unwrap());
352 }
353
354 #[test]
355 fn test_wrong_pubkey_fails() {
356 let s1 = SolanaSigner::generate().unwrap();
357 let s2 = SolanaSigner::generate().unwrap();
358 let sig = s1.sign(b"wrong").unwrap();
359 let verifier = SolanaVerifier::from_public_key_bytes(&s2.public_key_bytes()).unwrap();
360 assert!(!verifier.verify(b"wrong", &sig).unwrap());
361 }
362
363 #[test]
364 fn test_sign_prehashed_roundtrip() {
365 let signer = SolanaSigner::generate().unwrap();
366 let msg = b"prehash solana";
367 let sig = signer.sign_prehashed(msg).unwrap();
368 let verifier = SolanaVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
369 assert!(verifier.verify(msg, &sig).unwrap());
370 }
371
372 #[test]
373 fn test_zeroize_on_drop() {
374 let signer = SolanaSigner::generate().unwrap();
375 let _: Zeroizing<Vec<u8>> = signer.private_key_bytes();
376 drop(signer);
377 }
378}