diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3c881c5..f4e5a29 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,13 +8,36 @@ 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`_. -`Unreleased`_ -------------- +`0.3.0`_ - 2024-05-15 +--------------------- + +Incompatible +~~~~~~~~~~~~ + +- The `shamir` command no longer works out of the box. It is necessary to install the + `cli` extra while installing the package. See README for instructions. + +Added +~~~~~ - Added BIP32 master extended private key to test vectors. - Added support for extendable backup flag. -.. _Unreleased: https://github.com/trezor/python-shamir-mnemonic/compare/v0.2.2...HEAD +Changed +~~~~~~~ + +- The `shamir_mnemonic` package now has zero extra dependencies on Python 3.7 and up, + making it more suitable as a dependency of other projects. +- The `shamir` CLI still requires `click`. A new extra `cli` was introduced to handle + this dependency. Use the command `pip install shamir-mnemonic[cli]` to install the CLI + dependencies along with the package. + +Removed +~~~~~~~ + +- Removed dependency on `attrs`. + +.. _0.3.0: https://github.com/trezor/python-shamir-mnemonic/compare/v0.2.2...v0.3.0 `0.2.2`_ - 2021-12-07 diff --git a/README.rst b/README.rst index 0566396..23c549f 100644 --- a/README.rst +++ b/README.rst @@ -35,11 +35,11 @@ not be used for handling sensitive secrets**. Installation ------------ -With pip from GitHub: +With pip from PyPI: .. code-block:: console - $ pip3 install shamir-mnemonic + $ pip3 install shamir-mnemonic[cli] # for CLI tool From local checkout for development: diff --git a/generate_vectors.py b/generate_vectors.py index 48c00a9..4e3261b 100755 --- a/generate_vectors.py +++ b/generate_vectors.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 import json import random +from dataclasses import astuple -import attr from bip32utils import BIP32Key from shamir_mnemonic import constants, rs1024, shamir, wordlist @@ -24,7 +24,7 @@ def encode_mnemonic(*args): def decode_mnemonic(mnemonic): - return list(attr.astuple(Share.from_mnemonic(mnemonic))) + return list(astuple(Share.from_mnemonic(mnemonic))) def generate_mnemonics_random(group_threshold, groups): diff --git a/pyproject.toml b/pyproject.toml index d1dbd91..a65d0ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "shamir-mnemonic" -version = "0.2.3" +version = "0.3.0" description = "SLIP-39 Shamir Mnemonics" authors = ["Trezor "] license = "MIT" @@ -11,14 +11,18 @@ readme = [ [tool.poetry.dependencies] python = ">=3.6,<4.0" -click = ">=7,<9" -bip32utils = "^0.3.post4" -pytest = "*" +dataclasses = { version = "*", python = "<=3.6" } +click = { version = ">=7,<9", optional = true } [tool.poetry.group.dev.dependencies] +bip32utils = "^0.3.post4" +pytest = "*" black = ">=20" isort = "^5" +[tool.poetry.extras] +cli = ["click"] + [tool.poetry.scripts] shamir = "shamir_mnemonic.cli:cli" diff --git a/shamir_mnemonic/cli.py b/shamir_mnemonic/cli.py index 5a75ea4..930f83f 100644 --- a/shamir_mnemonic/cli.py +++ b/shamir_mnemonic/cli.py @@ -2,8 +2,14 @@ import sys from typing import Sequence, Tuple -import click -from click import style +try: + import click + from click import style + +except ImportError: + print("Required dependencies are missing. Install them with:") + print(" pip install shamir_mnemonic[cli]") + sys.exit(1) from .recovery import RecoveryState from .shamir import generate_mnemonics diff --git a/shamir_mnemonic/recovery.py b/shamir_mnemonic/recovery.py index 20e89f1..6ae9ab2 100644 --- a/shamir_mnemonic/recovery.py +++ b/shamir_mnemonic/recovery.py @@ -1,8 +1,7 @@ from collections import defaultdict +from dataclasses import dataclass, field, replace from typing import Any, Dict, Optional, Tuple -import attr - from .constants import GROUP_PREFIX_LENGTH_WORDS from .shamir import ShareGroup, recover_ems from .share import Share, ShareCommonParameters @@ -24,7 +23,7 @@ def group_prefix(self, group_index: int) -> str: if not self.last_share: raise RuntimeError("Add at least one share first") - fake_share = attr.evolve(self.last_share, group_index=group_index) + fake_share = replace(self.last_share, group_index=group_index) return " ".join(fake_share.words()[:GROUP_PREFIX_LENGTH_WORDS]) def group_status(self, group_index: int) -> Tuple[int, int]: diff --git a/shamir_mnemonic/shamir.py b/shamir_mnemonic/shamir.py index c140b1a..d47e6a4 100644 --- a/shamir_mnemonic/shamir.py +++ b/shamir_mnemonic/shamir.py @@ -21,10 +21,9 @@ import hmac import secrets +from dataclasses import dataclass from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Sequence, Set, Tuple -import attr - from . import cipher from .constants import ( DIGEST_INDEX, @@ -101,7 +100,7 @@ def is_complete(self) -> bool: return False -@attr.s(auto_attribs=True, frozen=True) +@dataclass(frozen=True) class EncryptedMasterSecret: identifier: int extendable: bool diff --git a/shamir_mnemonic/share.py b/shamir_mnemonic/share.py index 383de09..8959add 100644 --- a/shamir_mnemonic/share.py +++ b/shamir_mnemonic/share.py @@ -1,7 +1,6 @@ +from dataclasses import dataclass from typing import Iterable, List, NamedTuple -import attr - from . import rs1024, wordlist from .constants import ( CUSTOMIZATION_STRING_EXTENDABLE, @@ -61,7 +60,7 @@ class ShareGroupParameters(NamedTuple): member_threshold: int -@attr.s(auto_attribs=True, frozen=True) +@dataclass(frozen=True) class Share: """Represents a single mnemonic share and its metadata"""