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(bridge-withdrawer): support FROST threshold signing #1948

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
27 changes: 15 additions & 12 deletions Cargo.lock

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

16 changes: 16 additions & 0 deletions buf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,22 @@ modules:
use:
- WIRE_JSON
ignore_unstable_packages: true
- path: proto/signerapis
name: buf.build/astria/signer-apis
lint:
use:
- BASIC
- ENUM_VALUE_PREFIX
- ENUM_ZERO_VALUE_SUFFIX
- FILE_LOWER_SNAKE_CASE
- PACKAGE_VERSION_SUFFIX
- RPC_REQUEST_STANDARD_NAME
- SERVICE_SUFFIX
disallow_comment_ignores: true
breaking:
use:
- WIRE_JSON
ignore_unstable_packages: true
- path: proto/vendored
name: buf.build/astria/vendored
lint:
Expand Down
2 changes: 1 addition & 1 deletion charts/evm-bridge-withdrawer/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 1.0.1
version: 1.0.2

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
Expand Down
4 changes: 4 additions & 0 deletions charts/evm-bridge-withdrawer/templates/configmaps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ data:
OTEL_SERVICE_NAME: "{{ tpl .Values.otel.serviceName . }}"
{{- if not .Values.global.dev }}
{{- else }}
ASTRIA_BRIDGE_WITHDRAWER_FROST_THRESHOLD_SIGNING_ENABLED: "{{ .Values.config.frostThresholdSigningEnabled }}"
ASTRIA_BRIDGE_WITHDRAWER_FROST_MIN_SIGNERS: "{{ .Values.config.frostMinSigners }}"
ASTRIA_BRIDGE_WITHDRAWER_FROST_PUBLIC_KEY_PACKAGE_PATH: "{{ .Values.config.frostPublicKeyPackagePath }}"
ASTRIA_BRIDGE_WITHDRAWER_FROST_PARTICIPANT_ENDPOINTS: "{{ .Values.config.frostParticipantEndpoints }}"
{{- end }}
---
{{- if not .Values.secretProvider.enabled }}
Expand Down
6 changes: 5 additions & 1 deletion charts/evm-bridge-withdrawer/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ images:
evmBridgeWithdrawer:
repo: ghcr.io/astriaorg/evm-bridge-withdrawer
pullPolicy: IfNotPresent
tag: 1.0.1
tag: 1.0.2
devTag: latest

config:
Expand All @@ -28,6 +28,10 @@ config:
rollupAssetDenom: ""
evmContractAddress: "0x"
evmRpcEndpoint: ""
frostThresholdSigningEnabled: "false"
frostMinSigners: "0"
frostPublicKeyPackagePath: ""
frostParticipantEndpoints: "[]"
sequencerPrivateKey:
devContent: ""
secret:
Expand Down
6 changes: 3 additions & 3 deletions charts/evm-stack/Chart.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ dependencies:
version: 0.1.4
- name: evm-bridge-withdrawer
repository: file://../evm-bridge-withdrawer
version: 1.0.1
version: 1.0.2
- name: postgresql
repository: oci://registry-1.docker.io/bitnamicharts
version: 15.2.4
- name: blockscout-stack
repository: https://blockscout.github.io/helm-charts
version: 1.6.8
digest: sha256:c437d6967341b9bb6e10a809ce13e81130bfb95fb111c8712088ab443adea3f1
generated: "2025-01-28T23:46:42.687706-05:00"
digest: sha256:7134d1bf040721c7fd84df068c7d89f6b68eb956bf93e37aacf819bc39d233a6
generated: "2025-02-10T12:38:51.155814856-05:00"
4 changes: 2 additions & 2 deletions charts/evm-stack/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 1.0.8
version: 1.0.9

