OP_CAT Enables Winternitz Signatures

251 views
Skip to first unread message

conduition

unread,
Jun 8, 2025, 6:59:53 AMJun 8
to Bitcoin Development Mailing List
Hi list,

Jeremy Rubin's earlier work has already shown
OP_CAT enables Lamport signatures [0]. Jeremy's
approach gives us a script pubkey which is a little
less than 8600 bytes, plus a witness stack of 2121
bytes, for a total witness size of ~10721 bytes. The
scheme relied on using RMD-160 hashes to achieve these
sizes - SHA256 would've bloated the scheme
significantly.

I'd like to concretely demonstrate one more post-quantum
signature algorithm which OP_CAT enables: Winternitz
One-Time Signatures (WOTS) [1]. Specifically we instantiate
Winternitz using SHA256 hash chains of length 16 (AKA
"w = 16"), with a checksum compression technique
inspired by page 4 of the SPHINCS+ paper [2].

We use WOTS to sign the SHA256 hash of an EC signature,
which is validated by OP_CHECKSIG. We break this 256
bit hash up into 64 words of 4 bits each, and then use
script trickery to concatenate and verify the 64 words
match the EC signature's hash.

See a prototype implementation in pseudo-script on
github here.

https://gist.github.com/conduition/c6fd78e90c21f669fad7e3b5fe113182

With this approach, the script + witness stack are
substantially smaller than with Lamport signatures,
even when using 256-bit hashes. More concretely, the
serialized witness stack looks like this:

64 x SHA256 hashes 2112 bytes
64 x message words 128 bytes
1 x BIP340 EC signature 65 bytes
1 x Witness Script 5610 bytes
1 x Control block 33 bytes
--------------------------------------
Total 7948 bytes


I suspect you could shrink this by a few more kilobytes:

- If you were willing to compromise on security in favor
of compactness, you could use RMD-160 hash chains, or
sign RMD160(SHA256(ec_signature)) so that you only need
to sign 40 words instead of 64 words.
- One could experiment with Winternitz chains of length 4,
breaking the message into 2-bit words instead of 4-bit words.
- I'm no script wizard, so I'm sure there are optimizations
left to make on the witness script.

To be useful, this locking script would need to be
hidden as a tapscript leaf and revealed only after
OP_CAT activation. Naturally, this assumes key-path
spending is disabled, otherwise the whole scheme would
be easily defeated by a quantum attacker.

I successfully tested this protocol out using a Bitcoin
Inquisition [3] regtest node. A file containing example
transactions is attached to this email. The second TX
spends the first, using this Winternitz scheme. The
spending TX comes in at only 2070 vbytes after accounting
for the witness discount.

(Big thanks to kallewoof for making the btcdeb
debugging tool [4], without which I would've never
gotten the script working)


regards,

conduition



[0]: https://gnusha.org/pi/bitcoindev/CAD5xwhgzR8e5r1e4H-5EH2mS...@mail.gmail.com
[1]: https://eprint.iacr.org/2011/191.pdf
[2]: https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=10179381
[3]: https://github.com/bitcoin-inquisition/bitcoin
[4]: https://github.com/kallewoof/btcdeb

PS If anyone would like to test this on signet, I'd
be more than happy to help. I couldn't get my OP_CAT
transactions mined for some reason so i stuck to regtest.
opcat_txs.txt
publickey - conduition@proton.me - 0x474891AD.asc
signature.asc

conduition

unread,
Jun 9, 2025, 11:46:20 AMJun 9
to Dustin Ray, Bitcoin Development Mailing List
Hi Dustin,

I agree that in a best case scenario, we should
hope for much smaller signatures as the default

in a post-quantum bitcoin network. Ideally some

new age cryptography such as lattices allows
this. If every Bitcoin transaction used a large
hash-based signature like Lamport, WOTS, or

SPHINCS, then L1 TPS would have to drop, or

blocksize would have to increase, and nobody

wants that.

But it's good to have options. WOTS not an ideal
one by any means, but it works, and assumes little
compared to lattices.

Maybe useful as an emergency quantum-resistant
escape hatch, in case the network doesn't come
to consensus on a more compact signature scheme,
or if the novel scheme that we do use turns out
to be insecure.

Best case is that in a few years, someone
invents a scheme with 64 byte signatures which
is quantum resistant, and we add a new opcode or
address format, and everyone migrates to that.
But let's not put all our eggs in one basket.

PS thanks for the link Yuval, I wasn't aware of
that prior work. I believe my construction
improves on Jonas', on two counts:

- My approach requires only CAT, not full GSR. If
we had more opcodes (namely OP_LSHIFT), my
script would get even smaller.
- My script results in much smaller witnesses.
8kb vs 24kb.

However, I didn't attempt to implement WOTS+, only
vanilla WOTS with checksum compression. This was
mostly because of the difficulty of XORing without
access to OP_XOR.

regards,
conduition

On Sunday, June 8th, 2025 at 4:20 PM, Dustin Ray <dustin...@pm.me> wrote:

