feat(signers): implement Serde and make Wallet API smaller (#20)
* feat(signers): implement Serde and make API smaller * fix: add abigen as a dev-dependency feature
This commit is contained in:
parent
570b45eb10
commit
1a47e933ae
|
@ -377,6 +377,7 @@ dependencies = [
|
|||
"ethers-core",
|
||||
"ethers-providers",
|
||||
"futures-util",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
@ -1163,18 +1164,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.111"
|
||||
version = "1.0.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d"
|
||||
checksum = "736aac72d1eafe8e5962d1d1c3d99b0df526015ba40915cb3c49d042e92ec243"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.111"
|
||||
version = "1.0.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250"
|
||||
checksum = "bf0343ce212ac0d3d6afd9391ac8e9c9efe06b533c8d33f660f6390cc4093f57"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -14,8 +14,8 @@ ethers-core = { version = "0.1.0", path = "../ethers-core" }
|
|||
|
||||
serde = { version = "1.0.110", default-features = false }
|
||||
rustc-hex = { version = "2.1.0", default-features = false }
|
||||
thiserror = { version = "1.0.19", default-features = false }
|
||||
once_cell = { version = "1.4.0", default-features = false }
|
||||
thiserror = { version = "1.0.15", default-features = false }
|
||||
once_cell = { version = "1.3.1", default-features = false }
|
||||
futures = "0.3.5"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -23,5 +23,4 @@ tokio = { version = "0.2.21", default-features = false, features = ["macros"] }
|
|||
serde_json = "1.0.55"
|
||||
|
||||
[features]
|
||||
default = ["abigen"]
|
||||
abigen = ["ethers-contract-abigen", "ethers-contract-derive"]
|
||||
|
|
|
@ -17,5 +17,5 @@ quote = "1.0"
|
|||
syn = "1.0.12"
|
||||
url = "2.1"
|
||||
serde_json = "1.0.53"
|
||||
once_cell = "1.4.0"
|
||||
once_cell = "1.3.1"
|
||||
rustc-hex = { version = "2.1.0", default-features = false }
|
||||
|
|
|
@ -6,6 +6,8 @@ use quote::quote;
|
|||
|
||||
pub(crate) fn imports() -> TokenStream {
|
||||
quote! {
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
// TODO: Can we make this context aware so that it imports either ethers_contract
|
||||
// or ethers::contract?
|
||||
use ethers::{
|
||||
|
|
|
@ -40,6 +40,7 @@ pub enum ContractError {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[must_use = "contract calls do nothing unless you `send` or `call` them"]
|
||||
/// Helper for managing a transaction before submitting it to a node
|
||||
pub struct ContractCall<'a, P, S, D> {
|
||||
/// The raw transaction object
|
||||
|
|
|
@ -11,6 +11,7 @@ use futures::stream::{Stream, StreamExt};
|
|||
use std::{collections::HashMap, marker::PhantomData};
|
||||
|
||||
/// Helper for managing the event filter before querying or streaming its logs
|
||||
#[must_use = "event filters do nothing unless you `query` or `stream` them"]
|
||||
pub struct Event<'a: 'b, 'b, P, D> {
|
||||
/// The event filter's state
|
||||
pub filter: Filter,
|
||||
|
|
|
@ -20,7 +20,7 @@ tiny-keccak = { version = "2.0.2", default-features = false }
|
|||
serde = { version = "1.0.110", default-features = false, features = ["derive"] }
|
||||
serde_json = { version = "1.0.53", default-features = false, features = ["alloc"] }
|
||||
rustc-hex = { version = "2.1.0", default-features = false }
|
||||
thiserror = { version = "1.0.19", default-features = false }
|
||||
thiserror = { version = "1.0.15", default-features = false }
|
||||
arrayvec = { version = "0.5.1", default-features = false, optional = true }
|
||||
glob = "0.3.0"
|
||||
|
||||
|
|
|
@ -6,16 +6,48 @@ use crate::{
|
|||
use rand::Rng;
|
||||
use rustc_hex::FromHex;
|
||||
use secp256k1::{
|
||||
self as Secp256k1, Error as SecpError, Message, PublicKey as PubKey, RecoveryId, SecretKey,
|
||||
self as Secp256k1,
|
||||
util::{COMPRESSED_PUBLIC_KEY_SIZE, SECRET_KEY_SIZE},
|
||||
Error as SecpError, Message, PublicKey as PubKey, RecoveryId, SecretKey,
|
||||
};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use serde::{
|
||||
de::Error as DeserializeError,
|
||||
de::{SeqAccess, Visitor},
|
||||
ser::SerializeTuple,
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use std::{fmt, ops::Deref, str::FromStr};
|
||||
use thiserror::Error;
|
||||
|
||||
/// A private key on Secp256k1
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PrivateKey(pub(super) SecretKey);
|
||||
|
||||
impl Serialize for PrivateKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_tuple(SECRET_KEY_SIZE)?;
|
||||
for e in &self.0.serialize() {
|
||||
seq.serialize_element(e)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PrivateKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let bytes = <[u8; SECRET_KEY_SIZE]>::deserialize(deserializer)?;
|
||||
Ok(PrivateKey(
|
||||
SecretKey::parse(&bytes).map_err(DeserializeError::custom)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PrivateKey {
|
||||
type Err = SecpError;
|
||||
|
||||
|
@ -133,7 +165,6 @@ impl PrivateKey {
|
|||
let r = H256::from_slice(&signature.r.b32());
|
||||
let s = H256::from_slice(&signature.s.b32());
|
||||
|
||||
// TODO: Check what happens when using the 1337 Geth chain id
|
||||
Signature { v: v as u8, r, s }
|
||||
}
|
||||
}
|
||||
|
@ -214,14 +245,80 @@ impl From<PrivateKey> for Address {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for PublicKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_tuple(COMPRESSED_PUBLIC_KEY_SIZE)?;
|
||||
for e in self.0.serialize_compressed().iter() {
|
||||
seq.serialize_element(e)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PublicKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ArrayVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ArrayVisitor {
|
||||
type Value = PublicKey;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a valid proof")
|
||||
}
|
||||
|
||||
fn visit_seq<S>(self, mut seq: S) -> Result<PublicKey, S::Error>
|
||||
where
|
||||
S: SeqAccess<'de>,
|
||||
{
|
||||
let mut bytes = [0u8; COMPRESSED_PUBLIC_KEY_SIZE];
|
||||
for b in &mut bytes[..] {
|
||||
*b = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| DeserializeError::custom("could not read bytes"))?;
|
||||
}
|
||||
|
||||
Ok(PublicKey(
|
||||
PubKey::parse_compressed(&bytes).map_err(DeserializeError::custom)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_tuple(COMPRESSED_PUBLIC_KEY_SIZE, ArrayVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::Bytes;
|
||||
use rustc_hex::FromHex;
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
for _ in 0..10 {
|
||||
let key = PrivateKey::new(&mut rand::thread_rng());
|
||||
let serialized = bincode::serialize(&key).unwrap();
|
||||
assert_eq!(serialized, &key.0.serialize());
|
||||
let de: PrivateKey = bincode::deserialize(&serialized).unwrap();
|
||||
assert_eq!(key, de);
|
||||
|
||||
let public = PublicKey::from(&key);
|
||||
let serialized = bincode::serialize(&public).unwrap();
|
||||
assert_eq!(&serialized[..], public.0.serialize_compressed().as_ref());
|
||||
let de: PublicKey = bincode::deserialize(&serialized).unwrap();
|
||||
assert_eq!(public, de);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signs_tx() {
|
||||
use crate::types::{Address, Bytes};
|
||||
|
||||
// retrieved test vector from:
|
||||
// https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
|
||||
let tx = TransactionRequest {
|
||||
|
|
|
@ -11,7 +11,7 @@ async-trait = { version = "0.1.31", default-features = false }
|
|||
reqwest = { version = "0.10.4", default-features = false, features = ["json", "rustls-tls"] }
|
||||
serde = { version = "1.0.110", default-features = false, features = ["derive"] }
|
||||
serde_json = { version = "1.0.53", default-features = false }
|
||||
thiserror = { version = "1.0.19", default-features = false }
|
||||
thiserror = { version = "1.0.15", default-features = false }
|
||||
url = { version = "2.1.1", default-features = false }
|
||||
|
||||
# required for implementing stream on the filters
|
||||
|
|
|
@ -24,7 +24,7 @@ pub use provider::{Provider, ProviderError};
|
|||
#[async_trait]
|
||||
/// Trait which must be implemented by data transports to be used with the Ethereum
|
||||
/// JSON-RPC provider.
|
||||
pub trait JsonRpcClient: Debug + Clone {
|
||||
pub trait JsonRpcClient: Debug + Clone + Send + Sync {
|
||||
/// A JSON-RPC Error
|
||||
type Error: Error + Into<ProviderError>;
|
||||
|
||||
|
|
|
@ -7,8 +7,9 @@ edition = "2018"
|
|||
[dependencies]
|
||||
ethers-core = { version = "0.1.0", path = "../ethers-core" }
|
||||
ethers-providers = { version = "0.1.0", path = "../ethers-providers" }
|
||||
thiserror = { version = "1.0.19", default-features = false }
|
||||
thiserror = { version = "1.0.15", default-features = false }
|
||||
futures-util = { version = "0.3.5", default-features = false }
|
||||
serde = "1.0.112"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.21", features = ["macros"] }
|
||||
|
|
|
@ -12,7 +12,7 @@ use std::error::Error;
|
|||
///
|
||||
/// Implement this trait to support different signing modes, e.g. Ledger, hosted etc.
|
||||
// TODO: We might need a `SignerAsync` trait for HSM use cases?
|
||||
pub trait Signer: Clone {
|
||||
pub trait Signer: Clone + Send + Sync {
|
||||
type Error: Error + Into<ClientError>;
|
||||
/// Signs the hash of the provided message after prefixing it
|
||||
fn sign_message<S: AsRef<[u8]>>(&self, message: S) -> Signature;
|
||||
|
|
|
@ -8,6 +8,7 @@ use ethers_core::{
|
|||
types::{Address, PrivateKey, PublicKey, Signature, Transaction, TransactionRequest, TxError},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// An Ethereum private-public key pair which can be used for signing messages. It can be connected to a provider
|
||||
|
@ -29,7 +30,7 @@ use std::str::FromStr;
|
|||
///
|
||||
/// // Optionally, the wallet's chain id can be set, in order to use EIP-155
|
||||
/// // replay protection with different chains
|
||||
/// let wallet = wallet.chain_id(1337u64);
|
||||
/// let wallet = wallet.set_chain_id(1337u64);
|
||||
///
|
||||
/// // The wallet can be used to sign messages
|
||||
/// let message = b"hello";
|
||||
|
@ -62,16 +63,16 @@ use std::str::FromStr;
|
|||
/// [`connect`]: ./struct.Wallet.html#method.connect
|
||||
/// [`Signature`]: ../ethers_core/types/struct.Signature.html
|
||||
/// [`hash_message`]: ../ethers_core/utils/fn.hash_message.html
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Wallet {
|
||||
/// The Wallet's private Key
|
||||
pub private_key: PrivateKey,
|
||||
private_key: PrivateKey,
|
||||
/// The Wallet's public Key
|
||||
pub public_key: PublicKey,
|
||||
public_key: PublicKey,
|
||||
/// The wallet's address
|
||||
pub address: Address,
|
||||
address: Address,
|
||||
/// The wallet's chain id (for EIP-155), signs w/o replay protection if left unset
|
||||
pub chain_id: u64,
|
||||
chain_id: u64,
|
||||
}
|
||||
|
||||
impl Signer for Wallet {
|
||||
|
@ -123,11 +124,26 @@ impl Wallet {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets the wallet's chain_id
|
||||
pub fn chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
|
||||
/// Sets the wallet's chain_id, used in conjunction with EIP-155 signing
|
||||
pub fn set_chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
|
||||
self.chain_id = chain_id.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Gets the wallet's public key
|
||||
pub fn public_key(&self) -> &PublicKey {
|
||||
&self.public_key
|
||||
}
|
||||
|
||||
/// Gets the wallet's private key
|
||||
pub fn private_key(&self) -> &PrivateKey {
|
||||
&self.private_key
|
||||
}
|
||||
|
||||
/// Gets the wallet's chain id
|
||||
pub fn chain_id(&self) -> u64 {
|
||||
self.chain_id
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PrivateKey> for Wallet {
|
||||
|
|
|
@ -19,6 +19,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||
features = ["full"]
|
||||
|
||||
[features]
|
||||
abigen = ["contract", "ethers-contract/abigen"]
|
||||
default = ["full"]
|
||||
full = [
|
||||
"contract",
|
||||
|
@ -27,20 +28,22 @@ full = [
|
|||
"core",
|
||||
]
|
||||
|
||||
core = ["ethers-core"]
|
||||
contract = ["ethers-contract"]
|
||||
providers = ["ethers-providers"]
|
||||
signers = ["ethers-signers"]
|
||||
core = ["ethers-core"]
|
||||
|
||||
[dependencies]
|
||||
ethers-contract = { version = "0.1.0", path = "../ethers-contract", features = ["abigen"], optional = true }
|
||||
ethers-contract = { version = "0.1.0", path = "../ethers-contract", optional = true }
|
||||
ethers-core = { version = "0.1.0", path = "../ethers-core", optional = true }
|
||||
ethers-providers = { version = "0.1.0", path = "../ethers-providers", optional = true }
|
||||
ethers-signers = { version = "0.1.0", path = "../ethers-signers", optional = true }
|
||||
ethers-core = { version = "0.1.0", path = "../ethers-core", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ethers-contract = { version = "0.1.0", path = "../ethers-contract", features = ["abigen"] }
|
||||
|
||||
anyhow = "1.0.31"
|
||||
tokio = { version = "0.2.21", features = ["macros"] }
|
||||
serde_json = "1.0.53"
|
||||
rand = "0.7"
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
serde_json = "1.0.53"
|
||||
tokio = { version = "0.2.21", features = ["macros"] }
|
||||
|
|
|
@ -11,7 +11,7 @@ fn main() {
|
|||
// recover the address that signed it
|
||||
let recovered = signature.recover(message).unwrap();
|
||||
|
||||
assert_eq!(recovered, wallet.address);
|
||||
assert_eq!(recovered, wallet.address());
|
||||
|
||||
println!("Verified signature produced by {:?}!", wallet.address);
|
||||
println!("Verified signature produced by {:?}!", wallet.address());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue