Reputation: 41
I am working on a project and as a part of it, I have to roughly simulate the Bitcoin Proof of Work computation. This involves iteratively computing SHA256 twice on a concatenation of a fixed "BlockHash" string and a 32-bit int nonce which is incremented every iteration. If the computed hash is less than a "TargetHash" string, we break the loop and print the nonce value.
I am trying to compare two sequential implementations, one written using C++ using OpenSSL's SHA256 implementation, and the other in Java using JDK's internal SHA256 implementation. I was expecting the OpenSSL implementation to be much faster than JDK, but the opposite is happening.
Here is my Java code:
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SHA256 {
/**
* convert byte[] to hex string
*
* @param hash
* @return hex string
*/
private static String bytesToHex(byte[] hash) {
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
/**
* get a sha256 of the input string
*
* @param inputString
* @return resulting hash in hex string
*/
public static String SHA256(String inputString) {
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
return bytesToHex(sha256.digest(inputString.getBytes(StandardCharsets.UTF_8)));
} catch (NoSuchAlgorithmException ex) {
System.err.println(ex.toString());
return null;
}
}
public static void main(String[] args){
String blockHash = SHA256("Some random string to generate a block hash.");
System.out.println("blockHash: " + blockHash);
String targetHash = "000000938023b712892a41e8438e3ff2242a68747105de0395826f60b38d88dc";
String tmp_hash="undefined";
int nonce = 0;
for(nonce=Integer.MIN_VALUE; nonce<=Integer.MAX_VALUE; nonce++) {
tmp_hash = SHA256(SHA256(blockHash+String.valueOf(nonce)));
if(targetHash.compareTo(tmp_hash)>0)
break;
}
System.out.println("Resulting Hash: " + tmp_hash);
System.out.println("Nonce:" + nonce);
}
}
And this is my C++ implementation:
#include <iostream>
#include <climits>
#include <cstring>
#include <sstream>
#include <string>
#include <iomanip>
#include "format.h"
using namespace std;
#include <openssl/sha.h>
string sha256(const string str)
{
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, str.c_str(), str.size());
SHA256_Final(hash, &sha256);
stringstream ss;
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++)
{
ss << hex << setw(2) << setfill('0') << (int)hash[i];
}
return ss.str();
}
int main(int argc, char *argv[])
{
string input = "Some random string to generate a block hash.";
string blockHash = sha256(input);
cout << "blockHash: " << blockHash << endl;
string targetHash = "000000938023b712892a41e8438e3ff2242a68747105de0395826f60b38d88dc";
string tmp_hash="undefined";
int nonce = 0;
for(nonce = INT_MIN; nonce <= INT_MAX; nonce++){
tmp_hash = sha256(sha256(fmt::format("{}{}", blockHash, nonce)));
if(strcmp(tmp_hash.c_str(), targetHash.c_str()) < 0)
break;
}
cout<<"Resulting Hash: "<<tmp_hash<<endl;
cout<<"Nonce: "<<nonce<<endl;
return 0;
}
The outputs using linux 'time' utility to measure runtime:
javac SHA256.java
time java SHA256
blockHash: 596143a6a70a23c86e4b218afeb05d151ed45a39e96368e213d17e0a491d894a
Resulting Hash: 0000008ce61c628ffb00b6668687504fd5d44da0a57adb40d6ff59f8e4af0a4a
Nonce:-2135751361
real 0m22.258s
user 0m22.977s
sys 0m0.097s
g++ -O2 -DFMT_HEADER_ONLY main.cpp -lcrypto -lssl
time ./a.out
blockHash: 596143a6a70a23c86e4b218afeb05d151ed45a39e96368e213d17e0a491d894a
Resulting Hash: 0000008ce61c628ffb00b6668687504fd5d44da0a57adb40d6ff59f8e4af0a4a
Nonce: -2135751361
real 0m35.703s
user 0m35.693s
sys 0m0.005s
This is just for an easy TargetHash, for more difficult ones, the difference is even greater. I am pretty sure here openssl sha256 implementation isn't a bottleneck and something else is, but being new to C++ I am not sure what. I was earlier using to_string(nonce) and s1.compare(s2), which I replaced with fmt::format and strcmp because they're faster, but still could only gain a few seconds. Any ideas will be really appreciated.
Upvotes: 0
Views: 1123
Reputation: 582
The bottleneck for your c++ code is your custom bytes_to_string function. Calling stringstream functions in a loop simply hits the performance.
You might want to look at this answer to another question.
replace the stringstream functions with the following code snippet. It is faster because it manipulates the string memory directly.
static const char characters[] = "0123456789ABCDEF";
std::string result (SHA256_DIGEST_LENGTH * 2, ' ');
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++)
{
result[2*i] = characters[(unsigned int) hash[i] >> 4];
result[2*i+1] = characters[(unsigned int) hash[i] & 0x0F];
}
return result;
Upvotes: 1