mirror of
https://github.com/Such-Software/wowcheckout.git
synced 2026-10-17 05:51:45 +02:00
fix: re-apply Wownero netbyte validator (regression from 696b04e)
Commit696b04eported routes.py changes from upstream xmrcheckout and silently reverted the Wownero-specific address validation introduced in20eff23. Result: every genuine Wownero address has been rejected with "Primary address is invalid" since that port, making /admin/merchants registration impossible for new merchants. Re-applies the 2-byte varint prefix decoder + keccak checksum + ed25519 view-key verification from20eff23, and drops the now-unused `from monero.address import Address, IntegratedAddress, SubAddress` line so future upstream pulls do not silently re-introduce the footgun (the import is the load-bearing tell that something has re-pulled the upstream Monero-only code path).
This commit is contained in:
+48
-14
@@ -29,7 +29,6 @@ from .config import (
|
|||||||
from .db import get_db
|
from .db import get_db
|
||||||
from .formatting import format_xmr_amount
|
from .formatting import format_xmr_amount
|
||||||
from .models import Invoice, ProfileHistory, SystemStatus, User, Webhook, WebhookDelivery
|
from .models import Invoice, ProfileHistory, SystemStatus, User, Webhook, WebhookDelivery
|
||||||
from monero.address import Address, IntegratedAddress, SubAddress
|
|
||||||
from .rates import get_wow_rate
|
from .rates import get_wow_rate
|
||||||
from .subaddress_allocator import MAX_SUBADDRESS_INDEX, create_subaddress_for_user
|
from .subaddress_allocator import MAX_SUBADDRESS_INDEX, create_subaddress_for_user
|
||||||
from .schemas import (
|
from .schemas import (
|
||||||
@@ -409,28 +408,63 @@ def _resolve_invoice_amount(
|
|||||||
|
|
||||||
|
|
||||||
def _validate_payment_address_and_view_key(payment_address: str, view_key: str) -> None:
|
def _validate_payment_address_and_view_key(payment_address: str, view_key: str) -> None:
|
||||||
|
"""Validate a Wownero primary address and its secret view key.
|
||||||
|
|
||||||
|
Wownero uses 2-byte varint prefixes (unlike Monero's 1-byte), so the
|
||||||
|
standard monero Python library cannot parse them correctly. We do
|
||||||
|
base58-decode + keccak checksum + ed25519 view-key verification here.
|
||||||
|
"""
|
||||||
|
from binascii import hexlify, unhexlify
|
||||||
|
from monero import base58, ed25519
|
||||||
|
from monero.keccak import keccak_256
|
||||||
|
|
||||||
|
# Wownero base58 prefix varints (first decoded byte)
|
||||||
|
_WOW_PRIMARY_BYTE0 = 178 # base58 prefix 4146 -> "Wo"
|
||||||
|
_WOW_SUBADDR_BYTE0 = 176 # base58 prefix 12208 -> "Ww"
|
||||||
|
_WOW_INTEGRATED_BYTE0 = 179 # base58 prefix 4147
|
||||||
|
|
||||||
try:
|
try:
|
||||||
address = Address(payment_address)
|
decoded = bytearray(unhexlify(base58.decode(payment_address)))
|
||||||
except ValueError as exc:
|
except Exception:
|
||||||
try:
|
|
||||||
SubAddress(payment_address)
|
|
||||||
except ValueError:
|
|
||||||
try:
|
|
||||||
IntegratedAddress(payment_address)
|
|
||||||
except ValueError:
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Primary address is invalid",
|
detail="Primary address is invalid",
|
||||||
) from exc
|
)
|
||||||
|
|
||||||
|
# Verify keccak-256 checksum (last 4 bytes)
|
||||||
|
if decoded[-4:] != keccak_256(decoded[:-4]).digest()[:4]:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Integrated addresses are not supported. Use the primary address.",
|
detail="Primary address is invalid",
|
||||||
) from exc
|
)
|
||||||
|
|
||||||
|
first_byte = decoded[0]
|
||||||
|
if first_byte == _WOW_SUBADDR_BYTE0:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Subaddresses are not supported. Use the primary address.",
|
detail="Subaddresses are not supported. Use the primary address.",
|
||||||
) from exc
|
)
|
||||||
if not address.check_private_view_key(view_key):
|
if first_byte == _WOW_INTEGRATED_BYTE0:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Integrated addresses are not supported. Use the primary address.",
|
||||||
|
)
|
||||||
|
if first_byte != _WOW_PRIMARY_BYTE0:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Primary address is invalid",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wownero has 2-byte varint prefix -> keys start at offset 2
|
||||||
|
pub_spend = decoded[2:34]
|
||||||
|
pub_view = decoded[34:66]
|
||||||
|
|
||||||
|
# Derive public view key from the secret view key and compare
|
||||||
|
try:
|
||||||
|
derived_pub_view = ed25519.public_from_secret_hex(view_key)
|
||||||
|
if unhexlify(derived_pub_view) != bytes(pub_view):
|
||||||
|
raise ValueError("mismatch")
|
||||||
|
except Exception:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Secret view key does not match the primary address",
|
detail="Secret view key does not match the primary address",
|
||||||
|
|||||||
Reference in New Issue
Block a user