> I don't mean to sound crass but i do find it incredibly ironic that the same community that went to war over the block size all of those years ago is now seriously considering dumping kilobytes of possibly *stateful* signature data into the blockchain.
>

> I am very concerned that allowing that volume of data is going to seriously harm decentralization. Low power and casual devices might struggle to keep up with managing a ledger with such a substantial footprint.
> > --
> > You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group.
> > To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+...@googlegroups.com.
> > To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/uCSokD_EM3XBQBiVIEeju5mPOy2OU-TTAQaavyo0Zs8s2GhAdokhJXLFpcBpG9cKF03dNZfq2kqO-PpxXouSIHsDosjYhdBGkFArC5yIHU0%3D%40proton.me.
publickey - conduition@proton.me - 0x474891AD.asc
signature.asc

Anthony Towns

unread,
Jul 5, 2025, 9:54:31 AM (13 days ago) Jul 5
to conduition, Bitcoin Development Mailing List
On Sun, Jun 08, 2025 at 03:20:08AM +0000, 'conduition' via Bitcoin Development Mailing List wrote:
> See a prototype implementation in pseudo-script on
> github here.
>
> https://gist.github.com/conduition/c6fd78e90c21f669fad7e3b5fe113182

I think you can do the four-bit pair to eight-bit conversion slightly
better with:

DUP 8 GREATERTHANOREQUAL # is the high-bit going to be set?
SWAP ROT SWAP # drop that flag lower in the stack
DUP ADD DUP ADD DUP ADD DUP ADD ADD # combine them mathematically
SWAP IF # was the flag set?
128 SWAP SUB # subtract from 128 converts 0x8100-0xff00 to 0x81-0xff
IFDUP NOT IF "0x80" ENDIF # special case 0x80 "negative zero"
ELSE
IFDUP NOT IF "0x00" ENDIF # special case actual 0
ENDIF

Should save about 640 bytes of script (11%, 8% total), I think.

> PS If anyone would like to test this on signet, I'd
> be more than happy to help. I couldn't get my OP_CAT
> transactions mined for some reason so i stuck to regtest.

inquisition.bitcoin-signet.net was down for a few days when you posted
this, due to running out of disk space, which probably would have made
getting txs relayed pretty hard. You'd probably have more luck now.

Cheers,
aj

Jonas Nick

unread,
Jul 7, 2025, 6:43:18 AM (11 days ago) Jul 7
to bitco...@googlegroups.com
Hi conduition,

Thanks for this work. I think it provides a very useful data point.

For further reductions in size, it may be worth looking into "Target Sum
Winternitz" [0], where the checksum is hardcoded into the verifier instead
of being an explicit part of the signature, at the cost of additional
signing complexity. In this scheme, the signer has to hash their message
with some randomness, encode into chunks and check if the sum of the chunks
matches the checksum. If not, they rehash the message with new randomness
until they have found the randomness that results in the correct checksum.

There is also some more recent work that promises "20% to 40% improvement in
the verification cost of the signature" [1]. However, I have not read the
paper and the increase in Bitcoin Script size may eat up theoretical
reductions in verification cost.

> I believe my construction improves on Jonas', on two counts: [...] My
> script results in much smaller witnesses. 8kb vs 24kb.

I think the size difference largely comes from the fact that my
implementation [2] is based on W-OTS+ [3] and not on W-OTS. The main
difference is that W-OTS relies on some variant of collision-resistance of
the hash function, whereas W-OTS+ only relies on the weaker preimage
resistance property. W-OTS+ is also standardized as part of XMSS [4] in the
form of a variant that was proven secure a little later [5].

However, using just W-OTS and therefore relying on collision-resistance seems
okay because Bitcoin already relies on collision-resistance of SHA256. If that
property was broken, the blockchain and the transaction Merkle tree would not
provide integrity anymore, resulting in chain splits. Therefore, I suggested [6]
to change my implementation to a Winternitz variant that does rely on
collision-resistance and whose Blockchain footprint is smaller. So far, no one
has implemented that, but it would certainly be very interesting to see if a
Great Script Restoration based implementation can significantly improve over
your implementation.

[0] https://eprint.iacr.org/2025/055.pdf
[1] https://eprint.iacr.org/2025/889.pdf
[2] https://github.com/jonasnick/GreatRSI
[3] https://eprint.iacr.org/2017/965.pdf
[4] https://datatracker.ietf.org/doc/html/rfc8391
[5] https://tches.iacr.org/index.php/TCHES/article/download/8730/8330/5451
[6] https://github.com/jonasnick/GreatRSI/issues/1#issuecomment-2548062773

conduition

unread,
Jul 7, 2025, 9:15:43 PM (10 days ago) Jul 7
to Anthony Towns, Bitcoin Development Mailing List
Great idea AJ, I didn't think about OP_DUP OP_ADD as a
stand-in for OP_LSHIFT. That saves a bunch of bytes. We can
save even more by using `OP_SIZE` to check if the combined
number is greater than 127, since the interpreter's OP_ADD
`output` should always be canonically represented as a
2-byte value if `128 <= output <= 255` (correct?).

