Skip to content

[UTXO-BUG] CRIT-1: Conservation law bypass via negative/zero-value outputs#2067

Merged
Scottcjn merged 1 commit intoScottcjn:mainfrom
ArokyaMatthew:utxo-bug/crit1-negative-value-outputs
Apr 6, 2026
Merged

[UTXO-BUG] CRIT-1: Conservation law bypass via negative/zero-value outputs#2067
Scottcjn merged 1 commit intoScottcjn:mainfrom
ArokyaMatthew:utxo-bug/crit1-negative-value-outputs

Conversation

@ArokyaMatthew
Copy link
Copy Markdown
Contributor

Vulnerability Class

Critical — Fund creation from nothing (200 RTC bounty)

The Bug

apply_transaction() in utxo_db.py validates that output_total + fee <= input_total, but never validates that individual output values are positive.

A negative-value output reduces output_total, allowing an attacker to create a second output with value exceeding input_total while the conservation check passes.

Attack Vector

# Alice has 100 RTC in a single UTXO
tx = {
    'tx_type': 'transfer',
    'inputs': [{'box_id': alice_box_id, 'spending_proof': 'sig'}],
    'outputs': [
        {'address': 'attacker', 'value_nrtc': 200 * UNIT},   # 200 RTC
        {'address': 'burn',     'value_nrtc': -100 * UNIT},   # -100 RTC
    ],
    'fee_nrtc': 0,
}

output_total = 200 + (-100) = 100 <= input_total = 100 → PASSES

Attacker now has 200 RTC from a 100 RTC input

Fix
Added validation in apply_transaction() that every output must have value_nrtc as a strictly positive integer. This also blocks zero-value UTXO set bloat and float-type values that cause silent SQLite truncation.

Tests Added
test_negative_value_output_rejected — demonstrates the attack above
test_zero_value_output_rejected — blocks zero-value dust outputs
test_float_value_nrtc_rejected — blocks non-integer values
All 37 tests pass.

Files Changed
node/utxo_db.py — 6 lines added (validation block)
node/test_utxo_db.py — 63 lines added (3 test cases)
Ref: Bounty #2819

MY WALLET IS aroky-x86-miner

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 5, 2026

Welcome to RustChain! Thanks for your first pull request.

Before we review, please make sure:

  • Your PR has a BCOS-L1 or BCOS-L2 label
  • New code files include an SPDX license header
  • You've tested your changes against the live node

Bounty tiers: Micro (1-10 RTC) | Standard (20-50) | Major (75-100) | Critical (100-150)

A maintainer will review your PR soon. Thanks for contributing!

@github-actions github-actions bot added BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related size/M PR: 51-200 lines labels Apr 5, 2026
@zhuzhushiwojia
Copy link
Copy Markdown

Great security audit work! These fixes are critical for the protocol.

@Scottcjn
Copy link
Copy Markdown
Owner

Scottcjn commented Apr 5, 2026

Partial overlap with createkr #2063 (merged) which added the fee < 0 rejection. However, this PR adds novel per-output validation — rejecting negative values, zero values, and non-integer value_nrtc on individual outputs. That defense-in-depth was not covered by #2063.

The conservation law bypass via negative outputs (not just negative fees) is a distinct attack vector. The float/type check on value_nrtc is also valuable hardening.

⚠️ Merge conflicts detected@ArokyaMatthew please rebase onto main so the fee < 0 check from #2063 does not conflict. Once rebased, this merges at 50 RTC (partial overlap deduction from full 75 RTC).

Wallet: ArokyaMatthew | Reason: bounty #2819 CRIT-1 per-output validation (novel portion, partial overlap #2063)

@Scottcjn
Copy link
Copy Markdown
Owner

Scottcjn commented Apr 5, 2026

Verified against production code. Real vulnerability.

