diff options
| author | gabrix73 <gabriel1@frozenstar.info> | 2026-06-01 18:41:36 +0200 |
|---|---|---|
| committer | gabrix73 <gabriel1@frozenstar.info> | 2026-06-01 18:41:36 +0200 |
| commit | 9f5d864d533ce86459e654f5d78212933c0269ea (patch) | |
| tree | 4b71e8c7ab4e78da93e5f3164803da4de68c7031 /core/src/block.rs | |
| download | onioncoin-0.1.0.tar.gz onioncoin-0.1.0.tar.xz onioncoin-0.1.0.zip | |
OnionCoin is a privacy cryptocurrency that rewards Tor relay operators
through a unique Proof-of-Contribution consensus mechanism.
Core Features:
- Proof-of-Relay: 30% of block rewards go to Tor operators
- Native .onion node identity (no IP exposure)
- Temporal obfuscation protocols
- Dandelion++ over Tor propagation
- Native inheritance system with dead man's switch
Technical Stack:
- Rust workspace with 8 crates
- Ed25519/X25519 cryptography
- arti (Rust Tor client) integration planned
- 10 minute block time, 5-10 TPS design
Status: Prototype
- Consensus logic complete with passing tests (30/33)
- Network layer conceptual design complete
- Tor integration pending
- Testnet launch planned Q3 2026
License: MIT
Author: Gabriele Salati (virebent)
Contact: g48rix@gmail.com
Website: https://www.gabrielesalati.eu
Repository: https://git.virebent.art/virebent/onioncoin
Diffstat (limited to 'core/src/block.rs')
| -rw-r--r-- | core/src/block.rs | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/core/src/block.rs b/core/src/block.rs new file mode 100644 index 0000000..d72f962 --- /dev/null +++ b/core/src/block.rs @@ -0,0 +1,253 @@ +use serde::{Deserialize, Serialize}; +use crate::transaction::Transaction; +use blake3::Hasher; + +/// Block hash +pub type BlockHash = [u8; 32]; + +/// A block in the OnionCoin blockchain +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Block { + /// Block header + pub header: BlockHeader, + + /// Transactions in this block + pub transactions: Vec<Transaction>, + + /// Staking actions (stake/unstake) + pub stake_actions: Vec<StakeAction>, +} + +impl Block { + /// Maximum block size (2 MB for Tor bandwidth constraints) + pub const MAX_SIZE: usize = 2 * 1024 * 1024; + + /// Target block time (10 minutes) + pub const TARGET_BLOCK_TIME_SECS: u64 = 10 * 60; + + /// Calculate block hash + pub fn hash(&self) -> BlockHash { + self.header.hash() + } + + /// Validate block structure + pub fn validate(&self) -> Result<(), BlockError> { + // Check size + let size = self.size(); + if size > Self::MAX_SIZE { + return Err(BlockError::TooLarge(size)); + } + + // Validate all transactions + for tx in &self.transactions { + tx.validate().map_err(|_| BlockError::InvalidTransaction)?; + } + + // Verify merkle root + let calculated_merkle = Self::calculate_merkle_root(&self.transactions); + if calculated_merkle != self.header.merkle_root { + return Err(BlockError::InvalidMerkleRoot); + } + + Ok(()) + } + + /// Get block size in bytes + pub fn size(&self) -> usize { + bincode::serialize(self) + .map(|s| s.len()) + .unwrap_or(0) + } + + /// Calculate merkle root of transactions + fn calculate_merkle_root(transactions: &[Transaction]) -> [u8; 32] { + if transactions.is_empty() { + return [0u8; 32]; + } + + let mut hashes: Vec<[u8; 32]> = transactions + .iter() + .map(|tx| tx.id()) + .collect(); + + // Build merkle tree bottom-up + while hashes.len() > 1 { + let mut next_level = Vec::new(); + + for chunk in hashes.chunks(2) { + let mut hasher = Hasher::new(); + hasher.update(&chunk[0]); + if chunk.len() > 1 { + hasher.update(&chunk[1]); + } else { + hasher.update(&chunk[0]); // Duplicate if odd + } + next_level.push(*hasher.finalize().as_bytes()); + } + + hashes = next_level; + } + + hashes[0] + } +} + +/// Block header +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BlockHeader { + /// Protocol version + pub version: u8, + + /// Previous block hash + pub previous_hash: BlockHash, + + /// Merkle root of transactions + pub merkle_root: [u8; 32], + + /// Block timestamp (Unix seconds) + pub timestamp: i64, + + /// Block height + pub height: u64, + + /// Validator's public key (PoS) + pub validator_pubkey: [u8; 32], + + /// Validator's signature + #[serde(with = "serde_bytes")] + pub validator_signature: [u8; 64], +} + +impl BlockHeader { + /// Calculate header hash + pub fn hash(&self) -> BlockHash { + let mut hasher = Hasher::new(); + hasher.update(&[self.version]); + hasher.update(&self.previous_hash); + hasher.update(&self.merkle_root); + hasher.update(&self.timestamp.to_le_bytes()); + hasher.update(&self.height.to_le_bytes()); + hasher.update(&self.validator_pubkey); + // Note: signature not included in hash + *hasher.finalize().as_bytes() + } +} + +/// Staking action (stake or unstake coins) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StakeAction { + /// Action type + pub action_type: StakeActionType, + + /// Validator public key + pub validator_pubkey: [u8; 32], + + /// Amount to stake/unstake + pub amount: u64, + + /// Signature + #[serde(with = "serde_bytes")] + pub signature: [u8; 64], +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum StakeActionType { + Stake, + Unstake, +} + +#[derive(Debug, thiserror::Error)] +pub enum BlockError { + #[error("Block too large: {0} bytes (max: {})", Block::MAX_SIZE)] + TooLarge(usize), + + #[error("Invalid transaction in block")] + InvalidTransaction, + + #[error("Invalid merkle root")] + InvalidMerkleRoot, + + #[error("Invalid validator signature")] + InvalidValidatorSignature, + + #[error("Invalid previous hash")] + InvalidPreviousHash, + + #[error("Block timestamp too far in future")] + TimestampTooFarFuture, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::transaction::*; + use chrono::Utc; + use onioncoin_timing::TimingMetadata; + + fn create_test_block() -> Block { + let seed = [42u8; 32]; + let timing = TimingMetadata::new(Utc::now(), &seed).unwrap(); + + let tx = Transaction { + version: 1, + inputs: vec![TransactionInput { + previous_output: OutPoint { + txid: [1u8; 32], + vout: 0, + }, + key_image: [2u8; 32], + }], + outputs: vec![TransactionOutput { + encrypted_amount: vec![0u8; 32], + stealth_address: [3u8; 32], + commitment: [4u8; 32], + }], + ring_signatures: vec![RingSignature::new(11)], + range_proof: vec![0u8; 64], + encrypted_fee: 1000, + timing, + }; + + let merkle_root = Block::calculate_merkle_root(&[tx.clone()]); + + Block { + header: BlockHeader { + version: 1, + previous_hash: [0u8; 32], + merkle_root, + timestamp: Utc::now().timestamp(), + height: 1, + validator_pubkey: [5u8; 32], + validator_signature: [6u8; 64], + }, + transactions: vec![tx], + stake_actions: vec![], + } + } + + #[test] + fn test_block_hash() { + let block = create_test_block(); + let hash = block.hash(); + assert_ne!(hash, [0u8; 32]); + } + + #[test] + fn test_block_validation() { + let block = create_test_block(); + assert!(block.validate().is_ok()); + } + + #[test] + fn test_merkle_root() { + let block = create_test_block(); + let calculated = Block::calculate_merkle_root(&block.transactions); + assert_eq!(calculated, block.header.merkle_root); + } + + #[test] + fn test_empty_merkle_root() { + let root = Block::calculate_merkle_root(&[]); + assert_eq!(root, [0u8; 32]); + } +} |
