rustywallet_silent/
label.rs

1//! Silent Payment labels for multiple addresses.
2
3use crate::error::{Result, SilentPaymentError};
4use secp256k1::{PublicKey, Secp256k1, SecretKey};
5use sha2::{Digest, Sha256};
6
7/// BIP352 label tag for hashing.
8const LABEL_TAG: &[u8] = b"BIP0352/Label";
9
10/// Silent Payment label for deriving multiple addresses.
11///
12/// Labels allow a receiver to have multiple distinct addresses
13/// from a single Silent Payment address, useful for:
14/// - Separating payments by purpose
15/// - Identifying payment sources
16/// - Organizing incoming funds
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Label {
19    /// Label index (m value)
20    index: u32,
21    /// Label tweak (32 bytes)
22    tweak: [u8; 32],
23}
24
25impl Label {
26    /// Create a new label from index.
27    pub fn new(index: u32) -> Self {
28        let tweak = Self::compute_tweak(index);
29        Self { index, tweak }
30    }
31
32    /// Compute label tweak from index.
33    fn compute_tweak(m: u32) -> [u8; 32] {
34        // BIP352: label = hash("BIP0352/Label" || ser32(m))
35        let tag_hash = Sha256::digest(LABEL_TAG);
36
37        let mut hasher = Sha256::new();
38        hasher.update(tag_hash);
39        hasher.update(tag_hash);
40        hasher.update(m.to_be_bytes());
41
42        let result = hasher.finalize();
43        let mut tweak = [0u8; 32];
44        tweak.copy_from_slice(&result);
45        tweak
46    }
47
48    /// Get the label index.
49    pub fn index(&self) -> u32 {
50        self.index
51    }
52
53    /// Get the label tweak.
54    pub fn tweak(&self) -> &[u8; 32] {
55        &self.tweak
56    }
57
58    /// Apply label to a spend public key.
59    ///
60    /// Returns B_m = B_spend + label * G
61    pub fn apply_to_pubkey(&self, spend_pubkey: &[u8; 33]) -> Result<[u8; 33]> {
62        let secp = Secp256k1::new();
63
64        let b_spend = PublicKey::from_slice(spend_pubkey)
65            .map_err(|e| SilentPaymentError::InvalidPublicKey(e.to_string()))?;
66
67        let label_sk = SecretKey::from_slice(&self.tweak)
68            .map_err(|e| SilentPaymentError::CryptoError(e.to_string()))?;
69
70        let label_point = PublicKey::from_secret_key(&secp, &label_sk);
71
72        let b_m = b_spend
73            .combine(&label_point)
74            .map_err(|e| SilentPaymentError::CryptoError(e.to_string()))?;
75
76        Ok(b_m.serialize())
77    }
78
79    /// Apply label to a spend private key.
80    ///
81    /// Returns b_m = b_spend + label
82    pub fn apply_to_privkey(&self, spend_privkey: &[u8; 32]) -> Result<[u8; 32]> {
83        let b_spend = SecretKey::from_slice(spend_privkey)
84            .map_err(|e| SilentPaymentError::InvalidPrivateKey(e.to_string()))?;
85
86        let label_sk = SecretKey::from_slice(&self.tweak)
87            .map_err(|e| SilentPaymentError::CryptoError(e.to_string()))?;
88
89        let b_m = b_spend
90            .add_tweak(&label_sk.into())
91            .map_err(|e| SilentPaymentError::CryptoError(e.to_string()))?;
92
93        Ok(b_m.secret_bytes())
94    }
95}
96
97/// Label manager for tracking multiple labels.
98#[derive(Debug, Clone)]
99pub struct LabelManager {
100    /// Known labels
101    labels: Vec<Label>,
102}
103
104impl LabelManager {
105    /// Create a new label manager.
106    pub fn new() -> Self {
107        Self { labels: Vec::new() }
108    }
109
110    /// Add a label by index.
111    pub fn add(&mut self, index: u32) -> &Label {
112        if let Some(pos) = self.labels.iter().position(|l| l.index == index) {
113            &self.labels[pos]
114        } else {
115            self.labels.push(Label::new(index));
116            self.labels.last().unwrap()
117        }
118    }
119
120    /// Get a label by index.
121    pub fn get(&self, index: u32) -> Option<&Label> {
122        self.labels.iter().find(|l| l.index == index)
123    }
124
125    /// Get all labels.
126    pub fn labels(&self) -> &[Label] {
127        &self.labels
128    }
129
130    /// Generate labels from 0 to n-1.
131    pub fn generate_range(&mut self, n: u32) {
132        for i in 0..n {
133            self.add(i);
134        }
135    }
136
137    /// Check if a public key matches any labeled spend key.
138    pub fn find_matching_label(
139        &self,
140        spend_pubkey: &[u8; 33],
141        target_pubkey: &[u8; 33],
142    ) -> Option<&Label> {
143        for label in &self.labels {
144            if let Ok(labeled) = label.apply_to_pubkey(spend_pubkey) {
145                if &labeled == target_pubkey {
146                    return Some(label);
147                }
148            }
149        }
150        None
151    }
152}
153
154impl Default for LabelManager {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_label_creation() {
166        let label0 = Label::new(0);
167        let label1 = Label::new(1);
168
169        assert_eq!(label0.index(), 0);
170        assert_eq!(label1.index(), 1);
171        assert_ne!(label0.tweak(), label1.tweak());
172    }
173
174    #[test]
175    fn test_label_deterministic() {
176        let label1 = Label::new(42);
177        let label2 = Label::new(42);
178
179        assert_eq!(label1.tweak(), label2.tweak());
180    }
181
182    #[test]
183    fn test_label_apply_pubkey() {
184        use rustywallet_keys::private_key::PrivateKey;
185
186        let key = PrivateKey::random();
187        let pk: [u8; 33] = key.public_key().to_compressed().try_into().unwrap();
188
189        let label = Label::new(1);
190        let labeled = label.apply_to_pubkey(&pk).unwrap();
191
192        // Should be different from original
193        assert_ne!(labeled, pk);
194    }
195
196    #[test]
197    fn test_label_manager() {
198        let mut manager = LabelManager::new();
199
200        manager.add(0);
201        manager.add(1);
202        manager.add(0); // duplicate
203
204        assert_eq!(manager.labels().len(), 2);
205        assert!(manager.get(0).is_some());
206        assert!(manager.get(1).is_some());
207        assert!(manager.get(2).is_none());
208    }
209
210    #[test]
211    fn test_label_manager_range() {
212        let mut manager = LabelManager::new();
213        manager.generate_range(5);
214
215        assert_eq!(manager.labels().len(), 5);
216        for i in 0..5 {
217            assert!(manager.get(i).is_some());
218        }
219    }
220}