Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions node/bridge_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,15 @@ def validate_chain_address_format(chain: str, address: str) -> Tuple[bool, str]:
return True, ""


def validate_bridge_route_address(
chain: str, address: str, *, rustchain_source_is_miner: bool = False
) -> Tuple[bool, str]:
"""Validate a bridge route address without conflating miner IDs and wallets."""
if chain == "rustchain" and rustchain_source_is_miner:
return validate_miner_id_format(address)
return validate_chain_address_format(chain, address)


# =============================================================================
# Bridge Transfer Functions
# =============================================================================
Expand Down Expand Up @@ -801,28 +810,36 @@ def initiate_bridge():
if not validation.ok:
return jsonify({"error": validation.error}), 400
details = validation.details or {}

# Validate address formats
for chain, addr in [
(details["source_chain"], details["source_address"]),
(details["dest_chain"], details["dest_address"])
]:
valid, msg = validate_chain_address_format(chain, addr)
if not valid:
return jsonify({"error": f"Invalid {chain} address: {msg}"}), 400

# Check admin initiation (bypasses balance check)

# Deposits lock balances keyed by RustChain miner_id. Require operator
# authorization before any address-format response can mask auth state
# or leak validation behavior for another miner's balance.
admin_key = request.headers.get("X-Admin-Key", "")
expected_admin_key = os.environ.get("RC_ADMIN_KEY", "")
admin_initiated = bool(expected_admin_key) and hmac.compare_digest(admin_key, expected_admin_key)
if details["direction"] == "deposit":
# Deposits create balance locks by source_address; require operator
# authorization until a wallet-owner signature flow exists.
if not expected_admin_key:
return jsonify({"error": "RC_ADMIN_KEY not configured"}), 503
if not admin_initiated:
return jsonify({"error": "unauthorized"}), 401

# Validate address formats
for chain, addr in [
(details["source_chain"], details["source_address"]),
(details["dest_chain"], details["dest_address"])
]:
valid, msg = validate_bridge_route_address(
chain,
addr,
rustchain_source_is_miner=(
details["direction"] == "deposit"
and chain == details["source_chain"]
and chain == "rustchain"
),
)
if not valid:
return jsonify({"error": f"Invalid {chain} address: {msg}"}), 400

# Create bridge transfer
req = BridgeTransferRequest(
direction=details["direction"],
Expand Down
2 changes: 1 addition & 1 deletion tests/test_bridge_lock_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ class TestAddressValidation:
def test_valid_rustchain_address(self, setup_test_db):
"""Test valid RustChain address."""
bridge_api = setup_test_db["bridge_api"]
valid, msg = bridge_api.validate_chain_address_format("rustchain", "RTC_test123abc")
valid, msg = bridge_api.validate_chain_address_format("rustchain", "RTC" + "a" * 40)
assert valid is True

def test_invalid_rustchain_address_prefix(self, setup_test_db):
Expand Down
Loading