solana_keypair/
lib.rs

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