This lets us elide the SWAP/ROT operations, dropping it to
35 bytes per of script per iteration of that loop (down
from 58 in my first impl!). Total savings across all loops
is 736 bytes, bringing the total script+witness size down
to about 7212 bytes, or 1803 vbytes. Very groovy!

// ... <b63> <b64>
SWAP DUP ADD DUP ADD DUP ADD DUP ADD ADD
SIZE <2> EQUAL IF
<128> SWAP SUB
IFDUP NOT IF <0x80> ENDIF
ELSE
DUP NOT IF <0x00> ENDIF
ENDIF

I revised the gist with the updated bitshift code, and more
detailed comments. Thank you!

https://gist.github.com/conduition/c6fd78e90c21f669fad7e3b5fe113182#file-winternitz-ts-L100-L137

regards,
conduition
> --
> You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/aGkYLuZZz2itqVJx%40erisian.com.au.
publickey - conduition@proton.me - 0x474891AD.asc
signature.asc

conduition

unread,
Jul 7, 2025, 9:15:46 PM (10 days ago) Jul 7
to Jonas Nick, bitco...@googlegroups.com
Hey Jonas, really cool to hear from you on this :)

> For further reductions in size, it may be worth looking
> into "Target Sum Winternitz" [0], where the checksum is
> hardcoded into the verifier instead of being an explicit
> part of the signature, at the cost of additional signing
> complexity.

If you take a second look at the script, we're actually
doing fixed-sum winternitz [0]. For w = 16 as I selected,
the optimal checksum for efficient signing is 512. You can
compute the optimal checksum with the expression `w*(n /
log2(w))/2` where n is the bit-length of the message to
sign.

Though unlike traditional fixed-sum WOTS, I didn't
implement the random salt counter appended to the sig, as
it isn't strictly needed. Remember: we're not WOTS-signing
a static TX sighash - we're signing an EC signature which
in turn signs the TX sighash. We can retry the EC signature
generation step with a new nonce `R` unlimited times until
we get an `(R, s)` pair whose hash fits the hardcoded
checksum requirement.

> I think the size difference largely comes from the fact
> that my implementation [2] is based on W-OTS+ [3] and not
> on W-OTS. The main difference is that W-OTS relies on
> some variant of collision-resistance of the hash
> function, whereas W-OTS+ only relies on the weaker
> preimage resistance property.

Agreed. AFAICT, the only reason we'd use WOTS+ over stock
WOTS (w/o randomizers) would be if we wanted to use a less
collision-resistant hash algo (RMD160) as the primary hash
function. Someone would need to do the math to see if the
hash size savings are enough to offset the added script
size cost.

Maybe you're not the right person to ask, but riddle me
this: Would OP_HASH160 (aka rmd160(sha256(...))) be a
possible contender for the hash function here, to shrink
the witness size further while still retaining some of the
collision resistance of SHA256?

[0]: https://gist.github.com/conduition/c6fd78e90c21f669fad7e3b5fe113182#file-winternitz-ts-L95-L98

regards,
conduition
> --
> You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/c2abfd68-f118-4951-ba4a-499fc819332f%40gmail.com.
publickey - conduition@proton.me - 0x474891AD.asc
signature.asc

conduition

unread,
Jul 9, 2025, 2:56:06 PM (8 days ago) Jul 9
to Jonas Nick, bitco...@googlegroups.com
Minor correction: the optimal checksum for w = 16 should actually be 480, not 512. I forgot the average of [0, 16) is actually 7.5 and not 8.
> To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/Um1180WhyfREJS4CHTfTCzAuDywzNlFlsaIFFwLEGcETcwKCDuJMgSwSs4idfqgCDqtMTuc4FUmcTHWnK2z_tzxw8bdVD9zDiGTCfdbJFjs%3D%40proton.me.
publickey - conduition@proton.me - 0x474891AD.asc
signature.asc

Jonas Nick

unread,
Jul 9, 2025, 2:56:10 PM (8 days ago) Jul 9
to conduition, bitco...@googlegroups.com
> Agreed. AFAICT, the only reason we'd use WOTS+ over stock
> WOTS (w/o randomizers) would be if we wanted to use a less
> collision-resistant hash algo (RMD160) as the primary hash
> function.

When using RMD160 in WOTS+ instead of SHA256, you reduce the security level to
80 bits. Roughly speaking, while WOTS+ relies only on preimage resistance,
quantum computers get a quadratic speedup finding preimages due to Grover's
algorithm. A more detailed analysis of this is in [0] (see Theorem 2 and Table
1).

> Would OP_HASH160 (aka rmd160(sha256(...))) be a
> possible contender for the hash function here, to shrink
> the witness size further while still retaining some of the
> collision resistance of SHA256?

I'm probably missing something, but I don't see how this would work because you
can find a collision with about 2^80 queries.

[0] https://eprint.iacr.org/2015/1256.pdf (This should have been link [5] in the
previous email, sorry)
Reply all
Reply to author
Forward
0 new messages