Add separate ProofOfWork.solution proto field for Equihash

Avoid repurposing the 'ProofOfWork.payload' field for Equihash puzzle
solutions, as that may be of later use in interactive PoW schemes such
as P2P network DoS protection (where the challenge may be a random nonce
instead of derived from the offer ID). Instead, make the payload the
UTF-8 bytes of the offer ID, just as with Hashcash.

Also, make the puzzle seed the SHA-256 hash of the payload concatenated
with the challenge, instead of just the 256-bit challenge on its own, so
that the PoW is tied to a particular payload and cannot be reused for
other payloads in the case of future randomly chosen challenges.
This commit is contained in:
Steven Barclay 2021-12-07 02:22:33 +00:00
parent 24d2a7f222
commit db6025a08d
No known key found for this signature in database
GPG Key ID: 9FED6BF1176D500B
5 changed files with 34 additions and 17 deletions

View File

@ -17,6 +17,7 @@
package bisq.common.crypto;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Longs;
import java.util.Arrays;
@ -34,21 +35,26 @@ public class EquihashProofOfWorkService extends ProofOfWorkService {
}
@Override
public CompletableFuture<ProofOfWork> mint(String itemId, byte[] challenge, double difficulty) {
public CompletableFuture<ProofOfWork> mint(byte[] payload, byte[] challenge, double difficulty) {
double scaledDifficulty = scaledDifficulty(difficulty);
log.info("Got scaled & adjusted difficulty: {}", scaledDifficulty);
return CompletableFuture.supplyAsync(() -> {
long ts = System.currentTimeMillis();
byte[] solution = new Equihash(90, 5, scaledDifficulty).puzzle(challenge).findSolution().serialize();
byte[] seed = getSeed(payload, challenge);
byte[] solution = new Equihash(90, 5, scaledDifficulty).puzzle(seed).findSolution().serialize();
long counter = Longs.fromByteArray(Arrays.copyOf(solution, 8));
var proofOfWork = new ProofOfWork(solution, counter, challenge, difficulty,
System.currentTimeMillis() - ts, getVersion());
var proofOfWork = new ProofOfWork(payload, counter, challenge, difficulty,
System.currentTimeMillis() - ts, solution, getVersion());
log.info("Completed minting proofOfWork: {}", proofOfWork);
return proofOfWork;
});
}
private byte[] getSeed(byte[] payload, byte[] challenge) {
return Hash.getSha256Hash(Bytes.concat(payload, challenge));
}
@Override
public byte[] getChallenge(String itemId, String ownerId) {
String escapedItemId = itemId.replace(" ", " ");
@ -60,8 +66,9 @@ public class EquihashProofOfWorkService extends ProofOfWorkService {
boolean verify(ProofOfWork proofOfWork) {
double scaledDifficulty = scaledDifficulty(proofOfWork.getDifficulty());
var puzzle = new Equihash(90, 5, scaledDifficulty).puzzle(proofOfWork.getChallenge());
return puzzle.deserializeSolution(proofOfWork.getPayload()).verify();
byte[] seed = getSeed(proofOfWork.getPayload(), proofOfWork.getChallenge());
var puzzle = new Equihash(90, 5, scaledDifficulty).puzzle(seed);
return puzzle.deserializeSolution(proofOfWork.getSolution()).verify();
}
private static double scaledDifficulty(double difficulty) {

View File

@ -39,11 +39,6 @@ public class HashCashService extends ProofOfWorkService {
}
@Override
public CompletableFuture<ProofOfWork> mint(String itemId, byte[] challenge, double difficulty) {
byte[] payload = getBytes(itemId);
return mint(payload, challenge, difficulty);
}
public CompletableFuture<ProofOfWork> mint(byte[] payload,
byte[] challenge,
double difficulty) {
@ -56,7 +51,9 @@ public class HashCashService extends ProofOfWorkService {
hash = toSha256Hash(payload, challenge, ++counter);
}
while (numberOfLeadingZeros(hash) <= log2Difficulty);
ProofOfWork proofOfWork = new ProofOfWork(payload, counter, challenge, difficulty, System.currentTimeMillis() - ts, 0);
byte[] solution = Longs.toByteArray(counter);
ProofOfWork proofOfWork = new ProofOfWork(payload, counter, challenge, difficulty,
System.currentTimeMillis() - ts, solution, 0);
log.info("Completed minting proofOfWork: {}", proofOfWork);
return proofOfWork;
});

View File

@ -37,6 +37,8 @@ public final class ProofOfWork implements NetworkPayload {
@Getter
private final long duration;
@Getter
private final byte[] solution;
@Getter
private final int version;
public ProofOfWork(byte[] payload,
@ -44,12 +46,14 @@ public final class ProofOfWork implements NetworkPayload {
byte[] challenge,
double difficulty,
long duration,
byte[] solution,
int version) {
this.payload = payload;
this.counter = counter;
this.challenge = challenge;
this.difficulty = difficulty;
this.duration = duration;
this.solution = solution;
this.version = version;
}
@ -66,6 +70,7 @@ public final class ProofOfWork implements NetworkPayload {
.setChallenge(ByteString.copyFrom(challenge))
.setDifficulty(difficulty)
.setDuration(duration)
.setSolution(ByteString.copyFrom(solution))
.setVersion(version)
.build();
}
@ -77,6 +82,7 @@ public final class ProofOfWork implements NetworkPayload {
proto.getChallenge().toByteArray(),
proto.getDifficulty(),
proto.getDuration(),
proto.getSolution().toByteArray(),
proto.getVersion()
);
}

View File

@ -19,6 +19,8 @@ package bisq.common.crypto;
import com.google.common.base.Preconditions;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@ -45,14 +47,18 @@ public abstract class ProofOfWorkService {
this.version = version;
}
public abstract CompletableFuture<ProofOfWork> mint(String itemId, byte[] challenge, double difficulty);
public abstract byte[] getChallenge(String itemId, String ownerId);
public abstract CompletableFuture<ProofOfWork> mint(byte[] payload, byte[] challenge, double difficulty);
abstract boolean verify(ProofOfWork proofOfWork);
public byte[] getPayload(String itemId) {
return itemId.getBytes(StandardCharsets.UTF_8);
}
public abstract byte[] getChallenge(String itemId, String ownerId);
public CompletableFuture<ProofOfWork> mint(String itemId, String ownerId, double difficulty) {
return mint(itemId, getChallenge(itemId, ownerId), difficulty);
return mint(getPayload(itemId), getChallenge(itemId, ownerId), difficulty);
}
public boolean verify(ProofOfWork proofOfWork,

View File

@ -884,7 +884,8 @@ message ProofOfWork {
bytes challenge = 3;
double difficulty = 4;
int64 duration = 5;
int32 version = 6;
bytes solution = 6;
int32 version = 7;
}
message AccountAgeWitness {