solana_clap_utils/
keypair.rs

1//! Loading signers and keypairs from the command line.
2//!
3//! This module contains utilities for loading [Signer]s and [Keypair]s from
4//! standard signing sources, from the command line, as in the Solana CLI.
5//!
6//! The key function here is [`signer_from_path`], which loads a `Signer` from
7//! one of several possible sources by interpreting a "path" command line
8//! argument. Its documentation includes a description of all possible signing
9//! sources supported by the Solana CLI. Many other functions here are
10//! variations on, or delegate to, `signer_from_path`.
11
12use {
13    crate::{
14        input_parsers::{pubkeys_sigs_of, STDOUT_OUTFILE_TOKEN},
15        offline::{SIGNER_ARG, SIGN_ONLY_ARG},
16        ArgConstant,
17    },
18    bip39::{Language, Mnemonic, Seed},
19    clap::ArgMatches,
20    rpassword::prompt_password,
21    solana_derivation_path::{DerivationPath, DerivationPathError},
22    solana_remote_wallet::{
23        locator::{Locator as RemoteWalletLocator, LocatorError as RemoteWalletLocatorError},
24        remote_keypair::generate_remote_keypair,
25        remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
26    },
27    solana_sdk::{
28        hash::Hash,
29        message::Message,
30        pubkey::Pubkey,
31        signature::{
32            generate_seed_from_seed_phrase_and_passphrase, keypair_from_seed,
33            keypair_from_seed_and_derivation_path, keypair_from_seed_phrase_and_passphrase,
34            read_keypair, read_keypair_file, Keypair, NullSigner, Presigner, Signature, Signer,
35        },
36    },
37    std::{
38        cell::RefCell,
39        convert::TryFrom,
40        error,
41        io::{stdin, stdout, Write},
42        ops::Deref,
43        process::exit,
44        rc::Rc,
45        str::FromStr,
46    },
47    thiserror::Error,
48};
49
50pub struct SignOnly {
51    pub blockhash: Hash,
52    pub message: Option<String>,
53    pub present_signers: Vec<(Pubkey, Signature)>,
54    pub absent_signers: Vec<Pubkey>,
55    pub bad_signers: Vec<Pubkey>,
56}
57
58impl SignOnly {
59    pub fn has_all_signers(&self) -> bool {
60        self.absent_signers.is_empty() && self.bad_signers.is_empty()
61    }
62
63    pub fn presigner_of(&self, pubkey: &Pubkey) -> Option<Presigner> {
64        presigner_from_pubkey_sigs(pubkey, &self.present_signers)
65    }
66}
67pub type CliSigners = Vec<Box<dyn Signer>>;
68pub type SignerIndex = usize;
69pub struct CliSignerInfo {
70    pub signers: CliSigners,
71}
72
73impl CliSignerInfo {
74    pub fn index_of(&self, pubkey: Option<Pubkey>) -> Option<usize> {
75        if let Some(pubkey) = pubkey {
76            self.signers
77                .iter()
78                .position(|signer| signer.pubkey() == pubkey)
79        } else {
80            Some(0)
81        }
82    }
83    pub fn index_of_or_none(&self, pubkey: Option<Pubkey>) -> Option<usize> {
84        if let Some(pubkey) = pubkey {
85            self.signers
86                .iter()
87                .position(|signer| signer.pubkey() == pubkey)
88        } else {
89            None
90        }
91    }
92    pub fn signers_for_message(&self, message: &Message) -> Vec<&dyn Signer> {
93        self.signers
94            .iter()
95            .filter_map(|k| {
96                if message.signer_keys().contains(&&k.pubkey()) {
97                    Some(k.as_ref())
98                } else {
99                    None
100                }
101            })
102            .collect()
103    }
104}
105
106/// A command line argument that loads a default signer in absence of other signers.
107///
108/// This type manages a default signing source which may be overridden by other
109/// signing sources via its [`generate_unique_signers`] method.
110///
111/// [`generate_unique_signers`]: DefaultSigner::generate_unique_signers
112///
113/// `path` is a signing source as documented by [`signer_from_path`], and
114/// `arg_name` is the name of its [clap] command line argument, which is passed
115/// to `signer_from_path` as its `keypair_name` argument.
116#[derive(Debug, Default)]
117pub struct DefaultSigner {
118    /// The name of the signers command line argument.
119    pub arg_name: String,
120    /// The signing source.
121    pub path: String,
122    is_path_checked: RefCell<bool>,
123}
124
125impl DefaultSigner {
126    /// Create a new `DefaultSigner`.
127    ///
128    /// `path` is a signing source as documented by [`signer_from_path`], and
129    /// `arg_name` is the name of its [clap] command line argument, which is
130    /// passed to `signer_from_path` as its `keypair_name` argument.
131    ///
132    /// [clap]: https://docs.rs/clap
133    ///
134    /// # Examples
135    ///
136    /// ```no_run
137    /// use clap::{App, Arg, value_t_or_exit};
138    /// use solana_clap_utils::keypair::DefaultSigner;
139    /// use solana_clap_utils::offline::OfflineArgs;
140    ///
141    /// let clap_app = App::new("my-program")
142    ///     // The argument we'll parse as a signer "path"
143    ///     .arg(Arg::with_name("keypair")
144    ///         .required(true)
145    ///         .help("The default signer"))
146    ///     .offline_args();
147    ///
148    /// let clap_matches = clap_app.get_matches();
149    /// let keypair_str = value_t_or_exit!(clap_matches, "keypair", String);
150    ///
151    /// let default_signer = DefaultSigner::new("keypair", &keypair_str);
152    /// # assert!(default_signer.arg_name.len() > 0);
153    /// assert_eq!(default_signer.path, keypair_str);
154    /// # Ok::<(), Box<dyn std::error::Error>>(())
155    /// ```
156    pub fn new<AN: AsRef<str>, P: AsRef<str>>(arg_name: AN, path: P) -> Self {
157        let arg_name = arg_name.as_ref().to_string();
158        let path = path.as_ref().to_string();
159        Self {
160            arg_name,
161            path,
162            ..Self::default()
163        }
164    }
165
166    fn path(&self) -> Result<&str, Box<dyn std::error::Error>> {
167        if !self.is_path_checked.borrow().deref() {
168            parse_signer_source(&self.path)
169                .and_then(|s| {
170                    if let SignerSourceKind::Filepath(path) = &s.kind {
171                        std::fs::metadata(path).map(|_| ()).map_err(|e| e.into())
172                    } else {
173                        Ok(())
174                    }
175                })
176                .map_err(|_| {
177                    std::io::Error::new(
178                        std::io::ErrorKind::Other,
179                        format!(
180                        "No default signer found, run \"solana-keygen new -o {}\" to create a new one",
181                        self.path
182                    ),
183                    )
184                })?;
185            *self.is_path_checked.borrow_mut() = true;
186        }
187        Ok(&self.path)
188    }
189
190    /// Generate a unique set of signers, possibly excluding this default signer.
191    ///
192    /// This function allows a command line application to have a default
193    /// signer, perhaps representing a default wallet, but to override that
194    /// signer and instead sign with one or more other signers.
195    ///
196    /// `bulk_signers` is a vector of signers, all of which are optional. If any
197    /// of those signers is `None`, then the default signer will be loaded; if
198    /// all of those signers are `Some`, then the default signer will not be
199    /// loaded.
200    ///
201    /// The returned value includes all of the `bulk_signers` that were not
202    /// `None`, and maybe the default signer, if it was loaded.
203    ///
204    /// # Examples
205    ///
206    /// ```no_run
207    /// use clap::{App, Arg, value_t_or_exit};
208    /// use solana_clap_utils::keypair::{DefaultSigner, signer_from_path};
209    /// use solana_clap_utils::offline::OfflineArgs;
210    /// use solana_sdk::signer::Signer;
211    ///
212    /// let clap_app = App::new("my-program")
213    ///     // The argument we'll parse as a signer "path"
214    ///     .arg(Arg::with_name("keypair")
215    ///         .required(true)
216    ///         .help("The default signer"))
217    ///     .arg(Arg::with_name("payer")
218    ///         .long("payer")
219    ///         .help("The account paying for the transaction"))
220    ///     .offline_args();
221    ///
222    /// let mut wallet_manager = None;
223    ///
224    /// let clap_matches = clap_app.get_matches();
225    /// let keypair_str = value_t_or_exit!(clap_matches, "keypair", String);
226    /// let maybe_payer = clap_matches.value_of("payer");
227    ///
228    /// let default_signer = DefaultSigner::new("keypair", &keypair_str);
229    /// let maybe_payer_signer = maybe_payer.map(|payer| {
230    ///     signer_from_path(&clap_matches, payer, "payer", &mut wallet_manager)
231    /// }).transpose()?;
232    /// let bulk_signers = vec![maybe_payer_signer];
233    ///
234    /// let unique_signers = default_signer.generate_unique_signers(
235    ///     bulk_signers,
236    ///     &clap_matches,
237    ///     &mut wallet_manager,
238    /// )?;
239    /// # Ok::<(), Box<dyn std::error::Error>>(())
240    /// ```
241    pub fn generate_unique_signers(
242        &self,
243        bulk_signers: Vec<Option<Box<dyn Signer>>>,
244        matches: &ArgMatches<'_>,
245        wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
246    ) -> Result<CliSignerInfo, Box<dyn error::Error>> {
247        let mut unique_signers = vec![];
248
249        // Determine if the default signer is needed
250        if bulk_signers.iter().any(|signer| signer.is_none()) {
251            let default_signer = self.signer_from_path(matches, wallet_manager)?;
252            unique_signers.push(default_signer);
253        }
254
255        for signer in bulk_signers.into_iter().flatten() {
256            if !unique_signers.iter().any(|s| s == &signer) {
257                unique_signers.push(signer);
258            }
259        }
260        Ok(CliSignerInfo {
261            signers: unique_signers,
262        })
263    }
264
265    /// Loads the default [Signer] from one of several possible sources.
266    ///
267    /// The `path` is not strictly a file system path, but is interpreted as
268    /// various types of _signing source_, depending on its format, one of which
269    /// is a path to a keypair file. Some sources may require user interaction
270    /// in the course of calling this function.
271    ///
272    /// This simply delegates to the [`signer_from_path`] free function, passing
273    /// it the `DefaultSigner`s `path` and `arg_name` fields as the `path` and
274    /// `keypair_name` arguments.
275    ///
276    /// See the [`signer_from_path`] free function for full documentation of how
277    /// this function interprets its arguments.
278    ///
279    /// # Examples
280    ///
281    /// ```no_run
282    /// use clap::{App, Arg, value_t_or_exit};
283    /// use solana_clap_utils::keypair::DefaultSigner;
284    /// use solana_clap_utils::offline::OfflineArgs;
285    ///
286    /// let clap_app = App::new("my-program")
287    ///     // The argument we'll parse as a signer "path"
288    ///     .arg(Arg::with_name("keypair")
289    ///         .required(true)
290    ///         .help("The default signer"))
291    ///     .offline_args();
292    ///
293    /// let clap_matches = clap_app.get_matches();
294    /// let keypair_str = value_t_or_exit!(clap_matches, "keypair", String);
295    /// let default_signer = DefaultSigner::new("keypair", &keypair_str);
296    /// let mut wallet_manager = None;
297    ///
298    /// let signer = default_signer.signer_from_path(
299    ///     &clap_matches,
300    ///     &mut wallet_manager,
301    /// )?;
302    /// # Ok::<(), Box<dyn std::error::Error>>(())
303    /// ```
304    pub fn signer_from_path(
305        &self,
306        matches: &ArgMatches,
307        wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
308    ) -> Result<Box<dyn Signer>, Box<dyn std::error::Error>> {
309        signer_from_path(matches, self.path()?, &self.arg_name, wallet_manager)
310    }
311
312    /// Loads the default [Signer] from one of several possible sources.
313    ///
314    /// The `path` is not strictly a file system path, but is interpreted as
315    /// various types of _signing source_, depending on its format, one of which
316    /// is a path to a keypair file. Some sources may require user interaction
317    /// in the course of calling this function.
318    ///
319    /// This simply delegates to the [`signer_from_path_with_config`] free
320    /// function, passing it the `DefaultSigner`s `path` and `arg_name` fields
321    /// as the `path` and `keypair_name` arguments.
322    ///
323    /// See the [`signer_from_path`] free function for full documentation of how
324    /// this function interprets its arguments.
325    ///
326    /// # Examples
327    ///
328    /// ```no_run
329    /// use clap::{App, Arg, value_t_or_exit};
330    /// use solana_clap_utils::keypair::{SignerFromPathConfig, DefaultSigner};
331    /// use solana_clap_utils::offline::OfflineArgs;
332    ///
333    /// let clap_app = App::new("my-program")
334    ///     // The argument we'll parse as a signer "path"
335    ///     .arg(Arg::with_name("keypair")
336    ///         .required(true)
337    ///         .help("The default signer"))
338    ///     .offline_args();
339    ///
340    /// let clap_matches = clap_app.get_matches();
341    /// let keypair_str = value_t_or_exit!(clap_matches, "keypair", String);
342    /// let default_signer = DefaultSigner::new("keypair", &keypair_str);
343    /// let mut wallet_manager = None;
344    ///
345    /// // Allow pubkey signers without accompanying signatures
346    /// let config = SignerFromPathConfig {
347    ///     allow_null_signer: true,
348    /// };
349    ///
350    /// let signer = default_signer.signer_from_path_with_config(
351    ///     &clap_matches,
352    ///     &mut wallet_manager,
353    ///     &config,
354    /// )?;
355    /// # Ok::<(), Box<dyn std::error::Error>>(())
356    /// ```
357    pub fn signer_from_path_with_config(
358        &self,
359        matches: &ArgMatches,
360        wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
361        config: &SignerFromPathConfig,
362    ) -> Result<Box<dyn Signer>, Box<dyn std::error::Error>> {
363        signer_from_path_with_config(
364            matches,
365            self.path()?,
366            &self.arg_name,
367            wallet_manager,
368            config,
369        )
370    }
371}
372
373#[derive(Debug)]
374pub(crate) struct SignerSource {
375    pub kind: SignerSourceKind,
376    pub derivation_path: Option<DerivationPath>,
377    pub legacy: bool,
378}
379
380impl SignerSource {
381    fn new(kind: SignerSourceKind) -> Self {
382        Self {
383            kind,
384            derivation_path: None,
385            legacy: false,
386        }
387    }
388
389    fn new_legacy(kind: SignerSourceKind) -> Self {
390        Self {
391            kind,
392            derivation_path: None,
393            legacy: true,
394        }
395    }
396}
397
398const SIGNER_SOURCE_PROMPT: &str = "prompt";
399const SIGNER_SOURCE_FILEPATH: &str = "file";
400const SIGNER_SOURCE_USB: &str = "usb";
401const SIGNER_SOURCE_STDIN: &str = "stdin";
402const SIGNER_SOURCE_PUBKEY: &str = "pubkey";
403
404pub(crate) enum SignerSourceKind {
405    Prompt,
406    Filepath(String),
407    Usb(RemoteWalletLocator),
408    Stdin,
409    Pubkey(Pubkey),
410}
411
412impl AsRef<str> for SignerSourceKind {
413    fn as_ref(&self) -> &str {
414        match self {
415            Self::Prompt => SIGNER_SOURCE_PROMPT,
416            Self::Filepath(_) => SIGNER_SOURCE_FILEPATH,
417            Self::Usb(_) => SIGNER_SOURCE_USB,
418            Self::Stdin => SIGNER_SOURCE_STDIN,
419            Self::Pubkey(_) => SIGNER_SOURCE_PUBKEY,
420        }
421    }
422}
423
424impl std::fmt::Debug for SignerSourceKind {
425    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
426        let s: &str = self.as_ref();
427        write!(f, "{s}")
428    }
429}
430
431#[derive(Debug, Error)]
432pub(crate) enum SignerSourceError {
433    #[error("unrecognized signer source")]
434    UnrecognizedSource,
435    #[error(transparent)]
436    RemoteWalletLocatorError(#[from] RemoteWalletLocatorError),
437    #[error(transparent)]
438    DerivationPathError(#[from] DerivationPathError),
439    #[error(transparent)]
440    IoError(#[from] std::io::Error),
441}
442
443pub(crate) fn parse_signer_source<S: AsRef<str>>(
444    source: S,
445) -> Result<SignerSource, SignerSourceError> {
446    let source = source.as_ref();
447    let source = {
448        #[cfg(target_family = "windows")]
449        {
450            // trim matched single-quotes since cmd.exe won't
451            let mut source = source;
452            while let Some(trimmed) = source.strip_prefix('\'') {
453                source = if let Some(trimmed) = trimmed.strip_suffix('\'') {
454                    trimmed
455                } else {
456                    break;
457                }
458            }
459            source.replace('\\', "/")
460        }
461        #[cfg(not(target_family = "windows"))]
462        {
463            source.to_string()
464        }
465    };
466    match uriparse::URIReference::try_from(source.as_str()) {
467        Err(_) => Err(SignerSourceError::UnrecognizedSource),
468        Ok(uri) => {
469            if let Some(scheme) = uri.scheme() {
470                let scheme = scheme.as_str().to_ascii_lowercase();
471                match scheme.as_str() {
472                    SIGNER_SOURCE_PROMPT => Ok(SignerSource {
473                        kind: SignerSourceKind::Prompt,
474                        derivation_path: DerivationPath::from_uri_any_query(&uri)?,
475                        legacy: false,
476                    }),
477                    SIGNER_SOURCE_FILEPATH => Ok(SignerSource::new(SignerSourceKind::Filepath(
478                        uri.path().to_string(),
479                    ))),
480                    SIGNER_SOURCE_USB => Ok(SignerSource {
481                        kind: SignerSourceKind::Usb(RemoteWalletLocator::new_from_uri(&uri)?),
482                        derivation_path: DerivationPath::from_uri_key_query(&uri)?,
483                        legacy: false,
484                    }),
485                    SIGNER_SOURCE_STDIN => Ok(SignerSource::new(SignerSourceKind::Stdin)),
486                    _ => {
487                        #[cfg(target_family = "windows")]
488                        // On Windows, an absolute path's drive letter will be parsed as the URI
489                        // scheme. Assume a filepath source in case of a single character shceme.
490                        if scheme.len() == 1 {
491                            return Ok(SignerSource::new(SignerSourceKind::Filepath(source)));
492                        }
493                        Err(SignerSourceError::UnrecognizedSource)
494                    }
495                }
496            } else {
497                match source.as_str() {
498                    STDOUT_OUTFILE_TOKEN => Ok(SignerSource::new(SignerSourceKind::Stdin)),
499                    ASK_KEYWORD => Ok(SignerSource::new_legacy(SignerSourceKind::Prompt)),
500                    _ => match Pubkey::from_str(source.as_str()) {
501                        Ok(pubkey) => Ok(SignerSource::new(SignerSourceKind::Pubkey(pubkey))),
502                        Err(_) => std::fs::metadata(source.as_str())
503                            .map(|_| SignerSource::new(SignerSourceKind::Filepath(source)))
504                            .map_err(|err| err.into()),
505                    },
506                }
507            }
508        }
509    }
510}
511
512pub fn presigner_from_pubkey_sigs(
513    pubkey: &Pubkey,
514    signers: &[(Pubkey, Signature)],
515) -> Option<Presigner> {
516    signers.iter().find_map(|(signer, sig)| {
517        if *signer == *pubkey {
518            Some(Presigner::new(signer, sig))
519        } else {
520            None
521        }
522    })
523}
524
525#[derive(Debug, Default)]
526pub struct SignerFromPathConfig {
527    pub allow_null_signer: bool,
528}
529
530/// Loads a [Signer] from one of several possible sources.
531///
532/// The `path` is not strictly a file system path, but is interpreted as various
533/// types of _signing source_, depending on its format, one of which is a path
534/// to a keypair file. Some sources may require user interaction in the course
535/// of calling this function.
536///
537/// The result of this function is a boxed object of the [Signer] trait. To load
538/// a concrete [Keypair], use the [keypair_from_path] function, though note that
539/// it does not support all signer sources.
540///
541/// The `matches` argument is the same set of parsed [clap] matches from which
542/// `path` was parsed. It is used to parse various additional command line
543/// arguments, depending on which signing source is requested, as described
544/// below in "Signing sources".
545///
546/// [clap]: https//docs.rs/clap
547///
548/// The `keypair_name` argument is the "name" of the signer, and is typically
549/// the name of the clap argument from which the `path` argument was parsed,
550/// like "keypair", "from", or "fee-payer". It is used solely for interactively
551/// prompting the user, either when entering seed phrases or selecting from
552/// multiple hardware wallets.
553///
554/// The `wallet_manager` is used for establishing connections to a hardware
555/// device such as Ledger. If `wallet_manager` is a reference to `None`, and a
556/// hardware signer is requested, then this function will attempt to create a
557/// wallet manager, assigning it to the mutable `wallet_manager` reference. This
558/// argument is typically a reference to `None`.
559///
560/// # Signing sources
561///
562/// The `path` argument can simply be a path to a keypair file, but it may also
563/// be interpreted in several other ways, in the following order.
564///
565/// Firstly, the `path` argument may be interpreted as a [URI], with the URI
566/// scheme indicating where to load the signer from. If it parses as a URI, then
567/// the following schemes are supported:
568///
569/// - `file:` &mdash; Read the keypair from a JSON keypair file. The path portion
570///    of the URI is the file path.
571///
572/// - `stdin:` &mdash; Read the keypair from stdin, in the JSON format used by
573///   the keypair file.
574///
575///   Non-scheme parts of the URI are ignored.
576///
577/// - `prompt:` &mdash; The user will be prompted at the command line
578///   for their seed phrase and passphrase.
579///
580///   In this URI the [query string][qs] may contain zero or one of the
581///   following key/value pairs that determine the [BIP44 derivation path][dp]
582///   of the private key from the seed:
583///
584///   - `key` &mdash; In this case the value is either one or two numerical
585///     indexes separated by a slash, which represent the "account", and
586///     "change" components of the BIP44 derivation path. Example: `key=0/0`.
587///
588///   - `full-path` &mdash; In this case the value is a full derivation path,
589///     and the user is responsible for ensuring it is correct. Example:
590///     `full-path=m/44/501/0/0/0`.
591///
592///   If neither is provided, then the default derivation path is used.
593///
594///   Note that when specifying derivation paths, this routine will convert all
595///   indexes into ["hardened"] indexes, even if written as "normal" indexes.
596///
597///   Other components of the URI besides the scheme and query string are ignored.
598///
599///   If the "skip_seed_phrase_validation" argument, as defined in
600///   [SKIP_SEED_PHRASE_VALIDATION_ARG] is found in `matches`, then the keypair
601///   seed will be generated directly from the seed phrase, without parsing or
602///   validating it as a BIP39 seed phrase. This allows the use of non-BIP39 seed
603///   phrases.
604///
605/// - `usb:` &mdash; Use a USB hardware device as the signer. In this case, the
606///   URI host indicates the device type, and is required. The only currently valid host
607///   value is "ledger".
608///
609///   Optionally, the first segment of the URI path indicates the base-58
610///   encoded pubkey of the wallet, and the "account" and "change" indices of
611///   the derivation path can be specified with the `key=` query parameter, as
612///   with the `prompt:` URI.
613///
614///   Examples:
615///
616///   - `usb://ledger`
617///   - `usb://ledger?key=0/0`
618///   - `usb://ledger/9rPVSygg3brqghvdZ6wsL2i5YNQTGhXGdJzF65YxaCQd`
619///   - `usb://ledger/9rPVSygg3brqghvdZ6wsL2i5YNQTGhXGdJzF65YxaCQd?key=0/0`
620///
621/// Next the `path` argument may be one of the following strings:
622///
623/// - `-` &mdash; Read the keypair from stdin. This is the same as the `stdin:`
624///   URI scheme.
625///
626/// - `ASK` &mdash; The user will be prompted at the command line for their seed
627///   phrase and passphrase. _This uses a legacy key derivation method and should
628///   usually be avoided in favor of `prompt:`._
629///
630/// Next, if the `path` argument parses as a base-58 public key, then the signer
631/// is created without a private key, but with presigned signatures, each parsed
632/// from the additional command line arguments, provided by the `matches`
633/// argument.
634///
635/// In this case, the remaining command line arguments are searched for clap
636/// arguments named "signer", as defined by [SIGNER_ARG], and each is parsed as
637/// a key-value pair of the form "pubkey=signature", where `pubkey` is the same
638/// base-58 public key, and `signature` is a serialized signature produced by
639/// the corresponding keypair. One of the "signer" signatures must be for the
640/// pubkey specified in `path` or this function will return an error; unless the
641/// "sign_only" clap argument, as defined by [SIGN_ONLY_ARG], is present in
642/// `matches`, in which case the signer will be created with no associated
643/// signatures.
644///
645/// Finally, if `path`, interpreted as a file path, represents a file on disk,
646/// then the signer is created by reading that file as a JSON-serialized
647/// keypair. This is the same as the `file:` URI scheme.
648///
649/// [qs]: https://en.wikipedia.org/wiki/Query_string
650/// [dp]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
651/// [URI]: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier
652/// ["hardened"]: https://wiki.trezor.io/Hardened_and_non-hardened_derivation
653///
654/// # Examples
655///
656/// This shows a reasonable way to set up clap to parse all possible signer
657/// sources. Note the use of the [`OfflineArgs::offline_args`] method to add
658/// correct clap definitions of the `--signer` and `--sign-only` arguments, as
659/// required by the base-58 pubkey offline signing method.
660///
661/// [`OfflineArgs::offline_args`]: crate::offline::OfflineArgs::offline_args
662///
663/// ```no_run
664/// use clap::{App, Arg, value_t_or_exit};
665/// use solana_clap_utils::keypair::signer_from_path;
666/// use solana_clap_utils::offline::OfflineArgs;
667///
668/// let clap_app = App::new("my-program")
669///     // The argument we'll parse as a signer "path"
670///     .arg(Arg::with_name("keypair")
671///         .required(true)
672///         .help("The default signer"))
673///     .offline_args();
674///
675/// let clap_matches = clap_app.get_matches();
676/// let keypair_str = value_t_or_exit!(clap_matches, "keypair", String);
677/// let mut wallet_manager = None;
678/// let signer = signer_from_path(
679///     &clap_matches,
680///     &keypair_str,
681///     "keypair",
682///     &mut wallet_manager,
683/// )?;
684/// # Ok::<(), Box<dyn std::error::Error>>(())
685/// ```
686pub fn signer_from_path(
687    matches: &ArgMatches,
688    path: &str,
689    keypair_name: &str,
690    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
691) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
692    let config = SignerFromPathConfig::default();
693    signer_from_path_with_config(matches, path, keypair_name, wallet_manager, &config)
694}
695
696/// Loads a [Signer] from one of several possible sources.
697///
698/// The `path` is not strictly a file system path, but is interpreted as various
699/// types of _signing source_, depending on its format, one of which is a path
700/// to a keypair file. Some sources may require user interaction in the course
701/// of calling this function.
702///
703/// This is the same as [`signer_from_path`] except that it additionaolly
704/// accepts a [`SignerFromPathConfig`] argument.
705///
706/// If the `allow_null_signer` field of `config` is `true`, then pubkey signers
707/// are allowed to have zero associated signatures via additional "signer"
708/// command line arguments. It the same effect as if the "sign_only" clap
709/// argument is present.
710///
711/// See [`signer_from_path`] for full documentation of how this function
712/// interprets its arguments.
713///
714/// # Examples
715///
716/// This shows a reasonable way to set up clap to parse all possible signer
717/// sources. Note the use of the [`OfflineArgs::offline_args`] method to add
718/// correct clap definitions of the `--signer` and `--sign-only` arguments, as
719/// required by the base-58 pubkey offline signing method.
720///
721/// [`OfflineArgs::offline_args`]: crate::offline::OfflineArgs::offline_args
722///
723/// ```no_run
724/// use clap::{App, Arg, value_t_or_exit};
725/// use solana_clap_utils::keypair::{signer_from_path_with_config, SignerFromPathConfig};
726/// use solana_clap_utils::offline::OfflineArgs;
727///
728/// let clap_app = App::new("my-program")
729///     // The argument we'll parse as a signer "path"
730///     .arg(Arg::with_name("keypair")
731///         .required(true)
732///         .help("The default signer"))
733///     .offline_args();
734///
735/// let clap_matches = clap_app.get_matches();
736/// let keypair_str = value_t_or_exit!(clap_matches, "keypair", String);
737/// let mut wallet_manager = None;
738///
739/// // Allow pubkey signers without accompanying signatures
740/// let config = SignerFromPathConfig {
741///     allow_null_signer: true,
742/// };
743///
744/// let signer = signer_from_path_with_config(
745///     &clap_matches,
746///     &keypair_str,
747///     "keypair",
748///     &mut wallet_manager,
749///     &config,
750/// )?;
751/// # Ok::<(), Box<dyn std::error::Error>>(())
752/// ```
753pub fn signer_from_path_with_config(
754    matches: &ArgMatches,
755    path: &str,
756    keypair_name: &str,
757    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
758    config: &SignerFromPathConfig,
759) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
760    let SignerSource {
761        kind,
762        derivation_path,
763        legacy,
764    } = parse_signer_source(path)?;
765    match kind {
766        SignerSourceKind::Prompt => {
767            let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
768            Ok(Box::new(keypair_from_seed_phrase(
769                keypair_name,
770                skip_validation,
771                false,
772                derivation_path,
773                legacy,
774            )?))
775        }
776        SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
777            Err(e) => Err(std::io::Error::new(
778                std::io::ErrorKind::Other,
779                format!("could not read keypair file \"{path}\". Run \"solana-keygen new\" to create a keypair file: {e}"),
780            )
781            .into()),
782            Ok(file) => Ok(Box::new(file)),
783        },
784        SignerSourceKind::Stdin => {
785            let mut stdin = std::io::stdin();
786            Ok(Box::new(read_keypair(&mut stdin)?))
787        }
788        SignerSourceKind::Usb(locator) => {
789            if wallet_manager.is_none() {
790                *wallet_manager = maybe_wallet_manager()?;
791            }
792            if let Some(wallet_manager) = wallet_manager {
793                Ok(Box::new(generate_remote_keypair(
794                    locator,
795                    derivation_path.unwrap_or_default(),
796                    wallet_manager,
797                    matches.is_present("confirm_key"),
798                    keypair_name,
799                )?))
800            } else {
801                Err(RemoteWalletError::NoDeviceFound.into())
802            }
803        }
804        SignerSourceKind::Pubkey(pubkey) => {
805            let presigner = pubkeys_sigs_of(matches, SIGNER_ARG.name)
806                .as_ref()
807                .and_then(|presigners| presigner_from_pubkey_sigs(&pubkey, presigners));
808            if let Some(presigner) = presigner {
809                Ok(Box::new(presigner))
810            } else if config.allow_null_signer || matches.is_present(SIGN_ONLY_ARG.name) {
811                Ok(Box::new(NullSigner::new(&pubkey)))
812            } else {
813                Err(std::io::Error::new(
814                    std::io::ErrorKind::Other,
815                    format!("missing signature for supplied pubkey: {pubkey}"),
816                )
817                .into())
818            }
819        }
820    }
821}
822
823/// Loads the pubkey of a [Signer] from one of several possible sources.
824///
825/// The `path` is not strictly a file system path, but is interpreted as various
826/// types of _signing source_, depending on its format, one of which is a path
827/// to a keypair file. Some sources may require user interaction in the course
828/// of calling this function.
829///
830/// The only difference between this function and [`signer_from_path`] is in the
831/// case of a "pubkey" path: this function does not require that accompanying
832/// command line arguments contain an offline signature.
833///
834/// See [`signer_from_path`] for full documentation of how this function
835/// interprets its arguments.
836///
837/// # Examples
838///
839/// ```no_run
840/// use clap::{App, Arg, value_t_or_exit};
841/// use solana_clap_utils::keypair::pubkey_from_path;
842///
843/// let clap_app = App::new("my-program")
844///     // The argument we'll parse as a signer "path"
845///     .arg(Arg::with_name("keypair")
846///         .required(true)
847///         .help("The default signer"));
848///
849/// let clap_matches = clap_app.get_matches();
850/// let keypair_str = value_t_or_exit!(clap_matches, "keypair", String);
851/// let mut wallet_manager = None;
852/// let pubkey = pubkey_from_path(
853///     &clap_matches,
854///     &keypair_str,
855///     "keypair",
856///     &mut wallet_manager,
857/// )?;
858/// # Ok::<(), Box<dyn std::error::Error>>(())
859/// ```
860pub fn pubkey_from_path(
861    matches: &ArgMatches,
862    path: &str,
863    keypair_name: &str,
864    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
865) -> Result<Pubkey, Box<dyn error::Error>> {
866    let SignerSource { kind, .. } = parse_signer_source(path)?;
867    match kind {
868        SignerSourceKind::Pubkey(pubkey) => Ok(pubkey),
869        _ => Ok(signer_from_path(matches, path, keypair_name, wallet_manager)?.pubkey()),
870    }
871}
872
873pub fn resolve_signer_from_path(
874    matches: &ArgMatches,
875    path: &str,
876    keypair_name: &str,
877    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
878) -> Result<Option<String>, Box<dyn error::Error>> {
879    let SignerSource {
880        kind,
881        derivation_path,
882        legacy,
883    } = parse_signer_source(path)?;
884    match kind {
885        SignerSourceKind::Prompt => {
886            let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
887            // This method validates the seed phrase, but returns `None` because there is no path
888            // on disk or to a device
889            keypair_from_seed_phrase(
890                keypair_name,
891                skip_validation,
892                false,
893                derivation_path,
894                legacy,
895            )
896            .map(|_| None)
897        }
898        SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
899            Err(e) => Err(std::io::Error::new(
900                std::io::ErrorKind::Other,
901                format!(
902                    "could not read keypair file \"{path}\". \
903                    Run \"solana-keygen new\" to create a keypair file: {e}"
904                ),
905            )
906            .into()),
907            Ok(_) => Ok(Some(path.to_string())),
908        },
909        SignerSourceKind::Stdin => {
910            let mut stdin = std::io::stdin();
911            // This method validates the keypair from stdin, but returns `None` because there is no
912            // path on disk or to a device
913            read_keypair(&mut stdin).map(|_| None)
914        }
915        SignerSourceKind::Usb(locator) => {
916            if wallet_manager.is_none() {
917                *wallet_manager = maybe_wallet_manager()?;
918            }
919            if let Some(wallet_manager) = wallet_manager {
920                let path = generate_remote_keypair(
921                    locator,
922                    derivation_path.unwrap_or_default(),
923                    wallet_manager,
924                    matches.is_present("confirm_key"),
925                    keypair_name,
926                )
927                .map(|keypair| keypair.path)?;
928                Ok(Some(path))
929            } else {
930                Err(RemoteWalletError::NoDeviceFound.into())
931            }
932        }
933        _ => Ok(Some(path.to_string())),
934    }
935}
936
937// Keyword used to indicate that the user should be prompted for a keypair seed phrase
938pub const ASK_KEYWORD: &str = "ASK";
939
940pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
941    long: "skip-seed-phrase-validation",
942    name: "skip_seed_phrase_validation",
943    help: "Skip validation of seed phrases. Use this if your phrase does not use the BIP39 official English word list",
944};
945
946/// Prompts user for a passphrase and then asks for confirmirmation to check for mistakes
947pub fn prompt_passphrase(prompt: &str) -> Result<String, Box<dyn error::Error>> {
948    let passphrase = prompt_password(prompt)?;
949    if !passphrase.is_empty() {
950        let confirmed = rpassword::prompt_password("Enter same passphrase again: ")?;
951        if confirmed != passphrase {
952            return Err("Passphrases did not match".into());
953        }
954    }
955    Ok(passphrase)
956}
957
958/// Loads a [Keypair] from one of several possible sources.
959///
960/// The `path` is not strictly a file system path, but is interpreted as various
961/// types of _signing source_, depending on its format, one of which is a path
962/// to a keypair file. Some sources may require user interaction in the course
963/// of calling this function.
964///
965/// This is the same as [`signer_from_path`] except that it only supports
966/// signing sources that can result in a [Keypair]: prompt for seed phrase,
967/// keypair file, and stdin.
968///
969/// If `confirm_pubkey` is `true` then after deriving the pubkey, the user will
970/// be prompted to confirm that the pubkey is as expected.
971///
972/// See [`signer_from_path`] for full documentation of how this function
973/// interprets its arguments.
974///
975/// # Examples
976///
977/// ```no_run
978/// use clap::{App, Arg, value_t_or_exit};
979/// use solana_clap_utils::keypair::keypair_from_path;
980///
981/// let clap_app = App::new("my-program")
982///     // The argument we'll parse as a signer "path"
983///     .arg(Arg::with_name("keypair")
984///         .required(true)
985///         .help("The default signer"));
986///
987/// let clap_matches = clap_app.get_matches();
988/// let keypair_str = value_t_or_exit!(clap_matches, "keypair", String);
989///
990/// let signer = keypair_from_path(
991///     &clap_matches,
992///     &keypair_str,
993///     "keypair",
994///     false,
995/// )?;
996/// # Ok::<(), Box<dyn std::error::Error>>(())
997/// ```
998pub fn keypair_from_path(
999    matches: &ArgMatches,
1000    path: &str,
1001    keypair_name: &str,
1002    confirm_pubkey: bool,
1003) -> Result<Keypair, Box<dyn error::Error>> {
1004    let SignerSource {
1005        kind,
1006        derivation_path,
1007        legacy,
1008    } = parse_signer_source(path)?;
1009    match kind {
1010        SignerSourceKind::Prompt => {
1011            let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
1012            Ok(keypair_from_seed_phrase(
1013                keypair_name,
1014                skip_validation,
1015                confirm_pubkey,
1016                derivation_path,
1017                legacy,
1018            )?)
1019        }
1020        SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
1021            Err(e) => Err(std::io::Error::new(
1022                std::io::ErrorKind::Other,
1023                format!(
1024                    "could not read keypair file \"{path}\". \
1025                    Run \"solana-keygen new\" to create a keypair file: {e}"
1026                ),
1027            )
1028            .into()),
1029            Ok(file) => Ok(file),
1030        },
1031        SignerSourceKind::Stdin => {
1032            let mut stdin = std::io::stdin();
1033            Ok(read_keypair(&mut stdin)?)
1034        }
1035        _ => Err(std::io::Error::new(
1036            std::io::ErrorKind::Other,
1037            format!("signer of type `{kind:?}` does not support Keypair output"),
1038        )
1039        .into()),
1040    }
1041}
1042
1043/// Reads user input from stdin to retrieve a seed phrase and passphrase for keypair derivation.
1044///
1045/// Optionally skips validation of seed phrase. Optionally confirms recovered
1046/// public key.
1047pub fn keypair_from_seed_phrase(
1048    keypair_name: &str,
1049    skip_validation: bool,
1050    confirm_pubkey: bool,
1051    derivation_path: Option<DerivationPath>,
1052    legacy: bool,
1053) -> Result<Keypair, Box<dyn error::Error>> {
1054    let seed_phrase = prompt_password(format!("[{keypair_name}] seed phrase: "))?;
1055    let seed_phrase = seed_phrase.trim();
1056    let passphrase_prompt = format!(
1057        "[{keypair_name}] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue: ",
1058    );
1059
1060    let keypair = if skip_validation {
1061        let passphrase = prompt_passphrase(&passphrase_prompt)?;
1062        if legacy {
1063            keypair_from_seed_phrase_and_passphrase(seed_phrase, &passphrase)?
1064        } else {
1065            let seed = generate_seed_from_seed_phrase_and_passphrase(seed_phrase, &passphrase);
1066            keypair_from_seed_and_derivation_path(&seed, derivation_path)?
1067        }
1068    } else {
1069        let sanitized = sanitize_seed_phrase(seed_phrase);
1070        let parse_language_fn = || {
1071            for language in &[
1072                Language::English,
1073                Language::ChineseSimplified,
1074                Language::ChineseTraditional,
1075                Language::Japanese,
1076                Language::Spanish,
1077                Language::Korean,
1078                Language::French,
1079                Language::Italian,
1080            ] {
1081                if let Ok(mnemonic) = Mnemonic::from_phrase(&sanitized, *language) {
1082                    return Ok(mnemonic);
1083                }
1084            }
1085            Err("Can't get mnemonic from seed phrases")
1086        };
1087        let mnemonic = parse_language_fn()?;
1088        let passphrase = prompt_passphrase(&passphrase_prompt)?;
1089        let seed = Seed::new(&mnemonic, &passphrase);
1090        if legacy {
1091            keypair_from_seed(seed.as_bytes())?
1092        } else {
1093            keypair_from_seed_and_derivation_path(seed.as_bytes(), derivation_path)?
1094        }
1095    };
1096
1097    if confirm_pubkey {
1098        let pubkey = keypair.pubkey();
1099        print!("Recovered pubkey `{pubkey:?}`. Continue? (y/n): ");
1100        let _ignored = stdout().flush();
1101        let mut input = String::new();
1102        stdin().read_line(&mut input).expect("Unexpected input");
1103        if input.to_lowercase().trim() != "y" {
1104            println!("Exiting");
1105            exit(1);
1106        }
1107    }
1108
1109    Ok(keypair)
1110}
1111
1112fn sanitize_seed_phrase(seed_phrase: &str) -> String {
1113    seed_phrase
1114        .split_whitespace()
1115        .collect::<Vec<&str>>()
1116        .join(" ")
1117}
1118
1119#[cfg(test)]
1120mod tests {
1121    use {
1122        super::*,
1123        crate::offline::OfflineArgs,
1124        assert_matches::assert_matches,
1125        clap::{value_t_or_exit, App, Arg},
1126        solana_remote_wallet::{locator::Manufacturer, remote_wallet::initialize_wallet_manager},
1127        solana_sdk::{signer::keypair::write_keypair_file, system_instruction},
1128        tempfile::{NamedTempFile, TempDir},
1129    };
1130
1131    #[test]
1132    fn test_sanitize_seed_phrase() {
1133        let seed_phrase = " Mary   had\ta\u{2009}little  \n\t lamb";
1134        assert_eq!(
1135            "Mary had a little lamb".to_owned(),
1136            sanitize_seed_phrase(seed_phrase)
1137        );
1138    }
1139
1140    #[test]
1141    fn test_signer_info_signers_for_message() {
1142        let source = Keypair::new();
1143        let fee_payer = Keypair::new();
1144        let nonsigner1 = Keypair::new();
1145        let nonsigner2 = Keypair::new();
1146        let recipient = Pubkey::new_unique();
1147        let message = Message::new(
1148            &[system_instruction::transfer(
1149                &source.pubkey(),
1150                &recipient,
1151                42,
1152            )],
1153            Some(&fee_payer.pubkey()),
1154        );
1155        let signers = vec![
1156            Box::new(fee_payer) as Box<dyn Signer>,
1157            Box::new(source) as Box<dyn Signer>,
1158            Box::new(nonsigner1) as Box<dyn Signer>,
1159            Box::new(nonsigner2) as Box<dyn Signer>,
1160        ];
1161        let signer_info = CliSignerInfo { signers };
1162        let msg_signers = signer_info.signers_for_message(&message);
1163        let signer_pubkeys = msg_signers.iter().map(|s| s.pubkey()).collect::<Vec<_>>();
1164        let expect = vec![
1165            signer_info.signers[0].pubkey(),
1166            signer_info.signers[1].pubkey(),
1167        ];
1168        assert_eq!(signer_pubkeys, expect);
1169    }
1170
1171    #[test]
1172    fn test_parse_signer_source() {
1173        assert_matches!(
1174            parse_signer_source(STDOUT_OUTFILE_TOKEN).unwrap(),
1175            SignerSource {
1176                kind: SignerSourceKind::Stdin,
1177                derivation_path: None,
1178                legacy: false,
1179            }
1180        );
1181        let stdin = "stdin:".to_string();
1182        assert_matches!(
1183            parse_signer_source(stdin).unwrap(),
1184            SignerSource {
1185                kind: SignerSourceKind::Stdin,
1186                derivation_path: None,
1187                legacy: false,
1188            }
1189        );
1190        assert_matches!(
1191            parse_signer_source(ASK_KEYWORD).unwrap(),
1192            SignerSource {
1193                kind: SignerSourceKind::Prompt,
1194                derivation_path: None,
1195                legacy: true,
1196            }
1197        );
1198        let pubkey = Pubkey::new_unique();
1199        assert!(
1200            matches!(parse_signer_source(pubkey.to_string()).unwrap(), SignerSource {
1201                kind: SignerSourceKind::Pubkey(p),
1202                derivation_path: None,
1203                legacy: false,
1204            }
1205            if p == pubkey)
1206        );
1207
1208        // Set up absolute and relative path strs
1209        let file0 = NamedTempFile::new().unwrap();
1210        let path = file0.path();
1211        assert!(path.is_absolute());
1212        let absolute_path_str = path.to_str().unwrap();
1213
1214        let file1 = NamedTempFile::new_in(std::env::current_dir().unwrap()).unwrap();
1215        let path = file1.path().file_name().unwrap().to_str().unwrap();
1216        let path = std::path::Path::new(path);
1217        assert!(path.is_relative());
1218        let relative_path_str = path.to_str().unwrap();
1219
1220        assert!(
1221            matches!(parse_signer_source(absolute_path_str).unwrap(), SignerSource {
1222                kind: SignerSourceKind::Filepath(p),
1223                derivation_path: None,
1224                legacy: false,
1225            } if p == absolute_path_str)
1226        );
1227        assert!(
1228            matches!(parse_signer_source(relative_path_str).unwrap(), SignerSource {
1229                kind: SignerSourceKind::Filepath(p),
1230                derivation_path: None,
1231                legacy: false,
1232            } if p == relative_path_str)
1233        );
1234
1235        let usb = "usb://ledger".to_string();
1236        let expected_locator = RemoteWalletLocator {
1237            manufacturer: Manufacturer::Ledger,
1238            pubkey: None,
1239        };
1240        assert_matches!(parse_signer_source(usb).unwrap(), SignerSource {
1241                kind: SignerSourceKind::Usb(u),
1242                derivation_path: None,
1243                legacy: false,
1244            } if u == expected_locator);
1245        let usb = "usb://ledger?key=0/0".to_string();
1246        let expected_locator = RemoteWalletLocator {
1247            manufacturer: Manufacturer::Ledger,
1248            pubkey: None,
1249        };
1250        let expected_derivation_path = Some(DerivationPath::new_bip44(Some(0), Some(0)));
1251        assert_matches!(parse_signer_source(usb).unwrap(), SignerSource {
1252                kind: SignerSourceKind::Usb(u),
1253                derivation_path: d,
1254                legacy: false,
1255            } if u == expected_locator && d == expected_derivation_path);
1256        // Catchall into SignerSource::Filepath fails
1257        let junk = "sometextthatisnotapubkeyorfile".to_string();
1258        assert!(Pubkey::from_str(&junk).is_err());
1259        assert_matches!(
1260            parse_signer_source(&junk),
1261            Err(SignerSourceError::IoError(_))
1262        );
1263
1264        let prompt = "prompt:".to_string();
1265        assert_matches!(
1266            parse_signer_source(prompt).unwrap(),
1267            SignerSource {
1268                kind: SignerSourceKind::Prompt,
1269                derivation_path: None,
1270                legacy: false,
1271            }
1272        );
1273        assert!(
1274            matches!(parse_signer_source(format!("file:{absolute_path_str}")).unwrap(), SignerSource {
1275                kind: SignerSourceKind::Filepath(p),
1276                derivation_path: None,
1277                legacy: false,
1278            } if p == absolute_path_str)
1279        );
1280        assert!(
1281            matches!(parse_signer_source(format!("file:{relative_path_str}")).unwrap(), SignerSource {
1282                kind: SignerSourceKind::Filepath(p),
1283                derivation_path: None,
1284                legacy: false,
1285            } if p == relative_path_str)
1286        );
1287    }
1288
1289    #[test]
1290    fn signer_from_path_with_file() -> Result<(), Box<dyn std::error::Error>> {
1291        let dir = TempDir::new()?;
1292        let dir = dir.path();
1293        let keypair_path = dir.join("id.json");
1294        let keypair_path_str = keypair_path.to_str().expect("utf-8");
1295
1296        let keypair = Keypair::new();
1297        write_keypair_file(&keypair, &keypair_path)?;
1298
1299        let args = vec!["program", keypair_path_str];
1300
1301        let clap_app = App::new("my-program")
1302            .arg(
1303                Arg::with_name("keypair")
1304                    .required(true)
1305                    .help("The signing keypair"),
1306            )
1307            .offline_args();
1308
1309        let clap_matches = clap_app.get_matches_from(args);
1310        let keypair_str = value_t_or_exit!(clap_matches, "keypair", String);
1311
1312        let wallet_manager = initialize_wallet_manager()?;
1313
1314        let signer = signer_from_path(
1315            &clap_matches,
1316            &keypair_str,
1317            "signer",
1318            &mut Some(wallet_manager),
1319        )?;
1320
1321        assert_eq!(keypair.pubkey(), signer.pubkey());
1322
1323        Ok(())
1324    }
1325}