Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EIP-1193 support #414

Merged
merged 14 commits into from
Dec 7, 2020
Merged
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ before_script:
- if [[ "${TRAVIS_RUST_VERSION}" == stable ]]; then
rustup component add rustfmt && cargo install cargo-deny;
fi
- rustup target add wasm32-unknown-unknown

script:
- if [[ "${TRAVIS_RUST_VERSION}" == "stable" ]]; then
Expand All @@ -42,6 +43,7 @@ script:
- cargo build
- cargo test
- cargo check --no-default-features
- cargo check --target wasm32-unknown-unknown --no-default-features --features eip-1193
- cargo check --no-default-features --features http
- cargo check --no-default-features --features http-tls
- cargo check --no-default-features --features ws-tokio
Expand Down
12 changes: 10 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ log = "0.4.6"
parking_lot = "0.11.0"
rlp = "0.4"
rustc-hex = "2.1.0"
secp256k1 = { version = "0.19", features = ["recovery"] }
secp256k1 = { version = "0.19", features = ["recovery"], optional = true }
serde = { version = "1.0.90", features = ["derive"] }
serde_json = "1.0.39"
tiny-keccak = { version = "2.0.1", features = ["keccak"] }
Expand All @@ -44,16 +44,24 @@ soketto = { version = "0.4.1", optional = true }
## Shared (WS, HTTP)
native-tls = { version = "0.2", optional = true }
url = { version = "2.1", optional = true }
## EIP-1193
js-sys = { version = "0.3.45", optional = true }
### This is a transitive dependency, only here so we can turn on its wasm_bindgen feature
rand = { version = "0.7.3", optional = true }
wasm-bindgen = { version = "0.2.68", optional = true, features = ["serde-serialize"] }
wasm-bindgen-futures = { version = "0.4.18", optional = true }

[dev-dependencies]
# For examples
env_logger = "0.8"
tokio = { version = "0.2", features = ["full"] }

[features]
default = ["http-tls", "ws-tls-tokio"]
default = ["http-tls", "signing", "ws-tls-tokio"]
eip-1193 = ["js-sys", "wasm-bindgen", "wasm-bindgen-futures", "futures-timer/wasm-bindgen", "rand/wasm-bindgen"]
http = ["hyper", "hyper-proxy", "url", "base64", "typed-headers"]
http-tls = ["hyper-tls", "native-tls", "http"]
signing = ["secp256k1"]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of disabling signing capabilities in JS it's actually possible to use libsecpk256k1 which compiles to WASM. See how it's done in ethsign. That said, it might be pretty involved and would rather review that separately in a follow up PR, so we can just create an issue for now.

ws-tokio = ["soketto", "url", "tokio", "tokio-util"]
ws-async-std = ["soketto", "url", "async-std"]
ws-tls-tokio = ["async-native-tls", "native-tls", "async-native-tls/runtime-tokio", "ws-tokio"]
Expand Down
22 changes: 19 additions & 3 deletions src/api/accounts.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
//! Partial implementation of the `Accounts` namespace.

use crate::api::{Namespace, Web3};
use crate::api::Namespace;
#[cfg(feature = "signing")]
use crate::api::Web3;
#[cfg(feature = "signing")]
use crate::error;
use crate::signing::{self, Signature};
use crate::signing;
#[cfg(feature = "signing")]
use crate::signing::Signature;
use crate::types::H256;
#[cfg(feature = "signing")]
use crate::types::{
Address, Bytes, Recovery, RecoveryMessage, SignedData, SignedTransaction, TransactionParameters, H256, U256,
Address, Bytes, Recovery, RecoveryMessage, SignedData, SignedTransaction, TransactionParameters, U256,
};
use crate::Transport;
#[cfg(feature = "signing")]
use rlp::RlpStream;
#[cfg(feature = "signing")]
use std::convert::TryInto;

/// `Accounts` namespace
Expand All @@ -31,6 +40,7 @@ impl<T: Transport> Namespace<T> for Accounts<T> {

impl<T: Transport> Accounts<T> {
/// Gets the parent `web3` namespace
#[cfg(feature = "signing")]
enolan marked this conversation as resolved.
Show resolved Hide resolved
fn web3(&self) -> Web3<T> {
Web3::new(self.transport.clone())
}
Expand All @@ -41,6 +51,7 @@ impl<T: Transport> Accounts<T> {
/// parameters required for signing `nonce`, `gas_price` and `chain_id`. Note
/// that if all transaction parameters were provided, this future will resolve
/// immediately.
#[cfg(feature = "signing")]
pub async fn sign_transaction<K: signing::Key>(
&self,
tx: TransactionParameters,
Expand Down Expand Up @@ -100,6 +111,7 @@ impl<T: Transport> Accounts<T> {
/// notation, that is the recovery value `v` is either `27` or `28` (as
/// opposed to the standard notation where `v` is either `0` or `1`). This
/// is important to consider when using this signature with other crates.
#[cfg(feature = "signing")]
pub fn sign<S>(&self, message: S, key: impl signing::Key) -> SignedData
where
S: AsRef<[u8]>,
Expand Down Expand Up @@ -140,6 +152,7 @@ impl<T: Transport> Accounts<T> {
///
/// Recovery signature data uses 'Electrum' notation, this means the `v`
/// value is expected to be either `27` or `28`.
#[cfg(feature = "signing")]
pub fn recover<R>(&self, recovery: R) -> error::Result<Address>
where
R: Into<Recovery>,
Expand All @@ -158,6 +171,7 @@ impl<T: Transport> Accounts<T> {
}

/// A transaction used for RLP encoding, hashing and signing.
#[cfg(feature = "signing")]
struct Transaction {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Transaction is only used in case of signing, why not move it to the mod signing along with all the use statements? The goal is to minimize the number of #[cfg(feature = "signing")] :)

to: Option<Address>,
nonce: U256,
Expand All @@ -167,6 +181,7 @@ struct Transaction {
data: Vec<u8>,
}

#[cfg(feature = "signing")]
impl Transaction {
/// RLP encode an unsigned transaction for the specified chain ID.
fn rlp_append_unsigned(&self, rlp: &mut RlpStream, chain_id: u64) {
Expand Down Expand Up @@ -205,6 +220,7 @@ impl Transaction {
}

/// Sign and return a raw signed transaction.
#[cfg(feature = "signing")]
fn sign(self, sign: impl signing::Key, chain_id: u64) -> SignedTransaction {
let mut rlp = RlpStream::new();
self.rlp_append_unsigned(&mut rlp, chain_id);
Expand Down
7 changes: 7 additions & 0 deletions src/api/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ impl<T: Transport> Eth<T> {
CallFuture::new(self.transport.execute("eth_chainId", vec![]))
}

/// Get available user accounts. This method is only available in the browser. With MetaMask,
/// this will cause the popup that prompts the user to allow or deny access to their accounts
/// to your app.
pub fn request_accounts(&self) -> CallFuture<Vec<Address>, T::Out> {
CallFuture::new(self.transport.execute("eth_requestAccounts", vec![]))
}

/// Get storage entry
pub fn storage(&self, address: Address, idx: U256, block: Option<BlockNumber>) -> CallFuture<H256, T::Out> {
let address = helpers::serialize(&address);
Expand Down
12 changes: 9 additions & 3 deletions src/contract/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
//! Ethereum Contract Interface

use crate::api::{Accounts, Eth, Namespace};
#[cfg(feature = "signing")]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here an in other places, please group signing-related functionality in a single mod block and only feature gate that block.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please group all signing related things into a submodule here as well? Basically I'd like a single #[cfg(feature = "signing")] stament instead of sprinkling it all around.

use crate::api::Accounts;
use crate::api::{Eth, Namespace};
use crate::confirm;
use crate::contract::tokens::{Detokenize, Tokenize};
#[cfg(feature = "signing")]
use crate::signing;
#[cfg(feature = "signing")]
use crate::types::TransactionParameters;
use crate::types::{
Address, BlockId, Bytes, CallRequest, FilterBuilder, TransactionCondition, TransactionParameters,
TransactionReceipt, TransactionRequest, H256, U256,
Address, BlockId, Bytes, CallRequest, FilterBuilder, TransactionCondition, TransactionReceipt, TransactionRequest,
H256, U256,
};
use crate::Transport;
use std::{collections::HashMap, hash::Hash, time};
Expand Down Expand Up @@ -142,6 +147,7 @@ impl<T: Transport> Contract<T> {
}

/// Execute a signed contract function and wait for confirmations
#[cfg(feature = "signing")]
pub async fn signed_call_with_confirmations(
&self,
func: &str,
Expand Down
17 changes: 16 additions & 1 deletion src/signing.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
//! Signing capabilities and utilities.

use crate::types::{Address, H256};
#[cfg(feature = "signing")]
use crate::types::Address;
use crate::types::H256;
#[cfg(feature = "signing")]
use secp256k1::recovery::{RecoverableSignature, RecoveryId};
#[cfg(feature = "signing")]
use secp256k1::{Message, PublicKey, Secp256k1};
#[cfg(feature = "signing")]
use std::ops::Deref;

#[cfg(feature = "signing")]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here as well. Perhaps it's better to simply disable the entire signing.rs file?

pub(crate) use secp256k1::SecretKey;

/// Error during signing.
Expand Down Expand Up @@ -41,6 +47,7 @@ impl std::error::Error for RecoveryError {}
///
/// If it's enough to pass a reference to `SecretKey` (lifetimes) than you can use `SecretKeyRef`
/// wrapper.
#[cfg(feature = "signing")]
pub trait Key {
/// Sign given message and include chain-id replay protection.
///
Expand All @@ -57,23 +64,27 @@ pub trait Key {
///
/// A wrapper around `secp256k1::SecretKey` reference, which enables it to be used in methods expecting
/// `Key` capabilities.
#[cfg(feature = "signing")]
pub struct SecretKeyRef<'a> {
key: &'a SecretKey,
}

#[cfg(feature = "signing")]
impl<'a> SecretKeyRef<'a> {
/// A simple wrapper around a reference to `SecretKey` which allows it to be usable for signing.
pub fn new(key: &'a SecretKey) -> Self {
Self { key }
}
}

#[cfg(feature = "signing")]
impl<'a> From<&'a SecretKey> for SecretKeyRef<'a> {
fn from(key: &'a SecretKey) -> Self {
Self::new(key)
}
}

#[cfg(feature = "signing")]
impl<'a> Deref for SecretKeyRef<'a> {
type Target = SecretKey;

Expand All @@ -82,6 +93,7 @@ impl<'a> Deref for SecretKeyRef<'a> {
}
}

#[cfg(feature = "signing")]
impl<T: Deref<Target = SecretKey>> Key for T {
fn sign(&self, message: &[u8], chain_id: Option<u64>) -> Result<Signature, SigningError> {
let message = Message::from_slice(&message).map_err(|_| SigningError::InvalidMessage)?;
Expand Down Expand Up @@ -121,6 +133,7 @@ pub struct Signature {
/// Recover a sender, given message and the signature.
///
/// Signature and `recovery_id` can be obtained from `types::Recovery` type.
#[cfg(feature = "signing")]
pub fn recover(message: &[u8], signature: &[u8], recovery_id: i32) -> Result<Address, RecoveryError> {
let message = Message::from_slice(message).map_err(|_| RecoveryError::InvalidMessage)?;
let recovery_id = RecoveryId::from_i32(recovery_id).map_err(|_| RecoveryError::InvalidSignature)?;
Expand All @@ -140,6 +153,7 @@ pub fn recover(message: &[u8], signature: &[u8], recovery_id: i32) -> Result<Add
/// crate is 65 bytes long, that is because it is prefixed by `0x04` to
/// indicate an uncompressed public key; this first byte is ignored when
/// computing the hash.
#[cfg(feature = "signing")]
pub(crate) fn public_key_address(public_key: &PublicKey) -> Address {
let public_key = public_key.serialize_uncompressed();

Expand All @@ -150,6 +164,7 @@ pub(crate) fn public_key_address(public_key: &PublicKey) -> Address {
}

/// Gets the public address of a private key.
#[cfg(feature = "signing")]
pub(crate) fn secret_key_address(key: &SecretKey) -> Address {
let secp = Secp256k1::signing_only();
let public_key = PublicKey::from_secret_key(&secp, key);
Expand Down
Loading