Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions bittensor_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
92 changes: 89 additions & 3 deletions bittensor_cli/src/commands/wallets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading