From e9371ee98aac9fe057c42c64b0c8e53701eba27b Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Wed, 3 Jun 2026 17:39:59 +0200 Subject: [PATCH] feat(wasm-utxo): expose ZEC branch ID APIs on ZcashBitGoPsbt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds consensus_branch_id() to the wasm_bindgen BitGoPsbt impl (Zcash variant only; returns None for non-ZEC PSBTs). Adds a zcash_branch_id_for_height() free function wrapping the existing Rust branch_id_for_height, returning a Result so it throws on unrecognised network strings ("zcash"/"zec" for mainnet, "zcashTest"/"tzec" for testnet — anything else is an error). TypeScript ZcashBitGoPsbt gains: consensusBranchId: number | undefined — reads the branch ID stored in the PSBT proprietary map (absent for v5 or non-ZEC PSBTs). static branchIdForHeight(network, height): number | undefined — returns the consensus branch ID active at a given chain height. These APIs allow callers (e.g. ims-utxo parse path) to validate a PSBT's consensus branch ID against the current chain height without importing @bitgo-beta/utxo-lib. Refs: T1-3519 --- .../js/fixedScriptWallet/ZcashBitGoPsbt.ts | 18 ++++++++++- .../src/wasm/fixed_script_wallet/mod.rs | 31 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/wasm-utxo/js/fixedScriptWallet/ZcashBitGoPsbt.ts b/packages/wasm-utxo/js/fixedScriptWallet/ZcashBitGoPsbt.ts index fc588ecc2ae..31291680708 100644 --- a/packages/wasm-utxo/js/fixedScriptWallet/ZcashBitGoPsbt.ts +++ b/packages/wasm-utxo/js/fixedScriptWallet/ZcashBitGoPsbt.ts @@ -1,4 +1,4 @@ -import { BitGoPsbt as WasmBitGoPsbt } from "../wasm/wasm_utxo.js"; +import { BitGoPsbt as WasmBitGoPsbt, zcash_branch_id_for_height } from "../wasm/wasm_utxo.js"; import { type WalletKeysArg, RootWalletKeys } from "./RootWalletKeys.js"; import { BitGoPsbt, type CreateEmptyOptions, type HydrationUnspent } from "./BitGoPsbt.js"; import { ZcashTransaction, type ITransaction } from "../transaction.js"; @@ -264,6 +264,22 @@ export class ZcashBitGoPsbt extends BitGoPsbt { return this.wasm.expiry_height(); } + /** + * Get the Zcash consensus branch ID stored in the PSBT proprietary map. + * Returns undefined for v5 PSBTs or PSBTs without the key. + */ + get consensusBranchId(): number | undefined { + return this.wasm.consensus_branch_id(); + } + + /** + * Return the Zcash consensus branch ID active at `height` on `network`. + * Returns undefined if `height` is before Overwinter activation. + */ + static branchIdForHeight(network: ZcashNetworkName, height: number): number | undefined { + return zcash_branch_id_for_height(network, height); + } + /** * Extract the final Zcash transaction from a finalized PSBT * diff --git a/packages/wasm-utxo/src/wasm/fixed_script_wallet/mod.rs b/packages/wasm-utxo/src/wasm/fixed_script_wallet/mod.rs index 8c021f32cee..0a8675beeb8 100644 --- a/packages/wasm-utxo/src/wasm/fixed_script_wallet/mod.rs +++ b/packages/wasm-utxo/src/wasm/fixed_script_wallet/mod.rs @@ -961,6 +961,17 @@ impl BitGoPsbt { } } + /// Get the Zcash consensus branch ID from the PSBT proprietary map (returns None for non-Zcash PSBTs) + pub fn consensus_branch_id(&self) -> Option { + use crate::fixed_script_wallet::bitgo_psbt::{ + propkv::get_zec_consensus_branch_id, BitGoPsbt as InnerBitGoPsbt, + }; + match &self.psbt { + InnerBitGoPsbt::Zcash(z, _) => get_zec_consensus_branch_id(&z.psbt), + _ => None, + } + } + pub fn get_outputs_with_address(&self) -> Result { crate::wasm::psbt::get_outputs_with_address_from_psbt(self.psbt.psbt(), self.psbt.network()) } @@ -1943,3 +1954,23 @@ impl BitGoPsbt { } impl_wasm_psbt_ops!(BitGoPsbt, psbt); + +/// Return the Zcash consensus branch ID active at `height` on `network`. +/// +/// `network`: "zcash" / "zec" for mainnet, "zcashTest" / "tzec" for testnet. +/// Returns `None` if `height` is before Overwinter activation. +/// Throws if `network` is not a recognised Zcash network name. +#[wasm_bindgen] +pub fn zcash_branch_id_for_height(network: &str, height: u32) -> Result, JsValue> { + let is_mainnet = match network { + "zcash" | "zec" => true, + "zcashTest" | "tzec" => false, + _ => { + return Err(JsValue::from_str(&format!( + "unknown Zcash network {:?}: expected \"zcash\", \"zec\", \"zcashTest\", or \"tzec\"", + network + ))) + } + }; + Ok(crate::zcash::branch_id_for_height(height, is_mainnet)) +}