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
mining/miner.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import threading
|
|
5
|
+
from typing import Dict, Optional
|
|
6
|
+
from mining.difficulty import DifficultySystem
|
|
7
|
+
from gtx.digital_bill import DigitalBill
|
|
8
|
+
|
|
9
|
+
class GenesisMiner:
|
|
10
|
+
"""Mines GTX Genesis bills with configurable difficulty"""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.difficulty_system = DifficultySystem()
|
|
14
|
+
self.mining_active = False
|
|
15
|
+
self.current_thread = None
|
|
16
|
+
|
|
17
|
+
def mine_bill(self, denomination, user_address, bill_data=None):
|
|
18
|
+
"""Mine a GTX Genesis bill"""
|
|
19
|
+
difficulty = self.difficulty_system.get_bill_difficulty(denomination)
|
|
20
|
+
|
|
21
|
+
digital_bill = DigitalBill(
|
|
22
|
+
denomination=denomination,
|
|
23
|
+
user_address=user_address,
|
|
24
|
+
difficulty=difficulty,
|
|
25
|
+
bill_data=bill_data or {}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
print(f"⛏️ Mining GTX ${denomination:,} Bill - Difficulty: {difficulty} zeros")
|
|
29
|
+
|
|
30
|
+
start_time = time.time()
|
|
31
|
+
mining_result = self._perform_mining(digital_bill, difficulty)
|
|
32
|
+
|
|
33
|
+
if mining_result["success"]:
|
|
34
|
+
mining_time = time.time() - start_time
|
|
35
|
+
bill = digital_bill.finalize(
|
|
36
|
+
hash=mining_result["hash"],
|
|
37
|
+
nonce=mining_result["nonce"],
|
|
38
|
+
mining_time=mining_time
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
print(f"✅ Successfully mined GTX ${denomination:,} bill!")
|
|
42
|
+
print(f"⏱️ Mining time: {mining_time:.2f}s")
|
|
43
|
+
|
|
44
|
+
return bill
|
|
45
|
+
else:
|
|
46
|
+
return {"success": False, "error": "Mining failed"}
|
|
47
|
+
|
|
48
|
+
def _perform_mining(self, digital_bill, difficulty):
|
|
49
|
+
"""Perform proof-of-work mining"""
|
|
50
|
+
target = "0" * difficulty
|
|
51
|
+
nonce = 0
|
|
52
|
+
start_time = time.time()
|
|
53
|
+
|
|
54
|
+
while self.mining_active:
|
|
55
|
+
mining_data = digital_bill.get_mining_data(nonce)
|
|
56
|
+
data_string = json.dumps(mining_data, sort_keys=True)
|
|
57
|
+
bill_hash = hashlib.sha256(data_string.encode()).hexdigest()
|
|
58
|
+
|
|
59
|
+
if bill_hash.startswith(target):
|
|
60
|
+
mining_time = time.time() - start_time
|
|
61
|
+
return {
|
|
62
|
+
"success": True,
|
|
63
|
+
"hash": bill_hash,
|
|
64
|
+
"nonce": nonce,
|
|
65
|
+
"mining_time": mining_time
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
nonce += 1
|
|
69
|
+
|
|
70
|
+
# Progress updates
|
|
71
|
+
if nonce % 100000 == 0:
|
|
72
|
+
current_time = time.time()
|
|
73
|
+
hashrate = nonce / (current_time - start_time)
|
|
74
|
+
print(f"⏳ Attempts: {nonce:,} | Rate: {hashrate:,.0f} H/s")
|
|
75
|
+
|
|
76
|
+
return {"success": False, "error": "Mining stopped"}
|
|
77
|
+
|
|
78
|
+
def start_auto_mining(self, denominations, user_address, callback=None):
|
|
79
|
+
"""Start auto-mining multiple bills"""
|
|
80
|
+
self.mining_active = True
|
|
81
|
+
|
|
82
|
+
def auto_mine():
|
|
83
|
+
results = []
|
|
84
|
+
for denomination in denominations:
|
|
85
|
+
if not self.mining_active:
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
result = self.mine_bill(denomination, user_address)
|
|
89
|
+
results.append(result)
|
|
90
|
+
|
|
91
|
+
if callback:
|
|
92
|
+
callback(result)
|
|
93
|
+
|
|
94
|
+
time.sleep(1)
|
|
95
|
+
|
|
96
|
+
return results
|
|
97
|
+
|
|
98
|
+
self.current_thread = threading.Thread(target=auto_mine, daemon=True)
|
|
99
|
+
self.current_thread.start()
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
def stop_mining(self):
|
|
103
|
+
"""Stop all mining activities"""
|
|
104
|
+
self.mining_active = False
|
|
105
|
+
if self.current_thread:
|
|
106
|
+
self.current_thread.join(timeout=5)
|
|
107
|
+
print("🛑 Mining stopped")
|
storage/__init__.py
ADDED
|
File without changes
|
storage/cache.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sqlite3
|
|
3
|
+
import pickle
|
|
4
|
+
import gzip
|
|
5
|
+
import time
|
|
6
|
+
from typing import Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
class BlockchainCache:
|
|
9
|
+
"""Caches blockchain data to avoid repeated network requests"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, cache_dir=None):
|
|
12
|
+
if cache_dir is None:
|
|
13
|
+
cache_dir = os.path.join(os.path.expanduser("~"), ".luna_wallet")
|
|
14
|
+
self.cache_dir = cache_dir
|
|
15
|
+
self.cache_file = os.path.join(cache_dir, "blockchain_cache.db")
|
|
16
|
+
self._init_cache()
|
|
17
|
+
|
|
18
|
+
def _init_cache(self):
|
|
19
|
+
"""Initialize cache database"""
|
|
20
|
+
conn = sqlite3.connect(self.cache_file)
|
|
21
|
+
cursor = conn.cursor()
|
|
22
|
+
|
|
23
|
+
cursor.execute('''
|
|
24
|
+
CREATE TABLE IF NOT EXISTS blocks (
|
|
25
|
+
height INTEGER PRIMARY KEY,
|
|
26
|
+
hash TEXT UNIQUE,
|
|
27
|
+
block_data BLOB,
|
|
28
|
+
timestamp REAL,
|
|
29
|
+
last_accessed REAL
|
|
30
|
+
)
|
|
31
|
+
''')
|
|
32
|
+
|
|
33
|
+
cursor.execute('''
|
|
34
|
+
CREATE TABLE IF NOT EXISTS mempool (
|
|
35
|
+
tx_hash TEXT PRIMARY KEY,
|
|
36
|
+
tx_data BLOB,
|
|
37
|
+
received_time REAL,
|
|
38
|
+
address_involved TEXT
|
|
39
|
+
)
|
|
40
|
+
''')
|
|
41
|
+
|
|
42
|
+
cursor.execute('''
|
|
43
|
+
CREATE TABLE IF NOT EXISTS cache_meta (
|
|
44
|
+
key TEXT PRIMARY KEY,
|
|
45
|
+
value TEXT
|
|
46
|
+
)
|
|
47
|
+
''')
|
|
48
|
+
|
|
49
|
+
conn.commit()
|
|
50
|
+
conn.close()
|
|
51
|
+
|
|
52
|
+
def save_block(self, height: int, block_hash: str, block_data: Dict):
|
|
53
|
+
"""Save block to cache"""
|
|
54
|
+
try:
|
|
55
|
+
conn = sqlite3.connect(self.cache_file)
|
|
56
|
+
cursor = conn.cursor()
|
|
57
|
+
|
|
58
|
+
compressed_data = gzip.compress(pickle.dumps(block_data))
|
|
59
|
+
|
|
60
|
+
cursor.execute('''
|
|
61
|
+
INSERT OR REPLACE INTO blocks
|
|
62
|
+
(height, hash, block_data, timestamp, last_accessed)
|
|
63
|
+
VALUES (?, ?, ?, ?, ?)
|
|
64
|
+
''', (height, block_hash, compressed_data, time.time(), time.time()))
|
|
65
|
+
|
|
66
|
+
conn.commit()
|
|
67
|
+
conn.close()
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print(f"Cache save error: {e}")
|
|
70
|
+
|
|
71
|
+
def get_block(self, height: int) -> Optional[Dict]:
|
|
72
|
+
"""Get block from cache"""
|
|
73
|
+
try:
|
|
74
|
+
conn = sqlite3.connect(self.cache_file)
|
|
75
|
+
cursor = conn.cursor()
|
|
76
|
+
|
|
77
|
+
cursor.execute('SELECT block_data FROM blocks WHERE height = ?', (height,))
|
|
78
|
+
result = cursor.fetchone()
|
|
79
|
+
|
|
80
|
+
if result:
|
|
81
|
+
# Update access time
|
|
82
|
+
cursor.execute('UPDATE blocks SET last_accessed = ? WHERE height = ?',
|
|
83
|
+
(time.time(), height))
|
|
84
|
+
conn.commit()
|
|
85
|
+
conn.close()
|
|
86
|
+
|
|
87
|
+
return pickle.loads(gzip.decompress(result[0]))
|
|
88
|
+
|
|
89
|
+
conn.close()
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(f"Cache read error: {e}")
|
|
92
|
+
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
def get_block_range(self, start_height: int, end_height: int) -> List[Dict]:
|
|
96
|
+
"""Get multiple blocks from cache"""
|
|
97
|
+
blocks = []
|
|
98
|
+
try:
|
|
99
|
+
conn = sqlite3.connect(self.cache_file)
|
|
100
|
+
cursor = conn.cursor()
|
|
101
|
+
|
|
102
|
+
cursor.execute('''
|
|
103
|
+
SELECT height, block_data FROM blocks
|
|
104
|
+
WHERE height BETWEEN ? AND ?
|
|
105
|
+
ORDER BY height
|
|
106
|
+
''', (start_height, end_height))
|
|
107
|
+
|
|
108
|
+
results = cursor.fetchall()
|
|
109
|
+
conn.close()
|
|
110
|
+
|
|
111
|
+
for height, block_data in results:
|
|
112
|
+
try:
|
|
113
|
+
block = pickle.loads(gzip.decompress(block_data))
|
|
114
|
+
blocks.append(block)
|
|
115
|
+
except:
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(f"Block range cache error: {e}")
|
|
120
|
+
|
|
121
|
+
return blocks
|
|
122
|
+
|
|
123
|
+
def get_highest_cached_height(self) -> int:
|
|
124
|
+
"""Get the highest block height in cache"""
|
|
125
|
+
try:
|
|
126
|
+
conn = sqlite3.connect(self.cache_file)
|
|
127
|
+
cursor = conn.cursor()
|
|
128
|
+
|
|
129
|
+
cursor.execute('SELECT MAX(height) FROM blocks')
|
|
130
|
+
result = cursor.fetchone()
|
|
131
|
+
conn.close()
|
|
132
|
+
|
|
133
|
+
return result[0] if result[0] is not None else -1
|
|
134
|
+
except:
|
|
135
|
+
return -1
|
|
136
|
+
|
|
137
|
+
def clear_old_blocks(self, max_age_hours=24):
|
|
138
|
+
"""Clear blocks older than specified hours"""
|
|
139
|
+
try:
|
|
140
|
+
cutoff = time.time() - (max_age_hours * 3600)
|
|
141
|
+
conn = sqlite3.connect(self.cache_file)
|
|
142
|
+
cursor = conn.cursor()
|
|
143
|
+
|
|
144
|
+
cursor.execute('DELETE FROM blocks WHERE last_accessed < ?', (cutoff,))
|
|
145
|
+
conn.commit()
|
|
146
|
+
conn.close()
|
|
147
|
+
except Exception as e:
|
|
148
|
+
print(f"Cache cleanup error: {e}")
|
storage/database.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sqlite3
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from typing import Dict, List, Optional, Any
|
|
6
|
+
|
|
7
|
+
class WalletDatabase:
|
|
8
|
+
"""Manages wallet data storage"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, db_path=None):
|
|
11
|
+
self.db_path = db_path or os.path.join(os.path.expanduser("~"), ".luna_wallet", "wallets.db")
|
|
12
|
+
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
|
13
|
+
self._init_database()
|
|
14
|
+
|
|
15
|
+
def _init_database(self):
|
|
16
|
+
"""Initialize wallet database"""
|
|
17
|
+
conn = sqlite3.connect(self.db_path)
|
|
18
|
+
cursor = conn.cursor()
|
|
19
|
+
|
|
20
|
+
# Wallets table
|
|
21
|
+
cursor.execute('''
|
|
22
|
+
CREATE TABLE IF NOT EXISTS wallets (
|
|
23
|
+
address TEXT PRIMARY KEY,
|
|
24
|
+
label TEXT,
|
|
25
|
+
public_key TEXT,
|
|
26
|
+
encrypted_private_key TEXT,
|
|
27
|
+
balance REAL DEFAULT 0.0,
|
|
28
|
+
created REAL,
|
|
29
|
+
last_accessed REAL,
|
|
30
|
+
metadata TEXT
|
|
31
|
+
)
|
|
32
|
+
''')
|
|
33
|
+
|
|
34
|
+
# Transactions table
|
|
35
|
+
cursor.execute('''
|
|
36
|
+
CREATE TABLE IF NOT EXISTS transactions (
|
|
37
|
+
tx_hash TEXT PRIMARY KEY,
|
|
38
|
+
wallet_address TEXT,
|
|
39
|
+
tx_type TEXT,
|
|
40
|
+
from_address TEXT,
|
|
41
|
+
to_address TEXT,
|
|
42
|
+
amount REAL,
|
|
43
|
+
fee REAL,
|
|
44
|
+
timestamp REAL,
|
|
45
|
+
block_height INTEGER,
|
|
46
|
+
status TEXT,
|
|
47
|
+
memo TEXT,
|
|
48
|
+
raw_data TEXT,
|
|
49
|
+
FOREIGN KEY (wallet_address) REFERENCES wallets (address)
|
|
50
|
+
)
|
|
51
|
+
''')
|
|
52
|
+
|
|
53
|
+
# Pending transactions table
|
|
54
|
+
cursor.execute('''
|
|
55
|
+
CREATE TABLE IF NOT EXISTS pending_transactions (
|
|
56
|
+
tx_hash TEXT PRIMARY KEY,
|
|
57
|
+
wallet_address TEXT,
|
|
58
|
+
from_address TEXT,
|
|
59
|
+
to_address TEXT,
|
|
60
|
+
amount REAL,
|
|
61
|
+
fee REAL,
|
|
62
|
+
created_time REAL,
|
|
63
|
+
status TEXT DEFAULT 'pending',
|
|
64
|
+
retry_count INTEGER DEFAULT 0,
|
|
65
|
+
last_retry REAL,
|
|
66
|
+
raw_data TEXT
|
|
67
|
+
)
|
|
68
|
+
''')
|
|
69
|
+
|
|
70
|
+
conn.commit()
|
|
71
|
+
conn.close()
|
|
72
|
+
|
|
73
|
+
def save_wallet(self, wallet_data: Dict) -> bool:
|
|
74
|
+
"""Save wallet to database"""
|
|
75
|
+
try:
|
|
76
|
+
conn = sqlite3.connect(self.db_path)
|
|
77
|
+
cursor = conn.cursor()
|
|
78
|
+
|
|
79
|
+
cursor.execute('''
|
|
80
|
+
INSERT OR REPLACE INTO wallets
|
|
81
|
+
(address, label, public_key, encrypted_private_key, balance, created, last_accessed, metadata)
|
|
82
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
83
|
+
''', (
|
|
84
|
+
wallet_data['address'],
|
|
85
|
+
wallet_data.get('label', ''),
|
|
86
|
+
wallet_data.get('public_key', ''),
|
|
87
|
+
wallet_data.get('encrypted_private_key', ''),
|
|
88
|
+
wallet_data.get('balance', 0.0),
|
|
89
|
+
wallet_data.get('created', time.time()),
|
|
90
|
+
time.time(),
|
|
91
|
+
json.dumps(wallet_data.get('metadata', {}))
|
|
92
|
+
))
|
|
93
|
+
|
|
94
|
+
conn.commit()
|
|
95
|
+
conn.close()
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f"Save wallet error: {e}")
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
def load_wallet(self, address: str) -> Optional[Dict]:
|
|
103
|
+
"""Load wallet from database"""
|
|
104
|
+
try:
|
|
105
|
+
conn = sqlite3.connect(self.db_path)
|
|
106
|
+
cursor = conn.cursor()
|
|
107
|
+
|
|
108
|
+
cursor.execute('SELECT * FROM wallets WHERE address = ?', (address,))
|
|
109
|
+
result = cursor.fetchone()
|
|
110
|
+
conn.close()
|
|
111
|
+
|
|
112
|
+
if result:
|
|
113
|
+
return {
|
|
114
|
+
'address': result[0],
|
|
115
|
+
'label': result[1],
|
|
116
|
+
'public_key': result[2],
|
|
117
|
+
'encrypted_private_key': result[3],
|
|
118
|
+
'balance': result[4],
|
|
119
|
+
'created': result[5],
|
|
120
|
+
'last_accessed': result[6],
|
|
121
|
+
'metadata': json.loads(result[7]) if result[7] else {}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print(f"Load wallet error: {e}")
|
|
126
|
+
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
def save_transaction(self, transaction: Dict, wallet_address: str) -> bool:
|
|
130
|
+
"""Save transaction to database"""
|
|
131
|
+
try:
|
|
132
|
+
conn = sqlite3.connect(self.db_path)
|
|
133
|
+
cursor = conn.cursor()
|
|
134
|
+
|
|
135
|
+
cursor.execute('''
|
|
136
|
+
INSERT OR REPLACE INTO transactions
|
|
137
|
+
(tx_hash, wallet_address, tx_type, from_address, to_address, amount, fee,
|
|
138
|
+
timestamp, block_height, status, memo, raw_data)
|
|
139
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
140
|
+
''', (
|
|
141
|
+
transaction.get('hash', ''),
|
|
142
|
+
wallet_address,
|
|
143
|
+
transaction.get('type', 'transfer'),
|
|
144
|
+
transaction.get('from', ''),
|
|
145
|
+
transaction.get('to', ''),
|
|
146
|
+
transaction.get('amount', 0),
|
|
147
|
+
transaction.get('fee', 0),
|
|
148
|
+
transaction.get('timestamp', time.time()),
|
|
149
|
+
transaction.get('block_height'),
|
|
150
|
+
transaction.get('status', 'confirmed'),
|
|
151
|
+
transaction.get('memo', ''),
|
|
152
|
+
json.dumps(transaction)
|
|
153
|
+
))
|
|
154
|
+
|
|
155
|
+
conn.commit()
|
|
156
|
+
conn.close()
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
print(f"Save transaction error: {e}")
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
def get_wallet_transactions(self, wallet_address: str, limit: int = 100) -> List[Dict]:
|
|
164
|
+
"""Get transactions for a wallet"""
|
|
165
|
+
try:
|
|
166
|
+
conn = sqlite3.connect(self.db_path)
|
|
167
|
+
cursor = conn.cursor()
|
|
168
|
+
|
|
169
|
+
cursor.execute('''
|
|
170
|
+
SELECT raw_data FROM transactions
|
|
171
|
+
WHERE wallet_address = ?
|
|
172
|
+
ORDER BY timestamp DESC
|
|
173
|
+
LIMIT ?
|
|
174
|
+
''', (wallet_address, limit))
|
|
175
|
+
|
|
176
|
+
results = cursor.fetchall()
|
|
177
|
+
conn.close()
|
|
178
|
+
|
|
179
|
+
transactions = []
|
|
180
|
+
for result in results:
|
|
181
|
+
try:
|
|
182
|
+
tx = json.loads(result[0])
|
|
183
|
+
transactions.append(tx)
|
|
184
|
+
except:
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
return transactions
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
print(f"Get transactions error: {e}")
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
def save_pending_transaction(self, transaction: Dict, wallet_address: str) -> bool:
|
|
194
|
+
"""Save pending transaction"""
|
|
195
|
+
try:
|
|
196
|
+
conn = sqlite3.connect(self.db_path)
|
|
197
|
+
cursor = conn.cursor()
|
|
198
|
+
|
|
199
|
+
cursor.execute('''
|
|
200
|
+
INSERT OR REPLACE INTO pending_transactions
|
|
201
|
+
(tx_hash, wallet_address, from_address, to_address, amount, fee,
|
|
202
|
+
created_time, status, raw_data)
|
|
203
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
204
|
+
''', (
|
|
205
|
+
transaction.get('hash', ''),
|
|
206
|
+
wallet_address,
|
|
207
|
+
transaction.get('from', ''),
|
|
208
|
+
transaction.get('to', ''),
|
|
209
|
+
transaction.get('amount', 0),
|
|
210
|
+
transaction.get('fee', 0),
|
|
211
|
+
time.time(),
|
|
212
|
+
'pending',
|
|
213
|
+
json.dumps(transaction)
|
|
214
|
+
))
|
|
215
|
+
|
|
216
|
+
conn.commit()
|
|
217
|
+
conn.close()
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
print(f"Save pending transaction error: {e}")
|
|
222
|
+
return False
|
storage/encryption.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import base64
|
|
4
|
+
import hashlib
|
|
5
|
+
from cryptography.fernet import Fernet
|
|
6
|
+
from cryptography.hazmat.primitives import hashes
|
|
7
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
8
|
+
from typing import Dict, Optional
|
|
9
|
+
class EncryptionManager:
|
|
10
|
+
"""Handles encryption and decryption of sensitive data"""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.salt = b'luna_wallet_salt' # In production, use random salt per wallet
|
|
14
|
+
|
|
15
|
+
def encrypt_wallet(self, wallet_data: Dict, password: str) -> Dict:
|
|
16
|
+
"""Encrypt wallet data"""
|
|
17
|
+
try:
|
|
18
|
+
# Derive key from password
|
|
19
|
+
key = self._derive_key(password)
|
|
20
|
+
fernet = Fernet(key)
|
|
21
|
+
|
|
22
|
+
# Encrypt private key separately
|
|
23
|
+
private_key = wallet_data.get('private_key', '')
|
|
24
|
+
if private_key:
|
|
25
|
+
encrypted_private = fernet.encrypt(private_key.encode()).decode()
|
|
26
|
+
wallet_data['encrypted_private_key'] = encrypted_private
|
|
27
|
+
del wallet_data['private_key']
|
|
28
|
+
|
|
29
|
+
# Encrypt entire wallet data
|
|
30
|
+
wallet_json = json.dumps(wallet_data)
|
|
31
|
+
encrypted_wallet = fernet.encrypt(wallet_json.encode()).decode()
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
'encrypted_data': encrypted_wallet,
|
|
35
|
+
'version': '1.0',
|
|
36
|
+
'salt': base64.b64encode(self.salt).decode()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
except Exception as e:
|
|
40
|
+
print(f"Encryption error: {e}")
|
|
41
|
+
return {}
|
|
42
|
+
|
|
43
|
+
def decrypt_wallet(self, encrypted_data: Dict, password: str) -> Optional[Dict]:
|
|
44
|
+
"""Decrypt wallet data"""
|
|
45
|
+
try:
|
|
46
|
+
encrypted_wallet = encrypted_data.get('encrypted_data', '')
|
|
47
|
+
if not encrypted_wallet:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
# Derive key from password
|
|
51
|
+
key = self._derive_key(password)
|
|
52
|
+
fernet = Fernet(key)
|
|
53
|
+
|
|
54
|
+
# Decrypt wallet
|
|
55
|
+
decrypted_data = fernet.decrypt(encrypted_wallet.encode())
|
|
56
|
+
wallet_data = json.loads(decrypted_data.decode())
|
|
57
|
+
|
|
58
|
+
# Decrypt private key if present
|
|
59
|
+
encrypted_private = wallet_data.get('encrypted_private_key')
|
|
60
|
+
if encrypted_private:
|
|
61
|
+
private_key = fernet.decrypt(encrypted_private.encode()).decode()
|
|
62
|
+
wallet_data['private_key'] = private_key
|
|
63
|
+
del wallet_data['encrypted_private_key']
|
|
64
|
+
|
|
65
|
+
return wallet_data
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"Decryption error: {e}")
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
def _derive_key(self, password: str) -> bytes:
|
|
72
|
+
"""Derive encryption key from password"""
|
|
73
|
+
kdf = PBKDF2HMAC(
|
|
74
|
+
algorithm=hashes.SHA256(),
|
|
75
|
+
length=32,
|
|
76
|
+
salt=self.salt,
|
|
77
|
+
iterations=100000,
|
|
78
|
+
)
|
|
79
|
+
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
|
|
80
|
+
return key
|
|
81
|
+
|
|
82
|
+
def verify_password(self, encrypted_data: Dict, password: str) -> bool:
|
|
83
|
+
"""Verify password without full decryption"""
|
|
84
|
+
try:
|
|
85
|
+
# Try to decrypt a small part to verify password
|
|
86
|
+
key = self._derive_key(password)
|
|
87
|
+
fernet = Fernet(key)
|
|
88
|
+
|
|
89
|
+
# This will throw an exception if password is wrong
|
|
90
|
+
fernet.decrypt(encrypted_data.get('encrypted_data', 'A')[:10].encode())
|
|
91
|
+
return True
|
|
92
|
+
except:
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
def encrypt_data(self, data: str, password: str) -> str:
|
|
96
|
+
"""Encrypt arbitrary data"""
|
|
97
|
+
key = self._derive_key(password)
|
|
98
|
+
fernet = Fernet(key)
|
|
99
|
+
return fernet.encrypt(data.encode()).decode()
|
|
100
|
+
|
|
101
|
+
def decrypt_data(self, encrypted_data: str, password: str) -> str:
|
|
102
|
+
"""Decrypt arbitrary data"""
|
|
103
|
+
key = self._derive_key(password)
|
|
104
|
+
fernet = Fernet(key)
|
|
105
|
+
return fernet.decrypt(encrypted_data.encode()).decode()
|
transactions/__init__.py
ADDED
|
File without changes
|