Skip to content

Commit

Permalink
support ethermint signing
Browse files Browse the repository at this point in the history
  • Loading branch information
yihuang committed Aug 20, 2021
1 parent 60ce4de commit 0115216
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pbkdf2 = {version = "0.8"}
hmac = {version = "0.11"}
rand = {version = "0.8"}
rust_decimal = "1.9"
secp256k1 = "0.20"
secp256k1 = { version = "0.20", features = ["recovery"] }
tendermint-proto = "0.21"
tonic = "0.4"
bytes = "1.0"
Expand Down
71 changes: 56 additions & 15 deletions src/private_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::public_key::{PublicKey, COSMOS_PUBKEY_URL};
use crate::utils::bytes_to_hex_str;
use crate::utils::encode_any;
use crate::utils::hex_str_to_bytes;
use crate::utils::keccak256_hash;
use crate::{coin::Fee, Address};
use crate::{error::*, utils::contains_non_hex_chars};
use cosmos_sdk_proto::cosmos::crypto::secp256k1::PubKey as ProtoSecp256k1Pubkey;
Expand Down Expand Up @@ -38,6 +39,11 @@ struct TxParts {
signatures: Vec<Vec<u8>>,
}

enum SignType {
Cosmos,
Ethermint,
}

/// This structure represents a private key of a Cosmos Network.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub struct PrivateKey([u8; 32]);
Expand Down Expand Up @@ -145,6 +151,7 @@ impl PrivateKey {
args: MessageArgs,
memo: impl Into<String>,
pk_url: &str,
sign_type: SignType,
) -> Result<TxParts, PrivateKeyError> {
// prefix does not matter in this case, you could use a blank string
let our_pubkey = self.to_public_key(PublicKey::DEFAULT_PREFIX)?;
Expand Down Expand Up @@ -201,11 +208,23 @@ impl PrivateKey {

let secp256k1 = Secp256k1::new();
let sk = SecretKey::from_slice(&self.0)?;
let digest = Sha256::digest(&signdoc_buf);
let msg = CurveMessage::from_slice(&digest)?;
// Sign the signdoc
let signed = secp256k1.sign(&msg, &sk);
let compact = signed.serialize_compact().to_vec();
let compact = match sign_type {
SignType::Cosmos => {
let digest = Sha256::digest(&signdoc_buf);
let msg = CurveMessage::from_slice(&digest)?;
// Sign the signdoc
let signed = secp256k1.sign(&msg, &sk);
signed.serialize_compact().to_vec()
}
SignType::Ethermint => {
let hash = keccak256_hash(&signdoc_buf);
let s = Secp256k1::signing_only();
// SAFETY: hash is 32 bytes, as expected in `Message::from_slice` -- see `keccak256_hash`, hence `unwrap`
let sign_msg = CurveMessage::from_slice(hash.as_slice()).unwrap();
let (_, sig_bytes) = s.sign_recoverable(&sign_msg, &sk).serialize_compact();
sig_bytes.to_vec()
}
};

Ok(TxParts {
body,
Expand All @@ -216,16 +235,15 @@ impl PrivateKey {
})
}

/// Signs a transaction that contains at least one message using a single
/// private key, returns the standard Tx type, useful for simulations
pub fn get_signed_tx_ethermint(
fn do_get_signed_tx(
&self,
messages: &[Msg],
args: MessageArgs,
memo: impl Into<String>,
pk_url: &str,
sign_type: SignType,
) -> Result<Tx, PrivateKeyError> {
let parts = self.build_tx(messages, args, memo, pk_url)?;
let parts = self.build_tx(messages, args, memo, pk_url, sign_type)?;
Ok(Tx {
body: Some(parts.body),
auth_info: Some(parts.auth_info),
Expand All @@ -235,25 +253,36 @@ impl PrivateKey {

/// Signs a transaction that contains at least one message using a single
/// private key, returns the standard Tx type, useful for simulations
pub fn get_signed_tx(
pub fn get_signed_tx_ethermint(
&self,
messages: &[Msg],
args: MessageArgs,
memo: impl Into<String>,
pk_url: &str,
) -> Result<Tx, PrivateKeyError> {
self.get_signed_tx_ethermint(messages, args, memo, COSMOS_PUBKEY_URL)
self.do_get_signed_tx(messages, args, memo, pk_url, SignType::Ethermint)
}

/// Signs a transaction that contains at least one message using a single
/// private key.
pub fn sign_std_msg_ethermint(
/// private key, returns the standard Tx type, useful for simulations
pub fn get_signed_tx(
&self,
messages: &[Msg],
args: MessageArgs,
memo: impl Into<String>,
) -> Result<Tx, PrivateKeyError> {
self.do_get_signed_tx(messages, args, memo, COSMOS_PUBKEY_URL, SignType::Cosmos)
}

fn do_sign_std_msg(
&self,
messages: &[Msg],
args: MessageArgs,
memo: impl Into<String>,
pk_url: &str,
sign_type: SignType,
) -> Result<Vec<u8>, PrivateKeyError> {
let parts = self.build_tx(messages, args, memo, pk_url)?;
let parts = self.build_tx(messages, args, memo, pk_url, sign_type)?;

let tx_raw = TxRaw {
body_bytes: parts.body_buf,
Expand All @@ -269,6 +298,18 @@ impl PrivateKey {
Ok(txraw_buf)
}

/// Signs a transaction that contains at least one message using a single
/// private key.
pub fn sign_std_msg_ethermint(
&self,
messages: &[Msg],
args: MessageArgs,
memo: impl Into<String>,
pk_url: &str,
) -> Result<Vec<u8>, PrivateKeyError> {
self.do_sign_std_msg(messages, args, memo, pk_url, SignType::Ethermint)
}

/// Signs a transaction that contains at least one message using a single
/// private key.
pub fn sign_std_msg(
Expand All @@ -277,7 +318,7 @@ impl PrivateKey {
args: MessageArgs,
memo: impl Into<String>,
) -> Result<Vec<u8>, PrivateKeyError> {
self.sign_std_msg_ethermint(messages, args, memo, COSMOS_PUBKEY_URL)
self.do_sign_std_msg(messages, args, memo, COSMOS_PUBKEY_URL, SignType::Cosmos)
}
}

Expand Down
11 changes: 1 addition & 10 deletions src/public_key.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::error::*;
use crate::utils::hex_str_to_bytes;
use crate::utils::{hex_str_to_bytes, keccak256_hash};
use crate::{address::Address, utils::ArrayString};
use bech32::Variant;
use bech32::{self, FromBase32, ToBase32};
Expand All @@ -10,7 +10,6 @@ use std::hash::Hash;
use std::str::FromStr;

use secp256k1::{constants, PublicKey as PublicKeyEC};
use tiny_keccak::{Hasher, Keccak};

pub static COSMOS_PUBKEY_URL: &str = "/cosmos.crypto.secp256k1.PubKey";

Expand Down Expand Up @@ -210,14 +209,6 @@ impl fmt::Debug for PublicKey {
}
}

fn keccak256_hash(bytes: &[u8]) -> Vec<u8> {
let mut hasher = Keccak::v256();
hasher.update(bytes);
let mut resp = vec![0u8; 32];
hasher.finalize(&mut resp);
resp
}

#[test]
fn check_bech32() {
let raw_bytes = [
Expand Down
10 changes: 10 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::{str, usize};
use tiny_keccak::{Hasher, Keccak};

/// A function that takes a hexadecimal representation of bytes
/// back into a stream of bytes.
Expand Down Expand Up @@ -142,6 +143,15 @@ pub fn encode_any(input: impl prost::Message, type_url: String) -> Any {
Any { type_url, value }
}

/// keccak256_hash is used in ethermint mode
pub fn keccak256_hash(bytes: &[u8]) -> Vec<u8> {
let mut hasher = Keccak::v256();
hasher.update(bytes);
let mut resp = vec![0u8; 32];
hasher.finalize(&mut resp);
resp
}

#[cfg(test)]
mod tests {
// Note this useful idiom: importing names from outer (for mod tests) scope.
Expand Down

0 comments on commit 0115216

Please sign in to comment.