In Tapscript Miniscript descriptors (tr()), public keys serialize as 32-byte x-only pubkeys per BIP-340, BIP-342, and BIP-379, discarding parity. However, KeyParser::KeyCompare in src/script/descriptor.cpp compared keys as full 33-byte CPubKeys, retaining the parity byte.
When a descriptor contains public keys that differ only in parity prefix (such as pk(X) alongside pk(03X) where X defaults to 02, or pk(02X) alongside pk(03X)), KeyParser::KeyCompare treated them as distinct keys. Miniscript duplicate key checking passed, declaring the descriptor sane even though both branches serialize on-chain to <X> OP_CHECKSIG. Because a single signature satisfies both branches, third parties could malleate transactions by altering branch selectors without holding private keys, violating BIP-379 non-malleability invariants.
This patch updates KeyParser::KeyCompare in src/script/descriptor.cpp and KeyConverter::KeyCompare in src/test/miniscript_tests.cpp to compare keys as XOnlyPubKey whenever miniscript::IsTapscript(m_script_ctx) evaluates true. Under Tapscript context, keys sharing identical x-coordinates compare equivalent (!comp(a, b) && !comp(b, a)), ensuring DuplicateKeyCheck() detects the collision and rejects the descriptor. In src/test/descriptor_tests.cpp, unit tests verify that tr() descriptors containing parity-differing duplicate keys (including public hex and private WIF formats) are rejected as not sane, while wsh() descriptors continue to treat 33-byte compressed keys with differing parities as distinct.
Fixes #36414.