From e9553efd2abb18160ecf2ea11ed96e1940a81757 Mon Sep 17 00:00:00 2001 From: crowniteto Date: Sun, 24 May 2026 18:25:59 +0000 Subject: [PATCH 1/2] fix(p2p): Validate pagination values in integrated blocks route --- tests/test_p2p_blocks.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/test_p2p_blocks.py diff --git a/tests/test_p2p_blocks.py b/tests/test_p2p_blocks.py new file mode 100644 index 000000000..bb377e899 --- /dev/null +++ b/tests/test_p2p_blocks.py @@ -0,0 +1 @@ +import pytest \ No newline at end of file From 0f2b990966fb84fa0494e41b6fcc73a514478a92 Mon Sep 17 00:00:00 2001 From: crowniteto Date: Mon, 25 May 2026 20:56:44 +0000 Subject: [PATCH 2/2] fix(utxo): validate public_key hex format before address converter (#6114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Malformed public_key strings (non-hex, wrong length) passed through to address_from_pubkey() which calls bytes.fromhex() — causing unhandled ValueError/500 instead of structured 400. Fix: - Pre-validate public_key is exactly 64 hex chars (32-byte Ed25519) - Return 400 with clear error message on invalid format - Wrap _addr_from_pk_fn call in try/except as defense-in-depth - 3 regression tests covering non-hex, short, and valid keys --- node/utxo_endpoints.py | 13 +++- tests/test_utxo_malformed_pubkey_6114.py | 83 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tests/test_utxo_malformed_pubkey_6114.py diff --git a/node/utxo_endpoints.py b/node/utxo_endpoints.py index 70a040cd6..bbb6988d0 100644 --- a/node/utxo_endpoints.py +++ b/node/utxo_endpoints.py @@ -451,7 +451,18 @@ def utxo_transfer(): }), 400 # Verify pubkey → address - expected_addr = _addr_from_pk_fn(public_key) + # FIX(#6114): catch malformed hex in public_key before converter blows up + try: + if len(public_key) != 64 or not all(c in "0123456789abcdefABCDEF" for c in public_key): + return jsonify({ + "error": "public_key must be 64 hex characters (32-byte Ed25519 key)", + "got": public_key[:20] + ("..." if len(public_key) > 20 else ""), + }), 400 + expected_addr = _addr_from_pk_fn(public_key) + except (ValueError, Exception) as e: + return jsonify({ + "error": f"Invalid public_key: {e}", + }), 400 if from_address != expected_addr: return jsonify({ 'error': 'Public key does not match from_address', diff --git a/tests/test_utxo_malformed_pubkey_6114.py b/tests/test_utxo_malformed_pubkey_6114.py new file mode 100644 index 000000000..becbde9c7 --- /dev/null +++ b/tests/test_utxo_malformed_pubkey_6114.py @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: MIT +"""Regression test for #6114: malformed public_key should return 400, not 500.""" + +import os +import sys +import importlib + +import pytest +from flask import Flask + + +NODE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "node")) +if NODE_DIR not in sys.path: + sys.path.insert(0, NODE_DIR) + +utxo_endpoints = importlib.import_module("utxo_endpoints") + + +def _addr_from_pk(pubkey_hex: str) -> str: + """Stub: only valid hex survives; raises ValueError on bad hex.""" + try: + bytes.fromhex(pubkey_hex) + except ValueError: + raise ValueError(f"Invalid hex: {pubkey_hex[:20]}") + return f"RTC1{pubkey_hex[:40]}" + + +@pytest.fixture +def client(tmp_path): + app = Flask(__name__) + utxo_endpoints.register_utxo_blueprint( + app, + utxo_db=object(), + db_path=str(tmp_path / "utxo.db"), + verify_sig_fn=lambda *args: False, + addr_from_pk_fn=_addr_from_pk, + current_slot_fn=lambda: 1, + ) + return app.test_client() + + +def test_malformed_public_key_returns_400(client): + """Non-hex public_key should return 400, not 500.""" + resp = client.post("/utxo/transfer", json={ + "from_address": "RTC1aaaa", + "to_address": "RTC1bbbb", + "public_key": "not-hex-at-all!!", + "signature": "abcd1234", + "nonce": "n1", + "amount_rtc": 1.0, + }) + assert resp.status_code == 400 + data = resp.get_json() + assert "public_key" in data["error"].lower() or "hex" in data["error"].lower() + + +def test_short_public_key_returns_400(client): + """Too-short hex public_key should return 400.""" + resp = client.post("/utxo/transfer", json={ + "from_address": "RTC1aaaa", + "to_address": "RTC1bbbb", + "public_key": "abcd", + "signature": "abcd1234", + "nonce": "n1", + "amount_rtc": 1.0, + }) + assert resp.status_code == 400 + + +def test_valid_hex_length_passes_hex_check(client): + """Valid 64-char hex public_key should pass hex validation (may fail later).""" + valid_pk = "a" * 64 + resp = client.post("/utxo/transfer", json={ + "from_address": "RTC1aaaa", + "to_address": "RTC1bbbb", + "public_key": valid_pk, + "signature": "b" * 128, + "nonce": "n1", + "amount_rtc": 1.0, + }) + data = resp.get_json() + if resp.status_code == 400 and "error" in data: + assert "64 hex" not in data["error"]