Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions packages/wasm-utxo/js/fixedScriptWallet/ZcashBitGoPsbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,4 +330,34 @@ export class ZcashBitGoPsbt extends BitGoPsbt<ZcashParsedOutput> {
override extractTransaction(maxFeeRate?: number): ZcashTransaction {
return ZcashTransaction.fromWasm(this.wasm.extract_zcash_transaction(maxFeeRate));
}

/**
* Add a plain transparent output. Just `script`/`value` (no `unifiedAddress`) is the same as
* the generic {@link BitGoPsbt.addOutput}, unchanged.
*
* If `unifiedAddress` is given, it must be a Unified Address whose transparent receiver is
* exactly `script` — mismatches throw rather than being silently stored. It is then kept
* verbatim, keyed by this output's index, so {@link ZcashBitGoPsbt.transparentOutputUnifiedAddress}
* can later return the original UA string rather than just the bare scriptPubkey.
*
* `unifiedAddress` is only supported on a legacy v4 `ZcashBitGoPsbt` — the v6 (Ironwood)
* shielded side has its own UA path (`ZcashIronwoodBitGoPsbt.addShieldedOutput`'s
* `unifiedAddress` option).
*
* @param script - The output scriptPubkey
* @param value - The value in zatoshi
* @param unifiedAddress - Optional full Unified Address `script` was resolved from
* @returns The index of the newly added output
*/
addTransparentOutput(script: Uint8Array, value: bigint, unifiedAddress?: string): number {
return this.wasm.add_transparent_output(script, value, unifiedAddress);
}

/**
* The Unified Address stored by {@link ZcashBitGoPsbt.addTransparentOutput} for output `index`,
* if one was supplied — `undefined` otherwise.
*/
transparentOutputUnifiedAddress(index: number): string | undefined {
return this.wasm.transparent_output_unified_address(index);
}
}
28 changes: 23 additions & 5 deletions packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2728,11 +2728,29 @@ impl BitGoPsbt {
.zip(psbt.outputs.iter())
.enumerate()
.map(|(output_index, (tx_output, psbt_output))| {
ParsedOutput::parse(psbt_output, tx_output, wallet_keys, network, paygo_pubkeys)
.map_err(|error| ParseTransactionError::Output {
index: output_index,
error,
})
let mut parsed = ParsedOutput::parse(
psbt_output,
tx_output,
wallet_keys,
network,
paygo_pubkeys,
)
.map_err(|error| ParseTransactionError::Output {
index: output_index,
error,
})?;
// Prefer the caller's original Unified Address (if `add_transparent_output` was
// given one for this output): mirrors `shielded_outputs`'s treatment of
// `add_ironwood_output`'s UA — the caller-supplied UA, not a bare address
// reconstructed from the scriptPubKey alone, is what a client actually pasted in.
if let BitGoPsbt::Zcash(z, _) = self {
if let Some(ua) =
propkv::get_transparent_output_unified_address(&z.psbt, output_index)
{
parsed.address = Some(ua);
}
}
Ok(parsed)
})
.collect()
}
Expand Down
40 changes: 40 additions & 0 deletions packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/propkv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,14 @@ pub enum ZecV6KeySubtype {
/// storing the original string here lets output parsing return the exact UA the caller passed,
/// receivers and all, after a serialize/deserialize round-trip.
UnifiedAddress = 0x05,
/// The full ZIP-316 Unified Address string (UTF-8) a plain transparent output (identified by
/// its index in `unsigned_tx.output`) was addressed to, if the caller supplied one to
/// [`crate::fixed_script_wallet::bitgo_psbt::zcash_psbt`]'s `add_transparent_output`. Mirrors
/// `UnifiedAddress` above, but keyed by transparent output index rather than Orchard action
/// index, and stored only for the output's transparent receiver (already fully recoverable
/// from the scriptPubKey) — kept so output parsing can hand back the exact UA the caller
/// passed, not one reconstructed as a bare transparent address.
TransparentUnifiedAddress = 0x06,
}

fn set_zec_v6(
Expand Down Expand Up @@ -433,6 +441,38 @@ pub fn clear_ironwood_unified_addresses(psbt: &mut miniscript::bitcoin::psbt::Ps
});
}

/// Store the full Unified Address string one plain transparent output (identified by its
/// `output_index` in `unsigned_tx.output`) was addressed to, so it survives a
/// serialize/deserialize round-trip verbatim rather than being lost down to just the
/// scriptPubKey. Keyed by `output_index`. Overwrites any existing value for that index.
pub fn set_transparent_output_unified_address(
psbt: &mut miniscript::bitcoin::psbt::Psbt,
output_index: usize,
ua: &str,
) {
let key = ProprietaryKey {
prefix: BITGO_ZEC_V6.to_vec(),
subtype: ZecV6KeySubtype::TransparentUnifiedAddress as u8,
key: (output_index as u32).to_le_bytes().to_vec(),
};
psbt.proprietary.insert(key, ua.as_bytes().to_vec());
}

/// Fetch the Unified Address string stored by [`set_transparent_output_unified_address`] for
/// `output_index`, if present and valid UTF-8.
pub fn get_transparent_output_unified_address(
psbt: &miniscript::bitcoin::psbt::Psbt,
output_index: usize,
) -> Option<String> {
let key = ProprietaryKey {
prefix: BITGO_ZEC_V6.to_vec(),
subtype: ZecV6KeySubtype::TransparentUnifiedAddress as u8,
key: (output_index as u32).to_le_bytes().to_vec(),
};
let bytes = psbt.proprietary.get(&key)?;
String::from_utf8(bytes.clone()).ok()
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading
Loading