Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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).",
)
@click.option(
"-p",
"--passphrase",
default="",
type=str,
help="Optional passphrase for seed derivation.",
)
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()}")


@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)


@cli.command("to-seed")
@click.option(
"-p",
"--passphrase",
default="",
type=str,
help="Optional passphrase for seed derivation.",
)
@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())


if __name__ == "__main__":
cli()