Skip to content

Changes BIP-360 to use tapscript #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 46 commits into from
Jul 7, 2025
Merged

Conversation

EthanHeilman
Copy link
Collaborator

@EthanHeilman EthanHeilman commented Jun 15, 2025

This is a draft of changes that move BIP-360 from using an witness attestation containing PQ signatures to instead using tapscript with a very P2TR like output format. The reason for this change was that much the witness attestation construction was intended to prevent PQ signatures and public keys from being as storage mechanisms, e.g. used to store JPEGs onchain by requiring that any PQ public key or PQ signature which was stored on-chain must successfully verify. Unfortunately research showed that requiring verification did not prevent using public keys and signatures for storage for details see: jpeg resistance of various post-quantum signature schemes, Westerbaan (2025). This PR abandons this earlier approach in favor of backwards compatibility with tapscript.

Changes:

  1. P2QRH is now a P2TR with the vulnerable key-spend path disabled.
  2. Number of PQ signature algorithms supported reduced from three to two.
  3. PQ signature algorithm support is now added via opcodes or tapleaf version.

TODOs:

Must be resolved before merging

  • Brief discussion of SQIsign
  • Discuss stack witness element size limit
  • Improve readability and grammar of changes
  • Add image of P2QRH output Merkle tree structure
  • Carefully think over any issues arising from allowing larger witness stack elements may cause because tapscript
    assumes no stack element sizes greater than 520 bytes.
  • Write full specification for P2QRH
  • Resolve all open questions
  • Related work

Open Questions

None

Closed Questions

Should P2QRH and PQ Signatures opcodes be separate BIPs: Yes

The case for:

  • This would allow P2QRH to be evaluated and accepted without needing an exact agreement on PQ signatures speeding up the standardization process.
  • Both of these could be activated separately.

The case against:

  • Full quantum resistance requires both P2QRH and PQ signatures. We really should be sure P2QRH has everything needed for PQ signatures before we activate it.

Sighash tag specific to signature algorithm? Yes

Currently BIP-360 proposes using the tag "TapSighash" in the tagged hash of the sighash for PQ signatures. I can see an argument that the sighash should be specific to the signature algorithm used to prevent signatures from one algorithm being used for an algorithm.

  • "TapSighashML" for ML-DSA
  • "TapSighashSHL" for SHL-DSA

OP_CHECKSIG_VERIFY variants for PQ signatures? Yes (if we use OP_SUCCESSx opcodes)

Currently the specification only creates the OP_CHECKSIG for PQ signatures. Do want a OP_CHECKSIG_ML_VERIFY and OP_CHECKSIG_SHL_VERIFY?

I'm leaning toward no since users can get the same functionality with OP_VERIFY.

The yes argument is that script writers will expect OP_CHECKSIG_ML_VERIFY to exist.

OP_CHECKSIGADD variants for PQ signatures? Yes (if we use OP_SUCCESSx opcodes)

I'm leaning toward a strong yes:

  • Doing multisig without OP_CHECKSIGADD requires more complicated scripts.
  • Is confusing to script writers if this exists for schnorr and not PQ signatures.
  • Prevents PQ being a drop in replacement for schnorr sigs, people will have rewrite more of their scripts to support PQ signatures.
  • If there are opportunities to take advantages of PQ signature batching, this would enable nodes to exploit these opportunities.

New opcode OP_DUPHASH256? No

This approach would change the code of OP_DUP to prevent duplicating any stack element larger than 520 bytes. This would not be a consensus change as currently there is no way to put an object larger than 520 bytes on the stack.

Second, we would introduce an opcode OP_DUPHASH256 which duplicates a stack element of any size and then immediately hashes it. This allows a stack elements greater than 520 bytes to be compared to a hash without removing it from the stack. This functionality is needed to commit to PQ public keys in tapscript.

Not needed, approach of packaging pubkey and signature together and then building this into hash removes this requirement

Drop version byte in the control block? No

Currently the P2QRH control block consists of the version byte and the merkle path. The version byte is very similar to the header byte in a P2TR control block. The header byte has two purposes, specify the parity of the public key and providing versioning.

For:

  • Versioning is always good

Against:

  • Adds unneeded complexity
  • Two slightly different control block formats for P2TR and P2QRH is a confusing.
  • Without the version byte the control block just becomes the merkle path. If it is just a merkle path the intent is clear.

