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-signer): implement FROST threshold signer node #1960

Open
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

noot
Copy link
Collaborator

@noot noot commented Feb 10, 2025

Summary

implement FROST threshold signer node which implements the FrostParticipantService gRPC service defined in #1948. the bridge withdrawer can then collect signature partials from various threshold signer nodes to sign a withdrawal transaction in a more distributed manner.

Background

improves security of the withdrawer over having one private key used; instead, we can employ an m-of-n threshold signature scheme to sign withdrawals.

Changes

  • implement FROST threshold signer node which implements the FrostParticipantService gRPC service
  • currently, the signer is evm-rollup specific; when it receives a sequencer withdrawal tx to sign, it verifies that the withdrawals are valid using an evm rollup node. specifically, it performs the same logic that the withdrawer uses to create the withdrawal tx in the first place (one tx = every withdrawal in an evm block converted to an action) to verify.

Testing

tested with the withdrawer in #1948; see https://www.notion.so/astria-org/bridge-threshold-withdrawer-testing-1936bd31a90c803ab33ccc1221f545a1?pvs=4

Changelogs

Ensure all relevant changelog files are updated as necessary. See
keepachangelog for change
categories. Replace this text with e.g. "Changelogs updated." or "No updates
required." to acknowledge changelogs have been considered.

Related Issues

closes #1937

@github-actions github-actions bot added the sequencer pertaining to the astria-sequencer crate label Feb 10, 2025
@noot noot marked this pull request as ready for review February 11, 2025 16:35
@noot noot requested review from SuperFluffy, Fraser999, a team and joroshiba as code owners February 11, 2025 16:35
@quasystaty1
Copy link
Contributor

To deploy this in a production environment, we need to add a Helm chart to manage the new binary within the cluster. Additionally, updates to our GitHub workflow are required to enforce merge restrictions and streamline the release process.

github-merge-queue bot pushed a commit that referenced this pull request Mar 20, 2025
## Summary
support FROST ed25519 threshold signing in the bridge withdrawer. 

## Background
improves security of the withdrawer over having one private key used;
instead, we can employ an m-of-n threshold signature scheme to sign
withdrawals.

## Changes
- create `FrostParticipantService` gRPC proto definition, which is to be
implemented by a binary which contains a FROST secret key partial that
participates in the threshold signing process
- update withdrawer config/setup to support single signer (previous
behaviour) or threshold signing via the `Signer` trait
- create `FrostSigner` which has gRPC clients for the signing
participants
- `FrostSigner.sign()` performs the signing process by calling each
participant for their commitment (part 1) and signature share (part 2)
and finally aggregates the shares to create a valid signature.
- `FrostSigner.sign()` will fail if it does not receive responses from
at least the minimum number of signers required. the min number of
signers is determined during key generation, which is done completely
separately.

## Testing
blackbox tests with mock signer servers have been added.

also, this was tested with a 2/2 threshold signer node setup. withdrawal
transactions are successfully signed, submitted, and included on the
sequencer. see
https://www.notion.so/astria-org/bridge-threshold-withdrawer-testing-1936bd31a90c803ab33ccc1221f545a1?pvs=4

use the following testing keys which are for 2/2 threshold and represent
astria address `astria1w2lxx9p02u7u934ljl060wannqm40g3y089lmh`

put the following keys into their own files.

pubkey package:

```bash
{
  "header": {
    "version": 0,
    "ciphersuite": "FROST-ED25519-SHA512-v1"
  },
  "verifying_shares": {
    "0100000000000000000000000000000000000000000000000000000000000000": "bd209bd0c47806fee2db29bde0b705f6a191602f0307e8d959d74f189ae92e9e",
    "0200000000000000000000000000000000000000000000000000000000000000": "0a677e1c6ad0c1906bf6ac53810b29efcb9ca9e7ab50fe9fca3ae49e613b922f"
  },
  "verifying_key": "ecbe5e2fbaaf23f14ca43f34d71ad733c57afcadeb5782809af48a3da17b9b69"
```

secret key 1:

```bash
{
  "header": {
    "version": 0,
    "ciphersuite": "FROST-ED25519-SHA512-v1"
  },
  "identifier": "0100000000000000000000000000000000000000000000000000000000000000",
  "signing_share": "4b45de055567d7a2027bfc9a2463de4aaa6f2df995180e638dc938013e9bf70e",
  "verifying_share": "bd209bd0c47806fee2db29bde0b705f6a191602f0307e8d959d74f189ae92e9e",
  "verifying_key": "ecbe5e2fbaaf23f14ca43f34d71ad733c57afcadeb5782809af48a3da17b9b69",
  "min_signers": 2
}
```

secret key 2:

```bash
{
  "header": {
    "version": 0,
    "ciphersuite": "FROST-ED25519-SHA512-v1"
  },
  "identifier": "0200000000000000000000000000000000000000000000000000000000000000",
  "signing_share": "29a52cd248df9a7bb2346fb4ed53318f0bff320985c8e49062c68e4fd0012d0f",
  "verifying_share": "0a677e1c6ad0c1906bf6ac53810b29efcb9ca9e7ab50fe9fca3ae49e613b922f",
  "verifying_key": "ecbe5e2fbaaf23f14ca43f34d71ad733c57afcadeb5782809af48a3da17b9b69",
  "min_signers": 2
}
```

