Skip to content

Detached553#183

Draft
l0rinc wants to merge 34 commits into
masterfrom
detached553
Draft

Detached553#183
l0rinc wants to merge 34 commits into
masterfrom
detached553

Conversation

@l0rinc
Copy link
Copy Markdown
Owner

@l0rinc l0rinc commented Jun 1, 2026

No description provided.

l0rinc added 30 commits May 31, 2026 13:06
BIP342 makes unknown non-empty public key types behave like successful signature validation for all tapscript signature opcodes. Core implements this in EvalChecksigTapscript by setting success from the signature emptiness, only calling CheckSchnorrSignature for 32-byte keys, and having OP_CHECKSIGADD add that success bit to the popped number.

Add a script_tests.json vector for a script-path tapscript spend with witness signature aa and script bytes 00 51 ba 51 87:

  OP_0 OP_1 OP_CHECKSIGADD OP_1 OP_EQUAL

The one-byte pubkey OP_1 is an unknown pubkey type. With a non-empty signature, OP_CHECKSIGADD produces 0 + 1 and the final equality succeeds.

This catches an observed divergence in libbitcoin-system 0810898c8a93. Its op_check_sig_verify path gates Schnorr parsing on key->size() == 32, but op_check_sig_add unconditionally calls schnorr_split() for every non-empty signature before any unknown-pubkey bypass. A temporary libbitcoin-system test for this vector returned op:139, which is op_check_sig_add4, instead of script_success.

Verified:

  cmake --build build --target test_bitcoin -j$(nproc)
  build/bin/test_bitcoin --run_test=script_tests/script_json_test --catch_system_errors=no --log_level=test_suite
  -> *** No errors detected

  In a temporary libbitcoin-system copy at 0810898c8a93 with an equivalent tapscript assertion:
  ./test/libbitcoin-system-test --run_test=tapscript_tests/tapscript__checksigadd_unknown_pubkey_non_empty_signature__success --log_level=test_suite --report_level=detailed --show_progress=no --detect_memory_leak=0
  -> program.run() == error::script_success failed [op:139 != 0]

Limitations: this covers the script-path tapscript OP_CHECKSIGADD unknown-pubkey consensus case with a non-empty signature. It does not exercise the discourage-upgradable-pubkey standardness flag, empty signatures, or the already-covered OP_CHECKSIG/OP_CHECKSIGVERIFY paths.
BIP341 script-path validation checks the control block commitment before applying the future-leaf-version success rule. A non-0xc0 leaf version can avoid script execution, but it still has to prove that the revealed leaf is committed by the taproot output key.

Core path: VerifyWitnessProgram pops the control block and script, computes ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, script), calls VerifyTaprootCommitment, and only then returns success for a non-tapscript leaf version. This test builds a taproot output committed to a valid v0xc0 OP_TRUE leaf, mutates only the witness control byte to v0xc2, and requires WITNESS_PROGRAM_MISMATCH.

libbitcoin path: witness::extract_taproot only pops the script and calls taproot::verify_commit inside control.is_tapscript(). For other leaf versions it installs an OP_SUCCESS script, clears the stack, and returns script_success without checking the taproot commitment.

Proof: Core passes `cmake --build build --target test_bitcoin -j$(nproc)` and `build/bin/test_bitcoin --run_test=script_tests --catch_system_errors=no --log_level=test_suite` with no errors detected. In a temporary libbitcoin-system build at 0810898c8a93, an added test for a v0xc2 leaf with a mismatched P2TR program expected `error::invalid_commitment`; direct execution of `test/libbitcoin-system-test --run_test=tapscript_tests/tapscript__future_leaf_version_commitment_mismatch__invalid_commitment --log_level=test_suite --report_level=detailed --show_progress=no --detect_memory_leak=0` exited 201 because `extract_taproot` returned `script_success` (`script:0`).

Limitations: this covers script-path commitment validation for future leaf versions. It does not exercise tapscript signature hashing, key-path spends, or policy-only discouragement flags.
Bitcoin Core consensus requires tapscript IF/NOTIF arguments to be exactly empty-vector false or exactly 0x01 true. src/script/interpreter.cpp:620-628 returns SCRIPT_ERR_TAPSCRIPT_MINIMALIF when a tapscript OP_IF/OP_NOTIF argument has size > 1 or has one byte other than 0x01. This adds coverage in src/test/script_tests.cpp:1748-1778 for OP_IF with {0x01,0x00} and OP_NOTIF with {0x00}, both expected to fail with SCRIPT_ERR_TAPSCRIPT_MINIMALIF.

Core proof on this checkout:

  cmake --build build --target test_bitcoin -j$(nproc)

  build/bin/test_bitcoin --run_test=script_tests --catch_system_errors=no --log_level=test_suite

  Result: script_tests/tapscript_minimalif_requires_minimal_boolean entered and left successfully; final output was "*** No errors detected".

Current libbitcoin-system proof at 0810898c8a93eb26573d624fde599fd30c23e510:

  include/bitcoin/system/impl/machine/interpreter.ipp:130-164 sends OP_IF/OP_NOTIF through pop_bool_(..., bip342_rule). include/bitcoin/system/impl/machine/program.ipp:255-265 routes that to peek_minimal_bool when bip342 is active. include/bitcoin/system/impl/machine/stack_variant.ipp:136-158 dispatches chunk arguments to number::boolean::from_chunk. include/bitcoin/system/impl/machine/number_boolean.ipp:72-95 treats only the empty vector as strict false, then accepts from_integer(value, vary.front()). Therefore {0x01,0x00} is accepted as true and {0x00} is accepted as false instead of failing tapscript minimal-if.

Libbitcoin executable proof, built in a temporary copy with ./builds/gnu/install-gnu.sh --build-boost --build-secp256k1 --build-use-local-src --build-src-dir=/data/my_storage/libbitcoin-minimalif.oBjjgD/src --build-obj-dir=/data/my_storage/libbitcoin-minimalif.oBjjgD/obj --build-link=static --build-config=release --build-parallel=$(nproc) --prefix=/data/my_storage/libbitcoin-minimalif.oBjjgD/prefix --noninteractive --build-skip-tests, then linked with g++ -std=c++20 -O2 -DNDEBUG=1 minimalif_proof.cpp $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-minimalif.oBjjgD/prefix/lib/pkgconfig pkg-config --cflags --libs libbitcoin-system):

  if_nonminimal_true=script success

  notif_nonminimal_false=script success

  exit_status=1

The proof program exits 1 only because both spends returned script_success, which is the consensus mismatch this Core test catches.
Core initializes tapscript validation weight from the serialized full witness:
src/script/interpreter.cpp:1991 uses GetSerializeSize(witness.stack) plus
VALIDATION_WEIGHT_OFFSET, src/script/script.h:61-65 sets both the per-sigop
cost and offset to 50, and src/script/interpreter.cpp:367-374 charges every
non-empty signature, including signatures with upgradable public key versions.
The new src/test/script_tests.cpp:1782 case builds a witness whose full
serialized size is 111 bytes: two 1-byte non-empty signatures, a 71-byte
script with two unknown-key OP_CHECKSIGVERIFY operations, and a 33-byte
control block. Core therefore has 161 weight units, enough for both sigops.

