Skip to content

Commit

Permalink
Add forc-crypto plugin exposing a CLI for common-use cryptographic op…
Browse files Browse the repository at this point in the history
…erations

Fixes #4318

Supported algorithms:
* [x] keccak256
* [x] sha256
* [x] bech32-to-hex
* [x] hex-to-bech32
* [x] new-key
* [x] new-parse secret
  • Loading branch information
cr-fuel committed Oct 13, 2023
1 parent ae8a39c commit 406a291
Show file tree
Hide file tree
Showing 13 changed files with 518 additions and 29 deletions.
212 changes: 183 additions & 29 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"forc",
"forc-pkg",
"forc-plugins/forc-client",
"forc-plugins/forc-crypto",
"forc-plugins/forc-doc",
"forc-plugins/forc-fmt",
"forc-plugins/forc-lsp",
Expand Down
1 change: 1 addition & 0 deletions docs/book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
- [forc deploy](./forc/plugins/forc_client/forc_deploy.md)
- [forc run](./forc/plugins/forc_client/forc_run.md)
- [forc submit](./forc/plugins/forc_client/forc_submit.md)
- [forc crypto](./forc/plugins/forc_crypto.md)
- [forc doc](./forc/plugins/forc_doc.md)
- [forc explore](./forc/plugins/forc_explore.md)
- [forc fmt](./forc/plugins/forc_fmt.md)
Expand Down
1 change: 1 addition & 0 deletions docs/book/src/forc/plugins/forc_crypto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# forc crypto
28 changes: 28 additions & 0 deletions forc-plugins/forc-crypto/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "forc-crypto"
version = "0.46.0"
description = "A `forc` plugin for handling various cryptographic operations and conversions."
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true


[dependencies]
anyhow = "1.0.75"
async-trait = "0.1.58"
clap = { version = "3", features = ["derive", "env"] }
forc-tracing = { version = "0.46.0", path = "../../forc-tracing" }
fuel-core-types = { version = "0.20.5" }
fuel-crypto = { workspace = true }
fuels-core = { workspace = true }
futures = "0.3"
hex = "0.4.3"
libp2p-identity = { version = "0.2.4", features = ["secp256k1", "peerid"] }
rand = "0.8"
serde = "1.0"
serde_json = "1"
sha3 = "0.10.8"
tokio = { version = "1.8", features = ["macros", "rt-multi-thread", "process"] }
tracing = "0.1"
31 changes: 31 additions & 0 deletions forc-plugins/forc-crypto/src/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use anyhow::anyhow;
use fuel_crypto::fuel_types::Address;
use fuels_core::types::bech32::Bech32Address;
use std::str::{from_utf8, FromStr};

/// Takes a valid address in any supported format and returns them in all
/// supported format. This is meant to be a tool that can be used to convert any
/// address format to all other formats
pub fn dump_address<T: AsRef<[u8]>>(data: T) -> anyhow::Result<String> {
let bytes_32: Result<[u8; 32], _> = data.as_ref().try_into();
let (bech32, addr) = match bytes_32 {
Ok(bytes) => (
Bech32Address::from(Address::from(bytes)),
Address::from(bytes),
),
Err(_) => handle_string_conversion(data)?,
};

Ok(format!("Bench32: {}\nAddress: 0x{}\n", bech32, addr))
}

fn handle_string_conversion<T: AsRef<[u8]>>(data: T) -> anyhow::Result<(Bech32Address, Address)> {
let addr = from_utf8(data.as_ref())?;
if let Ok(bech32) = Bech32Address::from_str(addr) {
Ok((bech32.clone(), Address::from(bech32)))
} else if let Ok(addr) = Address::from_str(addr) {
Ok((Bech32Address::from(addr), addr))
} else {
Err(anyhow!("{} cannot be parsed to a valid address", addr))
}
}
54 changes: 54 additions & 0 deletions forc-plugins/forc-crypto/src/content.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use anyhow::{anyhow, Result};
use std::{
fs::read,
path::PathBuf,
str::{from_utf8, FromStr},
};

#[derive(Clone, Debug, PartialEq)]
pub enum Content {
Path(PathBuf, Vec<u8>),
Binary(Vec<u8>),
}

