Skip to content

Instantly share code, notes, and snippets.

@conduition
Last active July 8, 2025 07:03
Show Gist options
  • Save conduition/c6fd78e90c21f669fad7e3b5fe113182 to your computer and use it in GitHub Desktop.
Save conduition/c6fd78e90c21f669fad7e3b5fe113182 to your computer and use it in GitHub Desktop.
WInternitz One-time Signatures on Bitcoin using OP_CAT
// input witness stack:
// <h1> <b1>
// ...
// <h64> <b64>
// <ec_signature>
OP_DUP OP_TOALTSTACK // copy EC signature to alt stack
<G> OP_CHECKSIGVERIFY // verify EC signature matches TX
for i = 64; i > 0; i--:
// starting stack:
// <h_i> <b_i>
OP_DUP OP_TOALTSTACK // copy message word <b_i> to alt stack
// compute the number of hash iterations
<15> OP_SWAP OP_SUB // <h_i> <15 - b_i>
// 13 bytes
OP_DUP <8> OP_GREATERTHANOREQUAL // <h_i> <15 - b_i> <true/false>
OP_IF // <h_i> <15 - b_i>
OP_SWAP // <15 - b_i> <h_i>
{for 0..4 OP_HASH256 done} // <15 - b_i> <sha256^8(h_i)>
OP_SWAP // <sha256^8(h_i)> <15 - b_i>
<8> OP_SUB // <sha256^8(h_i)> <15 - b_i - 8>
OP_ENDIF
// 11 bytes
OP_DUP <4> OP_GREATERTHANOREQUAL // <h_i> <15 - b_i> <true/false>
OP_IF // <h_i> <15 - b_i>
OP_SWAP // <15 - b_i> <h_i>
OP_HASH256 OP_HASH256 // <15 - b_i> <sha256^4(h_i)>
OP_SWAP // <sha256^4(h_i)> <15 - b_i>
<4> OP_SUB // <sha256^4(h_i)> <15 - b_i - 4>
OP_ENDIF
// 10 bytes
OP_DUP <2> OP_GREATERTHANOREQUAL // <h_i> <15 - b_i> <true/false>
OP_IF // <h_i> <15 - b_i>
OP_SWAP // <15 - b_i> <h_i>
OP_HASH256 // <15 - b_i> <sha256(sha256(h_i))>
OP_SWAP // <sha256(sha256(h_i))> <15 - b_i>
<2> OP_SUB // <sha256(sha256(h_i))> <15 - b_i - 2>
OP_ENDIF
// 7 bytes
OP_DUP // <h_i> <15 - b_i> <15 - b_i>
OP_IF // <h_i> <15 - b_i>
OP_SWAP // <15 - b_i> <h_i>
OP_SHA256 // <15 - b_i> <sha256(h_i)>
OP_SWAP // <sha256(h_i)> <15 - b_i>
OP_1SUB // <sha256(h_i)> <15 - b_i - 1>
OP_ENDIF
// Stack: <h_i_tip> <0>
OP_NOT // <h_i_tip> <1>
OP_VERIFY // <h_i_tip>
OP_TOALTSTACK //
// Alt stack: <ec_signature> <b64> <h64_tip> ... <b1> <h1_tip>
OP_FROMALTSTACK // <h1_tip>
OP_FROMALTSTACK // <h1_tip> <b1>
OP_TUCK // <b1> <h1_tip> <b1>
OP_SWAP // <b1> <b1> <h1_tip>
// Hash each chain tip with the next one, creating an imbalanced merkle tree.
// Also add each message byte together to compute the WOTS checksum.
for 0..63:
OP_FROMALTSTACK // <b1> <b1> <h1_tip> <h2_tip>
OP_CAT OP_SHA256 // <b1> <b1> <sha256(h1_tip || h2_tip)>
OP_SWAP // <b1> <hash_tip> <b1>
OP_FROMALTSTACK // <b1> <hash_tip> <b1> <b2>
OP_TUCK OP_ADD // <b1> <hash_tip> <b2> <b1+b2>
OP_ROT // <b1> <b2> <b1+b2> <hash_tip>
// etc
// For example, the second iteration looks like:
// OP_FROMALTSTACK // <b1> <b2> <b1+b2> <hash_tip> <h3_tip>
// OP_CAT OP_SHA256 // <b1> <b2> <b1+b2> <hash_tip_new>
// OP_SWAP // <b1> <b2> <hash_tip_new> <b1+b2>
// OP_FROMALTSTACK // <b1> <b2> <hash_tip_new> <b1+b2> <b3>
// OP_TUCK OP_ADD // <b1> <b2> <hash_tip_new> <b3> <b1+b2+b3>
// OP_ROT // <b1> <b2> <b3> <b1+b2+b3> <hash_tip_new>
// Eventually, we have:
// <b1> <b2> ... <b64> <b1+b2+...+b64> <hash_tip>
// Verify the recomputed WOTS chain tips match the given digest
<wots_pubkey_digest> OP_EQUALVERIFY // <b1> ... <b64> <b1+b2+...+b64>
// Verify the WOTS checksum equals a fixed value (to prevent forgery).
// 480 is the optimal checksum to make signing faster. Which checksum
// we choose has no effect on security, as long as it is consistent.
<480> OP_EQUALVERIFY // <b1> ... <b64>
// Concatenate the message words into bytes
for i in 0..32:
// ... <b63> <b64>
// combine the 4-bit numbers mathematically (10 bytes)
OP_SWAP // ... <b64> <b63>
OP_DUP OP_ADD // ... <b64> <(b63 << 1)>
OP_DUP OP_ADD // ... <b64> <(b63 << 2)>
OP_DUP OP_ADD // ... <b64> <(b63 << 3)>
OP_DUP OP_ADD // ... <b64> <(b63 << 4)>
OP_ADD // ... <(b63 << 4) + b64>
// At this point the top stack value `x = (b63 << 4) + b64` is numerically correct,
// but is represented as a byte vector `m = scriptnum_encode(x)` on the stack.
// For sig-hashing to work correctly we must represent `x` as a one-byte vector,
// which may mean converting it to a negative number.
//
// ... <m>
// 24 bytes
OP_SIZE <2> OP_EQUAL OP_IF
// x >= 128: convert `m` to a negative int8 `128 - m` so that `script_encode(128 - m) = [x]`.
// Subtracting from 128 converts 0x8100-0xff00 to 0x81-0xff.
<128> OP_SWAP OP_SUB // ... <128 - m>
OP_IFDUP OP_NOT // ... <128 - m> <0> OR ... <1>
// special case to handle x = 128, resulting in a "negative zero" byte we must push.
OP_IF <0x80> OP_ENDIF
OP_ELSE
OP_IFDUP OP_NOT // ... <m> <0> OR ... <1>
// special case to handle x = 0, resulting in a zero byte we must push.
OP_IF <0x00> OP_ENDIF
ENDIF
// ... <[x]>
OP_TOALTSTACK // ...
// alt stack: <ec_signature> <b64 + (b63 << 4)> .. <b2 + (b1 << 4)>
for 0..32:
OP_FROMALTSTACK
// Raw message bytes are now on the stack
// <m1> ... <m32>
for 0..31:
OP_CAT
// Raw message vector is now on the stack
// <m1 || m2 || ... || m32>
// Pop the EC sig from the alt stack and validate its hash was the
// message signed by the WOTS key.
OP_FROMALTSTACK // <m1 || m2 || ... || m32> <ec_signature>
OP_SHA256 // <m1 || m2 || ... || m32> <sha256(ec_signature)>
OP_EQUAL // <true/false>
@Welbert5753
Copy link

what should i do

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment