solana_sdk/signer/
keypair.rs

1#![cfg(feature = "full")]
2
3use {
4    crate::{
5        derivation_path::DerivationPath,
6        pubkey::Pubkey,
7        signature::Signature,
8        signer::{Signer, SignerError},
9    },
10    ed25519_dalek::Signer as DalekSigner,
11    ed25519_dalek_bip32::Error as Bip32Error,
12    hmac::Hmac,
13    rand::{rngs::OsRng, CryptoRng, RngCore},
14    std::{
15        error,
16        fs::{self, File, OpenOptions},
17        io::{Read, Write},
18        path::Path,
19    },
20    wasm_bindgen::prelude::*,
21};
22
23/// A vanilla Ed25519 key pair
24#[wasm_bindgen]
25#[derive(Debug)]
26pub struct Keypair(ed25519_dalek::Keypair);
27
28impl Keypair {
29    /// Constructs a new, random `Keypair` using a caller-provided RNG
30    pub fn generate<R>(csprng: &mut R) -> Self
31    where
32        R: CryptoRng + RngCore,
33    {
34        Self(ed25519_dalek::Keypair::generate(csprng))
35    }
36
37    /// Constructs a new, random `Keypair` using `OsRng`
38    pub fn new() -> Self {
39        let mut rng = OsRng::default();
40        Self::generate(&mut rng)
41    }
42
43    /// Recovers a `Keypair` from a byte array
44    pub fn from_bytes(bytes: &[u8]) -> Result<Self, ed25519_dalek::SignatureError> {
45        ed25519_dalek::Keypair::from_bytes(bytes).map(Self)
46    }
47
48    /// Returns this `Keypair` as a byte array
49    pub fn to_bytes(&self) -> [u8; 64] {
50        self.0.to_bytes()
51    }
52
53    /// Recovers a `Keypair` from a base58-encoded string
54    pub fn from_base58_string(s: &str) -> Self {
55        Self::from_bytes(&bs58::decode(s).into_vec().unwrap()).unwrap()
56    }
57
58    /// Returns this `Keypair` as a base58-encoded string
59    pub fn to_base58_string(&self) -> String {
60        bs58::encode(&self.0.to_bytes()).into_string()
61    }
62
63    /// Gets this `Keypair`'s SecretKey
64    pub fn secret(&self) -> &ed25519_dalek::SecretKey {
65        &self.0.secret
66    }
67}
68
69impl Signer for Keypair {
70    #[inline]
71    fn pubkey(&self) -> Pubkey {
72        Pubkey::from(self.0.public.to_bytes())
73    }
74
75    fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
76        Ok(self.pubkey())
77    }
78
79    fn sign_message(&self, message: &[u8]) -> Signature {
80        Signature::new(&self.0.sign(message).to_bytes())
81    }
82
83    fn try_sign_message(&self, message: &[u8]) -> Result<Signature, SignerError> {
84        Ok(self.sign_message(message))
85    }
86
87    fn is_interactive(&self) -> bool {
88        false
89    }
90}
91
92impl<T> PartialEq<T> for Keypair
93where
94    T: Signer,
95{
96    fn eq(&self, other: &T) -> bool {
97        self.pubkey() == other.pubkey()
98    }
99}
100
101/// Reads a JSON-encoded `Keypair` from a `Reader` implementor
102pub fn read_keypair<R: Read>(reader: &mut R) -> Result<Keypair, Box<dyn error::Error>> {
103    let bytes: Vec<u8> = serde_json::from_reader(reader)?;
104    let dalek_keypair = ed25519_dalek::Keypair::from_bytes(&bytes)
105        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
106    Ok(Keypair(dalek_keypair))
107}
108
109/// Reads a `Keypair` from a file
110pub fn read_keypair_file<F: AsRef<Path>>(path: F) -> Result<Keypair, Box<dyn error::Error>> {
111    let mut file = File::open(path.as_ref())?;
112    read_keypair(&mut file)
113}
114
115/// Writes a `Keypair` to a `Write` implementor with JSON-encoding
116pub fn write_keypair<W: Write>(
117    keypair: &Keypair,
118    writer: &mut W,
119) -> Result<String, Box<dyn error::Error>> {
120    let keypair_bytes = keypair.0.to_bytes();
121    let serialized = serde_json::to_string(&keypair_bytes.to_vec())?;
122    writer.write_all(&serialized.clone().into_bytes())?;
123    Ok(serialized)
124}
125
126/// Writes a `Keypair` to a file with JSON-encoding
127pub fn write_keypair_file<F: AsRef<Path>>(
128    keypair: &Keypair,
129    outfile: F,
130) -> Result<String, Box<dyn error::Error>> {
131    let outfile = outfile.as_ref();
132
133    if let Some(outdir) = outfile.parent() {
134        fs::create_dir_all(outdir)?;
135    }
136
137    let mut f = {
138        #[cfg(not(unix))]
139        {
140            OpenOptions::new()
141        }
142        #[cfg(unix)]
143        {
144            use std::os::unix::fs::OpenOptionsExt;
145            OpenOptions::new().mode(0o600)
146        }
147    }
148    .write(true)
149    .truncate(true)
150    .create(true)
151    .open(outfile)?;
152
153    write_keypair(keypair, &mut f)
154}
155
156/// Constructs a `Keypair` from caller-provided seed entropy
157pub fn keypair_from_seed(seed: &[u8]) -> Result<Keypair, Box<dyn error::Error>> {
158    if seed.len() < ed25519_dalek::SECRET_KEY_LENGTH {
159        return Err("Seed is too short".into());
160    }
161    let secret = ed25519_dalek::SecretKey::from_bytes(&seed[..ed25519_dalek::SECRET_KEY_LENGTH])
162        .map_err(|e| e.to_string())?;
163    let public = ed25519_dalek::PublicKey::from(&secret);
164    let dalek_keypair = ed25519_dalek::Keypair { secret, public };
165    Ok(Keypair(dalek_keypair))
166}
167
168/// Generates a Keypair using Bip32 Hierarchical Derivation if derivation-path is provided;
169/// otherwise generates the base Bip44 Safecoin keypair from the seed
170pub fn keypair_from_seed_and_derivation_path(
171    seed: &[u8],
172    derivation_path: Option<DerivationPath>,
173) -> Result<Keypair, Box<dyn error::Error>> {
174    let derivation_path = derivation_path.unwrap_or_default();
175    bip32_derived_keypair(seed, derivation_path).map_err(|err| err.to_string().into())
176}
177
178/// Generates a Keypair using Bip32 Hierarchical Derivation
179fn bip32_derived_keypair(
180    seed: &[u8],
181    derivation_path: DerivationPath,
182) -> Result<Keypair, Bip32Error> {
183    let extended = ed25519_dalek_bip32::ExtendedSecretKey::from_seed(seed)
184        .and_then(|extended| extended.derive(&derivation_path))?;
185    let extended_public_key = extended.public_key();
186    Ok(Keypair(ed25519_dalek::Keypair {
187        secret: extended.secret_key,
188        public: extended_public_key,
189    }))
190}
191
192pub fn generate_seed_from_seed_phrase_and_passphrase(
193    seed_phrase: &str,
194    passphrase: &str,
195) -> Vec<u8> {
196    const PBKDF2_ROUNDS: u32 = 2048;
197    const PBKDF2_BYTES: usize = 64;
198
199    let salt = format!("mnemonic{}", passphrase);
200
201    let mut seed = vec![0u8; PBKDF2_BYTES];
202    pbkdf2::pbkdf2::<Hmac<sha2::Sha512>>(
203        seed_phrase.as_bytes(),
204        salt.as_bytes(),
205        PBKDF2_ROUNDS,
206        &mut seed,
207    );
208    seed
209}
210
211pub fn keypair_from_seed_phrase_and_passphrase(
212    seed_phrase: &str,
213    passphrase: &str,
214) -> Result<Keypair, Box<dyn error::Error>> {
215    keypair_from_seed(&generate_seed_from_seed_phrase_and_passphrase(
216        seed_phrase,
217        passphrase,
218    ))
219}
220
221#[cfg(test)]
222mod tests {
223    use {
224        super::*,
225        bip39::{Language, Mnemonic, MnemonicType, Seed},
226        std::mem,
227    };
228
229    fn tmp_file_path(name: &str) -> String {
230        use std::env;
231        let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
232        let keypair = Keypair::new();
233
234        format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey())
235    }
236
237    #[test]
238    fn test_write_keypair_file() {
239        let outfile = tmp_file_path("test_write_keypair_file.json");
240        let serialized_keypair = write_keypair_file(&Keypair::new(), &outfile).unwrap();
241        let keypair_vec: Vec<u8> = serde_json::from_str(&serialized_keypair).unwrap();
242        assert!(Path::new(&outfile).exists());
243        assert_eq!(
244            keypair_vec,
245            read_keypair_file(&outfile).unwrap().0.to_bytes().to_vec()
246        );
247
248        #[cfg(unix)]
249        {
250            use std::os::unix::fs::PermissionsExt;
251            assert_eq!(
252                File::open(&outfile)
253                    .expect("open")
254                    .metadata()
255                    .expect("metadata")
256                    .permissions()
257                    .mode()
258                    & 0o777,
259                0o600
260            );
261        }
262
263        assert_eq!(
264            read_keypair_file(&outfile).unwrap().pubkey().as_ref().len(),
265            mem::size_of::<Pubkey>()
266        );
267        fs::remove_file(&outfile).unwrap();
268        assert!(!Path::new(&outfile).exists());
269    }
270
271    #[test]
272    fn test_write_keypair_file_overwrite_ok() {
273        let outfile = tmp_file_path("test_write_keypair_file_overwrite_ok.json");
274
275        write_keypair_file(&Keypair::new(), &outfile).unwrap();
276        write_keypair_file(&Keypair::new(), &outfile).unwrap();
277    }
278
279    #[test]
280    fn test_write_keypair_file_truncate() {
281        let outfile = tmp_file_path("test_write_keypair_file_truncate.json");
282
283        write_keypair_file(&Keypair::new(), &outfile).unwrap();
284        read_keypair_file(&outfile).unwrap();
285
286        // Ensure outfile is truncated
287        {
288            let mut f = File::create(&outfile).unwrap();
289            f.write_all(String::from_utf8([b'a'; 2048].to_vec()).unwrap().as_bytes())
290                .unwrap();
291        }
292        write_keypair_file(&Keypair::new(), &outfile).unwrap();
293        read_keypair_file(&outfile).unwrap();
294    }
295
296    #[test]
297    fn test_keypair_from_seed() {
298        let good_seed = vec![0; 32];
299        assert!(keypair_from_seed(&good_seed).is_ok());
300
301        let too_short_seed = vec![0; 31];
302        assert!(keypair_from_seed(&too_short_seed).is_err());
303    }
304
305    #[test]
306    fn test_keypair_from_seed_phrase_and_passphrase() {
307        let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English);
308        let passphrase = "42";
309        let seed = Seed::new(&mnemonic, passphrase);
310        let expected_keypair = keypair_from_seed(seed.as_bytes()).unwrap();
311        let keypair =
312            keypair_from_seed_phrase_and_passphrase(mnemonic.phrase(), passphrase).unwrap();
313        assert_eq!(keypair.pubkey(), expected_keypair.pubkey());
314    }
315
316    #[test]
317    fn test_keypair() {
318        let keypair = keypair_from_seed(&[0u8; 32]).unwrap();
319        let pubkey = keypair.pubkey();
320        let data = [1u8];
321        let sig = keypair.sign_message(&data);
322
323        // Signer
324        assert_eq!(keypair.try_pubkey().unwrap(), pubkey);
325        assert_eq!(keypair.pubkey(), pubkey);
326        assert_eq!(keypair.try_sign_message(&data).unwrap(), sig);
327        assert_eq!(keypair.sign_message(&data), sig);
328
329        // PartialEq
330        let keypair2 = keypair_from_seed(&[0u8; 32]).unwrap();
331        assert_eq!(keypair, keypair2);
332    }
333}