Last active
July 8, 2025 07:03
-
-
Save conduition/c6fd78e90c21f669fad7e3b5fe113182 to your computer and use it in GitHub Desktop.
WInternitz One-time Signatures on Bitcoin using OP_CAT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
what should i do