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
19 changes: 4 additions & 15 deletions autonity_cli/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,15 @@
from eth_utils.conversions import to_int
from eth_utils.crypto import keccak
from hexbytes import HexBytes
from trezorlib.client import get_default_client
from trezorlib.exceptions import Cancelled
from trezorlib.messages import Features
from trezorlib.tools import parse_path
from trezorlib.transport import DeviceIsBusy
from web3.types import TxParams

from . import config
from . import config, device
from .logging import log
from .utils import to_checksum_address

TREZOR_DEFAULT_PREFIX = "m/44h/60h/0h/0"


class Authenticator(Protocol):
address: ChecksumAddress
Expand Down Expand Up @@ -67,7 +63,7 @@ def sign_message(self, message: str) -> bytes:
class TrezorAuthenticator:
def __init__(self, path_or_index: str):
if path_or_index.isdigit():
path_str = f"{TREZOR_DEFAULT_PREFIX}/{int(path_or_index)}"
path_str = f"{device.TREZOR_DEFAULT_PREFIX}/{int(path_or_index)}"
else:
path_str = path_or_index
try:
Expand All @@ -76,20 +72,13 @@ def __init__(self, path_or_index: str):
raise click.ClickException(
f"Invalid Trezor BIP32 derivation path '{path_str}'."
) from exc
try:
self.client = get_default_client()
except DeviceIsBusy as exc:
raise click.ClickException("Device in use by another process.") from exc
except Exception as exc:
raise click.ClickException(
"No Trezor device found. Check device is connected, unlocked, and detected by OS."
) from exc
self.client = device.get_client()
device_info = self.device_info(self.client.features)
log(f"Connected to Trezor: {device_info}")

try:
address_str = trezor_eth.get_address(self.client, self.path)
except Cancelled as exc:
except Cancelled as exc: # user cancelled optional passphrase prompt
raise click.Abort() from exc

self.address = to_checksum_address(address_str)
Expand Down
61 changes: 48 additions & 13 deletions autonity_cli/commands/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
from typing import List, Optional

import click
import eth_account
from autonity import Autonity
from click import ClickException, Path, argument, group, option
Expand All @@ -11,8 +12,11 @@
from web3 import Web3
from web3.types import BlockIdentifier

from .. import config
from ..auth import validate_authenticator, validate_authenticator_account
from .. import config, device
from ..auth import (
validate_authenticator,
validate_authenticator_account,
)
from ..denominations import (
format_auton_quantity,
format_newton_quantity,
Expand All @@ -31,6 +35,7 @@
keyfile_option,
keystore_option,
newton_or_token_option,
optgroup,
rpc_endpoint_option,
)
from ..user import get_account_stats
Expand All @@ -54,20 +59,50 @@ def account_group() -> None:


@account_group.command(name="list")
@option("--with-files", is_flag=True, help="also show keyfile names.")
@keystore_option()
def list_cmd(keystore: Optional[str], with_files: bool) -> None:
@optgroup.group("Keyfile accounts")
@keystore_option(cls=optgroup.option)
@optgroup.group("Trezor accounts")
@optgroup.option("--trezor", is_flag=True, help="Enumerate Trezor accounts")
@optgroup.option(
"--prefix",
metavar="PREFIX",
default=device.TREZOR_DEFAULT_PREFIX,
show_default=True,
help="Custom BIP32 derivation prefix",
)
@optgroup.option(
"--start",
type=int,
default=0,
show_default=True,
help="Start index at BIP32 derivation prefix",
)
@optgroup.option(
"-n",
type=int,
default=20,
show_default=True,
help="Number of Trezor accounts to list",
)
def list_cmd(
keystore: Optional[str], trezor: bool, prefix: str, start: int, n: int
) -> None:
"""
List the accounts for files in the keystore directory.
List accounts in keyfiles or in a Trezor device.
"""

keystore = config.get_keystore_directory(keystore)
keyfiles = address_keyfile_dict(keystore)
for addr, keyfile in keyfiles.items():
if with_files:
print(addr + " " + keyfile)
else:
print(addr)
if trezor and keystore:
raise click.ClickException(
"Options --trezor and --keystore are mutually exclusive."
)

if trezor:
accounts = device.enumerate_accounts(prefix, start, n)
else:
keystore = config.get_keystore_directory(keystore)
accounts = address_keyfile_dict(keystore).items()
for addr, path in accounts:
print(addr + " " + path)


@account_group.command()
Expand Down
47 changes: 47 additions & 0 deletions autonity_cli/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Hardware wallet common functions.

Currently only Trezor devices are supported."""

import click
import trezorlib.ethereum as trezor_eth
from eth_typing import ChecksumAddress
from trezorlib.client import TrezorClient, get_default_client
from trezorlib.exceptions import Cancelled
from trezorlib.tools import parse_path
from trezorlib.transport import DeviceIsBusy

from .utils import to_checksum_address

TREZOR_DEFAULT_PREFIX = "m/44h/60h/0h/0"


def get_client() -> TrezorClient:
try:
return get_default_client()
except DeviceIsBusy as exc:
raise click.ClickException("Device in use by another process.") from exc
except Exception as exc:
raise click.ClickException(
"No Trezor device found. Check device is connected, unlocked, and detected by OS."
) from exc


def enumerate_accounts(
prefix: str, start: int, n: int
) -> list[tuple[ChecksumAddress, str]]:
accounts: list[tuple[ChecksumAddress, str]] = []
client = get_client()
try:
for index in range(start, start + n):
path_str = prefix + f"/{index}"
try:
path = parse_path(path_str)
except ValueError as exc:
raise click.ClickException(
f"Invalid Trezor BIP32 derivation path '{path_str}'."
) from exc
address_str = trezor_eth.get_address(client, path)
accounts.append((to_checksum_address(address_str), path_str))
except Cancelled as exc: # user cancelled optional passphrase prompt
raise click.Abort() from exc
return accounts
25 changes: 15 additions & 10 deletions autonity_cli/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ def make_option(
help="encrypted private key file (falls back to 'keyfile' in config file).",
)

keystore_option_info = OptionInfo(
args=(
"--keystore",
"-s",
),
type=Path(exists=True),
help=(
"keystore directory (falls back to 'keystore' in config file, "
"defaults to ~/.autonity/keystore)."
),
)


trezor_option_info = OptionInfo(
args=["--trezor"],
metavar="ACCOUNT",
Expand All @@ -72,21 +85,13 @@ def make_option(
)


def keystore_option() -> Decorator[Func]:
def keystore_option(cls: Decorator[Any] = click.option) -> Decorator[Func]:
"""
Option: --keystore <directory>.
"""

def decorator(fn: Func) -> Func:
return click.option(
"--keystore",
"-s",
type=Path(exists=True),
help=(
"keystore directory (falls back to 'keystore' in config file, "
"defaults to ~/.autonity/keystore)."
),
)(fn)
return make_option(keystore_option_info, cls=cls)(fn)

return decorator

Expand Down
Loading