libcrypto 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
libcrypto/__init__.py ADDED
@@ -0,0 +1,72 @@
1
+ """
2
+ LibCrypto Wallet Module
3
+
4
+ This module provides comprehensive cryptocurrency wallet functionality including:
5
+ - BIP32 Hierarchical Deterministic (HD) wallets
6
+ - Multi-currency address generation
7
+ - Key format conversions
8
+ - WIF (Wallet Import Format) support
9
+ """
10
+ from .wallet import Wallet
11
+ from .bip32 import HDWallet, HDNode, BIP32Error
12
+ from .keys import PrivateKey, PublicKey, KeyError
13
+ from .mnemonic import (
14
+ generate_mnemonic,
15
+ validate_mnemonic,
16
+ mnemonic_to_seed,
17
+ mnemonic_to_entropy,
18
+ entropy_to_mnemonic,
19
+ InvalidMnemonicError,
20
+ InvalidEntropyError
21
+ )
22
+ from .addresses import AddressGenerator, AddressError
23
+ from .formats import (
24
+ private_key_to_wif,
25
+ wif_to_private_key,
26
+ base58_encode,
27
+ base58_decode,
28
+ bytes_to_hex,
29
+ hex_to_bytes,
30
+ InvalidFormatError
31
+ )
32
+
33
+ __version__ = "1.0.1"
34
+ __all__ = [
35
+ # Library Version
36
+ '__version__',
37
+
38
+ # High-Level Wallet
39
+ 'Wallet',
40
+
41
+ # HD Wallet
42
+ 'HDWallet',
43
+ 'HDNode',
44
+ 'BIP32Error',
45
+
46
+ # Key classes
47
+ 'PrivateKey',
48
+ 'PublicKey',
49
+ 'KeyError',
50
+
51
+ # Address generation
52
+ 'AddressGenerator',
53
+ 'AddressError',
54
+
55
+ # Format conversions
56
+ 'private_key_to_wif',
57
+ 'wif_to_private_key',
58
+ 'base58_encode',
59
+ 'base58_decode',
60
+ 'bytes_to_hex',
61
+ 'hex_to_bytes',
62
+ 'InvalidFormatError',
63
+
64
+ # Mnemonic functions
65
+ 'generate_mnemonic',
66
+ 'validate_mnemonic',
67
+ 'mnemonic_to_seed',
68
+ 'mnemonic_to_entropy',
69
+ 'entropy_to_mnemonic',
70
+ 'InvalidMnemonicError',
71
+ 'InvalidEntropyError'
72
+ ]
libcrypto/_version.py ADDED
@@ -0,0 +1,19 @@
1
+ """
2
+ Version information for LibCrypto package.
3
+ This is the single source of truth for version numbers, managed by bump-my-version.
4
+
5
+ Requires Python 3.8+
6
+ """
7
+
8
+ __author__ = "Mmdrza"
9
+ __author_email__ = "pymmdrza@gmail.com"
10
+ __description__ = (
11
+ "A professional library For Cryptography and Cryptocurrencies in Python."
12
+ )
13
+ __url__ = "https://github.com/Pymmdrza/libcrypto"
14
+ # version Libcrypto
15
+ __version__ = "1.0.0"
16
+
17
+ # Additional metadata
18
+ __license__ = "MIT"
19
+ __copyright__ = "2025 Mmdrza"
libcrypto/addresses.py ADDED
@@ -0,0 +1,191 @@
1
+ """
2
+ Cryptocurrency Address Generation
3
+
4
+ This module provides address generation functionality for multiple cryptocurrencies
5
+ including Bitcoin, Ethereum, Litecoin, and others. Supports various address formats
6
+ like P2PKH, P2SH, P2WPKH, SegWit, and Ethereum-style addresses.
7
+ """
8
+ from typing import Union
9
+
10
+ from .hash import sha256, hash160, keccak256
11
+ from .constants import ADDRESS_VERSIONS, BECH32_HRP
12
+ from .formats import base58_check_encode, base58_encode, InvalidFormatError
13
+ from .secp256k1 import public_key_to_point_coords, Secp256k1Error
14
+
15
+
16
+ class AddressError(ValueError):
17
+ """Raised when address generation fails."""
18
+ pass
19
+
20
+
21
+ class Bech32:
22
+ """Bech32 encoding/decoding for SegWit addresses."""
23
+ CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
24
+ BECH32_CONST = 1
25
+
26
+ @classmethod
27
+ def _polymod(cls, values):
28
+ GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
29
+ chk = 1
30
+ for value in values:
31
+ top = chk >> 25
32
+ chk = (chk & 0x1ffffff) << 5 ^ value
33
+ for i in range(5):
34
+ chk ^= GEN[i] if ((top >> i) & 1) else 0
35
+ return chk
36
+
37
+ @classmethod
38
+ def _hrp_expand(cls, hrp):
39
+ return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
40
+
41
+ @classmethod
42
+ def _create_checksum(cls, hrp, data):
43
+ values = cls._hrp_expand(hrp) + data
44
+ polymod = cls._polymod(values + [0, 0, 0, 0, 0, 0]) ^ cls.BECH32_CONST
45
+ return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
46
+
47
+ @classmethod
48
+ def encode(cls, hrp, data):
49
+ combined = data + cls._create_checksum(hrp, data)
50
+ return hrp + '1' + ''.join([cls.CHARSET[d] for d in combined])
51
+
52
+ @classmethod
53
+ def convert_bits(cls, data, frombits, tobits, pad=True):
54
+ acc, bits, ret, maxv = 0, 0, [], (1 << tobits) - 1
55
+ for value in data:
56
+ if value < 0 or (value >> frombits):
57
+ return None
58
+ acc = (acc << frombits) | value
59
+ bits += frombits
60
+ while bits >= tobits:
61
+ bits -= tobits
62
+ ret.append((acc >> bits) & maxv)
63
+ if pad and bits:
64
+ ret.append((acc << (tobits - bits)) & maxv)
65
+ elif not pad and (bits >= frombits or ((acc << (tobits - bits)) & maxv)):
66
+ return None
67
+ return ret
68
+
69
+
70
+ class AddressGenerator:
71
+ """
72
+ Address generator for multiple cryptocurrencies.
73
+ """
74
+
75
+ @classmethod
76
+ def from_public_key(cls, public_key: bytes, address_type: str, network: str) -> str:
77
+ """
78
+ Generate address from a public key.
79
+ """
80
+ if network == 'ethereum':
81
+ return cls._generate_ethereum_address(public_key)
82
+ elif network == 'tron':
83
+ return cls._generate_tron_address(public_key)
84
+ elif network == 'ripple':
85
+ return cls._generate_ripple_address(public_key)
86
+ elif network in ('solana', 'ton'):
87
+ # These networks use Ed25519, which is a different curve.
88
+ # Providing a secp256k1 key will result in an invalid address.
89
+ raise NotImplementedError(
90
+ f"{network.capitalize()} uses the Ed25519 curve. "
91
+ "Address generation from secp256k1 keys is not supported and would be incorrect."
92
+ )
93
+ else:
94
+ return cls._generate_bitcoin_style_address(public_key, address_type, network)
95
+
96
+ @classmethod
97
+ def _generate_bitcoin_style_address(cls, public_key: bytes, address_type: str, network: str) -> str:
98
+ """Generate Bitcoin-like addresses (P2PKH, P2SH, SegWit)."""
99
+ if network not in ADDRESS_VERSIONS:
100
+ raise AddressError(f"Unsupported network: {network}")
101
+
102
+ versions = ADDRESS_VERSIONS[network]
103
+ key_hash = hash160(public_key)
104
+
105
+ if address_type == 'p2pkh':
106
+ versioned_payload = bytes([versions['p2pkh']]) + key_hash
107
+ return base58_check_encode(versioned_payload)
108
+
109
+ elif address_type == 'p2sh-p2wpkh':
110
+ # P2SH-wrapped SegWit address (for compatibility)
111
+ redeem_script = b'\x00\x14' + key_hash
112
+ script_hash = hash160(redeem_script)
113
+ versioned_payload = bytes([versions['p2sh']]) + script_hash
114
+ return base58_check_encode(versioned_payload)
115
+
116
+ elif address_type == 'p2wpkh':
117
+ # Native SegWit (Bech32)
118
+ if network not in BECH32_HRP:
119
+ raise AddressError(f"SegWit (Bech32) not supported for network: {network}")
120
+
121
+ converted = Bech32.convert_bits(key_hash, 8, 5)
122
+ if converted is None:
123
+ raise AddressError("Failed to convert bits for Bech32 encoding.")
124
+
125
+ return Bech32.encode(BECH32_HRP[network], [0] + converted)
126
+
127
+ else:
128
+ raise AddressError(f"Unsupported address type for {network}: {address_type}")
129
+
130
+ @classmethod
131
+ def _get_uncompressed_pubkey(cls, public_key: bytes) -> bytes:
132
+ """Ensures the public key is in uncompressed format (64 bytes, no prefix)."""
133
+ if len(public_key) == 33: # Compressed
134
+ try:
135
+ point = public_key_to_point_coords(public_key)
136
+ return point.x.to_bytes(32, 'big') + point.y.to_bytes(32, 'big')
137
+ except Exception as e:
138
+ raise AddressError(f"Failed to decompress public key: {e}")
139
+ elif len(public_key) == 65 and public_key[0] == 0x04: # Uncompressed with prefix
140
+ return public_key[1:]
141
+ elif len(public_key) == 64: # Uncompressed without prefix
142
+ return public_key
143
+ else:
144
+ raise AddressError(f"Invalid public key length for this operation: {len(public_key)}")
145
+
146
+ @classmethod
147
+ def _generate_ethereum_address(cls, public_key: bytes) -> str:
148
+ """Generate Ethereum address (with EIP-55 checksum)."""
149
+ uncompressed_key = cls._get_uncompressed_pubkey(public_key)
150
+
151
+ keccak_hash = keccak256(uncompressed_key)
152
+ address_bytes = keccak_hash[-20:]
153
+ address_hex = address_bytes.hex()
154
+
155
+ # EIP-55 checksum
156
+ checksum_hash = keccak256(address_hex.encode('ascii')).hex()
157
+ checksummed = '0x'
158
+ for i, char in enumerate(address_hex):
159
+ if int(checksum_hash[i], 16) >= 8:
160
+ checksummed += char.upper()
161
+ else:
162
+ checksummed += char
163
+ return checksummed
164
+
165
+ @classmethod
166
+ def _generate_tron_address(cls, public_key: bytes) -> str:
167
+ """Generate TRON address."""
168
+ uncompressed_key = cls._get_uncompressed_pubkey(public_key)
169
+
170
+ keccak_hash = keccak256(uncompressed_key)
171
+ address_bytes = b'\x41' + keccak_hash[-20:] # TRON prefix 0x41
172
+
173
+ return base58_check_encode(address_bytes)
174
+
175
+ @classmethod
176
+ def _generate_ripple_address(cls, public_key: bytes) -> str:
177
+ """Generate Ripple (XRP) address."""
178
+ # Ripple requires a compressed public key
179
+ if len(public_key) == 65:
180
+ from .secp256k1 import compress_public_key
181
+ public_key = compress_public_key(public_key)
182
+
183
+ if len(public_key) != 33:
184
+ raise AddressError(f"Invalid public key length for Ripple: {len(public_key)}")
185
+
186
+ key_hash = hash160(public_key)
187
+ versioned_payload = b'\x00' + key_hash # Ripple account ID prefix
188
+
189
+ # Ripple uses a specific Base58 alphabet
190
+ ripple_alphabet = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"
191
+ return base58_check_encode(versioned_payload, alphabet=ripple_alphabet)
libcrypto/bip32.py ADDED
@@ -0,0 +1,234 @@
1
+ """
2
+ BIP32 Hierarchical Deterministic (HD) Wallet Implementation
3
+
4
+ This module provides BIP32 compliant hierarchical deterministic wallet functionality
5
+ including master key generation from seed, child key derivation, and extended key
6
+ serialization (xprv/xpub format).
7
+ """
8
+ import struct
9
+ from typing import Optional
10
+
11
+ from .constants import (
12
+ BIP32_HARDENED_OFFSET, BIP32_HMAC_KEY, XPRV_MAINNET, XPUB_MAINNET,
13
+ XPRV_TESTNET, XPUB_TESTNET, SECP256K1_N
14
+ )
15
+ from .formats import base58_check_encode, base58_check_decode, InvalidFormatError
16
+ from .hash import hmac_sha512, hash160
17
+ from .secp256k1 import private_key_to_public_key
18
+
19
+
20
+ class BIP32Error(ValueError):
21
+ """Raised when BIP32 operations fail."""
22
+ pass
23
+
24
+
25
+ class HDNode:
26
+ """A single node in a BIP32 hierarchical deterministic wallet tree."""
27
+
28
+ def __init__(self, private_key: Optional[bytes], chain_code: bytes,
29
+ depth: int = 0, parent_fingerprint: bytes = b'\x00\x00\x00\x00',
30
+ child_number: int = 0, network: str = 'mainnet'):
31
+
32
+ if len(chain_code) != 32:
33
+ raise BIP32Error(f"Chain code must be 32 bytes, got {len(chain_code)}")
34
+ if private_key and len(private_key) != 32:
35
+ raise BIP32Error(f"Private key must be 32 bytes, got {len(private_key)}")
36
+
37
+ self._private_key = private_key
38
+ self.chain_code = chain_code
39
+ self.depth = depth
40
+ self.parent_fingerprint = parent_fingerprint
41
+ self.child_number = child_number
42
+ self.network = network
43
+
44
+ self._public_key = None
45
+ if self._private_key:
46
+ self._derive_public_from_private()
47
+
48
+ def _derive_public_from_private(self):
49
+ private_int = int.from_bytes(self._private_key, 'big')
50
+ self._public_key = private_key_to_public_key(private_int, compressed=True)
51
+
52
+ @property
53
+ def private_key(self) -> Optional[bytes]:
54
+ return self._private_key
55
+
56
+ @property
57
+ def public_key(self) -> bytes:
58
+ if self._public_key is None:
59
+ # This case is for public-only derivation which is not implemented.
60
+ raise BIP32Error("Public key not available or derivation from public key is not supported.")
61
+ return self._public_key
62
+
63
+ @property
64
+ def is_private(self) -> bool:
65
+ return self._private_key is not None
66
+
67
+ @property
68
+ def fingerprint(self) -> bytes:
69
+ return hash160(self.public_key)[:4]
70
+
71
+ def derive_child(self, index: int) -> 'HDNode':
72
+ """Derive a child key at the given index."""
73
+ is_hardened = index >= BIP32_HARDENED_OFFSET
74
+
75
+ if is_hardened:
76
+ if not self.is_private:
77
+ raise BIP32Error("Cannot derive hardened child from a public key.")
78
+ data = b'\x00' + self._private_key + struct.pack('>I', index)
79
+ else:
80
+ # Public key derivation is complex (point addition).
81
+ # We will only support private key derivation for simplicity and security.
82
+ if not self.is_private:
83
+ raise BIP32Error("Derivation from public-only node is not supported.")
84
+ data = self.public_key + struct.pack('>I', index)
85
+
86
+ hmac_result = hmac_sha512(self.chain_code, data)
87
+ child_key_material, child_chain_code = hmac_result[:32], hmac_result[32:]
88
+
89
+ child_key_int = int.from_bytes(child_key_material, 'big')
90
+ parent_key_int = int.from_bytes(self._private_key, 'big')
91
+
92
+ derived_private_int = (child_key_int + parent_key_int) % SECP256K1_N
93
+ if derived_private_int == 0:
94
+ # In the rare case of an invalid key, BIP32 suggests trying the next index.
95
+ # For simplicity, we raise an error.
96
+ raise BIP32Error("Invalid child key generated, leads to key being zero.")
97
+
98
+ child_private_key = derived_private_int.to_bytes(32, 'big')
99
+
100
+ return HDNode(
101
+ private_key=child_private_key,
102
+ chain_code=child_chain_code,
103
+ depth=self.depth + 1,
104
+ parent_fingerprint=self.fingerprint,
105
+ child_number=index,
106
+ network=self.network
107
+ )
108
+
109
+ def derive_path(self, path: str) -> 'HDNode':
110
+ """Derive a key using a derivation path string (e.g., "m/44'/0'/0'")."""
111
+ if path == "m" or path == "/":
112
+ return self
113
+ if path.startswith(('m/', '/')):
114
+ path = path[2:]
115
+
116
+ node = self
117
+ for segment in path.split('/'):
118
+ if not segment: continue
119
+
120
+ hardened = segment.endswith("'")
121
+ index_str = segment[:-1] if hardened else segment
122
+
123
+ try:
124
+ index = int(index_str)
125
+ if hardened:
126
+ index += BIP32_HARDENED_OFFSET
127
+ except ValueError:
128
+ raise BIP32Error(f"Invalid path segment: {segment}")
129
+
130
+ node = node.derive_child(index)
131
+
132
+ return node
133
+
134
+ def serialize_private(self) -> str:
135
+ """Serialize as an extended private key (xprv)."""
136
+ if not self.is_private:
137
+ raise BIP32Error("Cannot serialize a private key from a public-only node.")
138
+
139
+ version = XPRV_MAINNET if self.network == 'mainnet' else XPRV_TESTNET
140
+
141
+ data = struct.pack('>I', version)
142
+ data += bytes([self.depth])
143
+ data += self.parent_fingerprint
144
+ data += struct.pack('>I', self.child_number)
145
+ data += self.chain_code
146
+ data += b'\x00' + self._private_key
147
+
148
+ return base58_check_encode(data)
149
+
150
+ def serialize_public(self) -> str:
151
+ """Serialize as an extended public key (xpub)."""
152
+ version = XPUB_MAINNET if self.network == 'mainnet' else XPUB_TESTNET
153
+
154
+ data = struct.pack('>I', version)
155
+ data += bytes([self.depth])
156
+ data += self.parent_fingerprint
157
+ data += struct.pack('>I', self.child_number)
158
+ data += self.chain_code
159
+ data += self.public_key
160
+
161
+ return base58_check_encode(data)
162
+
163
+ @classmethod
164
+ def deserialize(cls, extended_key: str) -> 'HDNode':
165
+ """Deserialize an extended key (xprv) to an HDNode."""
166
+ try:
167
+ data = base58_check_decode(extended_key)
168
+ except InvalidFormatError as e:
169
+ raise BIP32Error(f"Invalid extended key format: {e}") from e
170
+
171
+ if len(data) != 78:
172
+ raise BIP32Error(f"Extended key must be 78 bytes, got {len(data)}")
173
+
174
+ version = struct.unpack('>I', data[:4])[0]
175
+
176
+ if version not in [XPRV_MAINNET, XPRV_TESTNET]:
177
+ raise BIP32Error("Deserialization of public extended keys (xpub) is not supported.")
178
+
179
+ network = 'mainnet' if version == XPRV_MAINNET else 'testnet'
180
+
181
+ depth = data[4]
182
+ parent_fingerprint = data[5:9]
183
+ child_number = struct.unpack('>I', data[9:13])[0]
184
+ chain_code = data[13:45]
185
+
186
+ if data[45] != 0x00:
187
+ raise BIP32Error("Private key must be prefixed with 0x00.")
188
+ private_key = data[46:78]
189
+
190
+ return cls(
191
+ private_key=private_key,
192
+ chain_code=chain_code,
193
+ depth=depth,
194
+ parent_fingerprint=parent_fingerprint,
195
+ child_number=child_number,
196
+ network=network
197
+ )
198
+
199
+
200
+ class HDWallet:
201
+ """BIP32 Hierarchical Deterministic Wallet."""
202
+
203
+ def __init__(self, seed: bytes, network: str = 'mainnet'):
204
+ self.network = network
205
+ self._master_node = self._generate_master_node(seed)
206
+
207
+ def _generate_master_node(self, seed: bytes) -> HDNode:
208
+ hmac_result = hmac_sha512(BIP32_HMAC_KEY, seed)
209
+ master_private_key, master_chain_code = hmac_result[:32], hmac_result[32:]
210
+
211
+ private_int = int.from_bytes(master_private_key, 'big')
212
+ if not (1 <= private_int < SECP256K1_N):
213
+ raise BIP32Error("Invalid master key generated from seed (out of curve order).")
214
+
215
+ return HDNode(
216
+ private_key=master_private_key,
217
+ chain_code=master_chain_code,
218
+ network=self.network
219
+ )
220
+
221
+ @property
222
+ def master_node(self) -> HDNode:
223
+ return self._master_node
224
+
225
+ def derive_from_path(self, path: str) -> HDNode:
226
+ """Derive a node from a full BIP32 path."""
227
+ return self._master_node.derive_path(path)
228
+
229
+ @classmethod
230
+ def from_mnemonic(cls, mnemonic: str, passphrase: str = "", network: str = 'mainnet') -> 'HDWallet':
231
+ """Create HDWallet from a BIP39 mnemonic."""
232
+ from .mnemonic import mnemonic_to_seed
233
+ seed = mnemonic_to_seed(mnemonic, passphrase)
234
+ return cls(seed, network)
libcrypto/cli.py ADDED
@@ -0,0 +1,159 @@
1
+ import typer
2
+ from rich.console import Console
3
+ from rich.panel import Panel
4
+ from rich.table import Table
5
+ from typing import List, Optional
6
+
7
+ from . import __version__
8
+ from .wallet import Wallet, COIN_CONFIG
9
+ from .mnemonic import generate_mnemonic, validate_mnemonic
10
+
11
+ # Create a Typer application instance. This is the main entry point for commands.
12
+ app = typer.Typer(
13
+ name="libcrypto",
14
+ help="A professional library for Cryptography and Cryptocurrencies.",
15
+ add_completion=False,
16
+ )
17
+
18
+ # Create a rich Console instance for beautiful output.
19
+ console = Console()
20
+
21
+
22
+ def version_callback(value: bool):
23
+ """Callback function to display the version and exit."""
24
+ if value:
25
+ console.print(f"libcrypto version: [bold green]{__version__}[/bold green]")
26
+ raise typer.Exit()
27
+
28
+
29
+ @app.callback()
30
+ def main(
31
+ version: bool = typer.Option(
32
+ None,
33
+ "--version",
34
+ "-v",
35
+ help="Show the application's version and exit.",
36
+ callback=version_callback,
37
+ is_eager=True,
38
+ )
39
+ ):
40
+ """
41
+ LibCrypto CLI: Manage crypto keys, addresses, and mnemonics.
42
+ """
43
+ # This main function serves as a top-level entry point.
44
+ # The 'version_callback' handles the --version flag.
45
+ pass
46
+
47
+
48
+ @app.command()
49
+ def info():
50
+ """
51
+ Display information about the libcrypto package.
52
+ """
53
+ info_text = (
54
+ f"[bold]libcrypto v{__version__}[/bold]\n"
55
+ "[dim]A professional library for Cryptography and Cryptocurrencies in Python.[/dim]\n\n"
56
+ "Author: [cyan]Mmdrza[/cyan]\n"
57
+ "Repository: [link=https://github.com/Pymmdrza/libcrypto]https://github.com/Pymmdrza/libcrypto[/link]"
58
+ )
59
+ panel = Panel(info_text, title="Package Information", border_style="blue", expand=False)
60
+ console.print(panel)
61
+
62
+
63
+ @app.command()
64
+ def generate(
65
+ private_key: Optional[str] = typer.Option(
66
+ None,
67
+ "--private-key",
68
+ "-p",
69
+ help="Generate addresses from an existing private key (WIF or Hex).",
70
+ ),
71
+ coins: List[str] = typer.Option(
72
+ ["bitcoin", "ethereum", "tron", "ripple", "litecoin", "dogecoin", "dash", "bitcoin_cash"],
73
+ "--coin",
74
+ "-c",
75
+ help="Specify coin(s) to generate addresses for. Can be used multiple times.",
76
+ ),
77
+ ):
78
+ """
79
+ Generate a new wallet or derive addresses from an existing private key.
80
+ """
81
+ try:
82
+ if private_key:
83
+ wallet = Wallet(private_key)
84
+ console.print(Panel("Addresses derived from your provided private key",
85
+ title="[bold yellow]Wallet Details[/bold yellow]", border_style="yellow"))
86
+ else:
87
+ wallet = Wallet.generate()
88
+ console.print(Panel("A new secure wallet has been generated for you!",
89
+ title="[bold green]New Wallet Created[/bold green]", border_style="green"))
90
+
91
+ # --- Display Keys ---
92
+ key_table = Table(title="Crypto Wallet Detail", show_header=False, box=None)
93
+ key_table.add_column("Attribute", style="cyan")
94
+ key_table.add_column("Value", style="white")
95
+
96
+ key_table.add_row("Private Key (Hex)", wallet.private_key.hex)
97
+ key_table.add_row("Private Key (WIF)", wallet.private_key.to_wif())
98
+ key_table.add_row("Public Key (Compressed)", wallet._public_key_compressed.hex)
99
+ key_table.add_row("Public Key (Uncompressed)", wallet._public_key_uncompressed.hex)
100
+ console.print(key_table)
101
+
102
+ # --- Display Addresses ---
103
+ address_table = Table(title="Generated Addresses", box=None)
104
+ address_table.add_column("Coin", style="bold magenta")
105
+ address_table.add_column("Address Type", style="yellow")
106
+ address_table.add_column("Address", style="bold green")
107
+
108
+ for coin in coins:
109
+ coin = coin.lower()
110
+ if coin not in COIN_CONFIG:
111
+ console.print(f"[bold red]Error:[/] Unsupported coin '{coin}'. Skipping.")
112
+ continue
113
+
114
+ addresses = wallet.get_all_addresses(coin)
115
+ for addr_type, address in addresses.items():
116
+ address_table.add_row(coin.capitalize(), addr_type, address)
117
+
118
+ console.print(address_table)
119
+
120
+ except Exception as e:
121
+ console.print(f"[bold red]An error occurred:[/] {e}")
122
+ raise typer.Exit(code=1)
123
+
124
+
125
+ # --- Mnemonic Sub-Commands ---
126
+ mnemonic_app = typer.Typer(name="mnemonic", help="Generate and validate BIP39 mnemonic phrases.")
127
+ app.add_typer(mnemonic_app, name="mnemonic")
128
+
129
+
130
+ @mnemonic_app.command("generate")
131
+ def mnemonic_generate(
132
+ word_count: int = typer.Option(12, "--words", "-w",
133
+ help="Number of words for the mnemonic (12, 15, 18, 21, or 24).")
134
+ ):
135
+ """
136
+ Generate a new BIP39 mnemonic phrase.
137
+ """
138
+ try:
139
+ mnemonic = generate_mnemonic(word_count)
140
+ console.print(
141
+ Panel(f"[bold cyan]{mnemonic}[/bold cyan]", title="Generated Mnemonic Phrase", border_style="green"))
142
+ except ValueError as e:
143
+ console.print(f"[bold red]Error:[/] {e}")
144
+
145
+
146
+ @mnemonic_app.command("validate")
147
+ def mnemonic_validate(
148
+ phrase: str = typer.Argument(..., help="The mnemonic phrase to validate, enclosed in quotes.")
149
+ ):
150
+ """
151
+ Validate a BIP39 mnemonic phrase.
152
+ """
153
+ is_valid = validate_mnemonic(phrase)
154
+ if is_valid:
155
+ console.print(Panel("[bold green]This mnemonic phrase is valid.[/bold green]", title="Validation Success",
156
+ border_style="green"))
157
+ else:
158
+ console.print(Panel("[bold red]This mnemonic phrase is invalid.[/bold red]", title="Validation Failed",
159
+ border_style="red"))