Liam Eagen came up with a clever optimization for publishing proof data in BitVM-style bridges. Instead of Lamport/Winternitz signatures, it uses adaptor signatures. The message is split into chunks (e.g., 8- or 11-bit digits), and for each digit a Schnorr signature is provided.
Naively, the unlocking script would require a separate public key for each digit. The following construction shows how to use OP_CODESEPARATOR to instead require just a single public key, regardless of the number of digits. The key idea is to use OP_CODESEPARATOR to modify the sighash so that each adaptor signature is tied to a specific digit.
The Script to verify N distinct adaptor signatures with a single key:
//
// unlocking script
//
<sig1>
<sig2>
...
<sigN>
//
// locking script
//
<pubkey>
// repeat N-1 times
OP_TUCK
OP_CHECKSIGVERIFY
OP_CODESEPARATOR
// last signature
OP_CHECKSIG
Pushing N different keys would normally require 33 bytes each, for a total of 33*N bytes. With our optimization, this drops to 33 + 2*(N-1) bytes.
So the total script size (locking and unlocking script including N 65-byte signatures and checksig opcodes) becomes:
65*N + 33 + 3*(N-1) + 1 bytes.
Assuming that the Groth16 proof size is 128 bytes plus 20 bytes of public input, and the digits are 11 bits, we have N = 148*8/11 = 108. Therefore, the total size is about 7.3 kB (= 1.8 kvB), instead of 10.7 kB unoptimized.
Thanks to uncomputable for pointing out a bug in the script and providing an elegant fix, and to Sander for implementing a proof of concept.