Post
Topic
Board Development & Technical Discussion
Re: Emulating OP_CHECKSIGFROMSTACK with a chain of OP_CHECKSIG operations
by
stwenhao
on 18/08/2025, 03:54:16 UTC
Quote
Create a 4-of-4 multisig address using these 4 public keys and send some sats to the address
In that case, all you need, is just the simplest 2-of-2 multisig. The first public key can be set to anything, and the second one can have the private key, set to the hash of the message. Which means:
Code:
SHA-256("TEST")=94ee059335e587e501cc4bf90613e0814f00a7b08bc7c648fd865a2af6a22cc2
d=94ee059335e587e501cc4bf90613e0814f00a7b08bc7c648fd865a2af6a22cc2
Q=03DD3E1A26D56446C4D09D1A72D545599603519B34165352054D0DF2F6D453F93D
And then, if you want to use some multisig, you can just do that:
Code:
2 <yourPubkey> 03DD3E1A26D56446C4D09D1A72D545599603519B34165352054D0DF2F6D453F93D 2 OP_CHECKMULTISIG
However, in that case, it can be even simplified to a single signature: https://gnusha.org/pi/bitcoindev/ZVcdKupXU+wjawRI@erisian.com.au/
Code:
Sign-to-contract looks like:

 * generate a secret random nonce r0
 * calculate the public version R0 = r0*G
 * calculate a derived nonce r = r0 + SHA256(R0, data), where "data"
   is what you want to commit to
 * generate your signature using public nonce R=r*G as usual
And then, for any existing public key, you can commit to any message, by just tweaking your R-value, so all interested parties can see, that you signed this transaction, and committed to a given message, at the same time.

However, this is not the end goal, because then, how do you want to meet some use cases of OP_CHECKSIGFROMSTACK? One of them is allowing to move your coins, if a given message is signed. Which means, that you have for example some transaction:
Code:
SHA-256("txdata")=6c25cd565b52a36694c131a9ec0385dd2854532c8cbbe05c574a73eea6fe6268
SHA-256(6c25cd565b52a36694c131a9ec0385dd2854532c8cbbe05c574a73eea6fe6268)=f98412627d319e88ca3b80540a9c94338cd6bea9d39e0ff07377f3d1757b0cdd
z=f98412627d319e88ca3b80540a9c94338cd6bea9d39e0ff07377f3d1757b0cdd
And then, you want to make your coins spendable, only if someone will sign a transaction, where z-value is set to f98412627d319e88ca3b80540a9c94338cd6bea9d39e0ff07377f3d1757b0cdd, with a given public key. How do you want to use multisig tricks, or R-value tweaking tricks, to get there? Because the whole point of OP_CHECKSIGFROMSTACK is to allow spending a given coin, if some arbitrary message with a given z-value is signed. Which means, that you want to put for example f98412627d319e88ca3b80540a9c94338cd6bea9d39e0ff07377f3d1757b0cdd as a message in OP_CHECKSIGFROMSTACK, which then would be hashed, would give us f98412627d319e88ca3b80540a9c94338cd6bea9d39e0ff07377f3d1757b0cdd, and would be checked for ECDSA correctness for a given public key.

In the meantime, I figured out, how to make a chain of public key recovery operations:
Code:
+--------+----------------------------------------------------------------+------------------------------------+
| Sigops | Address                                                        | Script                             |
+--------+----------------------------------------------------------------+------------------------------------+
|      1 | tb1qae4ms66yxwfe9whxx8y0v8wc7qyjg0rrttdtx0qtyzfr5fu5hu3qv5lwmj | ac                                 |
+--------+----------------------------------------------------------------+------------------------------------+
|      2 | tb1qlp50wauhzxnn05km0sz7hhpu636v28a4gezl77hf2t0gtc3caepswac0k3 | 6ead757c                        ac |
+--------+----------------------------------------------------------------+------------------------------------+
|      3 | tb1qasxq9f4t9m9udu0ykm7g8t7qhrk32k75mxlg62cvr78a7208282qrhccmv | 6ead757c 6ead77                 ac |
+--------+----------------------------------------------------------------+------------------------------------+
|      4 | tb1qkwv68xranw4hzs0vd2d4dqmgk5ktw6ezc2aeeggtlkp6mt7tj6es0hcndm | 6ead757c 6ead77 6ead757c        ac |
+--------+----------------------------------------------------------------+------------------------------------+
|      5 | tb1qqjjya57d88tc8gzl8jhmmpzg5tg0lkydnvn6r65p2vewwk0y3etqd5hy5t | 6ead757c 6ead77 6ead757c 6ead77 ac |
+--------+----------------------------------------------------------------+------------------------------------+
|      6 | tb1q9430nrfs7w4k8y3kk9snvf5xn9hu2sg9773tnhhx4dpp08mlu60s33w8w3 | 6ead757c 6ead77 6ead757c 6ead77    |
|        |                                                                | 6ead757c                        ac |
+--------+----------------------------------------------------------------+------------------------------------+
|      7 | tb1q9dhhr3vk42gz8q3fugyy5fkh4d9jr00t8yq2uus7jw4j78xgryfqjcz2gz | 6ead757c 6ead77 6ead757c 6ead77    |
|        |                                                                | 6ead757c 6ead77                 ac |
+--------+----------------------------------------------------------------+------------------------------------+
|      8 | tb1qd34ywwtlsu0gmqugfu6jqyk47ztrdtrjj483yn5sdhk3ewge89qq70dw5q | 6ead757c 6ead77 6ead757c 6ead77    |
|        |                                                                | 6ead757c 6ead77 6ead757c        ac |
+--------+----------------------------------------------------------------+------------------------------------+
|      9 | tb1quumhtnq5xafms4dwls582vspvqhwa5nzywmcsag7dk296g9kvkyq6gz6p8 | 6ead757c 6ead77 6ead757c 6ead77    |
|        |                                                                | 6ead757c 6ead77 6ead757c 6ead77 ac |
+--------+----------------------------------------------------------------+------------------------------------+
|     10 | tb1qw7a6954v45jk3kawdx0m6z8ycjs0w9x3445hhva2hz8zpymgv00sqd8pew | 6ead757c 6ead77 6ead757c 6ead77    |
|        |                                                                | 6ead757c 6ead77 6ead757c 6ead77    |
|        |                                                                | 6ead757c                        ac |
+--------+----------------------------------------------------------------+------------------------------------+
And here is how it can be decoded for five sigops:
Code:
decodescript 6ead757c6ead776ead757c6ead77ac
{
  "asm": "OP_2DUP OP_CHECKSIGVERIFY OP_DROP OP_SWAP OP_2DUP OP_CHECKSIGVERIFY OP_NIP OP_2DUP OP_CHECKSIGVERIFY OP_DROP OP_SWAP OP_2DUP OP_CHECKSIGVERIFY OP_NIP OP_CHECKSIG",
  "desc": "raw(6ead757c6ead776ead757c6ead77ac)#8lv7zd9x",
  "type": "nonstandard",
  "p2sh": "2MyDk9pDzL17MXvFHydFdAEKNwFhauqza4z",
  "segwit": {
    "asm": "0 04a44ed3cd39d783a05f3cafbd8448a2d0ffd88d9b27a1ea815332e759e48e56",
    "desc": "addr(tb1qqjjya57d88tc8gzl8jhmmpzg5tg0lkydnvn6r65p2vewwk0y3etqd5hy5t)#zrvc4p8m",
    "hex": "002004a44ed3cd39d783a05f3cafbd8448a2d0ffd88d9b27a1ea815332e759e48e56",
    "address": "tb1qqjjya57d88tc8gzl8jhmmpzg5tg0lkydnvn6r65p2vewwk0y3etqd5hy5t",
    "type": "witness_v0_scripthash",
    "p2sh-segwit": "2N7uvJnvanFT2L4jEAuanbhM9FbFaygTWvb"
  }
}
Any witness stack push can take up to 520 bytes, which means, that the upper limit is something like 149 sigops. However, the shorter the chain, the cheaper it is, so I am trying to make it smaller, to not consume hundreds of sigops for a simple OP_CHECKSIGFROMSTACK.