Skip to content

Commit d7b9a3b

Browse files
authored
Merge pull request #322 from input-output-hk/add_key_mnemonic
feat(key): add mnemonic generation and key derivation
2 parents d287832 + 96325f9 commit d7b9a3b

File tree

4 files changed

+131
-2
lines changed

4 files changed

+131
-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: 109 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,112 @@ 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+
key_number_err = f"`key_number` must be specified when key_type is '{key_type.value}'"
158+
if key_type == consts.KeyType.DREP:
159+
key_args.append("--drep-key")
160+
elif key_type == consts.KeyType.CC_COLD:
161+
key_args.append("--cc-cold-key")
162+
elif key_type == consts.KeyType.CC_HOT:
163+
key_args.append("--cc-hot-key")
164+
elif key_type == consts.KeyType.PAYMENT:
165+
if key_number is None:
166+
raise ValueError(key_number_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+
raise ValueError(key_number_err)
171+
key_args.extend(["--stake-key-with-number", str(key_number)])
172+
else:
173+
err = f"Unsupported key_type: {key_type}"
174+
raise ValueError(err)
175+
176+
self._clusterlib_obj.cli(
177+
[
178+
"key",
179+
"derive-from-mnemonic",
180+
f"--key-output-{out_format.value}",
181+
*key_args,
182+
"--account-number",
183+
str(account_number),
184+
"--mnemonic-from-file",
185+
str(mnemonic_file),
186+
"--signing-key-file",
187+
str(out_file),
188+
]
189+
)
190+
191+
helpers._check_outfiles(out_file)
192+
return out_file
193+
85194
def __repr__(self) -> str:
86195
return f"<{self.__class__.__name__}: clusterlib_obj={id(self._clusterlib_obj)}>"

0 commit comments

Comments
 (0)