Skip to content

Commit 7deb39f

Browse files
committed
Fix: Could not import private key in base32/base64
The command `aleph account create` did not permit users to import their private key encoded in base32 or base64. This adds the `--key-format` argument to the command to specify this.
1 parent 5a316d6 commit 7deb39f

File tree

4 files changed

+96
-2
lines changed

4 files changed

+96
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ or
6262
6363
### Formatting code
6464

65-
> hatch run linting:format
65+
> hatch run linting:fmt
6666
6767
### Checking types
6868

src/aleph_client/commands/account.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from __future__ import annotations
22

33
import asyncio
4+
import base64
45
import logging
6+
from enum import Enum
57
from pathlib import Path
68
from typing import Annotated, Optional
79

@@ -31,6 +33,7 @@
3133
from typer.colors import GREEN, RED
3234

3335
from aleph_client.commands import help_strings
36+
from aleph_client.commands.help_strings import INVALID_KEY_FORMAT
3437
from aleph_client.commands.utils import (
3538
input_multiline,
3639
setup_logging,
@@ -44,6 +47,25 @@
4447
console = Console()
4548

4649

50+
class KeyEncoding(str, Enum):
51+
HEXADECIMAL = "hexadecimal"
52+
BASE32 = "base32"
53+
BASE64 = "base64"
54+
55+
56+
def decode_private_key(private_key: str, encoding: KeyEncoding) -> bytes:
57+
"""Decode a private key from a string via the specified encoding."""
58+
if encoding == KeyEncoding.HEXADECIMAL:
59+
return bytes_from_hex(private_key)
60+
elif encoding == KeyEncoding.BASE32:
61+
# Base32 keys are always uppercase
62+
return base64.b32decode(private_key.upper())
63+
elif encoding == KeyEncoding.BASE64:
64+
return base64.b64decode(private_key)
65+
else:
66+
raise ValueError(INVALID_KEY_FORMAT.format(encoding))
67+
68+
4769
@app.command()
4870
async def create(
4971
private_key: Annotated[Optional[str], typer.Option(help=help_strings.PRIVATE_KEY)] = None,
@@ -52,6 +74,7 @@ async def create(
5274
replace: Annotated[bool, typer.Option(help=help_strings.CREATE_REPLACE)] = False,
5375
active: Annotated[bool, typer.Option(help=help_strings.CREATE_ACTIVE)] = True,
5476
debug: Annotated[bool, typer.Option()] = False,
77+
key_format: Annotated[KeyEncoding, typer.Option(help="Encoding of the private key")] = KeyEncoding.HEXADECIMAL,
5578
):
5679
"""Create or import a private key."""
5780

@@ -84,7 +107,7 @@ async def create(
84107
if chain == Chain.SOL:
85108
private_key_bytes = parse_solana_private_key(private_key)
86109
else:
87-
private_key_bytes = bytes_from_hex(private_key)
110+
private_key_bytes = decode_private_key(private_key, key_format)
88111
else:
89112
private_key_bytes = generate_key()
90113
if not private_key_bytes:

src/aleph_client/commands/help_strings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,4 @@
7171
AGGREGATE_SECURITY_KEY_PROTECTED = (
7272
"The aggregate key `security` is protected. Use `aleph aggregate [allow|revoke]` to manage it."
7373
)
74+
INVALID_KEY_FORMAT = "Invalid key format: {}"

tests/unit/test_commands.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,76 @@ def test_account_import_evm(env_files):
6767
assert new_key != old_key
6868

6969

70+
def test_account_import_evm_base32(env_files):
71+
settings.CONFIG_FILE = env_files[1]
72+
old_key = env_files[0].read_bytes()
73+
result = runner.invoke(
74+
app,
75+
[
76+
"account",
77+
"create",
78+
"--replace",
79+
"--private-key-file",
80+
str(env_files[0]),
81+
"--chain",
82+
"ETH",
83+
"--private-key",
84+
"JXINYIKE2QOUXCZRAFA2FG4AMYYPEOLS4OIGEZ2WK4WCQDWYSAMQ====",
85+
"--key-format",
86+
"base32",
87+
],
88+
)
89+
assert result.exit_code == 0, result.stdout
90+
new_key = env_files[0].read_bytes()
91+
assert new_key != old_key
92+
93+
94+
def test_account_import_evm_base64(env_files):
95+
settings.CONFIG_FILE = env_files[1]
96+
old_key = env_files[0].read_bytes()
97+
result = runner.invoke(
98+
app,
99+
[
100+
"account",
101+
"create",
102+
"--replace",
103+
"--private-key-file",
104+
str(env_files[0]),
105+
"--chain",
106+
"ETH",
107+
"--private-key",
108+
"TdDcIUTUHUuLMQFBopuAZjDyOXLjkGJnVlcsKA7YkBk=",
109+
"--key-format",
110+
"base64",
111+
],
112+
)
113+
assert result.exit_code == 0, result.stdout
114+
new_key = env_files[0].read_bytes()
115+
assert new_key != old_key
116+
117+
118+
def test_account_import_evm_format_invalid(env_files):
119+
"""Test that an invalid key format raises an error."""
120+
settings.CONFIG_FILE = env_files[1]
121+
result = runner.invoke(
122+
app,
123+
[
124+
"account",
125+
"create",
126+
"--replace",
127+
"--private-key-file",
128+
str(env_files[0]),
129+
"--chain",
130+
"ETH",
131+
"--private-key",
132+
"TdDcIUTUHUuLMQFBopuAZjDyOXLjkGJnVlcsKA7YkBk=",
133+
"--key-format",
134+
"invalid",
135+
],
136+
)
137+
assert result.exit_code != 0, result.stdout
138+
139+
70140
def test_account_import_sol(env_files):
71141
settings.CONFIG_FILE = env_files[1]
72142
old_key = env_files[0].read_bytes()

0 commit comments

Comments
 (0)