use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use getset::Getters;
use serde::{Deserialize, Serialize};
use crate::roles::combiner::merge_map;
pub(crate) const FLAG_TRANSPARENT_INPUTS_MODIFIABLE: u8 = 0b0000_0001;
pub(crate) const FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE: u8 = 0b0000_0010;
pub(crate) const FLAG_HAS_SIGHASH_SINGLE: u8 = 0b0000_0100;
pub(crate) const FLAG_SHIELDED_MODIFIABLE: u8 = 0b1000_0000;
pub(crate) const PLACEHOLDER_ANCHOR: [u8; 32] = [0; 32];
#[derive(Clone, Copy, Debug)]
pub(crate) enum AnchorRequirement {
#[cfg_attr(not(any(feature = "orchard", feature = "sapling")), allow(dead_code))]
NotRequired,
Required,
}
impl AnchorRequirement {
pub(crate) fn for_pre_authorization(tx_version: u32) -> Self {
if tx_version == zcash_protocol::constants::V6_TX_VERSION {
AnchorRequirement::NotRequired
} else {
AnchorRequirement::Required
}
}
pub(crate) fn resolve(
self,
anchor: Option<[u8; 32]>,
bundle_is_empty: bool,
) -> Option<[u8; 32]> {
match (self, anchor) {
(_, Some(anchor)) => Some(anchor),
(AnchorRequirement::NotRequired, None) => Some(PLACEHOLDER_ANCHOR),
(AnchorRequirement::Required, None) if bundle_is_empty => Some(PLACEHOLDER_ANCHOR),
(AnchorRequirement::Required, None) => None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
pub struct Global {
#[getset(get = "pub")]
pub(crate) tx_version: u32,
#[getset(get = "pub")]
pub(crate) version_group_id: u32,
#[getset(get = "pub")]
pub(crate) consensus_branch_id: u32,
pub(crate) fallback_lock_time: Option<u32>,
#[getset(get = "pub")]
pub(crate) expiry_height: u32,
pub(crate) coin_type: u32,
pub(crate) tx_modifiable: u8,
#[getset(get = "pub")]
pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
}
impl Global {
pub fn inputs_modifiable(&self) -> bool {
(self.tx_modifiable & FLAG_TRANSPARENT_INPUTS_MODIFIABLE) != 0
}
pub fn outputs_modifiable(&self) -> bool {
(self.tx_modifiable & FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE) != 0
}
pub fn has_sighash_single(&self) -> bool {
(self.tx_modifiable & FLAG_HAS_SIGHASH_SINGLE) != 0
}
pub fn shielded_modifiable(&self) -> bool {
(self.tx_modifiable & FLAG_SHIELDED_MODIFIABLE) != 0
}
pub(crate) fn merge(mut self, other: Self) -> Option<Self> {
let Self {
tx_version,
version_group_id,
consensus_branch_id,
fallback_lock_time,
expiry_height,
coin_type,
tx_modifiable,
proprietary,
} = other;
if self.tx_version != tx_version
|| self.version_group_id != version_group_id
|| self.consensus_branch_id != consensus_branch_id
|| self.fallback_lock_time != fallback_lock_time
|| self.expiry_height != expiry_height
|| self.coin_type != coin_type
{
return None;
}
if (tx_modifiable & FLAG_TRANSPARENT_INPUTS_MODIFIABLE) == 0 {
self.tx_modifiable &= !FLAG_TRANSPARENT_INPUTS_MODIFIABLE;
}
if (tx_modifiable & FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE) == 0 {
self.tx_modifiable &= !FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE;
}
if (tx_modifiable & FLAG_HAS_SIGHASH_SINGLE) != 0 {
self.tx_modifiable |= FLAG_HAS_SIGHASH_SINGLE;
}
if ((self.tx_modifiable & !FLAG_SHIELDED_MODIFIABLE) >> 3) != 0
|| ((tx_modifiable & !FLAG_SHIELDED_MODIFIABLE) >> 3) != 0
{
return None;
}
if (tx_modifiable & FLAG_SHIELDED_MODIFIABLE) == 0 {
self.tx_modifiable &= !FLAG_SHIELDED_MODIFIABLE;
}
if !merge_map(&mut self.proprietary, proprietary) {
return None;
}
Some(self)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) struct Zip32Derivation {
pub(crate) seed_fingerprint: [u8; 32],
pub(crate) derivation_path: Vec<u32>,
}
pub fn determine_lock_time<L: LockTimeInput>(
global: &crate::common::Global,
inputs: &[L],
) -> Option<u32> {
let have_required_lock_time = inputs.iter().any(|input| {
input.required_time_lock_time().is_some() || input.required_height_lock_time().is_some()
});
let time_lock_time_unsupported = inputs
.iter()
.any(|input| input.required_height_lock_time().is_some());
let height_lock_time_unsupported = inputs
.iter()
.any(|input| input.required_time_lock_time().is_some());
match (
have_required_lock_time,
time_lock_time_unsupported,
height_lock_time_unsupported,
) {
(true, true, true) => None,
(true, false, true) => Some(
inputs
.iter()
.filter_map(|input| input.required_time_lock_time())
.max()
.expect("iterator is non-empty because have_required_lock_time is true"),
),
(true, _, false) => Some(
inputs
.iter()
.filter_map(|input| input.required_height_lock_time())
.max()
.expect("iterator is non-empty because have_required_lock_time is true"),
),
(false, _, _) => Some(global.fallback_lock_time.unwrap_or(0)),
}
}
pub trait LockTimeInput {
fn required_time_lock_time(&self) -> Option<u32>;
fn required_height_lock_time(&self) -> Option<u32>;
}
impl LockTimeInput for crate::transparent::Input {
fn required_time_lock_time(&self) -> Option<u32> {
self.required_time_lock_time
}
fn required_height_lock_time(&self) -> Option<u32> {
self.required_height_lock_time
}
}
#[cfg(feature = "transparent")]
impl LockTimeInput for ::transparent::pczt::Input {
fn required_time_lock_time(&self) -> Option<u32> {
*self.required_time_lock_time()
}
fn required_height_lock_time(&self) -> Option<u32> {
*self.required_height_lock_time()
}
}
#[cfg(test)]
mod tests {
use alloc::collections::BTreeMap;
use super::Global;
#[test]
fn tx_modifiable() {
let base = Global {
tx_version: 0,
version_group_id: 0,
consensus_branch_id: 0,
fallback_lock_time: None,
expiry_height: 0,
coin_type: 0,
tx_modifiable: 0b0000_0000,
proprietary: BTreeMap::new(),
};
for (left, right, expected) in [
(0b0000_0000, 0b0000_0000, Some(0b0000_0000)),
(0b0000_0000, 0b0000_0011, Some(0b0000_0000)),
(0b0000_0001, 0b0000_0011, Some(0b0000_0001)),
(0b0000_0010, 0b0000_0011, Some(0b0000_0010)),
(0b0000_0011, 0b0000_0011, Some(0b0000_0011)),
(0b0000_0000, 0b0000_0100, Some(0b0000_0100)),
(0b0000_0100, 0b0000_0100, Some(0b0000_0100)),
(0b0000_0011, 0b0000_0111, Some(0b0000_0111)),
(0b0000_0000, 0b0000_1000, None),
(0b0000_0000, 0b0001_0000, None),
(0b0000_0000, 0b0010_0000, None),
(0b0000_0000, 0b0100_0000, None),
(0b0000_0000, 0b1000_0000, Some(0b0000_0000)),
(0b1000_0000, 0b1000_0000, Some(0b1000_0000)),
] {
let mut a = base.clone();
a.tx_modifiable = left;
let mut b = base.clone();
b.tx_modifiable = right;
assert_eq!(
a.clone()
.merge(b.clone())
.map(|global| global.tx_modifiable),
expected
);
assert_eq!(b.merge(a).map(|global| global.tx_modifiable), expected);
}
}
}