Skip to content

Commit 90e67f6

Browse files
committed
feat(key): add mnemonic generation and key derivation
Add support for generating mnemonics and deriving extended signing keys from mnemonic sentences. Introduce KeyType and OutputFormat enums. Refactor helpers for file reading. Update KeyGroup with new methods for mnemonic and key derivation.
1 parent d287832 commit 90e67f6

File tree

4 files changed

+132
-2
lines changed

4 files changed

+132
-2
lines changed

cardano_clusterlib/clusterlib.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
from cardano_clusterlib.consts import CommandEras
99
from cardano_clusterlib.consts import DEFAULT_COIN
1010
from cardano_clusterlib.consts import Eras
11+
from cardano_clusterlib.consts import KeyType
1112
from cardano_clusterlib.consts import MAINNET_MAGIC
1213
from cardano_clusterlib.consts import MultiSigTypeArgs
1314
from cardano_clusterlib.consts import MultiSlotTypeArgs
15+
from cardano_clusterlib.consts import OutputFormat
1416
from cardano_clusterlib.consts import ScriptTypes
1517
from cardano_clusterlib.consts import Votes
1618
from cardano_clusterlib.exceptions import CLIError

cardano_clusterlib/consts.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,16 @@ class Votes(enum.Enum):
5353
YES = 1
5454
NO = 2
5555
ABSTAIN = 3
56+
57+
58+
class KeyType(enum.Enum):
59+
PAYMENT = "payment"
60+
STAKE = "stake"
61+
DREP = "drep"
62+
CC_COLD = "cc-cold"
63+
CC_HOT = "cc-hot"
64+
65+
66+
class OutputFormat(enum.Enum):
67+
TEXT_ENVELOPE = "text-envelope"
68+
BECH32 = "bech32"

cardano_clusterlib/helpers.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ def get_rand_str(length: int = 8) -> str:
1414
return "".join(random.choice(string.ascii_lowercase) for i in range(length))
1515

1616

17+
def read_from_file(file: itp.FileType) -> str:
18+
"""Read file content."""
19+
with open(pl.Path(file).expanduser(), encoding="utf-8") as in_file:
20+
return in_file.read()
21+
22+
1723
def read_address_from_file(addr_file: itp.FileType) -> str:
1824
"""Read address stored in file."""
19-
with open(pl.Path(addr_file).expanduser(), encoding="utf-8") as in_file:
20-
return in_file.read().strip()
25+
return read_from_file(file=addr_file).strip()
2126

2227

2328
def _prepend_flag(flag: str, contents: itp.UnpackableSequence) -> list[str]:

cardano_clusterlib/key_group.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import logging
44
import pathlib as pl
5+
import typing as tp
56

67
from cardano_clusterlib import clusterlib_helpers
8+
from cardano_clusterlib import consts
79
from cardano_clusterlib import helpers
810
from cardano_clusterlib import types as itp
911

@@ -82,5 +84,113 @@ def gen_non_extended_verification_key(
8284
helpers._check_outfiles(out_file)
8385
return out_file
8486

87+
def gen_mnemonic(
88+
self,
89+
size: tp.Literal[12, 15, 18, 21, 24],
90+
out_file: itp.FileType = "",
91+
) -> list[str]:
92+
"""Generate a mnemonic sentence that can be used for key derivation.
93+
94+
Args:
95+
size: Number of words in the mnemonic (12, 15, 18, 21, or 24).
96+
out_file: A path to a file where the mnemonic will be stored (optional).
97+
98+
Returns:
99+
list[str]: A list of words in the generated mnemonic.
100+
"""
101+
out_args = []
102+
if out_file:
103+
clusterlib_helpers._check_files_exist(out_file, clusterlib_obj=self._clusterlib_obj)
104+
out_args = ["--out-file", str(out_file)]
105+
106+
out = (
107+
self._clusterlib_obj.cli(
108+
[
109+
"key",
110+
"generate-mnemonic",
111+
*out_args,
112+
"--size",
113+
str(size),
114+
]
115+
)
116+
.stdout.strip()
117+
.decode("ascii")
118+
)
119+
120+
if out_file:
121+
helpers._check_outfiles(out_file)
122+
words = helpers.read_from_file(file=out_file).strip().split()
123+
else:
124+
words = out.split()
125+
126+
return words
127+
128+
def derive_from_mnemonic(
129+
self,
130+
key_name: str,
131+
key_type: consts.KeyType,
132+
mnemonic_file: itp.FileType,
133+
account_number: int = 0,
134+
key_number: int | None = None,
135+
out_format: consts.OutputFormat = consts.OutputFormat.TEXT_ENVELOPE,
136+
destination_dir: itp.FileType = ".",
137+
) -> pl.Path:
138+
"""Derive an extended signing key from a mnemonic sentence.
139+
140+
Args:
141+
key_name: A name of the key.
142+
key_type: A type of the key.
143+
mnemonic_file: A path to a file containing the mnemonic sentence.
144+
account_number: An account number (default is 0).
145+
key_number: A key number (optional, required for payment and stake keys).
146+
out_format: An output format (default is text-envelope).
147+
destination_dir: A path to directory for storing artifacts (optional).
148+
149+
Returns:
150+
Path: A path to the generated extended signing key file.
151+
"""
152+
destination_dir = pl.Path(destination_dir).expanduser()
153+
out_file = destination_dir / f"{key_name}.skey"
154+
clusterlib_helpers._check_files_exist(out_file, clusterlib_obj=self._clusterlib_obj)
155+
156+
key_args = []
157+
if key_type == consts.KeyType.DREP:
158+
key_args.append("--drep-key")
159+
elif key_type == consts.KeyType.CC_COLD:
160+
key_args.append("--cc-cold-key")
161+
elif key_type == consts.KeyType.CC_HOT:
162+
key_args.append("--cc-hot-key")
163+
elif key_type == consts.KeyType.PAYMENT:
164+
if key_number is None:
165+
err = "`key_number` must be specified when key_type is 'payment'"
166+
raise ValueError(err)
167+
key_args.extend(["--payment-key-with-number", str(key_number)])
168+
elif key_type == consts.KeyType.STAKE:
169+
if key_number is None:
170+
err = "`key_number` must be specified when key_type is 'stake'"
171+
raise ValueError(err)
172+
key_args.extend(["--stake-key-with-number", str(key_number)])
173+
else:
174+
err = f"Unsupported key_type: {key_type}"
175+
raise ValueError(err)
176+
177+
self._clusterlib_obj.cli(
178+
[
179+
"key",
180+
"derive-from-mnemonic",
181+
f"--key-output-{out_format.value}",
182+
*key_args,
183+
"--account-number",
184+
str(account_number),
185+
"--mnemonic-from-file",
186+
str(mnemonic_file),
187+
"--signing-key-file",
188+
str(out_file),
189+
]
190+
)
191+
192+
helpers._check_outfiles(out_file)
193+
return out_file
194+
85195
def __repr__(self) -> str:
86196
return f"<{self.__class__.__name__}: clusterlib_obj={id(self._clusterlib_obj)}>"

0 commit comments

Comments
 (0)