Skip to content

Commit

Permalink
basically compiles
Browse files Browse the repository at this point in the history
  • Loading branch information
ailisp committed Nov 18, 2024
1 parent 169eba3 commit 07a0616
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 68 deletions.
11 changes: 10 additions & 1 deletion chain-signatures/crypto-shared/src/kdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use k256::{
Scalar, Secp256k1, SecretKey,
};
use near_account_id::AccountId;
use sha3::{Digest, Sha3_256};
use sha3::{Digest, Keccak256, Sha3_256};

// Constant prefix that ensures epsilon derivation values are used specifically for
// near-mpc-recovery with key derivation protocol vX.Y.Z.
Expand All @@ -28,6 +28,15 @@ pub fn derive_epsilon(predecessor_id: &AccountId, path: &str) -> Scalar {
Scalar::from_non_biased(hash)
}

const EPSILON_DERIVATION_PREFIX_ETH: &str = "near-mpc-recovery v0.2.0 epsilon derivation:";
pub fn derive_epsilon_eth(requester: String, path: &str) -> Scalar {
let derivation_path = format!("{EPSILON_DERIVATION_PREFIX_ETH}{},{}", requester, path);
let mut hasher = Keccak256::new();
hasher.update(derivation_path);
let hash: [u8; 32] = hasher.finalize().into();
Scalar::from_non_biased(hash)
}

pub fn derive_key(public_key: PublicKey, epsilon: Scalar) -> PublicKey {
(<Secp256k1 as CurveArithmetic>::ProjectivePoint::GENERATOR * epsilon + public_key).to_affine()
}
Expand Down
14 changes: 7 additions & 7 deletions chain-signatures/node/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::config::{Config, LocalConfig, NetworkConfig, OverrideConfig};
use crate::gcp::GcpService;
use crate::protocol::{MpcSignProtocol, SignQueue};
use crate::{http_client, indexer, mesh, storage, web};
use crate::{http_client, indexer, indexer_eth, mesh, storage, web};
use clap::Parser;
use deadpool_redis::Runtime;
use local_ip_address::local_ip;
Expand Down Expand Up @@ -53,7 +53,7 @@ pub enum Cli {
indexer_options: indexer::Options,
/// Ethereum Indexer options
#[clap(flatten)]
eth_indexer_options: eth_indexer::Options,
indexer_eth_options: indexer_eth::Options,
/// Local address that other peers can use to message this node.
#[arg(long, env("MPC_LOCAL_ADDRESS"))]
my_address: Option<Url>,
Expand Down Expand Up @@ -86,6 +86,7 @@ impl Cli {
cipher_sk,
sign_sk,
indexer_options,
indexer_eth_options,
my_address,
storage_options,
override_config,
Expand Down Expand Up @@ -130,6 +131,7 @@ impl Cli {
}

args.extend(indexer_options.into_str_args());
args.extend(indexer_eth_options.into_str_args());
args.extend(storage_options.into_str_args());
args.extend(mesh_options.into_str_args());
args.extend(message_options.into_str_args());
Expand Down Expand Up @@ -185,7 +187,7 @@ pub fn run(cmd: Cli) -> anyhow::Result<()> {
cipher_sk,
sign_sk,
indexer_options,
eth_indexer_options,
indexer_eth_options,
my_address,
storage_options,
override_config,
Expand All @@ -207,10 +209,8 @@ pub fn run(cmd: Cli) -> anyhow::Result<()> {
&gcp_service,
&rt,
)?;
let eth_indexer_handle = indexer_eth::run(
&indexer_options,
&eth_indexer_options,
&mpc_contract_id,
let (eth_indexer_handle, eth_indexer) = indexer_eth::run(
&indexer_eth_options,
&account_id,
&sign_queue,
&gcp_service,
Expand Down
3 changes: 2 additions & 1 deletion chain-signatures/node/src/indexer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::gcp::error::DatastoreStorageError;
use crate::gcp::GcpService;
use crate::protocol::{SignQueue, SignRequest};
use crate::protocol::{Chain, SignQueue, SignRequest};
use crate::types::LatestBlockHeight;
use crypto_shared::{derive_epsilon, ScalarExt};
use k256::Scalar;
Expand Down Expand Up @@ -257,6 +257,7 @@ async fn handle_block(
entropy,
// TODO: use indexer timestamp instead.
time_added: Instant::now(),
chain: Chain::NEAR,
});
}
}
Expand Down
162 changes: 105 additions & 57 deletions chain-signatures/node/src/indexer_eth.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
use crate::gcp::error::DatastoreStorageError;
use crate::gcp::GcpService;
use crate::protocol::{SignQueue, SignRequest};
use crate::indexer::ContractSignRequest;
use crate::protocol::{Chain, SignQueue, SignRequest};
use crate::types::EthLatestBlockHeight;
use crypto_shared::kdf::derive_epsilon_eth;
use crypto_shared::{derive_epsilon, ScalarExt};
use ethers::prelude::*;
use k256::Scalar;
use near_account_id::AccountId;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
use web3::{
contract::{Contract, Options},
types::{BlockNumber, FilterBuilder, Log, H160, H256},
contract::Contract,
types::{BlockNumber, FilterBuilder, Log, H160, H256, U256},
Web3,
};

Expand Down Expand Up @@ -41,6 +44,25 @@ pub struct Options {
pub eth_running_threshold: u64,
}

impl Options {
pub fn into_str_args(self) -> Vec<String> {
let mut args = Vec::new();
args.extend([
"--eth-rpc-url".to_string(),
self.eth_rpc_url,
"--eth-contract-address".to_string(),
self.eth_contract_address,
"--eth-start-block".to_string(),
self.eth_start_block_height.to_string(),
"--eth-behind-threshold".to_string(),
self.eth_behind_threshold.to_string(),
"--eth-running-threshold".to_string(),
self.eth_running_threshold.to_string(),
]);
args
}
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct EthSignRequest {
pub payload: [u8; 32],
Expand All @@ -57,16 +79,16 @@ pub struct EthIndexer {
}

impl EthIndexer {
fn new(latest_block_height: LatestBlockHeight, options: &Options) -> Self {
fn new(latest_block_height: EthLatestBlockHeight, options: &Options) -> Self {
tracing::info!(
"creating new ethereum indexer, latest block height: {}",
latest_block_height.block_height
);
Self {
latest_block_height: Arc::new(RwLock::new(latest_block_height)),
last_updated_timestamp: Arc::new(RwLock::new(Instant::now())),
running_threshold: Duration::from_secs(options.running_threshold),
behind_threshold: Duration::from_secs(options.behind_threshold),
running_threshold: Duration::from_secs(options.eth_running_threshold),
behind_threshold: Duration::from_secs(options.eth_behind_threshold),
}
}

Expand Down Expand Up @@ -106,128 +128,152 @@ impl EthIndexer {
struct Context {
contract_address: H160,
web3: Web3<web3::transports::Http>,
contract: Contract<web3::transports::Http>,
gcp_service: GcpService,
queue: Arc<RwLock<SignQueue>>,
indexer: EthIndexer,
}

async fn handle_block(
block_number: u64,
ctx: &Context,
) -> anyhow::Result<()> {
async fn handle_block(block_number: u64, ctx: &Context) -> anyhow::Result<()> {
tracing::debug!(block_height = block_number, "handle eth block");

// Create filter for the specific block and SignatureRequested event
let signature_requested_topic = H256::from_slice(&web3::signing::keccak256(b"SignatureRequested(bytes32,address,uint256,uint256,string)"))?;

let signature_requested_topic = H256::from_slice(&web3::signing::keccak256(
b"SignatureRequested(bytes32,address,uint256,uint256,string)",
));

let filter = FilterBuilder::default()
.from_block(BlockNumber::Number(block_number.into()))
.to_block(BlockNumber::Number(block_number.into()))
.address(vec![ctx.contract_address])
.topic0(vec![signature_requested_topic]) // Filter for SignatureRequested event only
.topics(Some(vec![signature_requested_topic]), None, None, None)
.build();

let mut pending_requests = Vec::new();
// Get logs using filter
let logs = ctx.web3.eth().logs(filter).await?;

for log in logs {
let event = parse_event(&log)?;
tracing::info!("Found eth event: {:?}", event);
// Create sign request from event
let sign_request = EthSignRequest {
payload: event.message_hash,
path: format!("eth/{}", event.request_id),
let Some(payload) = Scalar::from_bytes(event.payload_hash) else {
tracing::warn!(
"eth `sign` did not produce payload hash correctly: {:?}",
event.payload_hash,
);
continue;
};
let request = ContractSignRequest {
payload,
path: event.path,
key_version: 0,
};
let epsilon = derive_epsilon_eth(event.requester.to_string(), &request.path);
let entropy = [0u8; 32]; // TODO
let sign_request = SignRequest {
request_id: event.request_id,
request,
epsilon,
entropy,
// TODO: use indexer timestamp instead.
time_added: Instant::now(),
chain: Chain::Ethereum,
};

// Add to queue
ctx.queue.write().await.push(SignRequest::Eth(sign_request));
pending_requests.push(sign_request);
}
let mut queue = ctx.queue.write().await;
for sign_request in pending_requests {
queue.add(sign_request);
}
drop(queue);

ctx.indexer
.update_block_height(block_number, &ctx.gcp_service)
.await?;

Ok(())
}

// Helper function to parse event logs
fn parse_event(log: &Log) -> anyhow::Result<SignatureRequestedEvent> {
// Ensure we have enough topics
if log.topics.len() < 2 {
anyhow::bail!("Invalid number of topics");
}

// Parse requester address from topics[1]
let requester = H160::from_slice(&log.topics[1].as_fixed_bytes()[12..]);

// Parse request_id and message_hash from data
// Parse request_id from topics[1]
let mut request_id = [0u8; 32];
request_id.copy_from_slice(&log.topics[1].as_bytes());

// Parse data fields
let data = log.data.0.as_slice();
let request_id = web3::types::U256::from_big_endian(&data[0..32]);
let mut message_hash = [0u8; 32];
message_hash.copy_from_slice(&data[32..64]);

// Parse requester address (20 bytes)
let requester = H160::from_slice(&data[12..32]);

// Parse epsilon (32 bytes)
let epsilon = U256::from_big_endian(&data[32..64]);

// Parse payload hash (32 bytes)
let mut payload_hash = [0u8; 32];
payload_hash.copy_from_slice(&data[64..96]);

// Parse path string
let path_offset = U256::from_big_endian(&data[96..128]).as_usize();
let path_length = U256::from_big_endian(&data[path_offset..path_offset + 32]).as_usize();
let path_bytes = &data[path_offset + 32..path_offset + 32 + path_length];
let path = String::from_utf8(path_bytes.to_vec())?;

Ok(SignatureRequestedEvent {
requester,
request_id,
message_hash,
requester,
epsilon,
payload_hash,
path,
})
}

pub fn run(
options: &Options,
node_account_id: &AccountId,
queue: &Arc<RwLock<SignQueue>>,
gcp_service: &GcpService,
rt: &tokio::runtime::Runtime,
) -> anyhow::Result<(JoinHandle<anyhow::Result<()>>, EthIndexer)> {
let transport = web3::transports::Http::new(&options.eth_rpc_url)?;
let web3 = Web3::new(transport);

let contract_address = H160::from_str(&options.eth_contract_address)?;

// Load contract ABI
let contract = Contract::from_json(
web3.eth(),
contract_address,
include_bytes!("../abi/YourContract.json")
)?;

let context = Context {
contract_address,
web3,
contract,
gcp_service: gcp_service.clone(),
queue: queue.clone(),
indexer: indexer.clone(),
};

let latest_block_height = rt.block_on(async {
match EthLatestBlockHeight::fetch(gcp_service).await {
Ok(latest) => latest,
Err(err) => {
tracing::warn!(%err, "failed to fetch eth latest block height; using start_block_height={} instead", options.start_block_height);
tracing::warn!(%err, "failed to fetch eth latest block height; using start_block_height={} instead", options.eth_start_block_height);
EthLatestBlockHeight {
account_id: node_account_id.clone(),
block_height: options.start_block_height,
block_height: options.eth_start_block_height,
}
}
}
});

let indexer = EthIndexer::new(latest_block_height, options);
let context = Context {
contract_address,
web3,
gcp_service: gcp_service.clone(),
queue: queue.clone(),
indexer: indexer.clone(),
};

let options = options.clone();
let join_handle = std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;

rt.block_on(async {
let provider = Provider::<Http>::try_from(options.eth_rpc_url.as_str())?;

loop {
let latest_block = provider.get_block_number().await?;
let latest_block = context.web3.eth().block_number().await?;
let current_block = context.indexer.latest_block_height().await;

if current_block < latest_block.as_u64() {
Expand All @@ -244,7 +290,9 @@ pub fn run(

#[derive(Debug)]
struct SignatureRequestedEvent {
request_id: [u8; 32],
requester: H160,
request_id: web3::types::U256,
message_hash: [u8; 32],
}
epsilon: U256,
payload_hash: [u8; 32],
path: String,
}
1 change: 1 addition & 0 deletions chain-signatures/node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod config;
pub mod gcp;
pub mod http_client;
pub mod indexer;
mod indexer_eth;
pub mod kdf;
pub mod mesh;
pub mod metrics;
Expand Down
1 change: 1 addition & 0 deletions chain-signatures/node/src/protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub use contract::primitives::ParticipantInfo;
pub use contract::ProtocolState;
pub use cryptography::CryptographicError;
pub use message::MpcMessage;
pub use signature::Chain;
pub use signature::SignQueue;
pub use signature::SignRequest;
pub use state::NodeState;
Expand Down
Loading

0 comments on commit 07a0616

Please sign in to comment.