Skip to content

Commit 7c37cf2

Browse files
committed
feat: add command-line interface with create, check, and to-seed commands
- Add CLI entry point using Click - Add Click as runtime dependency Signed-off-by: Willian Paixao <willian@ufpa.br>
1 parent b81ade2 commit 7c37cf2

5 files changed

Lines changed: 129 additions & 6 deletions

File tree

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
python-version: ${{ matrix.python-version }}
2323
- name: Install package
2424
run: |
25-
python -m pip install --upgrade build pip setuptools
25+
python -m pip install --upgrade build pip setuptools twine
2626
python -m pip install .
2727
- name: Run test
2828
run: |

CHANGELOG.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ All notable changes to this project will be documented in this file.
88
The format is based on `Keep a Changelog`_, and this project adheres to
99
`Semantic Versioning`_.
1010

11+
`0.22`_ - Unreleased
12+
--------------------
13+
14+
.. _0.22: https://github.com/trezor/python-mnemonic/compare/v0.21...HEAD
15+
16+
Added
17+
~~~~~
18+
19+
- Command-line interface with ``create``, ``check``, and ``to-seed`` commands
20+
- Click as a runtime dependency
21+
1122
`0.21`_ - 2024-01-05
1223
--------------------
1324

README.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ python-mnemonic
77
Reference implementation of BIP-0039: Mnemonic code for generating
88
deterministic keys
99

10+
Maintained by `Trezor <https://trezor.io>`_. See the `GitHub repository <https://github.com/trezor/python-mnemonic>`_ for source code and issue tracking.
11+
1012
Abstract
1113
--------
1214

@@ -77,4 +79,36 @@ Given the word list, calculate original entropy:
7779
7880
entropy = mnemo.to_entropy(words)
7981
82+
Command-line interface
83+
----------------------
84+
85+
The ``mnemonic`` command provides CLI access to the library:
86+
87+
.. code-block:: sh
88+
89+
$ mnemonic create --help
90+
$ mnemonic check --help
91+
$ mnemonic to-seed --help
92+
93+
Generate a new mnemonic phrase:
94+
95+
.. code-block:: sh
96+
97+
$ mnemonic create
98+
$ mnemonic create -s 256 -l english -p "my passphrase"
99+
100+
Validate a mnemonic phrase:
101+
102+
.. code-block:: sh
103+
104+
$ mnemonic check abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
105+
$ echo "abandon abandon ..." | mnemonic check
106+
107+
Derive seed from a mnemonic phrase:
108+
109+
.. code-block:: sh
110+
111+
$ mnemonic to-seed abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
112+
$ mnemonic to-seed -p "my passphrase" word1 word2 ...
113+
80114
.. _BIP-0039: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ include = [
1818

1919
[tool.poetry.dependencies]
2020
python = ">=3.8.1"
21+
click = "^8.0"
22+
23+
[tool.poetry.scripts]
24+
mnemonic = "mnemonic.cli:cli"
2125

2226
[tool.poetry.group.dev.dependencies]
2327
isort = "^5.13.2"

src/mnemonic/cli.py

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import sys
2+
13
import click
24

35
from mnemonic import Mnemonic
46

57

68
@click.group()
79
def cli() -> None:
10+
"""BIP-39 mnemonic phrase generator and validator."""
811
pass
912

1013

@@ -14,29 +17,100 @@ def cli() -> None:
1417
"--language",
1518
default="english",
1619
type=str,
17-
help="",
20+
help="Language for the mnemonic wordlist.",
1821
)
1922
@click.option(
2023
"-s",
2124
"--strength",
2225
default=128,
2326
type=int,
24-
help="",
27+
help="Entropy strength in bits (128, 160, 192, 224, or 256).",
28+
)
29+
@click.option(
30+
"-p",
31+
"--passphrase",
32+
default="",
33+
type=str,
34+
help="Optional passphrase for seed derivation.",
2535
)
26-
@click.option("-p", "--passphrase", default="", type=str, help="")
2736
def create(
2837
language: str,
2938
passphrase: str,
3039
strength: int,
3140
) -> None:
32-
""" """
41+
"""Generate a new mnemonic phrase and its derived seed."""
3342
mnemo = Mnemonic(language)
3443
words = mnemo.generate(strength)
3544
seed = mnemo.to_seed(words, passphrase)
36-
click.secho("SUCCESS!", fg="green", bold=True)
3745
click.echo(f"Mnemonic: {words}")
3846
click.echo(f"Seed: {seed.hex()}")
3947

4048

49+
@cli.command()
50+
@click.option(
51+
"-l",
52+
"--language",
53+
default=None,
54+
type=str,
55+
help="Language for the mnemonic wordlist. Auto-detected if not specified.",
56+
)
57+
@click.argument("words", nargs=-1)
58+
def check(language: str | None, words: tuple[str, ...]) -> None:
59+
"""Validate a mnemonic phrase's checksum.
60+
61+
WORDS can be provided as arguments or piped via stdin.
62+
"""
63+
if words:
64+
mnemonic = " ".join(words)
65+
else:
66+
mnemonic = sys.stdin.read().strip()
67+
68+
if not mnemonic:
69+
click.secho("Error: No mnemonic provided.", fg="red", err=True)
70+
sys.exit(1)
71+
72+
try:
73+
if language is None:
74+
language = Mnemonic.detect_language(mnemonic)
75+
mnemo = Mnemonic(language)
76+
if mnemo.check(mnemonic):
77+
click.secho("Valid mnemonic.", fg="green")
78+
sys.exit(0)
79+
else:
80+
click.secho("Invalid mnemonic checksum.", fg="red", err=True)
81+
sys.exit(1)
82+
except Exception as e:
83+
click.secho(f"Error: {e}", fg="red", err=True)
84+
sys.exit(1)
85+
86+
87+
@cli.command("to-seed")
88+
@click.option(
89+
"-p",
90+
"--passphrase",
91+
default="",
92+
type=str,
93+
help="Optional passphrase for seed derivation.",
94+
)
95+
@click.argument("words", nargs=-1)
96+
def to_seed(passphrase: str, words: tuple[str, ...]) -> None:
97+
"""Derive a seed from a mnemonic phrase.
98+
99+
WORDS can be provided as arguments or piped via stdin.
100+
Outputs the 64-byte seed in hexadecimal format.
101+
"""
102+
if words:
103+
mnemonic = " ".join(words)
104+
else:
105+
mnemonic = sys.stdin.read().strip()
106+
107+
if not mnemonic:
108+
click.secho("Error: No mnemonic provided.", fg="red", err=True)
109+
sys.exit(1)
110+
111+
seed = Mnemonic.to_seed(mnemonic, passphrase)
112+
click.echo(seed.hex())
113+
114+
41115
if __name__ == "__main__":
42116
cli()

0 commit comments

Comments
 (0)