zingolabs_zewif/sapling/sapling_sent_output.rs
1use anyhow::Context;
2use bc_envelope::prelude::*;
3
4use crate::{Amount, Indexed, Memo};
5
6/// Represents a sent output in a Sapling shielded transaction within a Zcash wallet.
7///
8/// `SaplingSentOutput` stores the plaintext details of a Sapling note that was sent by the
9/// wallet, which are not recoverable from the blockchain after transmission. This information
10/// enables selective disclosure, allowing a sender to prove they made a payment to a specific
11/// shielded address without revealing additional transaction details.
12///
13/// # Zcash Concept Relation
14/// In Zcash's Sapling protocol (activated in October 2018):
15///
16/// - **Shielded transactions** encrypt transaction details on the blockchain using zk-SNARKs
17/// - **Notes** are the fundamental unit of value transfer, similar to UTXOs in transparent transactions
18/// - **Sent output information** is stored by the sender's wallet to enable proofs of payment
19///
20/// Each sent output contains components of the Sapling note:
21/// - Diversifier: Part of the recipient's shielded address derivation
22/// - Public key: The recipient's public key for the transaction
23/// - Value: The amount of ZEC transferred
24/// - Rcm: Random commitment material used to construct the note commitment
25///
26/// # Data Preservation
27/// During wallet migration, sent output information must be preserved to maintain
28/// the ability to generate payment proofs for regulatory compliance, auditing,
29/// or other selective disclosure purposes. The sending wallet is the only entity
30/// that has this information in plaintext form.
31///
32/// # Examples
33/// ```
34/// # use zewif::{sapling::SaplingSentOutput, Blob, Amount};
35/// # use anyhow::Result;
36/// # fn example() -> Result<()> {
37/// // Create a new sent output
38/// let mut sent_output = SaplingSentOutput::new();
39///
40/// let value = Amount::from_u64(5000000)?; // 0.05 ZEC
41/// sent_output.set_value(value);
42///
43/// // Access the components
44/// let amount = sent_output.value();
45/// let zats: i64 = amount.into();
46/// assert_eq!(zats, 5000000);
47/// # Ok(())
48/// # }
49/// ```
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct SaplingSentOutput {
52 /// The index of the output in the transaction's Sapling bundle.
53 index: usize,
54
55 /// The string representation of the address to which the output was sent.
56 ///
57 /// This should be either a Sapling address or a Unified address with a Sapling receiver.
58 /// We preserve the original address string because in the case of Unified addresses, it is not
59 /// otherwise possible to reconstruct the data for receivers other than the Sapling receiver,
60 /// and as a consequence the recipient address in a restored wallet would appear different than
61 /// in the source wallet.
62 recipient_address: String,
63
64 /// The value of ZEC sent in this output, in zatoshis (1 ZEC = 10^8 zatoshis).
65 ///
66 /// This 64-bit unsigned integer specifies the amount transferred. It is constrained
67 /// by the protocol to a maximum value (2^63 - 1 zatoshis), ensuring it fits within
68 /// the note's value field for Sapling transactions.
69 value: Amount,
70
71 /// The memo attached to this output, if any.
72 memo: Option<Memo>,
73}
74
75impl Indexed for SaplingSentOutput {
76 fn index(&self) -> usize {
77 self.index
78 }
79
80 fn set_index(&mut self, index: usize) {
81 self.index = index;
82 }
83}
84
85impl SaplingSentOutput {
86 /// Creates a new `SaplingSentOutput` with default values.
87 ///
88 /// This constructor initializes a `SaplingSentOutput` with empty default values
89 /// for all fields. In practical use, these values would be set using the setter
90 /// methods before the object is used.
91 ///
92 /// # Returns
93 /// A new `SaplingSentOutput` instance with default values.
94 ///
95 /// # Examples
96 /// ```
97 /// # use zewif::sapling::SaplingSentOutput;
98 /// let sent_output = SaplingSentOutput::new();
99 /// ```
100 pub fn new() -> Self {
101 Self {
102 index: 0,
103 recipient_address: "".to_string(),
104 value: Amount::zero(),
105 memo: None,
106 }
107 }
108
109 /// Creates a new `SaplingSentOutput` from its constituent parts.
110 pub fn from_parts(
111 index: usize,
112 recipient_address: String,
113 value: Amount,
114 memo: Option<Memo>,
115 ) -> Self {
116 Self {
117 index,
118 recipient_address,
119 value,
120 memo,
121 }
122 }
123
124 /// Returns the string representation of the address used in construction of the output.
125 ///
126 /// This will be either a Sapling address or a Unified address with a Sapling receiver.
127 /// We preserve the original address string because in the case of Unified addresses, it is not
128 /// otherwise possible to reconstruct the data for receivers other than the Sapling receiver,
129 /// and as a consequence the recipient address in a restored wallet would appear different than
130 /// in the source wallet.
131 pub fn recipient_address(&self) -> &str {
132 &self.recipient_address
133 }
134
135 /// Sets the string representation of the address used in construction of the output.
136 pub fn set_recipient_address(&mut self, recipient_address: String) {
137 self.recipient_address = recipient_address;
138 }
139
140 /// Returns the value (amount) of ZEC sent in this output.
141 ///
142 /// This represents the amount of ZEC transferred in this specific note,
143 /// measured in zatoshis (1 ZEC = 10^8 zatoshis).
144 ///
145 /// # Returns
146 /// The amount of ZEC as an `Amount`.
147 ///
148 /// # Examples
149 /// ```
150 /// # use zewif::{sapling::SaplingSentOutput, Amount};
151 /// # use anyhow::Result;
152 /// # fn example() -> Result<()> {
153 /// let mut sent_output = SaplingSentOutput::new();
154 /// sent_output.set_value(Amount::from_u64(10_000_000)?); // 0.1 ZEC
155 ///
156 /// let value = sent_output.value();
157 /// let zats: i64 = value.into();
158 /// assert_eq!(zats, 10_000_000);
159 /// # Ok(())
160 /// # }
161 /// ```
162 pub fn value(&self) -> Amount {
163 self.value
164 }
165
166 /// Sets the value (amount) of ZEC for this sent output.
167 ///
168 /// # Arguments
169 /// * `value` - The amount of ZEC to set
170 ///
171 /// # Examples
172 /// ```
173 /// # use zewif::{sapling::SaplingSentOutput, Amount};
174 /// # use anyhow::Result;
175 /// # fn example() -> Result<()> {
176 /// let mut sent_output = SaplingSentOutput::new();
177 /// let amount = Amount::from_u64(50_000_000)?; // 0.5 ZEC
178 /// sent_output.set_value(amount);
179 /// # Ok(())
180 /// # }
181 /// ```
182 pub fn set_value(&mut self, value: Amount) {
183 self.value = value;
184 }
185
186 /// Returns the memo associated with the output, if known.
187 pub fn memo(&self) -> Option<&Memo> {
188 self.memo.as_ref()
189 }
190
191 /// Sets the memo associated with the output.
192 pub fn set_memo(&mut self, memo: Option<Memo>) {
193 self.memo = memo;
194 }
195}
196
197impl Default for SaplingSentOutput {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203impl From<SaplingSentOutput> for Envelope {
204 fn from(value: SaplingSentOutput) -> Self {
205 Envelope::new(value.index)
206 .add_type("SaplingSentOutput")
207 .add_assertion("recipient_address", value.recipient_address)
208 .add_assertion("value", value.value)
209 .add_optional_assertion("memo", value.memo)
210 }
211}
212
213impl TryFrom<Envelope> for SaplingSentOutput {
214 type Error = anyhow::Error;
215
216 fn try_from(envelope: Envelope) -> Result<Self, Self::Error> {
217 envelope
218 .check_type_envelope("SaplingSentOutput")
219 .context("SaplingSentOutput")?;
220 let index = envelope.extract_subject().context("index")?;
221 let recipient_address = envelope
222 .extract_object_for_predicate("recipient_address")
223 .context("recipient_address")?;
224 let value = envelope
225 .extract_object_for_predicate("value")
226 .context("value")?;
227 let memo = envelope
228 .extract_optional_object_for_predicate("memo")
229 .context("memo")?;
230
231 Ok(SaplingSentOutput {
232 index,
233 recipient_address,
234 value,
235 memo,
236 })
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::SaplingSentOutput;
243 use crate::{Amount, Memo, test_envelope_roundtrip};
244
245 impl crate::RandomInstance for SaplingSentOutput {
246 fn random() -> Self {
247 Self {
248 index: 0,
249 recipient_address: String::random(),
250 value: Amount::random(),
251 memo: Some(Memo::random()),
252 }
253 }
254 }
255
256 test_envelope_roundtrip!(SaplingSentOutput);
257}