Skip to main content

solana_keychain/memory/
mod.rs

1//! Memory-based local keypair signer
2
3mod keypair_util;
4
5use crate::{
6    error::SignerError,
7    sdk_adapter::keypair_from_bytes,
8    traits::{SignedTransaction, SolanaSigner},
9    transaction_util::TransactionUtil,
10};
11
12use crate::sdk_adapter::{
13    keypair_pubkey, keypair_sign_message, Keypair, Pubkey, Signature, Transaction,
14};
15use keypair_util::KeypairUtil;
16
17/// A Solana-based signer that uses an in-memory keypair
18pub struct MemorySigner {
19    keypair: Keypair,
20}
21
22impl std::fmt::Debug for MemorySigner {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        f.debug_struct("MemorySigner")
25            .field("pubkey", &keypair_pubkey(&self.keypair))
26            .finish_non_exhaustive()
27    }
28}
29
30impl MemorySigner {
31    /// Creates a new signer from a Solana keypair
32    pub fn new(keypair: Keypair) -> Self {
33        Self { keypair }
34    }
35
36    /// Creates a new signer from a private key byte array
37    pub fn from_bytes(private_key: &[u8]) -> Result<Self, SignerError> {
38        let keypair = keypair_from_bytes(private_key).map_err(|e| {
39            SignerError::InvalidPrivateKey(format!("Invalid private key bytes: {e}"))
40        })?;
41        Ok(Self { keypair })
42    }
43
44    /// Creates a new signer from a private key string that can be in multiple formats:
45    /// - Base58 encoded string
46    /// - U8Array format: "[0, 1, 2, ...]"
47    /// - File path to a JSON keypair file
48    pub fn from_private_key_string(private_key: &str) -> Result<Self, SignerError> {
49        let keypair = KeypairUtil::from_private_key_string(private_key)?;
50        Ok(Self::new(keypair))
51    }
52
53    async fn sign_bytes(&self, serialized: &[u8]) -> Result<Signature, SignerError> {
54        Ok(keypair_sign_message(&self.keypair, serialized))
55    }
56}
57
58#[async_trait::async_trait]
59impl SolanaSigner for MemorySigner {
60    fn pubkey(&self) -> Pubkey {
61        keypair_pubkey(&self.keypair)
62    }
63
64    async fn sign_transaction(
65        &self,
66        tx: &mut Transaction,
67    ) -> Result<SignedTransaction, SignerError> {
68        let signature = self.sign_bytes(&tx.message_data()).await?;
69
70        TransactionUtil::add_signature_to_transaction(tx, &self.pubkey(), signature)?;
71
72        Ok((TransactionUtil::serialize_transaction(tx)?, signature))
73    }
74
75    async fn sign_message(&self, message: &[u8]) -> Result<Signature, SignerError> {
76        self.sign_bytes(message).await
77    }
78
79    async fn sign_partial_transaction(
80        &self,
81        tx: &mut Transaction,
82    ) -> Result<SignedTransaction, SignerError> {
83        let signature = self.sign_bytes(&tx.message_data()).await?;
84
85        TransactionUtil::add_signature_to_transaction(tx, &self.pubkey(), signature)?;
86
87        Ok((TransactionUtil::serialize_transaction(tx)?, signature))
88    }
89
90    async fn is_available(&self) -> bool {
91        // Memory signer is always available
92        true
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use crate::test_util::create_test_transaction;
99
100    use super::*;
101
102    const TEST_KEYPAIR_BYTES: &str = "[41,99,180,88,51,57,48,80,61,63,219,75,176,49,116,254,227,176,196,204,122,47,166,133,155,252,217,0,253,17,49,143,47,94,121,167,195,136,72,22,157,48,77,88,63,96,57,122,181,243,236,188,241,134,174,224,100,246,17,170,104,17,151,48]";
103    const TEST_PUBKEY: &str = "4BuiY9QUUfPoAGNJBja3JapAuVWMc9c7in6UCgyC2zPR";
104
105    fn create_test_signer() -> MemorySigner {
106        MemorySigner::from_private_key_string(TEST_KEYPAIR_BYTES)
107            .expect("Failed to create test signer")
108    }
109
110    #[test]
111    fn test_create_from_u8_array() {
112        let signer = MemorySigner::from_private_key_string(TEST_KEYPAIR_BYTES);
113        assert!(signer.is_ok());
114    }
115
116    #[test]
117    fn test_pubkey() {
118        let signer = create_test_signer();
119        let pubkey = signer.pubkey();
120        assert_eq!(pubkey.to_string(), TEST_PUBKEY);
121    }
122
123    #[tokio::test]
124    async fn test_sign_message() {
125        let signer = create_test_signer();
126        let message = b"Hello Solana!";
127        let signature = signer.sign_message(message).await;
128
129        assert!(signature.is_ok());
130        let sig = signature.unwrap();
131        // Solana signatures are 64 bytes
132        assert_eq!(sig.as_ref().len(), 64);
133    }
134
135    #[tokio::test]
136    async fn test_is_available() {
137        let signer = create_test_signer();
138        assert!(signer.is_available().await);
139    }
140
141    #[tokio::test]
142    async fn test_sign_transaction() {
143        let signer = create_test_signer();
144
145        let mut tx = create_test_transaction(&keypair_pubkey(&signer.keypair));
146
147        let result = signer.sign_transaction(&mut tx).await;
148        assert!(result.is_ok());
149
150        let (serialized_tx, signature) = result.unwrap();
151
152        // Verify the signature is valid
153        assert_eq!(signature.as_ref().len(), 64);
154
155        // Verify the transaction is properly serialized
156        assert!(!serialized_tx.is_empty());
157
158        // Verify the transaction has the signature
159        assert_eq!(tx.signatures.len(), 1);
160        assert_eq!(tx.signatures[0], signature);
161    }
162
163    #[tokio::test]
164    async fn test_sign_partial_transaction() {
165        let signer = create_test_signer();
166
167        let mut tx = create_test_transaction(&keypair_pubkey(&signer.keypair));
168
169        let result = signer.sign_partial_transaction(&mut tx).await;
170        assert!(result.is_ok());
171
172        let (serialized_tx, signature) = result.unwrap();
173
174        // Verify the signature is valid
175        assert_eq!(signature.as_ref().len(), 64);
176
177        // Verify the transaction is properly serialized
178        assert!(!serialized_tx.is_empty());
179
180        // Verify the transaction has the signature
181        assert_eq!(tx.signatures.len(), 1);
182        assert_eq!(tx.signatures[0], signature);
183    }
184}