1- import codecs
21import itertools
32import logging
3+ import math
4+ import re
45import secrets
56
67from collections import namedtuple
910from shamir_mnemonic import generate_mnemonics
1011
1112from .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
1314from .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+
159194def create (
160195 name : str ,
161196 group_threshold : int ,
@@ -233,35 +268,36 @@ def mnemonics(
233268def 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
254288def 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
267303def 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
317355def 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