From c527f410dd499cd8dfd56a08b9988187caaa7fa8 Mon Sep 17 00:00:00 2001 From: createkr <228850445+createkr@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:08:37 +0800 Subject: [PATCH] security: reject negative UTXO transaction fees --- node/test_utxo_db.py | 18 ++++++++++++++++++ node/utxo_db.py | 3 +++ 2 files changed, 21 insertions(+) diff --git a/node/test_utxo_db.py b/node/test_utxo_db.py index d413cde4..f9ec6b03 100644 --- a/node/test_utxo_db.py +++ b/node/test_utxo_db.py @@ -138,6 +138,24 @@ def test_fee_exceeds_conservation(self): self.assertFalse(ok) + def test_negative_fee_rejected(self): + """Negative fee should fail — allows minting via weakened conservation.""" + self._apply_coinbase('alice', 100 * UNIT) + alice_boxes = self.db.get_unspent_for_address('alice') + + ok = self.db.apply_transaction({ + 'tx_type': 'transfer', + 'inputs': [{'box_id': alice_boxes[0]['box_id'], + 'spending_proof': 'sig'}], + 'outputs': [{'address': 'bob', 'value_nrtc': 1100 * UNIT}], + 'fee_nrtc': -1000 * UNIT, # negative fee bypasses conservation + }, block_height=10) + + self.assertFalse(ok) + # Balances unchanged + self.assertEqual(self.db.get_balance('alice'), 100 * UNIT) + self.assertEqual(self.db.get_balance('bob'), 0) + # -- double-spend -------------------------------------------------------- def test_double_spend_rejected(self): diff --git a/node/utxo_db.py b/node/utxo_db.py index e809e8b6..6606b3e1 100644 --- a/node/utxo_db.py +++ b/node/utxo_db.py @@ -334,6 +334,9 @@ def apply_transaction(self, tx: dict, block_height: int, # -- conservation check (skip for coinbase) ---------------------- output_total = sum(o['value_nrtc'] for o in outputs) + if fee < 0: + conn.execute("ROLLBACK") + return False if inputs and (output_total + fee) > input_total: conn.execute("ROLLBACK") return False