wallet/psbt/musig2: overpopulating participant leaf_hashes in utxoupdatepsbt and descriptorprocesspsbt #36323

issue guggero opened this issue on September 24, 2026
  1. guggero commented at 6:43 AM on September 24, 2026: contributor

    Is there an existing issue for this?

    • I have searched the existing issues

    Current behaviour

    My clanker noticed this while implementing musig2 descriptors in btcd. I've verified this on v31.1 manually and on master (31754523cd816eac48828ff42da63738afd66aae) with the reproducer test (see below).

    Problem

    For tr(musig(A,B),pk(C)), where A, B, and C are distinct public keys, the MuSig2 participants A and B are involved only in key-path spending. Their PSBT_IN_TAP_BIP32_DERIVATION and PSBT_OUT_TAP_BIP32_DERIVATION records should have empty leaf-hash lists. Only C's record should contain the hash of pk(C).

    Instead, both RPCs (utxoupdatepsbt and descriptorprocesspsbt) also put the hash of pk(C) in A's and B's records. Neither participant nor their aggregate occurs in that leaf.

    This comment/discussion also seems relevant. The suggested diff would have fixed the problem (but as the author mentioned would have changed a desired behavior).

    Expected behaviour

    Only the PSBT_IN_TAP_BIP32_DERIVATION and PSBT_OUT_TAP_BIP32_DERIVATION of participant C should have the hash of pk(C) populated.

    Steps to reproduce

    Using the example descriptor tr(musig([11111111]key_a,[22222222]key_b),pk([33333333]key_c)), this can easily be reproduced with an empty wallet:

    <details>

    <summary>`bitcoin-cli` reproduction commands (click to expand)</summary>

    
    $ bitcoin-cli getdescriptorinfo "tr(musig([11111111]0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,[22222222]02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5),pk([33333333]f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9))"
    
    
    {
      "descriptor": "tr(musig([11111111]0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,[22222222]02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5),pk([33333333]f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9))#3h4cjsd8",
      "checksum": "3h4cjsd8",
      "isrange": false,
      "issolvable": true,
      "hasprivatekeys": false
    }
    
    
    $ bitcoin-cli deriveaddresses "tr(musig([11111111]0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,[22222222]02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5),pk([33333333]f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9))#3h4cjsd8"
    
    
    [
      "bc1pvuuqzrum08qfjc4xylpgmse8x65p2dc5wnephvz6z5dk8j02gftsxesa85"
    ]
    
    
    $ bitcoin-cli createpsbt '[]' '[{"bc1pvuuqzrum08qfjc4xylpgmse8x65p2dc5wnephvz6z5dk8j02gftsxesa85":1}]'
    
    
    cHNidP8BADUCAAAAAAEA4fUFAAAAACJRIGc4AQ+becCZYqYnwo3DJzaoFTcUdPIbsFoVG2PJ6kJXAAAAAAAA
    
    
    $ bitcoin-cli utxoupdatepsbt "cHNidP8BADUCAAAAAAEA4fUFAAAAACJRIGc4AQ+becCZYqYnwo3DJzaoFTcUdPIbsFoVG2PJ6kJXAAAAAAAA" '["tr(musig([11111111]0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,[22222222]02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5),pk([33333333]f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9))#3h4cjsd8"]'
    
    
    cHNidP8BADUCAAAAAAEA4fUFAAAAACJRIGc4AQ+becCZYqYnwo3DJzaoFTcUdPIbsFoVG2PJ6kJXAAAAAAABBSA7RtJi0vYQ6QOLRL6r3+l6taD+uJhwrMImTt+39j7C7AEGJQDAIiD5MIoBkljDEEk0T4X4nVIptTHIRYNvmbCGAfETvOA2+awhBztG0mLS9hDpA4tEvqvf6Xq1oP64mHCswiZO37f2PsLsBQCDB7fWIQd5vmZ++dy7rFWgYpXOhwsHApv82y3OKNlZ8oFbFvgXmCUBpfTdm9noZk7F0bl4GeUjspfqnH+NqgsuluZGAGAzPl8RERERIQfGBH+UQe19bTBFQG6VwHzYXHeOS4zvPKerrAm5XHCe5SUBpfTdm9noZk7F0bl4GeUjspfqnH+NqgsuluZGAGAzPl8iIiIiIQf5MIoBkljDEEk0T4X4nVIptTHIRYNvmbCGAfETvOA2+SUBpfTdm9noZk7F0bl4GeUjspfqnH+NqgsuluZGAGAzPl8zMzMzIggCO0bSYtL2EOkDi0S+q9/perWg/riYcKzCJk7ft/Y+wuxCAnm+Zn753LusVaBilc6HCwcCm/zbLc4o2VnygVsW+BeYAsYEf5RB7X1tMEVAbpXAfNhcd45LjO88p6usCblccJ7lAA==
    
    
    $ bitcoin-cli decodepsbt cHNidP8BADUCAAAAAAEA4fUFAAAAACJRIGc4AQ+becCZYqYnwo3DJzaoFTcUdPIbsFoVG2PJ6kJXAAAAAAABBSA7RtJi0vYQ6QOLRL6r3+l6taD+uJhwrMImTt+39j7C7AEGJQDAIiD5MIoBkljDEEk0T4X4nVIptTHIRYNvmbCGAfETvOA2+awhBztG0mLS9hDpA4tEvqvf6Xq1oP64mHCswiZO37f2PsLsBQCDB7fWIQd5vmZ++dy7rFWgYpXOhwsHApv82y3OKNlZ8oFbFvgXmCUBpfTdm9noZk7F0bl4GeUjspfqnH+NqgsuluZGAGAzPl8RERERIQfGBH+UQe19bTBFQG6VwHzYXHeOS4zvPKerrAm5XHCe5SUBpfTdm9noZk7F0bl4GeUjspfqnH+NqgsuluZGAGAzPl8iIiIiIQf5MIoBkljDEEk0T4X4nVIptTHIRYNvmbCGAfETvOA2+SUBpfTdm9noZk7F0bl4GeUjspfqnH+NqgsuluZGAGAzPl8zMzMzIggCO0bSYtL2EOkDi0S+q9/perWg/riYcKzCJk7ft/Y+wuxCAnm+Zn753LusVaBilc6HCwcCm/zbLc4o2VnygVsW+BeYAsYEf5RB7X1tMEVAbpXAfNhcd45LjO88p6usCblccJ7lAA==
    
    
    {
      "tx": {
        "txid": "79109cd2c6a2d585601b4d630dc13c212b8e5a3f7dc92acee78cc85fdbbb1f51",
        "hash": "79109cd2c6a2d585601b4d630dc13c212b8e5a3f7dc92acee78cc85fdbbb1f51",
        "version": 2,
        "size": 53,
        "vsize": 53,
        "weight": 212,
        "locktime": 0,
        "vin": [
        ],
        "vout": [
          {
            "value": 1.00000000,
            "n": 0,
            "scriptPubKey": {
              "asm": "1 6738010f9b79c09962a627c28dc32736a815371474f21bb05a151b63c9ea4257",
              "desc": "rawtr(6738010f9b79c09962a627c28dc32736a815371474f21bb05a151b63c9ea4257)#vk5f75e2",
              "hex": "51206738010f9b79c09962a627c28dc32736a815371474f21bb05a151b63c9ea4257",
              "address": "bc1pvuuqzrum08qfjc4xylpgmse8x65p2dc5wnephvz6z5dk8j02gftsxesa85",
              "type": "witness_v1_taproot"
            }
          }
        ]
      },
      "global_xpubs": [
      ],
      "psbt_version": 0,
      "proprietary": [
      ],
      "unknown": {
      },
      "inputs": [
      ],
      "outputs": [
        {
          "taproot_internal_key": "3b46d262d2f610e9038b44beabdfe97ab5a0feb89870acc2264edfb7f63ec2ec",
          "taproot_tree": [
            {
              "depth": 0,
              "leaf_ver": 192,
              "script": "20f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9ac"
            }
          ],
          "taproot_bip32_derivs": [
            {
              "pubkey": "3b46d262d2f610e9038b44beabdfe97ab5a0feb89870acc2264edfb7f63ec2ec",
              "master_fingerprint": "8307b7d6",
              "path": "m",
              "leaf_hashes": [
              ]
            },
            {
              "pubkey": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
              "master_fingerprint": "11111111",
              "path": "m",
              "leaf_hashes": [
                "a5f4dd9bd9e8664ec5d1b97819e523b297ea9c7f8daa0b2e96e6460060333e5f"
              ]
            },
            {
              "pubkey": "c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
              "master_fingerprint": "22222222",
              "path": "m",
              "leaf_hashes": [
                "a5f4dd9bd9e8664ec5d1b97819e523b297ea9c7f8daa0b2e96e6460060333e5f"
              ]
            },
            {
              "pubkey": "f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
              "master_fingerprint": "33333333",
              "path": "m",
              "leaf_hashes": [
                "a5f4dd9bd9e8664ec5d1b97819e523b297ea9c7f8daa0b2e96e6460060333e5f"
              ]
            }
          ],
          "musig2_participant_pubkeys": [
            {
              "aggregate_pubkey": "023b46d262d2f610e9038b44beabdfe97ab5a0feb89870acc2264edfb7f63ec2ec",
              "participant_pubkeys": [
                "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
                "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
              ]
            }
          ]
        }
      ],
      "fee": -1.00000000
    }
    
    

    </details>

    As can be seen, the participants A and B both bet a leaf hash:

            {
              "pubkey": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
              "master_fingerprint": "11111111",
              "path": "m",
              "leaf_hashes": [
                "a5f4dd9bd9e8664ec5d1b97819e523b297ea9c7f8daa0b2e96e6460060333e5f"
              ]
            },
            {
              "pubkey": "c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
              "master_fingerprint": "22222222",
              "path": "m",
              "leaf_hashes": [
                "a5f4dd9bd9e8664ec5d1b97819e523b297ea9c7f8daa0b2e96e6460060333e5f"
              ]
            },
    

    The following regression test (feel free to use in a PR that fixes this issue, though this is LLM-authored) currently fails:

    <details>

    <summary>Python functional test</summary>

    diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
    index 3e24e125c74..a796071c0db 100755
    --- a/test/functional/rpc_psbt.py
    +++ b/test/functional/rpc_psbt.py
    @@ -306,6 +306,48 @@ class PSBTTest(BitcoinTestFramework):
             assert "participant_pubkeys" in out_participant_pks
             assert_equal(out_participant_pks["participant_pubkeys"], [out_pubkey1.hex(), out_pubkey2.hex()])
     
    +    def test_musig2_participant_leaf_hashes(self):
    +        self.log.info("Test MuSig2 participant origins are retained without unrelated leaf hashes")
    +        node = self.nodes[0]
    +        participant_a = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
    +        participant_b = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
    +        leaf_key = "f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"
    +        musig_descriptor = descsum_create(
    +            f"tr(musig([11111111]{participant_a},[22222222]{participant_b}),pk([33333333]{leaf_key}))"
    +        )
    +        # Also retain known origins when the aggregate matches neither the
    +        # internal key nor the script key of the output being updated.
    +        for descriptor in (
    +            musig_descriptor,
    +            descsum_create(f"tr([33333333]{leaf_key},pk([33333333]{leaf_key}))"),
    +        ):
    +            descriptors = [musig_descriptor, descriptor]
    +            address = node.deriveaddresses(descriptor)[0]
    +            script = bytes.fromhex(node.validateaddress(address)["scriptPubKey"])
    +
    +            # A synthetic UTXO suffices to exercise metadata updates without signing.
    +            psbt = PSBT.from_base64(node.createpsbt(
    +                [{"txid": "11" * 32, "vout": 0}], [{address: 1}]
    +            ))
    +            psbt.i[0].map[PSBT_IN_WITNESS_UTXO] = CTxOut(nValue=100_000_000, scriptPubKey=script).serialize()
    +            initial = psbt.to_base64()
    +            for updated in (
    +                node.utxoupdatepsbt(initial, descriptors),
    +                node.descriptorprocesspsbt(initial, descriptors, finalize=False)["psbt"],
    +            ):
    +                decoded = node.decodepsbt(updated)
    +                for scope in ("inputs", "outputs"):
    +                    origins = {
    +                        entry["pubkey"]: entry
    +                        for entry in decoded[scope][0]["taproot_bip32_derivs"]
    +                    }
    +                    # Only the script key is involved in the leaf (BIP371/373).
    +                    assert_equal(len(origins[leaf_key]["leaf_hashes"]), 1)
    +                    for participant, fingerprint in ((participant_a, "11111111"), (participant_b, "22222222")):
    +                        assert_equal(origins[participant[2:]]["leaf_hashes"], [])
    +                        assert_equal(origins[participant[2:]]["master_fingerprint"], fingerprint)
    +                        assert_equal(origins[participant[2:]]["path"], "m")
    +
         def test_musig2_untrusted_derivation(self):
             self.log.info("Test MuSig2 aggregate derivation from untrusted PSBT fields")
             node = self.nodes[0]
    @@ -1792,6 +1834,7 @@ class PSBTTest(BitcoinTestFramework):
             self.test_psbt_version()
             self.test_psbt_with_invalid_signature()
             self.test_musig2_untrusted_derivation()
    +        self.test_musig2_participant_leaf_hashes()
     
     if __name__ == '__main__':
         PSBTTest(__file__).main()
    

    </details>

    Relevant log output

    not relevant

    How did you obtain Bitcoin Core

    Pre-built binaries

    What version of Bitcoin Core are you using?

    v31.1.0 and master (31754523cd816eac48828ff42da63738afd66aae)

    Operating system and version

    Ubuntu 26.04

    Machine specifications

    not relevant


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-09-24 12:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me