Skip to content

Commit a25a4fa

Browse files
committed
Merge branch 'feature-generator'
2 parents 28ca06e + 06eb62e commit a25a4fa

25 files changed

+1663
-539
lines changed

.github/workflows/main.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3+
4+
name: Python package
5+
6+
on:
7+
push:
8+
branches:
9+
- master
10+
- feature-**
11+
- fix-**
12+
pull_request:
13+
branches:
14+
- master
15+
16+
jobs:
17+
build:
18+
19+
runs-on: ubuntu-latest
20+
strategy:
21+
matrix:
22+
# Requires Python3 w/ Type Annotations
23+
python-version: [3.9, 3.x]
24+
25+
steps:
26+
- uses: actions/checkout@v2
27+
- name: Set up Python ${{ matrix.python-version }}
28+
uses: actions/setup-python@v2
29+
with:
30+
python-version: ${{ matrix.python-version }}
31+
- name: Install dependencies
32+
run: |
33+
python3 -m pip install --upgrade pip
34+
python3 -m pip install wheel
35+
if [ -f requirements.txt ]; then python3 -m pip install -r requirements.txt; fi
36+
python3 -m pip install flake8 pytest
37+
- name: Lint with flake8
38+
run: |
39+
make analyze || true
40+
- name: Test with pytest
41+
run: |
42+
make test

GNUmakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ doctest:
3333

3434
analyze:
3535
flake8 -j 1 --max-line-length=200 \
36-
--ignore=E201,E202,E221,E223,E226,E231,E242,E251,E265,E272,E274 \
36+
--ignore=W503,E201,E202,E221,E223,E226,E231,E242,E251,E265,E272,E274 \
3737
slip39
3838

3939
pylint:

README.org

Lines changed: 368 additions & 126 deletions
Large diffs are not rendered by default.

README.pdf

161 KB
Binary file not shown.

images/slip39-pdf.png

145 KB
Loading

requirements-optional.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eth-account

requirements.txt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
shamir-mnemonic
2-
eth-account
3-
hdwallet
1+
chacha20poly1305>=0.0.3
42
fpdf2
3+
hdwallet
4+
mnemonic>=0.19,<1
5+
pyserial>=3.5
56
qrcode
7+
shamir-mnemonic

setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818
console_scripts = [
1919
'slip39 = slip39.main:main',
20-
'slip39-recovery = slip39.recovery.__main__:main',
20+
'slip39-recovery = slip39.recovery.main:main',
21+
'slip39-generator = slip39.generator.main:main',
2122
]
2223

2324
entry_points = {
@@ -30,6 +31,7 @@
3031
package_dir = {
3132
"slip39": "./slip39",
3233
"slip39.recovery": "./slip39/recovery",
34+
"slip39.generator": "./slip39/generator",
3335
}
3436

3537
long_description_content_type = 'text/markdown'
@@ -141,5 +143,5 @@
141143
keywords = "Ethereum cryptocurrency SLIP39 BIP39 seed recovery",
142144
url = "https://github.com/pjkundert/python-slip39",
143145
classifiers = classifiers,
144-
python_requires = ">=3.6",
146+
python_requires = ">=3.9",
145147
)

slip39/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,3 @@
22
from .types import * # noqa F403
33
from .api import * # noqa F403
44
from .recovery import * # noqa F403
5-
from .main import * # noqa F403

slip39/api.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import codecs
21
import itertools
32
import logging
3+
import math
4+
import re
45
import secrets
56

67
from collections import namedtuple
@@ -9,7 +10,7 @@
910
from shamir_mnemonic import generate_mnemonics
1011

1112
from .types import Account
12-
from .defaults import BITS_DEFAULT, BITS, MNEM_ROWS_COLS, cryptocurrency_supported
13+
from .defaults import BITS_DEFAULT, BITS, MNEM_ROWS_COLS, GROUP_REQUIRED_RATIO
1314
from .util import ordinal
1415

1516

@@ -22,7 +23,10 @@ def path_parser(
2223
paths: str,
2324
allow_unbounded: bool = True,
2425
) -> Tuple[str, Dict[str, Callable[[], int]]]:
25-
"""Create a format and a dictionary of iterators to feed into it."""
26+
"""Create a format and a dictionary of iterators to feed into it.
27+
28+
Supports paths with an arbitrary prefix, eg. 'm/' or '.../'
29+
"""
2630
path_segs = paths.split( '/' )
2731
unbounded = False
2832
ranges = {}
@@ -156,6 +160,37 @@ def organize_mnemonic( mnemonic, rows=None, cols=None, label="" ):
156160
yield line,words
157161

158162

163+
def group_parser( group_spec ):
164+
"""Parse a SLIP-39 group specification.
165+
166+
Fren6, Fren 6, Fren(6) - A 3/6 group (default is 1/2 of group size, rounded up)
167+
Fren2/6, Fren(2/6) - A 2/6 group
168+
169+
"""
170+
match = group_parser.RE.match( group_spec )
171+
if not match:
172+
raise ValueError( f"Invalid group specification: {group_spec!r}" )
173+
name = match.group( 'name' )
174+
size = match.group( 'size' )
175+
require = match.group( 'require' )
176+
if not size:
177+
size = 1
178+
if not require:
179+
# eg. default 2/4, 3/5
180+
require = math.ceil( int( size ) * GROUP_REQUIRED_RATIO )
181+
return name,(int(require),int(size))
182+
group_parser.RE = re.compile( # noqa E305
183+
r"""^
184+
\s*
185+
(?P<name> [^\d\(/]+ )
186+
\s*\(?\s*
187+
(:? (?P<require> \d* ) \s* / )?
188+
\s*
189+
(?P<size> \d* )
190+
\s*\)?\s*
191+
$""", re.VERBOSE )
192+
193+
159194
def create(
160195
name: str,
161196
group_threshold: int,
@@ -233,35 +268,36 @@ def mnemonics(
233268
def account(
234269
master_secret: Union[str,bytes],
235270
crypto: str = None, # default 'ETH'
236-
path: str = None, # default to the crypto's DEFAULT_PATH
271+
path: str = None, # default to the crypto's path_default
272+
format: str = None, # eg. 'bech32', or use the default address format for the crypto
237273
):
238274
"""Generate an HD wallet Account from the supplied master_secret seed, at the given HD derivation
239275
path, for the specified cryptocurrency.
240276
241277
"""
242-
if type( master_secret ) is bytes:
243-
master_secret = codecs.encode( master_secret, 'hex_codec' ).decode( 'ascii' )
244278
acct = Account(
245-
symbol = cryptocurrency_supported( crypto or 'ETH' )
279+
crypto = crypto or 'ETH',
280+
format = format,
246281
).from_seed(
247-
seed = master_secret
248-
)
249-
return acct.from_path(
250-
path = path or acct._cryptocurrency.DEFAULT_PATH
282+
seed = master_secret,
283+
path = path,
251284
)
285+
return acct
252286

253287

254288
def accounts(
255289
master_secret: Union[str,bytes],
256290
crypto: str = None, # default 'ETH'
257-
paths: str = None, # default to the crypto's DEFAULT_PATH; allow ranges
291+
paths: str = None, # default to the crypto's path_default; allow ranges
292+
format: str = None,
258293
allow_unbounded = True,
259294
):
295+
"""Create accounts for crypto, at the provided paths (allowing ranges), with the optionsal address format. """
260296
for path in [None] if paths is None else path_sequence( *path_parser(
261297
paths = paths,
262298
allow_unbounded = allow_unbounded,
263299
)):
264-
yield account( master_secret, crypto, path )
300+
yield account( master_secret, crypto=crypto, path=path, format=format )
265301

266302

267303
def accountgroups(
@@ -305,27 +341,30 @@ def address(
305341
master_secret: bytes,
306342
crypto: str = None,
307343
path: str = None,
344+
format: str = None,
308345
):
309346
"""Return the specified cryptocurrency HD account address at path."""
310347
return account(
311348
master_secret,
312349
path = path,
313-
crypto = cryptocurrency_supported( crypto or 'ETH' ),
350+
crypto = crypto,
351+
format = format,
314352
).address
315353

316354

317355
def addresses(
318356
master_secret: bytes,
319357
crypto: str = None, # default 'ETH'
320-
paths: str = None, # default: The crypto's DEFAULT_PATH; supports ranges
358+
paths: str = None, # default: The crypto's path_default; supports ranges
359+
format: str = None,
321360
allow_unbounded: bool = True,
322361
):
323362
"""Generate a sequence of cryptocurrency account (path, address, ...) for all designated
324363
cryptocurrencies. Usually a single (<path>, <address>) tuple is desired (different
325364
cryptocurrencies typically have their own unique path derivations.
326365
327366
"""
328-
for acct in accounts( master_secret, crypto, paths, allow_unbounded=allow_unbounded ):
367+
for acct in accounts( master_secret, crypto, paths, format, allow_unbounded=allow_unbounded ):
329368
yield (acct.crypto, acct.path, acct.address)
330369

331370

0 commit comments

Comments
 (0)