-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement manual signing support for token claims (#483)
* Implement eth personal sign common logic * Rename to eip191-common * Use no_std hash calculation for eip191 * Prepare hardcoded eip191-token-claim message builder * Add eip191 logic to token claim verifier at runtime * Update docs * Update dependencies * Fix cargo doc * Proper message preparing for token claim * Add real world test case * Fix cargo toml * Proper keccak_256 usage * Use primitives-ethereum deps at test * Rename eip712 to eip at runtime * Add comment to test case * Update comment for TokenClaimVerifier * Rename eip ti eth_sig * Proper import at benchmarking * Fix cargo toml * Rename mod deps at benchmarking
- Loading branch information
1 parent
b01b196
commit eba34f1
Showing
10 changed files
with
265 additions
and
13 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[package] | ||
name = "eip191-crypto" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
primitives-ethereum = { version = "0.1", path = "../primitives-ethereum", default-features = false } | ||
|
||
numtoa = { version = "0.2", default-features = false } | ||
sp-core-hashing-proc-macro = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/humanode-2022-09-21" } | ||
sp-io = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/humanode-2022-09-21" } | ||
sp-std = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/humanode-2022-09-21" } | ||
|
||
[dev-dependencies] | ||
hex = "0.4" | ||
|
||
[features] | ||
default = ["std"] | ||
std = ["primitives-ethereum/std", "sp-io/std", "sp-std/std"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
//! Common logic for EIP-191 message construction and signature verification. | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
use numtoa::NumToA; | ||
use primitives_ethereum::{EcdsaSignature, EthereumAddress}; | ||
use sp_io::hashing::keccak_256; | ||
use sp_std::{vec, vec::Vec}; | ||
|
||
/// Extract the signer address from the signature and the message. | ||
pub fn recover_signer(sig: &EcdsaSignature, message: &[u8]) -> Option<EthereumAddress> { | ||
let msg_hash = make_personal_message_hash(message); | ||
let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig.0, &msg_hash).ok()?; | ||
Some(ecdsa_public_key_to_ethereum_address(&pubkey)) | ||
} | ||
|
||
/// Prepare the eth personal sign message. | ||
fn make_personal_message_hash(message: &[u8]) -> [u8; 32] { | ||
let mut buf = vec![]; | ||
buf.extend_from_slice("\x19Ethereum Signed Message:\n".as_bytes()); | ||
buf.extend_from_slice(usize_as_string_bytes(message.len()).as_slice()); | ||
buf.extend_from_slice(message); | ||
keccak_256(&buf) | ||
} | ||
|
||
/// A helper function to represent message len as string bytes. | ||
/// | ||
/// <https://crates.io/crates/numtoa>. | ||
fn usize_as_string_bytes(message_len: usize) -> Vec<u8> { | ||
let mut buffer = [0u8; 20]; | ||
message_len.numtoa(10, &mut buffer).to_vec() | ||
} | ||
|
||
/// Convert the ECDSA public key to Ethereum address. | ||
fn ecdsa_public_key_to_ethereum_address(pubkey: &[u8; 64]) -> EthereumAddress { | ||
let mut address = [0u8; 20]; | ||
address.copy_from_slice(&keccak_256(pubkey)[12..]); | ||
EthereumAddress(address) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
/// This test contains the data obtained from Metamask/eth-sig-util. | ||
/// | ||
/// https://github.com/MetaMask/eth-sig-util/blob/8a470650074174f5338308d2acbd97caf5542434/src/personal-sign.test.ts#L88 | ||
#[test] | ||
fn valid_signature() { | ||
let message = "hello world"; | ||
let hex_signature = "0xce909e8ea6851bc36c007a0072d0524b07a3ff8d4e623aca4c71ca8e57250c4d0a3fc38fa8fbaaa81ead4b9f6bd03356b6f8bf18bccad167d78891636e1d69561b"; | ||
let expected_address = "0xbe93f9bacbcffc8ee6663f2647917ed7a20a57bb"; | ||
|
||
let address = recover_signer( | ||
&EcdsaSignature( | ||
hex::decode(&hex_signature[2..]) | ||
.unwrap() | ||
.try_into() | ||
.unwrap(), | ||
), | ||
message.as_bytes(), | ||
); | ||
|
||
assert_eq!( | ||
format!("0x{}", hex::encode(address.unwrap().0)), | ||
expected_address | ||
); | ||
} | ||
|
||
/// This test contains the data obtained from MetaMask browser extension via an injected web3 | ||
/// interface using personal_sign API. | ||
/// | ||
/// https://metamask.github.io/test-dapp/. | ||
/// | ||
/// It validates that the real-world external ecosystem works properly with our code. | ||
#[test] | ||
fn real_world_case() { | ||
let message = "Example `personal_sign` message"; | ||
let hex_signature = "0xbef8374833e572271b2f17d233a8e03c53c8f35e451cd33494793bbdc036f1d72dd955c0628483bc50bd3f7849d1d730a69cdd9775ab3eed556b87eaa20426511b"; | ||
let expected_address = "0xc16fb04cbc2c946399772688c33d9bb6ae6ac71b"; | ||
|
||
let address = recover_signer( | ||
&EcdsaSignature( | ||
hex::decode(&hex_signature[2..]) | ||
.unwrap() | ||
.try_into() | ||
.unwrap(), | ||
), | ||
message.as_bytes(), | ||
); | ||
|
||
assert_eq!( | ||
format!("0x{}", hex::encode(address.unwrap().0)), | ||
expected_address | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[package] | ||
name = "eip191-token-claim" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
hex = { version = "0.4", default-features = false } | ||
sp-std = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/humanode-2022-09-21" } | ||
|
||
[dev-dependencies] | ||
eip191-crypto = { version = "0.1", path = "../eip191-crypto" } | ||
primitives-ethereum = { version = "0.1", path = "../primitives-ethereum" } | ||
|
||
hex-literal = "0.3" | ||
|
||
[features] | ||
default = ["std"] | ||
std = ["hex/std", "sp-std/std"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
//! EIP-191 token claim message builder. | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
use sp_std::{vec, vec::Vec}; | ||
|
||
/// Token claim message. | ||
pub struct Message<'a> { | ||
/// Substrate address. | ||
pub substrate_address: &'a [u8; 32], | ||
/// Genesis hash. | ||
pub genesis_hash: &'a [u8; 32], | ||
} | ||
|
||
impl<'a> Message<'a> { | ||
/// Prepare EIP-191 token claim message. | ||
pub fn prepare_message(&self) -> Vec<u8> { | ||
let mut buf = vec![]; | ||
buf.extend_from_slice("I hereby sign that I claim HMND to 0x".as_bytes()); | ||
buf.extend_from_slice(hex::encode(self.substrate_address).as_bytes()); | ||
buf.extend_from_slice(" on network with genesis 0x".as_bytes()); | ||
buf.extend_from_slice(hex::encode(self.genesis_hash).as_bytes()); | ||
buf.extend_from_slice(".".as_bytes()); | ||
buf | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
|
||
use hex_literal::hex; | ||
use primitives_ethereum::EcdsaSignature; | ||
|
||
use super::*; | ||
|
||
/// This test contains the data obtained from MetaMask browser extension via an injected web3 | ||
/// interface. | ||
/// It validates that the real-world external ecosystem works properly with our code. | ||
#[test] | ||
fn real_world_case() { | ||
let substrate_address = | ||
hex!("1e38cdd099576380ca4df726fa8b740d3ae6b159e71cd5ef7aa621f5bd01d653"); | ||
let genesis_hash = hex!("bed15072ffa35432da5d20c33920b3afc2ab850e864a26b684e7f6caed6a1479"); | ||
|
||
let token_claim_message = Message { | ||
substrate_address: &substrate_address, | ||
genesis_hash: &genesis_hash, | ||
}; | ||
|
||
let signature = hex!("f76b13746bb661fb6a1242b5591d4442a88a09c1600c5ccb77e7083f37b7d17e6b975bbdd88e186870fcadd464dcff0a4b4f6d32e4a51291d4b1f543ea588ae11c"); | ||
|
||
let ethereum_address = eip191_crypto::recover_signer( | ||
&EcdsaSignature(signature), | ||
token_claim_message.prepare_message().as_slice(), | ||
) | ||
.unwrap(); | ||
|
||
assert_eq!( | ||
ethereum_address.0, | ||
hex!("f24ff3a9cf04c71dbc94d0b566f7a27b94566cac"), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.