dependencies:
- name: celestia-node
Expand All @@ -34,7 +34,7 @@ dependencies:
repository: "file://../evm-faucet"
condition: evm-faucet.enabled
- name: evm-bridge-withdrawer
version: 1.0.1
version: 1.0.2
repository: "file://../evm-bridge-withdrawer"
condition: evm-bridge-withdrawer.enabled
- name: postgresql
Expand Down
1 change: 1 addition & 0 deletions crates/astria-bridge-withdrawer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- Update `idna` dependency to resolve cargo audit warning [#1869](https://github.com/astriaorg/astria/pull/1869).
- Support FROST threshold signing using `astria-bridge-signer` nodes. [#1948](https://github.com/astriaorg/astria/pull/1948).

## [1.0.1] - 2024-11-01

Expand Down
3 changes: 3 additions & 0 deletions crates/astria-bridge-withdrawer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ homepage = "https://astria.org"

[dependencies]
http = "0.2.9"
frost-ed25519 = { version = "2.1.0", features = [] }

axum = { workspace = true }
futures = { workspace = true }
Expand All @@ -18,6 +19,7 @@ ethers = { workspace = true, features = ["ws"] }
hyper = { workspace = true }
humantime = { workspace = true }
ibc-types = { workspace = true }
pbjson-types = { workspace = true }
pin-project-lite = { workspace = true }
prost = { workspace = true }
serde = { workspace = true, features = ["derive"] }
Expand Down Expand Up @@ -54,6 +56,7 @@ astria-grpc-mock = { path = "../astria-grpc-mock" }
config = { package = "astria-config", path = "../astria-config", features = [
"tests",
] }
rand = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
tempfile = { workspace = true }
tendermint-rpc = { workspace = true }
Expand Down
16 changes: 16 additions & 0 deletions crates/astria-bridge-withdrawer/local.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,26 @@ ASTRIA_BRIDGE_WITHDRAWER_SEQUENCER_COMETBFT_ENDPOINT="http://127.0.0.1:26657"
# Chain ID of the sequencer chain which transactions are submitted to.
ASTRIA_BRIDGE_WITHDRAWER_SEQUENCER_CHAIN_ID="astria"

# Set to true to enable frost threshold signing.
ASTRIA_BRIDGE_WITHDRAWER_FROST_THRESHOLD_SIGNING_ENABLED=false

# The path to the file storing the private key for the sequencer account used for signing
# transactions. The file should contain a hex-encoded Ed25519 secret key.
# Only used if `frost_threshold_signing_enabled` is false.
ASTRIA_BRIDGE_WITHDRAWER_SEQUENCER_KEY_PATH=/path/to/priv_sequencer_key.json

# The minimum number of frost participants required to sign a transaction.
# Only used if `frost_threshold_signing_enabled` is true.
ASTRIA_BRIDGE_WITHDRAWER_FROST_MIN_SIGNERS=0

# The path to the json-encoded frost public key package.
# Only used if `frost_threshold_signing_enabled` is true.
ASTRIA_BRIDGE_WITHDRAWER_FROST_PUBLIC_KEY_PACKAGE_PATH=""

# The frost participant gRPC endpoints.
# Only used if `frost_threshold_signing_enabled` is true.
ASTRIA_BRIDGE_WITHDRAWER_FROST_PARTICIPANT_ENDPOINTS=[]

# The prefix that will be used to construct bech32m sequencer addresses.
ASTRIA_BRIDGE_WITHDRAWER_SEQUENCER_ADDRESS_PREFIX=astria

Expand Down
71 changes: 66 additions & 5 deletions crates/astria-bridge-withdrawer/src/bridge_withdrawer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use ethereum::watcher::Watcher;
use http::Uri;
use hyper::server::conn::AddrIncoming;
use startup::Startup;
use submitter::signer::Signer;
use tokio::{
select,
sync::oneshot::{
Expand Down Expand Up @@ -45,6 +46,10 @@ use self::{
};
use crate::{
api,
bridge_withdrawer::submitter::frost_signer::{
initialize_frost_participant_clients,
FrostSignerBuilder,
},
config::Config,
metrics::Metrics,
};
Expand All @@ -71,13 +76,20 @@ impl BridgeWithdrawer {
/// # Errors
///
/// - If the provided `api_addr` string cannot be parsed as a socket address.
pub fn new(cfg: Config, metrics: &'static Metrics) -> eyre::Result<(Self, ShutdownHandle)> {
pub async fn new(
cfg: Config,
metrics: &'static Metrics,
) -> eyre::Result<(Self, ShutdownHandle)> {
let shutdown_handle = ShutdownHandle::new();
let Config {
api_addr,
sequencer_cometbft_endpoint,
sequencer_chain_id,
frost_threshold_signing_enabled,
sequencer_key_path,
frost_min_signers,
frost_public_key_package_path,
frost_participant_endpoints,
sequencer_address_prefix,
fee_asset_denomination,
ethereum_contract_address,
Expand Down Expand Up @@ -119,18 +131,27 @@ impl BridgeWithdrawer {
let startup_handle = startup::InfoHandle::new(state.subscribe());

// make submitter object
let signer = make_signer(
frost_threshold_signing_enabled,
frost_min_signers,
frost_public_key_package_path,
frost_participant_endpoints,
sequencer_key_path,
sequencer_address_prefix,
)
.await
.wrap_err("failed to create signer")?;

let (submitter, submitter_handle) = submitter::Builder {
shutdown_token: shutdown_handle.token(),
startup_handle: startup_handle.clone(),
sequencer_cometbft_client,
sequencer_grpc_client,
sequencer_key_path,
sequencer_address_prefix: sequencer_address_prefix.clone(),
signer,
state: state.clone(),
metrics,
}
.build()
.wrap_err("failed to initialize submitter")?;
.build();

let ethereum_watcher = watcher::Builder {
ethereum_contract_address,
Expand Down Expand Up @@ -260,6 +281,46 @@ impl BridgeWithdrawer {
}
}

async fn make_signer(
frost_threshold_signing_enabled: bool,
frost_min_signers: usize,
frost_public_key_package_path: String,
frost_participant_endpoints: Vec<String>,
sequencer_key_path: String,
sequencer_address_prefix: String,
) -> eyre::Result<Arc<dyn Signer>> {
let signer: Arc<dyn Signer> = if frost_threshold_signing_enabled {
let public_key_package_str = std::fs::read_to_string(frost_public_key_package_path)
.wrap_err("failed to read frost public key package")?;
let public_key_package =
serde_json::from_str::<frost_ed25519::keys::PublicKeyPackage>(&public_key_package_str)
.wrap_err("failed to deserialize public key package")?;

let participant_clients =
initialize_frost_participant_clients(frost_participant_endpoints, &public_key_package)
.await
.wrap_err("failed to initialize frost participant clients")?;
Arc::new(
FrostSignerBuilder::new()
.min_signers(frost_min_signers)
.public_key_package(public_key_package)
.participant_clients(participant_clients)
.address_prefix(sequencer_address_prefix)
.try_build()
.wrap_err("failed to initialize frost signer")?,
)
} else {
Arc::new(
crate::bridge_withdrawer::submitter::signer::SequencerKey::builder()
.path(sequencer_key_path)
.prefix(sequencer_address_prefix)
.try_build()
.wrap_err("failed to load sequencer private key")?,
)
};
Ok(signer)
}

#[expect(
clippy::struct_field_names,
reason = "for parity with the `Shutdown` struct"
Expand Down
Loading
Loading