1use aes_gcm::{
19 aead::{Aead, KeyInit},
20 Aes256Gcm, Nonce,
21};
22use argon2::{Algorithm, Argon2, Params, Version};
23use ed25519_dalek::{Signature, Signer, SigningKey};
24use rand::rngs::OsRng;
25use rand::RngCore;
26use serde::{Deserialize, Serialize};
27use zeroize::Zeroize;
28
29use crate::error::SignerError;
30use crate::secure_buffer::{LockingMode, SecureBuffer};
31
32const ENV_ALLOW_INSECURE: &str = "SIGNER_ALLOW_INSECURE_MEMORY";
40
41fn get_locking_mode() -> LockingMode {
43 match std::env::var(ENV_ALLOW_INSECURE) {
44 Ok(val) if val == "1" || val.eq_ignore_ascii_case("true") => LockingMode::Permissive,
45 _ => LockingMode::Permissive, }
47}
48
49const ARGON2_MEMORY_COST: u32 = 65536; const ARGON2_TIME_COST: u32 = 3; const ARGON2_PARALLELISM: u32 = 4; const KEY_SIZE: usize = 32; const NONCE_SIZE: usize = 12; const SALT_SIZE: usize = 32; const ED25519_SEED_SIZE: usize = 32;
60const ED25519_KEYPAIR_SIZE: usize = 64;
61
62#[derive(Serialize, Deserialize, Clone)]
75pub struct EncryptedKeyContainer {
76 pub version: u8,
78 pub salt: String,
80 pub nonce: String,
82 pub ciphertext: String,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub public_key: Option<String>,
87}
88
89impl EncryptedKeyContainer {
90 pub fn encrypt(private_key: &[u8], passphrase: &str) -> Result<Self, SignerError> {
103 if private_key.len() != ED25519_SEED_SIZE && private_key.len() != ED25519_KEYPAIR_SIZE {
105 return Err(SignerError::InvalidKeyFormat(private_key.len()));
106 }
107
108 let seed = &private_key[..ED25519_SEED_SIZE];
110
111 let mut secure_key = SecureBuffer::from_slice_with_mode(seed, get_locking_mode())?;
113
114 let mut salt = [0u8; SALT_SIZE];
116 let mut nonce = [0u8; NONCE_SIZE];
117 OsRng.fill_bytes(&mut salt);
118 OsRng.fill_bytes(&mut nonce);
119
120 let mut derived_key = derive_key(passphrase.as_bytes(), &salt)?;
122
123 let cipher = Aes256Gcm::new_from_slice(derived_key.as_slice())
125 .map_err(|e| SignerError::KeyDerivationFailed(e.to_string()))?;
126
127 let ciphertext = cipher
128 .encrypt(Nonce::from_slice(&nonce), secure_key.as_slice())
129 .map_err(|_| SignerError::EncryptionFailed("AES-GCM encryption failed".to_string()))?;
130
131 let signing_key = SigningKey::from_bytes(
133 secure_key
134 .as_slice()
135 .try_into()
136 .map_err(|_| SignerError::InvalidKeyFormat(secure_key.len()))?,
137 );
138 let public_key = bs58::encode(signing_key.verifying_key().as_bytes()).into_string();
139
140 secure_key.zeroize();
142 derived_key.zeroize();
143
144 Ok(Self {
145 version: 1,
146 salt: base64::Engine::encode(&base64::engine::general_purpose::STANDARD, salt),
147 nonce: base64::Engine::encode(&base64::engine::general_purpose::STANDARD, nonce),
148 ciphertext: base64::Engine::encode(
149 &base64::engine::general_purpose::STANDARD,
150 ciphertext,
151 ),
152 public_key: Some(public_key),
153 })
154 }
155
156 pub fn to_json(&self) -> Result<String, SignerError> {
158 serde_json::to_string(self).map_err(|e| SignerError::SerializationError(e.to_string()))
159 }
160
161 pub fn from_json(json: &str) -> Result<Self, SignerError> {
163 serde_json::from_str(json).map_err(|e| SignerError::ContainerError(e.to_string()))
164 }
165}
166
167#[derive(Debug, Serialize, Deserialize)]
177pub struct EncryptedContainer {
178 pub salt: String,
179 pub nonce: String,
180 pub ciphertext: String,
181 pub algorithm: String,
182}
183
184#[derive(Serialize, Deserialize)]
190pub struct SigningResult {
191 pub signature: String,
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub signed_transaction: Option<String>,
196 pub public_key: String,
198}
199
200pub fn decrypt_and_sign(
217 container_json: &str,
218 passphrase: &str,
219 transaction_bytes: &[u8],
220) -> Result<SigningResult, SignerError> {
221 let container = EncryptedKeyContainer::from_json(container_json)?;
223
224 let salt =
226 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &container.salt)?;
227 let nonce =
228 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &container.nonce)?;
229 let ciphertext = base64::Engine::decode(
230 &base64::engine::general_purpose::STANDARD,
231 &container.ciphertext,
232 )?;
233
234 let mut derived_key = derive_key(passphrase.as_bytes(), &salt)?;
236
237 let cipher = Aes256Gcm::new_from_slice(derived_key.as_slice())
239 .map_err(|e| SignerError::KeyDerivationFailed(e.to_string()))?;
240
241 let plaintext = cipher
242 .decrypt(Nonce::from_slice(&nonce), ciphertext.as_slice())
243 .map_err(|_| SignerError::DecryptionFailed)?;
244
245 let mut secure_key = SecureBuffer::from_slice_with_mode(&plaintext, get_locking_mode())?;
247
248 derived_key.zeroize();
250
251 let result = sign_with_secure_key(&mut secure_key, transaction_bytes);
253
254 secure_key.zeroize();
256
257 result
258}
259
260fn sign_with_secure_key(
262 secure_key: &mut SecureBuffer,
263 transaction_bytes: &[u8],
264) -> Result<SigningResult, SignerError> {
265 if secure_key.len() != ED25519_SEED_SIZE {
267 return Err(SignerError::InvalidKeyFormat(secure_key.len()));
268 }
269
270 let signing_key = SigningKey::from_bytes(
272 secure_key
273 .as_slice()
274 .try_into()
275 .map_err(|_| SignerError::InvalidKeyFormat(secure_key.len()))?,
276 );
277
278 let public_key = signing_key.verifying_key();
280 let public_key_b58 = bs58::encode(public_key.as_bytes()).into_string();
281
282 let signature: Signature = signing_key.sign(transaction_bytes);
284
285 let signature_b58 = bs58::encode(signature.to_bytes()).into_string();
287
288 let signed_transaction = if transaction_bytes.len() >= 3 {
290 let mut signed_tx = Vec::with_capacity(1 + 64 + transaction_bytes.len());
291 signed_tx.push(1u8); signed_tx.extend_from_slice(&signature.to_bytes());
293 signed_tx.extend_from_slice(transaction_bytes);
294 Some(base64::Engine::encode(
295 &base64::engine::general_purpose::STANDARD,
296 &signed_tx,
297 ))
298 } else {
299 None
300 };
301
302 Ok(SigningResult {
303 signature: signature_b58,
304 signed_transaction,
305 public_key: public_key_b58,
306 })
307}
308
309pub fn sign_transaction(
315 private_key: &[u8],
316 transaction_bytes: &[u8],
317) -> Result<SigningResult, SignerError> {
318 let mut secure_key = SecureBuffer::from_slice_with_mode(private_key, get_locking_mode())?;
319 let result = sign_with_secure_key(&mut secure_key, transaction_bytes);
320 secure_key.zeroize();
321 result
322}
323
324pub fn create_encrypted_key_container(
326 private_key: &[u8],
327 passphrase: &str,
328) -> Result<String, SignerError> {
329 let container = EncryptedKeyContainer::encrypt(private_key, passphrase)?;
330 container.to_json()
331}
332
333pub fn encrypt_keypair(
342 keypair_bytes: &[u8],
343 passphrase: &str,
344) -> Result<EncryptedContainer, SignerError> {
345 let mut salt = [0u8; 16]; rand::thread_rng().fill_bytes(&mut salt);
347
348 let mut nonce_bytes = [0u8; NONCE_SIZE];
349 rand::thread_rng().fill_bytes(&mut nonce_bytes);
350
351 let key = derive_key_compat(passphrase.as_bytes(), &salt)?;
352 let cipher = Aes256Gcm::new_from_slice(key.as_bytes())
353 .map_err(|e| SignerError::EncryptionFailed(e.to_string()))?;
354 let nonce = Nonce::from_slice(&nonce_bytes);
355
356 let ciphertext = cipher
357 .encrypt(nonce, keypair_bytes)
358 .map_err(|e| SignerError::EncryptionFailed(e.to_string()))?;
359
360 Ok(EncryptedContainer {
361 salt: base64::Engine::encode(&base64::engine::general_purpose::STANDARD, salt),
362 nonce: base64::Engine::encode(&base64::engine::general_purpose::STANDARD, nonce_bytes),
363 ciphertext: base64::Engine::encode(
364 &base64::engine::general_purpose::STANDARD,
365 ciphertext,
366 ),
367 algorithm: "argon2id_aes256gcm".to_string(),
368 })
369}
370
371pub fn decrypt_keypair(
373 container: &EncryptedContainer,
374 passphrase: &str,
375) -> Result<SecureBuffer, SignerError> {
376 use base64::Engine;
377 let engine = base64::engine::general_purpose::STANDARD;
378
379 let salt = engine
380 .decode(&container.salt)
381 .map_err(|_| SignerError::DecryptionFailed)?;
382 let nonce_bytes = engine
383 .decode(&container.nonce)
384 .map_err(|_| SignerError::DecryptionFailed)?;
385 let ciphertext = engine
386 .decode(&container.ciphertext)
387 .map_err(|_| SignerError::DecryptionFailed)?;
388
389 let key = derive_key_compat(passphrase.as_bytes(), &salt)?;
390 let cipher = Aes256Gcm::new_from_slice(key.as_bytes())
391 .map_err(|_| SignerError::DecryptionFailed)?;
392 let nonce = Nonce::from_slice(&nonce_bytes);
393
394 let mut plaintext = cipher
395 .decrypt(nonce, ciphertext.as_ref())
396 .map_err(|_| SignerError::DecryptionFailed)?;
397
398 let buf = SecureBuffer::from_bytes(&plaintext)?;
399 plaintext.zeroize();
400 Ok(buf)
401}
402
403pub fn sign_ed25519(secret_key: &[u8], message: &[u8]) -> Result<Vec<u8>, SignerError> {
409 if secret_key.len() != 32 {
410 return Err(SignerError::InvalidKeyLength {
411 expected: 32,
412 got: secret_key.len(),
413 });
414 }
415 let key_bytes: [u8; 32] = secret_key.try_into().unwrap();
416 let signing_key = SigningKey::from_bytes(&key_bytes);
417 let signature = signing_key.sign(message);
418 Ok(signature.to_bytes().to_vec())
419}
420
421pub fn sign_secp256k1(secret_key: &[u8], message: &[u8]) -> Result<Vec<u8>, SignerError> {
423 use k256::ecdsa::SigningKey as K256SigningKey;
424 use sha3::{Digest, Keccak256};
425
426 if secret_key.len() != 32 {
427 return Err(SignerError::InvalidKeyLength {
428 expected: 32,
429 got: secret_key.len(),
430 });
431 }
432
433 let signing_key = K256SigningKey::from_bytes(secret_key.into())
434 .map_err(|e| SignerError::SigningFailed(e.to_string()))?;
435
436 let hash = Keccak256::digest(message);
438 let (signature, _recovery_id) = signing_key
439 .sign_prehash_recoverable(&hash)
440 .map_err(|e| SignerError::SigningFailed(e.to_string()))?;
441
442 Ok(signature.to_bytes().to_vec())
443}
444
445fn derive_key(passphrase: &[u8], salt: &[u8]) -> Result<SecureBuffer, SignerError> {
451 let params = Params::new(
452 ARGON2_MEMORY_COST,
453 ARGON2_TIME_COST,
454 ARGON2_PARALLELISM,
455 Some(KEY_SIZE),
456 )
457 .map_err(|e| SignerError::KeyDerivationFailed(e.to_string()))?;
458
459 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
460
461 let mut key = SecureBuffer::with_mode(KEY_SIZE, get_locking_mode())?;
462
463 argon2
464 .hash_password_into(passphrase, salt, key.as_mut_slice())
465 .map_err(|e| SignerError::KeyDerivationFailed(e.to_string()))?;
466
467 Ok(key)
468}
469
470fn derive_key_compat(passphrase: &[u8], salt: &[u8]) -> Result<SecureBuffer, SignerError> {
472 let params = Params::new(ARGON2_MEMORY_COST, ARGON2_TIME_COST, 1, Some(KEY_SIZE))
473 .map_err(|e| SignerError::KeyDerivationFailed(e.to_string()))?;
474 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
475
476 let mut key_buf = SecureBuffer::new(KEY_SIZE)?;
477 argon2
478 .hash_password_into(passphrase, salt, key_buf.as_mut_bytes())
479 .map_err(|e| SignerError::KeyDerivationFailed(e.to_string()))?;
480
481 Ok(key_buf)
482}
483
484#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn test_encrypt_decrypt_roundtrip() {
494 let mut seed = [0u8; 32];
496 OsRng.fill_bytes(&mut seed);
497 let passphrase = "test_passphrase_123";
498
499 let container = EncryptedKeyContainer::encrypt(&seed, passphrase).unwrap();
501 let json = container.to_json().unwrap();
502
503 let message = b"test transaction message";
505
506 let result = decrypt_and_sign(&json, passphrase, message).unwrap();
508
509 let signing_key = SigningKey::from_bytes(&seed);
511 let public_key = signing_key.verifying_key();
512
513 assert_eq!(
514 result.public_key,
515 bs58::encode(public_key.as_bytes()).into_string()
516 );
517 }
518
519 #[test]
520 fn test_wrong_passphrase_fails() {
521 let mut seed = [0u8; 32];
522 OsRng.fill_bytes(&mut seed);
523
524 let container = EncryptedKeyContainer::encrypt(&seed, "correct_password").unwrap();
525 let json = container.to_json().unwrap();
526
527 let result = decrypt_and_sign(&json, "wrong_password", b"test");
528 assert!(matches!(result, Err(SignerError::DecryptionFailed)));
529 }
530
531 #[test]
532 fn test_signature_verification() {
533 use ed25519_dalek::Verifier;
534
535 let mut seed = [0u8; 32];
536 OsRng.fill_bytes(&mut seed);
537 let message = b"Hello, Solana!";
538
539 let result = sign_transaction(&seed, message).unwrap();
540
541 let signing_key = SigningKey::from_bytes(&seed);
543 let signature_bytes = bs58::decode(&result.signature).into_vec().unwrap();
544 let signature = Signature::from_slice(&signature_bytes).unwrap();
545
546 assert!(signing_key
547 .verifying_key()
548 .verify(message, &signature)
549 .is_ok());
550 }
551
552 #[test]
553 fn test_encrypt_decrypt_keypair_roundtrip() {
554 let keypair = [42u8; 32];
555 let passphrase = "test-passphrase";
556
557 let container = encrypt_keypair(&keypair, passphrase).unwrap();
558 assert_eq!(container.algorithm, "argon2id_aes256gcm");
559
560 let decrypted = decrypt_keypair(&container, passphrase).unwrap();
561 assert_eq!(decrypted.as_bytes(), &keypair);
562 }
563
564 #[test]
565 fn test_decrypt_keypair_wrong_passphrase() {
566 let keypair = [42u8; 32];
567 let container = encrypt_keypair(&keypair, "correct").unwrap();
568 let result = decrypt_keypair(&container, "wrong");
569 assert!(result.is_err());
570 }
571
572 #[test]
573 fn test_sign_ed25519() {
574 let secret = [1u8; 32];
575 let message = b"hello solana";
576 let sig = sign_ed25519(&secret, message).unwrap();
577 assert_eq!(sig.len(), 64);
578 }
579
580 #[test]
581 fn test_sign_secp256k1() {
582 let secret = [2u8; 32];
583 let message = b"hello base";
584 let sig = sign_secp256k1(&secret, message).unwrap();
585 assert_eq!(sig.len(), 64);
586 }
587
588 #[test]
589 fn test_sign_invalid_key_length() {
590 let short_key = [0u8; 16];
591 assert!(sign_ed25519(&short_key, b"msg").is_err());
592 assert!(sign_secp256k1(&short_key, b"msg").is_err());
593 }
594}