checkout `noot/frost-participant` (see
#1960)

build astria-cli, astria-bridge-withdrawer, astria-bridge-signer and
astria-sequencer.

run anvil (using this as fake rollup evm node for testing)

run astria-sequencer + cometbft as normal

transfer funds from funded address to withdrawer address

```bash
./target/debug/astria-cli sequencer transfer --amount 100000000 \
--private-key 2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90 \
--sequencer-url http://localhost:26657 \
--sequencer.chain-id astria astria1w2lxx9p02u7u934ljl060wannqm40g3y089lmh
```

initialize bridge account on astria, with
`astria1w2lxx9p02u7u934ljl060wannqm40g3y089lmh` being the withdrawer
address

```bash
./target/debug/astria-cli sequencer init-bridge-account \
--private-key 2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90 \
--withdrawer-address astria1w2lxx9p02u7u934ljl060wannqm40g3y089lmh \
--sequencer-url http://localhost:26657 \
--sequencer.chain-id astria \
--rollup-name test
```

result:

```bash
sending tx from address: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm
InitBridgeAccount completed!
Included in block: 1252
Rollup name: test
Rollup ID: n4bQgYhMfWWaL-qgxVrQFaO_TxsrC4Is0V1sFbDwCgg=
```

deploy contracts to anvil using `astria-bridge-contracts` 

- set `.env` and `source .env`

```bash
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
RPC_URL="http://localhost:8545"
BASE_CHAIN_ASSET_PRECISION=9
BASE_CHAIN_BRIDGE_ADDRESS="astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm"
BASE_CHAIN_ASSET_DENOMINATION="nria"
```

- `forge script script/AstriaWithdrawer.s.sol:AstriaWithdrawerScript
--rpc-url $RPC_URL --broadcast --sig "deploy()" -vvvv`
- contract deployed at `0x5FbDB2315678afecb367f032d93F642f64180aa3` ,
update `.env` again

run two astria-bridge-signers, one with grpc port `9001` and first
secret key package, second with grpc port `9002` and second secret key
package.

start the bridge withdrawer, configured for 2/2 threshold (make sure to
update pubkey package path):

```bash
ASTRIA_BRIDGE_WITHDRAWER_FROST_THRESHOLD_SIGNING_ENABLED=true
ASTRIA_BRIDGE_WITHDRAWER_FROST_MIN_SIGNERS=2
ASTRIA_BRIDGE_WITHDRAWER_FROST_PUBLIC_KEY_PACKAGE_PATH="pub_bridge.key"
ASTRIA_BRIDGE_WITHDRAWER_FROST_PARTICIPANT_ENDPOINTS="http://127.0.0.1:9001,http://127.0.0.1:9002"
ASTRIA_BRIDGE_WITHDRAWER_SEQUENCER_BRIDGE_ADDRESS="astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm"
ASTRIA_BRIDGE_WITHDRAWER_ETHEREUM_CONTRACT_ADDRESS="0x5FbDB2315678afecb367f032d93F642f64180aa3"

```

finally, send a withdrawal on the rollup:

```bash
forge script script/AstriaWithdrawer.s.sol:AstriaWithdrawerScript \
   --rpc-url $RPC_URL --broadcast --sig "withdrawToSequencer()" -vvvv
```

in the withdrawer, should see:

```bash
2025-02-10T17:33:57.417370Z  INFO process_batch: astria_bridge_withdrawer::bridge_withdrawer::submitter: withdraw batch successfully executed. sequencer.block=41 sequencer.tx_hash=69E3997A2A67CD4716D1051D9962219FBCEF9F3F7557C276D9F4D414D30166F2 rollup.height=4 batch.value=1000
```

## Changelogs
Changelogs updated.

## Related Issues

closes  #1937

---------

Co-authored-by: Richard Janis Goldschmidt <[email protected]>
Base automatically changed from noot/bridge-withdrawer to main March 20, 2025 20:18
@noot noot requested a review from a team as a code owner March 20, 2025 20:18
@github-actions github-actions bot added proto pertaining to the Astria Protobuf spec cd labels Mar 20, 2025
Copy link
Member

@SuperFluffy SuperFluffy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide a summary in the PR text on how this service should work? I.e. who is calling it and how? I have a rudimentary understanding of how the threshold signing works, but it would be great to have a summary as to what role this server plays and how (and in which order) its RPCs are called.

I would also ike to see a couple of points to be addressed by this PR listed below. I have to still go through the functionality of the inner service.

  1. follow the rest of our services in that a singleton BridgeSigner is instantiated, rather than everything being done in main.rs
  2. this would make it easier to also implement blackbox test - related to this, outlining how a blackbox test would look (together with a followup PR) would be great.
  3. move changes in astria-cli, astria-bridge-withdrawer, and astria-sequencer to separate PRs
  4. provide a changelog and entry in codeowners
  5. add tests to local.env.example to prevent it from degrading (similar to the other services)
  6. add the various env vars that are understood by the service but are not part of Config (NO_COLOR and the other OTEL related ones; see the other services).

@@ -0,0 +1,30 @@
# A list of filter directives of the form target[span{field=value}]=level.
ASTRIA_BRIDGE_SIGNER_LOG=astria_bridge_signer=info
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't prefix it with the target by default, just make this =info:

Suggested change
ASTRIA_BRIDGE_SIGNER_LOG=astria_bridge_signer=info
ASTRIA_BRIDGE_SIGNER_LOG=info

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is standard across our other components. Unsure of the implications here, but happy to change if necessary

/// # Errors
///
/// Returns an error if the provider cannot be created.
pub fn new(rollup_rpc_endpoint: String) -> eyre::Result<Self> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is part of the Server, then the server should instantiate it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ethanoroshiba ethanoroshiba force-pushed the noot/frost-participant branch from f09cad5 to ac5fcd8 Compare March 25, 2025 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cd proto pertaining to the Astria Protobuf spec sequencer pertaining to the astria-sequencer crate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support threshold signing in bridge withdrawer
4 participants