impl Content {
pub fn from_hex_or_utf8(input: Vec<u8>) -> Result<Vec<u8>> {
if let Ok(text) = from_utf8(&input) {
let text = text.trim();
if let Some(text) = text.strip_prefix("0x") {
if let Ok(bin) = hex::decode(text) {
return Ok(bin);
}
}
Ok(text.as_bytes().to_vec())
} else {
Err(anyhow!(
"{:?} is not a valid UTF-8 string nor a valid hex string",
input
))
}
}
}

impl FromStr for Content {
type Err = anyhow::Error;

fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let path = PathBuf::from(s);
match read(&path) {
Ok(content) => Ok(Content::Path(path, Self::from_hex_or_utf8(content)?)),
Err(_) => Ok(Content::Binary(Self::from_hex_or_utf8(
s.as_bytes().to_vec(),
)?)),
}
}
}

impl AsRef<[u8]> for Content {
fn as_ref(&self) -> &[u8] {
match self {
Content::Path(_, content) => content.as_ref(),
Content::Binary(raw) => raw.as_ref(),
}
}
}
8 changes: 8 additions & 0 deletions forc-plugins/forc-crypto/src/keccak256.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use sha3::{Digest, Keccak256};

/// Hashes a given data using Keccak256
pub fn hash<T: AsRef<[u8]>>(data: T) -> anyhow::Result<Vec<u8>> {
let mut hasher = Keccak256::new();
hasher.update(data);
Ok(hasher.finalize().to_vec())
}
15 changes: 15 additions & 0 deletions forc-plugins/forc-crypto/src/keygen/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use clap::ValueEnum;

pub mod new_key;
pub mod parse_secret;

pub const BLOCK_PRODUCTION: &str = "block_production";
pub const P2P: &str = "p2p";

#[derive(Clone, Debug, Default, ValueEnum)]
#[clap(rename_all = "snake_case")]
pub enum KeyType {
#[default]
BlockProduction,
Peering,
}
64 changes: 64 additions & 0 deletions forc-plugins/forc-crypto/src/keygen/new_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! This file is migrated from https://github.com/FuelLabs/fuel-core/blob/master/bin/keygen/src/keygen.rs
use super::{KeyType, BLOCK_PRODUCTION, P2P};
use anyhow::Result;
use fuel_core_types::{
fuel_crypto::{
rand::{prelude::StdRng, SeedableRng},
SecretKey,
},
fuel_tx::Input,
};
use libp2p_identity::{secp256k1, Keypair, PeerId};
use serde_json::json;
use std::ops::Deref;

/// Generate a random new secret & public key in the format expected by fuel-core
#[derive(Debug, clap::Args)]
#[clap(author, version, about = "Creates a new key for use with fuel-core")]
pub struct Arg {
#[clap(long = "pretty", short = 'p')]
pretty: bool,
#[clap(
long = "key-type",
short = 'k',
value_enum,
default_value = BLOCK_PRODUCTION
)]
key_type: KeyType,
}

pub fn handler(arg: Arg) -> Result<String> {
let mut rng = StdRng::from_entropy();
let secret = SecretKey::random(&mut rng);
let public_key = secret.public_key();
let secret_str = secret.to_string();

let output = match arg.key_type {
KeyType::BlockProduction => {
let address = Input::owner(&public_key);
json!({
"secret": secret_str,
"address": address,
"type": BLOCK_PRODUCTION,
})
}
KeyType::Peering => {
let mut bytes = *secret.deref();
let p2p_secret = secp256k1::SecretKey::try_from_bytes(&mut bytes)
.expect("Should be a valid private key");
let p2p_keypair = secp256k1::Keypair::from(p2p_secret);
let libp2p_keypair = Keypair::from(p2p_keypair);
let peer_id = PeerId::from_public_key(&libp2p_keypair.public());
json!({
"secret": secret_str,
"peer_id": peer_id.to_string(),
"type": P2P
})
}
};
Ok(if arg.pretty {
serde_json::to_string_pretty(&output)
} else {
serde_json::to_string(&output)
}?)
}
59 changes: 59 additions & 0 deletions forc-plugins/forc-crypto/src/keygen/parse_secret.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! This file is migrated from https://github.com/FuelLabs/fuel-core/blob/master/bin/keygen/src/keygen.rs
use super::{KeyType, BLOCK_PRODUCTION, P2P};
use anyhow::Result;
use fuel_core_types::{fuel_crypto::SecretKey, fuel_tx::Input};
use libp2p_identity::{secp256k1, Keypair, PeerId};
use serde_json::json;
use std::{ops::Deref, str::FromStr};

