mirror of
https://github.com/Such-Software/wowcheckout.git
synced 2026-10-17 05:51:45 +02:00
fix: use correct Wownero network bytes and 2-byte varint prefix for address validation
The monero Python library assumes 1-byte network prefixes (Monero format). Wownero uses 2-byte varint prefixes (base58 prefix 4146 for primary, 12208 for subaddress), so Address() was rejecting valid Wownero addresses with "Primary address is invalid". Replace monero lib Address usage with native Wownero validation: - Correct netbytes: 0xb2 (primary), 0xb0 (subaddress), 0xb3 (integrated) - 2-byte varint prefix offset for key extraction (bytes 2-33, 34-65) - 2-byte varint prefix in subaddress derivation encoding
This commit is contained in:
+48
-14
@@ -28,7 +28,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, User, Webhook, WebhookDelivery
|
from .models import Invoice, ProfileHistory, 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 (
|
||||||
@@ -358,28 +357,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",
|
||||||
|
|||||||
@@ -3,16 +3,28 @@ from __future__ import annotations
|
|||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from monero import base58, const, ed25519
|
from monero import base58, ed25519
|
||||||
from monero.address import Address
|
|
||||||
from monero.keccak import keccak_256
|
from monero.keccak import keccak_256
|
||||||
|
|
||||||
# Override monero library network constants for Wownero
|
# Wownero network prefix bytes (first byte of varint-encoded base58 prefix).
|
||||||
from monero import const as _mc
|
# Primary: base58 prefix 4146 -> varint [0xb2, 0x20] -> "Wo"
|
||||||
_mc.NETS = ('main',)
|
# Subaddr: base58 prefix 12208 -> varint [0xb0, 0x5f] -> "Ww"
|
||||||
_mc.MASTERADDR_NETBYTES = (53,) # Wownero mainnet address prefix
|
_WOW_PRIMARY_VARINT = bytes([0xb2, 0x20])
|
||||||
_mc.SUBADDR_NETBYTES = (63,) # Wownero subaddress prefix
|
_WOW_SUBADDR_VARINT = bytes([0xb0, 0x5f])
|
||||||
_mc.INTADDRR_NETBYTES = (54,) # Wownero integrated address prefix
|
|
||||||
|
|
||||||
|
def _decode_wow_address(payment_address: str) -> tuple[bytes, bytes]:
|
||||||
|
"""Decode a Wownero primary address and return (pub_spend, pub_view) as raw bytes."""
|
||||||
|
decoded = bytearray(unhexlify(base58.decode(payment_address)))
|
||||||
|
# Verify checksum
|
||||||
|
if decoded[-4:] != keccak_256(decoded[:-4]).digest()[:4]:
|
||||||
|
raise ValueError("Invalid checksum")
|
||||||
|
if decoded[0] != _WOW_PRIMARY_VARINT[0]:
|
||||||
|
raise ValueError(f"Not a Wownero primary address (netbyte {decoded[0]})")
|
||||||
|
# 2-byte varint prefix, then 32-byte spend key, 32-byte view key, 4-byte checksum
|
||||||
|
pub_spend = bytes(decoded[2:34])
|
||||||
|
pub_view = bytes(decoded[34:66])
|
||||||
|
return pub_spend, pub_view
|
||||||
|
|
||||||
|
|
||||||
def derive_subaddress(
|
def derive_subaddress(
|
||||||
@@ -22,9 +34,9 @@ def derive_subaddress(
|
|||||||
account_index: int,
|
account_index: int,
|
||||||
address_index: int,
|
address_index: int,
|
||||||
) -> str:
|
) -> str:
|
||||||
address = Address(payment_address)
|
"""Derive a Wownero subaddress from a primary address and secret view key."""
|
||||||
|
pub_spend, _pub_view = _decode_wow_address(payment_address)
|
||||||
view_key_bytes = unhexlify(view_key)
|
view_key_bytes = unhexlify(view_key)
|
||||||
spend_pub_bytes = unhexlify(address.spend_key())
|
|
||||||
|
|
||||||
data = (
|
data = (
|
||||||
b"SubAddr\0"
|
b"SubAddr\0"
|
||||||
@@ -33,9 +45,9 @@ def derive_subaddress(
|
|||||||
)
|
)
|
||||||
m = ed25519.scalar_reduce(keccak_256(data).digest())
|
m = ed25519.scalar_reduce(keccak_256(data).digest())
|
||||||
mG = ed25519.scalarmult_B(m)
|
mG = ed25519.scalarmult_B(m)
|
||||||
D = ed25519.edwards_add(spend_pub_bytes, mG)
|
D = ed25519.edwards_add(pub_spend, mG)
|
||||||
C = ed25519.scalarmult(view_key_bytes, D)
|
C = ed25519.scalarmult(view_key_bytes, D)
|
||||||
prefix = const.SUBADDR_NETBYTES[const.NETS.index(address.net)]
|
# Wownero subaddress uses 2-byte varint prefix
|
||||||
payload = bytearray([prefix]) + D + C
|
payload = bytearray(_WOW_SUBADDR_VARINT) + D + C
|
||||||
checksum = keccak_256(payload).digest()[:4]
|
checksum = keccak_256(payload).digest()[:4]
|
||||||
return base58.encode(hexlify(payload + checksum))
|
return base58.encode(hexlify(payload + checksum))
|
||||||
|
|||||||
Reference in New Issue
Block a user