Current libbitcoin-system at 0810898c8a93eb26573d624fde599fd30c23e510 does
not use the full witness for this budget. src/chain/witness_extract.cpp:174-210
copies the witness, then pops the control block and script before tapscript
execution. include/bitcoin/system/impl/machine/program_construct.ipp:104-125
initializes the budget from that remaining witness pointer, and
include/bitcoin/system/impl/machine/program.ipp:603-614 decrements it by 50
per non-empty signature. For this same witness, the post-extraction stack
serializes to only 5 bytes, so libbitcoin starts with budget 56 and fails the
second sigop.

Proof on Core:
  cmake --build build --target test_bitcoin -j$(nproc)
  [5/5] Linking CXX executable bin/test_bitcoin

  build/bin/test_bitcoin --run_test=script_tests/tapscript_sigops_budget_counts_script_and_control --catch_system_errors=no --log_level=test_suite
  *** No errors detected

  build/bin/test_bitcoin --run_test=script_tests --catch_system_errors=no --log_level=test_suite
  ./test/script_tests.cpp(1782): Entering test case "tapscript_sigops_budget_counts_script_and_control"
  ./test/script_tests.cpp(1782): Leaving test case "tapscript_sigops_budget_counts_script_and_control"
  *** No errors detected

Proof on libbitcoin-system 0810898c8a93eb26573d624fde599fd30c23e510:
  g++ -std=c++20 -O2 -DNDEBUG=1 tapscript_budget_proof.cpp -o tapscript_budget_proof $(pkg-config --cflags --libs libbitcoin-system)
  ./tapscript_budget_proof
  taproot_script=21020202020202020202020202020202020202020202020202020202020202020202ad21020202020202020202020202020202020202020202020202020202020202020202ad51
  full_witness_serialized_size=111
  post_extract_stack_serialized_size=5
  libbitcoin_result=op_check_sig_budget
  exit_status=1

Also ran: git diff --check
Core block validation intentionally treats P2SH, WITNESS, and TAPROOT script verification as retroactive for all non-exception blocks. GetBlockScriptFlags() starts with SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT and only replaces that set for chainparams script_flag_exceptions; DERSIG, CLTV, CSV, and NULLDUMMY remain height-gated separately.

Add a validation_chainstatemanager test for a synthetic mainnet height-1 non-exception block index. It asserts that the assigned block script flags include P2SH/WITNESS/TAPROOT but not the later height-gated flags, then verifies that an empty native v0 P2WSH spend fails with SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY under the actual block flags.

Libbitcoin differs in the current code path. chain_state.cpp only enables the BIP141/BIP143/BIP147 group once the BIP9 bit1 checkpoint hash is cached, script_patterns.ipp makes is_pay_to_witness() require bip141_rule, and interpreter_connect.ipp only dispatches native witness validation through connect_witness() when is_pay_to_witness() is true. Before that checkpoint, the same native v0 witness program is executed as a legacy script whose final pushed program bytes are truthy.

Libbitcoin proof source: /data/my_storage/libbitcoin-proof-budget.XETPUC/retroactive_witness_proof.cpp

Build proof command:

g++ -std=c++20 -O2 -DNDEBUG=1 /data/my_storage/libbitcoin-proof-budget.XETPUC/retroactive_witness_proof.cpp -o /data/my_storage/libbitcoin-proof-budget.XETPUC/retroactive_witness_proof $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/pkgconfig pkg-config --cflags --libs libbitcoin-system)

Proof run output:

script_pubkey=00200202020202020202020202020202020202020202020202020202020202020202
witness_stack_size=0
pre_segwit_flags=bip16_rule
pre_segwit_result=script success
segwit_flags=bip16_rule|bip141_rule|bip143_rule
segwit_result=invalid witness
exit_status=1

The proof returns nonzero when the divergence is reproduced: libbitcoin accepts the spend with pre-SegWit consensus flags and rejects it once BIP141 is enabled. Core rejects it for the synthetic height-1 non-exception block because WITNESS is part of block script flags retroactively.

Core verifiers:

cmake --build build --target test_bitcoin -j$(nproc)
build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_script_flags_retroactive_witness --catch_system_errors=no --log_level=test_suite
build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests --catch_system_errors=no --log_level=test_suite
git diff --check

Verifier results: build succeeded; focused test and full validation_chainstatemanager_tests reported no errors; git diff --check was clean.

Limitations: this covers the non-exception block-flag path and a native v0 witness-script spend. It does not cover the historical script_flag_exceptions or witness commitment activation, which are separate block-level checks.
Core block validation keeps SCRIPT_VERIFY_TAPROOT enabled for every non-exception block through GetBlockScriptFlags(). That means a native v1 32-byte witness program is interpreted as Taproot even for a synthetic historical block before the BIP341 activation height, unless the block hash is one of the configured script_flag_exceptions.

Add a validation_chainstatemanager test for a mainnet height-1 non-exception block index. It obtains the actual block script flags, checks that WITNESS and TAPROOT are present, and verifies that an empty P2TR witness fails with SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY.

Libbitcoin differs in the current code path. Its chain_state.cpp only enables bip341_rule and bip342_rule after the BIP9 bit2 checkpoint hash is cached. interpreter_connect.ipp routes version-1 witness programs through the taproot branch, but returns script_success immediately when bip341_rule is not set. The same empty P2TR spend is therefore unencumbered before the taproot checkpoint and invalid after taproot is enabled.

Libbitcoin proof source: /data/my_storage/libbitcoin-proof-budget.XETPUC/retroactive_taproot_proof.cpp

Build proof command:

g++ -std=c++20 -O2 -DNDEBUG=1 /data/my_storage/libbitcoin-proof-budget.XETPUC/retroactive_taproot_proof.cpp -o /data/my_storage/libbitcoin-proof-budget.XETPUC/retroactive_taproot_proof $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/pkgconfig pkg-config --cflags --libs libbitcoin-system)

Proof run output:

script_pubkey=51200202020202020202020202020202020202020202020202020202020202020202
witness_stack_size=0
pre_taproot_flags=bip16_rule|bip141_rule|bip143_rule|bip147_rule
pre_taproot_result=script success
taproot_flags=pre_taproot|bip341_rule|bip342_rule
taproot_result=invalid witness
exit_status=1

The proof returns nonzero when the divergence is reproduced: libbitcoin accepts the empty P2TR spend before bip341_rule and rejects it once taproot flags are active. Core rejects it for the synthetic height-1 non-exception block because TAPROOT is part of the block script flags retroactively.

Core verifiers:

cmake --build build --target test_bitcoin -j$(nproc)
build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_script_flags_retroactive_taproot --catch_system_errors=no --log_level=test_suite
build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests --catch_system_errors=no --log_level=test_suite
git diff --check

Verifier results: build succeeded; focused test and full validation_chainstatemanager_tests reported no errors; git diff --check was clean.

Limitations: this covers the non-exception retroactive Taproot script path with an empty key-path witness. It does not cover the configured Taproot script_flag_exception block or witness commitment activation, which are separate block-level checks.
Core block validation keeps SCRIPT_VERIFY_P2SH enabled for every non-exception block through GetBlockScriptFlags(). That retroactive policy means a historical non-exception block must execute a P2SH redeem script even before the original BIP16 activation time.

Add a validation_chainstatemanager test for a mainnet height-1 non-exception block index. It obtains the actual block script flags, checks P2SH is present, and verifies that a P2SH output whose redeem script is OP_FALSE fails with SCRIPT_ERR_EVAL_FALSE. Without P2SH enforcement the same spend only proves that the pushed redeem script hashes to the scriptPubKey and would leave true.

Libbitcoin differs in the current code path. chain_state.cpp enables bip16_rule only when the block timestamp reaches the BIP16 activation time. script_patterns.ipp makes is_pay_to_script_hash() require bip16_rule, and interpreter_connect.ipp only executes connect_embedded() when that predicate is true. Before BIP16, the same spend succeeds as a legacy HASH160/EQUAL script; with bip16_rule, the OP_FALSE redeem script is executed and fails.

Libbitcoin proof source: /data/my_storage/libbitcoin-proof-budget.XETPUC/retroactive_p2sh_proof.cpp

Build proof command:

g++ -std=c++20 -O2 -DNDEBUG=1 /data/my_storage/libbitcoin-proof-budget.XETPUC/retroactive_p2sh_proof.cpp -o /data/my_storage/libbitcoin-proof-budget.XETPUC/retroactive_p2sh_proof $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/pkgconfig pkg-config --cflags --libs libbitcoin-system)

Proof run output:

redeem_script=00
script_sig=0100
script_pubkey=a9149f7fd096d37ed2c0e3f7f0cfc924beef4ffceb6887
pre_bip16_flags=no_rules
pre_bip16_result=script success
bip16_flags=bip16_rule
bip16_result=stack false
exit_status=1

The proof returns nonzero when the divergence is reproduced: libbitcoin accepts the P2SH spend before bip16_rule and rejects it once BIP16 is enabled. Core rejects it for the synthetic height-1 non-exception block because P2SH is part of the block script flags retroactively.

Core verifiers:

cmake --build build --target test_bitcoin -j$(nproc)
build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_script_flags_retroactive_p2sh --catch_system_errors=no --log_level=test_suite
build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests --catch_system_errors=no --log_level=test_suite
git diff --check

Verifier results: build succeeded; focused test and full validation_chainstatemanager_tests reported no errors; git diff --check was clean.

Limitations: this covers the non-exception retroactive P2SH script path with an OP_FALSE redeem script. It does not cover the configured BIP16 script_flag_exception block.
Bitcoin Core treats CSV as a buried deployment. deploymentstatus.h activates buried deployments by height alone, so GetBlockScriptFlags enables SCRIPT_VERIFY_CHECKSEQUENCEVERIFY at CSVHeight for any non-exception block hash.

libbitcoin keeps BIP9 bit-0 tied to the historical activation checkpoint hash: chain_state::activation only adds bip68/bip112/bip113 when values.bip9_bit0_hash equals settings.bip9_bit0_active_checkpoint.hash(), and to_header caches the block hash at that height. An alternative chain at height 419328 with a different hash therefore omits BIP112.

Proof program: /data/my_storage/libbitcoin-proof-budget.XETPUC/csv_checkpoint_activation_proof.cpp

csv_height=419328
script_pubkey=51b27551
wrong_checkpoint_has_bip112=0
wrong_checkpoint_result=script success
matching_checkpoint_has_bip112=1
matching_checkpoint_result=op_check_sequence_verify2
exit_status=1

Verified with:
- cmake --build build --target test_bitcoin -j2
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_script_flags_csv_height_ignores_activation_hash
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests
- git diff --check
Bitcoin Core buries Segwit activation by height. GetBlockScriptFlags enables SCRIPT_VERIFY_NULLDUMMY at SegwitHeight regardless of the block hash, because buried deployments use index.nHeight >= DeploymentHeight(dep).

libbitcoin keeps BIP9 bit-1 tied to the historical activation checkpoint hash: chain_state::activation only adds bip141/bip143/bip147 when values.bip9_bit1_hash equals settings.bip9_bit1_active_checkpoint.hash(), and to_header caches the block hash at that height. An alternative chain at height 481824 with a different hash therefore omits BIP147.

Proof program: /data/my_storage/libbitcoin-proof-budget.XETPUC/nulldummy_checkpoint_activation_proof.cpp

segwit_height=481824
script_sig=51
script_pubkey=0000ae
wrong_checkpoint_has_bip147=0
wrong_checkpoint_result=script success
matching_checkpoint_has_bip147=1
matching_checkpoint_result=op_check_multisig_verify9
exit_status=1

Verified with:
- cmake --build build --target test_bitcoin -j2
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_script_flags_nulldummy_height_ignores_activation_hash
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests
- git diff --check
Bitcoin Core activates the CSV buried deployment by height alone. At and above CSVHeight, ContextualCheckBlock uses previous-block median-time-past for absolute locktime finality under BIP113, independent of the activation block hash.

libbitcoin keeps BIP9 bit-0 tied to the historical activation checkpoint hash: chain_state::activation only adds bip68/bip112/bip113 when values.bip9_bit0_hash equals settings.bip9_bit0_active_checkpoint.hash(). An alternative chain at height 419328 with a different hash therefore omits BIP113 and compares nLockTime against the block timestamp instead of MTP.

Proof program: /data/my_storage/libbitcoin-proof-budget.XETPUC/bip113_checkpoint_activation_proof.cpp

csv_height=419328
locktime=500001001
block_time=500001002
median_time_past=500001000
wrong_checkpoint_has_bip113=0
wrong_checkpoint_result=transaction success
matching_checkpoint_has_bip113=1
matching_checkpoint_result=transaction absolute time locked
exit_status=1

Verified with:
- cmake --build build --target test_bitcoin -j2
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_finality_bip113_height_ignores_activation_hash
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests
- git diff --check
Bitcoin Core activates the CSV buried deployment by height alone. At CSVHeight, LOCKTIME_VERIFY_SEQUENCE enforces BIP68 relative sequence locks independent of the activation block hash.

libbitcoin keeps BIP9 bit-0 tied to the historical activation checkpoint hash: chain_state::activation only adds bip68/bip112/bip113 when values.bip9_bit0_hash equals settings.bip9_bit0_active_checkpoint.hash(). An alternative chain at height 419328 with a different hash therefore omits BIP68 and accepts a transaction whose sequence lock is still immature.

Proof program: /data/my_storage/libbitcoin-proof-budget.XETPUC/bip68_checkpoint_activation_proof.cpp

csv_height=419328
prevout_height=419327
sequence=2
wrong_checkpoint_has_bip68=0
wrong_checkpoint_result=transaction success
matching_checkpoint_has_bip68=1
matching_checkpoint_result=transaction relative time locked
exit_status=1

Verified with:
- cmake --build build --target test_bitcoin -j2
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_sequence_locks_bip68_height_ignores_activation_hash
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests
- git diff --check
Bitcoin Core rejects non-minimal CompactSize encodings during transaction deserialization. This covers an otherwise-valid coinbase transaction whose one-input vector is encoded as fd0100 instead of 01.

Libbitcoin proof source: /data/my_storage/libbitcoin-proof-budget.XETPUC/noncanonical_compactsize_tx_proof.cpp

Libbitcoin proof output:

input_count_encoding=fd0100
raw_size=65
canonical_size=63
tx_is_valid=1
inputs=1
outputs=1
check_result=transaction success
canonical_uses_minimal_count=1
exit_status=1

Verification:
- cmake --build build --target test_bitcoin -j$(nproc)
- build/bin/test_bitcoin --run_test=transaction_tests/tx_noncanonical_compactsize
- build/bin/test_bitcoin --run_test=transaction_tests
- git diff --check
Bitcoin Core treats SegWit commitment validation as active by buried height, independent of the activation block hash. This covers a block with a witness commitment output but no coinbase witness reserved value: before checking witness roots it is not mutated, but once SegWit is active it is mutated.

Libbitcoin proof source: /data/my_storage/libbitcoin-proof-budget.XETPUC/segwit_commitment_checkpoint_activation_proof.cpp

Libbitcoin proof output:

segwit_height=481824
coinbase_witness_items=0
has_witness_commitment=1
wrong_checkpoint_has_bip141=0
wrong_checkpoint_result=block success
matching_checkpoint_has_bip141=1
matching_checkpoint_result=invalid witness commitment
exit_status=1

Verification:
- cmake --build build --target test_bitcoin -j$(nproc)
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_witness_commitment_height_ignores_activation_hash
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests
- git diff --check
Bitcoin Core clears P2SH/WITNESS/TAPROOT script flags for the historical BIP16 exception block hash. This covers a P2SH redeem script that succeeds under the exception flags but fails when P2SH is enforced.

Libbitcoin proof source: /data/my_storage/libbitcoin-proof-budget.XETPUC/p2sh_exception_hash_proof.cpp

Libbitcoin proof output:
exception_hash=00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22
script_sig=0100
script_pubkey=a9149f7fd096d37ed2c0e3f7f0cfc924beef4ffceb6887
exception_state_has_bip16=1
no_p2sh_result=script success
exception_hash_result=stack false
exit_status=1

Verification:
- cmake --build build --target test_bitcoin -j$(nproc)
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_script_flags_p2sh_exception_hash
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests
- git diff --check
Bitcoin Core rejects non-minimal CompactSize encodings while deserializing a block. This covers the block transaction-count field with a canonical one-transaction control block and a noncanonical fd0100 encoding for the same count.

Libbitcoin proof source: /data/my_storage/libbitcoin-proof-budget.XETPUC/noncanonical_compactsize_block_proof.cpp

Libbitcoin proof output:
tx_count_encoding=fd0100
raw_size=146
canonical_size=144
block_is_valid=1
transactions=1
check_result=block success
canonical_uses_minimal_count=1
exit_status=1

Verification:
- cmake --build build --target test_bitcoin -j$(nproc)
- build/bin/test_bitcoin --run_test=validation_tests/block_noncanonical_compactsize
- build/bin/test_bitcoin --run_test=validation_tests
- git diff --check
Bitcoin Core rejects non-minimal CompactSize encodings inside transaction witnesses during deserialization. This covers a segwit-serialized coinbase transaction whose single one-byte witness item length is encoded as fd0100 instead of 01.

Libbitcoin proof source: /data/my_storage/libbitcoin-proof-budget.XETPUC/noncanonical_compactsize_witness_proof.cpp

Libbitcoin proof output:

witness_item_length_encoding=fd0100
raw_size=70
canonical_size=68
tx_is_valid=1
tx_is_segregated=1
witness_items=1
witness_item_size=1
check_result=transaction success
canonical_uses_minimal_item_length=1
exit_status=1

Verification:
- cmake --build build --target test_bitcoin -j$(nproc)
- build/bin/test_bitcoin --run_test=transaction_tests/tx_noncanonical_witness_compactsize
- build/bin/test_bitcoin --run_test=transaction_tests
- git diff --check

Limitations: This covers the witness item length parser path; it does not exhaustively enumerate every CompactSize field.
Core requires a P2SH-wrapped witness redeemScript to be encoded as exactly one canonical push in scriptSig. Cover the canonical success case and the OP_PUSHDATA1 malleated failure case.

libbitcoin proof (/data/my_storage/libbitcoin-proof-budget.XETPUC/p2sh_witness_noncanonical_redeem_push_proof):

redeem_script=00204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260
script_sig_encoding=4c22
script_sig=4c2200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260
script_sig_ops=1
witness_script=51
witness_items=1
libbitcoin_result=transaction success
exit_status=1
Add coverage for a segwit transaction whose witness item count is encoded as fd0100 for the value 1. Core rejects this during transaction deserialization via ReadCompactSize before consensus validation can proceed.

Libbitcoin accepts the same transaction, preserves one witness item, reports transaction success, and reserializes with the shorter canonical count encoding. This is distinct from the witness item-length case: the malformed CompactSize is the stack count itself.

Core verification:

  cmake --build build --target test_bitcoin -j$(nproc)

  build/bin/test_bitcoin --run_test=transaction_tests/tx_noncanonical_witness_compactsize

  build/bin/test_bitcoin --run_test=transaction_tests

  git diff --check

Libbitcoin proof (/data/my_storage/libbitcoin-proof-budget.XETPUC/noncanonical_compactsize_witness_stack_count_proof.cpp):

  witness_stack_count_encoding=fd0100

  raw_size=70

  canonical_size=68

  tx_is_valid=1

  tx_is_segregated=1

  witness_items=1

  witness_item_size=1

  check_result=transaction success

  canonical_uses_minimal_stack_count=1

  exit_status=1
Add coverage for a witness-bearing block whose witness commitment is internally valid but whose witness root is not expected. Core treats this as mutated through CheckWitnessMalleation when check_witness_root is false, while the same block is not mutated when witness commitment validation is expected.

Libbitcoin has an unexpected_witness_transaction guard, but the public block check/accept path does not call it: block::check(ctx) delegates to transaction::check(ctx), which only checks absolute locktime, and block::accept(ctx) also succeeds for the coinbase-only block.

Core verification:

  cmake --build build --target test_bitcoin -j$(nproc)

  build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_witness_data_requires_expected_commitment

  build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests

  git diff --check

Libbitcoin proof (/data/my_storage/libbitcoin-proof-budget.XETPUC/pre_segwit_unexpected_witness_block_proof.cpp):

  height=481823

  coinbase_witness_items=1

  coinbase_witness_item_size=32

  has_witness_commitment=1

  witness_commitment_valid=1

  block_is_segregated=1

  pre_bip141_check=block success

  pre_bip141_accept=block success

  bip141_check=block success

  exit_status=1
Bitcoin Core treats a non-empty witness stack as witness data even if the sole stack item has zero byte length. This locks in that a canonical segwit serialization with one empty witness item is accepted by deserialization and CheckTransaction, rather than treated as a superfluous witness record.

Libbitcoin proof source: /data/my_storage/libbitcoin-proof-budget.XETPUC/empty_witness_item_view_proof.cpp

Libbitcoin proof output:

witness_stack_count=1
witness_item_size=0
tx_raw_size=67
object_tx_is_valid=1
object_tx_is_segregated=1
object_witness_items=1
object_witness_item_size=0
object_check_result=transaction success
view_block_is_valid=0
view_transactions=0
exit_status=1

