diff --git a/bee-node/bee-node/src/tools/convert.rs b/bee-node/bee-node/src/tools/convert.rs new file mode 100644 index 0000000000..9417dfbdbd --- /dev/null +++ b/bee-node/bee-node/src/tools/convert.rs @@ -0,0 +1,151 @@ +// Copyright 2020-2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use bee_block::address::{Address, Ed25519Address}; +use crypto::hashes::{blake2b::Blake2b256, Digest}; +use structopt::StructOpt; +use thiserror::Error; + +#[derive(Clone, Debug, Error)] +pub enum ConvertError { + #[error("invalid Bech32 address length")] + InvalidAddressLength(), + #[error("Invalid Address")] + InvalidAddress(), +} + +#[derive(Clone, Debug, StructOpt)] +pub enum ConvertTool { + /// Converts a Bech32 address to a hex encoded one. + Bech32ToHex { + #[structopt(long)] + bech32: String, + }, + /// Converts a hex encoded address to a Bech32 one. + HexToBech32 { + #[structopt(long)] + hex: String, + #[structopt(long)] + hrp: String, + }, + /// Converts a hex encoded public key to a Bech32 address. + HexPubkeyToBech32 { + #[structopt(long)] + pubkey: String, + #[structopt(long)] + hrp: String, + }, +} + +pub fn exec(tool: &ConvertTool) -> Result<(), ConvertError> { + match tool { + ConvertTool::Bech32ToHex { bech32 } => { + let hex = bech32_to_hex(bech32.as_str()); + match hex { + Ok(_) => println!("Your Hex encoded address is:\t{}", hex.unwrap()), + Err(e) => println!("Error: {}", e), + } + } + ConvertTool::HexToBech32 { hex, hrp } => { + let bech32 = hex_to_bech32(hex.as_str(), hrp.as_str()); + match bech32 { + Ok(_) => println!("Your Bech32 address is:\t{:?}", bech32.unwrap()), + Err(e) => println!("Error: {}", e), + } + } + ConvertTool::HexPubkeyToBech32 { pubkey, hrp } => { + let bech32 = hex_public_key_to_bech32_address(pubkey.as_str(), hrp.as_str()); + match bech32 { + Ok(_) => println!("Your Bech32 address is:\t{:?}", bech32.unwrap()), + Err(e) => println!("Error: {}", e), + } + } + } + Ok(()) +} + +/// Transforms bech32 to hex +fn bech32_to_hex(bech32: &str) -> Result { + let address = Address::try_from_bech32(bech32).map_err(|_| ConvertError::InvalidAddress())?; + let hex_string = match address { + (_, Address::Ed25519(ed)) => ed.to_string(), + (_, Address::Alias(alias)) => alias.to_string(), + (_, Address::Nft(nft)) => nft.to_string(), + }; + Ok(hex_string) +} + +/// Transforms a hex encoded address to a bech32 encoded address +fn hex_to_bech32(hex: &str, bech32_hrp: &str) -> Result { + let address: Ed25519Address = hex + .parse::() + .map_err(|_| ConvertError::InvalidAddress())?; + Ok(Address::Ed25519(address).to_bech32(bech32_hrp)) +} + +/// Transforms a hex encoded public key to a bech32 encoded address +fn hex_public_key_to_bech32_address(hex: &str, bech32_hrp: &str) -> Result { + let mut public_key = [0u8; Ed25519Address::LENGTH]; + hex::decode_to_slice(&hex, &mut public_key).map_err(|_| ConvertError::InvalidAddressLength())?; + + let address = Blake2b256::digest(&public_key) + .try_into() + .map_err(|_e| ConvertError::InvalidAddress())?; + let address: Ed25519Address = Ed25519Address::new(address); + Ok(Address::Ed25519(address).to_bech32(bech32_hrp)) +} + +#[cfg(test)] +mod bech32tests { + use crate::tools::convert::*; + // spec: https://github.com/iotaledger/tips/blob/main/tips/TIP-0011/tip-0011.md + const HRP: &str = "iota"; + + #[test] + fn test_bech32_to_hex() { + let bech32_address = "iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx".to_string(); + let hex_encoded_address = bech32_to_hex(&bech32_address).unwrap(); + + assert_eq!( + &hex_encoded_address, + "0xefdc112efe262b304bcf379b26c31bad029f616ee3ec4aa6345a366e4c9e43a3" + ); + + let bech32_to_hex = ConvertTool::Bech32ToHex { bech32: bech32_address }; + exec(&bech32_to_hex).unwrap(); + } + + #[test] + fn test_hex_to_bech32() { + let hex_address = "0xefdc112efe262b304bcf379b26c31bad029f616ee3ec4aa6345a366e4c9e43a3".to_string(); + let bech32_address = hex_to_bech32(&hex_address, HRP).unwrap(); + + assert_eq!( + &bech32_address, + "iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx" + ); + + let hex_to_bech32 = ConvertTool::HexToBech32 { + hex: hex_address, + hrp: HRP.to_string(), + }; + exec(&hex_to_bech32).unwrap(); + } + + #[test] + fn test_public_key_to_bech32() { + let public_key = "6f1581709bb7b1ef030d210db18e3b0ba1c776fba65d8cdaad05415142d189f8".to_string(); + let bech32_address = hex_public_key_to_bech32_address(&public_key, HRP).unwrap(); + + assert_eq!( + &bech32_address, + "iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx" + ); + + let public_key_to_bech32 = ConvertTool::HexPubkeyToBech32 { + pubkey: public_key, + hrp: HRP.to_string(), + }; + exec(&public_key_to_bech32).unwrap(); + } +} diff --git a/bee-node/bee-node/src/tools/mod.rs b/bee-node/bee-node/src/tools/mod.rs index 8827b27c06..122df1e32b 100644 --- a/bee-node/bee-node/src/tools/mod.rs +++ b/bee-node/bee-node/src/tools/mod.rs @@ -1,6 +1,7 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod convert; mod ed25519; mod jwt_api; mod password; @@ -32,6 +33,8 @@ pub enum Tool { Password(password::PasswordTool), /// Generates a JWT for the Node API. JwtApi(jwt_api::JwtApiTool), + /// Converts back & forth between Bech32 and Hex. + Convert(convert::ConvertTool), } #[derive(Debug, Error)] @@ -50,6 +53,8 @@ pub enum ToolError { Password(#[from] password::PasswordError), #[error("{0}")] JwtApi(#[from] jwt_api::JwtApiError), + #[error("{0}")] + Convert(#[from] convert::ConvertError), } pub fn exec(tool: &Tool, local: &Local, node_config: &NodeConfig) -> Result<(), ToolError> { @@ -62,6 +67,7 @@ pub fn exec(tool: &Tool, local: &Local, node_config: &Nod Tool::SnapshotInfo(tool) => snapshot_info::exec(tool)?, Tool::Password(tool) => password::exec(tool)?, Tool::JwtApi(tool) => jwt_api::exec(tool, local, node_config)?, + Tool::Convert(tool) => convert::exec(tool)?, } Ok(())