From c0e607da1ea7cba3583cf2a19f29975d1c961753 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 4 Jan 2023 16:35:44 -0500 Subject: [PATCH] feat(providers): support personal account apis (#2009) * feat(providers): add personal key mgmt rpcs * add personal_importRawKey and personal_unlockAccount * expose geth personal api * do not prefix with 0x * serialize privkey as string * add allow-insecure-unlock option to Geth --- ethers-core/src/utils/geth.rs | 15 +++++++++++- ethers-providers/src/lib.rs | 19 +++++++++++++++ ethers-providers/src/provider.rs | 40 ++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs index 83fdfa0d..4e8f72c5 100644 --- a/ethers-core/src/utils/geth.rs +++ b/ethers-core/src/utils/geth.rs @@ -16,7 +16,7 @@ const GETH_STARTUP_TIMEOUT_MILLIS: u64 = 10_000; const GETH_DIAL_LOOP_TIMEOUT: Duration = Duration::new(20, 0); /// The exposed APIs -const API: &str = "eth,net,web3,txpool,admin,miner"; +const API: &str = "eth,net,web3,txpool,admin,personal,miner"; /// The geth command const GETH: &str = "geth"; @@ -180,6 +180,7 @@ pub struct Geth { ipc_path: Option, data_dir: Option, chain_id: Option, + insecure_unlock: bool, genesis: Option, mode: GethMode, } @@ -259,6 +260,13 @@ impl Geth { self } + /// Allow geth to unlock accounts when rpc apis are open. + #[must_use] + pub fn insecure_unlock(mut self) -> Self { + self.insecure_unlock = true; + self + } + /// Disable discovery for the geth instance. /// /// This will put the geth instance into non-dev mode, discarding any previously set dev-mode @@ -328,6 +336,11 @@ impl Geth { cmd.arg("--ws.port").arg(port.to_string()); cmd.arg("--ws.api").arg(API); + // pass insecure unlock flag if set + if self.insecure_unlock { + cmd.arg("--allow-insecure-unlock"); + } + // Set the port for authenticated APIs cmd.arg("--authrpc.port").arg(authrpc_port.to_string()); diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs index 2abee833..98f59842 100644 --- a/ethers-providers/src/lib.rs +++ b/ethers-providers/src/lib.rs @@ -507,6 +507,25 @@ pub trait Middleware: Sync + Send + Debug { self.inner().mining().await.map_err(FromErr::from) } + // Personal namespace + + async fn import_raw_key( + &self, + private_key: Bytes, + passphrase: String, + ) -> Result { + self.inner().import_raw_key(private_key, passphrase).await.map_err(FromErr::from) + } + + async fn unlock_account + Send + Sync>( + &self, + account: T, + passphrase: String, + duration: Option, + ) -> Result { + self.inner().unlock_account(account, passphrase, duration).await.map_err(FromErr::from) + } + // Admin namespace async fn add_peer(&self, enode_url: String) -> Result { diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 446c3229..11083891 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -818,6 +818,46 @@ impl Middleware for Provider

{ self.request("eth_mining", ()).await } + // Personal namespace + // NOTE: This will eventually need to be enabled by users explicitly because the personal + // namespace is being deprecated: + // Issue: https://github.com/ethereum/go-ethereum/issues/25948 + // PR: https://github.com/ethereum/go-ethereum/pull/26390 + + /// Sends the given key to the node to be encrypted with the provided passphrase and stored. + /// + /// The key represents a secp256k1 private key and should be 32 bytes. + async fn import_raw_key( + &self, + private_key: Bytes, + passphrase: String, + ) -> Result { + // private key should not be prefixed with 0x - it is also up to the user to pass in a key + // of the correct length + + // the private key argument is supposed to be a string + let private_key_hex = hex::encode(private_key); + let private_key = utils::serialize(&private_key_hex); + let passphrase = utils::serialize(&passphrase); + self.request("personal_importRawKey", [private_key, passphrase]).await + } + + /// Prompts the node to decrypt the given account from its keystore. + /// + /// If the duration provided is `None`, then the account will be unlocked indefinitely. + /// Otherwise, the account will be unlocked for the provided number of seconds. + async fn unlock_account + Send + Sync>( + &self, + account: T, + passphrase: String, + duration: Option, + ) -> Result { + let account = utils::serialize(&account.into()); + let duration = utils::serialize(&duration.unwrap_or(0)); + let passphrase = utils::serialize(&passphrase); + self.request("personal_unlockAccount", [account, passphrase, duration]).await + } + // Admin namespace /// Requests adding the given peer, returning a boolean representing whether or not the peer