diff --git a/mining/arpow_miner.py b/mining/arpow_miner.py new file mode 100644 index 0000000..8720cc9 --- /dev/null +++ b/mining/arpow_miner.py @@ -0,0 +1,104 @@ +from pyethereum import utils +import random + + +def sha3(x): + return utils.decode_int(utils.zunpad(utils.sha3(x))) + + +class SeedObj(): + def __init__(self, seed): + self.seed = seed + self.a = 3**160 + self.c = 7**80 + self.n = 2**256 - 4294968273 # secp256k1n, why not + + def rand(self, r): + self.seed = (self.seed * self.a + self.c) % self.n + return self.seed % r + + +def encode_int(x): + o = '' + for _ in range(8): + o = chr(x % 256) + o + x //= 256 + return o + + +ops = { + "plus": lambda x, y: (x + y) % 2**64, + "times": lambda x, y: (x * y) % 2**64, + "xor": lambda x, y: x ^ y, + "and": lambda x, y: x & y, + "or": lambda x, y: x | y, + "not": lambda x, y: 2**64-1-x, + "nxor": lambda x, y: (2**64-1-x) ^ y, + "rshift": lambda x, y: x >> (y % 64) +} + + +def gentape(W, H, SEED): + s = SeedObj(SEED) + tape = [] + for i in range(H): + op = ops.keys()[s.rand(len(ops))] + r = s.rand(100) + if r < 20 and i > 20: + x1 = tape[-r]["x1"] + else: + x1 = s.rand(W) + x2 = s.rand(W) + tape.append({"op": op, "x1": x1, "x2": x2}) + return tape + + +def runtape(TAPE, SEED, params): + s = SeedObj(SEED) + # Fill up tape inputs + mem = [0] * params["w"] + for i in range(params["w"]): + mem[i] = s.rand(2**64) + # Direction variable (run forwards or backwards) + dir = 1 + # Run the tape on the inputs + for i in range(params["h"] // 100): + for j in (range(100) if dir == 1 else range(99, -1, -1)): + t = TAPE[i * 100 + j] + mem[t["x1"]] = ops[t["op"]](mem[t["x1"]], mem[t["x2"]]) + # 16% of the time, we flip the order of the next 100 ops; + # this adds in conditional branching + if 2 < mem[t["x1"]] % 37 < 9: + dir *= -1 + return sha3(''.join(encode_int(x) for x in mem)) + + +def PoWVerify(header, nonce, params): + tape = gentape(params["w"], params["h"], + sha3(header + encode_int(nonce // params["n"]))) + h = runtape(tape, sha3(header + encode_int(nonce)), params) + print h + return h < 2**256 / params["diff"] + + +def PoWRun(header, params): + # actual randomness, so that miners don't overlap + nonce = random.randrange(2**50) * params["n"] + tape = None + while 1: + print nonce + if nonce % params["n"] == 0: + tape = gentape(params["w"], params["h"], + sha3(header + encode_int(nonce // params["n"]))) + h = runtape(tape, sha3(header + encode_int(nonce)), params) + if h < 2**256 / params["diff"]: + return nonce + else: + nonce += 1 + +params = { + "w": 100, + "h": 15000, # generally, w*log(w) at least + "diff": 2**24, # initial + "n": 1000 +} diff --git a/mining/hashimoto.py b/mining/hashimoto.py new file mode 100644 index 0000000..763e51a --- /dev/null +++ b/mining/hashimoto.py @@ -0,0 +1,128 @@ +try: + shathree = __import__('sha3') +except: + shathree = __import__('python_sha3') +import random +import time + + +def sha3(x): + return decode_int(shathree.sha3_256(x).digest()) # + + +def decode_int(s): + o = 0 + for i in range(len(s)): + o = o * 256 + ord(s[i]) + return o + + +def encode_int(x): + o = '' + for _ in range(32): + o = chr(x % 256) + o + x //= 256 + return o + +P = 2**256 - 4294968273 + + +def produce_dag(params, seed): + o = [sha3(seed)] + init = o[0] + picker = 1 + for i in range(1, params["n"]): + x = 0 + picker = (picker * init) % P + curpicker = picker + for j in range(params["k"]): + x |= o[curpicker % i] + curpicker >>= 10 + o.append((x * x) % P) # use any "hash function" here + return o + + +def quick_calc(params, seed, pos): + init = sha3(seed) + known = {0: init} + + def calc(p): + if p not in known: + picker = pow(init, p, P) + x = 0 + for j in range(params["k"]): + x |= calc(picker % p) + picker >>= 10 + known[p] = (x * x) % P + return known[p] + + o = calc(pos) + print 'Calculated pos %d with %d accesses' % (pos, len(known)) + return o + + +def hashimoto(daggerset, params, header, nonce): + rand = sha3(header+encode_int(nonce)) + mix = 0 + for i in range(40): + shifted_A = rand >> i + dag = daggerset[shifted_A % params["numdags"]] + mix ^= dag[(shifted_A // params["numdags"]) % params["n"]] + return mix ^ rand + + +def get_daggerset(params, block): + if block.number == 0: + return [produce_dag(params, i) for i in range(params["numdags"])] + elif block.number % params["epochtime"]: + return get_daggerset(block.parent) + else: + o = get_daggerset(block.parent) + o[sha3(block.parent.nonce) % params["numdags"]] = \ + produce_dag(params, sha3(block.parent.nonce)) + return o + + +def mine(daggerset, params, header): + nonce = random.randrange(2**50) + orignonce = nonce + origtime = time.time() + while 1: + h = hashimoto(daggerset, params, header, nonce) + if h <= 2**256 / params["diff"]: + noncediff = nonce - orignonce + timediff = time.time() - origtime + print 'Found nonce: %d, tested %d nonces in %f seconds (%f per sec)' % \ + (nonce, noncediff, timediff, noncediff / timediff) + return nonce + nonce += 1 + + +def verify(daggerset, params, header, nonce): + return hashimoto(daggerset, params, header, nonce) \ + <= 2**256 / params["diff"] + + +def light_hashimoto(seedset, params, header, nonce): + rand = sha3(header+encode_int(nonce)) + mix = 0 + for i in range(40): + shifted_A = rand >> i + seed = seedset[shifted_A % params["numdags"]] + # can further optimize with cross-round memoization + mix ^= quick_calc(params, seed, + (shifted_A // params["numdags"]) % params["n"]) + return mix ^ rand + + +def light_verify(seedset, params, header, nonce): + return light_hashimoto(seedset, params, header, nonce) \ + <= 2**256 / params["diff"] + +params = { + "numdags": 40, + "n": 250000, + "diff": 2**14, + "epochtime": 100, + "k": 3 +} diff --git a/mining.go b/mining/mining.go similarity index 100% rename from mining.go rename to mining/mining.go diff --git a/mining.py b/mining/mining.py similarity index 100% rename from mining.py rename to mining/mining.py diff --git a/mining/python_sha3.py b/mining/python_sha3.py new file mode 100644 index 0000000..fa72032 --- /dev/null +++ b/mining/python_sha3.py @@ -0,0 +1,411 @@ +#! /usr/bin/env python +# coding: utf-8 + +# The Keccak sponge function was designed by Guido Bertoni, Joan Daemen, +# Michaël Peeters and Gilles Van Assche. For more information, feedback or +# questions, please refer to their website: http://keccak.noekeon.org/ +# +# Based on the implementation by Renaud Bauvin, +# from http://keccak.noekeon.org/KeccakInPython-3.0.zip +# +# Modified by Moshe Kaplan to be hashlib-compliant +# +# To the extent possible under law, the implementer has waived all copyright +# and related or neighboring rights to the source code in this file. +# http://creativecommons.org/publicdomain/zero/1.0/ + + +import math + + +def sha3_224(data=None): + return Keccak(c=448, r=1152, n=224, data=data) + + +def sha3_256(data=None): + return Keccak(c=512, r=1088, n=256, data=data) + + +def sha3_384(data=None): + return Keccak(c=768, r=832, n=384, data=data) + + +def sha3_512(data=None): + return Keccak(c=1024, r=576, n=512, data=data) + + +class KeccakError(Exception): + """Custom error Class used in the Keccak implementation""" + + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + + +class Keccak: + def __init__(self, r, c, n, data=None): + # Initialize the constants used throughout Keccak + # bitrate + self.r = r + # capacity + self.c = c + # output size + self.n = n + + self.b = r + c + # b = 25*w + self.w = self.b // 25 + # 2**l = w + self.l = int(math.log(self.w, 2)) + + self.n_r = 12 + 2 * self.l + + self.block_size = r + self.digest_size = n + + # Initialize the state of the sponge + # The state is made up of 25 words, each word being w bits. + self.S = [[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]] + + # A string of hexchars, where each char represents 4 bits. + self.buffered_data = "" + + # Store the calculated digest. + # We'll only apply padding and recalculate the hash if it's modified. + self.last_digest = None + + if data: + self.update(data) + + # Constants + + ## Round constants + RC = [0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008] + + ## Rotation offsets + r = [[0, 36, 3, 41, 18], + [1, 44, 10, 45, 2], + [62, 6, 43, 15, 61], + [28, 55, 25, 21, 56], + [27, 20, 39, 8, 14]] + + @staticmethod + def Round(A, RCfixed, w): + """Perform one round of computation as defined in the Keccak-f permutation + + A: current state (5x5 matrix) + RCfixed: value of round constant to use (integer) + """ + + #Initialization of temporary variables + B = [[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]] + C = [0, 0, 0, 0, 0] + D = [0, 0, 0, 0, 0] + + #Theta step + for x in range(5): + C[x] = A[x][0] ^ A[x][1] ^ A[x][2] ^ A[x][3] ^ A[x][4] + + for x in range(5): + D[x] = C[(x - 1) % 5] ^ _rot(C[(x + 1) % 5], 1, w) + + for x in range(5): + for y in range(5): + A[x][y] = A[x][y] ^ D[x] + + #Rho and Pi steps + for x in range(5): + for y in range(5): + B[y][(2 * x + 3 * y) % 5] = _rot(A[x][y], Keccak.r[x][y], w) + + #Chi step + for x in range(5): + for y in range(5): + A[x][y] = B[x][y] ^ ((~B[(x + 1) % 5][y]) & B[(x + 2) % 5][y]) + + #Iota step + A[0][0] = A[0][0] ^ RCfixed + + return A + + @staticmethod + def KeccakF(A, n_r, w): + """Perform Keccak-f function on the state A + + A: 5x5 matrix containing the state, where each entry is a string of hexchars that is 'w' bits long + n_r: number of rounds + w: word size + """ + + for i in xrange(n_r): + A = Keccak.Round(A, Keccak.RC[i] % (1 << w), w) + + return A + + ### Padding rule + # This is a disgusting piece of code. Clean it. + @staticmethod + def pad10star1(M, n): + """Pad M with the pad10*1 padding rule to reach a length multiple of r bits + + M: message pair (length in bits, string of hex characters ('9AFC...') + n: length in bits (must be a multiple of 8) + Example: pad10star1([60, 'BA594E0FB9EBBD30'],8) returns 'BA594E0FB9EBBD93' + """ + + [my_string_length, my_string] = M + + # Check the parameter n + if n % 8 != 0: + raise KeccakError.KeccakError("n must be a multiple of 8") + + # Check the length of the provided string + if len(my_string) % 2 != 0: + #Pad with one '0' to reach correct length (don't know test + #vectors coding) + my_string += '0' + if my_string_length > (len(my_string) // 2 * 8): + raise KeccakError.KeccakError("the string is too short to contain the number of bits announced") + + nr_bytes_filled = my_string_length // 8 + nbr_bits_filled = my_string_length % 8 + l = my_string_length % n + if ((n - 8) <= l <= (n - 2)): + if (nbr_bits_filled == 0): + my_byte = 0 + else: + my_byte = int(my_string[nr_bytes_filled * 2:nr_bytes_filled * 2 + 2], 16) + my_byte = (my_byte >> (8 - nbr_bits_filled)) + my_byte = my_byte + 2 ** (nbr_bits_filled) + 2 ** 7 + my_byte = "%02X" % my_byte + my_string = my_string[0:nr_bytes_filled * 2] + my_byte + else: + if (nbr_bits_filled == 0): + my_byte = 0 + else: + my_byte = int(my_string[nr_bytes_filled * 2:nr_bytes_filled * 2 + 2], 16) + my_byte = (my_byte >> (8 - nbr_bits_filled)) + my_byte = my_byte + 2 ** (nbr_bits_filled) + my_byte = "%02X" % my_byte + my_string = my_string[0:nr_bytes_filled * 2] + my_byte + while((8 * len(my_string) // 2) % n < (n - 8)): + my_string = my_string + '00' + my_string = my_string + '80' + + return my_string + + def update(self, arg): + # Update the hash object with the string arg. Repeated calls are equivalent to a single call with the concatenation of all the arguments: m.update(a); m.update(b) is equivalent to m.update(a+b). arg is a normal bytestring. + + self.last_digest = None + # Convert the data into a workable format, and add it to the buffer + self.buffered_data += arg.encode('hex') + + # Absorb any blocks we can: + if len(self.buffered_data) * 4 >= self.r: + extra_bits = len(self.buffered_data) * 4 % self.r + + # An exact fit! + if extra_bits == 0: + P = self.buffered_data + self.buffered_data = "" + else: + # Slice it up into the first r*a bits, for some constant a>=1, and the remaining total-r*a bits. + P = self.buffered_data[:-extra_bits // 4] + self.buffered_data = self.buffered_data[-extra_bits // 4:] + + #Absorbing phase + for i in xrange((len(P) * 8 // 2) // self.r): + to_convert = P[i * (2 * self.r // 8):(i + 1) * (2 * self.r // 8)] + '00' * (self.c // 8) + P_i = _convertStrToTable(to_convert, self.w, self.b) + + # First apply the XOR to the state + block + for y in xrange(5): + for x in xrange(5): + self.S[x][y] = self.S[x][y] ^ P_i[x][y] + # Then apply the block permutation, Keccak-F + self.S = Keccak.KeccakF(self.S, self.n_r, self.w) + + def digest(self): + """Return the digest of the strings passed to the update() method so far. + + This is a string of digest_size bytes which may contain non-ASCII + characters, including null bytes.""" + + if self.last_digest: + return self.last_digest + + # UGLY WARNING + # Handle bytestring/hexstring conversions + M = _build_message_pair(self.buffered_data.decode('hex')) + + # First finish the padding and force the final update: + self.buffered_data = Keccak.pad10star1(M, self.r) + self.update('') + # UGLY WARNING over + + assert len(self.buffered_data) == 0, "Why is there data left in the buffer? %s with length %d" % (self.buffered_data, len(self.buffered_data) * 4) + + # Squeezing time! + Z = '' + outputLength = self.n + while outputLength > 0: + string = _convertTableToStr(self.S, self.w) + # Read the first 'r' bits of the state + Z = Z + string[:self.r * 2 // 8] + outputLength -= self.r + if outputLength > 0: + S = KeccakF(S, verbose) + + self.last_digest = Z[:2 * self.n // 8].decode('hex') + return self.last_digest + + def hexdigest(self): + """Like digest() except the digest is returned as a string of hex digits + + This may be used to exchange the value safely in email or other + non-binary environments.""" + return self.digest().encode('hex') + + def copy(self): + # First initialize whatever can be done normally + duplicate = Keccak(c=self.c, r=self.r, n=self.n) + # Then copy over the state. + for i in xrange(5): + for j in xrange(5): + duplicate.S[i][j] = self.S[i][j] + # and any other stored data + duplicate.buffered_data = self.buffered_data + duplicate.last_digest = self.last_digest + return duplicate + + +## Generic utility functions + +def _build_message_pair(data): + hex_data = data.encode('hex') + size = len(hex_data) * 4 + return (size, hex_data) + + +def _rot(x, shift_amount, length): + """Rotate x shift_amount bits to the left, considering the \ + string of bits is length bits long""" + + shift_amount = shift_amount % length + return ((x >> (length - shift_amount)) + (x << shift_amount)) % (1 << length) + +### Conversion functions String <-> Table (and vice-versa) + + +def _fromHexStringToLane(string): + """Convert a string of bytes written in hexadecimal to a lane value""" + + #Check that the string has an even number of characters i.e. whole number of bytes + if len(string) % 2 != 0: + raise KeccakError.KeccakError("The provided string does not end with a full byte") + + #Perform the conversion + temp = '' + nrBytes = len(string) // 2 + for i in xrange(nrBytes): + offset = (nrBytes - i - 1) * 2 + temp += string[offset:offset + 2] + return int(temp, 16) + + +def _fromLaneToHexString(lane, w): + """Convert a lane value to a string of bytes written in hexadecimal""" + + laneHexBE = (("%%0%dX" % (w // 4)) % lane) + #Perform the conversion + temp = '' + nrBytes = len(laneHexBE) // 2 + for i in xrange(nrBytes): + offset = (nrBytes - i - 1) * 2 + temp += laneHexBE[offset:offset + 2] + return temp.upper() + + +def _convertStrToTable(string, w, b): + """Convert a string of hex-chars to its 5x5 matrix representation + + string: string of bytes of hex-coded bytes (e.g. '9A2C...')""" + + # Check that the input paramaters are expected + if w % 8 != 0: + raise KeccakError("w is not a multiple of 8") + + # Each character in the string represents 4 bits. + # The string should have exactly 'b' bits. + if len(string) * 4 != b: + raise KeccakError.KeccakError("string can't be divided in 25 blocks of w bits\ + i.e. string must have exactly b bits") + + #Convert + output = [[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]] + + bits_per_char = 2 * w // 8 + for x in xrange(5): + for y in xrange(5): + # Each entry will have b/25=w bits. + offset = (5 * y + x) * bits_per_char + # Store the data into the associated word. + hexstring = string[offset:offset + bits_per_char] + output[x][y] = _fromHexStringToLane(hexstring) + return output + + +def _convertTableToStr(table, w): + """Convert a 5x5 matrix representation to its string representation""" + + #Check input format + if w % 8 != 0: + raise KeccakError.KeccakError("w is not a multiple of 8") + if (len(table) != 5) or any(len(row) != 5 for row in table): + raise KeccakError.KeccakError("table must be 5x5") + + #Convert + output = [''] * 25 + for x in xrange(5): + for y in xrange(5): + output[5 * y + x] = _fromLaneToHexString(table[x][y], w) + output = ''.join(output).upper() + return output