Skip to content

Commit

Permalink
WIP: add support for transparent-source-only addresses.
Browse files Browse the repository at this point in the history
Signed-off-by: Daira-Emma Hopwood <[email protected]>
  • Loading branch information
daira committed Mar 12, 2024
1 parent a9aabb2 commit 24b8147
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 7 deletions.
2 changes: 2 additions & 0 deletions zcash_client_backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ and this library adheres to Rust's notion of
constraint on its `<AccountId>` parameter has been strengthened to `Copy`.
- `zcash_client_backend::fees`:
- Arguments to `ChangeStrategy::compute_balance` have changed.
- `zcash_client_backend::input_selection::GreedyInputSelectorError` has a
new variant `UnsupportedTransparentSourceOnlyAddress`.
- `zcash_client_backend::proto`:
- `ProposalDecodingError` has a new variant `TransparentMemo`.
- `zcash_client_backend::zip321::render::amount_str` now takes a
Expand Down
3 changes: 3 additions & 0 deletions zcash_client_backend/src/data_api/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,9 @@ where
}
transparent_output_meta.push((to, payment.amount));
}
Address::TransparentSourceOnly(_) => {
panic!("Transparent-source-only addresses should not occur at this stage");

Check warning on line 1036 in zcash_client_backend/src/data_api/wallet.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet.rs#L1035-L1036

Added lines #L1035 - L1036 were not covered by tests
}
}
}

Expand Down
154 changes: 153 additions & 1 deletion zcash_client_backend/src/data_api/wallet/input_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ use crate::{

#[cfg(feature = "transparent-inputs")]
use {
std::collections::BTreeSet, std::convert::Infallible,
crate::{
fees::TransactionBalance,
proposal::{StepOutput, StepOutputIndex},
zip321::Payment,
},
std::collections::BTreeSet,
std::convert::Infallible,
zcash_primitives::legacy::TransparentAddress,
zcash_primitives::transaction::components::OutPoint,
};
Expand Down Expand Up @@ -206,6 +212,8 @@ pub enum GreedyInputSelectorError<ChangeStrategyErrT, NoteRefT> {
Balance(BalanceError),
/// A unified address did not contain a supported receiver.
UnsupportedAddress(Box<UnifiedAddress>),
/// Support for transparent-source-only addresses requires the transparent-inputs feature.
UnsupportedTransparentSourceOnlyAddress,
/// An error was encountered in change selection.
Change(ChangeError<ChangeStrategyErrT, NoteRefT>),
}
Expand All @@ -223,6 +231,9 @@ impl<CE: fmt::Display, N: fmt::Display> fmt::Display for GreedyInputSelectorErro
// don't have network parameters here
write!(f, "Unified address contains no supported receivers.")
}
GreedyInputSelectorError::UnsupportedTransparentSourceOnlyAddress => {
write!(f, "Support for transparent-source-only addresses requires the transparent-inputs feature.")

Check warning on line 235 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L234-L235

Added lines #L234 - L235 were not covered by tests
}
GreedyInputSelectorError::Change(err) => {
write!(f, "An error occurred computing change and fees: {}", err)
}
Expand Down Expand Up @@ -343,6 +354,22 @@ where
#[cfg(feature = "orchard")]
let mut orchard_outputs = vec![];
let mut payment_pools = BTreeMap::new();
#[cfg(feature = "transparent-inputs")]
let mut ephemeral_outputs: Vec<(usize, Payment)> = vec![];

// TR - a
// - ephb
// - ephc
// - d
//
// becomes
//
// TR1 - a TR2 - b
// - eph-placeholder - c
// - d Proposal2
// - spend from eph-placeholder
//

for (idx, payment) in transaction_request.payments() {
match &payment.recipient_address {
Address::Transparent(addr) => {
Expand All @@ -352,6 +379,28 @@ where
script_pubkey: addr.script(),
});
}
#[cfg(feature = "transparent-inputs")]
Address::TransparentSourceOnly(data) => {
ephemeral_outputs.push((
*idx,
Payment {
recipient_address: Address::Transparent(
TransparentAddress::PublicKeyHash(*data),

Check warning on line 388 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L383-L388

Added lines #L383 - L388 were not covered by tests
),
amount: payment.amount,
memo: None,
label: payment.label.clone(),
message: payment.message.clone(),
other_params: payment.other_params.clone(),

Check warning on line 394 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L390-L394

Added lines #L390 - L394 were not covered by tests
},
));
}
#[cfg(not(feature = "transparent-inputs"))]
Address::TransparentSourceOnly(_) => {
return Err(InputSelectorError::Selection(
GreedyInputSelectorError::UnsupportedTransparentSourceOnlyAddress,

Check warning on line 401 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L399-L401

Added lines #L399 - L401 were not covered by tests
));
}
Address::Sapling(_) => {
payment_pools.insert(*idx, PoolType::Shielded(ShieldedProtocol::Sapling));
sapling_outputs.push(SaplingPayment(payment.amount));
Expand Down Expand Up @@ -386,6 +435,99 @@ where
}
}

#[cfg(feature = "transparent-inputs")]
struct Zip320SecondStep {
transaction_request: TransactionRequest,
payment_pools: BTreeMap<usize, PoolType>,
prior_step_inputs: StepOutput,
balance: TransactionBalance,
}

// If ephemeral state was detected, handle it here.
#[cfg(feature = "transparent-inputs")]
let (transaction_request, second_step) = if ephemeral_outputs.is_empty() {
(transaction_request, None)
} else {
// Construct a new TransactionRequest from `transaction_request` that excludes
// the ephemeral outputs, and in their place includes a single transparent
// output to the legacy address for this account.

let mut proposal1_excludes = BTreeSet::new();
let mut proposal2_payments = vec![];
let mut proposal2_payment_pools = BTreeMap::new();
let mut total_ephemeral_plus_fee: NonNegativeAmount = (|| todo!())();
let (first_idx, _) = ephemeral_outputs[0];
for (idx2, (idx1, payment)) in ephemeral_outputs.into_iter().enumerate() {
total_ephemeral_plus_fee = (total_ephemeral_plus_fee + payment.amount).ok_or(
InputSelectorError::Selection(GreedyInputSelectorError::Balance(
BalanceError::Overflow,

Check warning on line 463 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L455-L463

Added lines #L455 - L463 were not covered by tests
)),
)?;
proposal1_excludes.insert(idx1);
proposal2_payments.push(payment);
proposal2_payment_pools.insert(idx2, PoolType::Transparent);

Check warning on line 468 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L466-L468

Added lines #L466 - L468 were not covered by tests
}

// TODO: Calculate fee for transaction request 2 and add it to
// total_ephemeral_plus_fee.

let proposal1_transaction_request = TransactionRequest::from_indexed(
transaction_request
.payments()
.iter()
.filter_map(|(idx, payment)| {

Check warning on line 478 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L475-L478

Added lines #L475 - L478 were not covered by tests
// If this is the first ephemeral output in the original request,
// replace it with a transparent output to the legacy address.
// We use the legacy address here only as a dummy address for the
// ZIP-321 URI serialized in the proposal. It will be replaced by
// a unique ephemeral P2PKH address when the proposal is created.
if *idx == first_idx {
let legacy_address: TransparentAddress = (|| todo!())();
transparent_outputs.push(TxOut {
value: total_ephemeral_plus_fee,
script_pubkey: legacy_address.script(),

Check warning on line 488 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L484-L488

Added lines #L484 - L488 were not covered by tests
});
payment_pools.insert(first_idx, PoolType::Transparent);
Some((
first_idx,
Payment {
recipient_address: Address::Transparent(legacy_address),
amount: total_ephemeral_plus_fee,
memo: None,
label: None,
message: None,

Check warning on line 498 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L490-L498

Added lines #L490 - L498 were not covered by tests
// TODO: Add ZIP 320 ephemeral marker, or shift this
// into being a change output (which seems to need
// changes to the Proposal protobuf, as it doesn't
// explicitly store change, or have any way to mark
// the ephemeral output).
other_params: vec![],

Check warning on line 504 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L504

Added line #L504 was not covered by tests
},
))
} else if proposal1_excludes.contains(&idx) {
None

Check warning on line 508 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L507-L508

Added lines #L507 - L508 were not covered by tests
} else {
Some((*idx, payment.clone()))

Check warning on line 510 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L510

Added line #L510 was not covered by tests
}
})
.collect(),

Check warning on line 513 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L513

Added line #L513 was not covered by tests
)
.expect("correct by construction");
let proposal2_transaction_request =
TransactionRequest::new(proposal2_payments).expect("correct by construction");

Check warning on line 517 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L516-L517

Added lines #L516 - L517 were not covered by tests

(
proposal1_transaction_request,
Some(Zip320SecondStep {
transaction_request: proposal2_transaction_request,
payment_pools: proposal2_payment_pools,

Check warning on line 523 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L520-L523

Added lines #L520 - L523 were not covered by tests
// TODO: Update this depending on how the ephemeral output is represented.
prior_step_inputs: StepOutput::new(0, StepOutputIndex::Payment(first_idx)),
balance: (|| todo!())(),

Check warning on line 526 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L525-L526

Added lines #L525 - L526 were not covered by tests
}),
)
};

let mut shielded_inputs: Vec<ReceivedNote<DbT::NoteRef, Note>> = vec![];
let mut prior_available = NonNegativeAmount::ZERO;
let mut amount_required = NonNegativeAmount::ZERO;
Expand Down Expand Up @@ -432,6 +574,16 @@ where

match balance {
Ok(balance) => {
#[cfg(feature = "transparent-inputs")]

Check warning on line 577 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L577

Added line #L577 was not covered by tests
if let Some(zip320_step) = second_step {
return Proposal::multi_step(
(*self.change_strategy.fee_rule()).clone(),
target_height,
(|| todo!())(),

Check warning on line 582 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L579-L582

Added lines #L579 - L582 were not covered by tests
)
.map_err(InputSelectorError::Proposal);

Check warning on line 584 in zcash_client_backend/src/data_api/wallet/input_selection.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/wallet/input_selection.rs#L584

Added line #L584 was not covered by tests
}

return Proposal::single_step(
transaction_request,
payment_pools,
Expand Down
6 changes: 4 additions & 2 deletions zcash_client_backend/src/zip321.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,9 @@ mod parse {
Param::Amount(a) => payment.amount = a,
Param::Memo(m) => match payment.recipient_address {
Address::Sapling(_) | Address::Unified(_) => payment.memo = Some(m),
Address::Transparent(_) => return Err(Zip321Error::TransparentMemo(i)),
Address::Transparent(_) | Address::TransparentSourceOnly(_) => {
return Err(Zip321Error::TransparentMemo(i))
}
},

Param::Label(m) => payment.label = Some(m),
Expand Down Expand Up @@ -767,7 +769,7 @@ pub mod testing {
other_params in btree_map(VALID_PARAMNAME, any::<String>(), 0..3),
) -> Payment {
let is_shielded = match recipient_address {
Address::Transparent(_) => false,
Address::Transparent(_) | Address::TransparentSourceOnly(_) => false,
Address::Sapling(_) | Address::Unified(_) => true,
};

Expand Down
4 changes: 3 additions & 1 deletion zcash_client_sqlite/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1266,7 +1266,9 @@ fn fake_compact_block_spending<P: consensus::Parameters, Fvk: TestFvk>(
)
.0,
),
Address::Transparent(_) => panic!("transparent addresses not supported in compact blocks"),
Address::Transparent(_) | Address::TransparentSourceOnly(_) => {
panic!("transparent addresses not supported in compact blocks")

Check warning on line 1270 in zcash_client_sqlite/src/testing.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_sqlite/src/testing.rs#L1269-L1270

Added lines #L1269 - L1270 were not covered by tests
}
Address::Unified(ua) => {
// This is annoying to implement, because the protocol-aware UA type has no
// concept of ZIP 316 preference order.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
idx)));
}
}
Address::Transparent(_) => {
Address::Transparent(_) | Address::TransparentSourceOnly(_) => {

Check warning on line 107 in zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs#L107

Added line #L107 was not covered by tests
return Err(WalletMigrationError::CorruptedData(
"Address field value decoded to a transparent address; should have been Sapling or unified.".to_string()));
}
Expand Down Expand Up @@ -231,7 +231,9 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
Address::Sapling(_) => {
Ok(pool_code(PoolType::Shielded(ShieldedProtocol::Sapling)))
}
Address::Transparent(_) => Ok(pool_code(PoolType::Transparent)),
Address::Transparent(_) | Address::TransparentSourceOnly(_) => {
Ok(pool_code(PoolType::Transparent))

Check warning on line 235 in zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs#L234-L235

Added lines #L234 - L235 were not covered by tests
}
Address::Unified(_) => Err(WalletMigrationError::CorruptedData(
"Unified addresses should not yet appear in the sent_notes table."
.to_string(),
Expand Down
23 changes: 22 additions & 1 deletion zcash_keys/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,22 @@ impl UnifiedAddress {
/// An address that funds can be sent to.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Address {
/// A Sapling payment address.
#[cfg(feature = "sapling")]
Sapling(PaymentAddress),

/// A transparent address corresponding to either a public key or a `Script`.
Transparent(TransparentAddress),

/// A [ZIP 316] Unified Address.
///
/// [ZIP 316]: https://zips.z.cash/zip-0316
Unified(UnifiedAddress),

/// A [ZIP 320] transparent-source-only P2PKH address, or "TEX address".
///
/// [ZIP 320]: https://zips.z.cash/zip-0320
TransparentSourceOnly([u8; 20]),
}

#[cfg(feature = "sapling")]
Expand Down Expand Up @@ -287,6 +299,10 @@ impl TryFromRawAddress for Address {
fn try_from_raw_transparent_p2sh(data: [u8; 20]) -> Result<Self, ConversionError<Self::Error>> {
Ok(TransparentAddress::ScriptHash(data).into())
}

fn try_from_raw_tex(data: [u8; 20]) -> Result<Self, ConversionError<Self::Error>> {
Ok(Address::TransparentSourceOnly(data))
}
}

impl Address {
Expand All @@ -310,6 +326,7 @@ impl Address {
}
},
Address::Unified(ua) => ua.to_address(net),
Address::TransparentSourceOnly(data) => ZcashAddress::from_tex(net, *data),
}
.to_string()
}
Expand All @@ -320,7 +337,9 @@ impl Address {
Address::Sapling(_) => {
matches!(pool_type, PoolType::Shielded(ShieldedProtocol::Sapling))
}
Address::Transparent(_) => matches!(pool_type, PoolType::Transparent),
Address::Transparent(_) | Address::TransparentSourceOnly(_) => {
matches!(pool_type, PoolType::Transparent)
}
Address::Unified(ua) => match pool_type {
PoolType::Transparent => ua.transparent().is_some(),
PoolType::Shielded(ShieldedProtocol::Sapling) => {
Expand Down Expand Up @@ -368,6 +387,7 @@ pub mod testing {
arb_payment_address().prop_map(Address::Sapling),
arb_transparent_addr().prop_map(Address::Transparent),
arb_unified_addr(Network::TestNetwork, request).prop_map(Address::Unified),
proptest::array::uniform20(any::<u8>()).prop_map(Address::TransparentSourceOnly),
]
}

Expand All @@ -376,6 +396,7 @@ pub mod testing {
return prop_oneof![
arb_transparent_addr().prop_map(Address::Transparent),
arb_unified_addr(Network::TestNetwork, request).prop_map(Address::Unified),
proptest::array::uniform20(any::<u8>()).prop_map(Address::TransparentSourceOnly),
];
}
}
Expand Down

0 comments on commit 24b8147

Please sign in to comment.