mirror of
https://github.com/Qortal/qortal-mobile.git
synced 2025-04-24 11:57:52 +00:00
moved chat pow to java
This commit is contained in:
parent
c6f6ab58f1
commit
2fd09f5e20
@ -39,6 +39,10 @@ dependencies {
|
|||||||
implementation "org.mindrot:jbcrypt:0.4"
|
implementation "org.mindrot:jbcrypt:0.4"
|
||||||
implementation "at.favre.lib:bcrypt:0.10.2"
|
implementation "at.favre.lib:bcrypt:0.10.2"
|
||||||
implementation 'com.password4j:password4j:1.8.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"
|
testImplementation "junit:junit:$junitVersion"
|
||||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,14 @@ package com.github.Qortal.qortalMobile;
|
|||||||
|
|
||||||
import com.getcapacitor.BridgeActivity;
|
import com.getcapacitor.BridgeActivity;
|
||||||
import com.github.Qortal.qortalMobile.NativeBcrypt;
|
import com.github.Qortal.qortalMobile.NativeBcrypt;
|
||||||
|
import com.github.Qortal.qortalMobile.NativePOW;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
public class MainActivity extends BridgeActivity {
|
public class MainActivity extends BridgeActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
registerPlugin(NativeBcrypt.class);
|
registerPlugin(NativeBcrypt.class);
|
||||||
|
registerPlugin(NativePOW.class);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,<br>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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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<Double> 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<NTPServer> 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<NTPServer> pendingServers = ntpServers.stream().filter(ntpServer -> now >= ntpServer.nextPoll).collect(Collectors.toList());
|
||||||
|
|
||||||
|
CompletionService<Boolean> 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
BIN
android/app/src/main/res/raw/memorypow.wasm
Normal file
BIN
android/app/src/main/res/raw/memorypow.wasm
Normal file
Binary file not shown.
@ -29,6 +29,8 @@ import PhraseWallet from "./utils/generateWallet/phrase-wallet";
|
|||||||
import { RequestQueueWithPromise } from "./utils/queue/queue";
|
import { RequestQueueWithPromise } from "./utils/queue/queue";
|
||||||
import { validateAddress } from "./utils/validateAddress";
|
import { validateAddress } from "./utils/validateAddress";
|
||||||
import { Sha256 } from "asmcrypto.js";
|
import { Sha256 } from "asmcrypto.js";
|
||||||
|
import NativePOW from './utils/nativepow'
|
||||||
|
|
||||||
import { TradeBotRespondMultipleRequest } from "./transactions/TradeBotRespondMultipleRequest";
|
import { TradeBotRespondMultipleRequest } from "./transactions/TradeBotRespondMultipleRequest";
|
||||||
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from "./constants/resourceTypes";
|
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from "./constants/resourceTypes";
|
||||||
import {
|
import {
|
||||||
@ -98,7 +100,7 @@ import {
|
|||||||
import { getData, removeKeysAndLogout, storeData } from "./utils/chromeStorage";
|
import { getData, removeKeysAndLogout, storeData } from "./utils/chromeStorage";
|
||||||
import {BackgroundFetch} from '@transistorsoft/capacitor-background-fetch';
|
import {BackgroundFetch} from '@transistorsoft/capacitor-background-fetch';
|
||||||
import { LocalNotifications } from '@capacitor/local-notifications';
|
import { LocalNotifications } from '@capacitor/local-notifications';
|
||||||
import ChatComputePowWorker from './chatComputePow.worker.js?worker';
|
// import ChatComputePowWorker from './chatComputePow.worker.js?worker';
|
||||||
|
|
||||||
const uid = new ShortUniqueId({ length: 9, dictionary: 'number' });
|
const uid = new ShortUniqueId({ length: 9, dictionary: 'number' });
|
||||||
|
|
||||||
@ -383,29 +385,13 @@ function playNotificationSound() {
|
|||||||
// chrome.runtime.sendMessage({ action: "PLAY_NOTIFICATION_SOUND" });
|
// chrome.runtime.sendMessage({ action: "PLAY_NOTIFICATION_SOUND" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const worker = new ChatComputePowWorker()
|
// const worker = new ChatComputePowWorker()
|
||||||
|
|
||||||
export async function performPowTask(chatBytes, difficulty) {
|
export async function performPowTask(chatBytes, difficulty) {
|
||||||
return new Promise((resolve, reject) => {
|
const chatBytesArray = Uint8Array.from(Object.values(chatBytes));
|
||||||
worker.onmessage = (e) => {
|
const result = await NativePOW.computeProofOfWork({ chatBytes, difficulty });
|
||||||
if (e.data.error) {
|
return {nonce: result.nonce, chatBytesArray}
|
||||||
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 handleNotificationDirect = async (directs) => {
|
const handleNotificationDirect = async (directs) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Sha256 } from 'asmcrypto.js';
|
import { Sha256 } from 'asmcrypto.js';
|
||||||
import wasmInit from './memory-pow.wasm?init';
|
import wasmInit from './memory-pow.wasm?init';
|
||||||
|
import NativePOW from './utils/nativepow'
|
||||||
let compute; // Exported compute function from Wasm
|
let compute; // Exported compute function from Wasm
|
||||||
let memory; // WebAssembly.Memory instance
|
let memory; // WebAssembly.Memory instance
|
||||||
let heap; // Uint8Array view of the memory buffer
|
let heap; // Uint8Array view of the memory buffer
|
||||||
@ -58,24 +58,26 @@ function sbrk(size) {
|
|||||||
|
|
||||||
// Proof-of-Work computation function
|
// Proof-of-Work computation function
|
||||||
async function computePow(chatBytes, difficulty) {
|
async function computePow(chatBytes, difficulty) {
|
||||||
if (!compute) {
|
// if (!compute) {
|
||||||
throw new Error('WebAssembly module not initialized. Call loadWasm first.');
|
// throw new Error('WebAssembly module not initialized. Call loadWasm first.');
|
||||||
}
|
// }
|
||||||
|
|
||||||
const chatBytesArray = Uint8Array.from(Object.values(chatBytes));
|
// const chatBytesArray = Uint8Array.from(Object.values(chatBytes));
|
||||||
const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;
|
// const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;
|
||||||
|
|
||||||
// Allocate memory for the hash
|
// // Allocate memory for the hash
|
||||||
const hashPtr = sbrk(32);
|
// const hashPtr = sbrk(32);
|
||||||
const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
|
// const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
|
||||||
hashAry.set(chatBytesHash);
|
// hashAry.set(chatBytesHash);
|
||||||
|
|
||||||
// Reuse the work buffer if already allocated
|
// // Reuse the work buffer if already allocated
|
||||||
if (!workBufferPtr) {
|
// if (!workBufferPtr) {
|
||||||
workBufferPtr = sbrk(workBufferLength);
|
// workBufferPtr = sbrk(workBufferLength);
|
||||||
}
|
// }
|
||||||
|
console.log('native')
|
||||||
const nonce = compute(hashPtr, workBufferPtr, workBufferLength, difficulty);
|
const nonce = await NativePOW.computeProofOfWork({ chatBytes, difficulty });
|
||||||
|
console.log('nonce', nonce)
|
||||||
|
(hashPtr, workBufferPtr, workBufferLength, difficulty);
|
||||||
|
|
||||||
return { nonce, chatBytesArray };
|
return { nonce, chatBytesArray };
|
||||||
}
|
}
|
||||||
@ -86,9 +88,9 @@ self.addEventListener('message', async (e) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Initialize Wasm if not already done
|
// Initialize Wasm if not already done
|
||||||
if (!compute) {
|
// if (!compute) {
|
||||||
await loadWasm();
|
// await loadWasm();
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Perform the POW computation
|
// Perform the POW computation
|
||||||
const result = await computePow(chatBytes, difficulty);
|
const result = await computePow(chatBytes, difficulty);
|
||||||
|
@ -7,6 +7,7 @@ import { ThemeProvider, createTheme } from '@mui/material/styles';
|
|||||||
import { CssBaseline } from '@mui/material';
|
import { CssBaseline } from '@mui/material';
|
||||||
import { MessageQueueProvider } from './MessageQueueContext.tsx';
|
import { MessageQueueProvider } from './MessageQueueContext.tsx';
|
||||||
import { RecoilRoot } from 'recoil';
|
import { RecoilRoot } from 'recoil';
|
||||||
|
import './utils/nativepow.ts'
|
||||||
const theme = createTheme({
|
const theme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
|
9
src/utils/nativepow.ts
Normal file
9
src/utils/nativepow.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { registerPlugin } from '@capacitor/core';
|
||||||
|
|
||||||
|
export interface NativePOWPlugin {
|
||||||
|
computeProofOfWork(options: { chatBytes: string; difficulty: number }): Promise<{ nonce: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NativePOW = registerPlugin<NativePOWPlugin>('NativePOW');
|
||||||
|
|
||||||
|
export default NativePOW
|
Loading…
x
Reference in New Issue
Block a user