Hush Full Node software. We were censored from Github, this is where all development happens now. https://hush.is
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

233 lines
12 KiB

// Copyright 2023 The Accumulate Authors
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
#include <filesystem>
#include <iostream>
#include <fstream>
#include <cstdio>
#include <chrono>
#include <ctime>
#include "../include/lxrhash.h"
// LXRHash uses a combination of shifts and exclusive or's to combine inputs and states to
// create a hash with strong avalanche properties. This means that if any one bit is changed
// in the inputs, on average half of the bits of the output change. This quality makes an
// algorithmic attack on mining very, very unlikely.
//
// First, the hash and nonce are "mixed" such that a reasonable avalanche property is obtained.
//
// Secondly, the resulting 48 bytes are mapped through a 1 GB byteMap where random accesses have
// an even chance of producing a value between 0-255. Cycling through lookups from random bytes in
// the 1 GB space produces a resulting value to be graded.
//
// The Shift XoR Random Number Generator (RNG) was invented by George Marsaglia and published
// in 2003. Please refer to the readme on the LXRHashing repository.
// https://gitlab.com/accumulatenetwork/ecosystem/LXRMining/-/blob/main/README.md
//
// * No elements of the triplets are zero
// * All elements are relatively prime to each other
// * All elements Are small relative to an unsigned long long (64)
// * The right shift generally smaller than the left shifts
//
// In my testing, and according to Marsaglia, no elements of these triplets fitting this criteria
// perform better or worse. Adding constants and other outside elements help the distribution of
// values.
//
// Further, two different sponge states are used, mix() and LXRPoW()
//
// mix()
// Combines the 32 byte hash with the unsigned long long nonce. This is done as rapidly as possible
// with an avalanche property only enough to ensure repeated hashes with minimal bit changes to
// nonces do not gain an advantage during mining by tracing the same paths through the byteMap,
// making better use of the cache.
//
// LXRPoW()
// Accepts a 32 byte hash with the unsigned long long nonce. The sponge is initialized using mix().
// Then Each byte is translated using a sequenced Pseudo random sequenced index into the
// 1 GB byteMap. This process is repeated for a number of loops, building up a sponge value.
// More loops makes the hash slower and more dependent on the random access speed to the
// 1 GB byteMap. As processors grow in speed, and chips accommodate greater caches, the
// byteMap and the loop number increased.
//
// mix() is then used again to "squeeze out" the hash, where the high 8 bytes represent the
// possible PoW solution.
// check()
// Quick checksum, with a bit of bit spreading to make the checksum stronger
unsigned long long LXRHash::check()
{
unsigned long long state = 0;
for (int i = 0; i < Size; i += 8)
{
unsigned long long a = 0; // Pull a long Big Endian value from byteMap
for (int j = 0; j < 8; j++)
{
a <<= 8;
a ^= (unsigned long long)(byteMap[i + j]);
}
state ^= state << 23 ^ a << 11; // Randomize state, fold in the long from byteMap
state ^= state >> 3; // in the byteMap, running them through a
state ^= state << 17; // Marsaglia Xorshift RNG
}
return state; // Return resulting state as the checksum
}
// init()
// initializes the 1 GB byteMap
// Please note that I just wrote cheap code to cache the byteMap. Since it takes about 5 minutes
// or more to generate the byteMap, this code looks for a byteMap.dat file and loads it if it
// exists.
void LXRHash::init()
{
if (LXRHash::load())
{
return;
}
printf("\nbyteMap.dat file not found. Building byteMap.dat file is slow. This takes six passes:\n");
// Initialize the byteMap with values from 0 to 255 repeated for the whole array
for (unsigned long long i = 0; i < Size; i++)
{
byteMap[i] = i;
}
printf("start cs %llx\n", check());
unsigned long long offset = 0x7a43b26c611e135f; // Arbitrary 64 bit initial offset (no zero bytes)
unsigned long long cnt = 0; // Marsaglia found a counter improves Xorshift
for (unsigned long long i = 0; i < Passes; i++) // Passes. 2 passes at least are needed.
{ //
for (unsigned long long j = 0; j < Size; j++) // Shuffle the byteMap by swapping every byte with
{ // another randomly chosen byte.
offset ^= offset << 13 ^ cnt; // More classical Marsaglia Xorshift RNG
offset ^= offset >> 23; //
offset ^= offset << 5; //
unsigned long long r = offset & MapMask; // Mask r to an index within the byteMap
unsigned char a = byteMap[r]; // Get the random byte
byteMap[r] = byteMap[j]; // Put the ith value there
byteMap[j] = a; // Put the random byte at the ith position
cnt += a; // Walk cnt forward by a, (0-255)
if (j % 200000000 == 0) // Give feedback
{ //
printf("."); // A period every 200M bytes swapped
fflush(stdout); // flush so user can see
} //
} //
printf("%llu", i + 1); // Count the passes
fflush(stdout);
}
printf("\n");
printf("Checksum: %llx\n", check()); // Print the file checksum
std::fstream byteMapFile;
byteMapFile = std::fstream("byteMap.dat", std::ios::out | std::ios::binary);
byteMapFile.write((char *)(byteMap), Size);
byteMapFile.close();
}
bool LXRHash::load()
{
// open the file:
std::streampos fileSize;
std::ifstream file("byteMap.dat", std::ios::binary);
// get its size:
file.seekg(0, std::ios::end); // Seek to the end
fileSize = file.tellg(); // get the file size
if (fileSize != Size) // if not what we expect (exactly the length of byteMap)
{ //
return (false); // then return false (to generate the right file)
} //
file.seekg(0, std::ios::beg); // Seek to the start of the file,
file.read((char *)(byteMap), Size); // and read the full byteMap
file.close(); //
unsigned long long cs = check(); //
if (check() != checksum) // Then check that the checksum is correct
{ //
printf("checksum mismatch l:%llx != cs:%llx\n", cs, checksum);
std::filesystem::remove("byteMap.dat"); // Remove the bad file
return (false); // Return fail
} //
return true; // If so, all good, don't need to regenerate the byteMap
}
unsigned long long LXRHash::LxrPoW(char unsigned hash[32], unsigned long long nonce)
{
// Mix the hash with the nonce to ensure the path through the byteMap is unique even if one bit
unsigned long long state = LXRHash::mix(hash, nonce); // is changed from another submission.
// Make the specified "loops" through the hash. The initial state is returned by Mix,
// bringing the total number of bits involved in the "sponge" to 48. Increasing the
// loops would slow down the hash, giving this mechanism an easy way to keep difficulty
// within the limits of the total hash power in a protocol
for (int i = 0; i < Loops; i++) // Make our loops over the
{ // entire hash
for (int j = 0; j < 32; j++) // Process each byte
{ //
unsigned long long b, v; // Do all math in unsigned long long
b = byteMap[state & MapMask]; // Pull a byte from the ByteMap
v = hash[j]; // Get a byte from the hash
state ^= state << 17 ^ b << 31; // A modified XorShift RNG
state ^= state >> 7 ^ v << 23; // Fold in a random byte from
state ^= state << 5; // the byteMap and the byte
hash[j] = state; // from the hash.
} // Then update the hash byte.
}
state = LXRHash::mix(hash, state); // Do a final mix() using state
return state; // as the nonce and return the state.
}
// mix()
// Mix spreads the bits of the hash and nonce throughout the hash and a "state"
unsigned long long LXRHash::mix(char unsigned hash[32], unsigned long long nonce)
{
unsigned long long state = 0xb32c25d16e362f32; // Set the state to an arbitrary unsigned long long with bits
nonce ^= nonce << 19 ^ state; // Run the nonce through a Xorshift
nonce ^= nonce >> 1;
nonce ^= nonce << 3;
unsigned long long array[5]; // Array of longs, the nonce, and 4 longs holding hash
array[0] = nonce; // this array along with the state is the sponge for the LXRHash
for (int i = 1; i < 5; i++) // Walk through the array
{ //
array[i] = 0; // Clear the array (not really needed, but it's cleaner)
for (int j = 0; j < 8; j++) // Get 8 bytes of the hash as array entry
{ //
array[i] <<= 8; // Shift the array to the left a byte
array[i] |= hash[((i - 1) << 3) + j]; // Or in the next byte
} //
state ^= state << 7 ^ array[i]; // shift state combine the whole hash into
state ^= state >> 3; // the state while randomizing the state
state ^= state << 5; // through a Xorshift RNG.
}
// mix what is effectively sponge of 5 elements and a state
for (int i = 0; i < 5; i++)
{
unsigned long long a, left1, right, left2;
a = array[i]; // Get each element of the array
left1 = (a & 0x1F) | 1; // Use the element to pick 3 random shifts for the Xorshift
right = ((a & 0xF0) >> 5) | 1; // Note that the right is smaller than the left
left2 = (((a & 0x1F0000) >> 17) << 1) ^ left1; // And xor-ing left1 with left2 ensures they are different
state ^= state << left1 ^ a << 5; // Randomize the state and array element
state ^= state >> right; // using the random shifts
state ^= state << left2; //
array[i] ^= state; // apply to the array
}
// Extract the 32 byte hash into the *hash passed in.
for (int i = 0; i < 4; i++)
{
unsigned long long a = array[i] ^ state;
for (int j = 0; j < 8; j++) // for 8 bytes
{ // Put each byte into hash, Big Endian order
hash[i * 8 + j] = a >> ((7 - j) * 8); // i indexes 8 byte group, j each byte
} //
state ^= state << 27 ^ i << 19; // Mix the state a bit more, fold in the counter
state ^= state >> 11; //
state ^= state << 13; //
}
return state;
}