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 +72 -0
- libcrypto/_version.py +19 -0
- libcrypto/addresses.py +191 -0
- libcrypto/bip32.py +234 -0
- libcrypto/cli.py +159 -0
- libcrypto/constants.py +288 -0
- libcrypto/formats.py +164 -0
- libcrypto/hash.py +88 -0
- libcrypto/keys.py +149 -0
- libcrypto/mnemonic.py +156 -0
- libcrypto/secp256k1.py +117 -0
- libcrypto/wallet.py +135 -0
- libcrypto-1.0.1.dist-info/METADATA +318 -0
- libcrypto-1.0.1.dist-info/RECORD +18 -0
- libcrypto-1.0.1.dist-info/WHEEL +5 -0
- libcrypto-1.0.1.dist-info/entry_points.txt +2 -0
- libcrypto-1.0.1.dist-info/licenses/LICENSE +21 -0
- libcrypto-1.0.1.dist-info/top_level.txt +1 -0
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"))
|