1use alloc::collections::BTreeMap;
4use alloc::vec::Vec;
5use core::fmt;
6
7use zcash_protocol::value::{BalanceError, ZatBalance, Zatoshis};
8
9use crate::{
10 address::{Script, TransparentAddress},
11 bundle::{Authorization, Authorized, Bundle, TxIn, TxOut},
12 pczt,
13 sighash::{SignableInput, TransparentAuthorizingContext},
14};
15
16#[cfg(feature = "transparent-inputs")]
17use {
18 crate::{
19 bundle::OutPoint,
20 sighash::{SighashType, SIGHASH_ALL},
21 },
22 sha2::Digest,
23};
24
25#[derive(Debug, PartialEq, Eq)]
26pub enum Error {
27 InvalidAddress,
28 InvalidAmount,
29 MissingSigningKey,
31}
32
33impl fmt::Display for Error {
34 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35 match self {
36 Error::InvalidAddress => write!(f, "Invalid address"),
37 Error::InvalidAmount => write!(f, "Invalid amount"),
38 Error::MissingSigningKey => write!(f, "Missing signing key"),
39 }
40 }
41}
42
43pub struct TransparentSigningSet {
48 #[cfg(feature = "transparent-inputs")]
49 secp: secp256k1::Secp256k1<secp256k1::SignOnly>,
50 #[cfg(feature = "transparent-inputs")]
51 keys: Vec<(secp256k1::SecretKey, secp256k1::PublicKey)>,
52}
53
54impl Default for TransparentSigningSet {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60impl TransparentSigningSet {
61 pub fn new() -> Self {
63 Self {
64 #[cfg(feature = "transparent-inputs")]
65 secp: secp256k1::Secp256k1::gen_new(),
66 #[cfg(feature = "transparent-inputs")]
67 keys: vec![],
68 }
69 }
70
71 #[cfg(feature = "transparent-inputs")]
75 pub fn add_key(&mut self, sk: secp256k1::SecretKey) -> secp256k1::PublicKey {
76 let pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &sk);
77 self.keys.push((sk, pubkey));
79 pubkey
80 }
81}
82
83#[cfg(feature = "transparent-inputs")]
85#[derive(Debug, Clone)]
86pub struct TransparentInputInfo {
87 pubkey: secp256k1::PublicKey,
88 utxo: OutPoint,
89 coin: TxOut,
90}
91
92#[cfg(feature = "transparent-inputs")]
93impl TransparentInputInfo {
94 pub fn outpoint(&self) -> &OutPoint {
95 &self.utxo
96 }
97
98 pub fn coin(&self) -> &TxOut {
99 &self.coin
100 }
101}
102
103pub struct TransparentBuilder {
104 #[cfg(feature = "transparent-inputs")]
105 inputs: Vec<TransparentInputInfo>,
106 vout: Vec<TxOut>,
107}
108
109#[derive(Debug, Clone)]
110pub struct Unauthorized {
111 #[cfg(feature = "transparent-inputs")]
112 inputs: Vec<TransparentInputInfo>,
113}
114
115impl Authorization for Unauthorized {
116 type ScriptSig = ();
117}
118
119impl TransparentBuilder {
120 pub fn empty() -> Self {
122 TransparentBuilder {
123 #[cfg(feature = "transparent-inputs")]
124 inputs: vec![],
125 vout: vec![],
126 }
127 }
128
129 #[cfg(feature = "transparent-inputs")]
132 pub fn inputs(&self) -> &[TransparentInputInfo] {
133 &self.inputs
134 }
135
136 pub fn outputs(&self) -> &[TxOut] {
138 &self.vout
139 }
140
141 #[cfg(feature = "transparent-inputs")]
143 pub fn add_input(
144 &mut self,
145 pubkey: secp256k1::PublicKey,
146 utxo: OutPoint,
147 coin: TxOut,
148 ) -> Result<(), Error> {
149 match coin.script_pubkey.address() {
153 Some(TransparentAddress::PublicKeyHash(hash)) => {
154 use ripemd::Ripemd160;
155 use sha2::Sha256;
156
157 if hash[..] != Ripemd160::digest(Sha256::digest(pubkey.serialize()))[..] {
158 return Err(Error::InvalidAddress);
159 }
160 }
161 _ => return Err(Error::InvalidAddress),
162 }
163
164 self.inputs
165 .push(TransparentInputInfo { pubkey, utxo, coin });
166
167 Ok(())
168 }
169
170 pub fn add_output(&mut self, to: &TransparentAddress, value: Zatoshis) -> Result<(), Error> {
171 self.vout.push(TxOut {
172 value,
173 script_pubkey: to.script(),
174 });
175
176 Ok(())
177 }
178
179 pub fn value_balance(&self) -> Result<ZatBalance, BalanceError> {
180 #[cfg(feature = "transparent-inputs")]
181 let input_sum = self
182 .inputs
183 .iter()
184 .map(|input| input.coin.value)
185 .sum::<Option<Zatoshis>>()
186 .ok_or(BalanceError::Overflow)?;
187
188 #[cfg(not(feature = "transparent-inputs"))]
189 let input_sum = Zatoshis::ZERO;
190
191 let output_sum = self
192 .vout
193 .iter()
194 .map(|vo| vo.value)
195 .sum::<Option<Zatoshis>>()
196 .ok_or(BalanceError::Overflow)?;
197
198 (ZatBalance::from(input_sum) - ZatBalance::from(output_sum)).ok_or(BalanceError::Underflow)
199 }
200
201 pub fn build(self) -> Option<Bundle<Unauthorized>> {
202 #[cfg(feature = "transparent-inputs")]
203 let vin: Vec<TxIn<Unauthorized>> = self
204 .inputs
205 .iter()
206 .map(|i| TxIn::new(i.utxo.clone()))
207 .collect();
208
209 #[cfg(not(feature = "transparent-inputs"))]
210 let vin: Vec<TxIn<Unauthorized>> = vec![];
211
212 if vin.is_empty() && self.vout.is_empty() {
213 None
214 } else {
215 Some(Bundle {
216 vin,
217 vout: self.vout,
218 authorization: Unauthorized {
219 #[cfg(feature = "transparent-inputs")]
220 inputs: self.inputs,
221 },
222 })
223 }
224 }
225
226 pub fn build_for_pczt(self) -> Option<pczt::Bundle> {
228 #[cfg(feature = "transparent-inputs")]
229 let inputs = self
230 .inputs
231 .iter()
232 .map(|i| pczt::Input {
233 prevout_txid: i.utxo.hash,
234 prevout_index: i.utxo.n,
235 sequence: None,
236 required_time_lock_time: None,
237 required_height_lock_time: None,
238 script_sig: None,
239 value: i.coin.value,
240 script_pubkey: i.coin.script_pubkey.clone(),
241 redeem_script: None,
243 partial_signatures: BTreeMap::new(),
244 sighash_type: SighashType::ALL,
245 bip32_derivation: BTreeMap::new(),
246 ripemd160_preimages: BTreeMap::new(),
247 sha256_preimages: BTreeMap::new(),
248 hash160_preimages: BTreeMap::new(),
249 hash256_preimages: BTreeMap::new(),
250 proprietary: BTreeMap::new(),
251 })
252 .collect::<Vec<_>>();
253
254 #[cfg(not(feature = "transparent-inputs"))]
255 let inputs = vec![];
256
257 if inputs.is_empty() && self.vout.is_empty() {
258 None
259 } else {
260 let outputs = self
261 .vout
262 .into_iter()
263 .map(|o| pczt::Output {
264 value: o.value,
265 script_pubkey: o.script_pubkey,
266 redeem_script: None,
270 bip32_derivation: BTreeMap::new(),
271 user_address: None,
272 proprietary: BTreeMap::new(),
273 })
274 .collect();
275
276 Some(pczt::Bundle { inputs, outputs })
277 }
278 }
279}
280
281impl TxIn<Unauthorized> {
282 #[cfg(feature = "transparent-inputs")]
283 pub fn new(prevout: OutPoint) -> Self {
284 TxIn {
285 prevout,
286 script_sig: (),
287 sequence: u32::MAX,
288 }
289 }
290}
291
292#[cfg(not(feature = "transparent-inputs"))]
293impl TransparentAuthorizingContext for Unauthorized {
294 fn input_amounts(&self) -> Vec<Zatoshis> {
295 vec![]
296 }
297
298 fn input_scriptpubkeys(&self) -> Vec<Script> {
299 vec![]
300 }
301}
302
303#[cfg(feature = "transparent-inputs")]
304impl TransparentAuthorizingContext for Unauthorized {
305 fn input_amounts(&self) -> Vec<Zatoshis> {
306 self.inputs.iter().map(|txin| txin.coin.value).collect()
307 }
308
309 fn input_scriptpubkeys(&self) -> Vec<Script> {
310 self.inputs
311 .iter()
312 .map(|txin| txin.coin.script_pubkey.clone())
313 .collect()
314 }
315}
316
317impl Bundle<Unauthorized> {
318 #[cfg_attr(not(feature = "transparent-inputs"), allow(unused_variables))]
319 pub fn apply_signatures<F>(
320 self,
321 calculate_sighash: F,
322 signing_set: &TransparentSigningSet,
323 ) -> Result<Bundle<Authorized>, Error>
324 where
325 F: Fn(SignableInput) -> [u8; 32],
326 {
327 #[cfg(feature = "transparent-inputs")]
328 let script_sigs = self
329 .authorization
330 .inputs
331 .iter()
332 .enumerate()
333 .map(|(index, info)| {
334 let (sk, _) = signing_set
336 .keys
337 .iter()
338 .find(|(_, pubkey)| pubkey == &info.pubkey)
339 .ok_or(Error::MissingSigningKey)?;
340
341 let sighash = calculate_sighash(SignableInput {
342 hash_type: SighashType::ALL,
343 index,
344 script_code: &info.coin.script_pubkey, script_pubkey: &info.coin.script_pubkey,
346 value: info.coin.value,
347 });
348
349 let msg =
350 secp256k1::Message::from_digest_slice(sighash.as_ref()).expect("32 bytes");
351 let sig = signing_set.secp.sign_ecdsa(&msg, sk);
352
353 let mut sig_bytes: Vec<u8> = sig.serialize_der()[..].to_vec();
355 sig_bytes.extend([SIGHASH_ALL]);
356
357 Ok(Script::default() << &sig_bytes[..] << &info.pubkey.serialize()[..])
359 });
360
361 #[cfg(not(feature = "transparent-inputs"))]
362 let script_sigs = core::iter::empty::<Result<Script, Error>>();
363
364 Ok(Bundle {
365 vin: self
366 .vin
367 .iter()
368 .zip(script_sigs)
369 .map(|(txin, sig)| {
370 Ok(TxIn {
371 prevout: txin.prevout.clone(),
372 script_sig: sig?,
373 sequence: txin.sequence,
374 })
375 })
376 .collect::<Result<_, _>>()?,
377 vout: self.vout,
378 authorization: Authorized,
379 })
380 }
381}