summaryrefslogtreecommitdiffstats
path: root/core/src/transaction.rs
diff options
context:
space:
mode:
authorgabrix73 <gabriel1@frozenstar.info>2026-06-01 18:41:36 +0200
committergabrix73 <gabriel1@frozenstar.info>2026-06-01 18:41:36 +0200
commit9f5d864d533ce86459e654f5d78212933c0269ea (patch)
tree4b71e8c7ab4e78da93e5f3164803da4de68c7031 /core/src/transaction.rs
downloadonioncoin-main.tar.gz
onioncoin-main.tar.xz
onioncoin-main.zip
Initial commit: OnionCoin prototype with Proof-of-Relay consensusHEADv0.1.0main
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.rs231
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());
+ }
+}