Skip to content
Open
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
9 changes: 7 additions & 2 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install package
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade setuptools
python -m pip install --upgrade build pip setuptools twine
python -m pip install .
- name: Run test
run: |
python tests/test_mnemonic.py
- name: Build wheel and sdist
run: |
python -m build
- name: Check long description
run: |
twine check dist/*
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog`_, and this project adheres to
`Semantic Versioning`_.

`0.22`_ - Unreleased
--------------------

.. _0.22: https://github.com/trezor/python-mnemonic/compare/v0.21...HEAD

Added
~~~~~

- Command-line interface with ``create``, ``check``, and ``to-seed`` commands
- Click as a runtime dependency

`0.21`_ - 2024-01-05
--------------------

Expand Down
45 changes: 41 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,31 @@ python-mnemonic
Reference implementation of BIP-0039: Mnemonic code for generating
deterministic keys

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

Abstract
--------

This BIP describes the implementation of a mnemonic code or mnemonic sentence --
a group of easy to remember words -- for the generation of deterministic wallets.

It consists of two parts: generating the mnenomic, and converting it into a
It consists of two parts: generating the mnemonic, and converting it into a
binary seed. This seed can be later used to generate deterministic wallets using
BIP-0032 or similar methods.

BIP Paper
---------

See https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
for full specification
See `BIP-0039`_ for the full specification.

Installation
------------

To install this library and its dependencies use:

``pip install mnemonic``
.. code-block:: sh

$ pip install mnemonic

Usage examples
--------------
Expand Down Expand Up @@ -75,3 +78,37 @@ Given the word list, calculate original entropy:
.. code-block:: python

entropy = mnemo.to_entropy(words)

Command-line interface
----------------------

The ``mnemonic`` command provides CLI access to the library:

.. code-block:: sh

$ mnemonic create --help
$ mnemonic check --help
$ mnemonic to-seed --help

Generate a new mnemonic phrase:

.. code-block:: sh

$ mnemonic create
$ mnemonic create -s 256 -l english -p "my passphrase"

Validate a mnemonic phrase:

.. code-block:: sh

$ mnemonic check abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
$ echo "abandon abandon ..." | mnemonic check

Derive seed from a mnemonic phrase:

.. code-block:: sh

$ mnemonic to-seed abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
$ mnemonic to-seed -p "my passphrase" word1 word2 ...

.. _BIP-0039: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ include = [

[tool.poetry.dependencies]
python = ">=3.8.1"
click = "^8.0"

[tool.poetry.scripts]
mnemonic = "mnemonic.cli:cli"

[tool.poetry.group.dev.dependencies]
isort = "^5.13.2"
Expand Down
116 changes: 116 additions & 0 deletions src/mnemonic/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import sys

import click

from mnemonic import Mnemonic


@click.group()
def cli() -> None:
"""BIP-39 mnemonic phrase generator and validator."""
pass


@cli.command()
@click.option(
"-l",
"--language",
default="english",
type=str,
help="Language for the mnemonic wordlist.",
)
@click.option(
"-s",
"--strength",
default=128,
type=int,
help="Entropy strength in bits (128, 160, 192, 224, or 256).",
)
Comment on lines +22 to +28
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The strength parameter should use click.Choice to restrict values to the valid options [128, 160, 192, 224, 256] instead of accepting any integer. This would provide better user experience by showing valid options in the help text and catching invalid values before calling the API.

Copilot uses AI. Check for mistakes.
@click.option(
"-p",
"--passphrase",
default="",
type=str,
help="Optional passphrase for seed derivation.",
)
Comment on lines +29 to +35
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The passphrase parameter is passed via command-line option, which means it will be visible in shell history and process listings. This poses a security risk as sensitive passphrases could be exposed. Consider adding support for reading the passphrase from stdin or an environment variable as a more secure alternative.

Copilot uses AI. Check for mistakes.
def create(
language: str,
passphrase: str,
strength: int,
) -> None:
"""Generate a new mnemonic phrase and its derived seed."""
mnemo = Mnemonic(language)
words = mnemo.generate(strength)
seed = mnemo.to_seed(words, passphrase)
click.echo(f"Mnemonic: {words}")
click.echo(f"Seed: {seed.hex()}")
Comment on lines +42 to +46
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The create command lacks error handling for invalid language or strength values. If an invalid language is provided, the Mnemonic constructor will raise a ConfigurationError. If an invalid strength value is provided (not in [128, 160, 192, 224, 256]), the generate method will raise a ValueError. Consider wrapping these calls in a try-except block to provide clear, user-friendly error messages.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +46
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new CLI functionality (create command) lacks test coverage. The repository has comprehensive automated testing for the Mnemonic class, so similar test coverage should be added for the CLI commands. Consider adding tests to verify the command executes successfully, generates valid mnemonics, and properly handles edge cases like invalid strength or language values.

Copilot uses AI. Check for mistakes.


@cli.command()
@click.option(
"-l",
"--language",
default=None,
type=str,
help="Language for the mnemonic wordlist. Auto-detected if not specified.",
)
@click.argument("words", nargs=-1)
def check(language: str | None, words: tuple[str, ...]) -> None:
"""Validate a mnemonic phrase's checksum.

WORDS can be provided as arguments or piped via stdin.
"""
if words:
mnemonic = " ".join(words)
else:
mnemonic = sys.stdin.read().strip()

if not mnemonic:
click.secho("Error: No mnemonic provided.", fg="red", err=True)
sys.exit(1)

try:
if language is None:
language = Mnemonic.detect_language(mnemonic)
mnemo = Mnemonic(language)
if mnemo.check(mnemonic):
click.secho("Valid mnemonic.", fg="green")
sys.exit(0)
else:
click.secho("Invalid mnemonic checksum.", fg="red", err=True)
sys.exit(1)
except Exception as e:
click.secho(f"Error: {e}", fg="red", err=True)
sys.exit(1)
Comment on lines +82 to +84
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The broad exception handler catches all exceptions and exits with code 1, which makes it difficult to distinguish between different error types (e.g., invalid language, language detection failure, ConfigurationError). Consider catching specific exceptions like ConfigurationError or ValueError separately to provide more helpful error messages to users.

Suggested change
except Exception as e:
click.secho(f"Error: {e}", fg="red", err=True)
sys.exit(1)
except ValueError as e:
# Handle expected user-related errors (e.g., invalid language or mnemonic format)
click.secho(f"Error: {e}", fg="red", err=True)
sys.exit(1)
except Exception as e:
# Handle unexpected internal errors separately with a distinct exit code
click.secho(f"Unexpected error: {e}", fg="red", err=True)
sys.exit(2)

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +84
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new CLI functionality (check command) lacks test coverage. The repository has comprehensive automated testing for the Mnemonic class, so similar test coverage should be added for the CLI commands. Consider adding tests to verify the command correctly validates both valid and invalid mnemonics, handles stdin input, and properly detects language when not specified.

Copilot uses AI. Check for mistakes.


@cli.command("to-seed")
@click.option(
"-p",
"--passphrase",
default="",
type=str,
help="Optional passphrase for seed derivation.",
)
Comment on lines +88 to +94
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The passphrase parameter is passed via command-line option, which means it will be visible in shell history and process listings. This poses a security risk as sensitive passphrases could be exposed. Consider adding support for reading the passphrase from stdin or an environment variable as a more secure alternative.

Copilot uses AI. Check for mistakes.
@click.argument("words", nargs=-1)
def to_seed(passphrase: str, words: tuple[str, ...]) -> None:
"""Derive a seed from a mnemonic phrase.

WORDS can be provided as arguments or piped via stdin.
Outputs the 64-byte seed in hexadecimal format.
"""
if words:
mnemonic = " ".join(words)
else:
mnemonic = sys.stdin.read().strip()

if not mnemonic:
click.secho("Error: No mnemonic provided.", fg="red", err=True)
sys.exit(1)

seed = Mnemonic.to_seed(mnemonic, passphrase)
click.echo(seed.hex())
Comment on lines +87 to +112
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new CLI functionality (to-seed command) lacks test coverage. The repository has comprehensive automated testing for the Mnemonic class, so similar test coverage should be added for the CLI commands. Consider adding tests to verify the command correctly derives seeds, handles stdin input, and applies the passphrase parameter correctly.

Copilot uses AI. Check for mistakes.


Comment on lines +111 to +114
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The to_seed command lacks error handling for invalid mnemonics. If the mnemonic is invalid or the language cannot be detected, the Mnemonic.to_seed() call may fail or produce unexpected results. Consider adding validation using Mnemonic.check() or wrapping the to_seed call in a try-except block to provide clear error messages to users.

Suggested change
seed = Mnemonic.to_seed(mnemonic, passphrase)
click.echo(seed.hex())
try:
# Auto-detect language and validate mnemonic before deriving seed
language = Mnemonic.detect_language(mnemonic)
mnemo = Mnemonic(language)
if not mnemo.check(mnemonic):
click.secho("Invalid mnemonic checksum.", fg="red", err=True)
sys.exit(1)
seed = mnemo.to_seed(mnemonic, passphrase)
click.echo(seed.hex())
except Exception as e:
click.secho(f"Error: {e}", fg="red", err=True)
sys.exit(1)

Copilot uses AI. Check for mistakes.
if __name__ == "__main__":
cli()