Skip to content
Merged
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.

94 changes: 3 additions & 91 deletions node/src/bin/stake_and_checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ use std::{
thread::JoinHandle,
};
use summit::args::{RunFlags, run_node_local};
use summit::engine::{PROTOCOL_VERSION, VALIDATOR_MINIMUM_STAKE};
use summit::engine::VALIDATOR_MINIMUM_STAKE;
use summit_types::PROTOCOL_VERSION;
use summit_types::checkpoint::Checkpoint;
use summit_types::consensus_state::ConsensusState;
use summit_types::execution_request::DepositRequest;
use summit_types::execution_request::compute_deposit_data_root;
use summit_types::reth::Reth;
use tokio::sync::mpsc;
use tracing::Level;
Expand Down Expand Up @@ -764,96 +766,6 @@ where
}
}

fn compute_deposit_data_root(
node_pubkey: &[u8; 32],
consensus_pubkey: &[u8; 48],
withdrawal_credentials: &[u8; 32],
amount: U256,
node_signature: &[u8; 64],
consensus_signature: &[u8; 96],
) -> [u8; 32] {
/*
Solidity computation:
bytes32 consensus_pubkey_hash = sha256(abi.encodePacked(consensus_pubkey, bytes16(0)));
bytes32 pubkey_root = sha256(abi.encodePacked(node_pubkey, consensus_pubkey_hash));
bytes32 node_signature_hash = sha256(node_signature);
bytes32 consensus_signature_hash = sha256(abi.encodePacked(
sha256(abi.encodePacked(consensus_signature[:64])),
sha256(abi.encodePacked(consensus_signature[64:], bytes32(0)))
));
bytes32 signature_root = sha256(abi.encodePacked(node_signature_hash, consensus_signature_hash));
bytes32 node = sha256(abi.encodePacked(
sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)),
sha256(abi.encodePacked(amount, bytes24(0), signature_root))
));
*/

// 1. consensus_pubkey_hash = sha256(consensus_pubkey || bytes16(0))
let mut hasher = Sha256::new();
hasher.update(consensus_pubkey);
hasher.update(&[0u8; 16]); // bytes16(0)
let consensus_pubkey_hash = hasher.finalize();

// 2. pubkey_root = sha256(node_pubkey || consensus_pubkey_hash)
let mut hasher = Sha256::new();
hasher.update(node_pubkey);
hasher.update(&consensus_pubkey_hash);
let pubkey_root = hasher.finalize();

// 3. node_signature_hash = sha256(node_signature)
let mut hasher = Sha256::new();
hasher.update(node_signature);
let node_signature_hash = hasher.finalize();

// 4. consensus_signature_hash = sha256(sha256(consensus_signature[0:64]) || sha256(consensus_signature[64:96] || bytes32(0)))
let mut hasher = Sha256::new();
hasher.update(&consensus_signature[0..64]);
let consensus_sig_part1 = hasher.finalize();

let mut hasher = Sha256::new();
hasher.update(&consensus_signature[64..96]);
hasher.update(&[0u8; 32]); // bytes32(0)
let consensus_sig_part2 = hasher.finalize();

let mut hasher = Sha256::new();
hasher.update(&consensus_sig_part1);
hasher.update(&consensus_sig_part2);
let consensus_signature_hash = hasher.finalize();

// 5. signature_root = sha256(node_signature_hash || consensus_signature_hash)
let mut hasher = Sha256::new();
hasher.update(&node_signature_hash);
hasher.update(&consensus_signature_hash);
let signature_root = hasher.finalize();

// 3. Convert amount to 8-byte little-endian (gwei)
let amount_gwei = amount / U256::from(10).pow(U256::from(9)); // Convert wei to gwei
let amount_u64 = amount_gwei.to::<u64>(); // Convert to u64 (should fit for reasonable amounts)
let amount_bytes = amount_u64.to_le_bytes(); // 8 bytes little-endian

// 4. node = sha256(sha256(pubkey_root || withdrawal_credentials) || sha256(amount || bytes24(0) || signature_root))
let mut hasher = Sha256::new();
hasher.update(&pubkey_root);
hasher.update(withdrawal_credentials);
let left_node = hasher.finalize();

let mut hasher = Sha256::new();
hasher.update(&amount_bytes);
hasher.update(&[0u8; 24]); // bytes24(0)
hasher.update(&signature_root);
let right_node = hasher.finalize();

let mut hasher = Sha256::new();
hasher.update(&left_node);
hasher.update(&right_node);
let deposit_data_root = hasher.finalize();

let digest_bytes: &[u8] = deposit_data_root.as_ref();
digest_bytes
.try_into()
.expect("SHA-256 digest is always 32 bytes")
}

fn get_node_flags(node: usize) -> RunFlags {
let path = format!("testnet/node{node}/");

Expand Down
3 changes: 2 additions & 1 deletion rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ path = "src/lib.rs"

[dependencies]
summit-finalizer.workspace = true
summit-types = { workspace = true }
anyhow = { workspace = true }
alloy-primitives.workspace = true
tokio = { workspace = true, features = ["net"] }
tokio-util.workspace = true
futures.workspace = true
axum = { version = "0.8.4"}
summit-types = { workspace = true }
commonware-consensus = { workspace = true }
commonware-cryptography = { workspace = true }
commonware-utils = { workspace = true }
Expand Down
104 changes: 99 additions & 5 deletions rpc/src/routes.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
use std::sync::Arc;

use alloy_primitives::{Address, U256, hex::FromHex as _};
use axum::{
Router,
extract::{Query, State},
Json, Router,
extract::{Path, Query, State},
routing::{get, post},
};
use commonware_codec::DecodeExt as _;
use commonware_codec::{DecodeExt as _, Encode as _};
use commonware_consensus::Block as ConsensusBlock;
use commonware_consensus::simplex::signing_scheme::Scheme;
use commonware_cryptography::Committable;
use commonware_cryptography::{Committable, Hasher as _, Sha256, Signer as _};
use commonware_utils::{from_hex_formatted, hex};
use serde::{Deserialize, Serialize};
use ssz::Encode;
use summit_types::{KeyPaths, PublicKey, utils::get_expanded_path};
use summit_types::{
KeyPaths, PROTOCOL_VERSION, PublicKey,
execution_request::{DepositRequest, compute_deposit_data_root},
utils::get_expanded_path,
};

use crate::{GenesisRpcState, PathSender, RpcState};

Expand All @@ -27,6 +32,16 @@ struct ValidatorBalanceQuery {
public_key: String,
}

#[derive(Serialize, Deserialize)]
struct DepositTransactionResponse {
node_pubkey: [u8; 32],
consensus_pubkey: Vec<u8>, // 48 bytes
withdrawal_credentials: [u8; 32],
node_signature: Vec<u8>, // 48 bytes
consensus_signature: Vec<u8>, // 96 bytes
deposit_data_root: [u8; 32],
}

pub(crate) struct RpcRoutes;

impl RpcRoutes {
Expand All @@ -48,6 +63,10 @@ impl RpcRoutes {
"/get_validator_balance",
get(Self::handle_get_validator_balance::<S, B>),
)
.route(
"/get_deposit_signature/:amount/:address",
get(Self::handle_get_deposit_signature),
)
.with_state(state)
}

Expand Down Expand Up @@ -79,6 +98,81 @@ impl RpcRoutes {
serde_json::to_string(&response).map_err(|e| format!("Failed to serialize response: {}", e))
}

async fn handle_get_deposit_signature<S: Scheme, B: ConsensusBlock + Committable>(
State(state): State<Arc<RpcState<S, B>>>,
Path((amount, address)): Path<(u64, String)>,
) -> Result<Json<DepositTransactionResponse>, String> {
// Withdrawal credentials (32 bytes) - 0x01 prefix for execution address withdrawal
// Format: 0x01 || 0x00...00 (11 bytes) || execution_address (20 bytes)
let mut withdrawal_credentials = [0u8; 32];
withdrawal_credentials[0] = 0x01; // ETH1 withdrawal prefix
// Bytes 1-11 remain zero
// Set the last 20 bytes to the withdrawal address (using the same address as the sender)
let withdrawal_address = Address::from_hex(address).unwrap();
withdrawal_credentials[12..32].copy_from_slice(withdrawal_address.as_slice());

//let amount = VALIDATOR_MINIMUM_STAKE;

let key_paths = KeyPaths::new(state.key_store_path.clone());

let consenus_priv_key = key_paths.consensus_private_key()?;
let consensus_pub = consenus_priv_key.public_key();

let node_priv_key = key_paths.node_private_key()?;
let node_pub = node_priv_key.public_key();

let req = DepositRequest {
node_pubkey: node_pub.clone(),
consensus_pubkey: consensus_pub.clone(),
withdrawal_credentials,
amount,
node_signature: [0; 64],
consensus_signature: [0; 96],
index: 0, // not included in the signature
};

let protocol_version_digest = Sha256::hash(&PROTOCOL_VERSION.to_le_bytes());
let message = req.as_message(protocol_version_digest);

// Sign with node (ed25519) key
let node_signature = node_priv_key.sign(None, &message);
let node_signature_bytes: [u8; 64] = node_signature
.as_ref()
.try_into()
.expect("ed25519 sig is alway 64 bytes");

// Sign with consensus (BLS) key
let consensus_signature = consenus_priv_key.sign(None, &message);
let consensus_signature_slice: &[u8] = consensus_signature.as_ref();
let consensus_signature_bytes: [u8; 96] = consensus_signature_slice
.try_into()
.expect("bls sig is alway 96 bytes");

let node_pubkey_bytes: [u8; 32] = node_pub.to_vec().try_into().expect("Cannot fail");
let consensus_pubkey_bytes: [u8; 48] =
consensus_pub.encode().as_ref()[..48].try_into().unwrap();

let deposit_amount = U256::from(amount) * U256::from(1_000_000_000u64); // gwei to wei

let deposit_root = compute_deposit_data_root(
&node_pubkey_bytes,
&consensus_pubkey_bytes,
&withdrawal_credentials,
deposit_amount,
&node_signature_bytes,
&consensus_signature_bytes,
);

Ok(Json(DepositTransactionResponse {
node_pubkey: node_pubkey_bytes,
consensus_pubkey: consensus_pubkey_bytes.to_vec(),
withdrawal_credentials,
node_signature: node_signature_bytes.to_vec(),
consensus_signature: consensus_signature_bytes.to_vec(),
deposit_data_root: deposit_root,
}))
}

async fn handle_get_pub_keys_genesis(
State(state): State<Arc<GenesisRpcState>>,
) -> Result<String, String> {
Expand Down
92 changes: 91 additions & 1 deletion types/src/execution_request.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{Digest, PublicKey};
use alloy_primitives::Address;
use alloy_primitives::{Address, U256};
use bytes::{Buf, BufMut};
use commonware_codec::{DecodeExt, Encode, Error, FixedSize, Read, Write};
use commonware_cryptography::{Hasher, Sha256, bls12381};
Expand Down Expand Up @@ -340,6 +340,96 @@ impl Read for DepositRequest {
}
}

pub fn compute_deposit_data_root(
node_pubkey: &[u8; 32],
consensus_pubkey: &[u8; 48],
withdrawal_credentials: &[u8; 32],
amount: U256,
node_signature: &[u8; 64],
consensus_signature: &[u8; 96],
) -> [u8; 32] {
/*
Solidity computation:
bytes32 consensus_pubkey_hash = sha256(abi.encodePacked(consensus_pubkey, bytes16(0)));
bytes32 pubkey_root = sha256(abi.encodePacked(node_pubkey, consensus_pubkey_hash));
bytes32 node_signature_hash = sha256(node_signature);
bytes32 consensus_signature_hash = sha256(abi.encodePacked(
sha256(abi.encodePacked(consensus_signature[:64])),
sha256(abi.encodePacked(consensus_signature[64:], bytes32(0)))
));
bytes32 signature_root = sha256(abi.encodePacked(node_signature_hash, consensus_signature_hash));
bytes32 node = sha256(abi.encodePacked(
sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)),
sha256(abi.encodePacked(amount, bytes24(0), signature_root))
));
*/

// 1. consensus_pubkey_hash = sha256(consensus_pubkey || bytes16(0))
let mut hasher = Sha256::new();
hasher.update(consensus_pubkey);
hasher.update(&[0u8; 16]); // bytes16(0)
let consensus_pubkey_hash = hasher.finalize();

// 2. pubkey_root = sha256(node_pubkey || consensus_pubkey_hash)
let mut hasher = Sha256::new();
hasher.update(node_pubkey);
hasher.update(&consensus_pubkey_hash);
let pubkey_root = hasher.finalize();

// 3. node_signature_hash = sha256(node_signature)
let mut hasher = Sha256::new();
hasher.update(node_signature);
let node_signature_hash = hasher.finalize();

// 4. consensus_signature_hash = sha256(sha256(consensus_signature[0:64]) || sha256(consensus_signature[64:96] || bytes32(0)))
let mut hasher = Sha256::new();
hasher.update(&consensus_signature[0..64]);
let consensus_sig_part1 = hasher.finalize();

let mut hasher = Sha256::new();
hasher.update(&consensus_signature[64..96]);
hasher.update(&[0u8; 32]); // bytes32(0)
let consensus_sig_part2 = hasher.finalize();

let mut hasher = Sha256::new();
hasher.update(&consensus_sig_part1);
hasher.update(&consensus_sig_part2);
let consensus_signature_hash = hasher.finalize();

// 5. signature_root = sha256(node_signature_hash || consensus_signature_hash)
let mut hasher = Sha256::new();
hasher.update(&node_signature_hash);
hasher.update(&consensus_signature_hash);
let signature_root = hasher.finalize();

// 3. Convert amount to 8-byte little-endian (gwei)
let amount_gwei = amount / U256::from(10).pow(U256::from(9)); // Convert wei to gwei
let amount_u64 = amount_gwei.to::<u64>(); // Convert to u64 (should fit for reasonable amounts)
let amount_bytes = amount_u64.to_le_bytes(); // 8 bytes little-endian

// 4. node = sha256(sha256(pubkey_root || withdrawal_credentials) || sha256(amount || bytes24(0) || signature_root))
let mut hasher = Sha256::new();
hasher.update(&pubkey_root);
hasher.update(withdrawal_credentials);
let left_node = hasher.finalize();

let mut hasher = Sha256::new();
hasher.update(&amount_bytes);
hasher.update(&[0u8; 24]); // bytes24(0)
hasher.update(&signature_root);
let right_node = hasher.finalize();

let mut hasher = Sha256::new();
hasher.update(&left_node);
hasher.update(&right_node);
let deposit_data_root = hasher.finalize();

let digest_bytes: &[u8] = deposit_data_root.as_ref();
digest_bytes
.try_into()
.expect("SHA-256 digest is always 32 bytes")
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 2 additions & 0 deletions types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ use commonware_consensus::simplex::types::Activity as CActivity;
pub type Digest = commonware_cryptography::sha256::Digest;
pub type Activity = CActivity<Signature, Digest>;

pub const PROTOCOL_VERSION: u32 = 1;

/// Auxiliary data needed for block construction
#[derive(Debug, Clone)]
pub struct BlockAuxData {
Expand Down