Tr(): rawnode() and rawleaf() support

Previous discussions on Issue 24114 and Partial Descriptors Gist suggests two new descriptors for tr():

  • rawnode(HEXHASH) which allows the specification of a branch using the merkle hash
  • rawleaf(HEXSCRIPT,[HEXLEAFVER]) which allows the addition of raw scripts with different leaf versions

Arguments for rawnode() include:

  • Adding support for Need-to-Know-Branches (N2KB) see Jeremy’s comment on 21365
  • Allowing users to specify TR trees with omitted information
  • Allowing users to simplify TR trees
    As an example, this descriptor:
tr(
  5a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5,{
    multi_a(
      2,
      tprv8btqJtFL5n1drr8jP95PBgTitQiCJyxDb27gGhbXM2vacGt3gV2Bfs5PHSBt6LbpPs5fUb4hGhD5TXudLrisjd14qtM8EZ3nVSv31jhLuop/*,
      415f59b2ce1ad8f714fd40b71865937dd911ad4742724cead5db936f73029d1e,1a3569fb509ababa09cf896c0a66619d0a01fd235d26b479a42503670beb9754
    ),
   {
     {
       and_v(
         v:older(4320),
         multi_a(
           2,
           ae3f20aee8d8798ceafb1671bf5607a45794676498e2edfac042958f7407ee1c,
           tpubD8asTJHaE9hJkKAXGnjyb67qTSE8UK98AKiTZDdpmJiySm8pJsqmrMhFTZV9cQyCdisA71G9bjGCZXcjXgFWCfjK5rfxVYmbnA5XikwzEHt/*
         )
       ),
       and_v(
         v:older(4320),
         multi_a(
           2,
           ae3f20aee8d8798ceafb1671bf5607a45794676498e2edfac042958f7407ee1c,
           415f59b2ce1ad8f714fd40b71865937dd911ad4742724cead5db936f73029d1e
         )
       )
    },
    {
      and_v(
        v:older(4320),
        multi_a(
          2,
          ae3f20aee8d8798ceafb1671bf5607a45794676498e2edfac042958f7407ee1c,
          1a3569fb509ababa09cf896c0a66619d0a01fd235d26b479a42503670beb9754
        )
      ),
      and_v(
        v:older(12960),
        pk(ae3f20aee8d8798ceafb1671bf5607a45794676498e2edfac042958f7407ee1c)
      )
    }
  }
})#vz6zss2j

has been reduced to:

tr(
  5a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5,
    {
        multi_a(
          2,
          tprv8btqJtFL5n1drr8jP95PBgTitQiCJyxDb27gGhbXM2vacGt3gV2Bfs5PHSBt6LbpPs5fUb4hGhD5TXudLrisjd14qtM8EZ3nVSv31jhLuop/*,
          415f59b2ce1ad8f714fd40b71865937dd911ad4742724cead5db936f73029d1e,
          1a3569fb509ababa09cf896c0a66619d0a01fd235d26b479a42503670beb9754
        ),
        rawnode(8a62dc0a100c4156cc2a4b7c2a97747ce0dfe90562673fd662678aaae93121fb)
    }
)

See test case verifying this

While it would seem that rawleaf offers no advantage since we have rawnode, @sipa makes the argument that since the script and leaf version can be communicated via PSBTs, then descriptors should also be able to pass this information See Sipa’s comment on 24114

There’s an unanswered question about whether rawleaf(HEXSCRIPT,[HEXLEAFVER]) should just be raw() that takes an optional HEXLEAFVER under tr() but I prefer rawleaf to avoid overloading the raw() descriptor.

Implementation

I have created a PoC here wip-tr-raw-nodes, see diff. I created two new DescriptorImpl subclasses, RawNodeDescriptor and RawLeafDescriptor. Both are only valid under P2TR context and can be inferred by InferDescriptor.

Descriptor Inference

  • If no taptree is inferred, and the Merkle root is not null, then tr(internal_key,rawnode(merkle_root)) is returned instead of the previous rawtr(tr_public_key). I am not sure if this should be the case. What advantage does inferring tr(internal_key,rawnode(merkle_root)) have over inferring rawtr(tr_public_key)?
  • rawleaf is inferred after no standard scripts could be inferred under a P2TR context.
3 Likes

cc @ajtowns @sipa @AntoineP , since this work is based off your prior discussions.

1 Like

Thank you for working on this!

It provides strictly more information: the internal key. Philosophically, I think, the idea of partial descriptors is that any subset of relevant information should be conveyable, so that would be sufficient justification for having it. But practically, imagine a signer that recognizes the internal key, but does not recognize the tweaked output key; having the internal_key and merkle_root would enable them to locate the correct key.

2 Likes

Makes sense. I didn’t see it that way. Thanks! @sipa

2 Likes

@sipa I wonder if you have any ideas about how N2KBs are supposed to be used. I imagine that other parties of the transaction will fear hidden spend paths.

Would be cool to get @JeremyRubin take as well

Could be useful for inheritance/escrow type services: “this isn’t my money, so I don’t care about other spend paths, but in an emergency, the spend path I do know about can be used to help that person recover their money even if they’ve lost all their keys”.

Could be useful for places where you’re using multiple wallets for some funds – maybe your main wallet understands some super clever contracts that you want to put in a tapscript, but your backup (hardware? cold?) wallet doesn’t and can only spend via the key path; in that case all the backup wallet needs to know is the ipk and a rawnode leaf.

3 Likes

Thanks for the clarification @ajtowns !

@Eunovo took a brief look at your implementation, perhaps its worth opening as a PR?

3 Likes

Cleaned up the commits and opened Tr partial descriptors by Eunovo · Pull Request #30243 · bitcoin/bitcoin (github.com) @josibake @ajtowns

5 Likes