rustywallet_coinjoin/
types.rs

1//! Common types for CoinJoin operations.
2
3use serde::{Deserialize, Serialize};
4
5/// A transaction input reference.
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct InputRef {
8    /// Transaction ID (32 bytes)
9    pub txid: [u8; 32],
10    /// Output index
11    pub vout: u32,
12    /// Amount in satoshis
13    pub amount: u64,
14    /// Script pubkey (for verification)
15    pub script_pubkey: Vec<u8>,
16}
17
18impl InputRef {
19    /// Create a new input reference.
20    pub fn new(txid: [u8; 32], vout: u32, amount: u64, script_pubkey: Vec<u8>) -> Self {
21        Self {
22            txid,
23            vout,
24            amount,
25            script_pubkey,
26        }
27    }
28
29    /// Create from outpoint.
30    pub fn from_outpoint(txid: [u8; 32], vout: u32, amount: u64) -> Self {
31        Self {
32            txid,
33            vout,
34            amount,
35            script_pubkey: Vec::new(),
36        }
37    }
38}
39
40/// A transaction output.
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct OutputDef {
43    /// Amount in satoshis
44    pub amount: u64,
45    /// Script pubkey
46    pub script_pubkey: Vec<u8>,
47    /// Address (for display)
48    pub address: Option<String>,
49}
50
51impl OutputDef {
52    /// Create a new output definition.
53    pub fn new(amount: u64, script_pubkey: Vec<u8>) -> Self {
54        Self {
55            amount,
56            script_pubkey,
57            address: None,
58        }
59    }
60
61    /// Create with address.
62    pub fn with_address(amount: u64, script_pubkey: Vec<u8>, address: String) -> Self {
63        Self {
64            amount,
65            script_pubkey,
66            address: Some(address),
67        }
68    }
69}
70
71/// CoinJoin participant.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct Participant {
74    /// Participant ID
75    pub id: String,
76    /// Inputs contributed
77    pub inputs: Vec<InputRef>,
78    /// Output address (script pubkey)
79    pub output_script: Vec<u8>,
80    /// Change address (optional)
81    pub change_script: Option<Vec<u8>>,
82}
83
84impl Participant {
85    /// Create a new participant.
86    pub fn new(id: impl Into<String>, inputs: Vec<InputRef>, output_script: Vec<u8>) -> Self {
87        Self {
88            id: id.into(),
89            inputs,
90            output_script,
91            change_script: None,
92        }
93    }
94
95    /// Set change address.
96    pub fn with_change(mut self, change_script: Vec<u8>) -> Self {
97        self.change_script = Some(change_script);
98        self
99    }
100
101    /// Total input amount.
102    pub fn total_input(&self) -> u64 {
103        self.inputs.iter().map(|i| i.amount).sum()
104    }
105}
106
107/// Fee distribution strategy.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
109pub enum FeeStrategy {
110    /// Split fee equally among participants
111    #[default]
112    Equal,
113    /// Fee proportional to input amounts
114    Proportional,
115    /// Single participant pays all fees
116    SinglePayer(usize),
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_input_ref() {
125        let input = InputRef::new([1u8; 32], 0, 100_000, vec![0x00, 0x14]);
126        assert_eq!(input.amount, 100_000);
127        assert_eq!(input.vout, 0);
128    }
129
130    #[test]
131    fn test_output_def() {
132        let output = OutputDef::with_address(50_000, vec![0x00, 0x14], "bc1q...".into());
133        assert_eq!(output.amount, 50_000);
134        assert!(output.address.is_some());
135    }
136
137    #[test]
138    fn test_participant() {
139        let inputs = vec![InputRef::from_outpoint([1u8; 32], 0, 100_000)];
140        let participant = Participant::new("alice", inputs, vec![0x00, 0x14]);
141        assert_eq!(participant.total_input(), 100_000);
142    }
143}