From 07b226022968a3733441899e49d082c100f3d90f Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 23 Jul 2025 16:56:56 -0700 Subject: [PATCH 1/2] Adds wallet verify --- bittensor_cli/src/commands/wallets.py | 92 ++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 99842ef98..2947a6a0a 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -1800,10 +1800,96 @@ async def sign( ) signed_message = keypair.sign(message.encode("utf-8")).hex() - console.print("[dark_sea_green3]Message signed successfully:") + signer_address = keypair.ss58_address + console.print("[dark_sea_green3]Message signed successfully!\n") + if json_output: - json_console.print(json.dumps({"signed_message": signed_message})) - console.print(signed_message) + json_console.print( + json.dumps( + {"signed_message": signed_message, "signer_address": signer_address} + ) + ) + else: + console.print(f"[yellow]Signature:[/yellow]\n{signed_message}") + console.print(f"[yellow]Signer address:[/yellow] {signer_address}") + + +async def verify( + message: str, + signature: str, + public_key_or_ss58: str, + json_output: bool = False, +): + """Verify a message signature using a public key or SS58 address.""" + + if is_valid_ss58_address(public_key_or_ss58): + print_verbose(f"[blue]SS58 address detected:[/blue] {public_key_or_ss58}") + keypair = Keypair(ss58_address=public_key_or_ss58) + signer_address = public_key_or_ss58 + else: + try: + public_key_hex = public_key_or_ss58.strip().lower() + if public_key_hex.startswith("0x"): + public_key_hex = public_key_hex[2:] + if len(public_key_hex) == 64: + bytes.fromhex(public_key_hex) + print_verbose("[blue]Hex public key detected[/blue] (64 characters)") + keypair = Keypair(public_key=public_key_hex) + signer_address = keypair.ss58_address + print_verbose( + f"[blue]Corresponding SS58 address:[/blue] {signer_address}" + ) + else: + raise ValueError("Public key must be 32 bytes (64 hex characters)") + + except (ValueError, TypeError) as e: + if json_output: + json_console.print( + json.dumps( + { + "verified": False, + "error": f"Invalid public key or SS58 address: {str(e)}", + } + ) + ) + else: + err_console.print( + f":cross_mark: Invalid SS58 address or hex public key (64 chars, with or without 0x prefix)- {str(e)}" + ) + return False + + try: + signature_bytes = bytes.fromhex(signature.strip().lower().replace("0x", "")) + except ValueError as e: + if json_output: + json_console.print( + json.dumps( + { + "verified": False, + "error": f"Invalid signature format: {str(e)}", + } + ) + ) + else: + err_console.print(f"[red]:cross_mark: Invalid signature format: {str(e)}") + return False + + is_valid = keypair.verify(message.encode("utf-8"), signature_bytes) + + if json_output: + json_console.print( + json.dumps( + {"verified": is_valid, "signer": signer_address, "message": message} + ) + ) + else: + if is_valid: + console.print("[dark_sea_green3]Signature is valid!\n") + console.print(f"[yellow]Signer:[/yellow] {signer_address}") + else: + err_console.print(":cross_mark: [red]Signature verification failed!") + + return is_valid async def schedule_coldkey_swap( From 0b9499b700f6270952ec150a67dd9130ace4bbfd Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 23 Jul 2025 16:57:07 -0700 Subject: [PATCH 2/2] add cli cmd --- bittensor_cli/cli.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d751e06ad..de3cecec3 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -817,6 +817,9 @@ def __init__(self): self.wallet_app.command( "sign", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"] )(self.wallet_sign) + self.wallet_app.command( + "verify", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"] + )(self.wallet_verify) # stake commands self.stake_app.command( @@ -3091,6 +3094,59 @@ def wallet_sign( return self._run_command(wallets.sign(wallet, message, use_hotkey, json_output)) + def wallet_verify( + self, + message: Optional[str] = typer.Option( + None, "--message", "-m", help="The message that was signed" + ), + signature: Optional[str] = typer.Option( + None, "--signature", "-s", help="The signature to verify (hex format)" + ), + public_key_or_ss58: Optional[str] = typer.Option( + None, + "--address", + "-a", + "--public-key", + "-p", + help="SS58 address or public key (hex) of the signer", + ), + quiet: bool = Options.quiet, + verbose: bool = Options.verbose, + json_output: bool = Options.json_output, + ): + """ + Verify a message signature using the signer's public key or SS58 address. + + This command allows you to verify that a message was signed by the owner of a specific address. + + USAGE + + Provide the original message, the signature (in hex format), and either the SS58 address + or public key of the signer to verify the signature. + + EXAMPLES + + [green]$[/green] btcli wallet verify --message "Hello world" --signature "0xabc123..." --address "5GrwvaEF..." + + [green]$[/green] btcli wallet verify -m "Test message" -s "0xdef456..." -p "0x1234abcd..." + """ + self.verbosity_handler(quiet, verbose, json_output) + + if not public_key_or_ss58: + public_key_or_ss58 = Prompt.ask( + "Enter the [blue]address[/blue] (SS58 or hex format)" + ) + + if not message: + message = Prompt.ask("Enter the [blue]message[/blue]") + + if not signature: + signature = Prompt.ask("Enter the [blue]signature[/blue]") + + return self._run_command( + wallets.verify(message, signature, public_key_or_ss58, json_output) + ) + def wallet_swap_coldkey( self, wallet_name: Optional[str] = Options.wallet_name,