From 25eabbfd419a7202fa15eda0bd68d3ae11f31c80 Mon Sep 17 00:00:00 2001
From: nicolas <48695862+merklefruit@users.noreply.github.com>
Date: Wed, 23 Oct 2024 20:22:18 +0200
Subject: [PATCH] feat: working dirk delegation
---
bolt-cli/.env.keystore.example | 7 -
bolt-cli/.env.local.example | 6 -
bolt-cli/.gitignore | 5 +-
bolt-cli/README.md | 231 ++++--
bolt-cli/build.rs | 7 +-
bolt-cli/src/cli.rs | 15 +-
bolt-cli/src/delegation.rs | 32 +-
bolt-cli/src/main.rs | 7 +-
bolt-cli/src/pb/mod.rs | 10 +-
bolt-cli/src/pb/v1.rs | 876 +++++++++++++++++++++
bolt-cli/src/utils/dirk.rs | 76 +-
bolt-cli/test_data/dirk/dirk.template.json | 3 +
bolt-cli/test_data/dirk/wallet1-pf.txt | 1 +
13 files changed, 1176 insertions(+), 100 deletions(-)
delete mode 100644 bolt-cli/.env.keystore.example
delete mode 100644 bolt-cli/.env.local.example
create mode 100644 bolt-cli/test_data/dirk/wallet1-pf.txt
diff --git a/bolt-cli/.env.keystore.example b/bolt-cli/.env.keystore.example
deleted file mode 100644
index 1e1938fd8..000000000
--- a/bolt-cli/.env.keystore.example
+++ /dev/null
@@ -1,7 +0,0 @@
-# generate keystore
-
-PATH=keys
-PASSWORD=password
-DELEGATEE_PUBKEY=0x83eeddfac5e60f8fe607ee8713efb8877c295ad9f8ca075f4d8f6f2ae241a30dd57f78f6f3863a9fe0d5b5db9d550b93
-OUTPUT_FILE_PATH=delegations.json
-CHAIN=kurtosis
\ No newline at end of file
diff --git a/bolt-cli/.env.local.example b/bolt-cli/.env.local.example
deleted file mode 100644
index 1d5225855..000000000
--- a/bolt-cli/.env.local.example
+++ /dev/null
@@ -1,6 +0,0 @@
-# generate local
-
-SECRET_KEYS=0f40d627fa199720b79db91ce3f57034680f3ee6eef161abfb8275e676a7fd15,0f40d627fa199720b79db91ce3f57034680f3ee6eef161abfb8275e676a7fd15
-DELEGATEE_PUBKEY=0x83eeddfac5e60f8fe607ee8713efb8877c295ad9f8ca075f4d8f6f2ae241a30dd57f78f6f3863a9fe0d5b5db9d550b93
-OUTPUT_FILE_PATH=delegations.json
-CHAIN=kurtosis
\ No newline at end of file
diff --git a/bolt-cli/.gitignore b/bolt-cli/.gitignore
index 31190eaba..12571ac34 100644
--- a/bolt-cli/.gitignore
+++ b/bolt-cli/.gitignore
@@ -1,6 +1,7 @@
/target
+
.env
.env.*
+
delegations.json
-!.env.local.example
-!.env.keystore.example
\ No newline at end of file
+pubkeys.json
diff --git a/bolt-cli/README.md b/bolt-cli/README.md
index 64098fba1..c52d3e9c2 100644
--- a/bolt-cli/README.md
+++ b/bolt-cli/README.md
@@ -1,82 +1,209 @@
# Bolt CLI
-Components:
+The Bolt CLI is a collection of command-line tools for interacting with the Bolt protocol.
-- `bolt-delegations-cli`: A command-line tool for generating delegation messages signed with a BLS12-381 key.
+## Installation
-## Bolt-delegations-cli
+The Bolt CLI can be built with Cargo. If you don't have the Rust toolchain installed
+on your machine, you can follow the steps [here](https://www.rust-lang.org/tools/install).
-`bolt-delegations-cli` is an offline command-line tool for safely generating delegation messages
-signed with a BLS12-381 key for the [Constraints API](https://docs.boltprotocol.xyz/api/builder)
-in [Bolt](https://docs.boltprotocol.xyz/).
+Once you have Rust installed, you can build the CLI binary in the following way:
-The tool supports two key sources:
+```shell
+# clone the Bolt repository if you haven't already
+git clone git@github.com:chainbound/bolt.git
-- Local: A BLS private key provided directly from a file.
-- Keystore: A keystore file that contains an encrypted BLS private key.
+# navigate to the Bolt CLI package directory
+cd bolt-cli
-Features:
+# build and install the binary on your machine
+cargo install --path . --force
-- Offline usage: Safely generate delegation messages in an offline environment.
-- Flexible key source: Support for both direct local BLS private keys and Ethereum keystore files (ERC-2335 format).
-- BLS delegation signing: Sign delegation messages using a BLS secret key and output the signed delegation in JSON format.
+# test the installation
+bolt-cli --version
+```
+
+## Usage
+
+Available commands:
+
+- [`delegate`](#delegate) - Generate BLS delegation messages for the Constraints API.
+- [`pubkeys`](#pubkeys) - List available BLS public keys from various key sources.
+
+### `Delegate`
+
+The `delegate` command generates signed delegation messages for the Constraints API.
+To learn more about the Constraints API, please refer to the [Bolt documentation][bolt-docs].
+
+The command supports three key sources for generating the signed messages:
+
+- Local BLS secret keys (as hex-encoded strings) via `secret-keys`
+- Local EIP-2335 filesystem keystore directories via `local-keystore`
+- Remote Dirk keystore via `dirk` (requires TLS credentials)
+
+
+Usage
+
+```text
+❯ bolt-cli delegate --help
+
+Generate BLS delegation or revocation messages
+
+Usage: bolt-cli delegate [OPTIONS] --delegatee-pubkey
+
+Commands:
+secret-keys Use local secret keys to generate the signed messages
+local-keystore Use an EIP-2335 filesystem keystore directory to generate the signed messages
+dirk Use a remote DIRK keystore to generate the signed messages
+help Print this message or the help of the given subcommand(s)
+
+Options:
+ --delegatee-pubkey
+ The BLS public key to which the delegation message should be signed
+
+ [env: DELEGATEE_PUBKEY=]
+
+ --out
+ The output file for the delegations
+
+ [env: OUTPUT_FILE_PATH=]
+ [default: delegations.json]
+
+ --chain
+ The chain for which the delegation message is intended
+
+ [env: CHAIN=]
+ [default: mainnet]
+ [possible values: mainnet, holesky, helder, kurtosis]
+
+ --action
+ The action to perform. The tool can be used to generate delegation or revocation messages (default: delegate)
+
+ [env: ACTION=]
+ [default: delegate]
+
+ Possible values:
+ - delegate: Create a delegation message
+ - revoke: Create a revocation message
+
+-h, --help
+ Print help (see a summary with '-h')
+```
+
+
-### Usage
+
+Examples
+
+1. Generating a delegation using a local BLS secret key
+
+```text
+bolt-cli delegate \
+ --delegatee-pubkey 0x8d0edf4fe9c80cd640220ca7a68a48efcbc56a13536d6b274bf3719befaffa13688ebee9f37414b3dddc8c7e77233ce8 \
+ --chain holesky \
+ secret-keys --secret-keys 642e0d33fde8968a48b5f560c1b20143eb82036c1aa6c7f4adc4beed919a22e3
+```
+
+2. Generating a delegation using an ERC-2335 keystore directory
+
+```text
+bolt-cli delegate \
+ --delegatee-pubkey 0x8d0edf4fe9c80cd640220ca7a68a48efcbc56a13536d6b274bf3719befaffa13688ebee9f37414b3dddc8c7e77233ce8 \
+ --chain holesky \
+ local-keystore --path test_data/lighthouse/validators --password-path test_data/lighthouse/secrets
+```
+
+3. Generating a revocation using a remote DIRK keystore
+
+```text
+bolt-cli delegate \
+ --delegatee-pubkey 0x83eeddfac5e60f8fe607ee8713efb8877c295ad9f8ca075f4d8f6f2ae241a30dd57f78f6f3863a9fe0d5b5db9d550b93 \
+ dirk --url https://localhost:9091 \
+ --client-cert-path ./test_data/dirk/client1.crt \
+ --client-key-path ./test_data/dirk/client1.key \
+ --ca-cert-path ./test_data/dirk/security/ca.crt \
+ --wallet-path wallet1 --passphrases secret
+```
+
+
+
+### `Pubkeys`
+
+The `pubkeys` command lists available BLS public keys from different key sources:
+
+- Local BLS secret keys (as hex-encoded strings) via `secret-keys`
+- Local EIP-2335 filesystem keystore directories via `local-keystore`
+- Remote Dirk keystore via `dirk` (requires TLS credentials)
+
+
+Usage
```text
-A CLI tool to generate signed delegation messages for BLS keys
+❯ bolt-cli pubkeys --help
+
+Output a list of pubkeys in JSON format
-Usage: bolt-delegations-cli
+Usage: bolt-cli pubkeys [OPTIONS]
Commands:
- generate Generate delegation messages
- help Print this message or the help of the given subcommand(s)
+ secret-keys Use local secret keys to generate the signed messages
+ local-keystore Use an EIP-2335 filesystem keystore directory to generate the signed messages
+ dirk Use a remote DIRK keystore to generate the signed messages
+ help Print this message or the help of the given subcommand(s)
Options:
- -h, --help Print help
- -V, --version Print version
+ --out The output file for the pubkeys [env: OUTPUT_FILE_PATH=] [default: pubkeys.json]
+ -h, --help Print help
+```
+
+
+
+
+Examples
+
+1. Listing BLS public keys from a local secret key
+
+```text
+bolt-cli pubkeys secret-keys --secret-keys 642e0d33fde8968a48b5f560c1b20143eb82036c1aa6c7f4adc4beed919a22e3
```
-#### Example
+2. Listing BLS public keys from an ERC-2335 keystore directory
-1. Using a local BLS private key:
+```text
+bolt-cli pubkeys local-keystore \
+ --path test_data/lighthouse/validators \
+ --password-path test_data/lighthouse/secrets
+```
- ```text
- bolt-delegations-cli generate \
- --delegatee-pubkey 0x7890ab... \
- --out my_delegations.json \
- --chain kurtosis \
- local \
- --secret-keys 0xabc123...,0xdef456..
- ```
+3. Listing BLS public keys from a remote DIRK keystore
-2. Using an Ethereum keystore file:
+```text
+bolt-cli pubkeys dirk --url https://localhost:9091 \
+ --client-cert-path ./test_data/dirk/client1.crt \
+ --client-key-path ./test_data/dirk/client1.key \
+ --ca-cert-path ./test_data/dirk/security/ca.crt \
+ --wallet-path wallet1 --passphrases secret
+```
- ```text
- bolt-delegations-cli generate \
- --delegatee-pubkey 0x7890ab... \
- --out my_delegations.json \
- --chain kurtosis \
- keystore \
- --path /keys \
- --password myS3cr3tP@ssw0rd
- ```
+
-When using the `keystore` key source, the `--path` flag should point to the directory
-containing the encrypted keypair directories.
+---
-In case of validator-specific passwords (e.g. Lighthouse format) the `--password-path`
-flag must be used instead of `--password`, pointing to the directory containing the password files.
+## Security
-You can find a reference Lighthouse keystore [here](./test_data/lighthouse/).
+The Bolt CLI is designed to be used offline. It does not require any network connections
+unless you are using the remote `dirk` key source. In that case, the tool will connect to
+the Dirk server with the provided TLS credentials.
-#### Supported Chains
+The tool does not store any sensitive information beyond the duration of the execution.
+It is recommended to use the tool in a secure environment and to avoid storing any sensitive
+information in the shell history.
-The tool supports the following chains:
+If you have any security concerns or have found a security issue/bug, please contact Chainbound
+on our official [Discord][discord] or [Twitter][twitter] channels.
-- `mainnet`
-- `holesky`
-- `helder`
-- `kurtosis`
+
-Each chain has its specific fork version used in computing the signing root.
+[bolt-docs]: https://docs.boltprotocol.xyz/
+[discord]: https://discord.gg/G5BJjCD9ss
+[twitter]: https://twitter.com/chainbound_
diff --git a/bolt-cli/build.rs b/bolt-cli/build.rs
index 07c7d4466..9342cfe4c 100644
--- a/bolt-cli/build.rs
+++ b/bolt-cli/build.rs
@@ -10,7 +10,12 @@ fn main() -> io::Result<()> {
}
tonic_build::configure().build_client(true).out_dir(PB_OUT_DIR).compile_protos(
- &["proto/eth2-signer-api/v1/lister.proto", "proto/eth2-signer-api/v1/signer.proto"],
+ &[
+ "proto/eth2-signer-api/v1/lister.proto",
+ "proto/eth2-signer-api/v1/signer.proto",
+ "proto/eth2-signer-api/v1/accountmanager.proto",
+ "proto/eth2-signer-api/v1/walletmanager.proto",
+ ],
&["proto/eth2-signer-api/v1/", "proto/eth2-signer-api/"],
)
}
diff --git a/bolt-cli/src/cli.rs b/bolt-cli/src/cli.rs
index 1b152126f..1f387988c 100644
--- a/bolt-cli/src/cli.rs
+++ b/bolt-cli/src/cli.rs
@@ -3,7 +3,7 @@ use serde::Deserialize;
use crate::utils::keystore::DEFAULT_KEYSTORE_PASSWORD;
-/// A CLI tool to generate signed delegation messages for BLS keys.
+/// A CLI tool to interact with Bolt Protocol ✨
#[derive(Parser, Debug, Clone, Deserialize)]
#[command(author, version, about, long_about = None)]
pub struct Opts {
@@ -119,13 +119,18 @@ pub struct DirkOpts {
#[clap(long, env = "DIRK_URL")]
pub url: String,
+ /// The path of the wallets in the DIRK keystore.
+ #[clap(long, env = "DIRK_WALLET_PATH")]
+ pub wallet_path: String,
+
+ /// The passphrases to unlock the wallet in the DIRK keystore.
+ /// If multiple are provided, they are tried in order until one works.
+ #[clap(long, env = "DIRK_PASSPHRASES", value_delimiter = ',', hide_env_values = true)]
+ pub passphrases: Option>,
+
/// The TLS credentials for connecting to the DIRK keystore.
#[clap(flatten)]
pub tls_credentials: TlsCredentials,
-
- /// The paths to the accounts in the DIRK keystore.
- #[clap(long, env = "DIRK_ACCOUNTS", value_delimiter = ',', hide_env_values = true)]
- pub accounts: Vec,
}
/// TLS credentials for connecting to a remote server.
diff --git a/bolt-cli/src/delegation.rs b/bolt-cli/src/delegation.rs
index 46b3cdede..1d3f7fde5 100644
--- a/bolt-cli/src/delegation.rs
+++ b/bolt-cli/src/delegation.rs
@@ -1,8 +1,9 @@
+use alloy_primitives::B256;
use alloy_signer::k256::sha2::{Digest, Sha256};
use ethereum_consensus::crypto::{
PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, Signature as BlsSignature,
};
-use eyre::Result;
+use eyre::{bail, Result};
use lighthouse_eth2_keystore::Keystore;
use serde::Serialize;
use tracing::debug;
@@ -70,6 +71,7 @@ pub fn generate_from_keystore(
) -> Result> {
let keystores_paths = keystore_paths(keys_path)?;
let mut signed_messages = Vec::with_capacity(keystores_paths.len());
+ debug!("Found {} keys in the keystore", keystores_paths.len());
for path in keystores_paths {
let ks = Keystore::from_json_file(path).map_err(KeystoreError::Eth2Keystore)?;
@@ -105,35 +107,47 @@ pub fn generate_from_keystore(
pub async fn generate_from_dirk(
dirk: &mut Dirk,
delegatee_pubkey: BlsPublicKey,
- account_paths: Vec,
+ account_path: String,
+ passphrases: Option>,
chain: Chain,
action: Action,
) -> Result> {
- let mut signed_messages = Vec::new();
-
// first read the accounts from the remote keystore
- let accounts = dirk.list_accounts(account_paths).await?;
- debug!("Found {} remote accounts", accounts.len());
+ let accounts = dirk.list_accounts(account_path).await?;
+ debug!("Found {} remote accounts to sign with", accounts.len());
+
+ let mut signed_messages = Vec::with_capacity(accounts.len());
// specify the signing domain (needs to be included in the signing request)
- let domain = compute_domain_from_mask(chain.fork_version());
+ let domain = B256::from(compute_domain_from_mask(chain.fork_version()));
for account in accounts {
// for each available pubkey we control, sign a delegation message
let pubkey = BlsPublicKey::try_from(account.public_key.as_slice())?;
+ // Note: before signing, we must unlock the account
+ if let Some(ref passphrases) = passphrases {
+ for passphrase in passphrases {
+ if dirk.unlock_account(account.name.clone(), passphrase.clone()).await? {
+ break;
+ }
+ }
+ } else {
+ bail!("A passphrase is required in order to sign messages remotely with Dirk");
+ }
+
match action {
Action::Delegate => {
let message = DelegationMessage::new(pubkey.clone(), delegatee_pubkey.clone());
let signing_root = compute_commit_boost_signing_root(message.digest(), &chain)?;
- let signature = dirk.request_signature(pubkey, signing_root, domain.into()).await?;
+ let signature = dirk.request_signature(&account, signing_root, domain).await?;
let signed = SignedDelegation { message, signature };
signed_messages.push(SignedMessage::Delegation(signed));
}
Action::Revoke => {
let message = RevocationMessage::new(pubkey.clone(), delegatee_pubkey.clone());
let signing_root = compute_commit_boost_signing_root(message.digest(), &chain)?;
- let signature = dirk.request_signature(pubkey, signing_root, domain.into()).await?;
+ let signature = dirk.request_signature(&account, signing_root, domain).await?;
let signed = SignedRevocation { message, signature };
signed_messages.push(SignedMessage::Revocation(signed));
}
diff --git a/bolt-cli/src/main.rs b/bolt-cli/src/main.rs
index 367eaf0a3..5dc574376 100644
--- a/bolt-cli/src/main.rs
+++ b/bolt-cli/src/main.rs
@@ -60,10 +60,12 @@ async fn main() -> Result<()> {
KeySource::Dirk { opts } => {
let mut dirk = Dirk::connect(opts.url, opts.tls_credentials).await?;
let delegatee_pubkey = parse_bls_public_key(&delegatee_pubkey)?;
+
let signed_messages = delegation::generate_from_dirk(
&mut dirk,
delegatee_pubkey,
- opts.accounts,
+ opts.wallet_path,
+ opts.passphrases,
chain,
action,
)
@@ -90,8 +92,9 @@ async fn main() -> Result<()> {
println!("Pubkeys generated and saved to {}", out);
}
KeySource::Dirk { opts } => {
+ // Note: we don't need to unlock wallets to list pubkeys
let mut dirk = Dirk::connect(opts.url, opts.tls_credentials).await?;
- let accounts = dirk.list_accounts(opts.accounts).await?;
+ let accounts = dirk.list_accounts(opts.wallet_path).await?;
let pubkeys = pubkeys::list_from_dirk_accounts(&accounts)?;
write_to_file(&out, &pubkeys)?;
diff --git a/bolt-cli/src/pb/mod.rs b/bolt-cli/src/pb/mod.rs
index 435dccf78..5a22114fc 100644
--- a/bolt-cli/src/pb/mod.rs
+++ b/bolt-cli/src/pb/mod.rs
@@ -5,8 +5,12 @@ pub mod eth2_signer_api {
#[allow(unused_imports)]
pub use super::v1::{
- lister_client::ListerClient, sign_request::Id as SignRequestId,
- signer_client::SignerClient, Account, DistributedAccount, ListAccountsRequest,
- ListAccountsResponse, ResponseState, SignRequest, SignResponse,
+ account_manager_client::AccountManagerClient, lister_client::ListerClient,
+ sign_request::Id as SignRequestId, signer_client::SignerClient,
+ wallet_manager_client::WalletManagerClient, Account, DistributedAccount,
+ ListAccountsRequest, ListAccountsResponse, LockAccountRequest, LockAccountResponse,
+ LockWalletRequest, LockWalletResponse, MultisignRequest, MultisignResponse, ResponseState,
+ SignRequest, SignResponse, UnlockAccountRequest, UnlockAccountResponse,
+ UnlockWalletRequest, UnlockWalletResponse,
};
}
diff --git a/bolt-cli/src/pb/v1.rs b/bolt-cli/src/pb/v1.rs
index a4e1e7a81..dbef7a820 100644
--- a/bolt-cli/src/pb/v1.rs
+++ b/bolt-cli/src/pb/v1.rs
@@ -1066,3 +1066,879 @@ pub mod signer_server {
const NAME: &'static str = SERVICE_NAME;
}
}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct UnlockAccountRequest {
+ #[prost(string, tag = "1")]
+ pub account: ::prost::alloc::string::String,
+ #[prost(bytes = "vec", tag = "2")]
+ pub passphrase: ::prost::alloc::vec::Vec,
+}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct LockAccountRequest {
+ #[prost(string, tag = "1")]
+ pub account: ::prost::alloc::string::String,
+}
+#[derive(Clone, Copy, PartialEq, ::prost::Message)]
+pub struct UnlockAccountResponse {
+ #[prost(enumeration = "ResponseState", tag = "1")]
+ pub state: i32,
+}
+#[derive(Clone, Copy, PartialEq, ::prost::Message)]
+pub struct LockAccountResponse {
+ #[prost(enumeration = "ResponseState", tag = "1")]
+ pub state: i32,
+}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct GenerateRequest {
+ #[prost(string, tag = "1")]
+ pub account: ::prost::alloc::string::String,
+ #[prost(bytes = "vec", tag = "2")]
+ pub passphrase: ::prost::alloc::vec::Vec,
+ #[prost(uint32, tag = "3")]
+ pub participants: u32,
+ #[prost(uint32, tag = "4")]
+ pub signing_threshold: u32,
+}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct GenerateResponse {
+ #[prost(enumeration = "ResponseState", tag = "1")]
+ pub state: i32,
+ #[prost(string, tag = "2")]
+ pub message: ::prost::alloc::string::String,
+ #[prost(bytes = "vec", tag = "3")]
+ pub public_key: ::prost::alloc::vec::Vec,
+ #[prost(message, repeated, tag = "4")]
+ pub participants: ::prost::alloc::vec::Vec,
+}
+/// Generated client implementations.
+pub mod account_manager_client {
+ #![allow(
+ unused_variables,
+ dead_code,
+ missing_docs,
+ clippy::wildcard_imports,
+ clippy::let_unit_value,
+ )]
+ use tonic::codegen::*;
+ use tonic::codegen::http::Uri;
+ #[derive(Debug, Clone)]
+ pub struct AccountManagerClient {
+ inner: tonic::client::Grpc,
+ }
+ impl AccountManagerClient {
+ /// Attempt to create a new client by connecting to a given endpoint.
+ pub async fn connect(dst: D) -> Result
+ where
+ D: TryInto,
+ D::Error: Into,
+ {
+ let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
+ Ok(Self::new(conn))
+ }
+ }
+ impl AccountManagerClient
+ where
+ T: tonic::client::GrpcService,
+ T::Error: Into,
+ T::ResponseBody: Body + std::marker::Send + 'static,
+ ::Error: Into + std::marker::Send,
+ {
+ pub fn new(inner: T) -> Self {
+ let inner = tonic::client::Grpc::new(inner);
+ Self { inner }
+ }
+ pub fn with_origin(inner: T, origin: Uri) -> Self {
+ let inner = tonic::client::Grpc::with_origin(inner, origin);
+ Self { inner }
+ }
+ pub fn with_interceptor(
+ inner: T,
+ interceptor: F,
+ ) -> AccountManagerClient>
+ where
+ F: tonic::service::Interceptor,
+ T::ResponseBody: Default,
+ T: tonic::codegen::Service<
+ http::Request,
+ Response = http::Response<
+ >::ResponseBody,
+ >,
+ >,
+ ,
+ >>::Error: Into + std::marker::Send + std::marker::Sync,
+ {
+ AccountManagerClient::new(InterceptedService::new(inner, interceptor))
+ }
+ /// Compress requests with the given encoding.
+ ///
+ /// This requires the server to support it otherwise it might respond with an
+ /// error.
+ #[must_use]
+ pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
+ self.inner = self.inner.send_compressed(encoding);
+ self
+ }
+ /// Enable decompressing responses.
+ #[must_use]
+ pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
+ self.inner = self.inner.accept_compressed(encoding);
+ self
+ }
+ /// Limits the maximum size of a decoded message.
+ ///
+ /// Default: `4MB`
+ #[must_use]
+ pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
+ self.inner = self.inner.max_decoding_message_size(limit);
+ self
+ }
+ /// Limits the maximum size of an encoded message.
+ ///
+ /// Default: `usize::MAX`
+ #[must_use]
+ pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
+ self.inner = self.inner.max_encoding_message_size(limit);
+ self
+ }
+ pub async fn unlock(
+ &mut self,
+ request: impl tonic::IntoRequest,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ > {
+ self.inner
+ .ready()
+ .await
+ .map_err(|e| {
+ tonic::Status::unknown(
+ format!("Service was not ready: {}", e.into()),
+ )
+ })?;
+ let codec = tonic::codec::ProstCodec::default();
+ let path = http::uri::PathAndQuery::from_static("/v1.AccountManager/Unlock");
+ let mut req = request.into_request();
+ req.extensions_mut().insert(GrpcMethod::new("v1.AccountManager", "Unlock"));
+ self.inner.unary(req, path, codec).await
+ }
+ pub async fn lock(
+ &mut self,
+ request: impl tonic::IntoRequest,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ > {
+ self.inner
+ .ready()
+ .await
+ .map_err(|e| {
+ tonic::Status::unknown(
+ format!("Service was not ready: {}", e.into()),
+ )
+ })?;
+ let codec = tonic::codec::ProstCodec::default();
+ let path = http::uri::PathAndQuery::from_static("/v1.AccountManager/Lock");
+ let mut req = request.into_request();
+ req.extensions_mut().insert(GrpcMethod::new("v1.AccountManager", "Lock"));
+ self.inner.unary(req, path, codec).await
+ }
+ pub async fn generate(
+ &mut self,
+ request: impl tonic::IntoRequest,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ > {
+ self.inner
+ .ready()
+ .await
+ .map_err(|e| {
+ tonic::Status::unknown(
+ format!("Service was not ready: {}", e.into()),
+ )
+ })?;
+ let codec = tonic::codec::ProstCodec::default();
+ let path = http::uri::PathAndQuery::from_static(
+ "/v1.AccountManager/Generate",
+ );
+ let mut req = request.into_request();
+ req.extensions_mut()
+ .insert(GrpcMethod::new("v1.AccountManager", "Generate"));
+ self.inner.unary(req, path, codec).await
+ }
+ }
+}
+/// Generated server implementations.
+pub mod account_manager_server {
+ #![allow(
+ unused_variables,
+ dead_code,
+ missing_docs,
+ clippy::wildcard_imports,
+ clippy::let_unit_value,
+ )]
+ use tonic::codegen::*;
+ /// Generated trait containing gRPC methods that should be implemented for use with AccountManagerServer.
+ #[async_trait]
+ pub trait AccountManager: std::marker::Send + std::marker::Sync + 'static {
+ async fn unlock(
+ &self,
+ request: tonic::Request,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ >;
+ async fn lock(
+ &self,
+ request: tonic::Request,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ >;
+ async fn generate(
+ &self,
+ request: tonic::Request,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ >;
+ }
+ #[derive(Debug)]
+ pub struct AccountManagerServer {
+ inner: Arc,
+ accept_compression_encodings: EnabledCompressionEncodings,
+ send_compression_encodings: EnabledCompressionEncodings,
+ max_decoding_message_size: Option,
+ max_encoding_message_size: Option,
+ }
+ impl AccountManagerServer {
+ pub fn new(inner: T) -> Self {
+ Self::from_arc(Arc::new(inner))
+ }
+ pub fn from_arc(inner: Arc) -> Self {
+ Self {
+ inner,
+ accept_compression_encodings: Default::default(),
+ send_compression_encodings: Default::default(),
+ max_decoding_message_size: None,
+ max_encoding_message_size: None,
+ }
+ }
+ pub fn with_interceptor(
+ inner: T,
+ interceptor: F,
+ ) -> InterceptedService
+ where
+ F: tonic::service::Interceptor,
+ {
+ InterceptedService::new(Self::new(inner), interceptor)
+ }
+ /// Enable decompressing requests with the given encoding.
+ #[must_use]
+ pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
+ self.accept_compression_encodings.enable(encoding);
+ self
+ }
+ /// Compress responses with the given encoding, if the client supports it.
+ #[must_use]
+ pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
+ self.send_compression_encodings.enable(encoding);
+ self
+ }
+ /// Limits the maximum size of a decoded message.
+ ///
+ /// Default: `4MB`
+ #[must_use]
+ pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
+ self.max_decoding_message_size = Some(limit);
+ self
+ }
+ /// Limits the maximum size of an encoded message.
+ ///
+ /// Default: `usize::MAX`
+ #[must_use]
+ pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
+ self.max_encoding_message_size = Some(limit);
+ self
+ }
+ }
+ impl tonic::codegen::Service> for AccountManagerServer
+ where
+ T: AccountManager,
+ B: Body + std::marker::Send + 'static,
+ B::Error: Into + std::marker::Send + 'static,
+ {
+ type Response = http::Response;
+ type Error = std::convert::Infallible;
+ type Future = BoxFuture;
+ fn poll_ready(
+ &mut self,
+ _cx: &mut Context<'_>,
+ ) -> Poll> {
+ Poll::Ready(Ok(()))
+ }
+ fn call(&mut self, req: http::Request) -> Self::Future {
+ match req.uri().path() {
+ "/v1.AccountManager/Unlock" => {
+ #[allow(non_camel_case_types)]
+ struct UnlockSvc(pub Arc);
+ impl<
+ T: AccountManager,
+ > tonic::server::UnaryService
+ for UnlockSvc {
+ type Response = super::UnlockAccountResponse;
+ type Future = BoxFuture<
+ tonic::Response,
+ tonic::Status,
+ >;
+ fn call(
+ &mut self,
+ request: tonic::Request,
+ ) -> Self::Future {
+ let inner = Arc::clone(&self.0);
+ let fut = async move {
+ ::unlock(&inner, request).await
+ };
+ Box::pin(fut)
+ }
+ }
+ let accept_compression_encodings = self.accept_compression_encodings;
+ let send_compression_encodings = self.send_compression_encodings;
+ let max_decoding_message_size = self.max_decoding_message_size;
+ let max_encoding_message_size = self.max_encoding_message_size;
+ let inner = self.inner.clone();
+ let fut = async move {
+ let method = UnlockSvc(inner);
+ let codec = tonic::codec::ProstCodec::default();
+ let mut grpc = tonic::server::Grpc::new(codec)
+ .apply_compression_config(
+ accept_compression_encodings,
+ send_compression_encodings,
+ )
+ .apply_max_message_size_config(
+ max_decoding_message_size,
+ max_encoding_message_size,
+ );
+ let res = grpc.unary(method, req).await;
+ Ok(res)
+ };
+ Box::pin(fut)
+ }
+ "/v1.AccountManager/Lock" => {
+ #[allow(non_camel_case_types)]
+ struct LockSvc(pub Arc);
+ impl<
+ T: AccountManager,
+ > tonic::server::UnaryService
+ for LockSvc {
+ type Response = super::LockAccountResponse;
+ type Future = BoxFuture<
+ tonic::Response,
+ tonic::Status,
+ >;
+ fn call(
+ &mut self,
+ request: tonic::Request,
+ ) -> Self::Future {
+ let inner = Arc::clone(&self.0);
+ let fut = async move {
+ ::lock(&inner, request).await
+ };
+ Box::pin(fut)
+ }
+ }
+ let accept_compression_encodings = self.accept_compression_encodings;
+ let send_compression_encodings = self.send_compression_encodings;
+ let max_decoding_message_size = self.max_decoding_message_size;
+ let max_encoding_message_size = self.max_encoding_message_size;
+ let inner = self.inner.clone();
+ let fut = async move {
+ let method = LockSvc(inner);
+ let codec = tonic::codec::ProstCodec::default();
+ let mut grpc = tonic::server::Grpc::new(codec)
+ .apply_compression_config(
+ accept_compression_encodings,
+ send_compression_encodings,
+ )
+ .apply_max_message_size_config(
+ max_decoding_message_size,
+ max_encoding_message_size,
+ );
+ let res = grpc.unary(method, req).await;
+ Ok(res)
+ };
+ Box::pin(fut)
+ }
+ "/v1.AccountManager/Generate" => {
+ #[allow(non_camel_case_types)]
+ struct GenerateSvc(pub Arc);
+ impl<
+ T: AccountManager,
+ > tonic::server::UnaryService
+ for GenerateSvc {
+ type Response = super::GenerateResponse;
+ type Future = BoxFuture<
+ tonic::Response,
+ tonic::Status,
+ >;
+ fn call(
+ &mut self,
+ request: tonic::Request,
+ ) -> Self::Future {
+ let inner = Arc::clone(&self.0);
+ let fut = async move {
+ ::generate(&inner, request).await
+ };
+ Box::pin(fut)
+ }
+ }
+ let accept_compression_encodings = self.accept_compression_encodings;
+ let send_compression_encodings = self.send_compression_encodings;
+ let max_decoding_message_size = self.max_decoding_message_size;
+ let max_encoding_message_size = self.max_encoding_message_size;
+ let inner = self.inner.clone();
+ let fut = async move {
+ let method = GenerateSvc(inner);
+ let codec = tonic::codec::ProstCodec::default();
+ let mut grpc = tonic::server::Grpc::new(codec)
+ .apply_compression_config(
+ accept_compression_encodings,
+ send_compression_encodings,
+ )
+ .apply_max_message_size_config(
+ max_decoding_message_size,
+ max_encoding_message_size,
+ );
+ let res = grpc.unary(method, req).await;
+ Ok(res)
+ };
+ Box::pin(fut)
+ }
+ _ => {
+ Box::pin(async move {
+ let mut response = http::Response::new(empty_body());
+ let headers = response.headers_mut();
+ headers
+ .insert(
+ tonic::Status::GRPC_STATUS,
+ (tonic::Code::Unimplemented as i32).into(),
+ );
+ headers
+ .insert(
+ http::header::CONTENT_TYPE,
+ tonic::metadata::GRPC_CONTENT_TYPE,
+ );
+ Ok(response)
+ })
+ }
+ }
+ }
+ }
+ impl Clone for AccountManagerServer {
+ fn clone(&self) -> Self {
+ let inner = self.inner.clone();
+ Self {
+ inner,
+ accept_compression_encodings: self.accept_compression_encodings,
+ send_compression_encodings: self.send_compression_encodings,
+ max_decoding_message_size: self.max_decoding_message_size,
+ max_encoding_message_size: self.max_encoding_message_size,
+ }
+ }
+ }
+ /// Generated gRPC service name
+ pub const SERVICE_NAME: &str = "v1.AccountManager";
+ impl tonic::server::NamedService for AccountManagerServer {
+ const NAME: &'static str = SERVICE_NAME;
+ }
+}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct UnlockWalletRequest {
+ #[prost(string, tag = "1")]
+ pub wallet: ::prost::alloc::string::String,
+ #[prost(bytes = "vec", tag = "2")]
+ pub passphrase: ::prost::alloc::vec::Vec,
+}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct LockWalletRequest {
+ #[prost(string, tag = "1")]
+ pub wallet: ::prost::alloc::string::String,
+}
+#[derive(Clone, Copy, PartialEq, ::prost::Message)]
+pub struct UnlockWalletResponse {
+ #[prost(enumeration = "ResponseState", tag = "1")]
+ pub state: i32,
+}
+#[derive(Clone, Copy, PartialEq, ::prost::Message)]
+pub struct LockWalletResponse {
+ #[prost(enumeration = "ResponseState", tag = "1")]
+ pub state: i32,
+}
+/// Generated client implementations.
+pub mod wallet_manager_client {
+ #![allow(
+ unused_variables,
+ dead_code,
+ missing_docs,
+ clippy::wildcard_imports,
+ clippy::let_unit_value,
+ )]
+ use tonic::codegen::*;
+ use tonic::codegen::http::Uri;
+ #[derive(Debug, Clone)]
+ pub struct WalletManagerClient {
+ inner: tonic::client::Grpc,
+ }
+ impl WalletManagerClient {
+ /// Attempt to create a new client by connecting to a given endpoint.
+ pub async fn connect(dst: D) -> Result
+ where
+ D: TryInto,
+ D::Error: Into,
+ {
+ let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
+ Ok(Self::new(conn))
+ }
+ }
+ impl WalletManagerClient
+ where
+ T: tonic::client::GrpcService,
+ T::Error: Into,
+ T::ResponseBody: Body + std::marker::Send + 'static,
+ ::Error: Into + std::marker::Send,
+ {
+ pub fn new(inner: T) -> Self {
+ let inner = tonic::client::Grpc::new(inner);
+ Self { inner }
+ }
+ pub fn with_origin(inner: T, origin: Uri) -> Self {
+ let inner = tonic::client::Grpc::with_origin(inner, origin);
+ Self { inner }
+ }
+ pub fn with_interceptor(
+ inner: T,
+ interceptor: F,
+ ) -> WalletManagerClient>
+ where
+ F: tonic::service::Interceptor,
+ T::ResponseBody: Default,
+ T: tonic::codegen::Service<
+ http::Request,
+ Response = http::Response<
+ >::ResponseBody,
+ >,
+ >,
+ ,
+ >>::Error: Into + std::marker::Send + std::marker::Sync,
+ {
+ WalletManagerClient::new(InterceptedService::new(inner, interceptor))
+ }
+ /// Compress requests with the given encoding.
+ ///
+ /// This requires the server to support it otherwise it might respond with an
+ /// error.
+ #[must_use]
+ pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
+ self.inner = self.inner.send_compressed(encoding);
+ self
+ }
+ /// Enable decompressing responses.
+ #[must_use]
+ pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
+ self.inner = self.inner.accept_compressed(encoding);
+ self
+ }
+ /// Limits the maximum size of a decoded message.
+ ///
+ /// Default: `4MB`
+ #[must_use]
+ pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
+ self.inner = self.inner.max_decoding_message_size(limit);
+ self
+ }
+ /// Limits the maximum size of an encoded message.
+ ///
+ /// Default: `usize::MAX`
+ #[must_use]
+ pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
+ self.inner = self.inner.max_encoding_message_size(limit);
+ self
+ }
+ pub async fn unlock(
+ &mut self,
+ request: impl tonic::IntoRequest,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ > {
+ self.inner
+ .ready()
+ .await
+ .map_err(|e| {
+ tonic::Status::unknown(
+ format!("Service was not ready: {}", e.into()),
+ )
+ })?;
+ let codec = tonic::codec::ProstCodec::default();
+ let path = http::uri::PathAndQuery::from_static("/v1.WalletManager/Unlock");
+ let mut req = request.into_request();
+ req.extensions_mut().insert(GrpcMethod::new("v1.WalletManager", "Unlock"));
+ self.inner.unary(req, path, codec).await
+ }
+ pub async fn lock(
+ &mut self,
+ request: impl tonic::IntoRequest,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ > {
+ self.inner
+ .ready()
+ .await
+ .map_err(|e| {
+ tonic::Status::unknown(
+ format!("Service was not ready: {}", e.into()),
+ )
+ })?;
+ let codec = tonic::codec::ProstCodec::default();
+ let path = http::uri::PathAndQuery::from_static("/v1.WalletManager/Lock");
+ let mut req = request.into_request();
+ req.extensions_mut().insert(GrpcMethod::new("v1.WalletManager", "Lock"));
+ self.inner.unary(req, path, codec).await
+ }
+ }
+}
+/// Generated server implementations.
+pub mod wallet_manager_server {
+ #![allow(
+ unused_variables,
+ dead_code,
+ missing_docs,
+ clippy::wildcard_imports,
+ clippy::let_unit_value,
+ )]
+ use tonic::codegen::*;
+ /// Generated trait containing gRPC methods that should be implemented for use with WalletManagerServer.
+ #[async_trait]
+ pub trait WalletManager: std::marker::Send + std::marker::Sync + 'static {
+ async fn unlock(
+ &self,
+ request: tonic::Request,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ >;
+ async fn lock(
+ &self,
+ request: tonic::Request,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ >;
+ }
+ #[derive(Debug)]
+ pub struct WalletManagerServer {
+ inner: Arc,
+ accept_compression_encodings: EnabledCompressionEncodings,
+ send_compression_encodings: EnabledCompressionEncodings,
+ max_decoding_message_size: Option,
+ max_encoding_message_size: Option,
+ }
+ impl WalletManagerServer {
+ pub fn new(inner: T) -> Self {
+ Self::from_arc(Arc::new(inner))
+ }
+ pub fn from_arc(inner: Arc) -> Self {
+ Self {
+ inner,
+ accept_compression_encodings: Default::default(),
+ send_compression_encodings: Default::default(),
+ max_decoding_message_size: None,
+ max_encoding_message_size: None,
+ }
+ }
+ pub fn with_interceptor(
+ inner: T,
+ interceptor: F,
+ ) -> InterceptedService
+ where
+ F: tonic::service::Interceptor,
+ {
+ InterceptedService::new(Self::new(inner), interceptor)
+ }
+ /// Enable decompressing requests with the given encoding.
+ #[must_use]
+ pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
+ self.accept_compression_encodings.enable(encoding);
+ self
+ }
+ /// Compress responses with the given encoding, if the client supports it.
+ #[must_use]
+ pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
+ self.send_compression_encodings.enable(encoding);
+ self
+ }
+ /// Limits the maximum size of a decoded message.
+ ///
+ /// Default: `4MB`
+ #[must_use]
+ pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
+ self.max_decoding_message_size = Some(limit);
+ self
+ }
+ /// Limits the maximum size of an encoded message.
+ ///
+ /// Default: `usize::MAX`
+ #[must_use]
+ pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
+ self.max_encoding_message_size = Some(limit);
+ self
+ }
+ }
+ impl tonic::codegen::Service> for WalletManagerServer
+ where
+ T: WalletManager,
+ B: Body + std::marker::Send + 'static,
+ B::Error: Into + std::marker::Send + 'static,
+ {
+ type Response = http::Response;
+ type Error = std::convert::Infallible;
+ type Future = BoxFuture;
+ fn poll_ready(
+ &mut self,
+ _cx: &mut Context<'_>,
+ ) -> Poll> {
+ Poll::Ready(Ok(()))
+ }
+ fn call(&mut self, req: http::Request) -> Self::Future {
+ match req.uri().path() {
+ "/v1.WalletManager/Unlock" => {
+ #[allow(non_camel_case_types)]
+ struct UnlockSvc(pub Arc);
+ impl<
+ T: WalletManager,
+ > tonic::server::UnaryService
+ for UnlockSvc {
+ type Response = super::UnlockWalletResponse;
+ type Future = BoxFuture<
+ tonic::Response,
+ tonic::Status,
+ >;
+ fn call(
+ &mut self,
+ request: tonic::Request,
+ ) -> Self::Future {
+ let inner = Arc::clone(&self.0);
+ let fut = async move {
+ ::unlock(&inner, request).await
+ };
+ Box::pin(fut)
+ }
+ }
+ let accept_compression_encodings = self.accept_compression_encodings;
+ let send_compression_encodings = self.send_compression_encodings;
+ let max_decoding_message_size = self.max_decoding_message_size;
+ let max_encoding_message_size = self.max_encoding_message_size;
+ let inner = self.inner.clone();
+ let fut = async move {
+ let method = UnlockSvc(inner);
+ let codec = tonic::codec::ProstCodec::default();
+ let mut grpc = tonic::server::Grpc::new(codec)
+ .apply_compression_config(
+ accept_compression_encodings,
+ send_compression_encodings,
+ )
+ .apply_max_message_size_config(
+ max_decoding_message_size,
+ max_encoding_message_size,
+ );
+ let res = grpc.unary(method, req).await;
+ Ok(res)
+ };
+ Box::pin(fut)
+ }
+ "/v1.WalletManager/Lock" => {
+ #[allow(non_camel_case_types)]
+ struct LockSvc(pub Arc);
+ impl<
+ T: WalletManager,
+ > tonic::server::UnaryService
+ for LockSvc {
+ type Response = super::LockWalletResponse;
+ type Future = BoxFuture<
+ tonic::Response,
+ tonic::Status,
+ >;
+ fn call(
+ &mut self,
+ request: tonic::Request,
+ ) -> Self::Future {
+ let inner = Arc::clone(&self.0);
+ let fut = async move {
+ ::lock(&inner, request).await
+ };
+ Box::pin(fut)
+ }
+ }
+ let accept_compression_encodings = self.accept_compression_encodings;
+ let send_compression_encodings = self.send_compression_encodings;
+ let max_decoding_message_size = self.max_decoding_message_size;
+ let max_encoding_message_size = self.max_encoding_message_size;
+ let inner = self.inner.clone();
+ let fut = async move {
+ let method = LockSvc(inner);
+ let codec = tonic::codec::ProstCodec::default();
+ let mut grpc = tonic::server::Grpc::new(codec)
+ .apply_compression_config(
+ accept_compression_encodings,
+ send_compression_encodings,
+ )
+ .apply_max_message_size_config(
+ max_decoding_message_size,
+ max_encoding_message_size,
+ );
+ let res = grpc.unary(method, req).await;
+ Ok(res)
+ };
+ Box::pin(fut)
+ }
+ _ => {
+ Box::pin(async move {
+ let mut response = http::Response::new(empty_body());
+ let headers = response.headers_mut();
+ headers
+ .insert(
+ tonic::Status::GRPC_STATUS,
+ (tonic::Code::Unimplemented as i32).into(),
+ );
+ headers
+ .insert(
+ http::header::CONTENT_TYPE,
+ tonic::metadata::GRPC_CONTENT_TYPE,
+ );
+ Ok(response)
+ })
+ }
+ }
+ }
+ }
+ impl Clone for WalletManagerServer {
+ fn clone(&self) -> Self {
+ let inner = self.inner.clone();
+ Self {
+ inner,
+ accept_compression_encodings: self.accept_compression_encodings,
+ send_compression_encodings: self.send_compression_encodings,
+ max_decoding_message_size: self.max_decoding_message_size,
+ max_encoding_message_size: self.max_encoding_message_size,
+ }
+ }
+ }
+ /// Generated gRPC service name
+ pub const SERVICE_NAME: &str = "v1.WalletManager";
+ impl tonic::server::NamedService for WalletManagerServer {
+ const NAME: &'static str = SERVICE_NAME;
+ }
+}
diff --git a/bolt-cli/src/utils/dirk.rs b/bolt-cli/src/utils/dirk.rs
index 2d161edf1..f4b311758 100644
--- a/bolt-cli/src/utils/dirk.rs
+++ b/bolt-cli/src/utils/dirk.rs
@@ -1,24 +1,32 @@
use std::fs;
use alloy_primitives::B256;
-use ethereum_consensus::crypto::bls::{PublicKey as BlsPublicKey, Signature as BlsSignature};
-use eyre::{Context, Result};
+use ethereum_consensus::crypto::bls::Signature as BlsSignature;
+use eyre::{bail, Context, Result};
use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
+use tracing::debug;
use crate::{
cli::TlsCredentials,
pb::eth2_signer_api::{
- Account, ListAccountsRequest, ListerClient, SignRequest, SignRequestId, SignerClient,
+ Account, AccountManagerClient, ListAccountsRequest, ListerClient, ResponseState,
+ SignRequest, SignRequestId, SignerClient, UnlockAccountRequest,
},
};
/// A Dirk remote signer.
///
+/// Available services:
+/// - `Lister`: List accounts in the keystore.
+/// - `Signer`: Request a signature from the remote signer.
+/// - `AccountManager`: Manage accounts in the keystore (lock and unlock accounts).
+///
/// Reference: https://github.com/attestantio/dirk
#[derive(Clone)]
pub struct Dirk {
lister: ListerClient,
signer: SignerClient,
+ account_mng: AccountManagerClient,
}
impl Dirk {
@@ -30,34 +38,76 @@ impl Dirk {
let lister = ListerClient::new(conn.clone());
let signer = SignerClient::new(conn.clone());
+ let account_mng = AccountManagerClient::new(conn);
- Ok(Self { lister, signer })
+ Ok(Self { lister, signer, account_mng })
}
/// List all accounts in the keystore.
- pub async fn list_accounts(&mut self, paths: Vec) -> Result> {
- let accs = self.lister.list_accounts(ListAccountsRequest { paths }).await?;
+ pub async fn list_accounts(&mut self, wallet_path: String) -> Result> {
+ // Request all accounts in the given path. Only one path at a time
+ // as done in https://github.com/wealdtech/go-eth2-wallet-dirk/blob/182f99b22b64d01e0d4ae67bf47bb055763465d7/grpc.go#L121
+ let req = ListAccountsRequest { paths: vec![wallet_path] };
+ let res = self.lister.list_accounts(req).await?.into_inner();
+
+ if !matches!(res.state(), ResponseState::Succeeded) {
+ bail!("Failed to list accounts: {:?}", res);
+ }
- Ok(accs.into_inner().accounts)
+ debug!("{} Accounts listed successfully", res.accounts.len());
+ Ok(res.accounts)
+ }
+
+ /// Unlock an account in the keystore with the given passphrase.
+ pub async fn unlock_account(
+ &mut self,
+ account_name: String,
+ passphrase: String,
+ ) -> Result {
+ let pf_bytes = passphrase.as_bytes().to_vec();
+ let req = UnlockAccountRequest { account: account_name.clone(), passphrase: pf_bytes };
+ let res = self.account_mng.unlock(req).await?.into_inner();
+
+ match res.state() {
+ ResponseState::Succeeded => {
+ debug!("Unlock request succeeded for account {}", account_name);
+ Ok(true)
+ }
+ ResponseState::Denied => {
+ debug!("Unlock request denied for account {}", account_name);
+ Ok(false)
+ }
+ ResponseState::Unknown => bail!("Unknown response from unlock account: {:?}", res),
+ ResponseState::Failed => bail!("Failed to unlock account: {:?}", res),
+ }
}
/// Request a signature from the remote signer.
pub async fn request_signature(
&mut self,
- pubkey: BlsPublicKey,
+ account: &Account,
hash: B256,
domain: B256,
) -> Result {
let req = SignRequest {
data: hash.to_vec(),
domain: domain.to_vec(),
- id: Some(SignRequestId::PublicKey(pubkey.to_vec())),
+ id: Some(SignRequestId::Account(account.name.clone())),
};
- let res = self.signer.sign(req).await?;
- let sig = res.into_inner().signature;
- let sig = BlsSignature::try_from(sig.as_slice()).wrap_err("Failed to parse signature")?;
+ let res = self.signer.sign(req).await?.into_inner();
+
+ if !matches!(res.state(), ResponseState::Succeeded) {
+ bail!("Failed to sign data: {:?}", res);
+ }
+ if res.signature.is_empty() {
+ bail!("Empty signature returned");
+ }
+
+ let sig = BlsSignature::try_from(res.signature.as_slice())
+ .wrap_err("Failed to parse signature")?;
+ debug!("Signature request succeeded for account {}", account.name);
Ok(sig)
}
}
@@ -133,7 +183,7 @@ mod tests {
let mut dirk = Dirk::connect(url, cred).await?;
- let accounts = dirk.list_accounts(vec!["wallet1".to_string()]).await?;
+ let accounts = dirk.list_accounts("wallet1".to_string()).await?;
println!("Dirk Accounts: {:?}", accounts);
// make sure to stop the dirk server
diff --git a/bolt-cli/test_data/dirk/dirk.template.json b/bolt-cli/test_data/dirk/dirk.template.json
index 54c954320..e51b81701 100644
--- a/bolt-cli/test_data/dirk/dirk.template.json
+++ b/bolt-cli/test_data/dirk/dirk.template.json
@@ -19,5 +19,8 @@
"localhost": {
"wallet1": "All"
}
+ },
+ "unlocker": {
+ "wallet-passphrases": ["file://$PWD/wallet1-pf.txt"]
}
}
diff --git a/bolt-cli/test_data/dirk/wallet1-pf.txt b/bolt-cli/test_data/dirk/wallet1-pf.txt
new file mode 100644
index 000000000..536aca34d
--- /dev/null
+++ b/bolt-cli/test_data/dirk/wallet1-pf.txt
@@ -0,0 +1 @@
+secret
\ No newline at end of file