From 9011bcdfdd105b86e9bfafdab1fafd46fd303ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= Date: Sun, 26 Jul 2026 20:20:12 +0000 Subject: [PATCH 01/11] Measure speed in microseconds --- SpeedSample.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SpeedSample.cpp b/SpeedSample.cpp index 8a92d9f..dc584d2 100644 --- a/SpeedSample.cpp +++ b/SpeedSample.cpp @@ -27,8 +27,8 @@ double SpeedSample::getSpeed() const { void SpeedSample::sample(const double V) { const timepoint newTime = now(); - auto delta = std::chrono::duration_cast(newTime - m_lastTime).count(); - m_lSpeeds.push_back((1000 * V) / delta); + auto delta = std::chrono::duration_cast(newTime - m_lastTime).count(); + m_lSpeeds.push_back((1000000.0 * V) / delta); m_lastTime = newTime; if (m_lSpeeds.size() > m_length) { m_lSpeeds.pop_front(); From 7e0437d80011052e8ce6e25a42d6b07765370706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= Date: Sun, 26 Jul 2026 21:39:56 +0000 Subject: [PATCH 02/11] Fix OpenCL queue properties --- Dispatcher.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dispatcher.cpp b/Dispatcher.cpp index eb878be..63a8c22 100644 --- a/Dispatcher.cpp +++ b/Dispatcher.cpp @@ -141,7 +141,8 @@ cl_command_queue Dispatcher::Device::createQueue(cl_context & clContext, cl_devi #endif #ifdef CL_VERSION_2_0 - const cl_command_queue ret = clCreateCommandQueueWithProperties(clContext, clDeviceId, &p, NULL); + const cl_queue_properties props[] = { CL_QUEUE_PROPERTIES, p, 0 }; + const cl_command_queue ret = clCreateCommandQueueWithProperties(clContext, clDeviceId, p ? props : NULL, NULL); #else const cl_command_queue ret = clCreateCommandQueue(clContext, clDeviceId, p, NULL); #endif From 7032d46c7f9b488ea3b6aafa5e376b8066bfd1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= Date: Sun, 26 Jul 2026 20:20:23 +0000 Subject: [PATCH 03/11] Align mp_number to 16 bytes --- profanity.cl | 2 +- types.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/profanity.cl b/profanity.cl index 9e2a5af..c55059a 100644 --- a/profanity.cl +++ b/profanity.cl @@ -48,7 +48,7 @@ #define bswap32(n) (rotate(n & 0x00FF00FF, 24U)|(rotate(n, 8U) & 0x00FF00FF)) typedef uint mp_word; -typedef struct { +typedef struct __attribute__((aligned(16))) { mp_word d[MP_WORDS]; } mp_number; diff --git a/types.hpp b/types.hpp index 0799448..7a9c180 100644 --- a/types.hpp +++ b/types.hpp @@ -14,7 +14,7 @@ typedef cl_uint mp_word; -typedef struct { +typedef struct alignas(16) { mp_word d[MP_NWORDS]; } mp_number; From 9e2db7561547908a2c766f64ca14e98c897c2e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= Date: Sun, 26 Jul 2026 20:42:14 +0000 Subject: [PATCH 04/11] Fuse iterate/transform/score into 1 kernel --- Dispatcher.cpp | 30 +++------ Dispatcher.hpp | 2 - Mode.cpp | 29 +++------ Mode.hpp | 2 - profanity.cl | 173 +++++++++++++++++++++++++++---------------------- 5 files changed, 111 insertions(+), 125 deletions(-) diff --git a/Dispatcher.cpp b/Dispatcher.cpp index 63a8c22..fc98fff 100644 --- a/Dispatcher.cpp +++ b/Dispatcher.cpp @@ -186,9 +186,7 @@ Dispatcher::Device::Device(Dispatcher & parent, cl_context & clContext, cl_progr m_clQueue(createQueue(clContext, clDeviceId) ), m_kernelInit( createKernel(clProgram, "profanity_init") ), m_kernelInverse(createKernel(clProgram, "profanity_inverse")), - m_kernelIterate(createKernel(clProgram, "profanity_iterate")), - m_kernelTransform( mode.transformKernel() == "" ? NULL : createKernel(clProgram, mode.transformKernel())), - m_kernelScore(createKernel(clProgram, mode.kernel)), + m_kernelIterate(createKernel(clProgram, mode.kernel)), m_memPrecomp(clContext, m_clQueue, CL_MEM_READ_ONLY | CL_MEM_HOST_WRITE_ONLY, sizeof(g_precomp), g_precomp), m_memPointsDeltaX(clContext, m_clQueue, CL_MEM_READ_WRITE | CL_MEM_HOST_NO_ACCESS, size, true), m_memInversedNegativeDoubleGy(clContext, m_clQueue, CL_MEM_READ_WRITE | CL_MEM_HOST_NO_ACCESS, size, true), @@ -323,23 +321,16 @@ void Dispatcher::initBegin(Device & d) { d.m_memPointsDeltaX.setKernelArg(d.m_kernelInverse, 0); d.m_memInversedNegativeDoubleGy.setKernelArg(d.m_kernelInverse, 1); - // Kernel arguments - profanity_iterate + // Kernel arguments - profanity_iterate_score_* d.m_memPointsDeltaX.setKernelArg(d.m_kernelIterate, 0); d.m_memInversedNegativeDoubleGy.setKernelArg(d.m_kernelIterate, 1); d.m_memPrevLambda.setKernelArg(d.m_kernelIterate, 2); + d.m_memResult.setKernelArg(d.m_kernelIterate, 3); + d.m_memData1.setKernelArg(d.m_kernelIterate, 4); + d.m_memData2.setKernelArg(d.m_kernelIterate, 5); - // Kernel arguments - profanity_transform_* - if(d.m_kernelTransform) { - d.m_memInversedNegativeDoubleGy.setKernelArg(d.m_kernelTransform, 0); - } - - // Kernel arguments - profanity_score_* - d.m_memInversedNegativeDoubleGy.setKernelArg(d.m_kernelScore, 0); - d.m_memResult.setKernelArg(d.m_kernelScore, 1); - d.m_memData1.setKernelArg(d.m_kernelScore, 2); - d.m_memData2.setKernelArg(d.m_kernelScore, 3); - - CLMemory::setKernelArg(d.m_kernelScore, 4, d.m_clScoreMax); // Updated in handleResult() + CLMemory::setKernelArg(d.m_kernelIterate, 6, d.m_clScoreMax); // Updated in handleResult() + CLMemory::setKernelArg(d.m_kernelIterate, 7, (cl_uchar) (m_mode.target == CONTRACT ? 1 : 0)); // Seed device initContinue(d); @@ -434,11 +425,6 @@ void Dispatcher::dispatch(Device & d) { enqueueKernelDevice(d, d.m_kernelIterate, m_size); #endif - if (d.m_kernelTransform) { - enqueueKernelDevice(d, d.m_kernelTransform, m_size); - } - - enqueueKernelDevice(d, d.m_kernelScore, m_size); clFlush(d.m_clQueue); #ifdef PROFANITY_DEBUG @@ -488,7 +474,7 @@ void Dispatcher::handleResult(Device & d) { if (r.found > 0 && i >= d.m_clScoreMax) { d.m_clScoreMax = i; - CLMemory::setKernelArg(d.m_kernelScore, 4, d.m_clScoreMax); + CLMemory::setKernelArg(d.m_kernelIterate, 6, d.m_clScoreMax); std::lock_guard lock(m_mutex); if (i >= m_clScoreMax) { diff --git a/Dispatcher.hpp b/Dispatcher.hpp index 13d75be..58a2aa6 100644 --- a/Dispatcher.hpp +++ b/Dispatcher.hpp @@ -52,8 +52,6 @@ class Dispatcher { cl_kernel m_kernelInit; cl_kernel m_kernelInverse; cl_kernel m_kernelIterate; - cl_kernel m_kernelTransform; - cl_kernel m_kernelScore; CLMemory m_memPrecomp; CLMemory m_memPointsDeltaX; diff --git a/Mode.cpp b/Mode.cpp index c6e3194..e683fbe 100644 --- a/Mode.cpp +++ b/Mode.cpp @@ -8,7 +8,7 @@ Mode::Mode() : score(0) { Mode Mode::benchmark() { Mode r; r.name = "benchmark"; - r.kernel = "profanity_score_benchmark"; + r.kernel = "profanity_iterate_score_benchmark"; return r; } @@ -40,7 +40,7 @@ static std::string::size_type hexValue(char c) { Mode Mode::matching(const std::string strHex) { Mode r; r.name = "matching"; - r.kernel = "profanity_score_matching"; + r.kernel = "profanity_iterate_score_matching"; if (strHex.size() > 40) { throw std::runtime_error("hex mask must be at most 40 characters, got " + std::to_string(strHex.size())); @@ -73,7 +73,7 @@ Mode Mode::matching(const std::string strHex) { Mode Mode::exact(const std::string strHex) { Mode r = matching(strHex); r.name = "exact"; - r.kernel = "profanity_exact_match"; + r.kernel = "profanity_iterate_exact_match"; return r; } @@ -81,7 +81,7 @@ Mode Mode::leading(const char charLeading) { Mode r; r.name = "leading"; - r.kernel = "profanity_score_leading"; + r.kernel = "profanity_iterate_score_leading"; r.data1[0] = static_cast(hexValue(charLeading)); return r; } @@ -89,7 +89,7 @@ Mode Mode::leading(const char charLeading) { Mode Mode::range(const cl_uchar min, const cl_uchar max) { Mode r; r.name = "range"; - r.kernel = "profanity_score_range"; + r.kernel = "profanity_iterate_score_range"; r.data1[0] = min; r.data2[0] = max; return r; @@ -98,7 +98,7 @@ Mode Mode::range(const cl_uchar min, const cl_uchar max) { Mode Mode::zeroBytes() { Mode r; r.name = "zeroBytes"; - r.kernel = "profanity_score_zerobytes"; + r.kernel = "profanity_iterate_score_zerobytes"; return r; } @@ -114,17 +114,6 @@ Mode Mode::numbers() { return r; } -std::string Mode::transformKernel() const { - switch (this->target) { - case ADDRESS: - return ""; - case CONTRACT: - return "profanity_transform_contract"; - default: - throw "No kernel for target"; - } -} - std::string Mode::transformName() const { switch (this->target) { case ADDRESS: @@ -139,7 +128,7 @@ std::string Mode::transformName() const { Mode Mode::leadingRange(const cl_uchar min, const cl_uchar max) { Mode r; r.name = "leadingrange"; - r.kernel = "profanity_score_leadingrange"; + r.kernel = "profanity_iterate_score_leadingrange"; r.data1[0] = min; r.data2[0] = max; return r; @@ -148,13 +137,13 @@ Mode Mode::leadingRange(const cl_uchar min, const cl_uchar max) { Mode Mode::mirror() { Mode r; r.name = "mirror"; - r.kernel = "profanity_score_mirror"; + r.kernel = "profanity_iterate_score_mirror"; return r; } Mode Mode::doubles() { Mode r; r.name = "doubles"; - r.kernel = "profanity_score_doubles"; + r.kernel = "profanity_iterate_score_doubles"; return r; } diff --git a/Mode.hpp b/Mode.hpp index 508fecb..9a412cc 100644 --- a/Mode.hpp +++ b/Mode.hpp @@ -39,8 +39,6 @@ class Mode { std::string kernel; HashTarget target; - // kernel transform fn name - std::string transformKernel() const; // Address, Contract, ... std::string transformName() const; diff --git a/profanity.cl b/profanity.cl index c55059a..a536bfb 100644 --- a/profanity.cl +++ b/profanity.cl @@ -628,15 +628,10 @@ __kernel void profanity_inverse(__global const mp_number * const pDeltaX, __glob // in hopes that using constant storage instead of private storage // will aid speeds. // -// After the above point addition this kernel calculates the public address -// corresponding to the point and stores it in pInverse which is used only -// as interim storage as it won't otherwise be used again this cycle. -// -// One of the scoring kernels will run after this and fetch the address -// from pInverse. -__kernel void profanity_iterate(__global mp_number * const pDeltaX, __global mp_number * const pInverse, __global mp_number * const pPrevLambda) { - const size_t id = get_global_id(0); - +// After the above point addition this calculates the public address +// corresponding to the point and returns it in private memory, where the +// scoring below grades it without a round trip through global memory. +inline void profanity_iterate(__global mp_number * const pDeltaX, __global mp_number * const pInverse, __global mp_number * const pPrevLambda, const size_t id, const uchar bContract, uchar * const hash) { // negativeGx = 0x8641998106234453aa5f9d6a3178f4f8fd640324d231d726a60d7ea3e907e497 mp_number negativeGx = { {0xe907e497, 0xa60d7ea3, 0xd231d726, 0xfd640324, 0x3178f4f8, 0xaa5f9d6a, 0x06234453, 0x86419981 } }; @@ -688,15 +683,38 @@ __kernel void profanity_iterate(__global mp_number * const pDeltaX, __global mp_ sha3_keccakf(&h); - // Save public address hash in pInverse, only used as interim storage until next cycle - pInverse[id].d[0] = h.d[3]; - pInverse[id].d[1] = h.d[4]; - pInverse[id].d[2] = h.d[5]; - pInverse[id].d[3] = h.d[6]; - pInverse[id].d[4] = h.d[7]; + // The address is the low 20 bytes of the hash, words 3 through 7. + uint address[5] = { h.d[3], h.d[4], h.d[5], h.d[6], h.d[7] }; + + if (bContract) { + __private const uchar * const sender = (__private const uchar *)address; + ethhash c = { { 0 } }; + + // set up keccak(0xd6, 0x94, address, 0x80) + c.b[0] = 0xd6; + c.b[1] = 0x94; + for (int i = 0; i < 20; ++i) { + c.b[i + 2] = sender[i]; + } + c.b[22] = 0x80; + + c.b[23] ^= 0x01; // length 23 + sha3_keccakf(&c); + + address[0] = c.d[3]; + address[1] = c.d[4]; + address[2] = c.d[5]; + address[3] = c.d[6]; + address[4] = c.d[7]; + } + + __private const uchar * const bytes = (__private const uchar *)address; + for (int i = 0; i < 20; ++i) { + hash[i] = bytes[i]; + } } -void profanity_result_update(const size_t id, __global const uchar * const hash, __global result * const pResult, const uchar score, const uchar scoreMax) { +void profanity_result_update(const size_t id, const uchar * const hash, __global result * const pResult, const uchar score, const uchar scoreMax) { if (score && score > scoreMax) { uchar hasResult = atomic_inc(&pResult[score].found); // NOTE: If "too many" results are found it'll wrap around to 0 again and overwrite last result. Only relevant if global worksize exceeds MAX(uint). @@ -711,42 +729,15 @@ void profanity_result_update(const size_t id, __global const uchar * const hash, } } -__kernel void profanity_transform_contract(__global mp_number * const pInverse) { - const size_t id = get_global_id(0); - __global const uchar * const hash = (__global const uchar *)&pInverse[id].d[0]; - - ethhash h; - for (int i = 0; i < 50; ++i) { - h.d[i] = 0; - } - // set up keccak(0xd6, 0x94, address, 0x80) - h.b[0] = 214; - h.b[1] = 148; - for (int i = 0; i < 20; i++) { - h.b[i + 2] = hash[i]; - } - h.b[22] = 128; - - h.b[23] ^= 0x01; // length 23 - sha3_keccakf(&h); - - pInverse[id].d[0] = h.d[3]; - pInverse[id].d[1] = h.d[4]; - pInverse[id].d[2] = h.d[5]; - pInverse[id].d[3] = h.d[6]; - pInverse[id].d[4] = h.d[7]; -} - -__kernel void profanity_score_benchmark(__global mp_number * const pInverse, __global result * const pResult, __constant const uchar * const data1, __constant const uchar * const data2, const uchar scoreMax) { - const size_t id = get_global_id(0); - __global const uchar * const hash = (__global const uchar *)&pInverse[id].d[0]; - int score = 0; - - profanity_result_update(id, hash, pResult, score, scoreMax); +inline int profanity_score_fn_benchmark(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { + return 0; } // Reports every hash that matches the given mask (data1) and pattern (data2) // exactly, unlike the scoring kernels which report at most one hash per score. +// It has no score to hand back, so it cannot go through PROFANITY_SCORE_KERNEL, +// but it takes the same arguments so that the host can set them without caring +// which kernel the mode selected. scoreMax is unused. // // pResult[0].found counts the matches found by this launch; matches are // appended at pResult[1..PROFANITY_MAX_SCORE] in arrival order. The host reads @@ -754,9 +745,18 @@ __kernel void profanity_score_benchmark(__global mp_number * const pInverse, __g // check below also guarantees that no two work items ever write the same slot. // Matches beyond the buffer capacity are counted but not stored; the host // reports how many were dropped. -__kernel void profanity_exact_match(__global mp_number * const pInverse, __global result * const pResult, __constant const uchar * const data1, __constant const uchar * const data2, const uchar scoreMax) { +__kernel void profanity_iterate_exact_match( + __global mp_number * const pDeltaX, + __global mp_number * const pInverse, + __global mp_number * const pPrevLambda, + __global result * const pResult, + __constant const uchar * const data1, + __constant const uchar * const data2, + const uchar scoreMax, + const uchar bContract) { const size_t id = get_global_id(0); - __global const uchar * const hash = (__global const uchar *)&pInverse[id].d[0]; + uchar hash[20]; + profanity_iterate(pDeltaX, pInverse, pPrevLambda, id, bContract, hash); for (int i = 0; i < 20; ++i) { // Wildcard positions have zero mask bits in data1 and zero bits in @@ -776,9 +776,7 @@ __kernel void profanity_exact_match(__global mp_number * const pInverse, __globa } } -__kernel void profanity_score_matching(__global mp_number * const pInverse, __global result * const pResult, __constant const uchar * const data1, __constant const uchar * const data2, const uchar scoreMax) { - const size_t id = get_global_id(0); - __global const uchar * const hash = (__global const uchar *)&pInverse[id].d[0]; +inline int profanity_score_fn_matching(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { @@ -787,12 +785,10 @@ __kernel void profanity_score_matching(__global mp_number * const pInverse, __gl } } - profanity_result_update(id, hash, pResult, score, scoreMax); + return score; } -__kernel void profanity_score_leading(__global mp_number * const pInverse, __global result * const pResult, __constant const uchar * const data1, __constant const uchar * const data2, const uchar scoreMax) { - const size_t id = get_global_id(0); - __global const uchar * const hash = (__global const uchar *)&pInverse[id].d[0]; +inline int profanity_score_fn_leading(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { @@ -811,12 +807,10 @@ __kernel void profanity_score_leading(__global mp_number * const pInverse, __glo } } - profanity_result_update(id, hash, pResult, score, scoreMax); + return score; } -__kernel void profanity_score_range(__global mp_number * const pInverse, __global result * const pResult, __constant const uchar * const data1, __constant const uchar * const data2, const uchar scoreMax) { - const size_t id = get_global_id(0); - __global const uchar * const hash = (__global const uchar *)&pInverse[id].d[0]; +inline int profanity_score_fn_range(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { @@ -832,12 +826,10 @@ __kernel void profanity_score_range(__global mp_number * const pInverse, __globa } } - profanity_result_update(id, hash, pResult, score, scoreMax); + return score; } -__kernel void profanity_score_zerobytes(__global mp_number * const pInverse, __global result * const pResult, __constant const uchar * const data1, __constant const uchar * const data2, const uchar scoreMax) { - const size_t id = get_global_id(0); - __global const uchar * const hash = (__global const uchar *)&pInverse[id].d[0]; +inline int profanity_score_fn_zerobytes(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { @@ -846,12 +838,10 @@ __kernel void profanity_score_zerobytes(__global mp_number * const pInverse, __g } } - profanity_result_update(id, hash, pResult, score, scoreMax); + return score; } -__kernel void profanity_score_leadingrange(__global mp_number * const pInverse, __global result * const pResult, __constant const uchar * const data1, __constant const uchar * const data2, const uchar scoreMax) { - const size_t id = get_global_id(0); - __global const uchar * const hash = (__global const uchar *)&pInverse[id].d[0]; +inline int profanity_score_fn_leadingrange(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { @@ -873,12 +863,10 @@ __kernel void profanity_score_leadingrange(__global mp_number * const pInverse, } } - profanity_result_update(id, hash, pResult, score, scoreMax); + return score; } -__kernel void profanity_score_mirror(__global mp_number * const pInverse, __global result * const pResult, __constant const uchar * const data1, __constant const uchar * const data2, const uchar scoreMax) { - const size_t id = get_global_id(0); - __global const uchar * const hash = (__global const uchar *)&pInverse[id].d[0]; +inline int profanity_score_fn_mirror(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 10; ++i) { @@ -901,12 +889,10 @@ __kernel void profanity_score_mirror(__global mp_number * const pInverse, __glob ++score; } - profanity_result_update(id, hash, pResult, score, scoreMax); + return score; } -__kernel void profanity_score_doubles(__global mp_number * const pInverse, __global result * const pResult, __constant const uchar * const data1, __constant const uchar * const data2, const uchar scoreMax) { - const size_t id = get_global_id(0); - __global const uchar * const hash = (__global const uchar *)&pInverse[id].d[0]; +inline int profanity_score_fn_doubles(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { @@ -918,5 +904,34 @@ __kernel void profanity_score_doubles(__global mp_number * const pInverse, __glo } } - profanity_result_update(id, hash, pResult, score, scoreMax); -} + return score; +} + +// One kernel per scoring mode, each taking a candidate from the point addition +// through to its score. bContract is uniform across the launch and selects the +// second hash that turns a sender into the contract it deploys at nonce 0. +#define PROFANITY_SCORE_KERNEL(NAME) \ +__kernel void profanity_iterate_score_##NAME( \ + __global mp_number * const pDeltaX, \ + __global mp_number * const pInverse, \ + __global mp_number * const pPrevLambda, \ + __global result * const pResult, \ + __constant const uchar * const data1, \ + __constant const uchar * const data2, \ + const uchar scoreMax, \ + const uchar bContract) { \ + const size_t id = get_global_id(0); \ + uchar hash[20]; \ + profanity_iterate(pDeltaX, pInverse, pPrevLambda, id, bContract, hash); \ + const int score = profanity_score_fn_##NAME(hash, data1, data2); \ + profanity_result_update(id, hash, pResult, score, scoreMax); \ +} + +PROFANITY_SCORE_KERNEL(benchmark) +PROFANITY_SCORE_KERNEL(matching) +PROFANITY_SCORE_KERNEL(leading) +PROFANITY_SCORE_KERNEL(range) +PROFANITY_SCORE_KERNEL(zerobytes) +PROFANITY_SCORE_KERNEL(leadingrange) +PROFANITY_SCORE_KERNEL(mirror) +PROFANITY_SCORE_KERNEL(doubles) From 274a0bbb363adfda5b901efee92263f4540e11c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= Date: Sun, 26 Jul 2026 23:00:36 +0000 Subject: [PATCH 05/11] Pack address in uint[5] for scoring --- profanity.cl | 91 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 38 deletions(-) diff --git a/profanity.cl b/profanity.cl index a536bfb..4986e2c 100644 --- a/profanity.cl +++ b/profanity.cl @@ -631,7 +631,7 @@ __kernel void profanity_inverse(__global const mp_number * const pDeltaX, __glob // After the above point addition this calculates the public address // corresponding to the point and returns it in private memory, where the // scoring below grades it without a round trip through global memory. -inline void profanity_iterate(__global mp_number * const pDeltaX, __global mp_number * const pInverse, __global mp_number * const pPrevLambda, const size_t id, const uchar bContract, uchar * const hash) { +inline void profanity_iterate(__global mp_number * const pDeltaX, __global mp_number * const pInverse, __global mp_number * const pPrevLambda, const size_t id, const uchar bContract, uint * const address) { // negativeGx = 0x8641998106234453aa5f9d6a3178f4f8fd640324d231d726a60d7ea3e907e497 mp_number negativeGx = { {0xe907e497, 0xa60d7ea3, 0xd231d726, 0xfd640324, 0x3178f4f8, 0xaa5f9d6a, 0x06234453, 0x86419981 } }; @@ -684,7 +684,11 @@ inline void profanity_iterate(__global mp_number * const pDeltaX, __global mp_nu sha3_keccakf(&h); // The address is the low 20 bytes of the hash, words 3 through 7. - uint address[5] = { h.d[3], h.d[4], h.d[5], h.d[6], h.d[7] }; + address[0] = h.d[3]; + address[1] = h.d[4]; + address[2] = h.d[5]; + address[3] = h.d[6]; + address[4] = h.d[7]; if (bContract) { __private const uchar * const sender = (__private const uchar *)address; @@ -707,14 +711,13 @@ inline void profanity_iterate(__global mp_number * const pDeltaX, __global mp_nu address[3] = c.d[6]; address[4] = c.d[7]; } +} - __private const uchar * const bytes = (__private const uchar *)address; - for (int i = 0; i < 20; ++i) { - hash[i] = bytes[i]; - } +inline uchar profanity_byte(const uint * const address, const int i) { + return (uchar)(address[i >> 2] >> ((i & 3) << 3)); } -void profanity_result_update(const size_t id, const uchar * const hash, __global result * const pResult, const uchar score, const uchar scoreMax) { +void profanity_result_update(const size_t id, const uint * const address, __global result * const pResult, const uchar score, const uchar scoreMax) { if (score && score > scoreMax) { uchar hasResult = atomic_inc(&pResult[score].found); // NOTE: If "too many" results are found it'll wrap around to 0 again and overwrite last result. Only relevant if global worksize exceeds MAX(uint). @@ -723,13 +726,13 @@ void profanity_result_update(const size_t id, const uchar * const hash, __global pResult[score].foundId = id; for (int i = 0; i < 20; ++i) { - pResult[score].foundHash[i] = hash[i]; + pResult[score].foundHash[i] = profanity_byte(address, i); } } } } -inline int profanity_score_fn_benchmark(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { +inline int profanity_score_fn_benchmark(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { return 0; } @@ -755,13 +758,16 @@ __kernel void profanity_iterate_exact_match( const uchar scoreMax, const uchar bContract) { const size_t id = get_global_id(0); - uchar hash[20]; - profanity_iterate(pDeltaX, pInverse, pPrevLambda, id, bContract, hash); + uint address[5]; + profanity_iterate(pDeltaX, pInverse, pPrevLambda, id, bContract, address); - for (int i = 0; i < 20; ++i) { + __constant const uint * const mask = (__constant const uint *)data1; + __constant const uint * const want = (__constant const uint *)data2; + + for (int i = 0; i < 5; ++i) { // Wildcard positions have zero mask bits in data1 and zero bits in // data2, so they compare equal for any hash byte. - if ((hash[i] & data1[i]) != data2[i]) { + if ((address[i] & mask[i]) != want[i]) { return; } } @@ -771,16 +777,16 @@ __kernel void profanity_iterate_exact_match( pResult[matchIndex + 1].foundId = id; for (int i = 0; i < 20; ++i) { - pResult[matchIndex + 1].foundHash[i] = hash[i]; + pResult[matchIndex + 1].foundHash[i] = profanity_byte(address, i); } } } -inline int profanity_score_fn_matching(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { +inline int profanity_score_fn_matching(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { - if (data1[i] > 0 && (hash[i] & data1[i]) == data2[i]) { + if (data1[i] > 0 && (profanity_byte(address, i) & data1[i]) == data2[i]) { ++score; } } @@ -788,18 +794,20 @@ inline int profanity_score_fn_matching(const uchar * const hash, __constant cons return score; } -inline int profanity_score_fn_leading(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { +inline int profanity_score_fn_leading(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { - if ((hash[i] & 0xF0) >> 4 == data1[0]) { + const uchar byte = profanity_byte(address, i); + + if ((byte & 0xF0) >> 4 == data1[0]) { ++score; } else { break; } - if ((hash[i] & 0x0F) == data1[0]) { + if ((byte & 0x0F) == data1[0]) { ++score; } else { @@ -810,12 +818,13 @@ inline int profanity_score_fn_leading(const uchar * const hash, __constant const return score; } -inline int profanity_score_fn_range(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { +inline int profanity_score_fn_range(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { - const uchar first = (hash[i] & 0xF0) >> 4; - const uchar second = (hash[i] & 0x0F); + const uchar byte = profanity_byte(address, i); + const uchar first = (byte & 0xF0) >> 4; + const uchar second = (byte & 0x0F); if (first >= data1[0] && first <= data2[0]) { ++score; @@ -829,11 +838,11 @@ inline int profanity_score_fn_range(const uchar * const hash, __constant const u return score; } -inline int profanity_score_fn_zerobytes(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { +inline int profanity_score_fn_zerobytes(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { - if (hash[i] == 0) { + if (profanity_byte(address, i) == 0) { score++; } } @@ -841,12 +850,13 @@ inline int profanity_score_fn_zerobytes(const uchar * const hash, __constant con return score; } -inline int profanity_score_fn_leadingrange(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { +inline int profanity_score_fn_leadingrange(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { - const uchar first = (hash[i] & 0xF0) >> 4; - const uchar second = (hash[i] & 0x0F); + const uchar byte = profanity_byte(address, i); + const uchar first = (byte & 0xF0) >> 4; + const uchar second = (byte & 0x0F); if (first >= data1[0] && first <= data2[0]) { ++score; @@ -866,15 +876,18 @@ inline int profanity_score_fn_leadingrange(const uchar * const hash, __constant return score; } -inline int profanity_score_fn_mirror(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { +inline int profanity_score_fn_mirror(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 10; ++i) { - const uchar leftLeft = (hash[9 - i] & 0xF0) >> 4; - const uchar leftRight = (hash[9 - i] & 0x0F); + const uchar left = profanity_byte(address, 9 - i); + const uchar right = profanity_byte(address, 10 + i); - const uchar rightLeft = (hash[10 + i] & 0xF0) >> 4; - const uchar rightRight = (hash[10 + i] & 0x0F); + const uchar leftLeft = (left & 0xF0) >> 4; + const uchar leftRight = (left & 0x0F); + + const uchar rightLeft = (right & 0xF0) >> 4; + const uchar rightRight = (right & 0x0F); if (leftRight != rightLeft) { break; @@ -892,11 +905,13 @@ inline int profanity_score_fn_mirror(const uchar * const hash, __constant const return score; } -inline int profanity_score_fn_doubles(const uchar * const hash, __constant const uchar * const data1, __constant const uchar * const data2) { +inline int profanity_score_fn_doubles(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { - if ((hash[i] == 0x00) || (hash[i] == 0x11) || (hash[i] == 0x22) || (hash[i] == 0x33) || (hash[i] == 0x44) || (hash[i] == 0x55) || (hash[i] == 0x66) || (hash[i] == 0x77) || (hash[i] == 0x88) || (hash[i] == 0x99) || (hash[i] == 0xAA) || (hash[i] == 0xBB) || (hash[i] == 0xCC) || (hash[i] == 0xDD) || (hash[i] == 0xEE) || (hash[i] == 0xFF)) { + const uchar byte = profanity_byte(address, i); + + if ((((byte >> 4) ^ byte) & 0x0f) == 0) { ++score; } else { @@ -921,10 +936,10 @@ __kernel void profanity_iterate_score_##NAME( \ const uchar scoreMax, \ const uchar bContract) { \ const size_t id = get_global_id(0); \ - uchar hash[20]; \ - profanity_iterate(pDeltaX, pInverse, pPrevLambda, id, bContract, hash); \ - const int score = profanity_score_fn_##NAME(hash, data1, data2); \ - profanity_result_update(id, hash, pResult, score, scoreMax); \ + uint address[5]; \ + profanity_iterate(pDeltaX, pInverse, pPrevLambda, id, bContract, address); \ + const int score = profanity_score_fn_##NAME(address, data1, data2); \ + profanity_result_update(id, address, pResult, score, scoreMax); \ } PROFANITY_SCORE_KERNEL(benchmark) From 59e84e40e0d35f31abfa4b9318bcea24f7080ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= Date: Mon, 27 Jul 2026 22:27:17 +0000 Subject: [PATCH 06/11] Fix compiler optimizing benchmark away --- profanity.cl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/profanity.cl b/profanity.cl index 4986e2c..4e62704 100644 --- a/profanity.cl +++ b/profanity.cl @@ -732,8 +732,16 @@ void profanity_result_update(const size_t id, const uint * const address, __glob } } +// Prevent the compiler from deleting the keccak behind profanity_iterate +// Scores 1 for address(0), which is unreachable, and 0 on everything else inline int profanity_score_fn_benchmark(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { - return 0; + uint sum = 0; + + for (int i = 0; i < 5; ++i) { + sum |= address[i]; + } + + return sum == 0; } // Reports every hash that matches the given mask (data1) and pattern (data2) From 5c61260f80ff851f54010443f5cd9ffcb9b65910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= Date: Fri, 31 Jul 2026 10:31:08 +0000 Subject: [PATCH 07/11] Fix missing inline bodies on AMD --- profanity.cl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/profanity.cl b/profanity.cl index 4e62704..4507c38 100644 --- a/profanity.cl +++ b/profanity.cl @@ -631,7 +631,7 @@ __kernel void profanity_inverse(__global const mp_number * const pDeltaX, __glob // After the above point addition this calculates the public address // corresponding to the point and returns it in private memory, where the // scoring below grades it without a round trip through global memory. -inline void profanity_iterate(__global mp_number * const pDeltaX, __global mp_number * const pInverse, __global mp_number * const pPrevLambda, const size_t id, const uchar bContract, uint * const address) { +static inline void profanity_iterate(__global mp_number * const pDeltaX, __global mp_number * const pInverse, __global mp_number * const pPrevLambda, const size_t id, const uchar bContract, uint * const address) { // negativeGx = 0x8641998106234453aa5f9d6a3178f4f8fd640324d231d726a60d7ea3e907e497 mp_number negativeGx = { {0xe907e497, 0xa60d7ea3, 0xd231d726, 0xfd640324, 0x3178f4f8, 0xaa5f9d6a, 0x06234453, 0x86419981 } }; @@ -713,7 +713,7 @@ inline void profanity_iterate(__global mp_number * const pDeltaX, __global mp_nu } } -inline uchar profanity_byte(const uint * const address, const int i) { +static inline uchar profanity_byte(const uint * const address, const int i) { return (uchar)(address[i >> 2] >> ((i & 3) << 3)); } @@ -734,7 +734,7 @@ void profanity_result_update(const size_t id, const uint * const address, __glob // Prevent the compiler from deleting the keccak behind profanity_iterate // Scores 1 for address(0), which is unreachable, and 0 on everything else -inline int profanity_score_fn_benchmark(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { +static inline int profanity_score_fn_benchmark(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { uint sum = 0; for (int i = 0; i < 5; ++i) { @@ -790,7 +790,7 @@ __kernel void profanity_iterate_exact_match( } } -inline int profanity_score_fn_matching(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { +static inline int profanity_score_fn_matching(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { @@ -802,7 +802,7 @@ inline int profanity_score_fn_matching(const uint * const address, __constant co return score; } -inline int profanity_score_fn_leading(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { +static inline int profanity_score_fn_leading(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { @@ -826,7 +826,7 @@ inline int profanity_score_fn_leading(const uint * const address, __constant con return score; } -inline int profanity_score_fn_range(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { +static inline int profanity_score_fn_range(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { @@ -846,7 +846,7 @@ inline int profanity_score_fn_range(const uint * const address, __constant const return score; } -inline int profanity_score_fn_zerobytes(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { +static inline int profanity_score_fn_zerobytes(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { @@ -858,7 +858,7 @@ inline int profanity_score_fn_zerobytes(const uint * const address, __constant c return score; } -inline int profanity_score_fn_leadingrange(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { +static inline int profanity_score_fn_leadingrange(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { @@ -884,7 +884,7 @@ inline int profanity_score_fn_leadingrange(const uint * const address, __constan return score; } -inline int profanity_score_fn_mirror(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { +static inline int profanity_score_fn_mirror(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 10; ++i) { @@ -913,7 +913,7 @@ inline int profanity_score_fn_mirror(const uint * const address, __constant cons return score; } -inline int profanity_score_fn_doubles(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { +static inline int profanity_score_fn_doubles(const uint * const address, __constant const uchar * const data1, __constant const uchar * const data2) { int score = 0; for (int i = 0; i < 20; ++i) { From a84b4d30a2cf00ebff8cd14d4efc9ef4475a35de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= Date: Fri, 31 Jul 2026 10:36:19 +0000 Subject: [PATCH 08/11] Use profanity_byte in contract derivation --- profanity.cl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/profanity.cl b/profanity.cl index 4507c38..3e8441c 100644 --- a/profanity.cl +++ b/profanity.cl @@ -558,6 +558,10 @@ __kernel void profanity_inverse(__global const mp_number * const pDeltaX, __glob pInverse[id] = copy1; } +static inline uchar profanity_byte(const uint * const address, const int i) { + return (uchar)(address[i >> 2] >> ((i & 3) << 3)); +} + // This kernel performs en elliptical curve point addition. See: // https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Point_addition // I've made one mathematical optimization by never calculating x_r, @@ -691,14 +695,13 @@ static inline void profanity_iterate(__global mp_number * const pDeltaX, __globa address[4] = h.d[7]; if (bContract) { - __private const uchar * const sender = (__private const uchar *)address; ethhash c = { { 0 } }; // set up keccak(0xd6, 0x94, address, 0x80) c.b[0] = 0xd6; c.b[1] = 0x94; for (int i = 0; i < 20; ++i) { - c.b[i + 2] = sender[i]; + c.b[i + 2] = profanity_byte(address, i); } c.b[22] = 0x80; @@ -713,10 +716,6 @@ static inline void profanity_iterate(__global mp_number * const pDeltaX, __globa } } -static inline uchar profanity_byte(const uint * const address, const int i) { - return (uchar)(address[i >> 2] >> ((i & 3) << 3)); -} - void profanity_result_update(const size_t id, const uint * const address, __global result * const pResult, const uchar score, const uchar scoreMax) { if (score && score > scoreMax) { uchar hasResult = atomic_inc(&pResult[score].found); // NOTE: If "too many" results are found it'll wrap around to 0 again and overwrite last result. Only relevant if global worksize exceeds MAX(uint). From 19d92009d77243a018fd29085b7eaa920a580e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= Date: Fri, 31 Jul 2026 10:36:33 +0000 Subject: [PATCH 09/11] Make pInverse const --- profanity.cl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/profanity.cl b/profanity.cl index 3e8441c..3559ecd 100644 --- a/profanity.cl +++ b/profanity.cl @@ -635,7 +635,7 @@ static inline uchar profanity_byte(const uint * const address, const int i) { // After the above point addition this calculates the public address // corresponding to the point and returns it in private memory, where the // scoring below grades it without a round trip through global memory. -static inline void profanity_iterate(__global mp_number * const pDeltaX, __global mp_number * const pInverse, __global mp_number * const pPrevLambda, const size_t id, const uchar bContract, uint * const address) { +static inline void profanity_iterate(__global mp_number * const pDeltaX, __global const mp_number * const pInverse, __global mp_number * const pPrevLambda, const size_t id, const uchar bContract, uint * const address) { // negativeGx = 0x8641998106234453aa5f9d6a3178f4f8fd640324d231d726a60d7ea3e907e497 mp_number negativeGx = { {0xe907e497, 0xa60d7ea3, 0xd231d726, 0xfd640324, 0x3178f4f8, 0xaa5f9d6a, 0x06234453, 0x86419981 } }; @@ -757,7 +757,7 @@ static inline int profanity_score_fn_benchmark(const uint * const address, __con // reports how many were dropped. __kernel void profanity_iterate_exact_match( __global mp_number * const pDeltaX, - __global mp_number * const pInverse, + __global const mp_number * const pInverse, __global mp_number * const pPrevLambda, __global result * const pResult, __constant const uchar * const data1, @@ -935,7 +935,7 @@ static inline int profanity_score_fn_doubles(const uint * const address, __const #define PROFANITY_SCORE_KERNEL(NAME) \ __kernel void profanity_iterate_score_##NAME( \ __global mp_number * const pDeltaX, \ - __global mp_number * const pInverse, \ + __global const mp_number * const pInverse, \ __global mp_number * const pPrevLambda, \ __global result * const pResult, \ __constant const uchar * const data1, \ From 9d9eb674dee0f8e850c7234a8c39c64ea189964d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= Date: Fri, 31 Jul 2026 10:39:05 +0000 Subject: [PATCH 10/11] Move cache disclaimer at top of README --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2e7d213..87299e2 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ This project "profanity2" was forked from the original project and modified to g Project "profanity2" is not generating key anymore, instead it adjusts user-provided public key until desired vanity address will be discovered. Users provide seed public key in form of 128-symbol hex string with `-z` parameter flag. Resulting private key should be used to be added to seed private key to achieve final private key of the desired vanity address (private keys are just 256-bit numbers). Running "profanity2" can even be outsourced to someone completely unreliable - it is still safe by design. +Note: when upgrading to a new version of profanity2, delete the `cache-opencl.*` files (or pass `--no-cache`) once so the OpenCL program is rebuilt with the new kernel. + ## Getting public key for mandatory `-z` parameter Generate private key and public key via openssl in terminal (remove prefix "04" from public key): @@ -252,9 +254,6 @@ reported), and a very short mask can produce more matches per GPU round than the buffer holds — the program prints a warning with the number of dropped matches if that happens. -Note: if you have run an older version of profanity2 before, delete the `cache-opencl.*` -files (or pass `--no-cache`) once so the OpenCL program is rebuilt with the new kernel. - ### Character classes anywhere (`--zeros`, `--letters`, `--numbers`) Score on the total amount of matching characters anywhere in the address: From aad8e135566573b136418914e6e842244aef10fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= Date: Fri, 31 Jul 2026 12:01:03 +0000 Subject: [PATCH 11/11] Update benchmarks table --- README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 87299e2..05678dc 100644 --- a/README.md +++ b/README.md @@ -323,16 +323,17 @@ zeroth transaction** of the found account instead of the account address itself: ``` ### Benchmarks - Current version -|Model|Clock Speed|Memory Speed|Modified straps|Speed|Time to match eight characters -|:-:|:-:|:-:|:-:|:-:|:-:| -|GTX 1070 OC|1950|4450|NO|179.0 MH/s| ~24s -|GTX 1070|1750|4000|NO|163.0 MH/s| ~26s -|RX 480|1328|2000|YES|120.0 MH/s| ~36s -|RTX 4090|-|-|-|1096 MH/s| ~3s -|Apple Silicon M1
(8-core GPU)|-|-|-|45.0 MH/s| ~97s -|Apple Silicon M1 Max
(32-core GPU)|-|-|-|172.0 MH/s| ~25s -|Apple Silicon M3 Pro
(18-core GPU)|-|-|-|97 MH/s| ~45s -|Apple Silicon M4 Max
(40-core GPU)|-|-|-|350 MH/s| ~12s +|Model|Clock Speed|Memory Speed|Speed|Time to match eight characters +|:-:|:-:|:-:|:-:|:-:| +|GTX 1070|1750|4000|225 MH/s| ~19s +|RTX 4090|2550|10500|1361 MH/s| ~3s +|RX 480|1328|4000|120 MH/s| ~36s +|RX 7900 XTX|2500|10000|592 MH/s| ~7s +|Apple Silicon M1
(8-core GPU)|1278|4266|60 MH/s| ~72s +|Apple Silicon M1 Max
(32-core GPU)|1296|6400|229 MH/s| ~19s +|Apple Silicon M2
(10-core GPU)|1398|6400|75 MH/s| ~57s +|Apple Silicon M3 Pro
(18-core GPU)|1398|6400|129 MH/s| ~33s +|Apple Silicon M4 Max
(40-core GPU)|1800|8533|467 MH/s| ~9s # License