1use std_shims::{
2 vec,
3 vec::Vec,
4 io::{self, Read, Write},
5};
6
7use zeroize::{Zeroize, ZeroizeOnDrop};
8use subtle::{Choice, ConstantTimeEq as _};
9
10use crate::{
11 io::*,
12 ed25519::{Scalar, CompressedPoint, Point, Commitment},
13 transaction::Timelock,
14 address::SubaddressIndex,
15 extra::{MAX_ARBITRARY_DATA_SIZE, MAX_EXTRA_SIZE_BY_RELAY_RULE, PaymentId},
16};
17
18#[derive(Clone, Zeroize, ZeroizeOnDrop)]
22pub(crate) struct AbsoluteId {
23 pub(crate) transaction: [u8; 32],
24 pub(crate) index_in_transaction: u64,
25}
26
27impl core::fmt::Debug for AbsoluteId {
28 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
29 fmt
30 .debug_struct("AbsoluteId")
31 .field("transaction", &hex::encode(self.transaction))
32 .field("index_in_transaction", &self.index_in_transaction)
33 .finish()
34 }
35}
36
37impl AbsoluteId {
38 fn ct_eq(&self, other: &Self) -> Choice {
40 self.transaction.ct_eq(&other.transaction) &
41 self.index_in_transaction.ct_eq(&other.index_in_transaction)
42 }
43
44 fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
49 w.write_all(&self.transaction)?;
50 w.write_all(&self.index_in_transaction.to_le_bytes())
51 }
52
53 fn read<R: Read>(r: &mut R) -> io::Result<AbsoluteId> {
58 Ok(AbsoluteId { transaction: read_bytes(r)?, index_in_transaction: read_u64(r)? })
59 }
60}
61
62#[derive(Clone, Zeroize, ZeroizeOnDrop)]
66pub(crate) struct RelativeId {
67 pub(crate) index_on_blockchain: u64,
68}
69
70impl core::fmt::Debug for RelativeId {
71 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
72 fmt.debug_struct("RelativeId").field("index_on_blockchain", &self.index_on_blockchain).finish()
73 }
74}
75
76impl RelativeId {
77 fn ct_eq(&self, other: &Self) -> Choice {
79 self.index_on_blockchain.ct_eq(&other.index_on_blockchain)
80 }
81
82 fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
87 w.write_all(&self.index_on_blockchain.to_le_bytes())
88 }
89
90 fn read<R: Read>(r: &mut R) -> io::Result<Self> {
95 Ok(RelativeId { index_on_blockchain: read_u64(r)? })
96 }
97}
98
99#[derive(Clone, Zeroize, ZeroizeOnDrop)]
101pub(crate) struct OutputData {
102 pub(crate) key: Point,
103 pub(crate) key_offset: Scalar,
104 pub(crate) commitment: Commitment,
105}
106
107impl core::fmt::Debug for OutputData {
108 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
109 fmt
110 .debug_struct("OutputData")
111 .field("key", &hex::encode(self.key.compress().to_bytes()))
112 .field("commitment", &self.commitment)
113 .finish_non_exhaustive()
114 }
115}
116
117impl OutputData {
118 pub(crate) fn ct_eq(&self, other: &Self) -> Choice {
120 self.key.ct_eq(&other.key) &
121 self.key_offset.ct_eq(&other.key_offset) &
122 self.commitment.ct_eq(&other.commitment)
123 }
124
125 pub(crate) fn key(&self) -> Point {
127 self.key
128 }
129
130 pub(crate) fn key_offset(&self) -> Scalar {
133 self.key_offset
134 }
135
136 pub(crate) fn commitment(&self) -> &Commitment {
138 &self.commitment
139 }
140
141 pub(crate) fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
146 w.write_all(&self.key.compress().to_bytes())?;
147 self.key_offset.write(w)?;
148 self.commitment.write(w)
149 }
150
151 pub(crate) fn read<R: Read>(r: &mut R) -> io::Result<OutputData> {
168 Ok(OutputData {
169 key: CompressedPoint::read(r)?
170 .decompress()
171 .ok_or_else(|| io::Error::other("output data included an invalid key"))?,
172 key_offset: Scalar::read(r)?,
173 commitment: Commitment::read(r)?,
174 })
175 }
176}
177
178#[derive(Clone, Zeroize, ZeroizeOnDrop)]
180pub(crate) struct Metadata {
181 pub(crate) additional_timelock: Timelock,
182 pub(crate) subaddress: Option<SubaddressIndex>,
183 pub(crate) payment_id: Option<PaymentId>,
184 pub(crate) arbitrary_data: Vec<Vec<u8>>,
185}
186
187impl core::fmt::Debug for Metadata {
188 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
189 fmt
190 .debug_struct("Metadata")
191 .field("additional_timelock", &self.additional_timelock)
192 .field("subaddress", &self.subaddress)
193 .field("payment_id", &self.payment_id)
194 .field("arbitrary_data", &self.arbitrary_data.iter().map(hex::encode).collect::<Vec<_>>())
195 .finish()
196 }
197}
198
199impl Metadata {
200 fn eq(&self, other: &Self) -> bool {
201 (self.additional_timelock == other.additional_timelock) &&
202 (self.subaddress == other.subaddress) &&
203 (self.payment_id == other.payment_id) &&
204 (self.arbitrary_data == other.arbitrary_data)
205 }
206
207 fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
212 self.additional_timelock.write(w)?;
213
214 if let Some(subaddress) = self.subaddress {
215 w.write_all(&[1])?;
216 w.write_all(&subaddress.account().to_le_bytes())?;
217 w.write_all(&subaddress.address().to_le_bytes())?;
218 } else {
219 w.write_all(&[0])?;
220 }
221
222 if let Some(payment_id) = self.payment_id {
223 w.write_all(&[1])?;
224 payment_id.write(w)?;
225 } else {
226 w.write_all(&[0])?;
227 }
228
229 VarInt::write(&self.arbitrary_data.len(), w)?;
230 for part in &self.arbitrary_data {
231 #[expect(clippy::as_conversions)]
232 const _ASSERT_MAX_ARBITRARY_DATA_SIZE_FITS_WITHIN_U8: [();
233 (u8::MAX as usize) - MAX_ARBITRARY_DATA_SIZE] = [(); _];
234 w.write_all(&[
235 u8::try_from(part.len()).expect("piece of arbitrary data exceeded max length of u8::MAX")
236 ])?;
237 w.write_all(part)?;
238 }
239 Ok(())
240 }
241
242 fn read<R: Read>(r: &mut R) -> io::Result<Metadata> {
247 let additional_timelock = Timelock::read(r)?;
248
249 let subaddress = match read_byte(r)? {
250 0 => None,
251 1 => Some(
252 SubaddressIndex::new(read_u32(r)?, read_u32(r)?)
253 .ok_or_else(|| io::Error::other("invalid subaddress in metadata"))?,
254 ),
255 _ => Err(io::Error::other("invalid subaddress is_some boolean in metadata"))?,
256 };
257
258 Ok(Metadata {
259 additional_timelock,
260 subaddress,
261 payment_id: if read_byte(r)? == 1 { PaymentId::read(r).ok() } else { None },
262 arbitrary_data: {
267 let chunks = <usize as VarInt>::read(r)?;
268 if chunks > MAX_EXTRA_SIZE_BY_RELAY_RULE {
270 Err(io::Error::other(
271 "amount of arbitrary data chunks exceeded amount possible under policy",
272 ))?;
273 }
274
275 let mut data = vec![];
276 let mut total_len = 0usize;
277 for _ in 0 .. chunks {
278 let len = read_byte(r)?;
279 let chunk = read_raw_vec(read_byte, usize::from(len), r)?;
280 total_len = total_len.saturating_add(chunk.len());
281 if total_len > MAX_EXTRA_SIZE_BY_RELAY_RULE {
282 Err(io::Error::other("amount of arbitrary data exceeded amount allowed by policy"))?;
283 }
284 data.push(chunk);
285 }
286 data
287 },
288 })
289 }
290}
291
292#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
302pub struct WalletOutput {
303 pub(crate) absolute_id: AbsoluteId,
305 pub(crate) relative_id: RelativeId,
307 pub(crate) data: OutputData,
309 pub(crate) metadata: Metadata,
311}
312
313impl PartialEq for WalletOutput {
314 fn eq(&self, other: &Self) -> bool {
316 bool::from(
317 self.absolute_id.ct_eq(&other.absolute_id) &
318 self.relative_id.ct_eq(&other.relative_id) &
319 self.data.ct_eq(&other.data),
320 ) & self.metadata.eq(&other.metadata)
321 }
322}
323impl Eq for WalletOutput {}
324
325impl WalletOutput {
326 pub fn transaction(&self) -> [u8; 32] {
328 self.absolute_id.transaction
329 }
330
331 pub fn index_in_transaction(&self) -> u64 {
333 self.absolute_id.index_in_transaction
334 }
335
336 pub fn index_on_blockchain(&self) -> u64 {
338 self.relative_id.index_on_blockchain
339 }
340
341 pub fn key(&self) -> Point {
343 self.data.key()
344 }
345
346 pub fn key_offset(&self) -> Scalar {
349 self.data.key_offset()
350 }
351
352 pub fn commitment(&self) -> &Commitment {
354 self.data.commitment()
355 }
356
357 pub fn additional_timelock(&self) -> Timelock {
363 self.metadata.additional_timelock
364 }
365
366 pub fn subaddress(&self) -> Option<SubaddressIndex> {
368 self.metadata.subaddress
369 }
370
371 pub fn payment_id(&self) -> Option<PaymentId> {
396 self.metadata.payment_id
397 }
398
399 pub fn arbitrary_data(&self) -> &[Vec<u8>] {
401 &self.metadata.arbitrary_data
402 }
403
404 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
409 self.absolute_id.write(w)?;
410 self.relative_id.write(w)?;
411 self.data.write(w)?;
412 self.metadata.write(w)
413 }
414
415 pub fn serialize(&self) -> Vec<u8> {
420 let mut serialized = Vec::with_capacity(128);
421 self.write(&mut serialized).expect("write failed but <Vec as io::Write> doesn't fail");
422 serialized
423 }
424
425 pub fn read<R: Read>(r: &mut R) -> io::Result<WalletOutput> {
430 Ok(WalletOutput {
431 absolute_id: AbsoluteId::read(r)?,
432 relative_id: RelativeId::read(r)?,
433 data: OutputData::read(r)?,
434 metadata: Metadata::read(r)?,
435 })
436 }
437}