feat(bridge-signer): implement FROST threshold signer node#1960
feat(bridge-signer): implement FROST threshold signer node#1960
Conversation
…into noot/frost-participant
## Summary Fixes incorrect comments related to signing keys in the sequencer app test utils. ## Background These changes were moved from #1960 into a separate PR. ## Changes - Corrects which addresses signing keys correspond to in `sequencer::app::test_utils`. ## Testing No testing required. Co-authored-by: noot <elizabeth@astria.org>
## Summary Fixes various typos in bridge withdrawer. ## Background The changes were moved from #1960 into a separate PR. ## Changes - Changes various instances of "sequencing" to "sequencer". ## Testing No testing required. ## Changelogs No updates required. Co-authored-by: noot <elizabeth@astria.org>
) ## Summary Adds optional bridge withdrawer address argument to the init bridge account command. ## Background These changes were moved from #1960 into a separate PR. ## Changes - Added optional bridge withdrawer address argument to the init bridge account command. - Opened corresponding docs PR here: astriaorg/docs#123. ## Testing Manually tested. ## Changelogs Changelog updated --------- Co-authored-by: Richard Janis Goldschmidt <github@aberrat.io>
## Summary adds helm chart for the new bridge signer binary ## Changes - adds new chart for bridge ## Testing Tested locally by running the local cluster with `frostThresholdSigning: true`, and by following documentations in #1948. to deploy test threshold signers run `just deploy bridge-signers`. This deploy two signers with appropriate keys. A future task should consider adding a smoke test for the Frost signer using the Helm chart introduced in this PR. ## Changelogs No updates required. ## Breaking Changelist - changes bridge-withdrawer appName templating, no longer uses the global entry.
| Ok(metrics_and_guard) => metrics_and_guard, | ||
| }; | ||
|
|
||
| info!( |
There was a problem hiding this comment.
This is happening outside of root span (we probably have that issue in all our services). I suggest to create a freestanding, instrumented function like fn init_service, where you can emit the config and which returns the BridgeSigner.
| }; | ||
|
|
||
| if let Err(e) = bridge_signer.run_until_stopped().await { | ||
| eprintln!("bridge signer failed: {e}"); |
There was a problem hiding this comment.
| eprintln!("bridge signer failed: {e}"); | |
| eprintln!("bridge signer exited with error:\n{e}"); |
| @@ -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 | |||
There was a problem hiding this comment.
it is indeed standard, but it's wrong.
|
|
||
| build: | ||
| if: ${{ always() && !cancelled() }} | ||
| needs: [cli, composer, conductor, sequencer, relayer, bridge-withdrawer] |
| let sequencer_asset_str = contract | ||
| .base_chain_asset_denomination() | ||
| .await | ||
| .wrap_err("failed to get base chain asset denomination")?; |
There was a problem hiding this comment.
get base chain asset from where?
| .wrap_err("failed to get block")? | ||
| .ok_or_eyre("block not found")?; | ||
| let actions: Vec<Action> = getter | ||
| .get_for_block_hash(block.hash.ok_or_eyre("block hash is None")?) |
There was a problem hiding this comment.
block hash was empty or is not set
| let tx = provider | ||
| .get_transaction_receipt(tx_hash) | ||
| .await | ||
| .wrap_err("failed to get transaction")? |
There was a problem hiding this comment.
get the transaction from where?
| Ok(actions) | ||
| } | ||
|
|
||
| fn parse_rollup_withdrawal_event_id(event_id: &str) -> eyre::Result<(H256, usize)> { |
There was a problem hiding this comment.
Unclear what this is supposed to be parsing. Is this returning a withdrawal event ID? This looks more like a struct TxHashWithEventIndex { tx_hash: H256, index: usize, }.
We should also type the errors (enum) and impl FromStr.
| } | ||
|
|
||
| fn parse_rollup_withdrawal_event_id(event_id: &str) -> eyre::Result<(H256, usize)> { | ||
| let regex = regex::Regex::new(r"^(0x[0-9a-fA-F]+).(0x[0-9a-fA-F]+)$") |
There was a problem hiding this comment.
Should be put into a LazyCell or OnceCell.
|
This PR is stale because it has been open 45 days with no activity. Remove stale label or this PR will be |
|
This PR was closed because it has been stale. |
## 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 astriaorg/astria#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 <github@aberrat.io>
## 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 astriaorg/astria#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 <github@aberrat.io>
Summary
implement FROST threshold signer node which implements the
FrostParticipantServicegRPC 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
FrostParticipantServicegRPC serviceTesting
tested with the withdrawer in #1948; see https://www.notion.so/astria-org/bridge-threshold-withdrawer-testing-1936bd31a90c803ab33ccc1221f545a1?pvs=4
Changelogs
Changelog created.
Related Issues
closes #1937