Skip to main content

solana_keypair/
lib.rs

1//! Concrete implementation of a Solana `Signer` from raw bytes
2#![cfg_attr(docsrs, feature(doc_cfg))]
3use {
4    ed25519_dalek::Signer as DalekSigner,
5    solana_seed_phrase::generate_seed_from_seed_phrase_and_passphrase,
6    solana_signer::SignerError,
7    std::{
8        error,
9        io::{Read, Write},
10        path::Path,
11    },
12};
13pub use {
14    solana_address::Address,
15    solana_signature::{error::Error as SignatureError, Signature},
16    solana_signer::{EncodableKey, EncodableKeypair, Signer},
17};
18
19#[cfg(feature = "seed-derivable")]
20pub mod seed_derivable;
21pub mod signable;
22
23/// A vanilla Ed25519 key pair
24#[derive(Debug)]
25pub struct Keypair(ed25519_dalek::SigningKey);
26
27pub const KEYPAIR_LENGTH: usize = 64;
28
29impl Keypair {
30    /// Can be used for generating a Keypair without a dependency on `rand` types
31    pub const SECRET_KEY_LENGTH: usize = 32;
32
33    /// Constructs a new, random `Keypair` using `OsRng`
34    #[allow(clippy::new_without_default)]
35    pub fn new() -> Self {
36        let secret_bytes = rand::random::<[u8; Self::SECRET_KEY_LENGTH]>();
37        Self(ed25519_dalek::SigningKey::from_bytes(&secret_bytes))
38    }
39
40    /// Constructs a new `Keypair` using secret key bytes
41    pub fn new_from_array(secret_key: [u8; 32]) -> Self {
42        Self(ed25519_dalek::SigningKey::from(secret_key))
43    }
44
45    /// Returns this `Keypair` as a byte array
46    pub fn to_bytes(&self) -> [u8; KEYPAIR_LENGTH] {
47        self.0.to_keypair_bytes()
48    }
49
50    /// Recovers a `Keypair` from a base58-encoded string
51    pub fn try_from_base58_string(s: &str) -> Result<Self, SignatureError> {
52        let mut buf = [0u8; ed25519_dalek::KEYPAIR_LENGTH];
53        five8::decode_64(s, &mut buf).map_err(SignatureError::from_source)?;
54        Self::try_from(&buf[..])
55    }
56
57    /// Recovers a `Keypair` from a base58-encoded string
58    ///
59    /// # Panics
60    ///
61    /// Panics if given a malformed base58 string, or if the contents of the
62    /// encoded string is invalid Keypair data.
63    pub fn from_base58_string(s: &str) -> Self {
64        Self::try_from_base58_string(s).unwrap()
65    }
66
67    /// Returns this `Keypair` as a base58-encoded string
68    pub fn to_base58_string(&self) -> String {
69        let mut out = [0u8; five8::BASE58_ENCODED_64_MAX_LEN];
70        let len = five8::encode_64(&self.to_bytes(), &mut out);
71        unsafe { String::from_utf8_unchecked(out[..len as usize].to_vec()) }
72    }
73
74    /// Gets this `Keypair`'s secret key bytes
75    pub fn secret_bytes(&self) -> &[u8; Self::SECRET_KEY_LENGTH] {
76        self.0.as_bytes()
77    }
78
79    /// Allows Keypair cloning
80    ///
81    /// Note that the `Clone` trait is intentionally unimplemented because making a
82    /// second copy of sensitive secret keys in memory is usually a bad idea.
83    ///
84    /// Only use this in tests or when strictly required. Consider using [`std::sync::Arc<Keypair>`]
85    /// instead.
86    pub fn insecure_clone(&self) -> Self {
87        Self(self.0.clone())
88    }
89}
90
91impl TryFrom<&[u8]> for Keypair {
92    type Error = SignatureError;
93
94    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
95        let keypair_bytes: &[u8; ed25519_dalek::KEYPAIR_LENGTH] =
96            bytes.try_into().map_err(|_| {
97                SignatureError::from_source(String::from(
98                    "candidate keypair byte array is the wrong length",
99                ))
100            })?;
101        ed25519_dalek::SigningKey::from_keypair_bytes(keypair_bytes)
102            .map_err(|_| {
103                SignatureError::from_source(String::from(
104                    "keypair bytes do not specify same pubkey as derived from their secret key",
105                ))
106            })
107            .map(Self)
108    }
109}
110
111#[cfg(test)]
112static_assertions::const_assert_eq!(Keypair::SECRET_KEY_LENGTH, ed25519_dalek::SECRET_KEY_LENGTH);
113
114impl Signer for Keypair {
115    #[inline]
116    fn pubkey(&self) -> Address {
117        Address::from(self.0.verifying_key().to_bytes())
118    }
119
120    fn try_pubkey(&self) -> Result<Address, SignerError> {
121        Ok(self.pubkey())
122    }
123
124    fn sign_message(&self, message: &[u8]) -> Signature {
125        Signature::from(self.0.sign(message).to_bytes())
126    }
127
128    fn try_sign_message(&self, message: &[u8]) -> Result<Signature, SignerError> {
129        Ok(self.sign_message(message))
130    }
131
132    fn is_interactive(&self) -> bool {
133        false
134    }
135}
136
137impl<T> PartialEq<T> for Keypair
138where
139    T: Signer,
140{
141    fn eq(&self, other: &T) -> bool {
142        self.pubkey() == other.pubkey()
143    }
144}
145
146impl EncodableKey for Keypair {
147    fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
148        read_keypair(reader)
149    }
150
151    fn write<W: Write>(&self, writer: &mut W) -> Result<String, Box<dyn error::Error>> {
152        write_keypair(self, writer)
153    }
154}
155
156impl EncodableKeypair for Keypair {
157    type Pubkey = Address;
158
159    /// Returns the associated pubkey. Use this function specifically for settings that involve
160    /// reading or writing pubkeys. For other settings, use `Signer::pubkey()` instead.
161    fn encodable_pubkey(&self) -> Self::Pubkey {
162        self.pubkey()
163    }
164}
165
166/// Reads a JSON-encoded `Keypair` from a `Reader` implementor
167pub fn read_keypair<R: Read>(reader: &mut R) -> Result<Keypair, Box<dyn error::Error>> {
168    let mut buffer = String::new();
169    reader.read_to_string(&mut buffer)?;
170    let trimmed = buffer.trim();
171    if !trimmed.starts_with('[') || !trimmed.ends_with(']') {
172        return Err(std::io::Error::new(
173            std::io::ErrorKind::InvalidData,
174            "Input must be a JSON array",
175        )
176        .into());
177    }
178    // we already checked that the string has at least two chars,
179    // so 1..trimmed.len() - 1 won't be out of bounds
180    #[allow(clippy::arithmetic_side_effects)]
181    let contents = &trimmed[1..trimmed.len() - 1];
182    let elements_vec: Vec<&str> = contents.split(',').map(|s| s.trim()).collect();
183    let len = elements_vec.len();
184    let elements: [&str; ed25519_dalek::KEYPAIR_LENGTH] =
185        elements_vec.try_into().map_err(|_| {
186            std::io::Error::new(
187                std::io::ErrorKind::InvalidData,
188                format!(
189                    "Expected {} elements, found {}",
190                    ed25519_dalek::KEYPAIR_LENGTH,
191                    len
192                ),
193            )
194        })?;
195    let mut out = [0u8; ed25519_dalek::KEYPAIR_LENGTH];
196    for (idx, element) in elements.into_iter().enumerate() {
197        let parsed: u8 = element.parse()?;
198        out[idx] = parsed;
199    }
200    Keypair::try_from(&out[..]).map_err(|e| std::io::Error::other(e.to_string()).into())
201}
202
203/// Reads a `Keypair` from a file
204pub fn read_keypair_file<F: AsRef<Path>>(path: F) -> Result<Keypair, Box<dyn error::Error>> {
205    Keypair::read_from_file(path)
206}
207
208/// Writes a `Keypair` to a `Write` implementor with JSON-encoding
209pub fn write_keypair<W: Write>(
210    keypair: &Keypair,
211    writer: &mut W,
212) -> Result<String, Box<dyn error::Error>> {
213    let keypair_bytes = keypair.to_bytes();
214    let mut result = Vec::with_capacity(64 * 4 + 2); // Estimate capacity: 64 numbers * (up to 3 digits + 1 comma) + 2 brackets
215
216    result.push(b'['); // Opening bracket
217
218    for (i, &num) in keypair_bytes.iter().enumerate() {
219        if i > 0 {
220            result.push(b','); // Comma separator for all elements except the first
221        }
222
223        // Convert number to string and then to bytes
224        let num_str = num.to_string();
225        result.extend_from_slice(num_str.as_bytes());
226    }
227
228    result.push(b']'); // Closing bracket
229    writer.write_all(&result)?;
230    let as_string = String::from_utf8(result)?;
231    Ok(as_string)
232}
233
234/// Writes a `Keypair` to a file with JSON-encoding
235pub fn write_keypair_file<F: AsRef<Path>>(
236    keypair: &Keypair,
237    outfile: F,
238) -> Result<String, Box<dyn error::Error>> {
239    keypair.write_to_file(outfile)
240}
241
242/// Constructs a `Keypair` from caller-provided seed entropy
243pub fn keypair_from_seed(seed: &[u8]) -> Result<Keypair, Box<dyn error::Error>> {
244    if seed.len() < ed25519_dalek::SECRET_KEY_LENGTH {
245        return Err("Seed is too short".into());
246    }
247    // this won't fail as we've already checked the length
248    let secret_key = ed25519_dalek::SecretKey::try_from(&seed[..ed25519_dalek::SECRET_KEY_LENGTH])?;
249    Ok(Keypair(ed25519_dalek::SigningKey::from(secret_key)))
250}
251
252pub fn keypair_from_seed_phrase_and_passphrase(
253    seed_phrase: &str,
254    passphrase: &str,
255) -> Result<Keypair, Box<dyn core::error::Error>> {
256    keypair_from_seed(&generate_seed_from_seed_phrase_and_passphrase(
257        seed_phrase,
258        passphrase,
259    ))
260}
261
262#[cfg(test)]
263mod tests {
264    use {
265        super::*,
266        bip39::{Language, Mnemonic, MnemonicType, Seed},
267        solana_signer::unique_signers,
268        std::{
269            fs::{self, File},
270            mem,
271        },
272    };
273
274    fn tmp_file_path(name: &str) -> String {
275        use std::env;
276        let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
277        let keypair = Keypair::new();
278
279        format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey())
280    }
281
282    #[test]
283    fn test_write_keypair_file() {
284        let outfile = tmp_file_path("test_write_keypair_file.json");
285        let serialized_keypair = write_keypair_file(&Keypair::new(), &outfile).unwrap();
286        let keypair_vec: Vec<u8> = serde_json::from_str(&serialized_keypair).unwrap();
287        assert!(Path::new(&outfile).exists());
288        assert_eq!(
289            keypair_vec,
290            read_keypair_file(&outfile).unwrap().to_bytes().to_vec()
291        );
292
293        #[cfg(unix)]
294        {
295            use std::os::unix::fs::PermissionsExt;
296            assert_eq!(
297                File::open(&outfile)
298                    .expect("open")
299                    .metadata()
300                    .expect("metadata")
301                    .permissions()
302                    .mode()
303                    & 0o777,
304                0o600
305            );
306        }
307
308        assert_eq!(
309            read_keypair_file(&outfile).unwrap().pubkey().as_ref().len(),
310            mem::size_of::<Address>()
311        );
312        fs::remove_file(&outfile).unwrap();
313        assert!(!Path::new(&outfile).exists());
314    }
315
316    #[test]
317    fn test_write_keypair_file_overwrite_ok() {
318        let outfile = tmp_file_path("test_write_keypair_file_overwrite_ok.json");
319
320        write_keypair_file(&Keypair::new(), &outfile).unwrap();
321        write_keypair_file(&Keypair::new(), &outfile).unwrap();
322    }
323
324    #[test]
325    fn test_write_keypair_file_truncate() {
326        let outfile = tmp_file_path("test_write_keypair_file_truncate.json");
327
328        write_keypair_file(&Keypair::new(), &outfile).unwrap();
329        read_keypair_file(&outfile).unwrap();
330
331        // Ensure outfile is truncated
332        {
333            let mut f = File::create(&outfile).unwrap();
334            f.write_all(String::from_utf8([b'a'; 2048].to_vec()).unwrap().as_bytes())
335                .unwrap();
336        }
337        write_keypair_file(&Keypair::new(), &outfile).unwrap();
338        read_keypair_file(&outfile).unwrap();
339    }
340
341    #[test]
342    fn test_keypair_from_seed() {
343        let good_seed = vec![0; 32];
344        assert!(keypair_from_seed(&good_seed).is_ok());
345
346        let too_short_seed = vec![0; 31];
347        assert!(keypair_from_seed(&too_short_seed).is_err());
348    }
349
350    #[test]
351    fn test_keypair() {
352        let keypair = keypair_from_seed(&[0u8; 32]).unwrap();
353        let pubkey = keypair.pubkey();
354        let data = [1u8];
355        let sig = keypair.sign_message(&data);
356
357        // Signer
358        assert_eq!(keypair.try_pubkey().unwrap(), pubkey);
359        assert_eq!(keypair.pubkey(), pubkey);
360        assert_eq!(keypair.try_sign_message(&data).unwrap(), sig);
361        assert_eq!(keypair.sign_message(&data), sig);
362
363        // PartialEq
364        let keypair2 = keypair_from_seed(&[0u8; 32]).unwrap();
365        assert_eq!(keypair, keypair2);
366    }
367
368    fn pubkeys(signers: &[&dyn Signer]) -> Vec<Address> {
369        signers.iter().map(|x| x.pubkey()).collect()
370    }
371
372    #[test]
373    fn test_unique_signers() {
374        let alice = Keypair::new();
375        let bob = Keypair::new();
376        assert_eq!(
377            pubkeys(&unique_signers(vec![&alice, &bob, &alice])),
378            pubkeys(&[&alice, &bob])
379        );
380    }
381
382    #[test]
383    fn test_containers() {
384        use std::{rc::Rc, sync::Arc};
385
386        struct Foo<S: Signer> {
387            #[allow(unused)]
388            signer: S,
389        }
390
391        fn foo(_s: impl Signer) {}
392
393        let _arc_signer = Foo {
394            signer: Arc::new(Keypair::new()),
395        };
396        foo(Arc::new(Keypair::new()));
397
398        let _rc_signer = Foo {
399            signer: Rc::new(Keypair::new()),
400        };
401        foo(Rc::new(Keypair::new()));
402
403        let _ref_signer = Foo {
404            signer: &Keypair::new(),
405        };
406        foo(Keypair::new());
407
408        let _box_signer = Foo {
409            signer: Box::new(Keypair::new()),
410        };
411        foo(Box::new(Keypair::new()));
412
413        let _signer = Foo {
414            signer: Keypair::new(),
415        };
416        foo(Keypair::new());
417    }
418
419    #[test]
420    fn test_keypair_from_seed_phrase_and_passphrase() {
421        let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English);
422        let passphrase = "42";
423        let seed = Seed::new(&mnemonic, passphrase);
424        let expected_keypair = keypair_from_seed(seed.as_bytes()).unwrap();
425        let keypair =
426            keypair_from_seed_phrase_and_passphrase(mnemonic.phrase(), passphrase).unwrap();
427        assert_eq!(keypair.pubkey(), expected_keypair.pubkey());
428    }
429
430    #[test]
431    fn test_base58() {
432        let keypair = keypair_from_seed(&[0u8; 32]).unwrap();
433        let as_base58 = keypair.to_base58_string();
434        let parsed = Keypair::from_base58_string(&as_base58);
435        assert_eq!(keypair, parsed);
436    }
437}