From 2fd09f5e20515fb0b8608846f0c536ee1657b9d7 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 21 Nov 2024 22:56:01 +0200 Subject: [PATCH] moved chat pow to java --- android/app/build.gradle | 4 + .../github/Qortal/qortalMobile/Crypto.java | 126 +++++++++ .../Qortal/qortalMobile/MainActivity.java | 2 + .../github/Qortal/qortalMobile/MemoryPoW.java | 168 ++++++++++++ .../com/github/Qortal/qortalMobile/NTP.java | 241 ++++++++++++++++++ .../github/Qortal/qortalMobile/NativePOW.java | 56 ++++ android/app/src/main/res/raw/memorypow.wasm | Bin 0 -> 3399 bytes src/background.ts | 30 +-- src/chatComputePow.worker.js | 42 +-- src/main.tsx | 1 + src/utils/nativepow.ts | 9 + 11 files changed, 637 insertions(+), 42 deletions(-) create mode 100644 android/app/src/main/java/com/github/Qortal/qortalMobile/Crypto.java create mode 100644 android/app/src/main/java/com/github/Qortal/qortalMobile/MemoryPoW.java create mode 100644 android/app/src/main/java/com/github/Qortal/qortalMobile/NTP.java create mode 100644 android/app/src/main/java/com/github/Qortal/qortalMobile/NativePOW.java create mode 100644 android/app/src/main/res/raw/memorypow.wasm create mode 100644 src/utils/nativepow.ts diff --git a/android/app/build.gradle b/android/app/build.gradle index d12aa37..a0da5dd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -39,6 +39,10 @@ dependencies { implementation "org.mindrot:jbcrypt:0.4" implementation "at.favre.lib:bcrypt:0.10.2" implementation 'com.password4j:password4j:1.8.2' + implementation 'com.dylibso.chicory:runtime:1.0.0-M1' + implementation 'commons-net:commons-net:3.6' + implementation 'org.bouncycastle:bcprov-jdk15to18:1.76' + implementation 'com.google.guava:guava:32.1.2-jre' testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" diff --git a/android/app/src/main/java/com/github/Qortal/qortalMobile/Crypto.java b/android/app/src/main/java/com/github/Qortal/qortalMobile/Crypto.java new file mode 100644 index 0000000..803def6 --- /dev/null +++ b/android/app/src/main/java/com/github/Qortal/qortalMobile/Crypto.java @@ -0,0 +1,126 @@ +package com.github.Qortal.qortalMobile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public abstract class Crypto { + + /** + * Returns 32-byte SHA-256 digest of message passed in input. + * + * @param input + * variable-length byte[] message + * @return byte[32] digest, or null if SHA-256 algorithm can't be accessed + */ + public static byte[] digest(byte[] input) { + if (input == null) + return null; + + try { + // SHA2-256 + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + return sha256.digest(input); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 message digest not available"); + } + } + + /** + * Returns 32-byte SHA-256 digest of message passed in input. + * + * @param input + * variable-length ByteBuffer message + * @return byte[32] digest, or null if SHA-256 algorithm can't be accessed + */ + public static byte[] digest(ByteBuffer input) { + if (input == null) + return null; + + try { + // SHA2-256 + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + sha256.update(input); + return sha256.digest(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 message digest not available"); + } + } + + /** + * Returns 32-byte digest of two rounds of SHA-256 on message passed in input. + * + * @param input + * variable-length byte[] message + * @return byte[32] digest, or null if SHA-256 algorithm can't be accessed + */ + public static byte[] doubleDigest(byte[] input) { + return digest(digest(input)); + } + + /** + * Returns 32-byte SHA-256 digest of file passed in input. + * + * @param file + * file in which to perform digest + * @return byte[32] digest, or null if SHA-256 algorithm can't be accessed + * + * @throws IOException if the file cannot be read + */ + public static byte[] digest(File file) throws IOException { + return Crypto.digest(file, 8192); + } + + /** + * Returns 32-byte SHA-256 digest of file passed in input, in hex format + * + * @param file + * file in which to perform digest + * @return String digest as a hexadecimal string, or null if SHA-256 algorithm can't be accessed + * + * @throws IOException if the file cannot be read + */ + public static String digestHexString(File file, int bufferSize) throws IOException { + byte[] digest = Crypto.digest(file, bufferSize); + + // Convert to hex + StringBuilder stringBuilder = new StringBuilder(); + for (byte b : digest) { + stringBuilder.append(String.format("%02x", b)); + } + return stringBuilder.toString(); + } + + /** + * Returns 32-byte SHA-256 digest of file passed in input. + * + * @param file + * file in which to perform digest + * @param bufferSize + * the number of bytes to load into memory + * @return byte[32] digest, or null if SHA-256 algorithm can't be accessed + * + * @throws IOException if the file cannot be read + */ + public static byte[] digest(File file, int bufferSize) throws IOException { + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + FileInputStream fileInputStream = new FileInputStream(file); + byte[] bytes = new byte[bufferSize]; + int count; + + while ((count = fileInputStream.read(bytes)) != -1) { + sha256.update(bytes, 0, count); + } + fileInputStream.close(); + + return sha256.digest(); + + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 message digest not available"); + } + } +} diff --git a/android/app/src/main/java/com/github/Qortal/qortalMobile/MainActivity.java b/android/app/src/main/java/com/github/Qortal/qortalMobile/MainActivity.java index 1351888..5a50ead 100644 --- a/android/app/src/main/java/com/github/Qortal/qortalMobile/MainActivity.java +++ b/android/app/src/main/java/com/github/Qortal/qortalMobile/MainActivity.java @@ -2,12 +2,14 @@ package com.github.Qortal.qortalMobile; import com.getcapacitor.BridgeActivity; import com.github.Qortal.qortalMobile.NativeBcrypt; +import com.github.Qortal.qortalMobile.NativePOW; import android.os.Bundle; public class MainActivity extends BridgeActivity { @Override public void onCreate(Bundle savedInstanceState) { registerPlugin(NativeBcrypt.class); + registerPlugin(NativePOW.class); super.onCreate(savedInstanceState); diff --git a/android/app/src/main/java/com/github/Qortal/qortalMobile/MemoryPoW.java b/android/app/src/main/java/com/github/Qortal/qortalMobile/MemoryPoW.java new file mode 100644 index 0000000..686a20e --- /dev/null +++ b/android/app/src/main/java/com/github/Qortal/qortalMobile/MemoryPoW.java @@ -0,0 +1,168 @@ +package com.github.Qortal.qortalMobile; + + +import com.github.Qortal.qortalMobile.NTP; +import com.github.Qortal.qortalMobile.Crypto; + + +import java.nio.ByteBuffer; +import java.util.concurrent.TimeoutException; + +public class MemoryPoW { + + /** + * Compute a MemoryPoW nonce + * + * @param data + * @param workBufferLength + * @param difficulty + * @return + * @throws TimeoutException + */ + public static Integer compute2(byte[] data, int workBufferLength, long difficulty) { + try { + return MemoryPoW.compute2(data, workBufferLength, difficulty, null); + + } catch (TimeoutException e) { + // This won't happen, because above timeout is null + return null; + } + } + + /** + * Compute a MemoryPoW nonce, with optional timeout + * + * @param data + * @param workBufferLength + * @param difficulty + * @param timeout maximum number of milliseconds to compute for before giving up,
or null if no timeout + * @return + * @throws TimeoutException + */ + public static Integer compute2(byte[] data, int workBufferLength, long difficulty, Long timeout) throws TimeoutException { + long startTime = NTP.getTime() != null ? NTP.getTime() : System.currentTimeMillis(); + + // Hash data with SHA256 + byte[] hash = Crypto.digest(data); + + long[] longHash = new long[4]; + ByteBuffer byteBuffer = ByteBuffer.wrap(hash); + longHash[0] = byteBuffer.getLong(); + longHash[1] = byteBuffer.getLong(); + longHash[2] = byteBuffer.getLong(); + longHash[3] = byteBuffer.getLong(); + byteBuffer = null; + + int longBufferLength = workBufferLength / 8; + long[] workBuffer = new long[longBufferLength]; + long[] state = new long[4]; + + long seed = 8682522807148012L; + long seedMultiplier = 1181783497276652981L; + + // For each nonce... + int nonce = -1; + long result = 0; + do { + ++nonce; + + // If we've been interrupted, exit fast with invalid value + if (Thread.currentThread().isInterrupted()) + return -1; + + if (timeout != null) { + long now = NTP.getTime() != null ? NTP.getTime() : System.currentTimeMillis(); + if (now > startTime + timeout) { + throw new TimeoutException("Timeout reached"); + } + } + + seed *= seedMultiplier; // per nonce + + state[0] = longHash[0] ^ seed; + state[1] = longHash[1] ^ seed; + state[2] = longHash[2] ^ seed; + state[3] = longHash[3] ^ seed; + + // Fill work buffer with random + for (int i = 0; i < workBuffer.length; ++i) + workBuffer[i] = xoshiro256p(state); + + // Random bounce through whole buffer + result = workBuffer[0]; + for (int i = 0; i < 1024; ++i) { + int index = (int) (xoshiro256p(state) & Integer.MAX_VALUE) % workBuffer.length; + result ^= workBuffer[index]; + } + + // Return if final value > difficulty + } while (Long.numberOfLeadingZeros(result) < difficulty); + + return nonce; + } + + + public static boolean verify2(byte[] data, int workBufferLength, long difficulty, int nonce) { + return verify2(data, null, workBufferLength, difficulty, nonce); + } + + public static boolean verify2(byte[] data, long[] workBuffer, int workBufferLength, long difficulty, int nonce) { + // Hash data with SHA256 + byte[] hash = Crypto.digest(data); + + long[] longHash = new long[4]; + ByteBuffer byteBuffer = ByteBuffer.wrap(hash); + longHash[0] = byteBuffer.getLong(); + longHash[1] = byteBuffer.getLong(); + longHash[2] = byteBuffer.getLong(); + longHash[3] = byteBuffer.getLong(); + byteBuffer = null; + + int longBufferLength = workBufferLength / 8; + + if (workBuffer == null) + workBuffer = new long[longBufferLength]; + + long[] state = new long[4]; + + long seed = 8682522807148012L; + long seedMultiplier = 1181783497276652981L; + + for (int i = 0; i <= nonce; ++i) + seed *= seedMultiplier; + + state[0] = longHash[0] ^ seed; + state[1] = longHash[1] ^ seed; + state[2] = longHash[2] ^ seed; + state[3] = longHash[3] ^ seed; + + // Fill work buffer with random + for (int i = 0; i < workBuffer.length; ++i) + workBuffer[i] = xoshiro256p(state); + + // Random bounce through whole buffer + long result = workBuffer[0]; + for (int i = 0; i < 1024; ++i) { + int index = (int) (xoshiro256p(state) & Integer.MAX_VALUE) % workBuffer.length; + result ^= workBuffer[index]; + } + + return Long.numberOfLeadingZeros(result) >= difficulty; + } + + private static final long xoshiro256p(long[] state) { + final long result = state[0] + state[3]; + final long temp = state[1] << 17; + + state[2] ^= state[0]; + state[3] ^= state[1]; + state[1] ^= state[2]; + state[0] ^= state[3]; + + state[2] ^= temp; + state[3] = (state[3] << 45) | (state[3] >>> (64 - 45)); // rol64(s[3], 45); + + return result; + } + +} diff --git a/android/app/src/main/java/com/github/Qortal/qortalMobile/NTP.java b/android/app/src/main/java/com/github/Qortal/qortalMobile/NTP.java new file mode 100644 index 0000000..f0a21fd --- /dev/null +++ b/android/app/src/main/java/com/github/Qortal/qortalMobile/NTP.java @@ -0,0 +1,241 @@ +package com.github.Qortal.qortalMobile; + +import org.apache.commons.net.ntp.NTPUDPClient; +import org.apache.commons.net.ntp.NtpV3Packet; +import org.apache.commons.net.ntp.TimeInfo; +import android.util.Log; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.*; +import java.util.stream.Collectors; + +public class NTP implements Runnable { + + private static final String TAG = "NTP"; + + private static boolean isStarted = false; + private static volatile boolean isStopping = false; + private static ExecutorService instanceExecutor; + private static NTP instance; + private static volatile boolean isOffsetSet = false; + private static volatile long offset = 0; + + static class NTPServer { + private static final int MIN_POLL = 64; + + public char usage = ' '; + public String remote; + public String refId; + public Integer stratum; + public char type = 'u'; // unicast + public int poll = MIN_POLL; + public byte reach = 0; + public Long delay; + public Double offset; + public Double jitter; + + private Deque offsets = new LinkedList<>(); + private double totalSquareOffsets = 0.0; + private long nextPoll; + private Long lastGood; + + public NTPServer(String remote) { + this.remote = remote; + } + + public boolean doPoll(NTPUDPClient client, final long now) { + Thread.currentThread().setName(String.format("NTP: %s", this.remote)); + + try { + boolean isUpdated = false; + try { + TimeInfo timeInfo = client.getTime(InetAddress.getByName(remote)); + timeInfo.computeDetails(); + NtpV3Packet ntpMessage = timeInfo.getMessage(); + + this.refId = ntpMessage.getReferenceIdString(); + this.stratum = ntpMessage.getStratum(); + this.poll = Math.max(MIN_POLL, 1 << ntpMessage.getPoll()); + + this.delay = timeInfo.getDelay(); + this.offset = (double) timeInfo.getOffset(); + + if (this.offsets.size() == 8) { + double oldOffset = this.offsets.removeFirst(); + this.totalSquareOffsets -= oldOffset * oldOffset; + } + + this.offsets.addLast(this.offset); + this.totalSquareOffsets += this.offset * this.offset; + + this.jitter = Math.sqrt(this.totalSquareOffsets / this.offsets.size()); + + this.reach = (byte) ((this.reach << 1) | 1); + this.lastGood = now; + + isUpdated = true; + } catch (IOException e) { + this.reach <<= 1; + Log.e(TAG, "Error polling server: " + remote, e); + } + + this.nextPoll = now + this.poll * 1000; + return isUpdated; + } finally { + Thread.currentThread().setName("NTP (dormant)"); + } + } + + public Integer getWhen() { + if (this.lastGood == null) + return null; + + return (int) ((System.currentTimeMillis() - this.lastGood) / 1000); + } + } + + private final NTPUDPClient client; + private final List ntpServers = new ArrayList<>(); + private final ExecutorService serverExecutor; + + private NTP(String[] serverNames) { + client = new NTPUDPClient(); + client.setDefaultTimeout(2000); + + for (String serverName : serverNames) + ntpServers.add(new NTPServer(serverName)); + + serverExecutor = Executors.newCachedThreadPool(); + } + + public static synchronized void start(String[] serverNames) { + if (isStarted) + return; + + isStarted = true; + instanceExecutor = Executors.newSingleThreadExecutor(); + instance = new NTP(serverNames); + instanceExecutor.execute(instance); + Log.d(TAG, "NTP started with servers: " + String.join(", ", serverNames)); + } + + public static void shutdownNow() { + if (instanceExecutor != null) + instanceExecutor.shutdownNow(); + Log.d(TAG, "NTP shutdown."); + } + + public static synchronized void setFixedOffset(Long offset) { + NTP.offset = offset; + isOffsetSet = true; + Log.d(TAG, "Fixed offset set: " + offset); + } + + public static Long getTime() { + if (!isOffsetSet) + return null; + + return System.currentTimeMillis() + NTP.offset; + } + + public void run() { + Thread.currentThread().setName("NTP instance"); + + try { + while (!isStopping) { + Thread.sleep(1000); + + boolean haveUpdates = pollServers(); + if (!haveUpdates) + continue; + + calculateOffset(); + } + } catch (InterruptedException e) { + Log.d(TAG, "NTP instance interrupted."); + } + } + + private boolean pollServers() throws InterruptedException { + final long now = System.currentTimeMillis(); + + List pendingServers = ntpServers.stream().filter(ntpServer -> now >= ntpServer.nextPoll).collect(Collectors.toList()); + + CompletionService ecs = new ExecutorCompletionService<>(serverExecutor); + for (NTPServer server : pendingServers) + ecs.submit(() -> server.doPoll(client, now)); + + boolean haveUpdate = false; + for (int i = 0; i < pendingServers.size(); ++i) { + if (isStopping) + return false; + + try { + haveUpdate = ecs.take().get() || haveUpdate; + } catch (ExecutionException e) { + Log.e(TAG, "Error during server polling", e); + } + } + + return haveUpdate; + } + + private void calculateOffset() { + double s0 = 0; + double s1 = 0; + double s2 = 0; + + for (NTPServer server : ntpServers) { + if (server.offset == null) { + server.usage = ' '; + continue; + } + + server.usage = '+'; + double value = server.offset * (double) server.stratum; + + s0 += 1; + s1 += value; + s2 += value * value; + } + + if (s0 < ntpServers.size() / 3 + 1) { + Log.d(TAG, String.format("Not enough replies (%d) to calculate network time", (int) s0)); + } else { + double thresholdStddev = Math.sqrt(((s0 * s2) - (s1 * s1)) / (s0 * (s0 - 1))); + double mean = s1 / s0; + + // Now only consider offsets within 1 stddev + s0 = 0; + s1 = 0; + s2 = 0; + + for (NTPServer server : ntpServers) { + if (server.offset == null || server.reach == 0) + continue; + + if (Math.abs(server.offset * (double) server.stratum - mean) > thresholdStddev) + continue; + + server.usage = '*'; + s0 += 1; + s1 += server.offset; + s2 += server.offset * server.offset; + } + + if (s0 > 1) { + double filteredMean = s1 / s0; + NTP.offset = (long) filteredMean; + isOffsetSet = true; + Log.d(TAG, "New NTP offset: " + NTP.offset); + } else { + Log.d(TAG, "Not enough useful values to calculate network time."); + } + } + } +} diff --git a/android/app/src/main/java/com/github/Qortal/qortalMobile/NativePOW.java b/android/app/src/main/java/com/github/Qortal/qortalMobile/NativePOW.java new file mode 100644 index 0000000..6d39c67 --- /dev/null +++ b/android/app/src/main/java/com/github/Qortal/qortalMobile/NativePOW.java @@ -0,0 +1,56 @@ +package com.github.Qortal.qortalMobile; + +import com.getcapacitor.Plugin; +import com.getcapacitor.PluginCall; +import com.getcapacitor.JSObject; +import com.getcapacitor.PluginMethod; +import com.getcapacitor.annotation.CapacitorPlugin; +import android.util.Log; + +import com.github.Qortal.qortalMobile.MemoryPoW; + +import java.util.Iterator; + +@CapacitorPlugin(name = "NativePOW") +public class NativePOW extends Plugin { + + @PluginMethod + public void computeProofOfWork(PluginCall call) { + try { + // Extract parameters from the call + JSObject chatBytesObject = call.getObject("chatBytes", new JSObject()); + int difficulty = call.getInt("difficulty", 0); + + // Convert chatBytesObject to a byte array + byte[] chatBytes = jsObjectToByteArray(chatBytesObject); + + // Use the MemoryPoW.compute2 method + int workBufferLength = 8 * 1024 * 1024; // 8 MiB buffer + Integer nonce = MemoryPoW.compute2(chatBytes, workBufferLength, difficulty); + + // Return result to the plugin caller + JSObject result = new JSObject(); + result.put("nonce", nonce); + + call.resolve(result); + + } catch (Exception e) { + call.reject("Error computing proof-of-work", e); + } + } + + private byte[] jsObjectToByteArray(JSObject jsObject) { + int length = jsObject.length(); + byte[] array = new byte[length]; + Iterator keys = jsObject.keys(); + + while (keys.hasNext()) { + String key = keys.next(); + int index = Integer.parseInt(key); + int value = jsObject.getInteger(key); + array[index] = (byte) value; + } + + return array; + } +} diff --git a/android/app/src/main/res/raw/memorypow.wasm b/android/app/src/main/res/raw/memorypow.wasm new file mode 100644 index 0000000000000000000000000000000000000000..073b179cab68d0eabc80c9b6ba9590d7e59c2e4f GIT binary patch literal 3399 zcma)8O^h2w7Jk)LZMWV2X^%(AL?e{yPWTICoC!lh_)Qx{$?z8zmR*v=cG`B&j3;*Y z(6%Ru6pazWh(8HMk#>cIGzfum1o5-d0tYS}cW)~M7ovsT6A@exD*+1cb&nl;5(8!R zd-dM;{;FQPy1;EU833?p^#s5P)@9ujqKgALTO-nCYf7GR&tFY6y=D-eQ9$7d6$Il- z;+ABW*-t+&^e)&>Ygf@20Mau(`46TS9Oo2fbgFKn;Z)l}*aFZ`2d$-g80@`$-wLoT zey3US!lMfhd2X%lFCO>8pmp0`P_7B=n***@gXT)7?d=7TahzH!aF$%Z*6=t>qv*C> z$Mb7k$vVzrBdE9yr{cCe&UMFG^4yhJ5{SmDi)9rfwcSd?i^W9x2cdFc`lC!;lOW+{ z4i+P3^ej$Xvy|R`t|6w7IAw{RrIvTUu%Az%j!UIncn%E=6qrBeA?$S!9JC zX4mCJ&rC^X3aMBi4x*EgDVaV?Wu#=bXJxQb>;$@Ck;`bO$#l=kVW%)yO6JI2lNkyI zc6ZqVT!$W&ZmN<|H5q)-qoqgjii3C*$jqU0k(^nS_5$|u^5x`HQdyy`Lw5?Dk)!zp zB@(n@8G}oHgk}kB$4=Zc9o|v(nVMJWEIM_6A$VN@5I{pPq8Zpi49AX`!4c#Nwgmoy z599m~$dV%V!Kt{!VEtb>QNKWv_>RbbkwB6NjmY9HoUo}7vuUKe7>PF^P2Gy*KO{~Q zy2a0;2!a-<(B9K`x(gzus}>0M~fcD+o+Y>%cCJ56N6fJalRHm{t6|B>e{c)UT1 zwq~BdxoFA%M=`2yUxYq`CdUcAgVLwoN6{hiKqP*SqEF)8k+0wY(Q@t~)W9bisdLB< zk=d=3|BC)?)E_2&Dcas4Vh?K-F!?RcwPfxbO3G_U?xBbZFCo+L`6`j#cx4+w zmHq*Lq7)mCfJ*@DqY^^V*lf$8uzkc-`BiB+%s){WPQuoQ(eF2*>R1#@7_C+tYf38{ zAIN|`>*uxMr(d21=cg_~4$;0Q2j`7TkVnm^eiBHNi1b&Z2Ie;qqu;1s0{!UMkDrd@ zI745z*(S5$Ga6}pj>(Peaecp4!RBn&st9*sWVQWDf3JRA!_#2&zdIwZQ)+Bybub1W zp0P$8_Q;WV$+4yiz6<@;&|5RHNg7u<`Smea%WnM1Lf2pP=s=8K_+R6FU&>8mGmo`b z>#u3|iqbE@tyDs9l@-|axuVK*#@6XvfjzMm4~H!wnZXJNq$AM*Ug z_R^37mdJlladpWJqvhR!6*r&~r5JH-d0q{OJdC01D~-Arf*<%*59)r+I}LDqY9$P6 loht6VqEQ< { - worker.onmessage = (e) => { - if (e.data.error) { - reject(new Error(e.data.error)); - } else { - resolve(e.data); - } - }; - - worker.onerror = (err) => { - reject(err); - }; - - // Send the task to the worker - worker.postMessage({ - chatBytes, - path: `${import.meta.env.BASE_URL}memory-pow.wasm.full`, - difficulty, - }); - }); + const chatBytesArray = Uint8Array.from(Object.values(chatBytes)); + const result = await NativePOW.computeProofOfWork({ chatBytes, difficulty }); + return {nonce: result.nonce, chatBytesArray} + } const handleNotificationDirect = async (directs) => { diff --git a/src/chatComputePow.worker.js b/src/chatComputePow.worker.js index afc782b..15178bd 100644 --- a/src/chatComputePow.worker.js +++ b/src/chatComputePow.worker.js @@ -1,6 +1,6 @@ import { Sha256 } from 'asmcrypto.js'; import wasmInit from './memory-pow.wasm?init'; - +import NativePOW from './utils/nativepow' let compute; // Exported compute function from Wasm let memory; // WebAssembly.Memory instance let heap; // Uint8Array view of the memory buffer @@ -58,24 +58,26 @@ function sbrk(size) { // Proof-of-Work computation function async function computePow(chatBytes, difficulty) { - if (!compute) { - throw new Error('WebAssembly module not initialized. Call loadWasm first.'); - } + // if (!compute) { + // throw new Error('WebAssembly module not initialized. Call loadWasm first.'); + // } - const chatBytesArray = Uint8Array.from(Object.values(chatBytes)); - const chatBytesHash = new Sha256().process(chatBytesArray).finish().result; + // const chatBytesArray = Uint8Array.from(Object.values(chatBytes)); + // const chatBytesHash = new Sha256().process(chatBytesArray).finish().result; - // Allocate memory for the hash - const hashPtr = sbrk(32); - const hashAry = new Uint8Array(memory.buffer, hashPtr, 32); - hashAry.set(chatBytesHash); - - // Reuse the work buffer if already allocated - if (!workBufferPtr) { - workBufferPtr = sbrk(workBufferLength); - } - - const nonce = compute(hashPtr, workBufferPtr, workBufferLength, difficulty); + // // Allocate memory for the hash + // const hashPtr = sbrk(32); + // const hashAry = new Uint8Array(memory.buffer, hashPtr, 32); + // hashAry.set(chatBytesHash); + + // // Reuse the work buffer if already allocated + // if (!workBufferPtr) { + // workBufferPtr = sbrk(workBufferLength); + // } + console.log('native') + const nonce = await NativePOW.computeProofOfWork({ chatBytes, difficulty }); + console.log('nonce', nonce) + (hashPtr, workBufferPtr, workBufferLength, difficulty); return { nonce, chatBytesArray }; } @@ -86,9 +88,9 @@ self.addEventListener('message', async (e) => { try { // Initialize Wasm if not already done - if (!compute) { - await loadWasm(); - } + // if (!compute) { + // await loadWasm(); + // } // Perform the POW computation const result = await computePow(chatBytes, difficulty); diff --git a/src/main.tsx b/src/main.tsx index 0531491..2b89ed3 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -7,6 +7,7 @@ import { ThemeProvider, createTheme } from '@mui/material/styles'; import { CssBaseline } from '@mui/material'; import { MessageQueueProvider } from './MessageQueueContext.tsx'; import { RecoilRoot } from 'recoil'; +import './utils/nativepow.ts' const theme = createTheme({ palette: { primary: { diff --git a/src/utils/nativepow.ts b/src/utils/nativepow.ts new file mode 100644 index 0000000..1937231 --- /dev/null +++ b/src/utils/nativepow.ts @@ -0,0 +1,9 @@ +import { registerPlugin } from '@capacitor/core'; + +export interface NativePOWPlugin { + computeProofOfWork(options: { chatBytes: string; difficulty: number }): Promise<{ nonce: string }>; +} + +const NativePOW = registerPlugin('NativePOW'); + +export default NativePOW \ No newline at end of file