feat: Phase 4 — mesh authentication, envelope signature verification, TX validation
- Identity announcements: verify Ed25519 key validity and X25519 consistency - Envelope signatures: verify Ed25519 signatures on signed messages, drop invalid - Block header validation: height range, hash length, timestamp sanity checks - TX relay validation: hex validity, size bounds, version check before broadcast - Rate limiter struct for per-peer relay operations - Message sequence number field (seq) added to TypedEnvelope for ordering Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
dd8e8e9e4f
commit
5853b6a065
@@ -302,6 +302,101 @@ pub fn build_lightning_relay_response(
|
||||
envelope.to_wire()
|
||||
}
|
||||
|
||||
// ─── Validation Functions ─────────────────────────────────────────────
|
||||
|
||||
/// Validate a received block header before storing/relaying.
|
||||
/// Rejects obviously invalid headers (bad version, impossibly far-ahead height).
|
||||
pub fn validate_block_header(height: u64, hash_hex: &str, timestamp: u32, last_known_height: u64) -> bool {
|
||||
// Hash must be 64 hex chars (32 bytes)
|
||||
if hash_hex.len() != 64 {
|
||||
warn!("Block header rejected: hash length {} != 64", hash_hex.len());
|
||||
return false;
|
||||
}
|
||||
// Height must not be impossibly far ahead (allow 100 blocks gap for mesh delays)
|
||||
if last_known_height > 0 && height > last_known_height + 100 {
|
||||
warn!(
|
||||
"Block header height {} is too far ahead of known height {}",
|
||||
height, last_known_height
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// Timestamp sanity: must not be before Bitcoin genesis (2009-01-03) or far in the future
|
||||
if timestamp < 1_231_006_505 {
|
||||
warn!("Block header rejected: timestamp {} before Bitcoin genesis", timestamp);
|
||||
return false;
|
||||
}
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs() as u32;
|
||||
if timestamp > now + 7200 {
|
||||
warn!("Block header rejected: timestamp {} is more than 2 hours in the future", timestamp);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Validate a raw transaction hex string before relaying to Bitcoin Core.
|
||||
/// Checks basic syntax constraints only (full validation is done by Bitcoin Core).
|
||||
pub fn validate_raw_transaction(tx_hex: &str) -> bool {
|
||||
// Must be valid hex
|
||||
let tx_bytes = match hex::decode(tx_hex) {
|
||||
Ok(b) => b,
|
||||
Err(_) => {
|
||||
warn!("TX relay rejected: invalid hex");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// Minimum valid transaction size is ~60 bytes, max 400KB
|
||||
if tx_bytes.len() < 60 || tx_bytes.len() > 400_000 {
|
||||
warn!("TX relay rejected: size {} out of range [60, 400000]", tx_bytes.len());
|
||||
return false;
|
||||
}
|
||||
// Check version bytes (first 4 bytes, little-endian) — valid versions: 1, 2, 3
|
||||
if tx_bytes.len() >= 4 {
|
||||
let version = u32::from_le_bytes([tx_bytes[0], tx_bytes[1], tx_bytes[2], tx_bytes[3]]);
|
||||
if version < 1 || version > 3 {
|
||||
warn!("TX relay rejected: version {} not in [1,3]", version);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Simple per-peer rate limiter for mesh relay operations.
|
||||
pub struct RelayRateLimiter {
|
||||
/// (peer_id, message_type) -> list of timestamps
|
||||
windows: RwLock<HashMap<(u32, &'static str), Vec<std::time::Instant>>>,
|
||||
}
|
||||
|
||||
impl RelayRateLimiter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
windows: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a relay operation is allowed. Returns true if within rate limits.
|
||||
/// max_per_minute: maximum operations per 60-second window.
|
||||
pub async fn check(&self, peer_id: u32, msg_type: &'static str, max_per_minute: usize) -> bool {
|
||||
let now = std::time::Instant::now();
|
||||
let cutoff = now - std::time::Duration::from_secs(60);
|
||||
let mut windows = self.windows.write().await;
|
||||
let key = (peer_id, msg_type);
|
||||
let timestamps = windows.entry(key).or_insert_with(Vec::new);
|
||||
|
||||
// Remove entries older than 60 seconds
|
||||
timestamps.retain(|t| *t > cutoff);
|
||||
|
||||
if timestamps.len() >= max_per_minute {
|
||||
warn!(peer_id, msg_type, "Rate limit exceeded: {} in last minute", timestamps.len());
|
||||
return false;
|
||||
}
|
||||
timestamps.push(now);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user