Closes #27583.
PSBT_GLOBAL_XPUB already serializes, deserializes and merges when PSBTs are combined. Nothing writes it, so a PSBT produced by the wallet never carries it.
The original motivation was the Ledger, and BIP 388 solved that for some devices, with Sjors working on the registration path. The field is still needed whenever the signer has no registered policy to work from. HWI's BitBox02 driver builds the multisig script config out of the PSBT's extended keys and refuses to sign without them, and HWI's own test harness marks that device as needing them, include_xpubs = True in test/test_bitbox02.py against False for the Trezor. Of the seven device drivers in HWI only those two read the field at all; the Ledger, Coldcard and Jade all go through a registration of their own. Today Specter fills the field itself after getting the PSBT from Core, and only when the wallet has more than one key.
<details><summary>Before and after with the BitBox02 and Trezor simulators</summary>
A 2-of-3 wsh(sortedmulti(...)) on regtest, with the simulator as one co-signer and two keys from Core wallets. The PSBT comes from walletcreatefundedpsbt on this branch; the "before" file is that same PSBT with its three PSBT_GLOBAL_XPUB records removed, so nothing else differs. HWI 3.2.0, BitBox02 simulator at firmware/v9.24.0, the version HWI pins.
without PSBT_GLOBAL_XPUB: REFUSED -> BadArgumentError: This BitBox02 is not one of the cosigners
with PSBT_GLOBAL_XPUB : SIGNED, 1 partial signature(s)
The refusal comes from _multisig_scriptconfig, which looks for the device's own xpub among the PSBT's and raises when the map is empty. Registering the account needs a name passed in, since HWI sends an empty one and the device then asks for it interactively, which a headless simulator cannot answer.
The Trezor T simulator signs either way, but it is handed different data. parse_multisig in its driver fills each co-signer node from the PSBT's global scope, and with nothing there it sends placeholders:
without PSBT_GLOBAL_XPUB
input : global xpubs=0 zeroed chain codes=3/3 depths=[0, 0, 0] paths=[[], [], []]
change: global xpubs=0 zeroed chain codes=3/3 depths=[0, 0, 0] paths=[[], [], []]
result: SIGNED, 1 partial signature(s)
with PSBT_GLOBAL_XPUB
input : global xpubs=3 zeroed chain codes=0/3 depths=[4, 4, 4] paths=[[0, 0], [0, 0], [0, 0]]
change: global xpubs=3 zeroed chain codes=0/3 depths=[4, 4, 4] paths=[[1, 0], [1, 0], [1, 0]]
result: SIGNED, 1 partial signature(s)
On this firmware the signature, the confirmation prompts and the screens are identical either way. What the field changes is that the device is told the truth instead of a placeholder.
</details>
Descriptor gains an accessor for its extended keys, each with the origin taken at the deepest hardened step, and DescriptorScriptPubKeyMan::FillPSBT writes them when the descriptor contributed to the PSBT. That covers walletcreatefundedpsbt, walletprocesspsbt, send, sendall, psbtbumpfee and the GUI send dialog, which all go through CWallet::FillPSBT.
The field is only written for descriptors with two or more extended keys. With one, the signer derives its own key from its seed, so the entry would add nothing and only cost space. The field is not small. On a 2-of-3 it adds 291 bytes, taking the PSBT from 933 to 1224. bip32derivs is respected. That same PSBT already carries the full witness_script and bip32_derivs, so what is added is the chain code of each account.
There are unit tests for the accessor, and on the wallet side a functional test with a wallet whose internal descriptor uses different keys from its external one, the only arrangement that can show it publishing on its own.
descriptorprocesspsbt is left out, since it signs from the descriptors it is given without going through the wallet, and I intend to cover it as a follow-up. musig() contributes nothing, since derivation is applied to the aggregate key.
The decodepsbt output changes, and a release note is included.