1use alloc::vec::Vec;
4use core::fmt::Debug;
5use core2::io::{self, Read, Write};
6
7use zcash_protocol::{
8 value::{BalanceError, ZatBalance as Amount, Zatoshis as NonNegativeAmount},
9 TxId,
10};
11
12use crate::{
13 address::{Script, TransparentAddress},
14 sighash::TransparentAuthorizingContext,
15};
16
17pub trait Authorization: Debug {
18 type ScriptSig: Debug + Clone + PartialEq;
19}
20
21#[derive(Debug)]
24pub struct EffectsOnly {
25 pub(crate) inputs: Vec<TxOut>,
26}
27
28impl Authorization for EffectsOnly {
29 type ScriptSig = ();
30}
31
32impl TransparentAuthorizingContext for EffectsOnly {
33 fn input_amounts(&self) -> Vec<NonNegativeAmount> {
34 self.inputs.iter().map(|input| input.value).collect()
35 }
36
37 fn input_scriptpubkeys(&self) -> Vec<Script> {
38 self.inputs
39 .iter()
40 .map(|input| input.script_pubkey.clone())
41 .collect()
42 }
43}
44
45#[derive(Debug, Copy, Clone, PartialEq, Eq)]
46pub struct Authorized;
47
48impl Authorization for Authorized {
49 type ScriptSig = Script;
50}
51
52pub trait MapAuth<A: Authorization, B: Authorization> {
53 fn map_script_sig(&self, s: A::ScriptSig) -> B::ScriptSig;
54 fn map_authorization(&self, s: A) -> B;
55}
56
57impl MapAuth<Authorized, Authorized> for () {
59 fn map_script_sig(
60 &self,
61 s: <Authorized as Authorization>::ScriptSig,
62 ) -> <Authorized as Authorization>::ScriptSig {
63 s
64 }
65
66 fn map_authorization(&self, s: Authorized) -> Authorized {
67 s
68 }
69}
70
71#[derive(Debug, Clone, PartialEq)]
72pub struct Bundle<A: Authorization> {
73 pub vin: Vec<TxIn<A>>,
74 pub vout: Vec<TxOut>,
75 pub authorization: A,
76}
77
78impl<A: Authorization> Bundle<A> {
79 pub fn is_coinbase(&self) -> bool {
86 matches!(&self.vin[..], [input] if input.prevout.is_null())
89 }
90
91 pub fn map_authorization<B: Authorization, F: MapAuth<A, B>>(self, f: F) -> Bundle<B> {
92 Bundle {
93 vin: self
94 .vin
95 .into_iter()
96 .map(|txin| TxIn {
97 prevout: txin.prevout,
98 script_sig: f.map_script_sig(txin.script_sig),
99 sequence: txin.sequence,
100 })
101 .collect(),
102 vout: self.vout,
103 authorization: f.map_authorization(self.authorization),
104 }
105 }
106
107 pub fn value_balance<E, F>(&self, mut get_prevout_value: F) -> Result<Amount, E>
113 where
114 E: From<BalanceError>,
115 F: FnMut(&OutPoint) -> Result<Amount, E>,
116 {
117 let input_sum = self.vin.iter().try_fold(Amount::zero(), |total, txin| {
118 get_prevout_value(&txin.prevout)
119 .and_then(|v| (total + v).ok_or_else(|| BalanceError::Overflow.into()))
120 })?;
121
122 let output_sum = self
123 .vout
124 .iter()
125 .map(|p| Amount::from(p.value))
126 .sum::<Option<Amount>>()
127 .ok_or(BalanceError::Overflow)?;
128
129 (input_sum - output_sum).ok_or_else(|| BalanceError::Underflow.into())
130 }
131}
132
133#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
134pub struct OutPoint {
135 pub(crate) hash: TxId,
136 pub(crate) n: u32,
137}
138
139impl OutPoint {
140 pub fn new(hash: [u8; 32], n: u32) -> Self {
143 OutPoint {
144 hash: TxId::from_bytes(hash),
145 n,
146 }
147 }
148
149 #[cfg(any(test, feature = "test-dependencies"))]
151 pub const fn fake() -> Self {
152 OutPoint {
153 hash: TxId::from_bytes([1u8; 32]),
154 n: 1,
155 }
156 }
157
158 pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
159 let mut hash = [0u8; 32];
160 reader.read_exact(&mut hash)?;
161 let mut n = [0; 4];
162 reader.read_exact(&mut n)?;
163 Ok(OutPoint::new(hash, u32::from_le_bytes(n)))
164 }
165
166 pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
167 writer.write_all(self.hash.as_ref())?;
168 writer.write_all(&self.n.to_le_bytes())
169 }
170
171 fn is_null(&self) -> bool {
174 self.hash.as_ref() == &[0; 32] && self.n == u32::MAX
177 }
178
179 pub fn n(&self) -> u32 {
181 self.n
182 }
183
184 pub fn hash(&self) -> &[u8; 32] {
186 self.hash.as_ref()
187 }
188
189 pub fn txid(&self) -> &TxId {
191 &self.hash
192 }
193}
194
195#[derive(Debug, Clone, PartialEq)]
196pub struct TxIn<A: Authorization> {
197 pub prevout: OutPoint,
198 pub script_sig: A::ScriptSig,
199 pub sequence: u32,
200}
201
202impl TxIn<Authorized> {
203 pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
204 let prevout = OutPoint::read(&mut reader)?;
205 let script_sig = Script::read(&mut reader)?;
206 let sequence = {
207 let mut sequence = [0; 4];
208 reader.read_exact(&mut sequence)?;
209 u32::from_le_bytes(sequence)
210 };
211
212 Ok(TxIn {
213 prevout,
214 script_sig,
215 sequence,
216 })
217 }
218
219 pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
220 self.prevout.write(&mut writer)?;
221 self.script_sig.write(&mut writer)?;
222 writer.write_all(&self.sequence.to_le_bytes())
223 }
224}
225
226#[derive(Clone, Debug, PartialEq, Eq)]
227pub struct TxOut {
228 pub value: NonNegativeAmount,
229 pub script_pubkey: Script,
230}
231
232impl TxOut {
233 pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
234 let value = {
235 let mut tmp = [0u8; 8];
236 reader.read_exact(&mut tmp)?;
237 NonNegativeAmount::from_nonnegative_i64_le_bytes(tmp)
238 }
239 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?;
240 let script_pubkey = Script::read(&mut reader)?;
241
242 Ok(TxOut {
243 value,
244 script_pubkey,
245 })
246 }
247
248 pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
249 writer.write_all(&self.value.to_i64_le_bytes())?;
250 self.script_pubkey.write(&mut writer)
251 }
252
253 pub fn recipient_address(&self) -> Option<TransparentAddress> {
255 self.script_pubkey.address()
256 }
257}
258
259#[cfg(any(test, feature = "test-dependencies"))]
260pub mod testing {
261 use proptest::collection::vec;
262 use proptest::prelude::*;
263 use proptest::sample::select;
264 use zcash_protocol::value::testing::arb_zatoshis;
265
266 use crate::address::Script;
267
268 use super::{Authorized, Bundle, OutPoint, TxIn, TxOut};
269
270 pub const VALID_OPCODES: [u8; 8] = [
271 0x00, 0x51, 0x52, 0x53, 0xac, 0x63, 0x65, 0x6a, ];
280
281 prop_compose! {
282 pub fn arb_outpoint()(hash in prop::array::uniform32(0u8..), n in 0..100u32) -> OutPoint {
283 OutPoint::new(hash, n)
284 }
285 }
286
287 prop_compose! {
288 pub fn arb_script()(v in vec(select(&VALID_OPCODES[..]), 1..256)) -> Script {
289 Script(v)
290 }
291 }
292
293 prop_compose! {
294 pub fn arb_txin()(
295 prevout in arb_outpoint(),
296 script_sig in arb_script(),
297 sequence in any::<u32>()
298 ) -> TxIn<Authorized> {
299 TxIn { prevout, script_sig, sequence }
300 }
301 }
302
303 prop_compose! {
304 pub fn arb_txout()(value in arb_zatoshis(), script_pubkey in arb_script()) -> TxOut {
305 TxOut { value, script_pubkey }
306 }
307 }
308
309 prop_compose! {
310 pub fn arb_bundle()(
311 vin in vec(arb_txin(), 0..10),
312 vout in vec(arb_txout(), 0..10),
313 ) -> Option<Bundle<Authorized>> {
314 if vin.is_empty() && vout.is_empty() {
315 None
316 } else {
317 Some(Bundle { vin, vout, authorization: Authorized })
318 }
319 }
320 }
321}