-
Notifications
You must be signed in to change notification settings - Fork 46
/
vanity_address.py
162 lines (126 loc) · 5.47 KB
/
vanity_address.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import logging
import multiprocessing
import signal
import time
import types
import typing
from collections.abc import Callable
from dataclasses import dataclass
from enum import Enum
from multiprocessing import Process, Queue, cpu_count
from timeit import default_timer as timer
import algosdk
from algosdk.mnemonic import from_private_key, to_private_key
logger = logging.getLogger(__name__)
PROGRESS_REFRESH_INTERVAL_SECONDS = 5
class MatchType(Enum):
START = "start"
ANYWHERE = "anywhere"
END = "end"
MatchFunction = Callable[[str, str], bool]
MATCH_FUNCTIONS: dict[MatchType, MatchFunction] = {
MatchType.START: lambda addr, keyword: addr.startswith(keyword),
MatchType.ANYWHERE: lambda addr, keyword: keyword in addr,
MatchType.END: lambda addr, keyword: addr.endswith(keyword),
}
@dataclass
class VanityAccount:
mnemonic: str
address: str
private_key: str
class Counter:
def __init__(self, initial_value: int = 0):
self.val = multiprocessing.RawValue("i", initial_value)
self.lock = multiprocessing.Lock()
def increment(self, value: int = 1) -> None:
with self.lock:
self.val.value += value
@property
def value(self) -> int:
return int(self.val.value)
def _log_progress(counter: Counter, start_time: float) -> None:
"""Logs progress of address matching at regular intervals."""
last_log_time = start_time
try:
while True:
total_count = counter.value
if timer() - last_log_time >= PROGRESS_REFRESH_INTERVAL_SECONDS:
elapsed_time = timer() - start_time
message = (
f"Iterated over ~{total_count} addresses in {elapsed_time:.2f} seconds."
if total_count > 0
else f"Elapsed time: {elapsed_time:.2f} seconds."
)
logger.info(f"Still searching for a match. {message}")
last_log_time = timer()
time.sleep(PROGRESS_REFRESH_INTERVAL_SECONDS)
except KeyboardInterrupt:
return
def _search_for_matching_address(keyword: str, match: MatchType, counter: Counter, queue: Queue) -> None:
"""
Searches for a matching address based on the specified keyword and matching criteria.
Args:
keyword (str): The keyword to search for in the address.
match (MatchType): The matching criteria for the keyword. It can be "start" to match addresses that start with
the keyword, "anywhere" to match addresses that contain the keyword anywhere,
or "end" to match addresses that end with the keyword.
lock (LockBase): A multiprocessing lock object to synchronize access to the shared data.
stop_event (EventClass): A multiprocessing event object to stop the search when a match is found.
shared_data (DictProxy): A multiprocessing dictionary to share data between processes.
"""
try:
local_count = 0
batch_size = 100
while True:
private_key, address = algosdk.account.generate_account() # type: ignore[no-untyped-call]
local_count += 1
if local_count % batch_size == 0:
counter.increment(local_count)
local_count = 0
if MATCH_FUNCTIONS[match](address, keyword):
generated_mnemonic = from_private_key(private_key) # type: ignore[no-untyped-call]
queue.put((address, generated_mnemonic))
return
except KeyboardInterrupt:
return
def generate_vanity_address(keyword: str, match: MatchType) -> VanityAccount:
"""
Generate a vanity address in the Algorand blockchain.
Args:
keyword (str): The keyword to search for in the address.
match (MatchType): The matching criteria for the keyword. It can be "start" to match addresses that start with
the keyword, "anywhere" to match addresses that contain the keyword anywhere,
or "end" to match addresses that end with the keyword.
Returns:
VanityAccount: An object containing the generated mnemonic and address
that match the specified keyword and matching criteria.
"""
jobs: list[Process] = []
def signal_handler(sig: int, frame: types.FrameType | None) -> typing.NoReturn:
logger.debug(f"KeyboardInterrupt captured for {sig} and frame {frame}. Terminating processes...")
for p in jobs:
p.terminate()
raise KeyboardInterrupt
num_processes = cpu_count()
logger.info(f"Using {num_processes} processes to search for a matching address...")
queue: Queue = Queue()
counter = Counter()
start_time: float = timer()
for _ in range(num_processes):
process = Process(target=_search_for_matching_address, args=(keyword, match, counter, queue))
jobs.append(process)
process.start()
# Start the logger process
logger_process = Process(target=_log_progress, args=(counter, start_time))
jobs.append(logger_process)
logger_process.start()
signal.signal(signal.SIGINT, signal_handler) # capture ctrl-c so we can report attempts and running time
address, mnemonic = queue.get() # this will return once one of the spawned processes finds a match
logger.info(f"Vanity address generation time: {timer() - start_time:.2f} seconds")
for p in jobs:
p.terminate()
return VanityAccount(
mnemonic=mnemonic,
address=address,
private_key=to_private_key(mnemonic), # type: ignore[no-untyped-call]
)