lunalib 1.5.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.

Potentially problematic release.


This version of lunalib might be problematic. Click here for more details.

Files changed (53) hide show
  1. core/__init__.py +0 -0
  2. core/blockchain.py +172 -0
  3. core/crypto.py +32 -0
  4. core/wallet.py +408 -0
  5. gtx/__init__.py +0 -0
  6. gtx/bill_registry.py +122 -0
  7. gtx/digital_bill.py +273 -0
  8. gtx/genesis.py +338 -0
  9. lunalib/__init__.py +21 -0
  10. lunalib/cli.py +18 -0
  11. lunalib/core/__init__.py +0 -0
  12. lunalib/core/blockchain.py +803 -0
  13. lunalib/core/crypto.py +270 -0
  14. lunalib/core/mempool.py +342 -0
  15. lunalib/core/sm2.py +723 -0
  16. lunalib/core/wallet.py +1342 -0
  17. lunalib/core/wallet_manager.py +638 -0
  18. lunalib/core/wallet_sync_helper.py +163 -0
  19. lunalib/gtx/__init__.py +0 -0
  20. lunalib/gtx/bill_registry.py +122 -0
  21. lunalib/gtx/digital_bill.py +273 -0
  22. lunalib/gtx/genesis.py +349 -0
  23. lunalib/luna_lib.py +87 -0
  24. lunalib/mining/__init__.py +0 -0
  25. lunalib/mining/cuda_manager.py +137 -0
  26. lunalib/mining/difficulty.py +106 -0
  27. lunalib/mining/miner.py +617 -0
  28. lunalib/requirements.txt +44 -0
  29. lunalib/storage/__init__.py +0 -0
  30. lunalib/storage/cache.py +148 -0
  31. lunalib/storage/database.py +222 -0
  32. lunalib/storage/encryption.py +105 -0
  33. lunalib/transactions/__init__.py +0 -0
  34. lunalib/transactions/security.py +234 -0
  35. lunalib/transactions/transactions.py +399 -0
  36. lunalib/transactions/validator.py +71 -0
  37. lunalib-1.5.1.dist-info/METADATA +283 -0
  38. lunalib-1.5.1.dist-info/RECORD +53 -0
  39. lunalib-1.5.1.dist-info/WHEEL +5 -0
  40. lunalib-1.5.1.dist-info/entry_points.txt +2 -0
  41. lunalib-1.5.1.dist-info/top_level.txt +6 -0
  42. mining/__init__.py +0 -0
  43. mining/cuda_manager.py +137 -0
  44. mining/difficulty.py +106 -0
  45. mining/miner.py +107 -0
  46. storage/__init__.py +0 -0
  47. storage/cache.py +148 -0
  48. storage/database.py +222 -0
  49. storage/encryption.py +105 -0
  50. transactions/__init__.py +0 -0
  51. transactions/security.py +172 -0
  52. transactions/transactions.py +424 -0
  53. transactions/validator.py +71 -0
