rustywallet_silent/lib.rs
1//! # rustywallet-silent
2//!
3//! Silent Payments (BIP352) implementation for rustywallet.
4//!
5//! Silent Payments allow receivers to publish a static address that senders
6//! can use to derive unique output addresses, providing privacy without
7//! requiring interaction between sender and receiver.
8//!
9//! ## Features
10//!
11//! - **Address Generation**: Create Silent Payment addresses with scan and spend keys
12//! - **Sending**: Derive unique output addresses for recipients
13//! - **Scanning**: Detect incoming payments using scan key
14//! - **Labels**: Support multiple addresses from a single Silent Payment address
15//! - **Change Handling**: Generate deterministic change outputs
16//!
17//! ## Quick Start
18//!
19//! ### Creating a Silent Payment Address
20//!
21//! ```rust
22//! use rustywallet_silent::prelude::*;
23//! use rustywallet_keys::private_key::PrivateKey;
24//!
25//! // Generate keys
26//! let scan_key = PrivateKey::random();
27//! let spend_key = PrivateKey::random();
28//!
29//! // Create address
30//! let address = SilentPaymentAddress::new(
31//! &scan_key.public_key(),
32//! &spend_key.public_key(),
33//! Network::Mainnet,
34//! ).unwrap();
35//!
36//! println!("Address: {}", address);
37//! ```
38//!
39//! ### Sending a Payment
40//!
41//! ```rust
42//! use rustywallet_silent::prelude::*;
43//! use rustywallet_keys::private_key::PrivateKey;
44//!
45//! // Sender's input key
46//! let sender_key = PrivateKey::random();
47//!
48//! // Recipient's address (normally parsed from string)
49//! let scan_key = PrivateKey::random();
50//! let spend_key = PrivateKey::random();
51//! let recipient = SilentPaymentAddress::new(
52//! &scan_key.public_key(),
53//! &spend_key.public_key(),
54//! Network::Mainnet,
55//! ).unwrap();
56//!
57//! // Create outputs
58//! let outpoints = vec![([0u8; 32], 0u32)]; // txid, vout
59//! let outputs = create_outputs(
60//! &[sender_key.to_bytes()],
61//! &outpoints,
62//! &[recipient],
63//! ).unwrap();
64//!
65//! // Use outputs[0].output_pubkey as the taproot output key
66//! ```
67//!
68//! ### Scanning for Payments
69//!
70//! ```rust
71//! use rustywallet_silent::prelude::*;
72//! use rustywallet_keys::private_key::PrivateKey;
73//!
74//! // Receiver's keys
75//! let scan_key = PrivateKey::random();
76//! let spend_key = PrivateKey::random();
77//!
78//! // Create scanner
79//! let scanner = SilentPaymentScanner::new(
80//! &scan_key.to_bytes(),
81//! &spend_key.to_bytes(),
82//! ).unwrap();
83//!
84//! // Scan transaction outputs
85//! // let detected = scanner.scan(&output_pubkeys, &input_pubkeys, &outpoints).unwrap();
86//! ```
87//!
88//! ## BIP352 Compliance
89//!
90//! This implementation follows BIP352 specification:
91//! - Bech32m encoding with `sp` (mainnet) and `tsp` (testnet) prefixes
92//! - ECDH-based shared secret derivation
93//! - Tagged hashing for domain separation
94//! - Support for labeled addresses
95//!
96//! ## Security Considerations
97//!
98//! - Keep scan and spend private keys secure
99//! - Scan key can be shared with a light client for detection
100//! - Spend key is required to actually spend received funds
101//! - Labels provide address separation without additional key material
102
103pub mod address;
104pub mod change;
105pub mod error;
106pub mod label;
107pub mod network;
108pub mod prelude;
109pub mod scanner;
110pub mod sender;
111
112pub use address::SilentPaymentAddress;
113pub use change::ChangeAddressGenerator;
114pub use error::{Result, SilentPaymentError};
115pub use label::{Label, LabelManager};
116pub use network::Network;
117pub use scanner::{DetectedPayment, LightScanner, SilentPaymentScanner};
118pub use sender::{create_multiple_outputs, create_outputs, SilentPaymentOutput};
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use rustywallet_keys::private_key::PrivateKey;
124
125 #[test]
126 fn test_full_workflow() {
127 // === Receiver Setup ===
128 let scan_key = PrivateKey::random();
129 let spend_key = PrivateKey::random();
130
131 let sp_address = SilentPaymentAddress::new(
132 &scan_key.public_key(),
133 &spend_key.public_key(),
134 Network::Mainnet,
135 )
136 .unwrap();
137
138 // Encode and share address
139 let encoded = sp_address.encode().unwrap();
140 assert!(encoded.starts_with("sp1"));
141
142 // === Sender Creates Payment ===
143 let sender_key = PrivateKey::random();
144 let sender_pubkey: [u8; 33] = sender_key
145 .public_key()
146 .to_compressed()
147 .try_into()
148 .unwrap();
149
150 let outpoints = vec![([1u8; 32], 0u32)];
151
152 // Parse recipient address
153 let recipient: SilentPaymentAddress = encoded.parse().unwrap();
154
155 // Create output
156 let outputs = create_outputs(
157 &[sender_key.to_bytes()],
158 &outpoints,
159 &[recipient],
160 )
161 .unwrap();
162
163 assert_eq!(outputs.len(), 1);
164
165 // === Receiver Scans ===
166 let scanner = SilentPaymentScanner::new(
167 &scan_key.to_bytes(),
168 &spend_key.to_bytes(),
169 )
170 .unwrap();
171
172 let detected = scanner
173 .scan(&[outputs[0].output_pubkey], &[sender_pubkey], &outpoints)
174 .unwrap();
175
176 assert_eq!(detected.len(), 1);
177
178 // Verify spending key
179 let secp = secp256k1::Secp256k1::new();
180 let sk = secp256k1::SecretKey::from_slice(&detected[0].spending_key).unwrap();
181 let pk = secp256k1::PublicKey::from_secret_key(&secp, &sk);
182 let (xonly, _) = pk.x_only_public_key();
183
184 assert_eq!(xonly.serialize(), outputs[0].output_pubkey);
185 }
186
187 #[test]
188 fn test_labeled_payment() {
189 // Receiver with labels
190 let scan_key = PrivateKey::random();
191 let spend_key = PrivateKey::random();
192
193 let mut scanner = SilentPaymentScanner::new(
194 &scan_key.to_bytes(),
195 &spend_key.to_bytes(),
196 )
197 .unwrap();
198
199 // Add labels
200 scanner.add_labels(5);
201
202 // Create labeled address
203 let label = Label::new(2);
204 let labeled_spend = label
205 .apply_to_pubkey(
206 &spend_key
207 .public_key()
208 .to_compressed()
209 .try_into()
210 .unwrap(),
211 )
212 .unwrap();
213
214 let labeled_spend_pk = secp256k1::PublicKey::from_slice(&labeled_spend).unwrap();
215 let labeled_address = SilentPaymentAddress::from_bytes(
216 scan_key
217 .public_key()
218 .to_compressed()
219 .try_into()
220 .unwrap(),
221 labeled_spend_pk.serialize(),
222 Network::Mainnet,
223 )
224 .unwrap();
225
226 // Sender pays to labeled address
227 let sender_key = PrivateKey::random();
228 let sender_pubkey: [u8; 33] = sender_key
229 .public_key()
230 .to_compressed()
231 .try_into()
232 .unwrap();
233
234 let outpoints = vec![([2u8; 32], 0u32)];
235
236 let outputs = create_outputs(
237 &[sender_key.to_bytes()],
238 &outpoints,
239 &[labeled_address],
240 )
241 .unwrap();
242
243 // Scan should detect with label
244 let detected = scanner
245 .scan(&[outputs[0].output_pubkey], &[sender_pubkey], &outpoints)
246 .unwrap();
247
248 assert_eq!(detected.len(), 1);
249 assert_eq!(detected[0].label, Some(2));
250 }
251
252 #[test]
253 fn test_multiple_outputs() {
254 let scan_key = PrivateKey::random();
255 let spend_key = PrivateKey::random();
256
257 let recipient = SilentPaymentAddress::new(
258 &scan_key.public_key(),
259 &spend_key.public_key(),
260 Network::Mainnet,
261 )
262 .unwrap();
263
264 let sender_key = PrivateKey::random();
265 let outpoints = vec![([3u8; 32], 0u32)];
266
267 // Create 3 outputs to same recipient
268 let outputs = create_multiple_outputs(
269 &[sender_key.to_bytes()],
270 &outpoints,
271 &recipient,
272 3,
273 )
274 .unwrap();
275
276 assert_eq!(outputs.len(), 3);
277
278 // All outputs should be unique
279 let mut pubkeys: Vec<_> = outputs.iter().map(|o| o.output_pubkey).collect();
280 pubkeys.sort();
281 pubkeys.dedup();
282 assert_eq!(pubkeys.len(), 3);
283 }
284
285 #[test]
286 fn test_change_address() {
287 let scan_key = PrivateKey::random();
288 let spend_key = PrivateKey::random();
289
290 let generator = ChangeAddressGenerator::new(
291 &scan_key.to_bytes(),
292 &spend_key.to_bytes(),
293 Network::Mainnet,
294 )
295 .unwrap();
296
297 let outpoints = vec![([4u8; 32], 0u32)];
298
299 // Generate change
300 let (change_pk, spending_key) = generator.generate_change(&outpoints, 0).unwrap();
301
302 // Verify it's a valid change output
303 let result = generator
304 .is_change_output(&change_pk, &outpoints, 5)
305 .unwrap();
306
307 assert!(result.is_some());
308 let (index, recovered_key) = result.unwrap();
309 assert_eq!(index, 0);
310 assert_eq!(recovered_key, spending_key);
311 }
312}