@@ -133,21 +133,58 @@ pub fn is_musig2_key(key: &ProprietaryKey) -> bool {
133133/// The consensus branch ID is stored as a 4-byte little-endian u32 value
134134/// under the BitGo proprietary key with subtype `ZecConsensusBranchId` (0x00).
135135///
136+ /// This function checks both the parsed `proprietary` map (where wasm-utxo stores it)
137+ /// and the raw `unknown` map (where utxolib stores it) for compatibility.
138+ ///
139+ /// # Temporary Compatibility Note
140+ ///
141+ /// The fallback to the `unknown` map is a **temporary workaround** needed because
142+ /// BitGoJS currently uses a mix of `utxo-lib` (TypeScript) and `wasm-utxo` (Rust/WASM)
143+ /// for PSBT operations. When `utxo-lib` serializes a PSBT, it stores proprietary keys
144+ /// in a format that ends up in the raw `unknown` map when deserialized by rust-bitcoin,
145+ /// rather than the parsed `proprietary` map.
146+ ///
147+ /// Once BitGoJS fully migrates to `wasm-utxo` for all Zcash PSBT operations, this
148+ /// fallback can be removed and the function can return to only checking `proprietary`.
149+ ///
136150/// # Returns
137151/// - `Some(u32)` if the consensus branch ID is present and valid
138152/// - `None` if the key is not present or the value is malformed
139153pub fn get_zec_consensus_branch_id ( psbt : & miniscript:: bitcoin:: psbt:: Psbt ) -> Option < u32 > {
140- let kv = find_kv (
154+ // First try the proprietary map (where wasm-utxo stores it)
155+ if let Some ( kv) = find_kv (
141156 ProprietaryKeySubtype :: ZecConsensusBranchId ,
142157 & psbt. proprietary ,
143158 )
144- . next ( ) ? ;
145- if kv . value . len ( ) == 4 {
146- let bytes : [ u8 ; 4 ] = kv. value . as_slice ( ) . try_into ( ) . ok ( ) ? ;
147- Some ( u32 :: from_le_bytes ( bytes ) )
148- } else {
149- None
159+ . next ( )
160+ {
161+ if kv. value . len ( ) == 4 {
162+ let bytes : [ u8 ; 4 ] = kv . value . as_slice ( ) . try_into ( ) . ok ( ) ? ;
163+ return Some ( u32 :: from_le_bytes ( bytes ) ) ;
164+ }
150165 }
166+
167+ // TEMPORARY: Also check the unknown map (where utxolib stores it as raw key-value pairs)
168+ // This is needed for compatibility while BitGoJS uses a mix of utxo-lib and wasm-utxo.
169+ // The key format from utxolib is: 0xfc + varint(5) + "BITGO" + 0x00
170+ // In rust-bitcoin's raw::Key struct:
171+ // - type_value: u8 = 0xfc (proprietary key type)
172+ // - key: Vec<u8> = [0x05, 'B', 'I', 'T', 'G', 'O', 0x00] (varint len + identifier + subtype)
173+ let expected_key_data: & [ u8 ] = & [
174+ 0x05 , // length of identifier (varint)
175+ b'B' , b'I' , b'T' , b'G' , b'O' , // "BITGO"
176+ 0x00 , // ZecConsensusBranchId subtype
177+ ] ;
178+
179+ for ( key, value) in & psbt. unknown {
180+ // Check if this is a proprietary key (0xfc) with the expected key data
181+ if key. type_value == 0xfc && key. key . as_slice ( ) == expected_key_data && value. len ( ) == 4 {
182+ let bytes: [ u8 ; 4 ] = value. as_slice ( ) . try_into ( ) . ok ( ) ?;
183+ return Some ( u32:: from_le_bytes ( bytes) ) ;
184+ }
185+ }
186+
187+ None
151188}
152189
153190/// Set Zcash consensus branch ID in PSBT global proprietary map.
@@ -239,4 +276,76 @@ mod tests {
239276 assert_eq ! ( NetworkUpgrade :: Nu5 . branch_id( ) , 0xc2d6d0b4 ) ;
240277 assert_eq ! ( NetworkUpgrade :: Nu6 . branch_id( ) , 0xc8e71055 ) ;
241278 }
279+
280+ #[ test]
281+ fn test_zec_consensus_branch_id_from_unknown_map ( ) {
282+ use crate :: zcash:: NetworkUpgrade ;
283+ use miniscript:: bitcoin:: psbt:: raw:: Key ;
284+ use miniscript:: bitcoin:: psbt:: Psbt ;
285+ use miniscript:: bitcoin:: Transaction ;
286+
287+ // Create a minimal PSBT
288+ let tx = Transaction {
289+ version : miniscript:: bitcoin:: transaction:: Version :: TWO ,
290+ lock_time : miniscript:: bitcoin:: locktime:: absolute:: LockTime :: ZERO ,
291+ input : vec ! [ ] ,
292+ output : vec ! [ ] ,
293+ } ;
294+ let mut psbt = Psbt :: from_unsigned_tx ( tx) . unwrap ( ) ;
295+
296+ // Initially no branch ID
297+ assert_eq ! ( get_zec_consensus_branch_id( & psbt) , None ) ;
298+
299+ // Simulate how utxolib stores the consensus branch ID in the unknown map
300+ // In rust-bitcoin's raw::Key struct:
301+ // - type_value: 0xfc (proprietary key type)
302+ // - key: [0x05, 'B', 'I', 'T', 'G', 'O', 0x00] (varint len + identifier + subtype)
303+ let utxolib_key = Key {
304+ type_value : 0xfc , // proprietary key type
305+ key : vec ! [
306+ 0x05 , // length of identifier (varint)
307+ b'B' , b'I' , b'T' , b'G' , b'O' , // "BITGO"
308+ 0x00 , // ZecConsensusBranchId subtype
309+ ] ,
310+ } ;
311+
312+ let nu5_branch_id = NetworkUpgrade :: Nu5 . branch_id ( ) ;
313+ let value = nu5_branch_id. to_le_bytes ( ) . to_vec ( ) ;
314+ psbt. unknown . insert ( utxolib_key, value) ;
315+
316+ // Should be retrievable from the unknown map
317+ assert_eq ! ( get_zec_consensus_branch_id( & psbt) , Some ( nu5_branch_id) ) ;
318+ }
319+
320+ #[ test]
321+ fn test_zec_consensus_branch_id_proprietary_takes_precedence ( ) {
322+ use crate :: zcash:: NetworkUpgrade ;
323+ use miniscript:: bitcoin:: psbt:: raw:: Key ;
324+ use miniscript:: bitcoin:: psbt:: Psbt ;
325+ use miniscript:: bitcoin:: Transaction ;
326+
327+ // Create a minimal PSBT
328+ let tx = Transaction {
329+ version : miniscript:: bitcoin:: transaction:: Version :: TWO ,
330+ lock_time : miniscript:: bitcoin:: locktime:: absolute:: LockTime :: ZERO ,
331+ input : vec ! [ ] ,
332+ output : vec ! [ ] ,
333+ } ;
334+ let mut psbt = Psbt :: from_unsigned_tx ( tx) . unwrap ( ) ;
335+
336+ // Set one value in the unknown map (utxolib format)
337+ let utxolib_key = Key {
338+ type_value : 0xfc ,
339+ key : vec ! [ 0x05 , b'B' , b'I' , b'T' , b'G' , b'O' , 0x00 ] ,
340+ } ;
341+ let sapling_branch_id = NetworkUpgrade :: Sapling . branch_id ( ) ;
342+ psbt. unknown . insert ( utxolib_key, sapling_branch_id. to_le_bytes ( ) . to_vec ( ) ) ;
343+
344+ // Set a different value in the proprietary map (wasm-utxo format)
345+ let nu5_branch_id = NetworkUpgrade :: Nu5 . branch_id ( ) ;
346+ set_zec_consensus_branch_id ( & mut psbt, nu5_branch_id) ;
347+
348+ // The proprietary map should take precedence
349+ assert_eq ! ( get_zec_consensus_branch_id( & psbt) , Some ( nu5_branch_id) ) ;
350+ }
242351}
0 commit comments