@@ -0,0 +1,234 @@
1
+ import time
2
+ import hashlib
3
+ from typing import Dict, Tuple
4
+
5
+ class TransactionSecurity:
6
+ """Enhanced transaction security system with SM2 support"""
7
+
8
+ def __init__(self):
9
+ self.min_transaction_amount = 0.000001
10
+ self.max_transaction_amount = 100000000
11
+ self.required_fee = 0.00001
12
+ self.rate_limits = {}
13
+ self.blacklisted_addresses = set()
14
+
15
+ # Try to import SM2 KeyManager
16
+ try:
17
+ from ..core.crypto import KeyManager as SM2KeyManager
18
+ self.key_manager = SM2KeyManager()
19
+ self.sm2_available = True
20
+ print("[SECURITY] SM2 KeyManager loaded successfully")
21
+ except ImportError as e:
22
+ self.key_manager = None
23
+ self.sm2_available = False
24
+ print(f"[SECURITY] SM2 KeyManager not available: {e}")
25
+
26
+ def validate_transaction_security(self, transaction: Dict) -> Tuple[bool, str]:
27
+ """Comprehensive transaction security validation with SM2"""
28
+ tx_type = transaction.get("type", "").lower()
29
+
30
+ if tx_type == "gtx_genesis":
31
+ return self._validate_genesis_transaction(transaction)
32
+ elif tx_type == "reward":
33
+ return self._validate_reward_transaction(transaction)
34
+ elif tx_type == "transfer":
35
+ return self._validate_transfer_transaction(transaction)
36
+ else:
37
+ return False, f"Unknown transaction type: {tx_type}"
38
+
39
+ def _validate_genesis_transaction(self, transaction: Dict) -> Tuple[bool, str]:
40
+ """Validate GTX Genesis transaction"""
41
+ required_fields = ["bill_serial", "denomination", "mining_difficulty", "hash", "nonce"]
42
+ for field in required_fields:
43
+ if field not in transaction:
44
+ return False, f"Missing GTX field: {field}"
45
+
46
+ # Validate denomination
47
+ denomination = transaction.get("denomination")
48
+ valid_denominations = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000]
49
+ if denomination not in valid_denominations:
50
+ return False, f"Invalid denomination: {denomination}"
51
+
52
+ # Validate mining proof
53
+ if not self._validate_mining_proof(transaction):
54
+ return False, "Invalid mining proof"
55
+
56
+ return True, "Valid GTX Genesis transaction"
57
+
58
+ def _validate_reward_transaction(self, transaction: Dict) -> Tuple[bool, str]:
59
+ """Validate reward transaction"""
60
+ required_fields = ["from", "to", "amount", "block_height", "hash"]
61
+ for field in required_fields:
62
+ if field not in transaction:
63
+ return False, f"Missing reward field: {field}"
64
+
65
+ # Only network can create rewards
66
+ if transaction.get("from") != "network":
67
+ return False, "Unauthorized reward creation"
68
+
69
+ return True, "Valid reward transaction"
70
+
71
+ def _validate_transfer_transaction(self, transaction: Dict) -> Tuple[bool, str]:
72
+ """Validate transfer transaction with SM2 signature"""
73
+ required_fields = ["from", "to", "amount", "signature", "public_key", "nonce"]
74
+ for field in required_fields:
75
+ if field not in transaction:
76
+ return False, f"Missing field: {field}"
77
+
78
+ # Amount validation
79
+ amount = transaction.get("amount", 0)
80
+ if amount < self.min_transaction_amount:
81
+ return False, f"Amount below minimum: {self.min_transaction_amount}"
82
+ if amount > self.max_transaction_amount:
83
+ return False, f"Amount above maximum: {self.max_transaction_amount}"
84
+
85
+ # Fee validation
86
+ fee = transaction.get("fee", 0)
87
+ if fee < self.required_fee:
88
+ return False, f"Insufficient fee: {fee} (required: {self.required_fee})"
89
+
90
+ # SM2 Signature validation
91
+ if not self._validate_signature_sm2(transaction):
92
+ return False, "Invalid SM2 signature"
93
+
94
+ # Anti-spam checks
95
+ from_address = transaction.get("from", "")
96
+ if not self._check_rate_limit(from_address):
97
+ return False, "Rate limit exceeded"
98
+
99
+ if self._is_blacklisted(from_address):
100
+ return False, "Address is blacklisted"
101
+
102
+ return True, "Valid transfer transaction"
103
+
104
+ def _validate_signature_sm2(self, transaction: Dict) -> bool:
105
+ """Validate transaction signature using SM2"""
106
+ try:
107
+ signature = transaction.get("signature", "")
108
+ public_key = transaction.get("public_key", "")
109
+ tx_type = transaction.get("type", "").lower()
110
+
111
+ # Skip system transactions
112
+ if tx_type in ["gtx_genesis", "reward"]:
113
+ return True
114
+
115
+ # For unsigned test transactions
116
+ if signature in ["system", "unsigned", "test"]:
117
+ print(f"[SECURITY] Skipping signature check for system/unsigned transaction")
118
+ return True
119
+
120
+ # Check SM2 signature length (should be 128 hex chars = 64 bytes)
121
+ if len(signature) != 128:
122
+ print(f"[SECURITY] Invalid SM2 signature length: {len(signature)} (expected 128)")
123
+ return False
124
+
125
+ # Check if all characters are valid hex
126
+ if not all(c in "0123456789abcdefABCDEF" for c in signature):
127
+ print(f"[SECURITY] Signature contains non-hex characters")
128
+ return False
129
+
130
+ # Check public key format (should start with '04' for uncompressed)
131
+ if not public_key.startswith('04'):
132
+ print(f"[SECURITY] Invalid public key format: {public_key[:20]}...")
133
+ return False
134
+
135
+ # Use KeyManager for verification if available
136
+ if self.sm2_available and self.key_manager:
137
+ # Create signing data string
138
+ signing_data = self._get_signing_data(transaction)
139
+
140
+ # Verify signature
141
+ is_valid = self.key_manager.verify_signature(signing_data, signature, public_key)
142
+ print(f"[SECURITY] SM2 signature verification: {is_valid}")
143
+ return is_valid
144
+
145
+ # Fallback: Basic format check if SM2 not available
146
+ print(f"[SECURITY] SM2 not available, using basic signature validation")
147
+ return len(signature) == 128 and signature.startswith(('04', '03', '02'))
148
+
149
+ except Exception as e:
150
+ print(f"[SECURITY] Signature validation error: {e}")
151
+ return False
152
+
153
+ def _get_signing_data(self, transaction: Dict) -> str:
154
+ """Create data string for signing"""
155
+ # Create copy without signature and hash
156
+ tx_copy = {k: v for k, v in transaction.items()
157
+ if k not in ['signature', 'hash']}
158
+
159
+ # Sort keys and convert to string
160
+ import json
161
+ return json.dumps(tx_copy, sort_keys=True)
162
+
163
+ def _validate_mining_proof(self, transaction: Dict) -> bool:
164
+ """Validate mining proof-of-work"""
165
+ try:
166
+ difficulty = transaction.get("mining_difficulty", 0)
167
+ bill_hash = transaction.get("hash", "")
168
+
169
+ # Verify difficulty requirement
170
+ target = "0" * difficulty
171
+ return bill_hash.startswith(target)
172
+ except:
173
+ return False
174
+
175
+ def _validate_signature(self, transaction: Dict) -> bool:
176
+ """Legacy signature validation (for backward compatibility)"""
177
+ print(f"[SECURITY] Using legacy signature validation")
178
+ return self._validate_signature_sm2(transaction)
179
+
180
+ def _check_rate_limit(self, address: str) -> bool:
181
+ """Check transaction rate limiting"""
182
+ now = time.time()
183
+
184
+ if address not in self.rate_limits:
185
+ self.rate_limits[address] = []
186
+
187
+ # Remove old entries (older than 1 minute)
188
+ self.rate_limits[address] = [t for t in self.rate_limits[address] if now - t < 60]
189
+
190
+ # Check if over limit (10 transactions per minute)
191
+ if len(self.rate_limits[address]) >= 10:
192
+ return False
193
+
194
+ self.rate_limits[address].append(now)
195
+ return True
196
+
197
+ def _is_blacklisted(self, address: str) -> bool:
198
+ """Check if address is blacklisted"""
199
+ return address.lower() in self.blacklisted_addresses
200
+
201
+ def blacklist_address(self, address: str):
202
+ """Add address to blacklist"""
203
+ self.blacklisted_addresses.add(address.lower())
204
+
205
+ def calculate_security_score(self, transaction: Dict) -> int:
206
+ """Calculate security score for transaction with SM2 bonus"""
207
+ score = 0
208
+
209
+ # Signature strength (SM2 gives higher score)
210
+ signature = transaction.get("signature", "")
211
+ if len(signature) == 128: # SM2 signature
212
+ score += 60 # Higher score for SM2
213
+ elif len(signature) == 64: # Legacy ECDSA
214
+ score += 40
215
+
216
+ # Public key presence
217
+ public_key = transaction.get("public_key", "")
218
+ if public_key and public_key.startswith('04'):
219
+ score += 30 # SM2 uncompressed format
220
+
221
+ # Timestamp freshness
222
+ timestamp = transaction.get("timestamp", 0)
223
+ if time.time() - timestamp < 600: # 10 minutes
224
+ score += 20
225
+
226
+ # Nonce uniqueness
227
+ if transaction.get("nonce"):
228
+ score += 10
229
+
230
+ # Additional security features
231
+ if transaction.get("security_hash"):
232
+ score += 10
233
+
234
+ return min(score, 100)
@@ -0,0 +1,399 @@
1
+ # lunalib/transactions/transactions.py
2
+ import time
3
+ import hashlib
4
+ import json
5
+ from typing import Dict, Optional, Tuple, List
6
+ from ..core.mempool import MempoolManager
7
+
8
+ # Import REAL SM2 KeyManager from crypto module
9
+ try:
10
+ from ..core.crypto import KeyManager as SM2KeyManager
11
+ SM2_AVAILABLE = True
12
+ print("DEBUG: Using SM2 KeyManager from crypto module")
13
+ except ImportError as e:
14
+ SM2_AVAILABLE = False
15
+ print(f"WARNING: SM2 KeyManager not available: {e}")
16
+
17
+ class TransactionSecurity:
18
+ """Transaction security validation and risk assessment"""
19
+
20
+ def validate_transaction(self, transaction: Dict) -> Tuple[bool, str]:
21
+ """Validate transaction structure"""
22
+ required_fields = ['type', 'from', 'to', 'amount', 'timestamp', 'hash']
23
+ for field in required_fields:
24
+ if field not in transaction:
25
+ return False, f'Missing required field: {field}'
26
+
27
+ # Validate amount
28
+ if transaction['amount'] <= 0 and transaction['type'] != 'reward':
29
+ return False, 'Amount must be positive'
30
+
31
+ return True, 'Valid'
32
+
33
+ def assess_risk(self, transaction: Dict) -> Tuple[str, str]:
34
+ """Assess transaction risk level"""
35
+ amount = transaction.get('amount', 0)
36
+ tx_type = transaction.get('type', 'transfer')
37
+
38
+ if tx_type in ['gtx_genesis', 'reward']:
39
+ return 'very_low', 'System transaction'
40
+
41
+ if amount > 1000000:
42
+ return 'high', 'Very large transaction'
43
+ elif amount > 100000:
44
+ return 'medium', 'Large transaction'
45
+ else:
46
+ return 'low', 'Normal transaction'
47
+
48
+ class FeeCalculator:
49
+ """Simple fee calculation"""
50
+
51
+ def __init__(self):
52
+ self.fee_config = {
53
+ 'transfer': 0.001,
54
+ 'reward': 0.0,
55
+ 'gtx_genesis': 0.0
56
+ }
57
+
58
+ def get_fee(self, transaction_type: str) -> float:
59
+ """Get fee for transaction type"""
60
+ return self.fee_config.get(transaction_type, 0.001)
61
+
62
+ class TransactionManager:
63
+ """Handles transaction creation, signing, validation, and broadcasting"""
64
+
65
+ def __init__(self, network_endpoints: List[str] = None):
66
+ self.security = TransactionSecurity()
67
+ self.fee_calculator = FeeCalculator()
68
+ self.mempool_manager = MempoolManager(network_endpoints)
69
+
70
+ # Initialize SM2 KeyManager if available
71
+ if SM2_AVAILABLE:
72
+ self.key_manager = SM2KeyManager()
73
+ else:
74
+ print("ERROR: SM2 KeyManager not available - cannot sign transactions")
75
+ self.key_manager = None
76
+
77
+ def create_transaction(self, from_address: str, to_address: str, amount: float,
78
+ private_key: Optional[str] = None, memo: str = "",
79
+ transaction_type: str = "transfer") -> Dict:
80
+ """Create and sign a transaction"""
81
+
82
+ # Calculate fee
83
+ fee = self.fee_calculator.get_fee(transaction_type)
84
+
85
+ transaction = {
86
+ "type": transaction_type,
87
+ "from": from_address,
88
+ "to": to_address,
89
+ "amount": float(amount),
90
+ "fee": fee,
91
+ "timestamp": int(time.time()),
92
+ "memo": memo,
93
+ "version": "2.0" # Version 2.0 = SM2 signatures
94
+ }
95
+
96
+ # Sign transaction with SM2 if private key provided
97
+ if private_key and self.key_manager:
98
+ try:
99
+ # Sign the transaction data
100
+ tx_string = self._get_signing_data(transaction)
101
+
102
+ print(f"[TRANSACTIONS CREATE DEBUG] Signing data: {tx_string}")
103
+ print(f"[TRANSACTIONS CREATE DEBUG] Private key available: {bool(private_key)}")
104
+ print(f"[TRANSACTIONS CREATE DEBUG] Private key length: {len(private_key)}")
105
+
106
+ signature = self.key_manager.sign_data(tx_string, private_key)
107
+
108
+ # Get public key from private key
109
+ public_key = self.key_manager.derive_public_key(private_key)
110
+
111
+ print(f"[TRANSACTIONS CREATE DEBUG] Generated signature: {signature}")
112
+ print(f"[TRANSACTIONS CREATE DEBUG] Generated public key: {public_key}")
113
+ print(f"[TRANSACTIONS CREATE DEBUG] Signature length: {len(signature)}")
114
+ print(f"[TRANSACTIONS CREATE DEBUG] Public key length: {len(public_key)}")
115
+
116
+ transaction["signature"] = signature
117
+ transaction["public_key"] = public_key
118
+
119
+ # Immediately test verification
120
+ test_verify = self.key_manager.verify_signature(tx_string, signature, public_key)
121
+ print(f"[TRANSACTIONS CREATE DEBUG] Immediate self-verification: {test_verify}")
122
+
123
+ if not test_verify:
124
+ print(f"[TRANSACTIONS CREATE ERROR] Signature doesn't verify immediately!")
125
+ print(f"[TRANSACTIONS CREATE ERROR] This suggests an SM2 implementation issue")
126
+
127
+ except Exception as e:
128
+ print(f"[TRANSACTIONS CREATE ERROR] Signing failed: {e}")
129
+ import traceback
130
+ traceback.print_exc()
131
+ transaction["signature"] = "unsigned"
132
+ transaction["public_key"] = "unsigned"
133
+ else:
134
+ # For unsigned transactions (rewards, gtx_genesis)
135
+ transaction["signature"] = "unsigned"
136
+ transaction["public_key"] = "unsigned"
137
+
138
+ # Calculate transaction hash (must be last)
139
+ transaction["hash"] = self._calculate_transaction_hash(transaction)
140
+
141
+ return transaction
142
+
143
+ def send_transaction(self, transaction: Dict) -> Tuple[bool, str]:
144
+ """Send transaction to mempool for broadcasting"""
145
+ try:
146
+ # Validate transaction first
147
+ is_valid, message = self.validate_transaction(transaction)
148
+ if not is_valid:
149
+ return False, f"Validation failed: {message}"
150
+
151
+ # Verify signature for non-system transactions
152
+ if transaction["type"] == "transfer":
153
+ if not self.verify_transaction_signature(transaction):
154
+ return False, "Invalid transaction signature"
155
+
156
+ # Add to mempool
157
+ success = self.mempool_manager.add_transaction(transaction)
158
+ if success:
159
+ return True, f"Transaction added to mempool: {transaction.get('hash', '')[:16]}..."
160
+ else:
161
+ return False, "Failed to add transaction to mempool"
162
+
163
+ except Exception as e:
164
+ return False, f"Error sending transaction: {str(e)}"
165
+
166
+ # TRANSFER TRANSACTION
167
+ def create_transfer(self, from_address: str, to_address: str, amount: float,
168
+ private_key: str, memo: str = "") -> Dict:
169
+ """Create a transfer transaction"""
170
+ return self.create_transaction(
171
+ from_address=from_address,
172
+ to_address=to_address,
173
+ amount=amount,
174
+ private_key=private_key,
175
+ memo=memo,
176
+ transaction_type="transfer"
177
+ )
178
+
179
+ # SYSTEM TRANSACTIONS (unsigned)
180
+ def create_gtx_transaction(self, bill_info: Dict) -> Dict:
181
+ """Create GTX Genesis transaction from mined bill"""
182
+ transaction = {
183
+ "type": "gtx_genesis",
184
+ "from": "mining",
185
+ "to": bill_info.get("owner_address", "unknown"),
186
+ "amount": float(bill_info.get("denomination", 0)),
187
+ "fee": 0.0,
188
+ "timestamp": int(time.time()),
189
+ "bill_serial": bill_info.get("serial", ""),
190
+ "mining_difficulty": bill_info.get("difficulty", 0),
191
+ "signature": "system",
192
+ "public_key": "system",
193
+ "version": "2.0"
194
+ }
195
+ transaction["hash"] = self._calculate_transaction_hash(transaction)
196
+ return transaction
197
+
198
+ def create_reward_transaction(self, to_address: str, amount: float,
199
+ block_height: int) -> Dict:
200
+ """Create reward transaction"""
201
+ transaction = {
202
+ "type": "reward",
203
+ "from": "network",
204
+ "to": to_address,
205
+ "amount": float(amount),
206
+ "fee": 0.0,
207
+ "block_height": block_height,
208
+ "timestamp": int(time.time()),
209
+ "signature": "system",
210
+ "public_key": "system",
211
+ "version": "2.0"
212
+ }
213
+ transaction["hash"] = self._generate_reward_hash(to_address, amount, block_height)
214
+ return transaction
215
+
216
+ # VALIDATION METHODS
217
+ def validate_transaction(self, transaction: Dict) -> Tuple[bool, str]:
218
+ """Validate transaction using security module"""
219
+ return self.security.validate_transaction(transaction)
220
+
221
+ def verify_transaction_signature(self, transaction: Dict) -> bool:
222
+ """Verify transaction signature with SM2"""
223
+ try:
224
+ signature = transaction.get("signature", "")
225
+ tx_type = transaction.get("type", "").lower()
226
+
227
+ # System transactions are always valid
228
+ if signature in ["system", "unsigned", "test"]:
229
+ print(f"[TRANSACTIONS] Skipping signature check for {signature} transaction")
230
+ return True
231
+
232
+ if not self.key_manager:
233
+ print("[TRANSACTIONS] No key manager available for verification")
234
+ return False
235
+
236
+ # Check SM2 signature format
237
+ if len(signature) != 128:
238
+ print(f"[TRANSACTIONS] Invalid SM2 signature length: {len(signature)} (expected 128)")
239
+ return False
240
+
241
+ # Get signing data (without public_key!)
242
+ sign_data = self._get_signing_data(transaction)
243
+ public_key = transaction.get("public_key", "")
244
+
245
+ print(f"[TRANSACTIONS VERIFY] Signing data length: {len(sign_data)}")
246
+ print(f"[TRANSACTIONS VERIFY] Signing data (first 100 chars): {sign_data[:100]}")
247
+
248
+ # Try to verify with KeyManager
249
+ print(f"[TRANSACTIONS] Attempting verification...")
250
+ is_valid = self.key_manager.verify_signature(sign_data, signature, public_key)
251
+
252
+ print(f"[TRANSACTIONS] SM2 signature verification result: {is_valid}")
253
+
254
+ return is_valid
255
+
256
+ except Exception as e:
257
+ print(f"[TRANSACTIONS] Verification error: {e}")
258
+ import traceback
259
+ traceback.print_exc()
260
+ return False
261
+ def _debug_signature_issue(self, transaction: Dict, sign_data: str, signature: str, public_key: str):
262
+ """Debug why signature verification is failing"""
263
+ print("\n" + "="*60)
264
+ print("DEBUGGING SIGNATURE ISSUE")
265
+ print("="*60)
266
+
267
+ # 1. Check if we can sign and verify a simple test
268
+ print("\n1. Testing SM2 with simple message...")
269
+ test_message = "Simple test message"
270
+ test_private = self.key_manager.generate_private_key()
271
+ test_public = self.key_manager.derive_public_key(test_private)
272
+ test_sig = self.key_manager.sign_data(test_message, test_private)
273
+ test_valid = self.key_manager.verify_signature(test_message, test_sig, test_public)
274
+ print(f" Simple test verification: {test_valid}")
275
+
276
+ # 2. Try to recreate what was signed during transaction creation
277
+ print("\n2. Reconstructing original transaction data...")
278
+ # Create the exact transaction data that should have been signed
279
+ reconstructed = {
280
+ "amount": float(transaction["amount"]),
281
+ "fee": float(transaction["fee"]),
282
+ "from": transaction["from"],
283
+ "memo": transaction.get("memo", ""),
284
+ "timestamp": int(transaction["timestamp"]),
285
+ "to": transaction["to"],
286
+ "type": transaction["type"],
287
+ "version": transaction.get("version", "2.0")
288
+ }
289
+
290
+ import json
291
+ reconstructed_json = json.dumps(reconstructed, sort_keys=True)
292
+ print(f" Reconstructed JSON: {reconstructed_json}")
293
+ print(f" Current signing data: {sign_data}")
294
+ print(f" Are they equal? {reconstructed_json == sign_data}")
295
+ print(f" Length difference: {len(reconstructed_json)} vs {len(sign_data)}")
296
+
297
+ # 3. Check for whitespace differences
298
+ print("\n3. Checking for whitespace differences...")
299
+ print(f" Reconstructed has spaces: {' ' in reconstructed_json}")
300
+ print(f" Sign data has spaces: {' ' in sign_data}")
301
+
302
+ # 4. Check float formatting
303
+ print("\n4. Checking float formatting...")
304
+ print(f" Amount in tx: {transaction['amount']} (type: {type(transaction['amount'])})")
305
+ print(f" Amount in reconstructed: {reconstructed['amount']} (type: {type(reconstructed['amount'])})")
306
+
307
+ # 5. Try different JSON serialization options
308
+ print("\n5. Trying different JSON formats...")
309
+ formats = [
310
+ ("Compact", lambda x: json.dumps(x, sort_keys=True, separators=(',', ':'))),
311
+ ("Default", lambda x: json.dumps(x, sort_keys=True)),
312
+ ("Indented", lambda x: json.dumps(x, sort_keys=True, indent=2)),
313
+ ]
314
+
315
+ for name, formatter in formats:
316
+ formatted = formatter(reconstructed)
317
+ is_valid_test = self.key_manager.verify_signature(formatted, signature, public_key)
318
+ print(f" {name} format: {is_valid_test} (length: {len(formatted)})")
319
+
320
+ print("="*60 + "\n")
321
+ def assess_transaction_risk(self, transaction: Dict) -> Tuple[str, str]:
322
+ """Assess transaction risk level"""
323
+ return self.security.assess_risk(transaction)
324
+
325
+ # MEMPOOL MANAGEMENT
326
+ def get_pending_transactions(self, address: str = None) -> List[Dict]:
327
+ """Get pending transactions from mempool"""
328
+ return self.mempool_manager.get_pending_transactions(address)
329
+
330
+ def is_transaction_pending(self, tx_hash: str) -> bool:
331
+ """Check if transaction is pending in mempool"""
332
+ return self.mempool_manager.is_transaction_pending(tx_hash)
333
+
334
+ def _get_signing_data(self, transaction: Dict) -> str:
335
+ """Create data string for signing - MUST match signing process exactly"""
336
+ # IMPORTANT: During signing, the transaction does NOT have 'public_key' yet!
337
+ # It's added AFTER signing. So we must exclude it during verification too.
338
+
339
+ # Fields that should NOT be included in signing data:
340
+ exclude_fields = ['signature', 'hash', 'public_key']
341
+
342
+ # Create copy without excluded fields
343
+ tx_copy = {k: v for k, v in transaction.items()
344
+ if k not in exclude_fields}
345
+
346
+ # Use the EXACT same format as during creation
347
+ import json
348
+
349
+ # Ensure consistent data types
350
+ for key in ['amount', 'fee']:
351
+ if key in tx_copy:
352
+ tx_copy[key] = float(tx_copy[key])
353
+
354
+ if 'timestamp' in tx_copy:
355
+ tx_copy['timestamp'] = int(tx_copy['timestamp'])
356
+
357
+ # Use COMPACT JSON (no extra spaces) - this is critical!
358
+ return json.dumps(tx_copy, sort_keys=True, separators=(',', ':'))
359
+ def _calculate_transaction_hash(self, transaction: Dict) -> str:
360
+ """Calculate transaction hash"""
361
+ # Remove existing hash if present
362
+ tx_copy = transaction.copy()
363
+ tx_copy.pop("hash", None)
364
+
365
+ # Convert to JSON and hash
366
+ data_string = json.dumps(tx_copy, sort_keys=True)
367
+ return hashlib.sha256(data_string.encode()).hexdigest()
368
+
369
+ def _generate_reward_hash(self, to_address: str, amount: float,
370
+ block_height: int) -> str:
371
+ """Generate unique hash for reward transaction"""
372
+ data = f"reward_{to_address}_{amount}_{block_height}_{time.time_ns()}"
373
+ return hashlib.sha256(data.encode()).hexdigest()
374
+
375
+ def stop(self):
376
+ """Stop the transaction manager"""
377
+ self.mempool_manager.stop()
378
+
379
+ # Create a global instance for convenience
380
+ _transaction_manager = None
381
+
382
+ def get_transaction_manager() -> TransactionManager:
383
+ """Get or create global transaction manager instance"""
384
+ global _transaction_manager
385
+ if _transaction_manager is None:
386
+ _transaction_manager = TransactionManager()
387
+ return _transaction_manager
388
+
389
+ # Convenience functions
390
+ def create_transfer(from_address: str, to_address: str, amount: float,
391
+ private_key: str, memo: str = "") -> Dict:
392
+ """Create transfer transaction (convenience function)"""
393
+ mgr = get_transaction_manager()
394
+ return mgr.create_transfer(from_address, to_address, amount, private_key, memo)
395
+
396
+ def send_transaction(transaction: Dict) -> Tuple[bool, str]:
397
+ """Send transaction (convenience function)"""
398
+ mgr = get_transaction_manager()
399
+ return mgr.send_transaction(transaction)