From 49313fa37826d20451483586077e77b79bd49b56 Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Wed, 11 Mar 2026 18:12:00 +0800 Subject: [PATCH 1/7] target chain from event.caip2_id instead of dest --- chain-signatures/crypto/src/kdf.rs | 69 +++++++++++++++---- .../node/src/protocol/signature.rs | 2 +- chain-signatures/node/src/rpc.rs | 4 ++ chain-signatures/node/src/stream/ops.rs | 18 +++-- chain-signatures/primitives/src/lib.rs | 27 +++++++- 5 files changed, 96 insertions(+), 24 deletions(-) diff --git a/chain-signatures/crypto/src/kdf.rs b/chain-signatures/crypto/src/kdf.rs index 0cc2da2a6..d3efaddff 100644 --- a/chain-signatures/crypto/src/kdf.rs +++ b/chain-signatures/crypto/src/kdf.rs @@ -13,7 +13,7 @@ use sha3::{Digest, Keccak256, Sha3_256}; const EPSILON_DERIVATION_PREFIX_V1: &str = "sig.network v1.0.0 epsilon derivation"; const EPSILON_DERIVATION_PREFIX_V2: &str = "sig.network v2.0.0 epsilon derivation"; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Chain { Near, Ethereum, @@ -22,25 +22,64 @@ pub enum Chain { Hydration, } +#[derive(Debug, Clone, Copy)] +struct ChainMeta { + deprecated_chain_id: &'static str, + caip2_chain_id: &'static str, +} + impl Chain { - pub fn deprecated_chain_id(&self) -> &str { + const ALL: [Chain; 5] = [ + Chain::Near, + Chain::Ethereum, + Chain::Solana, + Chain::Bitcoin, + Chain::Hydration, + ]; + + const fn meta(self) -> ChainMeta { match self { - Chain::Near => "0x18d", - Chain::Ethereum => "0x1", - Chain::Solana => "0x800001f5", - Chain::Bitcoin => "bip122:000000000019d6689c085ae165831e93", - Chain::Hydration => "polkadot:2034", + Chain::Near => ChainMeta { + deprecated_chain_id: "0x18d", + caip2_chain_id: "near:mainnet", + }, + Chain::Ethereum => ChainMeta { + deprecated_chain_id: "0x1", + caip2_chain_id: "eip155:1", + }, + Chain::Solana => ChainMeta { + deprecated_chain_id: "0x800001f5", + caip2_chain_id: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + }, + Chain::Bitcoin => ChainMeta { + deprecated_chain_id: "bip122:000000000019d6689c085ae165831e93", + caip2_chain_id: "bip122:000000000019d6689c085ae165831e93", + }, + Chain::Hydration => ChainMeta { + deprecated_chain_id: "polkadot:2034", + caip2_chain_id: "polkadot:2034", + }, } } - pub fn caip2_chain_id(&self) -> &str { - match self { - Chain::Near => "near:mainnet", - Chain::Ethereum => "eip155:1", - Chain::Solana => "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", - Chain::Bitcoin => "bip122:000000000019d6689c085ae165831e93", - Chain::Hydration => "polkadot:2034", - } + pub const fn deprecated_chain_id(self) -> &'static str { + self.meta().deprecated_chain_id + } + + pub const fn caip2_chain_id(self) -> &'static str { + self.meta().caip2_chain_id + } + + pub fn from_caip2_chain_id(chain_id: &str) -> Option { + Self::ALL + .into_iter() + .find(|chain| chain.caip2_chain_id() == chain_id) + } + + pub fn from_deprecated_chain_id(chain_id: &str) -> Option { + Self::ALL + .into_iter() + .find(|chain| chain.deprecated_chain_id() == chain_id) } } diff --git a/chain-signatures/node/src/protocol/signature.rs b/chain-signatures/node/src/protocol/signature.rs index dfcac4e02..d5fda47c2 100644 --- a/chain-signatures/node/src/protocol/signature.rs +++ b/chain-signatures/node/src/protocol/signature.rs @@ -992,7 +992,7 @@ impl SignGenerator { tracing::info!( ?sign_id, source_chain = ?self.indexed.chain, - target_chain = ?event.dest(), + target_chain = ?event.target_chain(), "generated signature for bidirectional request, awaiting indexer to process" ); } diff --git a/chain-signatures/node/src/rpc.rs b/chain-signatures/node/src/rpc.rs index f3a63bf6f..c78c55c58 100644 --- a/chain-signatures/node/src/rpc.rs +++ b/chain-signatures/node/src/rpc.rs @@ -418,6 +418,9 @@ impl RpcExecutor { tracing::error!(%err, "eth: failed to send publish action"); } } + Chain::Bitcoin => { + tracing::error!("bitcoin is currently not supported"); + } } }); } @@ -448,6 +451,7 @@ impl RpcExecutor { ChainClient::Err("no hydration client available for node") } } + Chain::Bitcoin => ChainClient::Err("bitcoin is currently not supported"), } } } diff --git a/chain-signatures/node/src/stream/ops.rs b/chain-signatures/node/src/stream/ops.rs index 967fe8f45..f811e7d59 100644 --- a/chain-signatures/node/src/stream/ops.rs +++ b/chain-signatures/node/src/stream/ops.rs @@ -15,7 +15,6 @@ use crate::stream::ExecutionOutcome; use anchor_lang::prelude::Pubkey; use k256::Scalar; use mpc_primitives::{SignId, Signature}; -use std::str::FromStr; use std::time::Instant; use tokio::sync::{mpsc, watch}; @@ -128,6 +127,15 @@ impl SignBidirectionalEvent { )), } } + + pub fn target_chain(&self) -> anyhow::Result { + let event_caip2_id = self.caip2_id(); + Chain::from_caip2_chain_id(&event_caip2_id).ok_or_else(|| { + anyhow::anyhow!( + "unable to parse target chain from event caip2_id field: {event_caip2_id}" + ) + }) + } } pub enum RespondBidirectionalEvent { @@ -417,12 +425,8 @@ pub(crate) async fn process_respond_event( }; tracing::info!(?sign_id, "bidirectional processing initial respond event"); - let target_chain = Chain::from_str(&event.dest()) - .map_err(|err| anyhow::anyhow!("unable to parse target chain from dest: {err:?}")); - let target_chain = match target_chain { - Ok(chain) => chain, - Err(_) => Chain::Ethereum, - }; + + let target_chain = event.target_chain()?; let Some(BacklogTransaction::Sign(_)) = backlog.get(source_chain, &sign_id).await else { anyhow::bail!("bidirectional tx not found for advancement: {sign_id:?}"); diff --git a/chain-signatures/primitives/src/lib.rs b/chain-signatures/primitives/src/lib.rs index 0a0b8660a..03de40b7e 100644 --- a/chain-signatures/primitives/src/lib.rs +++ b/chain-signatures/primitives/src/lib.rs @@ -126,6 +126,7 @@ pub enum Chain { Ethereum, Solana, Hydration, + Bitcoin, } impl Chain { @@ -135,15 +136,17 @@ impl Chain { Chain::Ethereum => "Ethereum", Chain::Solana => "Solana", Chain::Hydration => "Hydration", + Chain::Bitcoin => "Bitcoin", } } - pub const fn iter() -> [Chain; 4] { + pub const fn iter() -> [Chain; 5] { [ Chain::NEAR, Chain::Ethereum, Chain::Solana, Chain::Hydration, + Chain::Bitcoin, ] } @@ -153,6 +156,7 @@ impl Chain { Chain::Ethereum => ("CHECKPOINT_INTERVAL_ETHEREUM", 20), Chain::Solana => ("CHECKPOINT_INTERVAL_SOLANA", 120), Chain::Hydration => ("CHECKPOINT_INTERVAL_HYDRATION", 240), + Chain::Bitcoin => ("CHECKPOINT_INTERVAL_BITCOIN", 600), }; let interval = std::env::var(key) @@ -167,6 +171,7 @@ impl Chain { ("CHECKPOINT_INTERVAL_ETHEREUM", "2"), ("CHECKPOINT_INTERVAL_SOLANA", "5"), ("CHECKPOINT_INTERVAL_HYDRATION", "5"), + ("CHECKPOINT_INTERVAL_BITCOIN", "10"), ] } @@ -176,12 +181,31 @@ impl Chain { Chain::Ethereum => 15 * 60, Chain::Solana => 3, Chain::Hydration => 12, + Chain::Bitcoin => 600, } } pub fn expected_response_time_secs(&self) -> u64 { self.expected_finality_time_secs() + 60 } + + pub fn from_caip2_chain_id(caip2_chain_id: &str) -> Option { + mpc_crypto::kdf::Chain::from_caip2_chain_id(caip2_chain_id).map(Self::from_chain_crypto) + } + + pub fn from_deprecated_chain_id(chain_id: &str) -> Option { + mpc_crypto::kdf::Chain::from_deprecated_chain_id(chain_id).map(Self::from_chain_crypto) + } + + fn from_chain_crypto(chain_crypto: mpc_crypto::kdf::Chain) -> Self { + match chain_crypto { + mpc_crypto::kdf::Chain::Near => Chain::NEAR, + mpc_crypto::kdf::Chain::Ethereum => Chain::Ethereum, + mpc_crypto::kdf::Chain::Solana => Chain::Solana, + mpc_crypto::kdf::Chain::Hydration => Chain::Hydration, + mpc_crypto::kdf::Chain::Bitcoin => Chain::Bitcoin, + } + } } impl fmt::Display for Chain { @@ -199,6 +223,7 @@ impl FromStr for Chain { "ethereum" | "eth" => Ok(Chain::Ethereum), "solana" | "sol" => Ok(Chain::Solana), "hydration" | "hyd" => Ok(Chain::Hydration), + "bitcoin" | "btc" => Ok(Chain::Bitcoin), other => Err(format!("unknown or unsupported chain {other}")), } } From 61453632fa58f2c89b8f7b2df1e705e5e3f6627c Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Wed, 11 Mar 2026 19:01:18 +0800 Subject: [PATCH 2/7] make sure bidirectional requests with invalid target chain won't be processed --- .../node/src/protocol/signature.rs | 2 +- chain-signatures/node/src/stream/ops.rs | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/chain-signatures/node/src/protocol/signature.rs b/chain-signatures/node/src/protocol/signature.rs index d5fda47c2..d5f267390 100644 --- a/chain-signatures/node/src/protocol/signature.rs +++ b/chain-signatures/node/src/protocol/signature.rs @@ -992,7 +992,7 @@ impl SignGenerator { tracing::info!( ?sign_id, source_chain = ?self.indexed.chain, - target_chain = ?event.target_chain(), + target_chain = ?event.target_chain().unwrap(), "generated signature for bidirectional request, awaiting indexer to process" ); } diff --git a/chain-signatures/node/src/stream/ops.rs b/chain-signatures/node/src/stream/ops.rs index f811e7d59..f4837926f 100644 --- a/chain-signatures/node/src/stream/ops.rs +++ b/chain-signatures/node/src/stream/ops.rs @@ -260,7 +260,8 @@ pub(crate) async fn process_sign_event( args: sign_request.args.clone(), unix_timestamp_indexed: sign_request.unix_timestamp_indexed, }), - SignRequestType::SignBidirectional(_event) => { + SignRequestType::SignBidirectional(event) => { + event.target_chain()?; // For bidirectional requests, start with a Sign transaction // The protocol will advance it to Bidirectional after generating the signature BacklogTransaction::Sign(SignTx { @@ -310,13 +311,16 @@ pub(crate) async fn process_sign_request( args: sign_request.args.clone(), unix_timestamp_indexed: sign_request.unix_timestamp_indexed, }), - SignRequestType::SignBidirectional(_event) => BacklogTransaction::Sign(SignTx { - request_id: sign_id.request_id, - source_chain: sign_request.chain, - status: PendingRequestStatus::AwaitingResponse, - args: sign_request.args.clone(), - unix_timestamp_indexed: sign_request.unix_timestamp_indexed, - }), + SignRequestType::SignBidirectional(event) => { + event.target_chain()?; + BacklogTransaction::Sign(SignTx { + request_id: sign_id.request_id, + source_chain: sign_request.chain, + status: PendingRequestStatus::AwaitingResponse, + args: sign_request.args.clone(), + unix_timestamp_indexed: sign_request.unix_timestamp_indexed, + }) + } _ => anyhow::bail!("Unexpected sign request type"), }; From 706c3708ae87ef8fd6d89d341767c2300bd72ad5 Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Wed, 11 Mar 2026 19:03:55 +0800 Subject: [PATCH 3/7] remove unnecessary change --- chain-signatures/crypto/src/kdf.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/chain-signatures/crypto/src/kdf.rs b/chain-signatures/crypto/src/kdf.rs index d3efaddff..d4d5ea9a7 100644 --- a/chain-signatures/crypto/src/kdf.rs +++ b/chain-signatures/crypto/src/kdf.rs @@ -13,7 +13,7 @@ use sha3::{Digest, Keccak256, Sha3_256}; const EPSILON_DERIVATION_PREFIX_V1: &str = "sig.network v1.0.0 epsilon derivation"; const EPSILON_DERIVATION_PREFIX_V2: &str = "sig.network v2.0.0 epsilon derivation"; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy)] pub enum Chain { Near, Ethereum, @@ -22,7 +22,6 @@ pub enum Chain { Hydration, } -#[derive(Debug, Clone, Copy)] struct ChainMeta { deprecated_chain_id: &'static str, caip2_chain_id: &'static str, From 3981e2f255be9c362e029cd62cacff33d05afe79 Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Wed, 11 Mar 2026 19:07:51 +0800 Subject: [PATCH 4/7] no unwrap --- chain-signatures/node/src/protocol/signature.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain-signatures/node/src/protocol/signature.rs b/chain-signatures/node/src/protocol/signature.rs index d5f267390..801530c46 100644 --- a/chain-signatures/node/src/protocol/signature.rs +++ b/chain-signatures/node/src/protocol/signature.rs @@ -992,7 +992,7 @@ impl SignGenerator { tracing::info!( ?sign_id, source_chain = ?self.indexed.chain, - target_chain = ?event.target_chain().unwrap(), + target_chain = ?event.target_chain().ok(), "generated signature for bidirectional request, awaiting indexer to process" ); } From 8e57bd8b8fcaaed51fd5341a875be5d4b504e4d7 Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Wed, 11 Mar 2026 19:17:41 +0800 Subject: [PATCH 5/7] update bitcoin defaults --- chain-signatures/primitives/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/chain-signatures/primitives/src/lib.rs b/chain-signatures/primitives/src/lib.rs index 03de40b7e..21caa3b01 100644 --- a/chain-signatures/primitives/src/lib.rs +++ b/chain-signatures/primitives/src/lib.rs @@ -156,7 +156,7 @@ impl Chain { Chain::Ethereum => ("CHECKPOINT_INTERVAL_ETHEREUM", 20), Chain::Solana => ("CHECKPOINT_INTERVAL_SOLANA", 120), Chain::Hydration => ("CHECKPOINT_INTERVAL_HYDRATION", 240), - Chain::Bitcoin => ("CHECKPOINT_INTERVAL_BITCOIN", 600), + Chain::Bitcoin => return None, }; let interval = std::env::var(key) @@ -171,7 +171,6 @@ impl Chain { ("CHECKPOINT_INTERVAL_ETHEREUM", "2"), ("CHECKPOINT_INTERVAL_SOLANA", "5"), ("CHECKPOINT_INTERVAL_HYDRATION", "5"), - ("CHECKPOINT_INTERVAL_BITCOIN", "10"), ] } @@ -181,7 +180,7 @@ impl Chain { Chain::Ethereum => 15 * 60, Chain::Solana => 3, Chain::Hydration => 12, - Chain::Bitcoin => 600, + Chain::Bitcoin => 60 * 60, } } From a9a5903cc9440880bd899fc63edf374319e3aae4 Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Tue, 17 Mar 2026 17:34:49 +0800 Subject: [PATCH 6/7] merge develop and address comments --- Cargo.lock | 1 + chain-signatures/contract-sol/src/lib.rs | 42 +++++++++++++++++++ .../node/src/indexer_hydration.rs | 38 +++++++++++++++++ chain-signatures/node/src/indexer_sol.rs | 8 +++- .../node/src/protocol/signature.rs | 2 +- chain-signatures/node/src/stream/ops.rs | 32 ++++++-------- chain-signatures/primitives/Cargo.toml | 1 + chain-signatures/primitives/src/lib.rs | 14 ++++++- 8 files changed, 114 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38c8a8276..7fab47a02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8055,6 +8055,7 @@ dependencies = [ "serde_bytes", "serde_json", "sha3", + "thiserror 1.0.69", ] [[package]] diff --git a/chain-signatures/contract-sol/src/lib.rs b/chain-signatures/contract-sol/src/lib.rs index 26706e5bf..52300372e 100644 --- a/chain-signatures/contract-sol/src/lib.rs +++ b/chain-signatures/contract-sol/src/lib.rs @@ -218,19 +218,61 @@ pub struct SignatureRequestedEvent { pub fee_payer: Option, } +/// Event emitted when a bidirectional signing request is initiated +/// via the `sign_bidirectional` instruction on the Solana program. #[event] #[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub struct SignBidirectionalEvent { + /// The public key of the sender initiating the request. pub sender: Pubkey, + + /// The serialized transaction payload to be signed. pub serialized_transaction: Vec, + + /// CAIP-2 chain ID of the *target chain* where the signed transaction will be sent. + /// + /// Note: This is NOT the chain where `respond()` or `respond_bidirectional()` is executed. pub caip2_id: String, + + /// Version of the key to be used for signing. pub key_version: u32, + + /// Deposit associated with the request. pub deposit: u64, + + /// Derivation path used for signing. pub path: String, + + /// Signing algorithm identifier. + /// + /// If empty (`""`), ECDSA will be used by default. pub algo: String, + + /// Destination field (currently unused). + /// + /// Should be left empty (`""`). pub dest: String, + + /// Additional parameters encoded as a string (currently unused). + /// + /// Should be left empty (`""`). pub params: String, + + /// The program ID of the Solana program that emitted this event. + /// + /// Used by off-chain services (e.g., relayers, indexers) to filter and + /// verify events from the correct program. + /// + /// MUST be provided and MUST match the deployed program ID. pub program_id: Pubkey, + + /// Schema used to deserialize the output of the signed transaction. + /// + /// MUST be provided. pub output_deserialization_schema: Vec, + + /// Schema used to serialize the `respond_bidirectional` payload. + /// + /// MUST be provided. pub respond_serialization_schema: Vec, } diff --git a/chain-signatures/node/src/indexer_hydration.rs b/chain-signatures/node/src/indexer_hydration.rs index 95adabeea..c6b1edce0 100644 --- a/chain-signatures/node/src/indexer_hydration.rs +++ b/chain-signatures/node/src/indexer_hydration.rs @@ -184,18 +184,53 @@ impl SignatureEvent for HydrationSignatureRequestedEvent { } } +/// The deserialized representation of a bidirectional signing request +/// event emitted from the Hydration chain. #[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub struct HydrationSignBidirectionalRequestedEvent { + /// The 32-byte identifier of the sender. pub sender: [u8; 32], + + /// The serialized transaction payload to be signed. pub serialized_transaction: Vec, + + /// CAIP-2 chain ID of the *target chain* where the signed transaction will be sent. + /// + /// Note: This is NOT the chain where `respond()` or `respond_bidirectional()` is executed. pub caip2_id: String, + + /// Version of the key to be used for signing. pub key_version: u32, + + /// Deposit associated with the request. pub deposit: u64, + + /// Derivation path used for signing. pub path: String, + + /// Signing algorithm identifier. + /// + /// If empty (`""`), ECDSA will be used by default. pub algo: String, + + /// Destination field (currently unused). + /// + /// Should be left empty (`""`). pub dest: String, + + /// Additional parameters encoded as a string (currently unused). + /// + /// Should be left empty (`""`). pub params: String, + + /// Schema used to deserialize the output of the signed transaction. + /// + /// MUST be provided. pub output_deserialization_schema: Vec, + + /// Schema used to serialize the `respond_bidirectional` payload. + /// + /// MUST be provided. pub respond_serialization_schema: Vec, } @@ -664,6 +699,9 @@ fn decode_sign_bidirectional_requested( let output_deserialization_schema = get_named_vec_u8(&fields, "output_deserialization_schema")?; let respond_serialization_schema = get_named_vec_u8(&fields, "respond_serialization_schema")?; + Chain::from_caip2_chain_id(&caip2_id) + .map_err(|e| anyhow!("invalid caip2 chain id in sign bidirectional event: {e:?}"))?; + Ok(HydrationSignBidirectionalRequestedEvent { sender, serialized_transaction, diff --git a/chain-signatures/node/src/indexer_sol.rs b/chain-signatures/node/src/indexer_sol.rs index 9413c9d1d..45cd48e8b 100644 --- a/chain-signatures/node/src/indexer_sol.rs +++ b/chain-signatures/node/src/indexer_sol.rs @@ -568,7 +568,13 @@ fn parse_cpi_events( } } else if event_discriminator == SignBidirectionalEvent::DISCRIMINATOR { match ::deserialize(&mut &event_data[..]) { - Ok(ev) => acc.push(Box::new(ev) as SignatureEventBox), + Ok(ev) => { + if let Err(e) = Chain::from_caip2_chain_id(&ev.caip2_id) { + tracing::warn!("invalid caip2 chain id in sign bidirectional event: {e:?}") + } else { + acc.push(Box::new(ev) as SignatureEventBox) + } + } Err(e) => { tracing::warn!("Failed to deserialize SignBidirectionalEvent: {e}") } diff --git a/chain-signatures/node/src/protocol/signature.rs b/chain-signatures/node/src/protocol/signature.rs index df9b98f1d..99d10b889 100644 --- a/chain-signatures/node/src/protocol/signature.rs +++ b/chain-signatures/node/src/protocol/signature.rs @@ -1000,7 +1000,7 @@ impl SignGenerator { tracing::info!( ?sign_id, source_chain = ?self.indexed.chain, - target_chain = ?event.target_chain().ok(), + target_chain = ?event.target_chain(), "generated signature for bidirectional request, awaiting indexer to process" ); } diff --git a/chain-signatures/node/src/stream/ops.rs b/chain-signatures/node/src/stream/ops.rs index 36a86a60e..adcc14c9a 100644 --- a/chain-signatures/node/src/stream/ops.rs +++ b/chain-signatures/node/src/stream/ops.rs @@ -128,13 +128,9 @@ impl SignBidirectionalEvent { } } - pub fn target_chain(&self) -> anyhow::Result { - let event_caip2_id = self.caip2_id(); - Chain::from_caip2_chain_id(&event_caip2_id).ok_or_else(|| { - anyhow::anyhow!( - "unable to parse target chain from event caip2_id field: {event_caip2_id}" - ) - }) + pub fn target_chain(&self) -> Chain { + // we can directly unwrap because we've checked that the chain id is valid during event deserialization in the indexer + Chain::from_caip2_chain_id(&self.caip2_id()).unwrap() } } @@ -260,8 +256,7 @@ pub(crate) async fn process_sign_event( args: sign_request.args.clone(), unix_timestamp_indexed: sign_request.unix_timestamp_indexed, }), - SignRequestType::SignBidirectional(event) => { - event.target_chain()?; + SignRequestType::SignBidirectional(_) => { // For bidirectional requests, start with a Sign transaction // The protocol will advance it to Bidirectional after generating the signature BacklogTransaction::Sign(SignTx { @@ -311,16 +306,13 @@ pub(crate) async fn process_sign_request( args: sign_request.args.clone(), unix_timestamp_indexed: sign_request.unix_timestamp_indexed, }), - SignRequestType::SignBidirectional(event) => { - event.target_chain()?; - BacklogTransaction::Sign(SignTx { - request_id: sign_id.request_id, - source_chain: sign_request.chain, - status: PendingRequestStatus::AwaitingResponse, - args: sign_request.args.clone(), - unix_timestamp_indexed: sign_request.unix_timestamp_indexed, - }) - } + SignRequestType::SignBidirectional(_) => BacklogTransaction::Sign(SignTx { + request_id: sign_id.request_id, + source_chain: sign_request.chain, + status: PendingRequestStatus::AwaitingResponse, + args: sign_request.args.clone(), + unix_timestamp_indexed: sign_request.unix_timestamp_indexed, + }), _ => anyhow::bail!("Unexpected sign request type"), }; @@ -431,7 +423,7 @@ pub(crate) async fn process_respond_event( tracing::info!(?sign_id, "bidirectional processing initial respond event"); - let target_chain = event.target_chain()?; + let target_chain = event.target_chain(); if !matches!(entry.tx, BacklogTransaction::Sign(_)) { tracing::info!( diff --git a/chain-signatures/primitives/Cargo.toml b/chain-signatures/primitives/Cargo.toml index 918b5f1e0..56f462feb 100644 --- a/chain-signatures/primitives/Cargo.toml +++ b/chain-signatures/primitives/Cargo.toml @@ -12,6 +12,7 @@ near-sdk.workspace = true serde.workspace = true serde_bytes.workspace = true sha3.workspace = true +thiserror.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.12", features = ["custom"] } diff --git a/chain-signatures/primitives/src/lib.rs b/chain-signatures/primitives/src/lib.rs index 6949e966f..83c5b6a6c 100644 --- a/chain-signatures/primitives/src/lib.rs +++ b/chain-signatures/primitives/src/lib.rs @@ -155,6 +155,14 @@ pub enum Chain { Hydration, } +#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] +pub enum ChainFromError { + #[error("unknown CAIP-2 chain ID: {0}")] + UnknownCaip2Id(String), + #[error("unknown deprecated chain ID: {0}")] + UnknownDeprecatedId(String), +} + impl Chain { pub const fn as_str(&self) -> &'static str { match self { @@ -233,16 +241,18 @@ impl Chain { self.expected_finality_time_secs() + 60 } - pub fn from_caip2_chain_id(chain_id: &str) -> Option { + pub fn from_caip2_chain_id(chain_id: &str) -> Result { Self::iter() .into_iter() .find(|chain| chain.caip2_chain_id() == chain_id) + .ok_or_else(|| ChainFromError::UnknownCaip2Id(chain_id.to_string())) } - pub fn from_deprecated_chain_id(chain_id: &str) -> Option { + pub fn from_deprecated_chain_id(chain_id: &str) -> Result { Self::iter() .into_iter() .find(|chain| chain.deprecated_chain_id() == chain_id) + .ok_or_else(|| ChainFromError::UnknownDeprecatedId(chain_id.to_string())) } } From b4c34675e12943bbf812f314e49ebeffdd3c6cee Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Wed, 18 Mar 2026 10:28:22 +0800 Subject: [PATCH 7/7] address comments --- chain-signatures/contract-sol/src/lib.rs | 9 ++------- chain-signatures/node/src/protocol/signature.rs | 2 +- chain-signatures/node/src/stream/ops.rs | 8 +++++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/chain-signatures/contract-sol/src/lib.rs b/chain-signatures/contract-sol/src/lib.rs index 52300372e..3426640be 100644 --- a/chain-signatures/contract-sol/src/lib.rs +++ b/chain-signatures/contract-sol/src/lib.rs @@ -260,19 +260,14 @@ pub struct SignBidirectionalEvent { /// The program ID of the Solana program that emitted this event. /// - /// Used by off-chain services (e.g., relayers, indexers) to filter and - /// verify events from the correct program. + /// Used by MPC service to filter and verify events from the correct program. /// - /// MUST be provided and MUST match the deployed program ID. + /// MUST match the deployed program ID. pub program_id: Pubkey, /// Schema used to deserialize the output of the signed transaction. - /// - /// MUST be provided. pub output_deserialization_schema: Vec, /// Schema used to serialize the `respond_bidirectional` payload. - /// - /// MUST be provided. pub respond_serialization_schema: Vec, } diff --git a/chain-signatures/node/src/protocol/signature.rs b/chain-signatures/node/src/protocol/signature.rs index 99d10b889..df9b98f1d 100644 --- a/chain-signatures/node/src/protocol/signature.rs +++ b/chain-signatures/node/src/protocol/signature.rs @@ -1000,7 +1000,7 @@ impl SignGenerator { tracing::info!( ?sign_id, source_chain = ?self.indexed.chain, - target_chain = ?event.target_chain(), + target_chain = ?event.target_chain().ok(), "generated signature for bidirectional request, awaiting indexer to process" ); } diff --git a/chain-signatures/node/src/stream/ops.rs b/chain-signatures/node/src/stream/ops.rs index adcc14c9a..47e886f5c 100644 --- a/chain-signatures/node/src/stream/ops.rs +++ b/chain-signatures/node/src/stream/ops.rs @@ -128,9 +128,9 @@ impl SignBidirectionalEvent { } } - pub fn target_chain(&self) -> Chain { + pub fn target_chain(&self) -> Result { // we can directly unwrap because we've checked that the chain id is valid during event deserialization in the indexer - Chain::from_caip2_chain_id(&self.caip2_id()).unwrap() + Chain::from_caip2_chain_id(&self.caip2_id()) } } @@ -423,7 +423,9 @@ pub(crate) async fn process_respond_event( tracing::info!(?sign_id, "bidirectional processing initial respond event"); - let target_chain = event.target_chain(); + let target_chain = event.target_chain().map_err(|err| { + anyhow::anyhow!("failed to process respond event: {err:?} for sign id: {sign_id:?}") + })?; if !matches!(entry.tx, BacklogTransaction::Sign(_)) { tracing::info!(