Add recover_address() function
It can be used to recover ethereum address from signature.
This commit is contained in:
parent
0d6807e5a2
commit
ffdda2ac51
3 changed files with 70 additions and 15 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -1134,6 +1134,9 @@ name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hmac"
|
name = "hmac"
|
||||||
|
|
|
@ -31,7 +31,7 @@ deadpool-postgres = { version = "0.10.2", default-features = false }
|
||||||
# Used to read .env files
|
# Used to read .env files
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
# Used to work with hexadecimal strings
|
# Used to work with hexadecimal strings
|
||||||
hex = "0.4.3"
|
hex = { version = "0.4.3", features = ["serde"] }
|
||||||
# Used for logging
|
# Used for logging
|
||||||
log = { version = "0.4.14", features = ["serde"] }
|
log = { version = "0.4.14", features = ["serde"] }
|
||||||
env_logger = { version = "0.9.0", default-features = false }
|
env_logger = { version = "0.9.0", default-features = false }
|
||||||
|
|
|
@ -3,8 +3,15 @@ use std::str::FromStr;
|
||||||
use secp256k1::{Error as KeyError, SecretKey, rand::rngs::OsRng};
|
use secp256k1::{Error as KeyError, SecretKey, rand::rngs::OsRng};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use web3::ethabi::{token::Token, encode};
|
use web3::ethabi::{token::Token, encode};
|
||||||
use web3::signing::{keccak256, Key, SigningError};
|
use web3::signing::{
|
||||||
use web3::types::{Address, U256};
|
keccak256,
|
||||||
|
recover,
|
||||||
|
Key,
|
||||||
|
RecoveryError,
|
||||||
|
SecretKeyRef,
|
||||||
|
SigningError,
|
||||||
|
};
|
||||||
|
use web3::types::{Address, H256, Recovery, U256};
|
||||||
|
|
||||||
/// Generates signing key
|
/// Generates signing key
|
||||||
pub fn generate_ecdsa_key() -> SecretKey {
|
pub fn generate_ecdsa_key() -> SecretKey {
|
||||||
|
@ -15,8 +22,10 @@ pub fn generate_ecdsa_key() -> SecretKey {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct SignatureData {
|
pub struct SignatureData {
|
||||||
pub v: u64,
|
pub v: u64,
|
||||||
pub r: String,
|
#[serde(serialize_with = "hex::serde::serialize")]
|
||||||
pub s: String,
|
pub r: [u8; 32],
|
||||||
|
#[serde(serialize_with = "hex::serde::serialize")]
|
||||||
|
pub s: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
@ -29,28 +38,65 @@ pub enum SignatureError {
|
||||||
|
|
||||||
#[error("signing error")]
|
#[error("signing error")]
|
||||||
SigningError(#[from] SigningError),
|
SigningError(#[from] SigningError),
|
||||||
|
|
||||||
|
#[error("invalid signature")]
|
||||||
|
InvalidSignature,
|
||||||
|
|
||||||
|
#[error("recovery error")]
|
||||||
|
RecoveryError(#[from] RecoveryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign_message(
|
fn prepare_message(message: &[u8]) -> [u8; 32] {
|
||||||
signing_key: &str,
|
|
||||||
message: &[u8],
|
|
||||||
) -> Result<SignatureData, SignatureError> {
|
|
||||||
let key = SecretKey::from_str(signing_key)?;
|
|
||||||
let message_hash = keccak256(message);
|
let message_hash = keccak256(message);
|
||||||
let eip_191_message = [
|
let eip_191_message = [
|
||||||
"\x19Ethereum Signed Message:\n32".as_bytes(),
|
"\x19Ethereum Signed Message:\n32".as_bytes(),
|
||||||
&message_hash,
|
&message_hash,
|
||||||
].concat();
|
].concat();
|
||||||
let eip_191_message_hash = keccak256(&eip_191_message);
|
let eip_191_message_hash = keccak256(&eip_191_message);
|
||||||
let signature = Box::new(key).sign(&eip_191_message_hash, None)?;
|
eip_191_message_hash
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create EIP-191 signature
|
||||||
|
/// https://eips.ethereum.org/EIPS/eip-191
|
||||||
|
pub fn sign_message(
|
||||||
|
signing_key: &str,
|
||||||
|
message: &[u8],
|
||||||
|
) -> Result<SignatureData, SignatureError> {
|
||||||
|
let key = SecretKey::from_str(signing_key)?;
|
||||||
|
let key_ref = SecretKeyRef::new(&key);
|
||||||
|
let eip_191_message_hash = prepare_message(message);
|
||||||
|
// Create signature without replay protection (chain ID is None)
|
||||||
|
let signature = key_ref.sign(&eip_191_message_hash, None)?;
|
||||||
let signature_data = SignatureData {
|
let signature_data = SignatureData {
|
||||||
v: signature.v,
|
v: signature.v,
|
||||||
r: hex::encode(signature.r.as_bytes()),
|
r: signature.r.to_fixed_bytes(),
|
||||||
s: hex::encode(signature.s.as_bytes()),
|
s: signature.s.to_fixed_bytes(),
|
||||||
};
|
};
|
||||||
Ok(signature_data)
|
Ok(signature_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify EIP-191 signature
|
||||||
|
pub fn recover_address(
|
||||||
|
message: &[u8],
|
||||||
|
signature: &SignatureData,
|
||||||
|
) -> Result<Address, SignatureError> {
|
||||||
|
let eip_191_message_hash = prepare_message(message);
|
||||||
|
let recovery = Recovery::new(
|
||||||
|
"", // this message is not used
|
||||||
|
signature.v,
|
||||||
|
H256(signature.r),
|
||||||
|
H256(signature.s),
|
||||||
|
);
|
||||||
|
let (signature_raw, recovery_id) = recovery.as_signature()
|
||||||
|
.ok_or(SignatureError::InvalidSignature)?;
|
||||||
|
let address = recover(
|
||||||
|
&eip_191_message_hash,
|
||||||
|
&signature_raw,
|
||||||
|
recovery_id,
|
||||||
|
)?;
|
||||||
|
Ok(address)
|
||||||
|
}
|
||||||
|
|
||||||
pub type CallArgs = Vec<Box<dyn AsRef<[u8]>>>;
|
pub type CallArgs = Vec<Box<dyn AsRef<[u8]>>>;
|
||||||
|
|
||||||
pub fn sign_contract_call(
|
pub fn sign_contract_call(
|
||||||
|
@ -83,10 +129,16 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sign_message() {
|
fn test_sign_message() {
|
||||||
let signing_key = generate_ecdsa_key().to_string();
|
let signing_key = generate_ecdsa_key();
|
||||||
let message = "test_message";
|
let message = "test_message";
|
||||||
let result = sign_message(&signing_key, message.as_bytes()).unwrap();
|
let result = sign_message(
|
||||||
|
&signing_key.to_string(),
|
||||||
|
message.as_bytes(),
|
||||||
|
).unwrap();
|
||||||
assert!(result.v == 27 || result.v == 28);
|
assert!(result.v == 27 || result.v == 28);
|
||||||
|
|
||||||
|
let recovered = recover_address(message.as_bytes(), &result).unwrap();
|
||||||
|
assert_eq!(recovered, SecretKeyRef::new(&signing_key).address());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue