Skip to content

security: UTXO dual-write uses wrong account units and inflates balances #2095

@createkr

Description

@createkr

UTXO↔account dual-write writes 1000x incorrect balance units

Summary

When UTXO_DUAL_WRITE=1 is enabled, the /utxo/transfer endpoint writes amount_rtc * 1_000_000_000 (9 decimals) into balances.amount_i64, but every other writer and reader in the account model uses amount_rtc * 1_000_000 (6 decimals). This causes recipient balances to be 1000x larger than they should be.

Additionally, the /utxo/integrity endpoint compares UTXO totals (stored at 8 decimals / nanoRTC) against raw SUM(amount_i64) from the account model (stored at 6 decimals / micro-RTC) without any unit conversion, so it can never detect this mismatch.

Affected Files

  • node/utxo_endpoints.py — line 320 (dual-write multiplier), lines 133-155 (integrity endpoint)

Root Cause

Two independent unit mismatches in the UTXO↔account dual-write bridge:

1. Dual-write multiplier (line 320):

# BUG: writes 9 decimals, but account model expects 6
amount_i64 = int(amount_rtc * 1_000_000_000)  # 9 decimals

The account model (rustchain_v2_integrated_v2.2.1_rip200.py line 2370, 5737, 4973, etc.) consistently uses * 1_000_000 (6 decimals) for amount_i64. The dual-write path uses * 1_000_000_000 (9 decimals) — a 1000x over-credit.

2. Integrity endpoint unit comparison (lines 133-155):

# BUG: compares nanoRTC (8 decimals) against raw amount_i64 (6 decimals)
account_total = SUM(amount_i64)  # 6-decimal uRTC
result = integrity_check(expected_total=account_total)  # expects nanoRTC
result['account_total_rtc'] = account_total / UNIT  # UNIT = 100_000_000 (8 dec)

The integrity check passes SUM(amount_i64) directly to integrity_check(expected_total=...) which compares it against the UTXO total in nanoRTC (8 decimals). These are different units, so the comparison is meaningless.

Impact

When dual-write is enabled (UTXO_DUAL_WRITE=1):

  • A transfer of 10 RTC credits the recipient with 10,000 RTC in the account model (1000x)
  • A transfer of 0.001 RTC credits 1 RTC instead of 0.001 RTC
  • The /utxo/integrity endpoint cannot detect this corruption because it compares incompatible units
  • Ledger delta_i64 entries are also 1000x inflated

The UTXO model itself is unaffected — utxo_boxes.value_nrtc uses the correct 8-decimal UNIT = 100_000_000. Only the shadow account-model writes are corrupted.

Reproduction

  1. Start the node with UTXO_DUAL_WRITE=1
  2. Seed a UTXO balance for RTC_test_sender
  3. POST /utxo/transfer with amount_rtc: 10.0 from sender to recipient
  4. Query SELECT amount_i64 FROM balances WHERE miner_id = 'RTC_test_recipient'
  5. Expected: 10_000_000 (10 × 1,000,000). Actual (bug): 10_000_000_000 (10 × 1,000,000,000)

Distinction from Prior Findings

This finding is specific to the dual-write bridge between UTXO and account models — a unit conversion error that corrupts account-model balances by 1000x.

Fix

Change the dual-write multiplier from 1_000_000_000 to 1_000_000 (ACCOUNT_UNIT), and fix the integrity endpoint to convert account totals from 6-decimal to 8-decimal units before comparison.


Payout wallet: RTC1d48d848a5aa5ecf2c5f01aa5fb64837daaf2f35

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions