Skip to content

Draft: 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

Draft
wants to merge 15 commits into
base: p2qrh
Choose a base branch
from

Conversation

EthanHeilman
Copy link

@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 T2TR 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.

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
  • Readd Descriptor Format section
  • Add full specification for OP_CHECKSIG_ML
  • Add full specification for OP_CHECKSIG_SHL
  • Resolve all open questions
  • Create separate PR for PQ signature opcodes

Open Questions

Drop version byte in the control block?

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.

How to fix DUP + no witness stack element size attack

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.

New opcode OP_DUPHASH256

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.

OP_CHECKSIG_VERIFY variants for PQ signatures?

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?

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 opportunties.

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

@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
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
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
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.

Comment on lines +322 to +324
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.
Copy link
Owner

Choose a reason for hiding this comment

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

Should we just increase the limit to the largest signature size (SLH-DSA) ?

Copy link
Author

Choose a reason for hiding this comment

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

That's a good idea.


== Specification ==

We define the signature scheme and transaction structure as follows.

=== Descriptor Format ===
Copy link

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
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

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
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.

4 participants