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
866 changes: 37 additions & 829 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ tracing-subscriber = { version = "0.3.20", default-features = false, features =
"fmt",
] }
url = { version = "2.4.0", features = ["serde"] }
ethabi = "18.0.0"

anchor-client = { version = "0.31.0", features = ["async"] }
anchor-lang = "0.31.0"
solana-sdk = "2.2.2"
Expand Down
4 changes: 2 additions & 2 deletions chain-signatures/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async-trait = "0.1"
axum = "0.7.9"
axum-extra = "0.9.6"
chrono = "0.4.24"
google-datastore1 = "=5.0.4"

google-secretmanager1 = "5"
hkdf = "0.12.4"
highway = "1.1.0"
Expand Down Expand Up @@ -71,7 +71,7 @@ tokio.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
url.workspace = true
ethabi.workspace = true

anchor-client.workspace = true
anchor-lang.workspace = true
solana-sdk.workspace = true
Expand Down
11 changes: 1 addition & 10 deletions chain-signatures/node/src/gcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ pub mod error;

use crate::storage;

use google_datastore1::api::Key;
use google_datastore1::oauth2::AccessTokenAuthenticator;
use google_secretmanager1::api::{AddSecretVersionRequest, SecretPayload};
use google_secretmanager1::oauth2::authenticator::ApplicationDefaultCredentialsTypes;
use google_secretmanager1::oauth2::AccessTokenAuthenticator;
use google_secretmanager1::oauth2::{
ApplicationDefaultCredentialsAuthenticator, ApplicationDefaultCredentialsFlowOpts,
};
Expand Down Expand Up @@ -71,14 +70,6 @@ impl SecretManagerService {
}
}

pub trait Keyable: KeyKind {
fn key(&self) -> Key;
}

pub trait KeyKind {
fn kind() -> String;
}

#[derive(Clone)]
pub struct GcpService {
pub project_id: String,
Expand Down
69 changes: 69 additions & 0 deletions chain-signatures/node/src/indexer_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use crate::rpc::ContractStateWatcher;
use crate::sign_bidirectional::BidirectionalTx;
use crate::sign_bidirectional::BidirectionalTxId;
use crate::sign_bidirectional::PendingRequestStatus;

use alloy::primitives::keccak256;
use anchor_lang::prelude::Pubkey;
use k256::Scalar;
use mpc_primitives::SignId;
Expand Down Expand Up @@ -451,3 +453,70 @@ pub(crate) fn sender_string(sender: [u8; 32], source_chain: Chain) -> anyhow::Re
_ => anyhow::bail!("Unsupported chain: {source_chain}"),
}
}

/// Encode exactly as the legacy ethabi version:
/// (string, bytes, string, uint256, string, string, string, string)
///
/// Alloy adds extra metadata that throws off the previous ethabi encoding
/// so this implementation manually encodes the data to match the previous
/// ethabi without importing it.
#[allow(clippy::too_many_arguments)]
pub fn ethabi_request_id(
sender: String,
payload: [u8; 32],
path: String,
key_version: u32,
chain_id: String,
algo: String,
dest: String,
params: String,
) -> [u8; 32] {
const HEAD_WORDS: usize = 8;
const WORD_SIZE: usize = 32;

let head_size = HEAD_WORDS * WORD_SIZE;
let mut heads: Vec<[u8; WORD_SIZE]> = Vec::with_capacity(HEAD_WORDS);
let mut tails: Vec<u8> = Vec::new();

fn u256_word(value: u64) -> [u8; WORD_SIZE] {
let mut word = [0u8; WORD_SIZE];
word[WORD_SIZE - 8..].copy_from_slice(&value.to_be_bytes());
word
}

fn push_dynamic(
heads: &mut Vec<[u8; WORD_SIZE]>,
tails: &mut Vec<u8>,
head_size: usize,
bytes: &[u8],
) {
let offset = head_size + tails.len();
heads.push(u256_word(offset as u64));
tails.extend_from_slice(&u256_word(bytes.len() as u64));
tails.extend_from_slice(bytes);
let padding = (WORD_SIZE - (bytes.len() % WORD_SIZE)) % WORD_SIZE;
tails.extend(std::iter::repeat_n(0u8, padding));
}

push_dynamic(
&mut heads,
&mut tails,
head_size,
sender.to_string().as_bytes(),
);
push_dynamic(&mut heads, &mut tails, head_size, payload.as_slice());
push_dynamic(&mut heads, &mut tails, head_size, path.as_bytes());
heads.push(u256_word(key_version as u64));
push_dynamic(&mut heads, &mut tails, head_size, chain_id.as_bytes());
push_dynamic(&mut heads, &mut tails, head_size, algo.as_bytes());
push_dynamic(&mut heads, &mut tails, head_size, dest.as_bytes());
push_dynamic(&mut heads, &mut tails, head_size, params.as_bytes());

let mut encoded = Vec::with_capacity(head_size + tails.len());
for head in heads {
encoded.extend_from_slice(&head);
}
encoded.extend_from_slice(&tails);

*keccak256(encoded)
}
61 changes: 61 additions & 0 deletions chain-signatures/node/src/indexer_eth/abi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
alloy::sol! {
#[sol(rpc)]
contract ChainSignatures {
struct SignRequest {
bytes32 payload;
string path;
uint32 keyVersion;
string algo;
string dest;
string params;
}

struct AffinePoint {
uint256 x;
uint256 y;
}

struct Signature {
AffinePoint bigR;
uint256 s;
uint8 recoveryId;
}

function sign(SignRequest memory _request) external payable;
function getSignatureDeposit() external view returns (uint256);

event SignatureRequested(
address sender,
bytes32 payload,
uint32 keyVersion,
uint256 deposit,
uint256 chainId,
string path,
string algo,
string dest,
string params
);

event SignatureResponded(
bytes32 indexed requestId,
address responder,
Signature signature
);
}

event SignatureRequestedEncoding(
address sender,
bytes payload,
string path,
uint32 keyVersion,
uint256 chainId,
string algo,
string dest,
string params
);

struct ChainSignaturesConstructor {
address mpcNetwork;
uint256 signatureDeposit;
}
}
54 changes: 11 additions & 43 deletions chain-signatures/node/src/indexer_eth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
pub mod abi;
pub mod indexer_eth_direct_rpc;
pub mod indexer_eth_helios;

use crate::backlog::Backlog;
use crate::indexer_eth::abi::{ChainSignatures, SignatureRequestedEncoding};
use crate::mesh::MeshState;
use crate::node_client::NodeClient;
use crate::protocol::{Chain, IndexedSignRequest, Sign, SignRequestType};
use crate::respond_bidirectional::CompletedTx;
use crate::rpc::ContractStateWatcher;
use crate::sign_bidirectional::PendingRequestStatus;
use crate::storage::app_data_storage::AppDataStorage;

use alloy::eips::BlockNumberOrTag;
use alloy::primitives::hex::{self, ToHexExt};
use alloy::primitives::{Address, Bytes, U256};
use alloy::rpc::types::Log;
use alloy::sol_types::{sol, SolEvent};
use alloy::sol_types::SolEvent;
use k256::Scalar;
use mpc_crypto::{kdf::derive_epsilon_eth, ScalarExt as _};
use mpc_primitives::{SignArgs, SignId, LATEST_MPC_KEY_VERSION};
Expand Down Expand Up @@ -303,43 +306,6 @@ pub struct EthSignRequest {
pub key_version: u32,
}

sol! {
event SignatureRequested(
address sender,
bytes32 payload,
uint32 keyVersion,
uint256 deposit,
uint256 chainId,
string path,
string algo,
string dest,
string params
);

event SignatureRequestedEncoding(
address sender,
bytes payload,
string path,
uint32 keyVersion,
uint256 chainId,
string algo,
string dest,
string params
);

struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}

event SignatureResponded(
bytes32 indexed requestId,
address responder,
Signature signature
);
}

fn sign_request_from_filtered_log(log: Log, total_timeout: Duration) -> Option<IndexedSignRequest> {
let event = parse_event(&log);
tracing::debug!("found eth event: {:?}", event);
Expand Down Expand Up @@ -522,7 +488,7 @@ async fn process_respond_events(logs: &[Log], backlog: &Backlog, sign_tx: mpsc::
fn sign_id_from_signature_responded_log(log: &Log) -> Option<SignId> {
if log
.topic0()
.is_none_or(|topic| *topic != SignatureResponded::SIGNATURE_HASH)
.is_none_or(|topic| *topic != ChainSignatures::SignatureResponded::SIGNATURE_HASH)
{
return None;
}
Expand Down Expand Up @@ -977,8 +943,9 @@ impl EthereumIndexer {

let (respond_logs, potential_request_logs): (Vec<Log>, Vec<Log>) =
relevant_logs.into_iter().partition(|log| {
log.topic0()
.is_some_and(|topic| *topic == SignatureResponded::SIGNATURE_HASH)
log.topic0().is_some_and(|topic| {
*topic == ChainSignatures::SignatureResponded::SIGNATURE_HASH
})
});

if !respond_logs.is_empty() {
Expand All @@ -988,8 +955,9 @@ impl EthereumIndexer {
let request_logs: Vec<Log> = potential_request_logs
.into_iter()
.filter(|log| {
log.topic0()
.is_some_and(|topic| *topic == SignatureRequested::SIGNATURE_HASH)
log.topic0().is_some_and(|topic| {
*topic == ChainSignatures::SignatureRequested::SIGNATURE_HASH
})
})
.collect();

Expand Down
Loading
Loading