rustywallet_silent/
change.rs

1//! Change address handling for Silent Payments.
2
3use crate::address::SilentPaymentAddress;
4use crate::error::{Result, SilentPaymentError};
5use crate::network::Network;
6use secp256k1::{PublicKey, Secp256k1, SecretKey};
7use sha2::{Digest, Sha256};
8
9/// BIP352 change tag.
10const CHANGE_TAG: &[u8] = b"BIP0352/Change";
11
12/// Change address generator for Silent Payments.
13///
14/// When sending a Silent Payment, the sender may need to create
15/// a change output back to themselves. This module provides
16/// utilities for generating deterministic change addresses.
17pub struct ChangeAddressGenerator {
18    /// Scan private key
19    scan_privkey: [u8; 32],
20    /// Spend private key
21    spend_privkey: [u8; 32],
22    /// Network
23    network: Network,
24}
25
26impl ChangeAddressGenerator {
27    /// Create a new change address generator.
28    pub fn new(
29        scan_privkey: &[u8; 32],
30        spend_privkey: &[u8; 32],
31        network: Network,
32    ) -> Result<Self> {
33        // Validate keys
34        SecretKey::from_slice(scan_privkey)
35            .map_err(|e| SilentPaymentError::InvalidPrivateKey(e.to_string()))?;
36        SecretKey::from_slice(spend_privkey)
37            .map_err(|e| SilentPaymentError::InvalidPrivateKey(e.to_string()))?;
38
39        Ok(Self {
40            scan_privkey: *scan_privkey,
41            spend_privkey: *spend_privkey,
42            network,
43        })
44    }
45
46    /// Get the Silent Payment address for this wallet.
47    pub fn address(&self) -> Result<SilentPaymentAddress> {
48        let secp = Secp256k1::new();
49
50        let scan_sk = SecretKey::from_slice(&self.scan_privkey)
51            .map_err(|e| SilentPaymentError::InvalidPrivateKey(e.to_string()))?;
52        let spend_sk = SecretKey::from_slice(&self.spend_privkey)
53            .map_err(|e| SilentPaymentError::InvalidPrivateKey(e.to_string()))?;
54
55        let scan_pk = PublicKey::from_secret_key(&secp, &scan_sk);
56        let spend_pk = PublicKey::from_secret_key(&secp, &spend_sk);
57
58        SilentPaymentAddress::from_bytes(scan_pk.serialize(), spend_pk.serialize(), self.network)
59    }
60
61    /// Generate a change output key for a transaction.
62    ///
63    /// # Arguments
64    /// * `outpoints` - (txid, vout) pairs for all inputs
65    /// * `index` - Change output index (for multiple change outputs)
66    ///
67    /// # Returns
68    /// (output_pubkey, spending_key) tuple
69    pub fn generate_change(
70        &self,
71        outpoints: &[([u8; 32], u32)],
72        index: u32,
73    ) -> Result<([u8; 32], [u8; 32])> {
74        if outpoints.is_empty() {
75            return Err(SilentPaymentError::NoInputs);
76        }
77
78        let secp = Secp256k1::new();
79
80        // Compute change tweak
81        let tweak = self.compute_change_tweak(outpoints, index)?;
82
83        // Compute output key: P = B_spend + tweak * G
84        let spend_sk = SecretKey::from_slice(&self.spend_privkey)
85            .map_err(|e| SilentPaymentError::InvalidPrivateKey(e.to_string()))?;
86        let spend_pk = PublicKey::from_secret_key(&secp, &spend_sk);
87
88        let tweak_sk = SecretKey::from_slice(&tweak)
89            .map_err(|e| SilentPaymentError::CryptoError(e.to_string()))?;
90        let tweak_point = PublicKey::from_secret_key(&secp, &tweak_sk);
91
92        let output_pk = spend_pk
93            .combine(&tweak_point)
94            .map_err(|e| SilentPaymentError::CryptoError(e.to_string()))?;
95
96        let (xonly, _parity) = output_pk.x_only_public_key();
97
98        // Compute spending key: b_spend + tweak
99        let spending_key = spend_sk
100            .add_tweak(&tweak_sk.into())
101            .map_err(|e| SilentPaymentError::CryptoError(e.to_string()))?;
102
103        Ok((xonly.serialize(), spending_key.secret_bytes()))
104    }
105
106    /// Compute change tweak.
107    fn compute_change_tweak(&self, outpoints: &[([u8; 32], u32)], index: u32) -> Result<[u8; 32]> {
108        let secp = Secp256k1::new();
109
110        // Sort outpoints
111        let mut sorted: Vec<_> = outpoints.to_vec();
112        sorted.sort_by(|a, b| {
113            let cmp = a.0.cmp(&b.0);
114            if cmp == std::cmp::Ordering::Equal {
115                a.1.cmp(&b.1)
116            } else {
117                cmp
118            }
119        });
120
121        // Get scan public key
122        let scan_sk = SecretKey::from_slice(&self.scan_privkey)
123            .map_err(|e| SilentPaymentError::InvalidPrivateKey(e.to_string()))?;
124        let scan_pk = PublicKey::from_secret_key(&secp, &scan_sk);
125
126        // Hash: tag || smallest_outpoint || B_scan || index
127        let tag_hash = Sha256::digest(CHANGE_TAG);
128
129        let mut hasher = Sha256::new();
130        hasher.update(tag_hash);
131        hasher.update(tag_hash);
132        hasher.update(sorted[0].0);
133        hasher.update(sorted[0].1.to_le_bytes());
134        hasher.update(scan_pk.serialize());
135        hasher.update(index.to_be_bytes());
136
137        let result = hasher.finalize();
138        let mut tweak = [0u8; 32];
139        tweak.copy_from_slice(&result);
140
141        Ok(tweak)
142    }
143
144    /// Check if an output is a change output from this wallet.
145    pub fn is_change_output(
146        &self,
147        output_pk: &[u8; 32],
148        outpoints: &[([u8; 32], u32)],
149        max_index: u32,
150    ) -> Result<Option<(u32, [u8; 32])>> {
151        for index in 0..max_index {
152            let (expected_pk, spending_key) = self.generate_change(outpoints, index)?;
153            if &expected_pk == output_pk {
154                return Ok(Some((index, spending_key)));
155            }
156        }
157        Ok(None)
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use rustywallet_keys::private_key::PrivateKey;
165
166    #[test]
167    fn test_change_generator_creation() {
168        let scan_key = PrivateKey::random();
169        let spend_key = PrivateKey::random();
170
171        let generator = ChangeAddressGenerator::new(
172            &scan_key.to_bytes(),
173            &spend_key.to_bytes(),
174            Network::Mainnet,
175        )
176        .unwrap();
177
178        let addr = generator.address().unwrap();
179        assert_eq!(addr.network(), Network::Mainnet);
180    }
181
182    #[test]
183    fn test_generate_change() {
184        let scan_key = PrivateKey::random();
185        let spend_key = PrivateKey::random();
186
187        let generator = ChangeAddressGenerator::new(
188            &scan_key.to_bytes(),
189            &spend_key.to_bytes(),
190            Network::Mainnet,
191        )
192        .unwrap();
193
194        let outpoints = vec![([1u8; 32], 0u32)];
195        let (output_pk, spending_key) = generator.generate_change(&outpoints, 0).unwrap();
196
197        // Verify spending key produces correct public key
198        let secp = Secp256k1::new();
199        let sk = SecretKey::from_slice(&spending_key).unwrap();
200        let pk = PublicKey::from_secret_key(&secp, &sk);
201        let (xonly, _) = pk.x_only_public_key();
202
203        assert_eq!(xonly.serialize(), output_pk);
204    }
205
206    #[test]
207    fn test_change_deterministic() {
208        let scan_key = PrivateKey::random();
209        let spend_key = PrivateKey::random();
210
211        let generator = ChangeAddressGenerator::new(
212            &scan_key.to_bytes(),
213            &spend_key.to_bytes(),
214            Network::Mainnet,
215        )
216        .unwrap();
217
218        let outpoints = vec![([1u8; 32], 0u32)];
219
220        let (pk1, _) = generator.generate_change(&outpoints, 0).unwrap();
221        let (pk2, _) = generator.generate_change(&outpoints, 0).unwrap();
222
223        assert_eq!(pk1, pk2);
224    }
225
226    #[test]
227    fn test_different_indices() {
228        let scan_key = PrivateKey::random();
229        let spend_key = PrivateKey::random();
230
231        let generator = ChangeAddressGenerator::new(
232            &scan_key.to_bytes(),
233            &spend_key.to_bytes(),
234            Network::Mainnet,
235        )
236        .unwrap();
237
238        let outpoints = vec![([1u8; 32], 0u32)];
239
240        let (pk0, _) = generator.generate_change(&outpoints, 0).unwrap();
241        let (pk1, _) = generator.generate_change(&outpoints, 1).unwrap();
242
243        assert_ne!(pk0, pk1);
244    }
245
246    #[test]
247    fn test_is_change_output() {
248        let scan_key = PrivateKey::random();
249        let spend_key = PrivateKey::random();
250
251        let generator = ChangeAddressGenerator::new(
252            &scan_key.to_bytes(),
253            &spend_key.to_bytes(),
254            Network::Mainnet,
255        )
256        .unwrap();
257
258        let outpoints = vec![([1u8; 32], 0u32)];
259        let (output_pk, _) = generator.generate_change(&outpoints, 2).unwrap();
260
261        let result = generator.is_change_output(&output_pk, &outpoints, 5).unwrap();
262        assert!(result.is_some());
263        assert_eq!(result.unwrap().0, 2);
264    }
265}