How to Mimic OP_CHECKSIGFROMSTACK with Multiple OP_CHECKSIG Steps

5 replies 167 views
byte2019Senior Member
Posts: 270 · Reputation: 1836
#1Jun 25, 2023, 03:05 PM
Every signature connects two public keys on the secp256k1 curve. Basically, using 256-bit addition and multiplication, you can show that someone has control over a private key, as long as everything checks out according to the rules. But ECDSA isn’t just about signing; it can also function as a 256-bit calculator. A simple example is when you put a signature in the output script, and the user needs to provide a corresponding public key through public key recovery. If a signature is created with SIGHASH_ALL, it’s like pushing a message hash called z-value, converting it to a public key, and tweaking it with the r-value and s-value from that signature. The cool part is that you can do public key recovery multiple times, which means you can create a chain of these operations. For instance: You don’t just start with a public key but also include another signature that would work for the public key you just recovered. This indirectly shows that the r-value of the signature is reachable and that the k-value is known, as long as it’s also signed with SIGHASH_ALL. And you can keep extending this chain. Starting with "pubkeyA," you can use any "signatureB," recover the public key to get "pubkeyC," then require "signatureD" to get to "pubkeyE." The overall length of this chain is capped by consensus rules, such as the maximum size of a script and the maximum number of sigops, among other limits. So, if you kick off with "pubkeyA" and finish the chain with "pubkeyZ," the final public key…
2 Reply Quote Share
ninja_nodeFull Member
Posts: 89 · Reputation: 647
#2Jun 25, 2023, 09:09 PM
I can give you some hint: Instead of trying to recover some public key from some signature, or checking for any valid signature for a given key, just allow pushing any signature, and any public key. And then, when you will have that data on the stack, only then try to apply public key recovery on top of what was pushed (for example by using OP_2DUP just before), and check, if there is a match. From that point, you will get quite close to OP_CHECKSIGFROMSTACK, because then, all values in "s=(z+rd)/k" can be controlled by the solver. And then, if you form a bigger chain, like "OP_CHECKSIGVERIFY OP_CHECKSIG", or even "3 OP_CHECKMULTISIG", then you should know, what to do next.
3 Reply Quote Share
GrimMoonMember
Posts: 17 · Reputation: 163
#3Jun 26, 2023, 10:41 AM
This is convoluted and won't work. The first problem is the assumption that you can create a spendable UTXO by embedding the signature in the output script. That runs into a circular dependency, because the sighash commits to the transaction that defines the output in the first place. > We can start from "pubkeyA", allow using any "signatureB", apply public key recovery, and reach "pubkeyC" I’m not sure what this is supposed to mean. In ECDSA, a signature lets you recover the public key that produced it. So unless "pubkeyC" is just the same as "pubkeyB," this step doesn’t make sense.
2 Reply Quote Share
ninja_nodeFull Member
Posts: 89 · Reputation: 647
#4Jun 26, 2023, 03:41 PM
1. It is convoluted, because it is very unlikely, that a soft-fork, which would allow using OP_CHECKSIGFROMSTACK, would be activated directly. Only when convoluted solutions will be deployed, and people will see, that the same feature can be achieved, while consuming much more sigops, only then the community will agree to activate something, which would let doing the same things in less convoluted way. The same was true with transaction fees: they would never be lowered, if miners wouldn't start confirming such transactions. 2. It could work. Here is another hint: every time, when a public key is recovered, you have more than one key to choose from. If you do it properly, then you can reach a path between any two public keys. If you assume, that each OP_CHECKSIG lets you pick any of the two public keys (one recovered from R-value prefixed with 02, another with R-value prefixed with 03), then after 256 OP_CHECKSIGs, you can go from every public key, to every other public key, through a chain of signatures. Each public key recovery operation will force you to put z-value of a given transaction, wrapped into some kind of public key, and adjusted by used r-value and s-value. And if you do more operations on top of what you just recovered, then you can make equations, which would work, no matter which z-value would be picked. It is all about repeating public key recovery enough times, so that you can travel through secp256k1, like you would in a binary tree. By picking different public keys down the road, you can reach a different end result, and then, if you build a path between two public keys, representing what you want to sign, and by whom, then you can convert any signed message (consuming a single sigop), into a chain of equivalent OP_CHECKSIGs (consuming hundreds of sigops). It is the whole point, to travel from one public key to another. Each signature is just a connection between two different public keys, lying on secp256k1. One is Q-value, which is public key of the coin owner, and another is R-value, which is public key of the signature nonce. If inside a given Script, you can find a connection between two different public keys, then you can convert between convoluted and simple version, and deploy OP_CHECKSIGFROMSTACK, wrapped in hundreds of OP_CHECKSIGs.
1 Reply Quote Share
mr_gasMember
Posts: 24 · Reputation: 174
#5Jun 26, 2023, 05:31 PM
Not sure if this emulates CHECKSIGFROMSTACK in the way you are trying to achieve it. I found this thread interesting and wanted to share this idea: 1. CHECKSIGFROMSTACK requires 3 parameters: signature, message and public key. What if we could combine message and public key? So, public key contains the message. 2. Lets assume the message is "TEST". We encode each character in the message to hex: 54 45 53 54. 3. Generate keypairs for each hex being the first in public key after prefix. 4. Create a 4-of-4 multisig address using these 4 public keys and send some sats to the address. This UTXO can only be spent when all the 4 keys sign the transaction and all the public keys combined will give us the message: TEST. Note: This is just an experiment. Don’t try it on mainnet.
3 Reply Quote Share
byte2019Senior Member
Posts: 270 · Reputation: 1836
#6Jun 26, 2023, 10:35 PM
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: And then, if you want to use some multisig, you can just do that: However, in that case, it can be even simplified to a single signature: https://gnusha.org/pi/bitcoindev/ZVcdKupXU+wjawRI@erisian.com.au/ 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: 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: And here is how it can be decoded for five sigops: 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. Edit: In post-quantum world, it could be possible in Taproot: Spending "OP_SHA256 OP_CHECKSIG" as a TapScript Then, if all public keys could be trivially converted to corresponding private keys, people could push any message on the stack, hash it with OP_SHA256 (or any other opcode, which would give us 256-bit result, like OP_HASH256), and then, by providing a matching signature, it will be signed by the quantum-broken private key. However, as long as this is not the case, other methods are needed.
2 Reply Quote Share

Related topics