Complete the alpha-hardening Ralph task set, including tracker billing/accounting guards, validator fraud-audit primitives, wallet binding proof support, documentation runbooks, and updated tests. Verification: .venv/bin/python -m compileall -q packages tests; .venv/bin/python -m pytest -q --tb=short (313 passed, 3 skipped, 1 failed: tests/test_mining_cli.py::test_legacy_start_without_port_uses_next_available_port because meshnet-node pid 1263451 is already listening on port 7000).
50 lines
2.0 KiB
Python
50 lines
2.0 KiB
Python
"""Ed25519 proof-of-ownership for client wallet binding (ADR-0017 §5, issue C6).
|
|
|
|
Wallet addresses are base58-encoded Solana ed25519 public keys, the same
|
|
format used by ``packages/node/meshnet_node/wallet.py``. Binding a wallet to
|
|
an API key requires a signature over a message that embeds the api_key, so a
|
|
captured (wallet, signature) pair cannot be replayed to bind a different key.
|
|
|
|
No Solana RPC dependency is needed for verification, so this stays on plain
|
|
``cryptography`` rather than pulling in ``solders`` on the tracker.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from cryptography.exceptions import InvalidSignature
|
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
|
|
_B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
_B58_INDEX = {char: index for index, char in enumerate(_B58_ALPHABET)}
|
|
|
|
|
|
def b58decode(value: str) -> bytes:
|
|
"""Decode a base58 string (Solana/Bitcoin alphabet) to bytes."""
|
|
n = 0
|
|
for char in value:
|
|
digit = _B58_INDEX.get(char)
|
|
if digit is None:
|
|
raise ValueError(f"invalid base58 character: {char!r}")
|
|
n = n * 58 + digit
|
|
body = n.to_bytes((n.bit_length() + 7) // 8, "big") if n else b""
|
|
leading_zeros = len(value) - len(value.lstrip("1"))
|
|
return b"\x00" * leading_zeros + body
|
|
|
|
|
|
def binding_message(api_key: str, wallet: str) -> bytes:
|
|
"""Message a wallet owner must sign to bind ``wallet`` to ``api_key``."""
|
|
return f"meshnet-wallet-bind:{api_key}:{wallet}".encode()
|
|
|
|
|
|
def verify_wallet_signature(wallet: str, message: bytes, signature_hex: str) -> bool:
|
|
"""True if ``signature_hex`` is a valid ed25519 signature by ``wallet`` over ``message``."""
|
|
try:
|
|
pubkey_bytes = b58decode(wallet)
|
|
if len(pubkey_bytes) != 32:
|
|
return False
|
|
signature = bytes.fromhex(signature_hex)
|
|
Ed25519PublicKey.from_public_bytes(pubkey_bytes).verify(signature, message)
|
|
return True
|
|
except (ValueError, InvalidSignature):
|
|
return False
|