The proof compares libbitcoin chain::transaction with chain::block_view on the same canonical serialization. The transaction object accepts the witness stack, while block_view invalidates the block buffer before identity checks.

Verification:
- cmake --build build --target test_bitcoin -j$(nproc)
- build/bin/test_bitcoin --run_test=transaction_tests/tx_empty_witness_item_not_superfluous
- build/bin/test_bitcoin --run_test=transaction_tests
- git diff --check
BIP342 treats non-empty signatures with non-empty pubkeys of unknown length as successful signature checks, after counting the sigops budget. Cover that behavior for OP_CHECKSIGADD by using a 33-byte pubkey and a one-byte signature in a tapscript that must produce 1.

Libbitcoin proof source: /data/my_storage/libbitcoin-proof-budget.XETPUC/tapscript_checksigadd_unknown_key_proof.cpp

Libbitcoin proof output:

taproot_script=01010021020202020202020202020202020202020202020202020202020202020202020202ba5187
control_block=c179be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
script_pubkey=51208cea41939ad2a331ce905381419804e685235490b3d852da84ab8ed9fb0296c3
unknown_pubkey_size=33
nonempty_signature_size=1
libbitcoin_result=op_check_sig_add4
exit_status=1

Verification:
- cmake --build build --target test_bitcoin -j$(nproc)
- build/bin/test_bitcoin --run_test=script_tests/tapscript_checksigadd_unknown_pubkey_type_succeeds
- build/bin/test_bitcoin --run_test=script_tests
- git diff --check
Bitcoin Core testnet4 consensus preserves the real difficulty carried by the first block of a retarget period when the previous block used the testnet min-difficulty exception. src/pow.cpp:67-74 seeds CalculateNextWorkRequired() from pindexFirst->nBits when enforce_BIP94 is set; without BIP94 the same calculation seeds from pindexLast->nBits.

This adds a focused pow_tests case that builds a synthetic testnet4 retarget window with first_period_bits=0x1c0ffff0, last_period_bits=0x1d00ffff, and exact target timespan. Core must return 0x1c0ffff0 with enforce_BIP94 and 0x1d00ffff when the same params are copied with enforce_BIP94=false.

Core proof on this checkout:

  cmake --build build --target test_bitcoin -j$(nproc)

  build/bin/test_bitcoin --run_test=pow_tests/get_next_work_bip94_uses_first_period_bits

  build/bin/test_bitcoin --run_test=pow_tests

  git diff --check

  Result: all commands completed successfully; pow_tests reported 16 test cases and no errors.

Current libbitcoin-system proof at 0810898c8a93eb26573d624fde599fd30c23e510:

  /data/my_storage/libbitcoin-proof-budget.XETPUC/bip94_retarget_proof.cpp constructs matching retarget data with forks.retarget=true, forks.difficult=false, proof_of_work_limit=0x1d00ffff, first period bits 0x1c0ffff0, last period bits 0x1d00ffff, and exact target timespan. chain_state::work_required() has no BIP94 branch and retargets from bits_high(values), i.e. the previous block bits.

  Output:

    first_period_bits=0x1c0ffff0

    last_period_bits=0x1d00ffff

    libbitcoin_work_required=0x1d00ffff

    core_bip94_expected=0x1c0ffff0

    height=4032

    retargeting_interval=2016

    actual_timespan=1209600

    exit_status=1

Limitations: this is specific to Core's BIP94/testnet4 consensus rule. It does not claim a mainnet retarget difference, and libbitcoin-system does not currently expose a separate testnet4/enforce_BIP94 setting.
Core's ConnectBlock logic skips BIP30 duplicate-txid checks after the known BIP34 checkpoint matches, but resumes checking at height 1,983,702. Factor that decision into ShouldEnforceBIP30ForBlock and add a validation_tests regression that uses testnet3 consensus parameters to assert BIP30 remains skipped at 1,983,701 but is enforced again at 1,983,702 when the BIP34 checkpoint hash matches. This is a testnet3-only divergence guard, not a mainnet finding.

Core evidence: src/validation.cpp now calls ShouldEnforceBIP30ForBlock from ConnectBlock, and the helper preserves the existing return condition fEnforceBIP30 || block_index.nHeight >= 1983702. The new test is src/test/validation_tests.cpp:bip30_reactivated_at_bip34_indicated_height_limit.

Libbitcoin evidence: /data/my_storage/libbitcoin-system/src/settings.cpp sets testnet bip30_reactivate_height = 486'000'000_size, while /data/my_storage/libbitcoin-system/src/chain/chain_state.cpp only sets flags::bip30_rule after bip30_deactivate matches and height >= settings.bip30_reactivate_height.

Libbitcoin proof: /data/my_storage/libbitcoin-proof-budget.XETPUC/bip30_testnet_reactivation_proof.cpp produced:\nnetwork=testnet3\nlibbitcoin_bip30_reactivate_height=486000000\ncore_bip30_reactivation_height=1983702\nbip30_deactivate_checkpoint_height=21111\nbip30_deactivate_hash_matched=1\nlibbitcoin_has_bip30_at_core_height=0\ncore_expected_bip30_at_core_height=1\nlibbitcoin_has_bip30_at_own_reactivation_height=1\nexit_status=1

Verification:\ncmake --build build --target test_bitcoin -j4\nbuild/bin/test_bitcoin --run_test=validation_tests/bip30_reactivated_at_bip34_indicated_height_limit\nbuild/bin/test_bitcoin --run_test=validation_tests\ngit diff --check
BIP94 rejects the first block of a difficulty-adjustment period if its timestamp is more than MAX_TIMEWARP seconds before its parent. Core enforced this inside ContextualCheckBlockHeader; extract IsBIP94TimewarpAttack so the consensus boundary can be tested directly.\n\nCore evidence:\n- src/consensus/params.h:118-121 documents enforce_BIP94 as the timewarp mitigation.\n- src/consensus/consensus.h:30-35 sets MAX_TIMEWARP to 600 seconds.\n- src/validation.cpp:3994-4002 enforces active BIP94, non-genesis, retarget-height, and block_time < previous_time - MAX_TIMEWARP.\n- src/validation.cpp:4032-4034 rejects the contextual header with time-timewarp-attack.\n- src/test/validation_tests.cpp:98-116 covers the 601-second rejection, the inclusive 600-second boundary, non-retarget height, genesis, and disabled-BIP94 cases.\n\nlibbitcoin evidence:\n- /data/my_storage/libbitcoin-system/src/chain/header.cpp:379-386 only rejects insufficient version, median-time-past violation, or incorrect work from context.\n- /data/my_storage/libbitcoin-system/include/bitcoin/system/chain/context.hpp:49-57 defines the timestamp/work predicates as time_stamp <= median_time_past and bits != work_required; it has no previous-block MAX_TIMEWARP check.\n\nProof:\nBuilt and ran /data/my_storage/libbitcoin-proof-budget.XETPUC/bip94_timewarp_header_proof.cpp against current libbitcoin-system. It creates height=2016, previous_time=1700000000, median_time_past=1699990000, candidate_time=1699999399, max_timewarp=600. The candidate is after MTP but 601 seconds before the previous block, so Core's BIP94 rule rejects while libbitcoin header.accept(ctx) returns block_success.\n\nProof output:\nheight=2016\nprevious_time=1700000000\nmedian_time_past=1699990000\ncandidate_time=1699999399\nmax_timewarp=600\ncore_bip94_reject=1\nlibbitcoin_header_accept_code=0\nlibbitcoin_header_accepted=1\nexit_status=1\n\nVerification:\n- cmake --build build --target test_bitcoin -j4\n- build/bin/test_bitcoin --run_test=validation_tests/bip94_timewarp_retarget_timestamp_limit\n- build/bin/test_bitcoin --run_test=validation_tests\n- git diff --check\n\nLimitation: this is a testnet4/regtest BIP94 timestamp-rule divergence, independent of the previously committed BIP94 retarget-base-bits case.
Expose the BIP34 coinbase-height prefix check so it can be tested directly.

Bitcoin Core builds the required prefix with CScript{} << height. For heights
1 through 16, CScript::push_int64 encodes the integer as OP_1 through OP_16,
so at height 1 the accepted prefix is 51. A nominal push of the same integer
(0101...) is not the same BIP34 prefix and must be rejected.

libbitcoin-system currently uses a different predicate:
- src/chain/script.cpp:43-55: script::is_coinbase_pattern requires
  ops[0].is_nominal_push() and data equal to chunk::from_integer(height).
- src/chain/block.cpp:492-498 calls that predicate from the contextual
  coinbase-height check.
- include/bitcoin/system/impl/chain/operation_patterns.ipp:126-132 maps
  nominal one-byte data to push_size_1, not OP_1.

Proof built against libbitcoin-system 0810898c8 from
/data/my_storage/libbitcoin-proof-budget.XETPUC/bip34_small_height_coinbase_proof.cpp:
  g++ -std=c++23 \
    -I/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/include \
    -I/data/my_storage/libbitcoin-system/include \
    -I/data/my_storage/libbitcoin-system/src \
    /data/my_storage/libbitcoin-proof-budget.XETPUC/bip34_small_height_coinbase_proof.cpp \
    /data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/libbitcoin-system.a \
    /data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/libsecp256k1.a \
    -pthread \
    -o /data/my_storage/libbitcoin-proof-budget.XETPUC/bip34_small_height_coinbase_proof

Proof output:
  height=1
  core_height_script=5100
  nominal_height_script=010100
  bitcoin_core_accepts_core_script=1
  bitcoin_core_accepts_nominal_script=0
  libbitcoin_core_script_check=block height mismatch in coinbase
  libbitcoin_core_script_accepted=0
  libbitcoin_nominal_script_check=block success
  libbitcoin_nominal_script_accepted=1
  exit_status=1

This is consensus-relevant for chains where BIP34 is active at small heights,
including regtest-style height-1 activation. The proof uses a manual
libbitcoin context with the height-in-coinbase rule active at height 1.

Verification:
  cmake --build build --target test_bitcoin -j$(nproc)
  build/bin/test_bitcoin --run_test=validation_tests/bip34_coinbase_height_uses_scriptnum_encoding
  build/bin/test_bitcoin --run_test=validation_tests
  git diff --check
Core evidence: CalculateNextWorkRequired constructs bnPowLimit from the
Consensus::Params passed to the current call (src/pow.cpp:63) and clamps the
new target to that per-call limit (src/pow.cpp:81-82).

The regression first asks for a four-times-retarget under a loose
0x207fffff POW limit and observes 0x1d03fffc, then repeats the same retarget
under Bitcoin's stricter 0x1d00ffff POW limit and requires the result to clamp
back to 0x1d00ffff (src/test/pow_tests.cpp:50-76).

Libbitcoin evidence: chain_state::work_required_retarget initializes the
expanded POW limit with a function-local static from the first
proof_of_work_limit argument (libbitcoin-system/src/chain/chain_state.cpp:409-414)
and later compares against that cached limit when deciding whether to clamp
(libbitcoin-system/src/chain/chain_state.cpp:425-427). A process that computes
a retarget with a looser limit first can therefore compute an unclamped target
for a later stricter limit.

Proof: /data/my_storage/libbitcoin-proof-budget.XETPUC/static_pow_limit_retarget_proof.cpp
was compiled with:

g++ -std=c++20 -O2 /data/my_storage/libbitcoin-proof-budget.XETPUC/static_pow_limit_retarget_proof.cpp \
  -o /data/my_storage/libbitcoin-proof-budget.XETPUC/static_pow_limit_retarget_proof \
  $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/pkgconfig pkg-config --cflags --libs --static libbitcoin-system)

Proof output:

previous_bits=0x1d00ffff
first_call_pow_limit=0x207fffff
first_call_result=0x1d03fffc
second_call_pow_limit=0x1d00ffff
second_call_expected_clamp=0x1d00ffff
second_call_result=0x1d03fffc
second_call_uses_stale_limit=1
exit_status=1

Verification:
cmake --build build --target test_bitcoin -j$(nproc)
build/bin/test_bitcoin --run_test=pow_tests/get_next_work_uses_current_pow_limit
build/bin/test_bitcoin --run_test=pow_tests
git diff --check
Core's header proof-of-work validation is parameterized by the consensus
parameters supplied for that call: CheckProofOfWork() delegates to
CheckProofOfWorkImpl() and DeriveTarget() rejects targets above
params.powLimit (src/pow.cpp:140-164). The new regression exercises a
compact target between two pow limits, accepting it with a loose limit and
rejecting it with Bitcoin's 0x1d00ffff limit (src/test/pow_tests.cpp:173-189).

libbitcoin-system currently caches the first proof_of_work_limit passed to
header::is_invalid_proof_of_work() in a function-local static, so later header
checks can continue using an earlier chain's easier limit instead of the
current call's limit (/data/my_storage/libbitcoin-system/src/chain/header.cpp:324-342).

Proof compiled and run against current libbitcoin-system HEAD:
  /data/my_storage/libbitcoin-proof-budget.XETPUC/static_pow_limit_header_proof.cpp

Output:
  header_bits=0x1d03fffc
  first_call_pow_limit=0x207fffff
  first_call_result=block success
  second_call_pow_limit=0x1d00ffff
  second_call_expected=invalid proof of work
  second_call_result=block success
  second_call_uses_stale_limit=1
  exit_status=1

Verification:
  cmake --build build --target test_bitcoin -j$(nproc)
  build/bin/test_bitcoin --run_test=pow_tests/CheckProofOfWork_uses_current_pow_limit
  build/bin/test_bitcoin --run_test=pow_tests
  git diff --check
Bitcoin Core accepts compact proof-of-work targets with exponent 34
when the mantissa is small enough to keep the decoded target within
256 bits.  For nBits=0x22000001, arith_uint256::SetCompact() sets
neither the negative nor overflow flag and produces target 2^248.
That target is nonzero and below regtest's powLimit, so
CheckProofOfWork() accepts a zero hash for it.

libbitcoin rejects the same target before the hash comparison.  Its
compact::expand() delegates to base256e::expand(), whose exponent bound
turns this encoding into zero.  header::is_invalid_proof_of_work()
then treats zero target as the invalid sentinel, so the header would be
rejected even though Core's consensus target decoder accepts it.

Proof program:
/data/my_storage/libbitcoin-proof-budget.XETPUC/compact_exponent34_proof.cpp

Proof build:
g++ -std=c++20 -O2 /data/my_storage/libbitcoin-proof-budget.XETPUC/compact_exponent34_proof.cpp -o /data/my_storage/libbitcoin-proof-budget.XETPUC/compact_exponent34_proof $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/pkgconfig pkg-config --cflags --libs --static libbitcoin-system)

Proof output:
bits=0x22000001
regtest_limit_bits=0x207fffff
core_overflow_rule=0
core_target_nonzero=1
core_target_lte_regtest_limit=1
valid_by_core_range=1
libbitcoin_target_is_zero=1
exit_status=1

Core evidence:
src/arith_uint256.cpp:176-193
src/pow.cpp:146-158

libbitcoin evidence:
/data/my_storage/libbitcoin-system/include/bitcoin/system/impl/chain/compact.ipp:84-103
/data/my_storage/libbitcoin-system/include/bitcoin/system/impl/radix/base_2n.ipp:35-52
/data/my_storage/libbitcoin-system/src/chain/header.cpp:324-342

Verification:
cmake --build build --target test_bitcoin -j$(nproc)
build/bin/test_bitcoin --run_test=pow_tests/CheckProofOfWork_accepts_compact_exponent_34_target
build/bin/test_bitcoin --run_test=pow_tests
git diff --check
Core keys the two historical BIP30 repeat exceptions by both height and
block hash. A block at height 91842 or 91880 only skips BIP30 when the
candidate hash is exactly the historical duplicate-coinbase block.

This adds a focused unit test for that decision surface: the two known
mainnet repeat blocks are not BIP30-enforced, while the same height with a
wrong hash is still enforced.

Core evidence:
- src/validation.cpp:6127-6130 identifies the two BIP30 repeat blocks by
  height and hash.
- src/validation.cpp:6139-6141 initializes ShouldEnforceBIP30ForBlock from
  !IsBIP30Repeat(block_index), before the later BIP34 optimization.

libbitcoin evidence:
- /data/my_storage/libbitcoin-system/src/chain/chain_state.cpp:85-90 has
  the same two BIP30 exception checkpoints.
- /data/my_storage/libbitcoin-system/src/chain/chain_state.cpp:270-273 only
  disables bip30_rule when values.hash matches one of those exceptions.
- /data/my_storage/libbitcoin-system/src/chain/chain_state.cpp:680-697 builds
  a full-block chain_state by copying the pool data, then assigning
  data.hash = {}, so this constructor cannot match either BIP30 exception.

Differential proof:
  g++ -std=c++20 -O2 \
    /data/my_storage/libbitcoin-proof-budget.XETPUC/bip30_exception_block_state_proof.cpp \
    -o /data/my_storage/libbitcoin-proof-budget.XETPUC/bip30_exception_block_state_proof \
    $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/pkgconfig pkg-config --cflags --libs --static libbitcoin-system)
  /data/my_storage/libbitcoin-proof-budget.XETPUC/bip30_exception_block_state_proof; printf 'exit_status=%s\n' "$?"

Proof output:
  network=mainnet
  exception_height=91842
  direct_exception_has_bip30=0
  block_constructor_hash_is_null=1
  block_constructor_has_bip30=1
  core_expected_enforce_bip30_at_exception=0
  exit_status=1

Verifier:
  cmake --build build --target test_bitcoin -j$(nproc)
  build/bin/test_bitcoin --run_test=validation_tests/bip30_historical_duplicate_coinbase_exceptions
  build/bin/test_bitcoin --run_test=validation_tests
  git diff --check

Verifier output:
  *** No errors detected
  Running 1 test case...
  Running 10 test cases...
  *** No errors detected

Limitations:
- The libbitcoin proof targets the libbitcoin-system chain_state(pool, block)
  full-block constructor. libbitcoin-node's ordinary header-first path may use
  a header-derived context that stores the header hash before full block
  validation.
Bitcoin Core rejects transaction outputs whose individual value exceeds
MAX_MONEY and rejects transactions whose output total exceeds MAX_MONEY in
CheckTransaction. See src/consensus/tx_check.cpp lines 23-33.

Current libbitcoin-system reads output values as uint64_t and its stateless
transaction::check() only rejects empty transactions, invalid coinbase script
sizes, and null prevouts. It does not apply a MAX_MONEY-style range check
before returning transaction_success. See:
- /data/my_storage/libbitcoin-system/src/chain/output.cpp lines 130-136
- /data/my_storage/libbitcoin-system/src/chain/transaction.cpp lines 860-870
- /data/my_storage/libbitcoin-system/src/chain/transaction.cpp lines 686-693

Proof against current libbitcoin-system source:
/data/my_storage/libbitcoin-proof-budget.XETPUC/max_money_transaction_check_proof.cpp

Built and run with:
g++ -std=c++20 -O2 /data/my_storage/libbitcoin-proof-budget.XETPUC/max_money_transaction_check_proof.cpp -o /data/my_storage/libbitcoin-proof-budget.XETPUC/max_money_transaction_check_proof $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/pkgconfig pkg-config --cflags --libs --static libbitcoin-system)
/data/my_storage/libbitcoin-proof-budget.XETPUC/max_money_transaction_check_proof; printf 'exit_status=%s\n' "$?"

Output:
max_money=2100000000000000
single_output_value=2100000000000001
single_output_check=transaction success
total_output_spend=2100000000000001
total_output_check=transaction success
core_expected_single=bad-txns-vout-toolarge
core_expected_total=bad-txns-txouttotal-toolarge
exit_status=1

Core regression coverage added in src/test/transaction_tests.cpp checks both
reject reasons directly:
- bad-txns-vout-toolarge
- bad-txns-txouttotal-toolarge

Verification:
cmake --build build --target test_bitcoin -j$(nproc)
build/bin/test_bitcoin --run_test=transaction_tests/tx_output_money_range
build/bin/test_bitcoin --run_test=transaction_tests
git diff --check
l0rinc added 4 commits June 1, 2026 02:00
Bitcoin Core rejects transaction outputs whose serialized amount is negative
when interpreted as CAmount. CheckTransaction returns bad-txns-vout-negative
before any input-dependent checks. See src/consensus/tx_check.cpp lines 23-30.

Current libbitcoin-system stores output values as uint64_t and reads the
serialized 8-byte amount with read_8_bytes_little_endian(). Its stateless
transaction::check() does not apply a signed value-domain check before returning
transaction_success. See:
- /data/my_storage/libbitcoin-system/src/chain/output.cpp lines 130-136
- /data/my_storage/libbitcoin-system/src/chain/transaction.cpp lines 860-870

Proof against current libbitcoin-system source:
/data/my_storage/libbitcoin-proof-budget.XETPUC/negative_output_transaction_check_proof.cpp

Built and run with:
g++ -std=c++20 -O2 /data/my_storage/libbitcoin-proof-budget.XETPUC/negative_output_transaction_check_proof.cpp -o /data/my_storage/libbitcoin-proof-budget.XETPUC/negative_output_transaction_check_proof $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/pkgconfig pkg-config --cflags --libs --static libbitcoin-system)
/data/my_storage/libbitcoin-proof-budget.XETPUC/negative_output_transaction_check_proof; printf 'exit_status=%s\n' "$?"

Output:
serialized_negative_amount_uint64=18446744073709551615
libbitcoin_check=transaction success
core_expected=bad-txns-vout-negative
exit_status=1

Core regression coverage added in src/test/transaction_tests.cpp checks the
bad-txns-vout-negative reject reason directly.

Verification:
cmake --build build --target test_bitcoin -j$(nproc)
build/bin/test_bitcoin --run_test=transaction_tests/tx_negative_output_value
build/bin/test_bitcoin --run_test=transaction_tests
git diff --check
Add coverage for the header timestamp boundary where a child block is valid
because its timestamp is one second above its parent's median-time-past. The
constructed parent window uses ten low timestamps and one high parent timestamp;
Core accepts the child at parent_mtp + 1.

This guards against counting the parent timestamp twice when reconstructing and
rolling median-time-past state. Core's CBlockIndex::GetMedianTimePast is always
computed from the parent chain window used by ContextualCheckBlockHeader.

Libbitcoin evidence: database reconstruction populates chain_state::data with
an ordered timestamp window that already includes the stored parent block. When
chain_state(const chain_state& parent, const header& child, settings) rolls that
state forward, to_pool() pushes timestamp.self again and drops the oldest entry.
That raises the child context median in the proof below, making the same child
timestamp anachronistic.

Libbitcoin proof (/data/my_storage/libbitcoin-proof-budget.XETPUC/header_mtp_reconstructed_parent_proof.cpp):

  g++ -std=c++20 -O2 /data/my_storage/libbitcoin-proof-budget.XETPUC/header_mtp_reconstructed_parent_proof.cpp -o /data/my_storage/libbitcoin-proof-budget.XETPUC/header_mtp_reconstructed_parent_proof $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/pkgconfig pkg-config --cflags --libs --static libbitcoin-system)

  /data/my_storage/libbitcoin-proof-budget.XETPUC/header_mtp_reconstructed_parent_proof

  core_parent_mtp=1600000006
  libbitcoin_parent_state_mtp=1600000006
  child_time=1600000007
  libbitcoin_child_context_mtp=1600000007
  core_accepts_child_time=1
  libbitcoin_reconstructed_context_rejects_child_time=1
  exit_status=1

Core verification:

  cmake --build build --target test_bitcoin -j$(nproc)

  build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_header_time_uses_parent_mtp_once

  build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests

  git diff --check
Bitcoin Core's BIP68 time-based sequence lock calculation uses the MTP of the block before the spent output. CalculateSequenceLocks calls GetAncestor(nCoinHeight - 1)->GetMedianTimePast(), so a high timestamp on the prevout block itself can advance the spending block MTP without also raising the lock's starting point.

This test builds that boundary directly: block 10 has MTP 1600000000, block 11 has MTP 1600000512, and a block 12 spend of an output from block 11 with sequence 0x00400001 is valid because the lock pair is 1600000511.

Libbitcoin proof source: /data/my_storage/libbitcoin-proof-budget.XETPUC/bip68_prevout_mtp_reconstruction_proof.cpp

Libbitcoin source evidence:
- database consensus_chain_state.ipp populate_timestamps reconstructs a stored link's ordered timestamp window starting at that same link.
- database consensus_block.ipp spendable() obtains the prevout context with get_context(prevout, link) and passes prevout.mtp into input::is_relative_locked().
- system input.cpp considers a time lock still locked when spending_mtp - prevout_mtp is below the sequence interval.

Proof compile:
g++ -std=c++20 -O2 /data/my_storage/libbitcoin-proof-budget.XETPUC/bip68_prevout_mtp_reconstruction_proof.cpp -o /data/my_storage/libbitcoin-proof-budget.XETPUC/bip68_prevout_mtp_reconstruction_proof $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/pkgconfig pkg-config --cflags --libs --static libbitcoin-system)

Proof output:
core_prevout_base_mtp=1600000000
libbitcoin_reconstructed_prevout_mtp=1600000512
spending_parent_mtp=1600000512
sequence=4194305
core_accepts_sequence=1
libbitcoin_reconstructed_prevout_rejects=1

Verification:
- cmake --build build --target test_bitcoin -j$(nproc)
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests/block_sequence_locks_time_uses_prevout_parent_mtp
- build/bin/test_bitcoin --run_test=validation_chainstatemanager_tests
- git diff --check
Bitcoin Core's testnet min-difficulty rule does not simply reuse the
previous block's bits after a min-difficulty parent.  If the next block is
not itself more than 2 target spacings after the parent, GetNextWorkRequired
walks back over pow-limit special blocks and stops at the previous retarget
boundary or the previous non-pow-limit block.  A child after height
interval+1 must therefore inherit the retarget-boundary block bits, not the
min-difficulty parent bits.

Core evidence:
- src/pow.cpp GetNextWorkRequired returns powLimit only for the new block's
  own time gap.  Otherwise it walks pprev while the scanned block is not at a
  difficulty interval and has powLimit bits, then returns the scanned block's
  nBits.
- This test builds testnet blocks at heights 2016 and 2017, with height 2017
  using powLimit bits and the child only one spacing later.  Core returns the
  height-2016 boundary bits 0x1c0ffff0.

Libbitcoin evidence:
- libbitcoin-database consensus_chain_state.ipp populate_bits reconstructs a
  confirmed chain_state window by starting at the current link, so a confirmed
  parent at height interval+1 stores ordered bits ending in [boundary bits,
  parent powLimit bits].
- libbitcoin-system chain_state.cpp to_pool then pushes data.bits.self again
  when rolling that reconstructed parent to the child height.  The easy
  difficulty scan in easy_work_required sees the parent powLimit bits twice,
  causing the retarget-boundary stop to be paired with the duplicated parent
  bits rather than the boundary block bits.

Proof, built against current libbitcoin-system origin/master:
  g++ -std=c++20 -O2 /data/my_storage/libbitcoin-proof-budget.XETPUC/testnet_easy_bits_reconstruction_proof.cpp -o /data/my_storage/libbitcoin-proof-budget.XETPUC/testnet_easy_bits_reconstruction_proof $(PKG_CONFIG_PATH=/data/my_storage/libbitcoin-proof-budget.XETPUC/prefix/lib/pkgconfig pkg-config --cflags --libs --static libbitcoin-system)
  /data/my_storage/libbitcoin-proof-budget.XETPUC/testnet_easy_bits_reconstruction_proof; test $? -eq 1

Proof output:
  pow_limit_bits=0x1d00ffff
  retarget_boundary_bits=0x1c0ffff0
  parent_bits=0x1d00ffff
  parent_height=2017
  child_height=2018
  core_expected_child_bits=0x1c0ffff0
  libbitcoin_reconstructed_child_bits=0x1d00ffff
  diverged=1

Verification:
  cmake --build build --target test_bitcoin -j$(nproc)
  build/bin/test_bitcoin --run_test=pow_tests/get_next_work_testnet_easy_boundary_uses_retarget_bits
  build/bin/test_bitcoin --run_test=pow_tests
  git diff --check
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.

1 participant