apply_transaction() in utxo_db.py sums output values without checking individual positivity. An output of +200 and -100 passes the conservation check (sum = 100) but creates a spendable 200 RTC UTXO. Same gap exists in mempool_add().

Mitigating factor: The /utxo/transfer endpoint constructs its own outputs (line 311), so external exploitation requires a new endpoint or compromised block producer. But the UTXO core layer MUST be self-securing — defense-in-depth demands the fix.

Severity: HIGH with critical defense-in-depth implications. Not currently externally exploitable but becomes critical as UTXO migration completes.

Payment: 75 RTC (bounty #2819)

Merge order: this PR first, then #2070 rebased on top. Both modify apply_transaction() in different locations — no conflict expected.

Excellent work as always, @ArokyaMatthew.

…tputs

apply_transaction() never validates that output value_nrtc is positive.
A negative-value output reduces output_total, allowing an attacker to
create outputs exceeding input_total while the conservation check passes.

Attack: 100 RTC input -> [+200 RTC, -100 RTC] outputs
  output_total = 200 + (-100) = 100 <= input_total = 100  -> PASSES
  Attacker now has 200 RTC from a 100 RTC input.

Fix: validate every output has integer value_nrtc > 0 before the
conservation check. Also rejects zero-value dust and float types.

Tests added:
- test_negative_value_output_rejected
- test_zero_value_output_rejected
- test_float_value_nrtc_rejected

Bounty: #2819 (Critical, 200 RTC)
@Scottcjn Scottcjn force-pushed the utxo-bug/crit1-negative-value-outputs branch from af816e6 to 3742afc Compare April 6, 2026 00:02
@Scottcjn
Copy link
Copy Markdown
Owner

Scottcjn commented Apr 6, 2026

Rebased onto current main — merge conflict in node/test_utxo_db.py resolved.

Conflict: upstream main had added mempool conservation-of-value tests (DoS prevention) in the same region where this PR adds negative/zero value output tests. Both test sets are additive and independent — kept both.

  • node/utxo_db.py: merged cleanly (no conflict)
  • node/test_utxo_db.py: conflict resolved by keeping both upstream mempool tests AND this PR's bounty #2819 tests

Ready to merge.

@Scottcjn Scottcjn merged commit 4bc2f64 into Scottcjn:main Apr 6, 2026
7 of 8 checks passed
@Scottcjn
Copy link
Copy Markdown
Owner

Scottcjn commented Apr 6, 2026

Payment: 75 RTC transferred to ArokyaMatthew wallet (pending 24h confirmation).

Memo: UTXO CRIT-1: Conservation law bypass via negative outputs — bounty #2819

Thank you for the contribution!

@ArokyaMatthew
Copy link
Copy Markdown
Contributor Author

Bounty Recognition Request — 13 Merged PRs

Hi @Scottcjn,

I've had 13 PRs merged into the RustChain codebase covering security fixes, validation hardening, and test coverage across BFT consensus, UTXO, sync, governance, bridge, and transfer modules. I'd like to request the associated bounty credits be recognized.

Merged PRs Summary

PR Bounty
#2092 35 RTC
#2091 50 RTC
#2090 100 RTC
#2089 75 RTC
#2088 75 RTC
#2087 25 RTC
#2086 75 RTC
#2073 25 RTC
#2072 40 RTC
#2071 50 RTC
#2069 75 RTC
#2068 25 RTC
#2067 75 RTC
Total 725 RTC

All PRs have been reviewed and merged. Each included regression tests and detailed vulnerability descriptions per the bounty guidelines. I have mentioned my wallet in every PR submission.

Please credit the above to my wallet when convenient. Thank you for maintaining the bounty program — happy to keep contributing.


⚠️ IMPORTANT: Wallet Clarification

My wallet is aroky-x86-miner

It is NOT ArokyaMatthew — that is only my GitHub username.

Please send all 725 RTC to: aroky-x86-miner

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

Labels

BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) node Node server related size/M PR: 51-200 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants