forked from phpcoinn/node
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvanitygen.php
More file actions
223 lines (193 loc) · 7.29 KB
/
vanitygen.php
File metadata and controls
223 lines (193 loc) · 7.29 KB
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
<?php
const VANITYGEN_NAME = 'PHPCoin Vanity Address Generator';
const VANITYGEN_VERSION = '0.0.2';
const VANITYGEN_USAGE = 'Usage: php vanitygen.php prefix [-c] [-d]' . PHP_EOL .
' prefix Prefix for the PHPCoin address (e.g., "Php")' . PHP_EOL .
' -c Case sensitive matching' . PHP_EOL .
' -d Enable debug output' . PHP_EOL;
const VANITYGEN_URL = 'https://github.com/phpcoinn/node/blob/main/utils/vanitygen.php';
const DEFAULT_CHAIN_ID = '00';
$debug = false;
print VANITYGEN_NAME . ' v' . VANITYGEN_VERSION . PHP_EOL;
setupOrExit();
$options = getOptionsOrExit($argv);
// First, validate the original, user-provided prefix.
validatePrefix($options['prefix'], $options['case_sensitive']);
// If validation passes, normalize the prefix for the generator.
$options['prefix'] = normalizePrefix($options['prefix']);
// Finally, generate the address with the normalized prefix.
generateVanityAddress($options);
print PHP_EOL . 'Exiting ' . VANITYGEN_NAME . PHP_EOL;
/**
* Normalizes a prefix to ensure it starts with a capital 'P'.
*
* @param string $prefix The user-provided prefix.
* @return string The normalized prefix.
*/
function normalizePrefix(string $prefix): string
{
// If the prefix starts with 'p' (case-insensitively), replace the first char with 'P'.
if (strtolower(substr($prefix, 0, 1)) === 'p') {
return 'P' . substr($prefix, 1);
}
// Otherwise, prepend 'P'.
return 'P' . $prefix;
}
/**
* Validates the provided prefix against the list of mathematically possible second characters.
*
* Exits with an error message if the prefix is invalid.
*
* @param string $prefix The prefix to validate.
* @param bool $caseSensitive Whether the check should be case sensitive.
*/
function validatePrefix(string $prefix, bool $caseSensitive): void
{
// It is assumed the prefix has been normalized before being passed to this function.
if (strlen($prefix) < 2) {
return; // Not long enough for a 2-char check.
}
$prefixToCheck = substr($prefix, 0, 2);
$validPrefixes = [
'PX', 'PY', 'PZ', 'Pa', 'Pb', 'Pc', 'Pd', 'Pe', 'Pf', 'Pg', 'Ph', 'Pi', 'Pj', 'Pk', 'Pm', 'Pn', 'Po', 'Pp', 'Pq', 'Pr', 'Ps', 'Pt', 'Pu', 'Pv', 'Pw'
];
$isValid = false;
if ($caseSensitive) {
$isValid = in_array($prefixToCheck, $validPrefixes, true);
} else {
// For case-insensitive, we compare the lowercase versions.
$lowerValidPrefixes = array_map('strtolower', $validPrefixes);
$isValid = in_array(strtolower($prefixToCheck), $lowerValidPrefixes);
}
if (!$isValid) {
sort($validPrefixes);
$validPrefixesList = implode(', ', $validPrefixes);
exit('ERROR: Impossible prefix.' . PHP_EOL . 'Valid prefixes start with: ' . $validPrefixesList . PHP_EOL);
}
}
/**
* Generates a vanity PHPCoin address based on the provided options.
*
* @param array $options An associative array with keys:
* - 'prefix': The desired prefix for the address.
* - 'case_sensitive': Boolean indicating if the match should be case sensitive.
* @return array The generated account details containing 'address', 'public_key', and 'private_key'.
*/
function generateVanityAddress(array $options): array
{
$prefix = $options['prefix'];
$caseSensitive = $options['case_sensitive'];
print 'Prefix: ' . $prefix . PHP_EOL;
print 'Case Sensitive: ' . ($caseSensitive ? 'Yes' : 'No') . PHP_EOL;
$count = 0;
while (true) {
$account = Account::generateAcccount();
$address = $account['address'];
$count++;
_debug('Generation '. $count . ': ' . $address);
if (! $caseSensitive) {
$address = strtolower($address);
$prefix = strtolower($prefix);
}
if (str_starts_with($address, $prefix)) {
print 'Found vanity PHPCoin address after '. $count . ' tries!' . PHP_EOL;
print_r($account);
return $account;
}
if ($count % 500 === 0) {
print 'Generated ' . $count . ' PHPCoin addresses...' . PHP_EOL;
}
}
}
/**
* Parses command-line arguments into options and positional arguments.
*
* Supports:
* - Positional arguments (e.g., "prefix")
* - Long options (e.g., --foo)
* - Long options with value (e.g., --value=foo or --value foo)
* - Short options (e.g., -f)
* - Short options with value (e.g., -v=foo or -v foo)
*/
function getOptionsOrExit(array $argv): array
{
global $debug;
$options = [];
$arguments = [];
// Start at 1 to skip the script name ($argv[0])
for ($i = 1; $i < count($argv); $i++) {
$item = $argv[$i];
if (strpos($item, '--') === 0) { // 1. Long Option: --key or --key=value or --key value
$key = substr($item, 2);
$value = true; // Default for flags like --verbose
if (strpos($key, '=') !== false) { // Check for --key=value format
list($key, $value) = explode('=', $key, 2);
} else if (isset($argv[$i + 1]) && strpos($argv[$i + 1], '-') !== 0) {
// Check for --key value format
// Is there a next item? AND Is the next item NOT another option?
$value = $argv[$i + 1];
$i++; // Skip the next item, it's been consumed as a value
}
$options[$key] = $value;
} else if (strpos($item, '-') === 0) { // 2. Short Option: -k or -k=value or -k value
$key = substr($item, 1);
$value = true; // Default for flags like -v
// Check for -k=value format
if (strpos($key, '=') !== false) {
list($key, $value) = explode('=', $key, 2);
}
// Check for -k value format
// Is there a next item? AND Is the next item NOT another option?
else if (isset($argv[$i + 1]) && strpos($argv[$i + 1], '-') !== 0) {
$value = $argv[$i + 1];
$i++; // Skip the next item
}
$options[$key] = $value;
} else { // 3. Positional Argument
$arguments[] = $item;
}
}
if (empty($options) && empty($arguments)) {
exit(VANITYGEN_USAGE . PHP_EOL);
}
if (empty($arguments[0])) {
exit('ERROR: No prefix provided.' . PHP_EOL . VANITYGEN_USAGE . PHP_EOL);
}
if (isset($options['d'])) {
$debug = true;
}
return [
'prefix' => $arguments[0],
'case_sensitive' => isset($options['c'])
];
}
/**
* Sets up the environment or exits if conditions are not met.
*
* Ensures the script is run from the command line and that the autoload file exists.
*/
function setupOrExit(): void
{
if (php_sapi_name() !== 'cli') {
exit('ERROR: This script must be run from the command line' . PHP_EOL);
};
$autoload = Phar::running()
? 'vendor/autoload.php'
: dirname(__DIR__) . '/vendor/autoload.php';
if (! file_exists($autoload)) {
exit('ERROR: Autoload file not found. Please run "composer install".' . PHP_EOL);
}
require_once $autoload;
}
/**
* Outputs debug messages if debugging is enabled.
*
* @param string $message The debug message to output.
*/
function _debug(string $message): void
{
global $debug;
if ($debug) {
print '[DEBUG] ' . $message . PHP_EOL;
}
}