diff --git a/src/bin/mitractl.rs b/src/bin/mitractl.rs index 76da6a2..348575d 100644 --- a/src/bin/mitractl.rs +++ b/src/bin/mitractl.rs @@ -4,7 +4,8 @@ use uuid::Uuid; use mitra::config; use mitra::database::create_database_client; use mitra::database::migrate::apply_migrations; -use mitra::ethereum::utils::generate_ethereum_address; +use mitra::ethereum::signatures::generate_ecdsa_key; +use mitra::ethereum::utils::key_to_ethereum_address; use mitra::logger::configure_logger; use mitra::models::posts::queries::delete_post; use mitra::models::profiles::queries::delete_profile; @@ -77,7 +78,8 @@ async fn main() { match opts.subcmd { SubCommand::GenerateRsaKey(cmd) => cmd.execute(), SubCommand::GenerateEthereumAddress(_) => { - let (private_key, address) = generate_ethereum_address(); + let private_key = generate_ecdsa_key(); + let address = key_to_ethereum_address(&private_key); println!( "address {:?}; private key {}", address, private_key, diff --git a/src/ethereum/errors.rs b/src/ethereum/errors.rs index e24a00b..4c8d3d4 100644 --- a/src/ethereum/errors.rs +++ b/src/ethereum/errors.rs @@ -1,6 +1,7 @@ use crate::errors::DatabaseError; use super::contracts::ArtifactError; -use super::utils::{AddressError, SignatureError}; +use super::signatures::SignatureError; +use super::utils::AddressError; #[derive(thiserror::Error, Debug)] pub enum EthereumError { diff --git a/src/ethereum/mod.rs b/src/ethereum/mod.rs index 254b2f9..7fc2b47 100644 --- a/src/ethereum/mod.rs +++ b/src/ethereum/mod.rs @@ -3,4 +3,5 @@ pub mod contracts; mod errors; pub mod gate; pub mod nft; +pub mod signatures; pub mod utils; diff --git a/src/ethereum/nft.rs b/src/ethereum/nft.rs index c0589ef..3e5c504 100644 --- a/src/ethereum/nft.rs +++ b/src/ethereum/nft.rs @@ -6,9 +6,9 @@ use uuid::Uuid; use web3::{ api::Web3, contract::{Contract, Options}, - ethabi::{Event, EventParam, ParamType, RawLog, token::Token, encode}, + ethabi::{Event, EventParam, ParamType, RawLog, token::Token}, transports::Http, - types::{BlockNumber, FilterBuilder, H256, U256}, + types::{BlockNumber, FilterBuilder, H256}, }; use crate::config::BlockchainConfig; @@ -23,7 +23,8 @@ use crate::models::posts::queries::{ use super::api::connect; use super::contracts::{MANAGER, COLLECTIBLE, load_abi}; use super::errors::EthereumError; -use super::utils::{parse_address, sign_message, SignatureData}; +use super::signatures::{sign_contract_call, CallArgs, SignatureData}; +use super::utils::parse_address; const TOKEN_WAIT_TIME: i64 = 10; // in minutes @@ -189,18 +190,17 @@ pub fn create_mint_signature( user_address: &str, token_uri: &str, ) -> Result { - let contract_address = parse_address(&blockchain_config.contract_address)?; let user_address = parse_address(user_address)?; - let chain_id: U256 = blockchain_config.ethereum_chain_id().into(); - let chain_id_token = Token::Uint(chain_id); - let chain_id_bin = encode(&[chain_id_token]); - let message = [ - &chain_id_bin, - contract_address.as_bytes(), - "mint".as_bytes(), - user_address.as_bytes(), - token_uri.as_bytes(), - ].concat(); - let signature = sign_message(&blockchain_config.signing_key, &message)?; + let call_args: CallArgs = vec![ + Box::new(user_address), + Box::new(token_uri.to_string()), + ]; + let signature = sign_contract_call( + &blockchain_config.signing_key, + blockchain_config.ethereum_chain_id(), + &blockchain_config.contract_address, + "mint", + call_args, + )?; Ok(signature) } diff --git a/src/ethereum/signatures.rs b/src/ethereum/signatures.rs new file mode 100644 index 0000000..f0a89d6 --- /dev/null +++ b/src/ethereum/signatures.rs @@ -0,0 +1,108 @@ +use std::str::FromStr; + +use secp256k1::{Error as KeyError, SecretKey, rand::rngs::OsRng}; +use serde::Serialize; +use web3::ethabi::{token::Token, encode}; +use web3::signing::{keccak256, Key, SigningError}; +use web3::types::{Address, U256}; + +/// Generates signing key +pub fn generate_ecdsa_key() -> SecretKey { + let mut rng = OsRng::new().expect("failed to initialize RNG"); + SecretKey::new(&mut rng) +} + +#[derive(Serialize)] +pub struct SignatureData { + pub v: u64, + pub r: String, + pub s: String, +} + +#[derive(thiserror::Error, Debug)] +pub enum SignatureError { + #[error("invalid key")] + InvalidKey(#[from] KeyError), + + #[error("invalid data")] + InvalidData, + + #[error("signing error")] + SigningError(#[from] SigningError), +} + +pub fn sign_message( + signing_key: &str, + message: &[u8], +) -> Result { + let key = SecretKey::from_str(signing_key)?; + let message_hash = keccak256(message); + let eip_191_message = [ + "\x19Ethereum Signed Message:\n32".as_bytes(), + &message_hash, + ].concat(); + let eip_191_message_hash = keccak256(&eip_191_message); + let signature = Box::new(key).sign(&eip_191_message_hash, None)?; + let signature_data = SignatureData { + v: signature.v, + r: hex::encode(signature.r.as_bytes()), + s: hex::encode(signature.s.as_bytes()), + }; + Ok(signature_data) +} + +pub type CallArgs = Vec>>; + +pub fn sign_contract_call( + signing_key: &str, + chain_id: u32, + contract_address: &str, + method_name: &str, + method_args: CallArgs, +) -> Result { + let chain_id: U256 = chain_id.into(); + let chain_id_token = Token::Uint(chain_id); + let chain_id_bin = encode(&[chain_id_token]); + let contract_address = Address::from_str(contract_address) + .map_err(|_| SignatureError::InvalidData)?; + let mut message = [ + &chain_id_bin, + contract_address.as_bytes(), + method_name.as_bytes(), + ].concat(); + for arg in method_args { + message.extend(arg.as_ref().as_ref()); + }; + let signature = sign_message(signing_key, &message)?; + Ok(signature) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sign_message() { + let signing_key = generate_ecdsa_key().to_string(); + let message = "test_message"; + let result = sign_message(&signing_key, message.as_bytes()).unwrap(); + assert!(result.v == 27 || result.v == 28); + } + + #[test] + fn test_sign_contract_call() { + let signing_key = generate_ecdsa_key().to_string(); + let chain_id = 1; + let contract_address = "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"; + let method_name = "test"; + let method_args: CallArgs = vec![Box::new("arg1"), Box::new("arg2")]; + let result = sign_contract_call( + &signing_key, + chain_id, + contract_address, + method_name, + method_args, + ).unwrap(); + assert!(result.v == 27 || result.v == 28); + } +} diff --git a/src/ethereum/utils.rs b/src/ethereum/utils.rs index 62b525c..56403ef 100644 --- a/src/ethereum/utils.rs +++ b/src/ethereum/utils.rs @@ -1,10 +1,9 @@ use std::str::FromStr; use regex::Regex; -use secp256k1::{Error as KeyError, SecretKey, rand::rngs::OsRng}; -use serde::Serialize; +use secp256k1::SecretKey; use web3::{ - signing::{keccak256, Key, SigningError}, + signing::Key, types::Address, }; @@ -34,11 +33,8 @@ pub fn parse_caip2_chain_id(chain_id: &str) -> Result { Ok(eth_chain_id) } -pub fn generate_ethereum_address() -> (SecretKey, Address) { - let mut rng = OsRng::new().expect("failed to initialize RNG"); - let secret_key = SecretKey::new(&mut rng); - let address = Box::new(secret_key).address(); - (secret_key, address) +pub fn key_to_ethereum_address(private_key: &SecretKey) -> Address { + private_key.address() } #[derive(thiserror::Error, Debug)] @@ -49,43 +45,6 @@ pub fn parse_address(address: &str) -> Result { Address::from_str(address).map_err(|_| AddressError) } -#[derive(Serialize)] -pub struct SignatureData { - pub v: u64, - pub r: String, - pub s: String, -} - -#[derive(thiserror::Error, Debug)] -pub enum SignatureError { - #[error("invalid key")] - InvalidKey(#[from] KeyError), - - #[error("signing error")] - SigningError(#[from] SigningError), -} - -pub fn sign_message( - signing_key: &str, - message: &[u8], -) -> Result { - let key = SecretKey::from_str(signing_key)?; - let message_hash = keccak256(message); - let eip_191_message = [ - "\x19Ethereum Signed Message:\n32".as_bytes(), - &message_hash, - ].concat(); - let eip_191_message_hash = keccak256(&eip_191_message); - let signature = Box::new(key).sign(&eip_191_message_hash, None)?; - let signature_data = SignatureData { - v: signature.v, - r: hex::encode(signature.r.as_bytes()), - s: hex::encode(signature.s.as_bytes()), - }; - Ok(signature_data) -} - - #[cfg(test)] mod tests { use super::*;