Skip to main content

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}