zebra_consensus/block/subsidy/
funding_streams.rs

1//! Funding Streams calculations. - [§7.8][7.8]
2//!
3//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
4
5use zebra_chain::{
6    block::Height,
7    parameters::{subsidy::*, Network},
8    transaction::Transaction,
9    transparent::{self, Script},
10};
11
12#[cfg(test)]
13mod tests;
14
15/// 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> {
24    if receiver == FundingStreamReceiver::Deferred {
25        return None;
26    }
27
28    let funding_streams = network.funding_streams(height);
29    let num_addresses = funding_streams.recipient(receiver)?.addresses().len();
30
31    let 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;
39
40    assert!(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.
43    Some(index - 1)
44}
45
46/// 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> {
55    let index = funding_stream_address_index(height, network, receiver)?;
56    let funding_streams = network.funding_streams(height);
57    funding_streams.recipient(receiver)?.addresses().get(index)
58}
59
60/// 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 {
65    assert!(
66        address.is_script_hash(),
67        "incorrect funding stream address constant: {address} \
68         Zcash only supports transparent 'pay to script hash' (P2SH) addresses",
69    );
70
71    // Verify a Bitcoin P2SH single or multisig address.
72    let standard_script_hash = new_coinbase_script(address);
73
74    lock_script == &standard_script_hash
75}
76
77/// Returns a new funding stream coinbase output lock script, which pays to the P2SH `address`.
78pub fn new_coinbase_script(address: &transparent::Address) -> Script {
79    assert!(
80        address.is_script_hash(),
81        "incorrect coinbase script address: {address} \
82         Funding streams only support transparent 'pay to script hash' (P2SH) addresses",
83    );
84
85    // > 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
89    address.script()
90}
91
92/// 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}