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