Skip to content

Commit b0531b6

Browse files
authored
Merge pull request #561 from opentensor/feat/wallet-verify
Feat/wallet verify
2 parents c2733b6 + 0b9499b commit b0531b6

File tree

2 files changed

+145
-3
lines changed

2 files changed

+145
-3
lines changed

bittensor_cli/cli.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,9 @@ def __init__(self):
817817
self.wallet_app.command(
818818
"sign", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
819819
)(self.wallet_sign)
820+
self.wallet_app.command(
821+
"verify", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
822+
)(self.wallet_verify)
820823

821824
# stake commands
822825
self.stake_app.command(
@@ -3091,6 +3094,59 @@ def wallet_sign(
30913094

30923095
return self._run_command(wallets.sign(wallet, message, use_hotkey, json_output))
30933096

3097+
def wallet_verify(
3098+
self,
3099+
message: Optional[str] = typer.Option(
3100+
None, "--message", "-m", help="The message that was signed"
3101+
),
3102+
signature: Optional[str] = typer.Option(
3103+
None, "--signature", "-s", help="The signature to verify (hex format)"
3104+
),
3105+
public_key_or_ss58: Optional[str] = typer.Option(
3106+
None,
3107+
"--address",
3108+
"-a",
3109+
"--public-key",
3110+
"-p",
3111+
help="SS58 address or public key (hex) of the signer",
3112+
),
3113+
quiet: bool = Options.quiet,
3114+
verbose: bool = Options.verbose,
3115+
json_output: bool = Options.json_output,
3116+
):
3117+
"""
3118+
Verify a message signature using the signer's public key or SS58 address.
3119+
3120+
This command allows you to verify that a message was signed by the owner of a specific address.
3121+
3122+
USAGE
3123+
3124+
Provide the original message, the signature (in hex format), and either the SS58 address
3125+
or public key of the signer to verify the signature.
3126+
3127+
EXAMPLES
3128+
3129+
[green]$[/green] btcli wallet verify --message "Hello world" --signature "0xabc123..." --address "5GrwvaEF..."
3130+
3131+
[green]$[/green] btcli wallet verify -m "Test message" -s "0xdef456..." -p "0x1234abcd..."
3132+
"""
3133+
self.verbosity_handler(quiet, verbose, json_output)
3134+
3135+
if not public_key_or_ss58:
3136+
public_key_or_ss58 = Prompt.ask(
3137+
"Enter the [blue]address[/blue] (SS58 or hex format)"
3138+
)
3139+
3140+
if not message:
3141+
message = Prompt.ask("Enter the [blue]message[/blue]")
3142+
3143+
if not signature:
3144+
signature = Prompt.ask("Enter the [blue]signature[/blue]")
3145+
3146+
return self._run_command(
3147+
wallets.verify(message, signature, public_key_or_ss58, json_output)
3148+
)
3149+
30943150
def wallet_swap_coldkey(
30953151
self,
30963152
wallet_name: Optional[str] = Options.wallet_name,

bittensor_cli/src/commands/wallets.py

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1800,10 +1800,96 @@ async def sign(
18001800
)
18011801

18021802
signed_message = keypair.sign(message.encode("utf-8")).hex()
1803-
console.print("[dark_sea_green3]Message signed successfully:")
1803+
signer_address = keypair.ss58_address
1804+
console.print("[dark_sea_green3]Message signed successfully!\n")
1805+
18041806
if json_output:
1805-
json_console.print(json.dumps({"signed_message": signed_message}))
1806-
console.print(signed_message)
1807+
json_console.print(
1808+
json.dumps(
1809+
{"signed_message": signed_message, "signer_address": signer_address}
1810+
)
1811+
)
1812+
else:
1813+
console.print(f"[yellow]Signature:[/yellow]\n{signed_message}")
1814+
console.print(f"[yellow]Signer address:[/yellow] {signer_address}")
1815+
1816+
1817+
async def verify(
1818+
message: str,
1819+
signature: str,
1820+
public_key_or_ss58: str,
1821+
json_output: bool = False,
1822+
):
1823+
"""Verify a message signature using a public key or SS58 address."""
1824+
1825+
if is_valid_ss58_address(public_key_or_ss58):
1826+
print_verbose(f"[blue]SS58 address detected:[/blue] {public_key_or_ss58}")
1827+
keypair = Keypair(ss58_address=public_key_or_ss58)
1828+
signer_address = public_key_or_ss58
1829+
else:
1830+
try:
1831+
public_key_hex = public_key_or_ss58.strip().lower()
1832+
if public_key_hex.startswith("0x"):
1833+
public_key_hex = public_key_hex[2:]
1834+
if len(public_key_hex) == 64:
1835+
bytes.fromhex(public_key_hex)
1836+
print_verbose("[blue]Hex public key detected[/blue] (64 characters)")
1837+
keypair = Keypair(public_key=public_key_hex)
1838+
signer_address = keypair.ss58_address
1839+
print_verbose(
1840+
f"[blue]Corresponding SS58 address:[/blue] {signer_address}"
1841+
)
1842+
else:
1843+
raise ValueError("Public key must be 32 bytes (64 hex characters)")
1844+
1845+
except (ValueError, TypeError) as e:
1846+
if json_output:
1847+
json_console.print(
1848+
json.dumps(
1849+
{
1850+
"verified": False,
1851+
"error": f"Invalid public key or SS58 address: {str(e)}",
1852+
}
1853+
)
1854+
)
1855+
else:
1856+
err_console.print(
1857+
f":cross_mark: Invalid SS58 address or hex public key (64 chars, with or without 0x prefix)- {str(e)}"
1858+
)
1859+
return False
1860+
1861+
try:
1862+
signature_bytes = bytes.fromhex(signature.strip().lower().replace("0x", ""))
1863+
except ValueError as e:
1864+
if json_output:
1865+
json_console.print(
1866+
json.dumps(
1867+
{
1868+
"verified": False,
1869+
"error": f"Invalid signature format: {str(e)}",
1870+
}
1871+
)
1872+
)
1873+
else:
1874+
err_console.print(f"[red]:cross_mark: Invalid signature format: {str(e)}")
1875+
return False
1876+
1877+
is_valid = keypair.verify(message.encode("utf-8"), signature_bytes)
1878+
1879+
if json_output:
1880+
json_console.print(
1881+
json.dumps(
1882+
{"verified": is_valid, "signer": signer_address, "message": message}
1883+
)
1884+
)
1885+
else:
1886+
if is_valid:
1887+
console.print("[dark_sea_green3]Signature is valid!\n")
1888+
console.print(f"[yellow]Signer:[/yellow] {signer_address}")
1889+
else:
1890+
err_console.print(":cross_mark: [red]Signature verification failed!")
1891+
1892+
return is_valid
18071893

18081894

18091895
async def schedule_coldkey_swap(

0 commit comments

Comments
 (0)