For this specific error, the usual suspects are double length-prefixing or accidentally including the annex. Double-prefixing is the one I see most often.
The control block has to be one witness stack element, raw bytes. Layout:
<1 byte: leaf_version | parity>
<32 bytes: internal key>
<32 bytes × depth: merkle path hashes>
(parity comes from the tweaked pubkey's y-coordinate odd/even—you'll have it if you derived Q correctly.) No compactSize inside. No splitting.
Your ser_string call — that's the problem. Witness serialization already length-prefixes each stack item. So when you do ser_string(control_block), you're adding an extra prefix. The node checks (len - 33) % 32 == 0; one extra byte and that check fails, hence Invalid Taproot control block size. Just concatenate:
control_block = b''.join([
bytes([leaf_version | parity]),
internal_pubkey,
merkle_hash_1,
merkle_hash_2
])
witness_stack = [preimage_or_sig, script, control_block]
Splitting into separate elements — also wrong. The node takes the last element as the control block. If you split, it only sees hash2 (32 bytes) or whatever is last, so the size check fails. You can also hit bad-witness-nonstandard before script validation, depending on policy.
Useful way to think about it: layer 1 is BIP 341's control block format (33+32m bytes, validated by that (len-33)%32==0 check). Layer 2 is witness encoding (length prefix per element). You only define layer 1; layer 2 is automatic. Don't mix them.
If dropping ser_string and using a single blob fixes it, that was the issue. I've reproduced this on regtest—toggle only the control block build and the behavior matches.








