"""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