Skip to content
This repository has been archived by the owner on May 11, 2023. It is now read-only.

Commit

Permalink
common: improve WalletConnect
Browse files Browse the repository at this point in the history
* add gas limit
* remove nonce (could cause issue)
* don't transform `v` as it's legacy
* improve signature extraction
* add error for when WalletConnect API failed to sign

Signed-off-by: xphoniex <[email protected]>
  • Loading branch information
xphoniex committed May 2, 2022
1 parent 4561ce1 commit ed482a0
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 15 deletions.
2 changes: 1 addition & 1 deletion common/src/ethereum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ pub enum WalletError {
#[error(transparent)]
Local(#[from] ethers::signers::WalletError),
#[error(transparent)]
WalletConnect(#[from] ::walletconnect::client::CallError),
WalletConnect(#[from] walletconnect::WalletError),
#[error("no wallet specified")]
NoWallet,
}
Expand Down
134 changes: 120 additions & 14 deletions common/src/ethereum/walletconnect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ pub struct WalletConnect {
address: Address,
}

#[derive(Debug, thiserror::Error)]
pub enum WalletError {
#[error(transparent)]
CallError(#[from] CallError),
#[error("failed to sign the tx")]
FailedToSignTx,
}

impl WalletConnect {
pub fn new() -> Result<Self, Box<dyn Error>> {
let client = Client::new(
Expand Down Expand Up @@ -61,14 +69,15 @@ impl WalletConnect {
pub async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
&self,
msg: S,
) -> Result<Signature, CallError> {
) -> Result<Signature, WalletError> {
let msg = unsafe { std::str::from_utf8_unchecked(msg.as_ref()) };
self.client
.personal_sign(&[msg, &self.address_string()])
.await
.map_err(WalletError::from)
}

pub async fn sign_transaction(&self, msg: &TypedTransaction) -> Result<Signature, CallError> {
pub async fn sign_transaction(&self, msg: &TypedTransaction) -> Result<Signature, WalletError> {
let to = if let Some(NameOrAddress::Address(address)) = msg.to() {
Some(*address)
} else {
Expand All @@ -77,28 +86,125 @@ impl WalletConnect {
let tx = Transaction {
from: *msg.from().unwrap(),
to,
gas_limit: None,
gas_limit: msg.gas().cloned(),
gas_price: msg.gas_price(),
value: *msg.value().unwrap_or(&U256::from(0)),
data: msg.data().unwrap().to_vec(),
nonce: msg.nonce().copied(),
nonce: None,
};

let raw = self.client.sign_transaction(tx).await?.to_vec();
assert_eq!(raw[raw.len() - 66], 160);
assert_eq!(raw[raw.len() - 33], 160);

// Transform `v` according to:
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#specification
let mut v = raw[raw.len() - 67] as u64;
if v == 27 || v == 28 {
v += 2 * self.chain_id() + 8;
let mut v_r_s = None;
for offset in 0..7 {
let mut head = raw.len() - 67 + offset;
v_r_s = extract_v_r_s(&raw[head..]);
if v_r_s.is_some() {
break;
}

if offset == 0 {
continue;
}
head = raw.len() - 67 - offset;
v_r_s = extract_v_r_s(&raw[head..]);
if v_r_s.is_some() {
break;
}
}

let (v, r, s) = v_r_s.ok_or(WalletError::FailedToSignTx)?;
Ok(Signature {
v,
r: U256::from(&raw[raw.len() - 65..raw.len() - 33]),
s: U256::from(&raw[raw.len() - 32..]),
r: U256::from(r),
s: U256::from(s),
})
}
}

fn extract_v_r_s(tx: &[u8]) -> Option<(u64, &[u8], &[u8])> {
let mut head = 0_usize;
let v: u64 = tx[head].into();

head += 1;
if tx[head] <= 0x80 {
return None;
}
let len_r = (tx[head] - 0x80) as usize;
if head + len_r >= tx.len() {
return None;
}
let r = &tx[head + 1..head + 1 + len_r];

head += 1 + len_r;
if tx[head] <= 0x80 {
return None;
}
let len_s = (tx[head] - 0x80) as usize;
if head + len_s >= tx.len() {
return None;
}
let s = &tx[head + 1..head + 1 + len_s];

if 1 + r.len() + s.len() + 2 != tx.len() {
return None;
}

Some((v, r, s))
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_regular_sig() {
let tx = [
0x1c, 0xa0, 0x88, 0xff, 0x6c, 0xf0, 0xfe, 0xfd, 0x94, 0xdb, 0x46, 0x11, 0x11, 0x49,
0xae, 0x4b, 0xfc, 0x17, 0x9e, 0x9b, 0x94, 0x72, 0x1f, 0xff, 0xd8, 0x21, 0xd3, 0x8d,
0x16, 0x46, 0x4b, 0x3f, 0x71, 0xd0, 0xa0, 0x45, 0xe0, 0xaf, 0xf8, 0x00, 0x96, 0x1c,
0xfc, 0xe8, 0x05, 0xda, 0xef, 0x70, 0x16, 0xb9, 0xb6, 0x75, 0xc1, 0x37, 0xa6, 0xa4,
0x1a, 0x54, 0x8f, 0x7b, 0x60, 0xa3, 0x48, 0x4c, 0x06, 0xa3, 0x3a,
];

let v_r_s = extract_v_r_s(&tx);
assert!(v_r_s.is_some());
let (v, r, s) = v_r_s.unwrap();

assert_eq!(v, 0x1c);
assert_eq!(r, &tx[tx.len() - 65..tx.len() - 33]);
assert_eq!(s, &tx[tx.len() - 32..]);
}

#[test]
fn test_variable_sig() {
let tx = [
0x2c, 0xa0, 0x09, 0x0c, 0x0a, 0x25, 0xaf, 0x16, 0x3b, 0x51, 0x86, 0xd5, 0x6f, 0x61,
0xd2, 0xd1, 0xe7, 0xcf, 0xf1, 0x05, 0xb8, 0x9e, 0x24, 0xed, 0x48, 0x26, 0x7c, 0x43,
0xa0, 0x22, 0x27, 0xd9, 0xf7, 0x14, 0x9f, 0x9b, 0xcc, 0xf7, 0x3a, 0xef, 0xa7, 0x7d,
0x2c, 0xcb, 0x0b, 0x81, 0x59, 0x15, 0x04, 0xde, 0xcc, 0x07, 0xc1, 0x26, 0x92, 0xf9,
0x0f, 0xfe, 0x47, 0xd0, 0xf0, 0xbd, 0xea, 0x99, 0xa6, 0x8d,
];

let v_r_s = extract_v_r_s(&tx);
assert!(v_r_s.is_some());
let (v, r, s) = v_r_s.unwrap();

assert_eq!(v, 0x2c);
assert_eq!(r, &tx[tx.len() - 64..tx.len() - 32]);
assert_eq!(s, &tx[tx.len() - 31..]);
}

#[test]
fn test_malformed_sig() {
let tx = [
0x2c, 0xa0, 0x09, 0x0c, 0x0a, 0x25, 0xaf, 0x16, 0x3b, 0x51, 0x86, 0xd5, 0x6f, 0x61,
0xd2, 0xd1, 0xe7, 0xcf, 0xf1, 0x05, 0xb8, 0x9e, 0x24, 0xed, 0x48, 0x26, 0x7c, 0x43,
0xa0, 0x22, 0x27, 0xd9, 0xf7, 0x14, 0x81, 0x9b, 0xcc, 0xf7, 0x3a, 0xef, 0xa7, 0x7d,
0x2c, 0xcb, 0x0b, 0x81, 0x59, 0x15, 0x04, 0xde, 0xcc, 0x07, 0xc1, 0x26, 0x92, 0xf9,
0x0f, 0xfe, 0x47, 0xd0, 0xf0, 0xbd, 0xea, 0x99, 0xa6, 0x8d,
];

let v_r_s = extract_v_r_s(&tx);
assert!(v_r_s.is_none());
}
}

0 comments on commit ed482a0

Please sign in to comment.