feat(alpha): complete hardening backlog
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).
This commit is contained in:
49
packages/tracker/meshnet_tracker/wallet_proof.py
Normal file
49
packages/tracker/meshnet_tracker/wallet_proof.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user