[UTXO-BUG] CRITICAL: Empty outputs allow fund destruction (200 RTC)#2120
Conversation
CRITICAL vulnerability fix for Issue #2819 ### Vulnerability Empty outputs bypass conservation check: - outputs=[] + fee=0 → output_total=0 - Check: (0 + 0) > input_total → False (bypassed) - Result: Inputs spent, no outputs created → funds destroyed ### Impact - Severity: CRITICAL (200 RTC) - 100% fund destruction possible - Violates conservation law ### Fix Add empty outputs check before conservation validation ### Testing - ✅ Added test case: test_utxo_empty_outputs_bug.py - ✅ All 50 existing tests still pass - ✅ PoC test confirms vulnerability is fixed **Bounty**: #2819 - Red Team UTXO Implementation **Reporter**: XiaZong (RTC0816b68b604630945c94cde35da4641a926aa4fd) **Tier**: BCOS-L2
|
Welcome to RustChain! Thanks for your first pull request. Before we review, please make sure:
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! |
There was a problem hiding this comment.
Pull request overview
This PR addresses a critical UTXO validation flaw where transfer transactions with outputs=[] could spend inputs without creating any outputs, effectively destroying funds and violating conservation-of-value.
Changes:
- Reject transactions with empty
outputsinUtxoDB.apply_transaction()to prevent fund destruction. - Add a regression test that reproduces the empty-outputs exploit and asserts it is rejected.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
node/utxo_db.py |
Adds an explicit outputs non-empty validation during transaction application. |
node/test_utxo_empty_outputs_bug.py |
Adds a standalone regression test covering the empty-outputs vulnerability. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # CRITICAL FIX: Reject empty outputs to prevent fund destruction | ||
| # Without this check, outputs=[] bypasses conservation law | ||
| # and results in complete fund destruction | ||
| if not outputs: | ||
| conn.execute("ROLLBACK") | ||
| return False |
There was a problem hiding this comment.
apply_transaction() now rejects transactions with outputs=[], but mempool_add() still admits them (it allows outputs=[] because output_total becomes 0). This reintroduces the exact mempool DoS the mempool conservation checks are trying to prevent: an attacker can submit an outputs=[] tx that will later fail apply_transaction(), but still locks the input UTXOs in utxo_mempool_inputs until expiry. Mirror this empty-outputs rejection (and ideally the same per-output value validation) in mempool_add() to keep admission criteria aligned with apply_transaction().
node/test_utxo_empty_outputs_bug.py
Outdated
| import sys | ||
| import time | ||
|
|
||
| # Add parent directory to path for imports |
There was a problem hiding this comment.
The comment says this adds the parent directory to sys.path, but os.path.dirname(os.path.abspath(file)) is the current file’s directory (the node/ folder). Either update the comment to reflect what’s being added, or change the path insertion to actually add the parent directory if that’s what’s intended.
| # Add parent directory to path for imports | |
| # Add this file's directory to path for imports |
CRITICAL + MEDIUM vulnerability fixes for Issue #2819 **apply_transaction()**: Reject empty outputs (CRITICAL) **mempool_add()**: Reject empty outputs (MEDIUM - defense in depth) Testing: 50/50 existing tests + 2 new PoC tests pass
📝 Bounty Reference ClarificationCorrect Bounty Issue: Scottcjn/rustchain-bounties#2819 The bounty issue referenced in this PR is located in the rustchain-bounties repository (specs-only repo), not in the Rustchain repository (code repo). Bounty Details:
Why the reference appears broken:
Reporter: XiaZong (RTC0816b68b604630945c94cde35da4641a926aa4fd) |
|
Merged. Payment: 50 RTC Good UTXO red team finding. The empty outputs bypass of the conservation law check is a real vulnerability. Fix is clean and well-tested. Payment: 50 RTC to wallet Note: Reduced from 200 RTC bounty claim to 50 RTC because:
|
No description provided.