Skip to content

Commit

Permalink
Merge pull request #294 from aionnetwork/dev-Ross
Browse files Browse the repository at this point in the history
Native Equihash Validation
  • Loading branch information
aion-jin authored Mar 27, 2018
2 parents 0f25aae + bf519c4 commit a424f10
Show file tree
Hide file tree
Showing 18 changed files with 694 additions and 451 deletions.
1 change: 1 addition & 0 deletions modAion/src/org/aion/zero/types/A0BlockHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ private String toStringWithSuffix(final String suffix) {
.append(txTrieRoot.length).append(suffix);
toStringBuff.append(" receiptsTrieHash=").append(toHexString(receiptTrieRoot)).append(" receiptTrieRoot: ")
.append(receiptTrieRoot.length).append(suffix);
toStringBuff.append(" logsBloom=").append(toHexString(logsBloom)).append(suffix);
toStringBuff.append(" difficulty=").append(toHexString(difficulty)).append(" difficulty: ")
.append(difficulty.length).append(suffix);
toStringBuff.append(" number=").append(number).append(suffix);
Expand Down
126 changes: 112 additions & 14 deletions modAionImpl/src/org/aion/equihash/OptimizedEquiValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@
******************************************************************************/
package org.aion.equihash;

import org.aion.crypto.HashUtil;
import org.aion.crypto.hash.Blake2b;
import org.aion.crypto.hash.Blake2bNative;
import org.aion.log.AionLoggerFactory;
import org.aion.log.LogEnum;
import org.slf4j.Logger;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import static org.aion.base.util.ByteUtil.*;

Expand All @@ -42,9 +45,9 @@ public class OptimizedEquiValidator {
private int collisionBitLength;
private int collisionByteLength;
private int solutionWidth;
private HashSet<Integer> indexSet;
//private HashSet<Integer> indexSet;
protected static final Logger LOG = AionLoggerFactory.getLogger(LogEnum.CONS.name());

int iter = 0;

private Blake2b.Param initState;

Expand All @@ -60,8 +63,8 @@ public OptimizedEquiValidator(int n, int k) {
this.collisionByteLength = (collisionBitLength + 7) / 8;
this.solutionWidth = (1 << k) * (collisionBitLength + 1) / 8;
this.initState = this.InitialiseState();
this.hashes = new byte[solutionWidth][indicesHashLength];
this.indexSet = new HashSet<>();
this.hashes = new byte[512][indicesHashLength];
//this.indexSet = new HashSet<>();
}

/**
Expand Down Expand Up @@ -89,7 +92,7 @@ private Blake2b.Param InitialiseState() {
* @return True if valid solution based on block header and nonce
* @throws NullPointerException
*/
public synchronized boolean isValidSolution(byte[] solution, byte[] blockHeader, byte[] nonce) throws NullPointerException {
public boolean isValidSolution(byte[] solution, byte[] blockHeader, byte[] nonce) throws NullPointerException {
if (solution == null) {
LOG.debug("Null solution passed for validation");
throw new NullPointerException("Null solution");
Expand Down Expand Up @@ -117,16 +120,53 @@ public synchronized boolean isValidSolution(byte[] solution, byte[] blockHeader,

byte[] hash = new byte[indicesHashLength];

hashes = new byte[solutionWidth][indicesHashLength];

return verify(blockHeader, nonce, blake, indices, 0, hash, k);
}

public boolean isValidSolutionNative(byte[] solution, byte[] blockHeader, byte[] nonce) throws NullPointerException {
if (solution == null) {
LOG.debug("Null solution passed for validation");
throw new NullPointerException("Null solution");
} else if (blockHeader == null) {
LOG.debug("Null blockHeader passed for validation");
throw new NullPointerException("Null blockHeader");
} else if (nonce == null) {
LOG.debug("Null nonce passed for validation");
throw new NullPointerException("Null nonce");
}

if (solution.length != solutionWidth) {
LOG.debug("Invalid solution width: {}", solution.length);
return false;
}

int[] indices = EquiUtils.getIndicesFromMinimal(solution, collisionBitLength);

if (hasDuplicate(indices)) {
LOG.debug("Invalid solution - duplicate solution index");
return false;
}

byte[] hash = new byte[indicesHashLength];


byte[][] nativeHash = HashUtil.getSolutionHash(
merge("AION0PoW".getBytes(), merge(intToBytesLE(n), intToBytesLE(k))),
nonce,
indices,
blockHeader
);

return verifyNative(indices, 0, hash, k, nativeHash);
}

/**
* Generate hash based on indices and index.
* The generated hash is placed in the hashes array based on the index
*/
private void genHash(byte[] blockHeader, byte[] nonce, Blake2b blake, int[] indices, int index) {
private void genHash(byte[] blockHeader, byte[] nonce, Blake2b blake, int[] indices, int index, byte[] hash) {

iter++;
// Clear blake and re-use
blake.reset();

Expand All @@ -142,13 +182,13 @@ private void genHash(byte[] blockHeader, byte[] nonce, Blake2b blake, int[] indi

byte[] tmpHash = blake.digest();

System.arraycopy(tmpHash, (indices[index] % indicesPerHashOutput) * indicesHashLength, hashes[index], 0, indicesHashLength);
System.arraycopy(tmpHash, (indices[index] % indicesPerHashOutput) * indicesHashLength, hash, 0, indicesHashLength);
}

private boolean verify(byte[] blockHeader, byte[] nonce, Blake2b blake, int[] indices, int index, byte[] hash, int round) {
if(round == 0) {
//Generate hash
genHash(blockHeader, nonce, blake, indices, index);
genHash(blockHeader, nonce, blake, indices, index, hash);
return true;
}

Expand All @@ -160,20 +200,23 @@ private boolean verify(byte[] blockHeader, byte[] nonce, Blake2b blake, int[] in
return false;
}

boolean verify0 = verify(blockHeader, nonce, blake, indices, index, hashes[index], round-1);
byte[] hash0 = new byte[indicesHashLength];
byte[] hash1 = new byte[indicesHashLength];

boolean verify0 = verify(blockHeader, nonce, blake, indices, index, hash0, round-1);
if(!verify0) {
LOG.debug("Solution validation failed - unable to verify left subtree");
return false;
}

boolean verify1 = verify(blockHeader, nonce, blake, indices, index1, hashes[index1], round-1);
boolean verify1 = verify(blockHeader, nonce, blake, indices, index1, hash1, round-1);
if(!verify1) {
LOG.debug("Solution validation failed - unable to verify right subtree");
return false;
}

for(int i = 0; i < indicesHashLength; i++)
hash[i] = (byte)(hashes[index][i] ^ hashes[index1][i]);
hash[i] = (byte)(hash0[i] ^ hash1[i]);

int bits = (round < k ? round * collisionBitLength : n);

Expand All @@ -193,15 +236,70 @@ private boolean verify(byte[] blockHeader, byte[] nonce, Blake2b blake, int[] in
return true;
}

/*
Validation based on hashes generated by native blake2b impl
*/
private boolean verifyNative(int[] indices, int index, byte[] hash, int round, byte[][] hashes) {
if(round == 0) {
return true;
}

int index1 = index + (1 << (round-1));

// Check out of order indices
if(indices[index] >= indices[index1]) {
LOG.debug("Solution validation failed - indices out of order");
return false;
}

byte[] hash0 = hashes[index];
byte[] hash1 = hashes[index1];

boolean verify0 = verifyNative(indices, index, hash0, round-1, hashes);
if(!verify0) {
LOG.debug("Solution validation failed - unable to verify left subtree");
return false;
}

boolean verify1 = verifyNative(indices, index1, hash1, round-1, hashes);
if(!verify1) {
LOG.debug("Solution validation failed - unable to verify right subtree");
return false;
}

for(int i = 0; i < indicesHashLength; i++)
hash[i] = (byte)(hash0[i] ^ hash1[i]);

int bits = (round < k ? round * collisionBitLength : n);

for(int i = 0; i < bits/8; i++) {
if(hash[i] != 0) {
LOG.debug("Solution validation failed - Non-zero XOR");
return false;
}
}

// Check remainder bits
if((bits%8) > 0 && (hash[bits/8] >> (8 - (bits%8)) ) != 0) {
LOG.debug("Solution validation failed - Non-zero XOR");
return false;
}

return true;
}


/*
* Check if duplicates are present in the solutions index array
*/
private boolean hasDuplicate(int[] indices) {
//TODO: Switch this to a threadLocal implementation; avoid for now until threading strategy is determined.
Set<Integer> indexSet = new HashSet<>(512);

for(int index: indices) {
if(!indexSet.add(index))
return true;
}
indexSet.clear();

return false;
}
Expand Down
4 changes: 4 additions & 0 deletions modAionImpl/src/org/aion/zero/impl/sync/SyncMgr.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ public void validateAndAddHeaders(int _nodeIdHashcode, String _displayId, List<A
// ignore this batch if any invalidated header
if(!this.blockHeaderValidator.validate(current, log)) {
log.debug("<invalid-header num={} hash={}>", current.getNumber(), current.getHash());

// Print header to allow debugging
log.debug("Invalid header: {}", current.toString());

return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public EquihashSolutionRule(OptimizedEquiValidator validator) {

@Override
public boolean validate(A0BlockHeader header, List<RuleError> errors) {
if (!validator.isValidSolution(header.getSolution(), header.getHeaderBytes(true), header.getNonce())) {
if (!validator.isValidSolutionNative(header.getSolution(), header.getHeaderBytes(true), header.getNonce())) {
addError("Invalid solution", errors);
return false;
}
Expand Down
Loading

0 comments on commit a424f10

Please sign in to comment.