From 9f5d864d533ce86459e654f5d78212933c0269ea Mon Sep 17 00:00:00 2001 From: gabrix73 Date: Mon, 1 Jun 2026 18:41:36 +0200 Subject: Initial commit: OnionCoin prototype with Proof-of-Relay consensus 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 --- core/src/transaction.rs | 231 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 core/src/transaction.rs (limited to 'core/src/transaction.rs') diff --git a/core/src/transaction.rs b/core/src/transaction.rs new file mode 100644 index 0000000..d0a181d --- /dev/null +++ b/core/src/transaction.rs @@ -0,0 +1,231 @@ +use serde::{Deserialize, Serialize}; +use onioncoin_timing::TimingMetadata; + +/// Transaction ID (hash of transaction) +pub type TxId = [u8; 32]; + +/// A transaction in the OnionCoin network +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Transaction { + /// Protocol version + pub version: u8, + + /// Transaction inputs + pub inputs: Vec, + + /// Transaction outputs + pub outputs: Vec, + + /// Ring signatures for anonymity (simplified placeholder) + pub ring_signatures: Vec, + + /// Range proof to hide amounts (placeholder) + pub range_proof: Vec, + + /// Encrypted transaction fee + pub encrypted_fee: u64, + + /// Timing metadata for temporal privacy + pub timing: TimingMetadata, +} + +impl Transaction { + /// Calculate transaction ID (hash) + pub fn id(&self) -> TxId { + let serialized = bincode::serialize(self).expect("Serialization should not fail"); + let hash = blake3::hash(&serialized); + *hash.as_bytes() + } + + /// Validate transaction structure + pub fn validate(&self) -> Result<(), TransactionError> { + // Must have at least one input and one output + if self.inputs.is_empty() { + return Err(TransactionError::NoInputs); + } + + if self.outputs.is_empty() { + return Err(TransactionError::NoOutputs); + } + + // Ring signatures must match inputs + if self.ring_signatures.len() != self.inputs.len() { + return Err(TransactionError::InvalidRingSignatures); + } + + // Validate timing metadata + if !self.timing.validate(chrono::Utc::now()) { + return Err(TransactionError::InvalidTimingMetadata); + } + + Ok(()) + } + + /// Get size in bytes + pub fn size(&self) -> usize { + bincode::serialize(self) + .map(|s| s.len()) + .unwrap_or(0) + } +} + +/// Transaction input +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct TransactionInput { + /// Reference to previous transaction output + pub previous_output: OutPoint, + + /// Key image (prevents double spending in ring signatures) + pub key_image: [u8; 32], +} + +/// Reference to a specific output in a previous transaction +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct OutPoint { + /// Transaction ID + pub txid: TxId, + + /// Output index + pub vout: u32, +} + +/// Transaction output +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct TransactionOutput { + /// Encrypted amount (confidential transaction) + pub encrypted_amount: Vec, + + /// One-time stealth address (public key) + pub stealth_address: [u8; 32], + + /// Commitment to the amount (for verification) + pub commitment: [u8; 32], +} + +/// Ring signature for input anonymity +/// Simplified placeholder - production would use actual cryptographic implementation +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RingSignature { + /// Public keys in the ring (decoy outputs + real output) + pub ring: Vec<[u8; 32]>, + + /// Signature components + pub c: [u8; 32], + pub r: Vec<[u8; 32]>, + + /// Ring size (typically 11 for privacy) + pub ring_size: usize, +} + +impl RingSignature { + /// Create a new ring signature with specified ring size + pub fn new(ring_size: usize) -> Self { + Self { + ring: vec![[0u8; 32]; ring_size], + c: [0u8; 32], + r: vec![[0u8; 32]; ring_size], + ring_size, + } + } + + /// Verify ring signature (placeholder) + pub fn verify(&self) -> bool { + // In production: actual ring signature verification + self.ring.len() == self.ring_size && self.r.len() == self.ring_size + } +} + +#[derive(Debug, thiserror::Error)] +pub enum TransactionError { + #[error("Transaction has no inputs")] + NoInputs, + + #[error("Transaction has no outputs")] + NoOutputs, + + #[error("Invalid ring signatures")] + InvalidRingSignatures, + + #[error("Invalid timing metadata")] + InvalidTimingMetadata, + + #[error("Transaction too large")] + TooLarge, + + #[error("Invalid fee")] + InvalidFee, +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::Utc; + + fn create_test_transaction() -> Transaction { + let seed = [42u8; 32]; + let timing = TimingMetadata::new(Utc::now(), &seed).unwrap(); + + 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, + } + } + + #[test] + fn test_transaction_id() { + let tx1 = create_test_transaction(); + let tx2 = create_test_transaction(); + + // Same transaction should have same ID + assert_eq!(tx1.id(), tx2.id()); + } + + #[test] + fn test_transaction_validation() { + let tx = create_test_transaction(); + assert!(tx.validate().is_ok()); + } + + #[test] + fn test_transaction_no_inputs() { + let mut tx = create_test_transaction(); + tx.inputs.clear(); + + assert!(matches!( + tx.validate(), + Err(TransactionError::NoInputs) + )); + } + + #[test] + fn test_transaction_no_outputs() { + let mut tx = create_test_transaction(); + tx.outputs.clear(); + + assert!(matches!( + tx.validate(), + Err(TransactionError::NoOutputs) + )); + } + + #[test] + fn test_ring_signature_verification() { + let ring_sig = RingSignature::new(11); + assert!(ring_sig.verify()); + } +} -- cgit v1.2.3