clone_solana_keypair/
lib.rs

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