-
Notifications
You must be signed in to change notification settings - Fork 36.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make P2SH redeem script "IF .. PUSH <x> ELSE ... PUSH <y> ENDIF CHECKMULTISIG .. " standard #26348
Conversation
Isn't this already policy-accepted for P2SH, Segwit, and Taproot? Is there a benefit to having it raw? |
Interesting Script edge case! You must have balanced the cost of proposing this standardness rule change, and making a PR touching consensus code, with switching to using Segwit scripts (either v0 or v1) which don't have this limitation (and have other advantages). Is there a particular reason why you can't migrate to Segwit?
Bitcoin users would probably just do that under P2WSH or Taproot nowadays. And that would be standard. But even under P2SH context it's far from being the only alternative. You could also just count public keys, for instance (untested):
For a small number of pubkeys this is standard under P2SH, and the next limit you'd hit would probably be Writing Scripts by hand is a huge footgun, and we now have tooling to do that safely. The implementation of Miniscript in Bitcoin Core does not support P2SH context. But other implementations do if you really can't migrate to at least Segwit v0. |
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
The cost for the RSK community to modify RSK to switch to segwit is very high, as it is a hard-fork. All the code that produces and interprets Bitcoin transactions is consensus code in RSK.
But this is still a hard-fork, and the full upgrade can take several weeks, considering the public review stage and all further stages. The RSK community mid-term plan is to migrate to segwit. Regarding touching the consensus code, I firmly believe that GetSigOpCount() should be split in two functions: one that is called only by consensus code and one that is only called by AreInputsStandard(). This is obvious to me, since the standard rules can change over time, but not the consensus rules, and there is a real risk that a tweak of the standard rules ends up hiding an unintended consensus change. I was surprised to discover that GetSigOpCount() is called by consensus code with fAccurate==true and fAccurate==false, so basically that function should be set in stone. Therefore, even if the you are in doubt if the addition of two-paths multisigs is a benefit, I hope you still see the benefit to split GetSigOpCount() into two initially mirrored functions with different names, and we can do this in a separate PR. |
It's not accepted in P2SH. That's the problem. |
I understand the urgency for the RSK community. To be clear i'm not opposing relaxing this standardness rule on its own, i'm just questioning whether it's worth it. You mention the mid-term plan is for RSK to migrate to Segwit. But proposing a standardness rule change does not seem like a short-term plan to me: even in the best case scenario that this PR is getting enough interest, review (i haven't looked at the code yet but it's currently failing CI) and gets eventually merged before the next release branch-off, then that a release gets shipped with the new standardness rule and that at least part of the nodes and hashrate upgrade to this new rule. That's what, 1 year at least before your transactions get relayed? Is it reasonable to rely on direct communication with mining pools for that long? And won't the switch to Segwit happen before that? |
I've thought more about @darosior comment. Therefore, even if the patch is not merged into Bitcoin, by reviewing the patch and giving your thumbs up that the code is sound, you're helping also the rootstock community. |
Regarding the CI failure, I fixed it. But this brings an interesting topic to discuss (separate of this patch): Invalid scripts are detected obviously at some later moment and transactions containing invalid scripts are never accepted into the memory pool. To summarize, on an invalid script detected: option 1) return 20 -> avoid heavy computations, do not penalize My code currently does 3, from the point the script is detected as invalid. |
Tend to agree with darosior, see also the comment by achow101 in #20178 (comment) . So if it is not a goal to get this patch merged, it might be easier to comment out the |
But by changing our script to This patch enables RSK to go back to having 15 signatories in one year. |
you misunderstand: if core does not merge this patch, a sufficient number of miners will change to a proprietary ruleset to support rootstock. it would be a mistake, IMO, for core to not try to match the policy miners will adopt in a case like this. |
strong concept ACK, will need to review the particulars of the patch. |
I think @darosior has already said what I would say. 15 sig limit seems like the limit due to legacy p2sh(unless you put all keys behind hashes, havent worked all that out), and future tooling subsumes this discussion post-segwit. So we're left with the immediate issue at hand. Another alternative would be to ask RSK miners to mine these transactions directly? |
Was this chosen for a particular reason? Most of the patch complexity appears to be this restriction. |
Strongly disagree. Miners should aim to match nodes, not vice-versa. |
i don't think that's particularly true to how the mempool is used today. for example, fee estimation uses transactions that miners likely have to estimate how much fee to pay to be in the percentile desired by the user. if the user nodes do not have transactions that miners have, then the users fee estimation will be off, which is not incentive aligned for those users. so the users will want to -- especially in "benign" cases like this -- want to match miner policy. |
On the contrary, making it work for arbitrary IF-nesting requires using an additional stack, which brings the same complexities as normal script execution. Also, AFAIK the policy regarding standard transaction rules has always been to enable the minimum subset of standard scripts to cover existing use cases, as an additional security barrier. Right? |
I appreciate the interesting discussion regarding who establishes the mempool policy, and I think this is a hot topic right now, but in this particular case, that political discussion will just obfuscate the simple fact that enabling a two-path multisig is a simple, concrete, rational, logical and necessary step to support the federated networks use case. The existence of the Rootstock sidechain is a proof that the two-path multisig script is useful. Temporarily some miners will apply this patch, and then the rootstock community will switch to an alternate script (that supports less signatories) so that miners can later revert the patch. |
Apologies, I actually missed the crux of the solution here. I now know why the complexity is here.
There is a counter-balance to making sure the new check isn't too complex, as there is a maintenance trade-off as well cost if we get the correctness wrong and it shuts off other scripts' standardness. If something was extremely simple and relaxed a bit further, it's worth consideration. f.e. we could relax |
What is the current standard limit on the number of sigops in a P2WSH redeem script ? |
|
Theoretically, if every opcode in the script is a checksig, you can do 10k checks per script in P2WSH vs just 15 in P2SH. It seems that the only reason for such gap in cost is quadratic-hashing attacks. Segwit checksigs do not force script re-hashing, and that's why the limit is much much higher Therefore we can assume that MAX_P2SH_SIGOPS being low is a protection against quadratic-hashing attacks. When this limit was introduced (2014, see commit here and rationale here), the problem was already known (I reported it in 2013). Therefore, changing MAX_P2SH_SIGOPS to match exactly MAX_PUBKEYS_PER_MULTISIG, even if trivial, slightly degrades Bitcoin's DoS security. The patch I propose is a little more complex but does not reduce the DoS security of Bitcoin in any way. |
I think As a result, I don't see how you get more than 10 normal keys in the script you've presented in the first place: the Playing with miniscript suggests a solution:
which would let you have n=14 via a 508 byte script (n=15 gives a 536 byte script). (If your emergency keys are also valid for regular usage, you could increase that up to n=18). In general, the pkh approach would allow up to 20 distinct keys in a p2sh script (eg, if you wanted a 20-of-20 multisig), absent the
I don't think this is meaningfully true? You get I'm assuming a p2sh script of In the worst conceivable case, where you somehow achieve 15 sigops with a 107 byte script (34 bytes to push a pubkey, 58 bytes to push a tiny exploitable signature, and 15 opcodes to generate 15 sigops from that), that would result in a 100kB tx requiring 1GB of hashing, while having 20 opcodes generate 20 sigops would result in 1.3GB of hashing (ie a 29% increase, vs the 33% increase of 20 vs 15). If you're a miner, you're not constrained to 100kB txs, but in that case you're also not constrained by MAX_P2SH_SIGOPS either. So in conclusion:
|
Related: #2256 |
issue seems to be that there are existing coins stuck because of this. |
The RSK mainnet address on bitcoin is https://mempool.space/address/3DsneJha6CY6X9gU2M9uEc4nSdbYECB4Gh which has no spends so does seem stuck, but I haven't been able to reconstruct the redeemScript that corresponds to that address. The commit_federation tx that updated to that address on the RSK chain seems to be https://explorer.rsk.co/tx/0x83708fd332cbe0f9614dd61d0c95c729aded0b7a94d793f22412493d6f867b43?__ctab=Logs but that just has the address hardcoded in the data as hex, and as far as I've tried, I can't make the newFederationBtcPublicKeys listed there and any of the script templates and emergency keys match up with the 3Dsne address; there's a tool at https://github.com/rsksmart/powpeg-details/ that seems like it should be able to provide the redeem script, but also doesn't seem to produce a match (it doesn't help that there seems to have been a bug where the CSV value was pushed with the wrong endinaness in some versions). That stops me from being able to confirm that increasing MAX_P2SH_SIG_OPS actually unsticks these transactions. If increasing MAX_P2SH_SIG_OPS does fix the problem, I think that is the immediate fix, and switching to Some RSK addresses on bitcoin testnet are:
Those at least seem like they match the script template in the way I'd expect. |
Apparently I failed to sort the emergency keys correctly. The redeemScript is So I think that confirms that changing MAX_P2SH_SIGOPS is a sufficient fix, and I've made a patchset which is available at https://github.com/ajtowns/bitcoin/tree/202212-rsk-p2sh-sigops . That only touches policy and test code so is hopefully fairly easy to verify for correctness, and should be a trivial merge on top of either 24.0.1 or master. I'm not sure if it would make sense to merge in general though, as the risk of expanding the attack service for quadratic hashing does exist, and the benefits of being able to do weird multisig things in p2sh rather than switching to segwit or taproot seem very small. Even if it's not merged in core, I think it would be reasonable for miners to run that patch temporarily in order to unstick RSK peg-out transactions, without requiring constant manual intervention. If a miner really wanted to make sure that they don't introduce additional quadratic hashing risk, then I think you could set things up as follows:
Setting |
Spends seem to be going through on occasion now, eg: https://mempool.space/tx/b3ade8b5d893da6a76f1977ea27352ab1cac679eb5d950793161607088f01c14 albeit at relatively high fee rates, and the hard fork to a p2sh script that is spendable with current standardness rules is apparently due to activate ~tomorrow https://blog.rsk.co/noticia/rsk-hop-4-0-1-and-4-1-1-are-here-mainnet-versions/ |
This seems like a safe way to preserve the cap on sighashing while permitting these transactions.
Am I correct in understanding that, in addition to changing future redeemScripts, the hard fork (namely RSKIP357) increases the window within which RSK will try to get nonstandard migration transactions mined, i.e. RSK still needs some miner to include the nonstandard txns? Hoping to clarify whether the hard fork by itself automatically unblocks all stuck funds, my understanding is it doesn't (?). |
My understanding is the "stuck" funds are in 3DsneJha6CY6X9gU2M9uEc4nSdbYECB4Gh ( |
The stuck funds have been moved to 35iEoWHfDfEXRQ5ZWM5F6eMsY2Uxrc64YK which has successfully been spent from in tx 67e950218f85cf1c35ffcb0aad2436822b1de96c9d68a17ac8d9a14c4a02a85c (and that tx appears to have at least made it into my mempool when broadcast, prior to being mined). There's still about 0.0134 BTC in the old stuck address; meanwhile the new address now holds about 3476 BTC (funds were unstuck in block 773957, mined by marapool). So I think this PR can be closed? |
Going to close this for now. |
13eb8aa doc: Release notes for testnet defaulting to -acceptnonstdtxn=0 (Anthony Towns) e1dc15d config: default acceptnonstdtxn=0 on all chains (Anthony Towns) Pull request description: Changes `-acceptnonstxtxn` to default to 0 on testnet, matching the other chains. Allowing non-standard txs on testnet by default contributed to the difficulties RSK described in #26348: "We see that there are two script paths and, to reduce the script size, a single CHECKMULTISIG is used for the two paths, separating the signer count from the CHECKMULTISIG opcode. This script worked on testnet, because it lacks the standard checks performed in Mainnet." ACKs for top commit: MarcoFalke: lgtm ACK 13eb8aa sipa: utACK 13eb8aa instagibbs: utACK 13eb8aa theStack: Code-review ACK 13eb8aa Tree-SHA512: eff7a3f9fc9b94003a730beb96e6f3399bc8b8e93fde4b15f20a11eda61d9a3e076f4423989f98b794b32681abecbc3756a54cd0d37b136e2fb2ffbb47ee7774
To characterize a transaction as standard or non-standard, Bitcoin Core tries to estimate the number of signature checks a particular script can potentially request. A maximum of 15 signature checks is allowed. The function GetSigopCount()
counts the maximum number of signature checks performed by a script.
For each CHECKSIG or CHECKSIGVERIFY found in a script, one is added to the signature check counter. For multisigs, the maximum number of checks depends on the number of signatories. When CHECKMULTISIG is executed, this number is at the top of the stack. However, to avoid executing the script to obtain the top of the stack value, Bitcoin Core performs an estimation based on static analysis of the script, which returns only an upper bound. This is what GetSigOpCount(true) does. However, the static analysis is very rudimentary, and it overestimates the signature checks for certain common scripts.
How GetSigOpCount() works
GetSigOpCount() assumes that the number of signers is pushed into the stack immediately before the CHECKMULTISIG opcode. If the previous opcode is not a push opcode, then this function assumes the worst case for each CHECKMULTISIG found, which is 20 signatures (defined by MAX_PUBKEYS_PER_MULTISIG). The problem is that this constant is greater than the maximum number of checks allowed per P2SH redeem script, which means that any P2SH script that uses CHECKMULTISIG must specify the number of signers with a PUSH opcode right before.
The bug in the RSK sidechain
To add a time-locked emergency multisig to RSK powpeg, the RSK community, with the help of RSK core developers, migrated the multisig script that holds the funds of the peg from a traditional P2SH multisig to the following transaction redeem script:
We see that there are two script paths and, to reduce the script size, a single CHECKMULTISIG is used for the two paths, separating the signer count from the CHECKMULTISIG opcode. This script worked on testnet, because it lacks the standard checks performed in Mainnet.
The Liquid sidechain uses a very similar script, but using segwit addresses.
We can see here that GetSigOpCount() will find that END_IF is not a push opcode and will assume the number of signature checks is MAX_PUBKEYS_PER_MULTISIG.
Motivation for Changing Bitcoin's standard rules
Since having two execution paths for two different multisigs, one of them usually time-locked, is a very common use case, Bitcoin users can benefit from relaxing the standard rule and making two-paths multisigs standard.
The alternate way of having two-paths multisigs is by adding the CHECKMULTISIG at the end of every path. However, this reduces the maximum number of functionaries of the first (non time-locked) multisig, since the sum of both must be below 15.
Another benefit is that this change separates the new GetStandardSigOpCount() function used for standard rules from the old GetSigOpCount() method that is used in consensus code (both with fAccurate==true and fAccurate=false). This is very important because consensus rules should not be mixed with non-consensus rules in a dangerous way, as it is today.
An additional benefit for the RSK community is that if Bitcoin Core implements this improvement, the RSK community avoids preparing and activating a hard-fork to unblock the funds in the peg.
I include additional unit tests to show that: