taproot/script limits; default unknown-witness off; BIP8 stub#222
taproot/script limits; default unknown-witness off; BIP8 stub#222justinfilip wants to merge 14 commits intobitcoinknots:29.x-knotsfrom
Conversation
… expose -datacarrierwitnesslimit and -v1perinputwitnesslimit
…n SetupServerArgs; remove stray scoped block in InitParameterInteraction
…ript push-run and IF-branch push-only guards; drop redundant element check
…ierwitnesslimit and field
…mits) with TBD params and versionbits wiring
…ushlen<=256, disallow OP_IF in tapscript policy; default -acceptunknownwitness=false
kwsantiago
left a comment
There was a problem hiding this comment.
Needs work. Here are the main points:
- No test coverage: No functional tests added for new policy rules
- Performance concerns: Triple-parsing tapscripts (witness check, push runs, IF bodies) is inefficient. Should be single-pass.
- Policy rationale undocumented: Why 1024, 256, 80, 257? Need comments linking to BIP-0444 or analysis.
- Code duplication: Opcode parser copy-pasted 3+ times
- Incomplete opcode handling:
OP_0,OP_1NEGATE,OP_1-OP_16not recognized as pushes
…n; constants for limits; bounds for -v1perinputwitnesslimit; fix whichType use order; add comments with BIP-0444 rationale
…itness default; note tapscript tests as follow-up
…per-input witness cap, IF ban, push-run cap, and IF-body cap
…n-witness rejection
|
All points should be addressed @kwsantiago, very much appreciate the feedback |
kwsantiago
left a comment
There was a problem hiding this comment.
Excellent improvements @justinfilip !
Most previous issues were addressed. I left some remaining minor comments.
| // Small integer push has no explicit payload | ||
| return true; | ||
| } | ||
| size_t need = 0; |
| if (pos >= s.size()) return false; | ||
| pushed_len = s[pos++]; | ||
| } else if (op == OP_PUSHDATA2) { | ||
| if (pos + 1 >= s.size()) return false; |
There was a problem hiding this comment.
if (pos + 1 >= s.size()) should be if (pos + 2 > s.size()) for consistency (needs 2 bytes). Same pattern in lines 73, 94, 95.
| pushed_len = s[pos++]; | ||
| } else if (op == OP_PUSHDATA2) { | ||
| if (pos + 1 >= s.size()) return false; | ||
| pushed_len = s[pos] | (uint32_t(s[pos+1]) << 8); |
There was a problem hiding this comment.
On lines 70 and 74, casting to uint32_t then assigning to uint64_t pushed_len. If PUSHDATA4 has value > 2^32-1, the upper bits are lost.
Should cast directly to uint64_t.
| size_t payload = 0; | ||
| if (op >= 0x01 && op <= 0x4b) payload = op; | ||
| else if (op == OP_PUSHDATA1) { if (pos >= s.size()) return false; payload = s[pos++]; } | ||
| else if (op == OP_PUSHDATA2) { if (pos + 1 >= s.size()) return false; payload = s[pos] | (uint32_t(s[pos+1]) << 8); pos += 2; } |
There was a problem hiding this comment.
AdvanceOneOp repeats the same bounds checks as ParsePush.
Consider refactoring to share logic.
| } else if (op == OP_ELSE) { | ||
| if (!if_stack.empty()) { | ||
| size_t body_start = if_stack.back(); | ||
| size_t body_end = j - 1; |
There was a problem hiding this comment.
When body_end == body_start, no branch is added, but this represents an empty IF body.
Should this be checked/rejected?
| control[0] = 0xc0; | ||
|
|
||
| // Leaf with OP_IF containing 80-byte push-only body (would be rejected by IF ban first) | ||
| CScript leaf_if_80 = CScript() << OP_FALSE << OP_IF << std::vector<unsigned char>(80, 0) << OP_ENDIF << OP_TRUE; |
There was a problem hiding this comment.
On lines 228 and 237, these scripts use OP_FALSE << OP_IF which means the IF body never executes. The test is checking IF ban, not IF-body limit.
Consider testing actual IF-body limit separately if IF ban is lifted.
| args.SoftSetArg("-blockmaxweight", "4000000"); | ||
| } | ||
|
|
||
|
|
There was a problem hiding this comment.
Empty lines added without purpose. Remove.
| mempool_opts.persist_v1_dat = argsman.GetBoolArg("-persistmempoolv1", mempool_opts.persist_v1_dat); | ||
|
|
||
| // Policy flags to constrain per-input witness size for segwit v1 | ||
| if (auto lim = argsman.GetIntArg("-v1perinputwitnesslimit")) { |
There was a problem hiding this comment.
User sets -v1perinputwitnesslimit=50, gets 128 with no warning.
Should log when clamping occurs or return error for out-of-range values.
| bool permitephemeral_send{DEFAULT_PERMITEPHEMERAL_SEND}; | ||
| bool permitephemeral_dust{DEFAULT_PERMITEPHEMERAL_DUST}; | ||
| bool persist_v1_dat{DEFAULT_PERSIST_V1_DAT}; | ||
| // Policy to limit inscription-like data in witnesses/tapscripts |
There was a problem hiding this comment.
"inscription-like data" is vague.
Be more specific: "Total witness bytes per v1 input to prevent witness stuffing/inscription abuse."
|
|
||
| ## References | ||
|
|
||
| - BIP-0444: https://github.com/bitcoin/bips/blob/master/bip-0444.mediawiki (pending merge) |
There was a problem hiding this comment.
BIP-0444 link shows "(pending merge)" but links to non-existent file (404).
Update when BIP number is assigned or remove until finalized.
Appreciate the continual feedback and have noted the additional code review you've done. Before I continue with addressing the code problems you've pointed out, I'd like to get your thoughts on my response in the BIP discussion: bitcoin/bips#2005 (comment) |
|
I'd like to get @luke-jr thoughts on moving forward with this on Knots, given that Core shut down the BIP discussion. |
Policy items (each reject reason and where enforced).
Defaults and new flags (e.g., -v1perinputwitnesslimit, default -acceptunknownwitness=0).
Deployment stub: new deployment ID/name, TBD versionbits params, BIP8 delayed activation.
Testing plan: might add unit/functional tests for SPK size/push caps, tapscript IF-ban, push-run/IF-body, control block cap, per-input v1 cap.
Link to BIP PR/discussion.