Repository navigation
Conversation
stdiomask was only used for masked input on Linux and macOS; the Windows path already had its own mask loop. Handle the POSIX side in the CLI as well and drop the dependency. Add _getpass_posix to helper/security.py: the terminal is switched to cbreak mode, typed characters are echoed as the mask character, and Ctrl+C still raises KeyboardInterrupt. Input is decoded incrementally, so multi-byte UTF-8 characters in a BIP39 passphrase are kept and count as one entry for backspace. Only ASCII control characters are left out; separators and format characters such as the ideographic space an input method produces are part of a passphrase and change the wallet it derives. When stdin is not a terminal the line is read with input(). The PIV PIN prompt now uses the same helper instead of importing stdiomask directly. The import is local to the method because helper.security imports user_keys, which loads the PIV module. Drop stdiomask from setup.cfg, Pipfile, requirements.txt, dev-requirements.txt, the Sphinx autodoc mock list and .pylintrc. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main.py declared six modules through lazy-import. Five of them were already imported by the time those lines ran, so only web3 was ever deferred, and the deferral was lost as soon as a command was dispatched: the command factory imports every command module, and several of them imported web3 at the top. Commands with nothing to do with Ethereum paid for it anyway. Import web3 inside the functions that use it instead, along with eth_account, eth_utils and hexbytes in the Ethereum wallet API, and drop the dependency. In api.py the Web3 annotation becomes a string under TYPE_CHECKING so the module still type checks without importing it. Measured here, no card attached: history and info go from about 1.1 seconds to about 0.3, and neither loads web3 at all where before both loaded all 98 of its modules. Output is unchanged for --help, --version, history, info, cards and the per-command help. Remove the lazy_import VERSION data file and hidden import from setup.spec, along with the import of os that only served them. The six hidden import stays: ecdsa needs it as well. Drop lazy-import from setup.cfg, Pipfile, requirements.txt, dev-requirements.txt and .pylintrc. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On Windows the masked prompt read keys with msvcrt.getch and kept only printable ASCII, so every other character was discarded as it was typed. The BIP39 passphrase prompt accepts any UTF-8 text, and the confirmation prompt could not catch the difference because it dropped the same characters. A passphrase containing anything outside ASCII therefore derived a wallet from text the user had not entered. Read the keys with ReadConsoleInputW instead. The console records say whether a key produced a character, so arrow and function keys are ignored rather than adding their scan codes to the entry. Through msvcrt that distinction is not available: the lead byte announcing an arrow key cannot be told apart from a typed 'a with grave accent'. A character outside the Basic Multilingual Plane arrives as a surrogate pair and is rebuilt into the character it stands for, so it echoes one mask character and one backspace removes all of it. Only key-down records are taken, since a key delivers its character on both key-down and key-up. Alt is the exception: a character composed with Alt and the numeric keypad arrives on the Alt key-up alone, and the classic console's right-click paste delivers each half of a character outside the Basic Multilingual Plane the same way. Alt pressed on its own carries no character on its key-up, so nothing is doubled. Line editing, echo and Ctrl+C handling are switched off while the prompt is open, which brings back the Ctrl+C cancellation the prompt offers, and the console mode is restored afterwards. When standard input is not a console the line is read as is instead of reading the console anyway. The masked prompt, both platforms and the shared entry loop, moves out of helper.security into its own module, helper.masked_input, with getpass() as its one public function. It has nothing to do with card security, and it needs nothing beyond the standard library, so importing it no longer pulls in user_keys. That dependency was what forced the PIV module to import the prompt inside a method; it imports it normally now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The command factory imports its modules by name at run time, so PyInstaller only packs the ones listed as hidden imports. The solana module was never added to that list. In a frozen build the factory then fails to import it and, since it treats a missing command module as fatal, every command fails at dispatch, not only solana. Drop coincurve from the hidden imports: nothing in the project uses it and PyInstaller reports it as not found on every build. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Remove four packages nothing imports. argparse from PyPI is the 2015 backport for Python versions without the module in the standard library; this project requires 3.11 or newer, so the standard library copy was always the one imported. colander and pytz have been declared since the first commit and are not used anywhere. boto3 stayed in the Pipfile and in pylint's ignored modules after the AWS backup feature was removed. Declare three packages the Ethereum wallet code imports directly: eth-account, eth-utils and hexbytes. Until now they were installed only as dependencies of web3, so a web3 release that stopped depending on one of them would have broken the wallet with no warning at install time. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
masked_input imported ctypes with both an import statement and an import from statement, which code scanning flags as inconsistent. Both imports now use the same form. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Masked secret entry and dependency loading are handled by the CLI itself, with two packaging fixes alongside.
cryptnox_cli.command.helper.masked_input;stdiomaskis removed. On Linux and macOS the terminal is put in cbreak mode and bytes are decoded incrementally, so a multi-byte character counts as one entry. Only ASCII control characters are left out: separators and format characters are part of a passphrase and change the wallet it derives.ReadConsoleInputWthrough ctypes rather thanmsvcrt: the console records say whether a key produced a character, so arrow and function keys add nothing. Throughmsvcrtthe lead byte announcing an arrow key cannot be told apart from a typedà(U+00E0). A character outside the Basic Multilingual Plane arrives as a surrogate pair and is rebuilt into one character, so it echoes one mask character and one Backspace removes all of it. Only key-down records are taken, with one exception: a character composed with Alt and the numeric keypad arrives on the Alt key-up record alone, and the classic console's right-click paste delivers each half of a character outside the BMP the same way. Ctrl+C cancels the prompt again. Because the module has no project dependencies, the PIV module imports it normally instead of inside a method.lazy-importis removed. The command factory imports every command module on each dispatch, so a module-levelweb3import anywhere loads it for every command.historyandinfonow start in about a third of the time. A module-levelweb3import added later would silently undo this.setup.spec: thesolanacommand module is added to the hidden imports. The factory imports command modules by name at run time, so PyInstaller only packs the ones listed, and without this entry a frozen build fails at every dispatch. The stalecoincurveentry is dropped. Note the spec has noCOLLECTstep, so the build is one file regardless ofonefile=False.argparse(a backport for Python versions without the module),colanderandpytzare removed;eth-account,eth-utilsandhexbytesare declared, since the Ethereum wallet imports them directly rather than throughweb3.Command output is unchanged throughout.
How it was tested
--version, every--helpand the dispatching commands run. With a Cryptnox card (Basic, applet 2.0.0) in a Microsoft USB CCID reader:listworks, and PIN entry at the frozen executable's prompt masks the digits, accepts Backspace and leaves the console in its normal state.info,historyandget_xpubwere not run to completion because the test card has no seed.--help,--version,history,infoand the per-command help is byte-identical before and after the lazy-loading change.Third-party and generated code
No third-party code. Substantial parts of this pull request were produced with Claude Code.
🤖 Generated with Claude Code