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.
- core/__init__.py +0 -0
- core/blockchain.py +172 -0
- core/crypto.py +32 -0
- core/wallet.py +408 -0
- gtx/__init__.py +0 -0
- gtx/bill_registry.py +122 -0
- gtx/digital_bill.py +273 -0
- gtx/genesis.py +338 -0
- lunalib/__init__.py +21 -0
- lunalib/cli.py +18 -0
- lunalib/core/__init__.py +0 -0
- lunalib/core/blockchain.py +803 -0
- lunalib/core/crypto.py +270 -0
- lunalib/core/mempool.py +342 -0
- lunalib/core/sm2.py +723 -0
- lunalib/core/wallet.py +1342 -0
- lunalib/core/wallet_manager.py +638 -0
- lunalib/core/wallet_sync_helper.py +163 -0
- lunalib/gtx/__init__.py +0 -0
- lunalib/gtx/bill_registry.py +122 -0
- lunalib/gtx/digital_bill.py +273 -0
- lunalib/gtx/genesis.py +349 -0
- lunalib/luna_lib.py +87 -0
- lunalib/mining/__init__.py +0 -0
- lunalib/mining/cuda_manager.py +137 -0
- lunalib/mining/difficulty.py +106 -0
- lunalib/mining/miner.py +617 -0
- lunalib/requirements.txt +44 -0
- lunalib/storage/__init__.py +0 -0
- lunalib/storage/cache.py +148 -0
- lunalib/storage/database.py +222 -0
- lunalib/storage/encryption.py +105 -0
- lunalib/transactions/__init__.py +0 -0
- lunalib/transactions/security.py +234 -0
- lunalib/transactions/transactions.py +399 -0
- lunalib/transactions/validator.py +71 -0
- lunalib-1.5.1.dist-info/METADATA +283 -0
- lunalib-1.5.1.dist-info/RECORD +53 -0
- lunalib-1.5.1.dist-info/WHEEL +5 -0
- lunalib-1.5.1.dist-info/entry_points.txt +2 -0
- lunalib-1.5.1.dist-info/top_level.txt +6 -0
- mining/__init__.py +0 -0
- mining/cuda_manager.py +137 -0
- mining/difficulty.py +106 -0
- mining/miner.py +107 -0
- storage/__init__.py +0 -0
- storage/cache.py +148 -0
- storage/database.py +222 -0
- storage/encryption.py +105 -0
- transactions/__init__.py +0 -0
- transactions/security.py +172 -0
- transactions/transactions.py +424 -0
- 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)
|