1//! Funding Streams calculations. - [§7.8][7.8]
2//!
3//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
45use zebra_chain::{
6 block::Height,
7 parameters::{subsidy::*, Network},
8 transaction::Transaction,
9 transparent::{self, Script},
10};
1112#[cfg(test)]
13mod tests;
1415/// Returns the position in the address slice for each funding stream
16/// as described in [protocol specification §7.10][7.10]
17///
18/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
19fn funding_stream_address_index(
20 height: Height,
21 network: &Network,
22 receiver: FundingStreamReceiver,
23) -> Option<usize> {
24if receiver == FundingStreamReceiver::Deferred {
25return None;
26 }
2728let funding_streams = network.funding_streams(height);
29let num_addresses = funding_streams.recipient(receiver)?.addresses().len();
3031let index = 1u32
32.checked_add(funding_stream_address_period(height, network))
33 .expect("no overflow should happen in this sum")
34 .checked_sub(funding_stream_address_period(
35 funding_streams.height_range().start,
36 network,
37 ))
38 .expect("no overflow should happen in this sub") as usize;
3940assert!(index > 0 && index <= num_addresses);
41// spec formula will output an index starting at 1 but
42 // Zebra indices for addresses start at zero, return converted.
43Some(index - 1)
44}
4546/// Return the address corresponding to given height, network and funding stream receiver.
47///
48/// This function only returns transparent addresses, because the current Zcash funding streams
49/// only use transparent addresses,
50pub fn funding_stream_address(
51 height: Height,
52 network: &Network,
53 receiver: FundingStreamReceiver,
54) -> Option<&transparent::Address> {
55let index = funding_stream_address_index(height, network, receiver)?;
56let funding_streams = network.funding_streams(height);
57 funding_streams.recipient(receiver)?.addresses().get(index)
58}
5960/// Given a funding stream P2SH address, create a script and check if it is the same
61/// as the given lock_script as described in [protocol specification §7.10][7.10]
62///
63/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
64pub fn check_script_form(lock_script: &Script, address: &transparent::Address) -> bool {
65assert!(
66 address.is_script_hash(),
67"incorrect funding stream address constant: {address} \
68 Zcash only supports transparent 'pay to script hash' (P2SH) addresses",
69 );
7071// Verify a Bitcoin P2SH single or multisig address.
72let standard_script_hash = new_coinbase_script(address);
7374 lock_script == &standard_script_hash
75}
7677/// Returns a new funding stream coinbase output lock script, which pays to the P2SH `address`.
78pub fn new_coinbase_script(address: &transparent::Address) -> Script {
79assert!(
80 address.is_script_hash(),
81"incorrect coinbase script address: {address} \
82 Funding streams only support transparent 'pay to script hash' (P2SH) addresses",
83 );
8485// > The “prescribed way” to pay a transparent P2SH address is to use a standard P2SH script
86 // > of the form OP_HASH160 fs.RedeemScriptHash(height) OP_EQUAL as the scriptPubKey.
87 //
88 // [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
89address.script()
90}
9192/// Returns a list of outputs in `transaction`, which have a script address equal to `address`.
93pub fn filter_outputs_by_address(
94 transaction: &Transaction,
95 address: &transparent::Address,
96) -> Vec<transparent::Output> {
97 transaction
98 .outputs()
99 .iter()
100 .filter(|o| check_script_form(&o.lock_script, address))
101 .cloned()
102 .collect()
103}