I'm reading the Bitcoin Core's descriptor documentation and I noticed that the raw() SCRIPT expression can only be used at descriptor top-level. Indeed when called with this descriptor getdescriptorinfo returns -5 error with the message Can only have raw() at top level:


I assume that raw() is the only way you add an OP_RETURN type commitment in a taproot script path, or at least I couldn't figure out a way to do otherwise. I could think of using the pk() expression with a hash for example, but I guess it's bad to do that.

Why isn't it allowed to commit OP_RETURN type commitments, let alone other arbitrary script, inside a taproot address? Is it a descriptor restriction or a more fundamental limitation?

I read this but I don't think it really answer my point. If descriptors support arbitrary script only at top-level, why then?

2 Answers 2


The answer is twofold.

First, the Bitcoin Core descriptors are in the process of being extended with Miniscript. In the upcoming version (24.0), Miniscript will only be supported in Segwit version 0 outputs (P2WSH).
Once Miniscript support for Tapscript is added, you will be able to use a lot more Script functionalities in the script paths of a Taproot descriptor.

With regard to arbitrary Scripts, that is including those that are not representable (and therefore not solvable) using Miniscript. There is an ongoing discussion about supporting "partial descriptors", an otherwise solvable descriptor containing a spending path for which we have no solvability information.
A partial Taproot descriptor would allow you to have a rawnode(HEX) in the tree. This is what you need if you want to keep the solvability through the key path, because otherwise you could just use rawtr().

an OP_RETURN type commitment in a taproot script path, or at least I couldn't figure out a way to do otherwise. I could think of using the pk() expression with a hash for example, but I guess it's bad to do that.

Unrelated to your main question, but i don't think it's necessarily bad to use another form of commitment. If you were thinking this because of OP_RETURN outputs pruning from the UTxO set, note this is meaningless in this context.

You could use less expensive methods for committing the data. Such as tweaking a public key. And that would remove the need for partial descriptors.

  • Thanks for the explanations, to sum it up 1) supporting arbitrary script is out of scope for descriptors and is to be taken care by miniscript 2) descriptors shouldn't allow for spending conditions that we don't fully understand, even if it might be the case in the future. If I were to do a taproot script with an arbitrary path, I should first write it (how?) and then represent it with rawtr(), is that correct?
    – Sosthène
    Commented Aug 18, 2022 at 12:57
  • 1
    For 1), no. Miniscript is about safely supporting more scripts in a general manner, not any script. Miniscript is a subset of Bitcoin Script. Regarding 2) It was indeed the thinking of their initial designer. But see the discussion i linked above. We (contributors in this area, including the original designer) are starting to think partial descriptors should be supported. You are correct that as of version 24.0 you would have to use a rawtr() descriptor to track Taproot outputs with an OP_RETURN in one of the Script path branches. Commented Aug 18, 2022 at 13:54
  • Ugh, when I wrote "arbitrary script" what I meant was indeed "arbitrary script within what's supported by miniscript", thanks for correcting me. As for 2), is there some tooling that could help writing this kind of script? Just asking out of curiosity, at this point I'm basically convinced this is a bad idea anyway.
    – Sosthène
    Commented Aug 18, 2022 at 14:21
  • Just noticed your last edit, what I meant was that hacking the descriptors by pushing a pk() with the hash you want to commit in place of an OP_RETURN looks ugly and is probably bad. I'm also thinking about pubkey tweaking but a bit cautious because not sure what libs implemented it besides btcd and lnp/bp and I don't think I can use any of it on this project.
    – Sosthène
    Commented Aug 18, 2022 at 14:27
  • 2
    I'm curious what you are trying to achieve now. :) Re the tooling, for Bitcoin Core i'm not aware of say an RPC call that'd allow you to do that. But finding a library that allows you to create a Taproot scriptPubKey from a set of Script leaves shouldn't be too hard? For what it's worth this github.com/darosior/python-bip380 will probably feature what you want once i finally implement Taproot descriptors. Commented Aug 18, 2022 at 14:56

It's not clear to me why one would want to commit to an OP_RETURN inside of a P2TR output construction, let alone inside of a descriptor. I feel like there might be a misunderstanding here.

Using the OP_RETURN op-code makes an output script unspendable (and in most cases non-standard). Putting OP_RETURN as the first instruction in a scriptPubKey indicates a NULL DATA output, which permits writing a small amount of data to the blockchain. Because NULL DATA outputs are unspendable, they do not get inserted in the UTXO set, but since they're written out as part of the transaction in the blockchain, they're still published.

A popular application of NULL DATA outputs is the publishing of a commitment, e.g. the hash of a document to prove that it existed at the time, the witness commitment in segwit blocks, or a Merkle root of a whole tree of commitments as with OpenTimeStamps.

Putting an OP_RETURN into a leafscript of a Taptree does not serve the purpose of publishing the data, since only the leafscript used for spending the UTXO in the input will ever written to the blockchain. If the data should be published per the transaction, it should be a dedicated output.

If the creation of a commitment suffices, it would be enough to tweak the inner public key with the hash of the data. That way, the owner could at a later time publish the data, and attest to the hash by signing with the inner public key. The act of publishing would then be separate from the act of committing, but the commitment would still proof that the data existed at the time of the transaction output creation.

  • 2
    Note that for OpenTimestamps, publishing is never needed, and IIRC OpenTimestamps also supports committing the Merkle root of its tree using sign-to-contract (making an otherwise normal transaction signature commit to it). Commented Aug 31, 2022 at 15:47
  • Thanks for the answer. I got your point on the distinction between publishing and committing. Interestingly a project like RGB, whose business is commitment and not publishing, do use OP_RETURN inside a taproot leafscript (they call it "tapret" I think). I don't know the rationale for that instead of directly tweaking the internal pubkey as you propose. As to why I was asking how to put it inside a descriptor, it was because I couldn't think of another way to create the corresponding scriptpubkey using Bitcoin Core RPC. Wether it was a good idea or not is another question :)
    – Sosthène
    Commented Sep 13, 2022 at 14:51
  • Thinking about it a reason to do an OP_RETURN commitment inside a leafscript instead of directly tweaking the internal pubkey might be to commit other scripts in the same scriptpubkey?
    – Sosthène
    Commented Sep 13, 2022 at 14:52
  • Right, but you could just hash your data and use the hash as a leaf. You don't really need an OP_RETURN, do you?
    – Murch
    Commented Sep 13, 2022 at 15:17
  • I'm just saying that afaiu some people are doing that, and that they might have a reason for that.
    – Sosthène
    Commented Sep 19, 2022 at 14:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.