mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-06-27 00:31:35 +00:00
misc/keygen: Implement cli tool to handle key material (#2453)
- Load keys from file - Generate new keys (with optional prefix) - Replaces peer-id-generator Co-authored-by: Fabricio Dematte <fabriciodematte7p@gmail.com> Co-authored-by: Max Inden <mail@max-inden.de>
This commit is contained in:
@ -125,7 +125,7 @@ members = [
|
||||
"core",
|
||||
"misc/metrics",
|
||||
"misc/multistream-select",
|
||||
"misc/peer-id-generator",
|
||||
"misc/keygen",
|
||||
"muxers/mplex",
|
||||
"muxers/yamux",
|
||||
"protocols/dcutr",
|
||||
|
@ -1,16 +1,17 @@
|
||||
[package]
|
||||
name = "peer-id-generator"
|
||||
edition = "2021"
|
||||
rust-version = "1.56.1"
|
||||
name = "keygen"
|
||||
version = "0.1.0"
|
||||
description = "Generate peer ids that are prefixed with a specific string"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
authors = ["demfabris <demfabris@gmail.com>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
keywords = ["peer-to-peer", "libp2p", "networking"]
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
structopt = "0.3.26"
|
||||
zeroize = "1"
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
serde_json = "1.0.79"
|
||||
libp2p-core = { path = "../../core", default-features = false, version = "0.32.0"}
|
||||
num_cpus = "1.8"
|
||||
base64 = "0.13.0"
|
41
misc/keygen/src/config.rs
Normal file
41
misc/keygen/src/config.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
|
||||
use libp2p_core::identity::Keypair;
|
||||
use libp2p_core::PeerId;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Config {
|
||||
pub identity: Identity,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_file(path: &Path) -> Result<Self, Box<dyn Error>> {
|
||||
Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
|
||||
}
|
||||
|
||||
pub fn from_key_material(peer_id: PeerId, keypair: &Keypair) -> Result<Self, Box<dyn Error>> {
|
||||
let priv_key = base64::encode(keypair.to_protobuf_encoding()?);
|
||||
let peer_id = peer_id.to_base58();
|
||||
Ok(Self {
|
||||
identity: Identity { peer_id, priv_key },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Identity {
|
||||
#[serde(rename = "PeerID")]
|
||||
pub peer_id: String,
|
||||
pub priv_key: String,
|
||||
}
|
||||
|
||||
impl zeroize::Zeroize for Config {
|
||||
fn zeroize(&mut self) {
|
||||
self.identity.peer_id.zeroize();
|
||||
self.identity.priv_key.zeroize();
|
||||
}
|
||||
}
|
126
misc/keygen/src/main.rs
Normal file
126
misc/keygen/src/main.rs
Normal file
@ -0,0 +1,126 @@
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
use std::str::{self, FromStr};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
|
||||
mod config;
|
||||
|
||||
use libp2p_core::identity::{self, ed25519};
|
||||
use libp2p_core::PeerId;
|
||||
use structopt::StructOpt;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "libp2p key material generator")]
|
||||
struct Args {
|
||||
/// JSON formatted output
|
||||
#[structopt(long, global = true)]
|
||||
json: bool,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
cmd: Command,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
enum Command {
|
||||
/// Read from config file
|
||||
From {
|
||||
/// Provide a IPFS config file
|
||||
#[structopt(parse(from_os_str))]
|
||||
config: PathBuf,
|
||||
},
|
||||
/// Generate random
|
||||
Rand {
|
||||
/// The keypair prefix
|
||||
#[structopt(long)]
|
||||
prefix: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
// Due to the fact that a peer id uses a SHA-256 multihash, it always starts with the
|
||||
// bytes 0x1220, meaning that only some characters are valid.
|
||||
const ALLOWED_FIRST_BYTE: &[u8] = b"NPQRSTUVWXYZ";
|
||||
|
||||
// The base58 alphabet is not necessarily obvious.
|
||||
const ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let args = Args::from_args();
|
||||
|
||||
let (local_peer_id, local_keypair) = match args.cmd {
|
||||
// Generate keypair from some sort of key material. Currently supporting `IPFS` config file
|
||||
Command::From { config } => {
|
||||
let config = Zeroizing::new(config::Config::from_file(config.as_ref())?);
|
||||
|
||||
let keypair = identity::Keypair::from_protobuf_encoding(&Zeroizing::new(
|
||||
base64::decode(config.identity.priv_key.as_bytes())?,
|
||||
))?;
|
||||
|
||||
let peer_id = keypair.public().into();
|
||||
assert_eq!(
|
||||
PeerId::from_str(&config.identity.peer_id)?,
|
||||
peer_id,
|
||||
"Expect peer id derived from private key and peer id retrieved from config to match."
|
||||
);
|
||||
|
||||
(peer_id, keypair)
|
||||
}
|
||||
|
||||
// Generate a random keypair, optionally with a prefix
|
||||
Command::Rand { prefix } => {
|
||||
if let Some(prefix) = prefix {
|
||||
if prefix.as_bytes().iter().any(|c| !ALPHABET.contains(c)) {
|
||||
eprintln!("Prefix {} is not valid base58", prefix);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Checking conformity to ALLOWED_FIRST_BYTE.
|
||||
if !prefix.is_empty() && !ALLOWED_FIRST_BYTE.contains(&prefix.as_bytes()[0]) {
|
||||
eprintln!("Prefix {} is not reachable", prefix);
|
||||
eprintln!(
|
||||
"Only the following bytes are possible as first byte: {}",
|
||||
str::from_utf8(ALLOWED_FIRST_BYTE).unwrap()
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let (tx, rx) = mpsc::channel::<(PeerId, identity::Keypair)>();
|
||||
|
||||
// Find peer IDs in a multithreaded fashion.
|
||||
for _ in 0..thread::available_parallelism()?.get() {
|
||||
let prefix = prefix.clone();
|
||||
let tx = tx.clone();
|
||||
|
||||
thread::spawn(move || loop {
|
||||
let keypair = ed25519::Keypair::generate();
|
||||
let peer_id = identity::PublicKey::Ed25519(keypair.public()).to_peer_id();
|
||||
let base58 = peer_id.to_base58();
|
||||
if base58[8..].starts_with(&prefix) {
|
||||
tx.send((peer_id, identity::Keypair::Ed25519(keypair)))
|
||||
.expect("to send");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rx.recv().expect("to recv")
|
||||
} else {
|
||||
let keypair = identity::Keypair::Ed25519(ed25519::Keypair::generate());
|
||||
(keypair.public().into(), keypair)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if args.json {
|
||||
let config = config::Config::from_key_material(local_peer_id, &local_keypair)?;
|
||||
println!("{}", serde_json::to_string(&config)?);
|
||||
} else {
|
||||
println!(
|
||||
"PeerId: {:?} Keypair: {:?}",
|
||||
local_peer_id,
|
||||
local_keypair.to_protobuf_encoding()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use libp2p_core::identity;
|
||||
use std::{env, str, thread, time::Duration};
|
||||
|
||||
fn main() {
|
||||
// Due to the fact that a peer id uses a SHA-256 multihash, it always starts with the
|
||||
// bytes 0x1220, meaning that only some characters are valid.
|
||||
const ALLOWED_FIRST_BYTE: &'static [u8] = b"NPQRSTUVWXYZ";
|
||||
|
||||
let prefix = match env::args().nth(1) {
|
||||
Some(prefix) => prefix,
|
||||
None => {
|
||||
println!(
|
||||
"Usage: {} <prefix>\n\n\
|
||||
Generates a peer id that starts with the chosen prefix using a secp256k1 public \
|
||||
key.\n\n\
|
||||
Prefix must be a sequence of characters in the base58 \
|
||||
alphabet, and must start with one of the following: {}",
|
||||
env::current_exe()
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
str::from_utf8(ALLOWED_FIRST_BYTE).unwrap()
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// The base58 alphabet is not necessarily obvious.
|
||||
const ALPHABET: &'static [u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
if prefix.as_bytes().iter().any(|c| !ALPHABET.contains(c)) {
|
||||
println!("Prefix {} is not valid base58", prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
// Checking conformity to ALLOWED_FIRST_BYTE.
|
||||
if !prefix.is_empty() {
|
||||
if !ALLOWED_FIRST_BYTE.contains(&prefix.as_bytes()[0]) {
|
||||
println!("Prefix {} is not reachable", prefix);
|
||||
println!(
|
||||
"Only the following bytes are possible as first byte: {}",
|
||||
str::from_utf8(ALLOWED_FIRST_BYTE).unwrap()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find peer IDs in a multithreaded fashion.
|
||||
for _ in 0..num_cpus::get() {
|
||||
let prefix = prefix.clone();
|
||||
thread::spawn(move || loop {
|
||||
let keypair = identity::ed25519::Keypair::generate();
|
||||
let secret = keypair.secret();
|
||||
let peer_id = identity::PublicKey::Ed25519(keypair.public()).to_peer_id();
|
||||
let base58 = peer_id.to_base58();
|
||||
if base58[2..].starts_with(&prefix) {
|
||||
println!("Found {:?}", peer_id);
|
||||
println!("=> Private key = {:?}", secret.as_ref());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loop {
|
||||
thread::sleep(Duration::from_secs(3600));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user