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

feat(cli): Add web3signer KeySource #513

Merged
merged 10 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions bolt-cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bolt-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ thiserror = "1.0"
hex = "0.4.3"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
reqwest = "0.12.8"
reqwest = { version = "0.12.9", features = ["rustls-tls"] }
rand = "0.8.5"

[dev-dependencies]
Expand Down
23 changes: 23 additions & 0 deletions bolt-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ bolt delegate \
--wallet-path wallet1 --passphrases secret
```

4. Generating a delegation using a remote Web3Signer keystore

```text
bolt delegate \
--delegatee-pubkey 0x83eeddfac5e60f8fe607ee8713efb8877c295ad9f8ca075f4d8f6f2ae241a30dd57f78f6f3863a9fe0d5b5db9d550b93 \
--chain holesky \
web3-signer --url https://localhost:9000 \
--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
```

</details>

---
Expand All @@ -144,6 +156,7 @@ 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)
- Remote Web3Keystore via `web3signer`

<details>
<summary>Usage</summary>
Expand All @@ -159,6 +172,7 @@ 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
web3signer Use a remote web3signer keystore to generate the signed messages
help Print this message or the help of the given subcommand(s)

Options:
Expand Down Expand Up @@ -195,6 +209,15 @@ bolt pubkeys dirk --url https://localhost:9091 \
--wallet-path wallet1 --passphrases secret
```

4. Listing BLS public keys from a remote Web3Signer keystore

```text
bolt pubkeys web3signer --url https://localhost:9000 \
--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
```

</details>

---
Expand Down
36 changes: 36 additions & 0 deletions bolt-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,13 @@ pub enum KeysSource {
#[clap(flatten)]
opts: DirkOpts,
},

/// Use a remote web3signer keystore as source for the public keys.
Web3Signer {
/// The options for connecting to the web3signer keystore.
#[clap(flatten)]
opts: Web3SignerOpts,
},
}

#[derive(Debug, Clone, Parser)]
Expand All @@ -363,6 +370,12 @@ pub enum SecretsSource {
#[clap(flatten)]
opts: DirkOpts,
},

/// Use a remote Web3Signer keystore to generate the signed messages.
Web3Signer {
#[clap(flatten)]
opts: Web3SignerOpts,
},
}

/// Options for reading a keystore folder.
Expand Down Expand Up @@ -413,6 +426,29 @@ pub struct DirkOpts {
pub tls_credentials: TlsCredentials,
}

/// Options for connecting to a Web3Signer keystore.
#[derive(Debug, Clone, Parser)]
pub struct Web3SignerOpts {
/// The URL of the Web3Signer keystore.
#[clap(long, env = "WEB3SIGNER_URL")]
pub url: String,

/// The TLS credentials for connecting to the Web3Signer keystore.
#[clap(flatten)]
pub tls_credentials: RustTlsCredentials,
}

/// TLS credentials which are used for rustls-tls when connecting to reqwest.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct RustTlsCredentials {
tsnewnami marked this conversation as resolved.
Show resolved Hide resolved
/// Path to the CA certificate file. (.crt)
#[clap(long, env = "CA_CERT_PATH")]
pub ca_cert_path: String,
/// Path to the PEM encoded private key and certificate file. (.pem)
#[clap(long, env = "CLIENT_COMBINED_PEM")]
pub combined_pem_path: String,
}