/// Parse a secret key to view the associated public key
#[derive(Debug, clap::Args)]
#[clap(
author,
version,
about = "Parses a private key to view the associated public key"
)]
pub struct Arg {
secret: String,
#[clap(long = "pretty", short = 'p')]
pretty: bool,
#[clap(
long = "key-type",
short = 'k',
value_enum,
default_value = BLOCK_PRODUCTION
)]
key_type: KeyType,
}

pub fn handler(arg: Arg) -> Result<String> {
let secret = SecretKey::from_str(&arg.secret)?;
let output = match arg.key_type {
KeyType::BlockProduction => {
let address = Input::owner(&secret.public_key());
let output = json!({
"address": address.to_string(),
"type": BLOCK_PRODUCTION
});
output
}
KeyType::Peering => {
let mut bytes = *secret.deref();
let p2p_secret = secp256k1::SecretKey::try_from_bytes(&mut bytes)
.expect("Should be a valid private key");
let p2p_keypair = secp256k1::Keypair::from(p2p_secret);
let libp2p_keypair = Keypair::from(p2p_keypair);
let peer_id = PeerId::from_public_key(&libp2p_keypair.public());
let output = json!({
"peer_id": peer_id.to_string(),
"type": P2P
});
output
}
};
Ok(if arg.pretty {
serde_json::to_string_pretty(&output)
} else {
serde_json::to_string(&output)
}?)
}
65 changes: 65 additions & 0 deletions forc-plugins/forc-crypto/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! A `forc` plugin for converting a given string or path to their hash.

use anyhow::Result;
use clap::Parser;
use forc_tracing::{init_tracing_subscriber, println_error};
use std::default::Default;
use tracing::info;

mod address;
mod content;
mod keccak256;
mod keygen;
mod sha256;

#[derive(Debug, clap::Args)]
#[clap(author, version, about = "Hashes the argument or file with this hash")]
pub struct HashArgs {
pub content: content::Content,
}

#[derive(Debug, clap::Args)]
#[clap(
author,
version,
about = "Converts any valid address to all supported formats"
)]
pub struct AddressArgs {
pub content: String,
}

#[derive(Debug, Parser)]
#[clap(
name = "forc-crypto",
about = "Forc plugin for hashing arbitrary data.",
version
)]
pub enum Command {
Keccak256(HashArgs),
Sha256(HashArgs),
Address(AddressArgs),
NewKey(keygen::new_key::Arg),
ParseSecret(keygen::parse_secret::Arg),
}

fn main() {
init_tracing_subscriber(Default::default());
if let Err(err) = run() {
println_error(&format!("{}", err));
std::process::exit(1);
}
}

fn run() -> Result<()> {
let app = Command::parse();
let content = match app {
Command::Keccak256(arg) => hex::encode(keccak256::hash(arg.content)?),
Command::Sha256(arg) => hex::encode(sha256::hash(arg.content)?),
Command::Address(arg) => address::dump_address(arg.content)?,
Command::NewKey(arg) => keygen::new_key::handler(arg)?,
Command::ParseSecret(arg) => keygen::parse_secret::handler(arg)?,
};

info!("{}", content);
Ok(())
}
8 changes: 8 additions & 0 deletions forc-plugins/forc-crypto/src/sha256.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use fuel_crypto::Hasher;

/// Hashes a given data to Sha256
pub fn hash<T: AsRef<[u8]>>(data: T) -> anyhow::Result<Vec<u8>> {
let mut hasher = Hasher::default();
hasher.input(data);
Ok(hasher.finalize().to_vec())
}

0 comments on commit 406a291

Please sign in to comment.