mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-06-27 08:41:36 +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",
|
"core",
|
||||||
"misc/metrics",
|
"misc/metrics",
|
||||||
"misc/multistream-select",
|
"misc/multistream-select",
|
||||||
"misc/peer-id-generator",
|
"misc/keygen",
|
||||||
"muxers/mplex",
|
"muxers/mplex",
|
||||||
"muxers/yamux",
|
"muxers/yamux",
|
||||||
"protocols/dcutr",
|
"protocols/dcutr",
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "peer-id-generator"
|
name = "keygen"
|
||||||
edition = "2021"
|
|
||||||
rust-version = "1.56.1"
|
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Generate peer ids that are prefixed with a specific string"
|
edition = "2021"
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = ["demfabris <demfabris@gmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/libp2p/rust-libp2p"
|
repository = "https://github.com/libp2p/rust-libp2p"
|
||||||
keywords = ["peer-to-peer", "libp2p", "networking"]
|
keywords = ["peer-to-peer", "libp2p", "networking"]
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
[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"}
|
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