/// TLS credentials for connecting to a remote server.
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
pub struct TlsCredentials {
Expand Down
7 changes: 7 additions & 0 deletions bolt-cli/src/commands/delegate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ mod keystore;
/// Create delegations from remote Dirk signers.
mod dirk;

/// Create delegations from remote Web3Signers.
mod web3signer;

impl DelegateCommand {
/// Run the `delegate` command.
pub async fn run(self) -> Result<()> {
Expand Down Expand Up @@ -46,6 +49,10 @@ impl DelegateCommand {
let delegatee_pubkey = parse_bls_public_key(&self.delegatee_pubkey)?;
dirk::generate_from_dirk(opts, delegatee_pubkey, self.chain, self.action).await?
}
SecretsSource::Web3Signer { opts } => {
let delegatee_pubkey = parse_bls_public_key(&self.delegatee_pubkey)?;
web3signer::generate_from_web3signer(opts, delegatee_pubkey, self.action).await?
}
};

debug!("Generated {} signed messages", signed_messages.len());
Expand Down
103 changes: 103 additions & 0 deletions bolt-cli/src/commands/delegate/web3signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use crate::{
cli::{Action, Web3SignerOpts},
commands::delegate::types::{
DelegationMessage, RevocationMessage, SignedDelegation, SignedRevocation,
},
common::web3signer::Web3Signer,
};
use ethereum_consensus::crypto::{PublicKey as BlsPublicKey, Signature as BlsSignature};
use eyre::Result;
use tracing::debug;

use super::types::SignedMessage;

/// Generate signed delegations/recovations using a remote Web3Signer.
pub async fn generate_from_web3signer(
opts: Web3SignerOpts,
delegatee_pubkey: BlsPublicKey,
action: Action,
) -> Result<Vec<SignedMessage>> {
// Connect to web3signer.
let mut web3signer = Web3Signer::connect(opts.url, opts.tls_credentials).await?;

// Read in the accounts from the remote keystore.
let accounts = web3signer.list_accounts().await?;
debug!("Found {} remote accounts to sign with", accounts.len());

let mut signed_messages = Vec::with_capacity(accounts.len());

for account in accounts {
// Parse the BLS key of the account.
// Trim the pre-pended 0x.
let trimmed_account = &account.clone()[2..];
merklefruit marked this conversation as resolved.
Show resolved Hide resolved
let pubkey = BlsPublicKey::try_from(hex::decode(trimmed_account)?.as_slice())?;

match action {
Action::Delegate => {
let message = DelegationMessage::new(pubkey.clone(), delegatee_pubkey.clone());
// Web3Signer expects the pre-pended 0x.
let signing_root = format!("0x{}", &hex::encode(message.digest()));
let returned_signature =
web3signer.request_signature(&account, &signing_root).await?;
// Trim the 0x.
let trimmed_signature = &returned_signature[2..];
let signature = BlsSignature::try_from(hex::decode(trimmed_signature)?.as_slice())?;
let signed = SignedDelegation { message, signature };
signed_messages.push(SignedMessage::Delegation(signed));
}
Action::Revoke => {
let message = RevocationMessage::new(pubkey.clone(), delegatee_pubkey.clone());
// Web3Signer expects the pre-pended 0x.
let signing_root = format!("0x{}", &hex::encode(message.digest()));
let returned_signature =
web3signer.request_signature(&account, &signing_root).await?;
// Trim the 0x.
let trimmed_signature = &returned_signature[2..];
let signature = BlsSignature::try_from(trimmed_signature.as_bytes())?;
let signed = SignedRevocation { message, signature };
signed_messages.push(SignedMessage::Revocation(signed));
}
}
}

Ok(signed_messages)
}

#[cfg(test)]
mod tests {
use crate::{
cli::{Action, Chain, Web3SignerOpts},
commands::delegate::web3signer::generate_from_web3signer,
common::{parse_bls_public_key, web3signer::test_util::start_web3signer_test_server},
};

/// Test generating signed delegations using a remote Web3Signer signer.
///
/// ```shell
/// cargo test --package bolt --bin bolt -- commands::delegate::tests::test_delegation_web3signer
/// --exact --show-output --ignored --nocapture
/// ```
#[tokio::test]
#[ignore = "Requires Web3Signer to be installed on the system"]
async fn test_delegation_web3signer() -> eyre::Result<()> {
let _ = tracing_subscriber::fmt::try_init();
let (url, mut web3signer_proc, creds) = start_web3signer_test_server().await?;

let delegatee_pubkey = "0x83eeddfac5e60f8fe607ee8713efb8877c295ad9f8ca075f4d8f6f2ae241a30dd57f78f6f3863a9fe0d5b5db9d550b93";
let delegatee_pubkey = parse_bls_public_key(delegatee_pubkey)?;
let chain = Chain::Mainnet;

let opts = Web3SignerOpts { url, tls_credentials: creds };

let signed_delegations =
generate_from_web3signer(opts, delegatee_pubkey, Action::Delegate).await?;

let signed_message = signed_delegations.first().expect("to get signed delegation");

signed_message.verify_signature(chain)?;

web3signer_proc.kill()?;

Ok(())
}
}
Loading