keygen/
main.rs

1use std::{
2    error::Error,
3    path::PathBuf,
4    str::{self, FromStr},
5    sync::mpsc,
6    thread,
7};
8
9use base64::prelude::*;
10
11mod config;
12
13use clap::Parser;
14use libp2p_identity as identity;
15use libp2p_identity::PeerId;
16use zeroize::Zeroizing;
17
18#[derive(Debug, Parser)]
19#[command(name = "libp2p key material generator")]
20struct Args {
21    /// JSON formatted output
22    #[arg(long, global = true)]
23    json: bool,
24
25    #[command(subcommand)]
26    cmd: Command,
27}
28
29#[derive(Debug, Parser)]
30enum Command {
31    /// Read from config file
32    From {
33        /// Provide a IPFS config file
34        #[arg(value_parser)]
35        config: PathBuf,
36    },
37    /// Generate random
38    Rand {
39        /// The keypair prefix
40        #[arg(long)]
41        prefix: Option<String>,
42    },
43}
44
45// Due to the fact that a peer id uses a SHA-256 multihash, it always starts with the
46// bytes 0x1220, meaning that only some characters are valid.
47const ALLOWED_FIRST_BYTE: &[u8] = b"NPQRSTUVWXYZ";
48
49// The base58 alphabet is not necessarily obvious.
50const ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
51
52fn main() -> Result<(), Box<dyn Error>> {
53    let args = Args::parse();
54
55    let (local_peer_id, local_keypair) = match args.cmd {
56        // Generate keypair from some sort of key material. Currently supporting `IPFS` config file
57        Command::From { config } => {
58            let config = Zeroizing::new(config::Config::from_file(config.as_ref())?);
59
60            let keypair = identity::Keypair::from_protobuf_encoding(&Zeroizing::new(
61                BASE64_STANDARD.decode(config.identity.priv_key.as_bytes())?,
62            ))?;
63
64            let peer_id = keypair.public().into();
65            assert_eq!(
66                    PeerId::from_str(&config.identity.peer_id)?,
67                    peer_id,
68                    "Expect peer id derived from private key and peer id retrieved from config to match."
69                );
70
71            (peer_id, keypair)
72        }
73
74        // Generate a random keypair, optionally with a prefix
75        Command::Rand { prefix } => {
76            if let Some(prefix) = prefix {
77                if prefix.as_bytes().iter().any(|c| !ALPHABET.contains(c)) {
78                    eprintln!("Prefix {prefix} is not valid base58");
79                    std::process::exit(1);
80                }
81
82                // Checking conformity to ALLOWED_FIRST_BYTE.
83                if !prefix.is_empty() && !ALLOWED_FIRST_BYTE.contains(&prefix.as_bytes()[0]) {
84                    eprintln!("Prefix {prefix} is not reachable");
85                    eprintln!(
86                        "Only the following bytes are possible as first byte: {}",
87                        str::from_utf8(ALLOWED_FIRST_BYTE).unwrap()
88                    );
89                    std::process::exit(1);
90                }
91
92                let (tx, rx) = mpsc::channel::<(PeerId, identity::Keypair)>();
93
94                // Find peer IDs in a multithreaded fashion.
95                for _ in 0..thread::available_parallelism()?.get() {
96                    let prefix = prefix.clone();
97                    let tx = tx.clone();
98
99                    thread::spawn(move || loop {
100                        let keypair = identity::Keypair::generate_ed25519();
101                        let peer_id = keypair.public().to_peer_id();
102                        let base58 = peer_id.to_base58();
103                        if base58[8..].starts_with(&prefix) {
104                            tx.send((peer_id, keypair)).expect("to send");
105                        }
106                    });
107                }
108
109                rx.recv().expect("to recv")
110            } else {
111                let keypair = identity::Keypair::generate_ed25519();
112                (keypair.public().into(), keypair)
113            }
114        }
115    };
116
117    if args.json {
118        let config = config::Config::from_key_material(local_peer_id, &local_keypair)?;
119        println!("{}", serde_json::to_string(&config)?);
120    } else {
121        println!(
122            "PeerId: {:?} Keypair: {:?}",
123            local_peer_id,
124            local_keypair.to_protobuf_encoding()
125        );
126    }
127
128    Ok(())
129}