Detached553#183
Draft
l0rinc wants to merge 34 commits into
Draft
Conversation
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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.