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/transaction.rs | |
| download | onioncoin-9e960fa6d453b29aef970ba3cd81e9cbbfd94bcc.tar.gz onioncoin-9e960fa6d453b29aef970ba3cd81e9cbbfd94bcc.tar.xz onioncoin-9e960fa6d453b29aef970ba3cd81e9cbbfd94bcc.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/transaction.rs')
| -rw-r--r-- | core/src/transaction.rs | 231 |
1 files changed, 231 insertions, 0 deletions
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<TransactionInput>, + + /// Transaction outputs + pub outputs: Vec<TransactionOutput>, + + /// Ring signatures for anonymity (simplified placeholder) + pub ring_signatures: Vec<RingSignature>, + + /// Range proof to hide amounts (placeholder) + pub range_proof: Vec<u8>, + + /// 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<u8>, + + /// 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()); + } +} |
