Skip to content
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
1 change: 1 addition & 0 deletions Cargo.lock

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

37 changes: 37 additions & 0 deletions chain-signatures/contract-sol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,19 +218,56 @@ pub struct SignatureRequestedEvent {
pub fee_payer: Option<Pubkey>,
}

/// 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<u8>,

/// 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 MPC service to filter and verify events from the correct program.
///
/// MUST match the deployed program ID.
Comment on lines +261 to +265
pub program_id: Pubkey,

/// Schema used to deserialize the output of the signed transaction.
pub output_deserialization_schema: Vec<u8>,

/// Schema used to serialize the `respond_bidirectional` payload.
pub respond_serialization_schema: Vec<u8>,
}
38 changes: 38 additions & 0 deletions chain-signatures/node/src/indexer_hydration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>,

/// 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<u8>,

/// Schema used to serialize the `respond_bidirectional` payload.
///
/// MUST be provided.
pub respond_serialization_schema: Vec<u8>,
}

Expand Down Expand Up @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion chain-signatures/node/src/indexer_sol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,13 @@ fn parse_cpi_events(
}
} else if event_discriminator == SignBidirectionalEvent::DISCRIMINATOR {
match <SignBidirectionalEvent as AnchorDeserialize>::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}")
}
Expand Down
2 changes: 1 addition & 1 deletion chain-signatures/node/src/protocol/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,7 +1000,7 @@ impl SignGenerator {
tracing::info!(
?sign_id,
source_chain = ?self.indexed.chain,
target_chain = ?event.dest(),
target_chain = ?event.target_chain().ok(),
"generated signature for bidirectional request, awaiting indexer to process"
);
}
Expand Down
17 changes: 12 additions & 5 deletions chain-signatures/node/src/stream/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -128,6 +127,11 @@ impl SignBidirectionalEvent {
)),
}
}

pub fn target_chain(&self) -> Result<Chain, mpc_primitives::ChainFromError> {
// 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())
}
Comment on lines +131 to +134
}

pub enum RespondBidirectionalEvent {
Expand Down Expand Up @@ -252,7 +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) => {
SignRequestType::SignBidirectional(_) => {
// For bidirectional requests, start with a Sign transaction
// The protocol will advance it to Bidirectional after generating the signature
BacklogTransaction::Sign(SignTx {
Expand Down Expand Up @@ -302,7 +306,7 @@ 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 {
SignRequestType::SignBidirectional(_) => BacklogTransaction::Sign(SignTx {
request_id: sign_id.request_id,
source_chain: sign_request.chain,
status: PendingRequestStatus::AwaitingResponse,
Expand Down Expand Up @@ -418,8 +422,11 @@ 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 = 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!(
?sign_id,
Expand Down
1 change: 1 addition & 0 deletions chain-signatures/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
22 changes: 22 additions & 0 deletions chain-signatures/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -232,6 +240,20 @@ impl Chain {
pub fn expected_response_time_secs(&self) -> u64 {
self.expected_finality_time_secs() + 60
}

pub fn from_caip2_chain_id(chain_id: &str) -> Result<Self, ChainFromError> {
Self::iter()
.into_iter()
.find(|chain| chain.caip2_chain_id() == chain_id)
.ok_or_else(|| ChainFromError::UnknownCaip2Id(chain_id.to_string()))
}
Comment on lines +244 to +249

pub fn from_deprecated_chain_id(chain_id: &str) -> Result<Self, ChainFromError> {
Self::iter()
.into_iter()
.find(|chain| chain.deprecated_chain_id() == chain_id)
.ok_or_else(|| ChainFromError::UnknownDeprecatedId(chain_id.to_string()))
}
Comment on lines +244 to +256
}

impl fmt::Display for Chain {
Expand Down
Loading