@@ -5415,4 +5415,111 @@ mod tests {
54155415 // Result should be sorted back to [user, backup, bitgo]
54165416 assert_eq ! ( result. xpubs, xpubs) ;
54175417 }
5418+
5419+ /// Regression test: ZIP-243 sighash must be correct when the outputs preimage is exactly
5420+ /// 256 bytes (an exact multiple of the 128-byte BLAKE2b block size).
5421+ ///
5422+ /// A transaction with 8 P2SH outputs produces a 256-byte outputs preimage:
5423+ /// each P2SH output serializes to 8 (value) + 1 (varint 23) + 23 (script) = 32 bytes,
5424+ /// so 8 × 32 = 256 = 2 × 128.
5425+ ///
5426+ /// The old `blake2b_256_personal` implementation processed all complete blocks with the
5427+ /// finalization flag `f=0`, leaving an empty buffer for `finalize_variable_core`.
5428+ /// That caused a spurious all-zero block to be compressed as the final block, producing
5429+ /// the wrong `hashOutputs` and therefore a wrong sighash — making every signature over
5430+ /// such a transaction invalid.
5431+ ///
5432+ /// This test uses a pre-computed signature from the fixed implementation as a test vector.
5433+ /// A sign-then-verify round-trip would pass even on the buggy version because both sides
5434+ /// would agree on the same (wrong) sighash. Verifying a pre-generated correct signature
5435+ /// fails on the buggy version because the verifier recomputes a different (wrong) sighash.
5436+ #[ test]
5437+ fn test_zcash_sighash_block_aligned_outputs ( ) {
5438+ use crate :: fixed_script_wallet:: test_utils:: get_test_wallet_keys;
5439+ use crate :: zcash:: NetworkUpgrade ;
5440+ use miniscript:: bitcoin:: ecdsa:: Signature as EcdsaSig ;
5441+ use miniscript:: bitcoin:: hashes:: Hash ;
5442+ use miniscript:: bitcoin:: secp256k1:: Secp256k1 ;
5443+ use miniscript:: bitcoin:: { CompressedPublicKey , PublicKey , Txid } ;
5444+
5445+ let seed = "zcash_block_aligned" ;
5446+ let secp = Secp256k1 :: new ( ) ;
5447+
5448+ let wallet_keys = RootWalletKeys :: new ( get_test_wallet_keys ( seed) ) ;
5449+
5450+ let sapling_height = NetworkUpgrade :: Sapling . testnet_activation_height ( ) ;
5451+ let mut psbt = BitGoPsbt :: new_zcash_at_height (
5452+ Network :: ZcashTestnet ,
5453+ & wallet_keys,
5454+ sapling_height,
5455+ None ,
5456+ None ,
5457+ None ,
5458+ None ,
5459+ )
5460+ . expect ( "new_zcash_at_height" ) ;
5461+
5462+ // 1 input spending a P2SH output
5463+ psbt. add_wallet_input (
5464+ Txid :: all_zeros ( ) ,
5465+ 0 ,
5466+ 100_000 ,
5467+ & wallet_keys,
5468+ ScriptId { chain : 0 , index : 0 } ,
5469+ WalletInputOptions :: default ( ) ,
5470+ )
5471+ . expect ( "add_wallet_input" ) ;
5472+
5473+ // 8 P2SH outputs — each is 32 bytes (8 value + 1 varint + 23 script),
5474+ // so the outputs preimage is 8 × 32 = 256 bytes = 2 BLAKE2b blocks.
5475+ for i in 0 ..8u32 {
5476+ psbt. add_wallet_output ( 0 , i, 10_000 , & wallet_keys)
5477+ . expect ( "add_wallet_output" ) ;
5478+ }
5479+
5480+ // Pre-computed test vector: user key signature produced by the fixed implementation.
5481+ // Derived from seed "zcash_block_aligned", user key at m/0/0/0/0.
5482+ let sig_bytes = hex:: decode (
5483+ "3045022100b480fdda99f4a7c5bf62374204e88a336c355b282470c156e2d632987ca0313a\
5484+ 02203abd32d68fc95c56b27be9608b4cd34ad16a09b6854c35bf9d41989d67a46d9401",
5485+ )
5486+ . unwrap ( ) ;
5487+ let pubkey_bytes =
5488+ hex:: decode ( "039c8019dee0882c7625ad609a54d19fe9996941c0f73256fcabe1f699c9ff2816" )
5489+ . unwrap ( ) ;
5490+
5491+ let compressed = CompressedPublicKey :: from_slice ( & pubkey_bytes) . expect ( "compressed pubkey" ) ;
5492+ let pubkey = PublicKey :: new ( compressed. 0 ) ;
5493+ let sig = EcdsaSig :: from_slice ( & sig_bytes) . expect ( "pre-computed sig" ) ;
5494+
5495+ // Insert the pre-computed signature into the PSBT instead of signing.
5496+ // This ensures verification uses the fixed implementation's sighash as the reference.
5497+ let BitGoPsbt :: Zcash ( ref mut zcash_psbt, _) = psbt else {
5498+ panic ! ( "expected Zcash PSBT variant" ) ;
5499+ } ;
5500+ zcash_psbt. psbt . inputs [ 0 ] . partial_sigs . insert ( pubkey, sig) ;
5501+
5502+ let consensus_branch_id =
5503+ propkv:: get_zec_consensus_branch_id ( & zcash_psbt. psbt ) . expect ( "branch id" ) ;
5504+ let version_group_id = zcash_psbt
5505+ . version_group_id
5506+ . unwrap_or ( zcash_psbt:: ZCASH_SAPLING_VERSION_GROUP_ID ) ;
5507+ let expiry_height = zcash_psbt. expiry_height . unwrap_or ( 0 ) ;
5508+
5509+ let ok = psbt_wallet_input:: verify_ecdsa_signature_zcash (
5510+ & secp,
5511+ & zcash_psbt. psbt ,
5512+ 0 ,
5513+ compressed,
5514+ consensus_branch_id,
5515+ version_group_id,
5516+ expiry_height,
5517+ )
5518+ . expect ( "verify_ecdsa_signature_zcash" ) ;
5519+
5520+ assert ! (
5521+ ok,
5522+ "Zcash signature over 256-byte (block-aligned) outputs preimage must verify"
5523+ ) ;
5524+ }
54185525}
0 commit comments