Skip to content

Fix the two CI failures in the tapscript commits - #2

Closed
chrisguida wants to merge 5 commits into
privkeyio:policy-unproven-pubkey-datacarrierfrom
chrisguida:k422-ci-fixes
Closed

chrisguida wants to merge 5 commits into
privkeyio:policy-unproven-pubkey-datacarrierfrom
chrisguida:k422-ci-fixes

Conversation

@chrisguida

Copy link
Copy Markdown

Assembling the 29.4.3rc1 candidate with bitcoinknots#422 at 177e5ca turned up two CI failures in the new tapscript commits, both one-liners:

  • src/script/script.cpp:389: MSVC rejects size_t{opcode} as a narrowing brace-init (C2397), so both Win64 native jobs fail to build. Replaced with static_cast<size_t>(opcode).
  • src/test/script_tests.cpp:1667: MultiAScript fills key bytes with i + 1, which narrows 256 to 0 for n above 255 and fails the UBSan integer job in script_DataCarrierBytes_tapscript_unproven_pubkeys. Cast to unsigned char; the keys only need to differ.

Fork CI run of the assembled candidate with these two commits on top: https://github.com/chrisguida/bitcoin/actions/runs/38609164952 (in progress at the time of writing; the run without them: https://github.com/chrisguida/bitcoin/actions/runs/38509266693, 15 of 18 jobs green, the 3 failures being exactly these).

LionThunderFingers and others added 5 commits October 10, 2026 17:30
… bytes

The rule only recognised a key in a 33 or 65 byte push, so the same payload
written as tapscript went free: a 1-of-15 costs 132 datacarrier bytes under
P2WSH and nothing at all under a taproot leaf, for 32 bytes of publishable
space per key rather than 31.

A 32 byte push is not a key on sight, since nothing separates an x-only key
from a hash until something spends it. Count it where a checksig-family
opcode claims it instead, which leaves the preimage of a hashlock alone.

OP_CHECKSIGADD is how tapscript writes an m-of-n, so credit its canonical
shape as OP_CHECKMULTISIG's is already credited: a run of

  <pubkey> OP_CHECKSIG (<pubkey> OP_CHECKSIGADD)* <k> OP_NUMEQUAL

credits k, whatever size its keys are. Without it every honest tapscript
multisig past eleven keys would be charged for the keys it does not spend,
which is the opposite of the point: a 20-of-20 would cost 297 bytes. The
OP_CHECKSIG that opens the run has credited one of the k already, so only
the remaining k-1 are added there.

Padding the run, reordering it or naming a k it cannot satisfy forfeits the
credit rather than earning one, so working around the rule raises the charge
instead of lowering it. An opcode that leaves the stack as it found it does
not hide the key beneath it from the opcode that spends it, so it does not
buy the count off either.
Checks the taproot leaf against the P2WSH script that says the same thing,
shape by shape, so the two are charged alike rather than only plausibly,
including at the tolerance boundary: 1-of-11 is free and 1-of-12, 1-of-13
and 5-of-16 pay the same either way.

Covers the hashlock shapes a 32 byte push must not be mistaken for a key in,
the padded and inflated runs that forfeit the credit, the OP_CHECKSIG that
opens a run crediting one of the k and not two, a k above 16 written as a
minimal push, and the key stranded on the stack that neither rule reaches.
@kwsantiago

Copy link
Copy Markdown
Collaborator

Thanks, squashed into the commits that introduced them: eea2467.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants