See specification at https://github.com/bitcoincashorg/bitcoincash.org/pull/375
Details
- Reviewers
Mengerian jasonbcox Fabien markblundeberg - Group Reviewers
Restricted Project - Maniphest Tasks
- T528: Add Schnorr support to OP_CHECKMULTISIG (new mechanics)
- Commits
- rSTAGINGfbb292307a62: Implement new checkmultisig trigger logic and execution logic.
rABCfbb292307a62: Implement new checkmultisig trigger logic and execution logic.
make check
Diff Detail
- Repository
- rABC Bitcoin ABC
- Branch
- multisigschnorr
- Lint
Lint Passed - Unit
No Test Coverage - Build Status
Buildable 7030 Build 12107: Bitcoin ABC Buildbot (legacy) Build 12106: arc lint + arc unit
Event Timeline
src/script/interpreter.cpp | ||
---|---|---|
1023 ↗ | (On Diff #10073) | You are much better off working with unsigned when you handle bitfields. |
1026 ↗ | (On Diff #10073) | This is not the check you want to be doing. You want to check that:
|
1031 ↗ | (On Diff #10073) | Use proper variable instead of mutating other variable that mean something other than being a loop counter. You also probably want to have two loops, one to validate pubkey encoding and one to validate sigs. There is no point starting to validate sigs. More generally, you shoudl try to simplify this loop by shaving stuff out of it. |
1032 ↗ | (On Diff #10073) | When you nest most of the logic in an if, you indicate that you probably should bail the opposite branch early. |
1050 ↗ | (On Diff #10073) | It's not clear what this error stands for. It needs a better name. |
src/script/interpreter.cpp | ||
---|---|---|
1031 ↗ | (On Diff #10073) | Having a separate loop here to check pubkey encoding results is strange behavior: It would check all pubkeys for successful Schnorr multisigs, but not for the "0-of-N" or "false" cases. (Or for the legacy case). Two potential alternatives are:
|
src/script/interpreter.cpp | ||
---|---|---|
1031 ↗ | (On Diff #10073) | A separate loop to validate pubkey encoding would likely mean a change in behaviour -- all pubkeys must be correctly encoded. Is that intended? |
1032 ↗ | (On Diff #10073) | When the predicate here is false, we aren't bailing. There are still statements that need to be executed. |
1050 ↗ | (On Diff #10073) | I agree, any suggestions? |
src/script/interpreter.cpp | ||
---|---|---|
1031 ↗ | (On Diff #10073) | It doesn't check for 0 of n anyways and what is the false case ? If there is a discrepancy in behavior, this can make a good test case. I can't derive one from that comment. |
1032 ↗ | (On Diff #10073) | Execute them before and them continue. |
1050 ↗ | (On Diff #10073) | Thinking about it, it's a nullfail or a nulldummy error, not really a new kind of error. |
src/script/interpreter.cpp | ||
---|---|---|
1031 ↗ | (On Diff #10073) | The appropriate test cases are in place. If you want to arc patch this Diff and parents, and try the modifications you suggest, you can see how it affects the test cases. |
1032 ↗ | (On Diff #10073) | They can't be executed before since they modify values used in the body of this if-statement. It is possible to rewrite this to use if(!x) continue; but it just gets more verbose. |
1050 ↗ | (On Diff #10073) | Indeed, that's exactly what I had in a prior version of this Diff. :-D I'm happy to revert if that's desirable. |
src/test/script_tests.cpp | ||
---|---|---|
2114 ↗ | (On Diff #10354) | Todo: also add a test for the M=0 case for both legacy & new mode. |
src/script/interpreter.cpp | ||
---|---|---|
1034 ↗ | (On Diff #10354) | It's really great to see you enforce assumptions this way. |
1040 ↗ | (On Diff #10354) | You should extract this into its own function and have unit tests for it. This can be its own patch. The logic is simple, but it doesn't matter. |
1042 ↗ | (On Diff #10354) | You can iterate over all sigs and start with a loop that find the suitable pubkey. int ipubkey = 0; for (int i = 0; i < nSigCount; i++, ipubkey++) { if ((checkBits >> ipubkey) == 0) { // Help, we fucked up the bitfield validation! } while ((checkBits >> ipubkey) & 1 == 0) { ipubkey++; } if (ipubkey >= nKeyCount) { // Help, we fucked up the bitfield validation 2.0! } // Check signature and pubkey. } if ((checkBits >> ipubkey) == 0) { // Help, we fucked up the bitfield validation 3.0! } This would require you to validate the bitfield ahead of time, but this is desirable anyways. |
1050 ↗ | (On Diff #10354) | This is extremely confusing and error prone. Looks like you are going from the bottom key/sig to the topmost one. Can you explain why this is done in this order? |
src/test/script_tests.cpp | ||
2114 ↗ | (On Diff #10354) | I see you already know what to do here :) |
src/script/interpreter.cpp | ||
---|---|---|
1050 ↗ | (On Diff #10354) | Note these two lines are identical to the loop below, which you reviewed in D3675. It was clear enough then, has something changed? The loop order starts on the top of the stack and moves deeper. The least significant bit of the initial checkbits corresponds to the top public key (the last public key pushed on stack). |
src/script/interpreter.cpp | ||
---|---|---|
1066 ↗ | (On Diff #10354) | just nullfail |
Hmm what a strange failure ... can't see how that is related at all.
src/script/interpreter.cpp | ||
---|---|---|
1025 ↗ | (On Diff #10497) | Ah, I forgot to change this comment - should just read NEW MULTISIG (SCHNORR) |
src/script/interpreter.cpp | ||
---|---|---|
1054 ↗ | (On Diff #10497) | For such sanity checks, isn't it better to have an assert than to make a silent consensus judgement (script invalid & transaction invalid & block invalid)? If these conditions are violated then something is seriously wrong ... and there are already several asserts like this in interpreter.cpp. |
1067 ↗ | (On Diff #10497) | ditto |
1102 ↗ | (On Diff #10497) | ditto |
src/script/interpreter.cpp | ||
---|---|---|
1092 ↗ | (On Diff #10497) | NULLFAIL |
src/test/script_tests.cpp | ||
---|---|---|
2040 ↗ | (On Diff #10497) | oops, this & ~SCRIPT_VERIFY_MINIMALDATA isn't needed here. |
2150 ↗ | (On Diff #10497) | What's "Uxc'loo"? Also Antony pointed out that "first" / "last" is a bit vague since it could be interpreted as "first pushed" or "first popped". Also the text says 011 but the test does 110 |
2160 ↗ | (On Diff #10497) | ditto on bitfield |
2171 ↗ | (On Diff #10497) | ditto on first/last |
2181 ↗ | (On Diff #10497) | ditto on bitfield, first/last |
2215 ↗ | (On Diff #10497) | I just realized there is an important missing test case: run a new mode multisig with non-null bitfield but put in an ECDSA signature. (Error -> SIG_NONSCHNORR) tests.push_back(TestBuilder(CScript() << OP_1 << ToByteVector(keys.pubkey0) << OP_1 << OP_CHECKMULTISIG, "CHECKMULTISIG 1-of-1 ECDSA signature in Schnorr mode", newmultisigflags) .Num(0b1) .PushSigECDSA(keys.key0).SetScriptError(ScriptError::SIG_NONSCHNORR)); tests.push_back(TestBuilder(CScript() << OP_3 << ToByteVector(keys.pubkey0C) << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_3 << OP_CHECKMULTISIG, "CHECKMULTISIG 3-of-3 Schnorr with mixed-in ECDSA signature", newmultisigflags) .Num(0b111) .PushSigECDSA(keys.key0) .PushSigSchnorr(keys.key1) .PushSigSchnorr(keys.key2).SetScriptError(ScriptError::SIG_NONSCHNORR)); |
src/script/script_error.cpp | ||
---|---|---|
49 ↗ | (On Diff #10497) | bit -> bits |