Good to maintain tapleaf version compatibility especially because this could be used for PQ signature activation.

How to fix DUP + no witness stack element size attack? Simply limit OP_DUP to 520 bytes.

If we remove the witness stack element size limit for P2QRH and then someone runs a tapscript consisting of DUP DUP DUP DUP ... DUP they can amplify the memory usage but duplicating a very large stack element.

This is limited to the maximum number of stack elements on the stack

Stack + altstack element count limit The existing limit of 1000 elements in the stack and altstack together after every executed opcode remains. It is extended to also apply to the size of initial stack.

BIP-342 Tapscript Resource Limits

This creates an amplification factor of 1,000.

Assume biggest value possible 3.999mb bytes. Such a transaction use 4,000mb and would be ~3,999,000+1,000 = 4mb bytes in size. ~1000x amplification

SLH-DSA signatures are 17,088 bytes. This means that a transaction spend that can push 17,088 bytes on the stack can at most use memory 17,088,000 (17mb). Such a transaction would be ~17,088+1,000 = 18 kb bytes in size. ~1000x amplification

Current tapscript allows would use 520kb for a transaction which is 520+1,000 = 1.5kb. 346x amplification

Thus, removing this limit is not catastrophic. It is only 3 times worse than the present day. Still, not great.

Solution - limit OP_DUP to stack elements 520 bytes and less

We can remove this threat entirely by limiting stack elements to 520 bytes or less

@EthanHeilman EthanHeilman marked this pull request as draft June 15, 2025 20:10
@EthanHeilman EthanHeilman changed the base branch from master to p2qrh June 15, 2025 20:11
Comment on lines 340 to 343
<source>
script witness stack = [pubkey, signature] # len(pubkey) > 520 bytes and len(signature) > 520 bytes
tapscript = [OP_DUP, OP_HASH256, OP_PUSHDATA HASH256(expected_pubkey), OP_EQUALVERIFY, OP_CHECKSIG_SLH, OP_VERIFY]
</source>
Copy link

@jasonribble jasonribble Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be OP_HASH256 or OP_SHA256? If one over the other, why?

P2WSH uses OP_SHA256, and I'm unsure about tapscript.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer HASH256 to avoid having to worry about length extension.

tapscript uses tagged hash which is SHA256 with a tag to do domain separation. We might want to use tagged hash for this.

HashWriter TaggedHash(const std::string& tag)
{
    HashWriter writer{};
    uint256 taghash;
    CSHA256().Write((const unsigned char*)tag.data(), tag.size()).Finalize(taghash.begin());
    writer << taghash << taghash;
    return writer;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer HASH256 to avoid having to worry about length extension.

If I understand right, I agree. It definitely should be 256 bit length.

My curiosity would be that P2QRH would be the first script to use HASH256 rather than SHA256.

Copy link
Collaborator Author

@EthanHeilman EthanHeilman Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand right, I agree. It definitely should be 256 bit length.

I'm talking about this https://en.wikipedia.org/wiki/Length_extension_attack It doesn't actually matter but it is nice not have to think about. It has been argued this was why P2SH used HASH160.

My curiosity would be that P2QRH would be the first script to use HASH256 rather than SHA256.

As written, it wouldn't be part of the P2QRH standard.

That said, I've been considering preventing OP_DUP from duplicating stack elements larger than 520 bytes. If that change was made then OP_CHECKSIG_SLH would be computing the hash and then it would become part of the standard. It might make sense to use SHA256 or tagged hash.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Thank you

Copy link
Owner

@cryptoquick cryptoquick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work so far! Just got a few changes and questions.

of securely storing Bitcoins in a cold wallet for very long periods of time (50 to 100 years).

For PQ signatures we considered the NIST approved SLH-DSA (SPHINCS+), ML-DSA (CRYSTALS-Dilithium),
and FN-DSA (FALCON). Of these three algorithms, SLH-DSA has the largest signature size, but is the most conservative
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to mention our investigation into SQIsign here too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good idea, I'll add something.

I attended a talk on SQIsign a few days ago. It appears rapid progress is being made on SQIsign's performance and code complexity. It is still too early to tell, but things are looking better for SQIsign

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love to see some viable code for that so we can compare! I tried FastSQISignHD and the C was kind of a shitshow. All the key functions were commented out, and even after reenabling them, it revealed a bunch more work that needed to be done.
https://github.com/Pierrick-Dartois/SQISignHD-lib
This was a couple months ago, though, and it seems some more work has been done since.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQIsign is definitely still baking but there is enormous amounts of research being done on it. Hard to evaluate right now and any evaluations are likely to change.


== Specification ==

We define the signature scheme and transaction structure as follows.

=== Descriptor Format ===
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering the rationale for removing this section on Descriptor Format ?

Seems like it would be useful when creating p2qrh compliant wallets.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is being reworked and no longer fit the current plan. I want to finish the specification first and then revisit it

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could also easily be done as a separate BIP as has been done for all the other output types.

Copy link
Owner

@cryptoquick cryptoquick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some small edits to the excellent recent SQIsign addition.

Copy link
Owner

@cryptoquick cryptoquick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed CI was failing. I fixed the bug I introduced.
, Ethan Heilman also needs to be added to the README right next to Hunter Beast.

EthanHeilman and others added 2 commits June 23, 2025 12:31

== Specification ==

We define the signature scheme and transaction structure as follows.

=== Descriptor Format ===

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could also easily be done as a separate BIP as has been done for all the other output types.

Copy link

@xoloki xoloki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review is mostly concerned with formatting/grammar/etc, plus an observation regarding choosing only one lattice based signature type. I’ll do a more in depth review tomorrow of the technical contents.

FN-DSA (FALCON). Of these three algorithms, SLH-DSA has the largest signature size, but is the most conservative
choice from a security perspective because SLH-DSA is based on well studied and time-tested hash-based cryptography.
Both FN-DSA and ML-DSA signatures are significantly smaller than SLH-DSA signatures but are based on newer lattice-based
cryptography. Since ML-DSA and FN-DSA are both similar lattice-based designs, we choose to only support one of them as the
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it’s true that an advance in lattice based cryptanalysis might break both ML-DSA and FN-DSA, the constructions are sufficiently different that it is quite possible that one might be broken while the other remains intact. I’d argue for the inclusion of both.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we choose to include both, I can make a PR against Ethan’s fork to fix the other instances where only ML-DSA is specified.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it’s true that an advance in lattice based cryptanalysis might break both ML-DSA and FN-DSA, the constructions are sufficiently different that it is quite possible that one might be broken while the other remains intact.

I should clarify this in the BIP, thanks

I’d argue for the inclusion of both.

Given the cost in complexity and maintenance of supporting multiple signature schemes, we want to support as few as we get away with. Even 2 PQ schemes is pushing it, but I'd argue for the "rule of two". As long as Bitcoin has two wildly different secure signature algorithms, the network will always be protected from the nightmare of having an unexpected break in one of the signature algorithms.

One of the main criticisms of BIP-360 prior to this draft is that it supports multiple PQ schemes.

That said, we are taking a modular approach here so avoid potential arguments avoid algorithms. BIP-360 is only for P2QRH and not the PQ signature opcodes. We include the PQ signature opcodes in the BIP because want to be sure P2QRH can support them, there is no requirement that if BIP-360 is activated we must activate ML-DSA instead of FN-DSA.

The following activation approaches are possible:

  1. Activate P2QRH with PQ signature algorithms (likely SHL-DSA, ML-DSA, but this could change)
  2. Activate P2QRH, SHL-DSA together and then later activate PQ signature algorithms
  3. Activate P2QRH by itself and then later activate PQ signature algorithms

My personal opinion is that at the time BIP-360 is has consensus among the community, we should look at the state of PQ cryptography and determine which of these activation approaches we wish to take. If ML-DSA has become the "first among equals" of PQ signature and if we are seeing ML-DSA HSMs hitting the market and being deployed in public clouds like AWS and GCP, (1) becomes very derisked. If lattices are under attack and there is an increasingly large ML-DSA doom and gloom maybe we do (2). If SQIsign variants are showing year over year performance improvements maybe we do (2) or (3) and take a wait and see approach. The idea should be to focus on a strong decision for P2QRH in parallel with work on the algorithms.

The proposal for SHL-DSA and ML-DSA opcodes here are not locking in or locking out any algorithms.

Thanks for your comment, I'm going to include a section saying what I just said in the BIP.

1. The <code>scriptPubKey</code> must be of the form:
We propose adding the opcodes OP_CHECKSIG_ML (ML-DSA) and OP_CHECKSIG_SLH (SLH-DSA) to tapscript by redefining OP_SUCCESSx
opcodes. As ML-DSA and SLH-DSA are post-quantum signature algorithms this would allow the user of post-quantum signatures
in tapscript.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the relationship between BIP341 and BIP342, should we create a new BIP focused exclusively on our proposed extension to tapscript ? Seems we should define a new tapleaf version (0xC2 / 194) that is:

  1. optional. p2qrh supports tap leaves with existing version 0xC0 (Schnorr or DER-encoded ecdsa)

  2. extends tapscript (0xC0) with our proposed PQC op_codes

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the idea, although I don't want there to be two different versions of tapscript, so the opcodes will be added tapscript in general.

This also allows P2QRH to be fully independent of the PQ signature opcodes. You could activate the opcodes first or P2QRH first or both at the same time or some between them. If you activated the opcodes first you wouldn't be able to use them since you wouldn't be able to get the large 520 bytes signatures on the stack.

In theory you could have the witness stack element size increase be part of the PQ signature opcode softfork and not part of the P2QRH. This would you only get the witness stack element size increase if the redeem script has one of the PQ signature opcodes. I opted not to do this as it would add some complexity and all things being equal I tie break on the more simple solution.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added discussion on this in the rationale section

[key_type]: 0x02
[pubkey_length]: 0x0701 (1793 bytes)
[pubkey]: public_key_falcon_512
To spend a P2QRH output, the following conditions must be met:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose the following additional condition (inline with more comments below):

  1. Any tap scripts using PQC proposed in this BIP must reference the new tapleaf version corresponding to this BIP: 0xC2 (194 in digital representation).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this in more detail? What is the benefit here? Are you proposing using tapleaf versioning for the opcodes rather than an OP_SUCCESSx softfork?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for hinting that all of this should be discussed in the context of redefining OP_SUCCESSx codes. The following two statements from your proposed changes clarify a lot:

A P2QRH script witness stack differs in only one way from a P2TR script witness stack. P2QRH does not limit the size of the witness stack elements to 520 bytes. This is needed because PQ signatures and public keys can be larger than 520 bytes.

P2QRH could be activated first and then ML-DSA and SLH-DSA could be independently activated by redefining OP_SUCCESSx opcodes.

So sounds like the initial p2qrh soft-fork will disable the stack element size, even though at that time, PQC might not initially be an option (and only Schnorr or ECDSA can initially be used). Is this correct ?

( I was originally thinking earlier that maybe a new future leaf version would be needed that complements the rollout of OP_SUCCESSx codes by defining the increased stack element size ).

Copy link
Collaborator Author

@EthanHeilman EthanHeilman Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for explaining that, I get it now

So sounds like the initial p2qrh soft-fork will disable the stack element size, even though at that time, PQC might not initially be an option (and only Schnorr or ECDSA can initially be used). Is this correct ?

Yes, that is currently my thinking and the intent of what is written.

To be precise, we aren't disabling the stack element size, but raising it to 8,000 bytes by allowing the witness stack elements 8,000 bytes or less. This is limited increase as it would not allow pushing, creating or duplicating 8,000 byte elements in tapscript.

I very much wish PQ signatures were less than 520 bytes, but given the fact that they are greater than 520 bytes I don't see a better way to do it. Everything else introduces much more complexity. For instance:

  1. You could have a second witness that only the PQ opcodes could access, but that seems like a special case that everyone will have to remember. Languages don't generally have "hidden memory" that programmers need to remember exists when they use select opcodes.

  2. You could push proxy values on the stack which function like pointers to the PQ signatures. Again that feels like it breaks the assumptions that most tapscript developers have about the language and also adds lots of implementation complexity.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect. Thank you. Makes sense.

the same 256-bit security level. One may ask why not use the existing output type P2WSH instead of add a new one?
The problem with P2WSH is that it can only execute pre-tapscript scripts and not tapscript.
New programs in Bitcoin ecosystem have largely moved to tapscript for new scripts. Using P2WSH would require turning
back the clock and forcing projects to move from tapscript to pre-tapscript. More importantly, tapscript provides a far
Copy link

@Ademan Ademan Jul 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO merkle-tree-of-scripts is extremely important as well. Possibly more important in the QR context.

  1. On-chain-efficiency. Sets of scripts can be committed to without including every possible spending condition in a single script, which could potentially make for a very large script.
  2. More importantly, in a QR context where you might have multiple QR signature algorithms. If one of the algorithms is compromised, having a single P2WSH script would require you to reveal the vulnerable QR pubkey, whereas with P2QRH we can keep different algorithms in different leaves and only reveal the one we believe to be secure when spending. (I suppose in a P2WSH scheme you could require both QR signatures unconditionally, but that would be very heavy on chain)

In my mind the obvious immediate term P2QRH trees would would be: {pk(A), {ml_dsa(B), slh_dsa(C)}} (a schnorr leaf which is cheapest to spend at depth 0, then at depth 1, a leaf for either quantum secure algorithm, forgive me if my pseudo-descriptor is a bit off)

EDIT: You do cover this later, my bad. It still would be good to mention something in this paragraph though I think.

Copy link
Collaborator Author

@EthanHeilman EthanHeilman Jul 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the reviews

  1. I agree with you that keeping the Merkle tree from P2TR is important for those usecases. I'm concerned if I use this argument someone will dispute if those use cases are actually how people will use it. To avoid that argument I've tried to restrain my opinion here.

  2. For ML-DSA we commit to the public key hashes in P2QRH tapleafs not the public key so they are the same size as as schnorr or SLH-DSA public keys. Still there is some sufficient savings using different branches for pubkeys rather than a pattern like:

IF 1:
checksigSchnorr
ELSE IF 2:
checksigML
ELSE IF 3:
checksigSLH

In my mind the obvious immediate term P2QRH trees would would be: {pk(A), {ml_dsa(B), slh_dsa(C)}}

Agreed

ML-DSA and SLH-DSA could be independently activated. If at some future point another signature
algorithm was desired it could follow this pattern.

We consider two different paths for activating PQ signatures in Bitcoin. The first approach is to redefine OP_SUCCESSx opcodes for each
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OP_CHECKSIG, OP_CHECKSIGVERIFY, and OP_CHECKSIGADD all can support other signing algorithms without new opcodes. If the public key is not exactly 32 bytes long (x-only public key), then the signature check is considered to have succeeded. Of course there are other considerations, and I don't know if the proposed PQ schemes can be fit into these opcodes (especially given the stack size limit).

See https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#rules-for-signature-opcodes

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's interesting. I didn't know that

SLH-DSA has 32 byte public keys, ML-DSA has much larger public keys but we use a public key hash in the tapleaf so it is also 32 bytes. In the tapleaf world we would probably want to make the public keys 33-bytes with one byte to flag the type of algorithm. It's cool that we could softfork that in. I think we would still need the new tapleaf version for the increased initial stack element size if we were doing OP_SUCCESSx opcodes.

Number of signatures:
Turning our attention to public keys larger than 520 bytes. This is not needed for SLH-DSA as its public key is only 32 bytes.
This is a different problem than signatures as public keys are typically pushed onto
the stack by the tapleaf script (redeem script) to commit to public keys in output. The OP_PUSHDATA opcode in tapscript fails if asked to push
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary, or desirable. You're not constrained by the current PUSHDATA opcode behavior except to whatever extent you want to mirror tapscript, this seems like a case where it makes sense to diverge from plain tapscript, and it shouldn't complicate implementation much at all (just need a flag for EvalScript() that relaxes the pushdata limit). It'll also save you 32 witness bytes FWIW.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me qualify that a bit. you technically could redefine the push limits for PUSHDATA in all P2QRH scripts, however, bip-341 recommends sharing leaf versions between witness versions for static analysis that may not have access to the scriptPubkey (to disambiguate between witness versions). Therefore, I'd recommend raising the PUSHDATA limits only in a new leaf version, which is needed anyway for the PQ schemes to raise the stack item size limit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My justification for this is purely on the grounds of simplicity and a good developer experience. I'd be willing to rethink if I get lots of push back from the community.

I want there to be only one tapscript regardless of output (P2QRH, P2TR). This allows script writers and tooling to not have to think about what output it will used in. This is also the reason I prefer to use OP_SUCCESSx rather than tapleaf.

Therefore, I'd recommend raising the PUSHDATA limits only in a new leaf version, which is needed anyway for the PQ schemes to raise the stack item size limit.

We do not need a new tapleaf version to raise the stack item size limit or redefine OP_DUP. Adding an new opcode via OP_SUCCESSx is sufficient unless I'm missing something.

I plan to move most the PQ details into the PQ signature BIP when that gets posted to github. Let's revisit this discussion when PQ signature BIP is in draft.


For example, for CRYSTALS-Dilithium Level I, a single public key is 1,312 bytes, and a signature is 2,420 bytes, resulting in a substantial increase over current
ECDSA or Schnorr signatures.
For a Merkle path of length m, it would add an additional ~32 * m bytes to the P2QRH input<ref>If the number is large enough we will the compact size will need 2 bytes, increasing the size by 1 byte.</ref>.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/we will//

It's probably worth being specific. If m > 7, then the compact size will be 2 bytes. (m = 7 means the control block will be 32 * 7 + 1 bytes = 225 bytes, m = 8 means the control block will be 32 * 8 + 1 bytes = 257 bytes)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the way you wrote it, used some of your words here. I don't want to get that into the weeds here.

We are both wrong here in that compact size does 1 byte, 3 bytes, 5 bytes.
https://bitcoin.stackexchange.com/a/110963/442

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha you're totally right, good catch!

Copy link

@xoloki xoloki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly minor nits. Overall looking very good!

I had another question about some of the BIP-32 verbiage that isn't part of this PR, so should I address that elsewhere?

P2QRH (Pay to Quantum Resistant Hash) is a new output type that commits to the root of a tapleaf merkle tree. It is functionally
the same as a P2TR (Pay to Taproot) output with the quantum vulnerable key-spend path removed. Since P2QRH has no key-spend path, P2QRH omits the
taptweak public key as it is not needed. Instead a P2QRH output is just the 32 byte root of the tapleaf merkle tree as defined
in [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP-341].
Copy link

@xoloki xoloki Jul 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
in [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP-341].
in [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP-341], hashed as per below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -79,8 +83,8 @@ As the value being sent increases, so too should the fee in order to commit the
possible. Once the transaction is mined, it makes useless the public key revealed by spending a UTXO, so long as it is
never reused.

It is proposed to implement a Pay to Quantum Resistant Hash (P2QRH) output type that relies on a PQC signature
algorithm. This new output type protects transactions submitted to the mempool and helps preserve the free market by
It is proposed to implement a Pay to Quantum Resistant Hash (P2QRH) output type that relies on a PQ signature
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: P2QRH does not rely on a PQ signature; the initial soft fork from this BIP simply removes the vulnerable taproot key spend path. Maybe something like

Suggested change
It is proposed to implement a Pay to Quantum Resistant Hash (P2QRH) output type that relies on a PQ signature
It is proposed to implement a Pay to Quantum Resistant Hash (P2QRH) output type that enables the use of a PQ signature

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good find. Changed to: As the first step to address these issues we propose Pay to Quantum Resistant Hash (P2QRH), an output type that allows tapscript to be used in a quantum resistant manner.

The problem with P2WSH is that it can only execute pre-tapscript scripts and not tapscript.
New programs in Bitcoin ecosystem have largely moved to tapscript for new scripts. Using P2WSH would require turning
back the clock and forcing projects to move from tapscript to pre-tapscript. More importantly, tapscript provides a far
easier and safer upgrade path for adding PQ signatures. Changes to pre-script to enable it to support PQ signatures would likely
Copy link

@xoloki xoloki Jul 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly here, where you have both "pre-script" and "pre-tapscript". In both of these instances (here and the line below), maybe just Script is sufficient, or the full pre-taproot Script? With Script capitalized to show we are talking about the specification, not any particular script.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pre-script is a typo, let fix that.

I prefer pre-tapscript to pre-taproot since what we care about here the scripting language not the output type.

Rewrote to try to clarify.

The problem with P2WSH is that it only works with pre-tapscript Script and cannot work with tapscript Script.

them to switch to sending their coins using the PQ signature algorithms. This allows the upgrade to quantum
resistance to be largely invisible to users.

Consider the P2QRH output with three tapscripts:
Copy link

@xoloki xoloki Jul 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, having three tapscripts would not necessarily protect in the case of one of the three (Schnorr, ML-DSA, SLH-DSA) algorithms being broken. The broken algo could be used to spend the output, if the attacker had knowledge of the full script tree, and the public key itself (which would require a Grover walk in case of hashed keys). The only way to fully protect the output would be a single script that does something like

<schnorr key>
OP_CHECKSIG
<ml-dsa key>
OP_CHECKMLSIG_ADD
<slh-dsa key>
OP_CHECKSLSIG_ADD
3
OP_EQUAL

Of course, doing that would reveal the key to the broken algo, allowing a CRQC to attack it, but since we need 3/3 sigs that doesn't seem particularly problematic. But maybe we weren't trying to protect against the case where the script was revealed?

If the latter is the case, perhaps some explanatory text would suffice to remove the ambiguity. I had assumed on my first reading of BIP-360 that outputs need both ECC and PQ signatures, as per PXQDH and similar protocols.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the helpful reviewing.

But maybe we weren't trying to protect against the case where the script was revealed?

The script template can be known to the attacker, but the actual public keys should not be known.
The assumption here is that the Schorr public key for OP_CHECKSIG is not known to the attacker. This is same assumption that lets us say P2PKH protects against long-exposure attacks. As long as you don't spend or otherwise reveal the Schorr public key you should be safe.

One could certainly do 3-of-3 with each of the algorithms, but the spending transaction would be huge.

Attempted to clarify in the BIP.


To spend a P2QRH output, the following conditions must be met:
P2QRH uses SegWit version 3 outputs, resulting in addresses that start with <code>bc1r</code>, following
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's nice

@EthanHeilman
Copy link
Collaborator Author

@xoloki

I had another question about some of the BIP-32 verbiage that isn't part of this PR, so should I address that elsewhere?

Yes, open an issue about what you want changed with the talk of BIP-32

@cryptoquick
Copy link
Owner

Thanks for everyone who provided feedback over the weekend. It looks like everything has been addressed satisfactorily. Let's Get This Merged. Any further changes can take place in a new PR.

@cryptoquick cryptoquick merged commit a375b65 into cryptoquick:p2qrh Jul 7, 2025
5 checks passed
jbride pushed a commit to jbride/bips that referenced this pull request Jul 8, 2025
* Rewrote rationale

* Fix bolded principles

* Actually fix bold

* Updates to talk about signature + public key size rather than just signature size

* Took a pass over rationale

* Started work on specification

* Adds example tapscript hybrid signatures

* More work on the specification

* Cleans up TODO

* Fixing grammar, other minor changes

* SHL --> SLH

* Apply suggestions from code review

Co-authored-by: Hunter Beast <[email protected]>

* Adds discussion of SQIsign

* Fixes broken llink to libbitcoinpqc

Co-authored-by: Hunter Beast <[email protected]>

* Fixes writing in SQIsign section

Co-authored-by: Hunter Beast <[email protected]>

* Add rational section on big signatures and public keys

* Fixes typos

* Adds script validation from BIP 341

* Add commas

* Add design section, stack element size increase now in PQ sigs

* Fixes typo

* Fixes typos and formatting

Co-authored-by: Hunter Beast <[email protected]>

* Add authorship to readme

* Add diagram of P2QRH merke tree, scriptPubKey and Witness

* Remove completed todo

* Add security section

* Clean up wording, moves some things around

* Minor rewording

* Review suggestions

Co-authored-by: Hunter Beast <[email protected]>

* Clarified size differences

* Changed header size and order

* does --> doUpdate bip-0360.mediawiki

Co-authored-by: Hunter Beast <[email protected]>

* Add related work section

* Better scale figure

* Respond to review comments

* remove double space

Co-authored-by: Armin Sabouri <[email protected]>

* Address review comments

* Addressing Ademan comments

* Sync source svg

* Address review

* Addresses review

* Apply suggestions from code review

Co-authored-by: Joey Yandle <[email protected]>

* Update bip-0360.mediawiki

Co-authored-by: Joey Yandle <[email protected]>

* Update bip-0360.mediawiki

Co-authored-by: Joey Yandle <[email protected]>

* Addressing review comments

* Addressing reviews

---------

Co-authored-by: Hunter Beast <[email protected]>
Co-authored-by: Armin Sabouri <[email protected]>
Co-authored-by: Joey Yandle <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants