From ce265722674790553a6909e048143c7a51dfb2c2 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 30 Jan 2014 08:34:26 -0500 Subject: [PATCH] add skein-sha256 algo support --- conf/config_sample.py | 2 +- lib/skein.py | 206 +++++++++++++++++++++++++++++++++++++++ lib/skeinhash.py | 20 ++++ lib/template_registry.py | 4 + lib/threefish.py | 147 ++++++++++++++++++++++++++++ lib/util_numpy.py | 100 +++++++++++++++++++ 6 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 lib/skein.py create mode 100644 lib/skeinhash.py create mode 100644 lib/threefish.py create mode 100644 lib/util_numpy.py diff --git a/conf/config_sample.py b/conf/config_sample.py index 58e7d8c..2343803 100644 --- a/conf/config_sample.py +++ b/conf/config_sample.py @@ -19,7 +19,7 @@ COINDAEMON_TRUSTED_PASSWORD = 'somepassword' # Coin Algorithm is the option used to determine the algortithm used by stratum # This currently works with POW and POS coins # The available options are: -# scrypt, sha256d, scrypt-jane and quark +# scrypt, sha256d, scrypt-jane, skeinhash, and quark # If the option does not meet either of these criteria stratum defaults to scrypt # For Coins which support TX Messages please enter yes in the TX selection COINDAEMON_ALGO = 'scrypt' diff --git a/lib/skein.py b/lib/skein.py new file mode 100644 index 0000000..f822a60 --- /dev/null +++ b/lib/skein.py @@ -0,0 +1,206 @@ +# /usr/bin/env python +# coding=utf-8 + +# Copyright 2010 Jonathan Bowman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""Pure Python implementation of the Skein 512-bit hashing algorithm""" + +import array +import binascii +import os +import struct + +from threefish import (add64, bigint, bytes2words, Threefish512, words, + words2bytes, words_format, xrange, + zero_bytes, zero_words) + +# An empty bytestring that behaves itself whether in Python 2 or 3 +empty_bytes = array.array('B').tostring() + +class Skein512(object): + """Skein 512-bit hashing algorithm + + The message to be hashed may be set as `msg` when initialized, or + passed in later using the ``update`` method. + + Use `key` (a bytestring with arbitrary length) for MAC + functionality. + + `block_type` will typically be "msg", but may also be one of: + "key", "nonce", "cfg_final", or "out_final". These will affect the + tweak value passed to the underlying Threefish block cipher. Again, + if you don't know which one to choose, "msg" is probably what you + want. + + Example: + + >>> Skein512("Hello, world!").hexdigest() + '8449f597f1764274f8bf4a03ead22e0404ea2dc63c8737629e6e282303aebfd5dd96f07e21ae2e7a8b2bdfadd445bd1d71dfdd9745c95b0eb05dc01f289ad765' + + """ + block_size = 64 + block_bits = 512 + block_type = {'key': 0, + 'nonce': 0x5400000000000000, + 'msg': 0x7000000000000000, + 'cfg_final': 0xc400000000000000, + 'out_final': 0xff00000000000000} + + def __init__(self, msg='', digest_bits=512, key=None, + block_type='msg'): + self.tf = Threefish512() + if key: + self.digest_bits = 512 + self._start_new_type('key') + self.update(key) + self.tf.key = bytes2words(self.final(False)) + self.digest_bits = digest_bits + self.digest_size = (digest_bits + 7) >> 3 + self._start_new_type('cfg_final') + b = words2bytes((0x133414853,digest_bits,0,0,0,0,0,0)) + self._process_block(b,32) + self._start_new_type(block_type) + if msg: + self.update(msg) + + def _start_new_type(self, block_type): + """Setup new tweak values and internal buffer. + + Primarily for internal use. + + """ + self.buf = empty_bytes + self.tf.tweak = words([0, self.block_type[block_type]]) + + def _process_block(self, block, byte_count_add): + """Encrypt internal state using Threefish. + + Primarily for internal use. + + """ + block_len = len(block) + for i in xrange(0,block_len,64): + w = bytes2words(block[i:i+64]) + self.tf.tweak[0] = add64(self.tf.tweak[0], byte_count_add) + self.tf.prepare_tweak() + self.tf.prepare_key() + self.tf.key = self.tf.encrypt_block(w) + self.tf._feed_forward(self.tf.key, w) + # set second tweak value to ~SKEIN_T1_FLAG_FIRST: + self.tf.tweak[1] &= bigint(0xbfffffffffffffff) + + def update(self, msg): + """Update internal state with new data to be hashed. + + `msg` is a bytestring, and should be a bytes object in Python 3 + and up, or simply a string in Python 2.5 and 2.6. + + """ + self.buf += msg + buflen = len(self.buf) + if buflen > 64: + end = -(buflen % 64) or (buflen-64) + data = self.buf[0:end] + self.buf = self.buf[end:] + try: + self._process_block(data, 64) + except: + print(len(data)) + print(binascii.b2a_hex(data)) + + def final(self, output=True): + """Return hashed data as bytestring. + + `output` is primarily for internal use. It should only be False + if you have a clear reason for doing so. + + This function can be called as either ``final`` or ``digest``. + + """ + self.tf.tweak[1] |= bigint(0x8000000000000000) # SKEIN_T1_FLAG_FINAL + buflen = len(self.buf) + self.buf += zero_bytes[:64-buflen] + + self._process_block(self.buf, buflen) + + if not output: + hash_val = words2bytes(self.tf.key) + else: + hash_val = empty_bytes + self.buf = zero_bytes[:] + key = self.tf.key[:] # temporary copy + i=0 + while i*64 < self.digest_size: + self.buf = words_format[1].pack(i) + self.buf[8:] + self.tf.tweak = [0, self.block_type['out_final']] + self._process_block(self.buf, 8) + n = self.digest_size - i*64 + if n >= 64: + n = 64 + hash_val += words2bytes(self.tf.key)[0:n] + self.tf.key = key + i+=1 + return hash_val + + digest = final + + def hexdigest(self): + """Return a hexadecimal representation of the hashed data""" + return binascii.b2a_hex(self.digest()) + +class Skein512Random(Skein512): + """A Skein-based pseudo-random bytestring generator. + + If `seed` is unspecified, ``os.urandom`` will be used to provide the + seed. + + In case you are using this as an iterator, rather than generating + new data at each iteration, a pool of length `queue_size` is + generated periodically. + + """ + def __init__(self, seed=None, queue_size=512): + Skein512.__init__(self, block_type='nonce') + self.queue = [] + self.queue_size = queue_size + self.tf.key = zero_words[:] + if not seed: + seed = os.urandom(100) + self.reseed(seed) + + def reseed(self, seed): + """(Re)seed the generator.""" + self.digest_size = 64 + self.update(words2bytes(self.tf.key) + seed) + self.tf.key = bytes2words(self.final()) + + def getbytes(self, request_bytes): + """Return random bytestring of length `request_bytes`.""" + self.digest_size = 64 + request_bytes + self.update(words2bytes(self.tf.key)) + output = self.final() + self.tf.key = bytes2words(output[0:64]) + return output[64:] + + def __iter__(self): + return self + + def next(self): + if not self.queue: + self.queue = array.array('B', self.getbytes(self.queue_size)) + return self.queue.pop() + +if __name__ == '__main__': + print Skein512('123').hexdigest() \ No newline at end of file diff --git a/lib/skeinhash.py b/lib/skeinhash.py new file mode 100644 index 0000000..1a47249 --- /dev/null +++ b/lib/skeinhash.py @@ -0,0 +1,20 @@ +import hashlib +import struct +import skein + +def skeinhash(msg): + return hashlib.sha256(skein.Skein512(msg[:80]).digest()).digest() + +def skeinhashmid(msg): + s = skein.Skein512(msg[:64] + '\x00') # hack to force Skein512.update() + return struct.pack('<8Q', *s.tf.key.tolist()) + +if __name__ == '__main__': + mesg = "dissociative1234dissociative4567dissociative1234dissociative4567dissociative1234" + h = skeinhashmid(mesg) + print h.encode('hex') + print 'ad0d423b18b47f57724e519c42c9d5623308feac3df37aca964f2aa869f170bdf23e97f644e81511df49c59c5962887d17e277e7e8513345137638334c8e59a4' == h.encode('hex') + + h = skeinhash(mesg) + print h.encode('hex') + print '764da2e768811e91c6c0c649b052b7109a9bc786bce136a59c8d5a0547cddc54' == h.encode('hex') diff --git a/lib/template_registry.py b/lib/template_registry.py index a2a5763..ae13a00 100644 --- a/lib/template_registry.py +++ b/lib/template_registry.py @@ -9,6 +9,8 @@ elif settings.COINDAEMON_ALGO == 'scrypt-jane': import yac_scrypt elif settings.COINDAEMON_ALGO == 'quark': import quark_hash +elif settings.COINDAEMON_ALGO == 'skeinhash': + import skeinhash else: pass from twisted.internet import defer from lib.exceptions import SubmitException @@ -242,6 +244,8 @@ class TemplateRegistry(object): hash_bin = yac_scrypt.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]), int(ntime, 16)) elif settings.COINDAEMON_ALGO == 'quark': hash_bin = quark_hash.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) + elif settings.COINDAEMON_ALGO == 'skeinhash': + hash_bin = skeinhash.skeinhash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) else: hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) diff --git a/lib/threefish.py b/lib/threefish.py new file mode 100644 index 0000000..27ff914 --- /dev/null +++ b/lib/threefish.py @@ -0,0 +1,147 @@ +# /usr/bin/env python +# coding=utf-8 + +# Copyright 2010 Jonathan Bowman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""Pure Python implementation of the Threefish block cipher + +The core of the Skein 512-bit hashing algorithm + +""" +from util_numpy import add64, bigint, bytelist, bytes2words, imap, izip, sub64, \ + SKEIN_KS_PARITY, words, words2bytes, words_format, xrange, zero_bytes, zero_words, RotL_64, RotR_64, xor + +from itertools import cycle + +ROT = bytelist((46, 36, 19, 37, + 33, 27, 14, 42, + 17, 49, 36, 39, + 44, 9, 54, 56, + 39, 30, 34, 24, + 13, 50, 10, 17, + 25, 29, 39, 43, + 8, 35, 56, 22)) + +PERM = bytelist(((0,1),(2,3),(4,5),(6,7), + (2,1),(4,7),(6,5),(0,3), + (4,1),(6,3),(0,5),(2,7), + (6,1),(0,7),(2,5),(4,3))) + +class Threefish512(object): + """The Threefish 512-bit block cipher. + + The key and tweak may be set when initialized (as + bytestrings) or after initialization using the ``tweak`` or + ``key`` properties. When choosing the latter, be sure to call + the ``prepare_key`` and ``prepare_tweak`` methods. + + """ + def __init__(self, key=None, tweak=None): + """Set key and tweak. + + The key and the tweak will be lists of 8 64-bit words + converted from `key` and `tweak` bytestrings, or all + zeroes if not specified. + + """ + if key: + self.key = bytes2words(key) + self.prepare_key() + else: + self.key = words(zero_words[:] + [0]) + if tweak: + self.tweak = bytes2words(tweak, 2) + self.prepare_tweak() + else: + self.tweak = zero_words[:3] + + def prepare_key(self): + """Compute key.""" + final = reduce(xor, self.key[:8]) ^ SKEIN_KS_PARITY + try: + self.key[8] = final + except IndexError: + #self.key.append(final) + self.key = words(list(self.key) + [final]) + + def prepare_tweak(self): + """Compute tweak.""" + final = self.tweak[0] ^ self.tweak[1] + try: + self.tweak[2] = final + except IndexError: + #self.tweak.append(final) + self.tweak = words(list(self.tweak) + [final]) + + def encrypt_block(self, plaintext): + """Return 8-word ciphertext, encrypted from plaintext. + + `plaintext` must be a list of 8 64-bit words. + + """ + key = self.key + tweak = self.tweak + state = words(list(imap(add64, plaintext, key[:8]))) + state[5] = add64(state[5], tweak[0]) + state[6] = add64(state[6], tweak[1]) + + for r,s in izip(xrange(1,19),cycle((0,16))): + for i in xrange(16): + m,n = PERM[i] + state[m] = add64(state[m], state[n]) + state[n] = RotL_64(state[n], ROT[i+s]) + state[n] = state[n] ^ state[m] + for y in xrange(8): + state[y] = add64(state[y], key[(r+y) % 9]) + state[5] = add64(state[5], tweak[r % 3]) + state[6] = add64(state[6], tweak[(r+1) % 3]) + state[7] = add64(state[7], r) + + return state + + def _feed_forward(self, state, plaintext): + """Compute additional step required when hashing. + + Primarily for internal use. + + """ + state[:] = list(imap(xor, state, plaintext)) + + def decrypt_block(self, ciphertext): + """Return 8-word plaintext, decrypted from plaintext. + + `ciphertext` must be a list of 8 64-bit words. + + """ + key = self.key + tweak = self.tweak + state = ciphertext[:] + + for r,s in izip(xrange(18,0,-1),cycle((16,0))): + for y in xrange(8): + state[y] = sub64(state[y], key[(r+y) % 9]) + state[5] = sub64(state[5], tweak[r % 3]) + state[6] = sub64(state[6], tweak[(r+1) % 3]) + state[7] = sub64(state[7], r) + + for i in xrange(15,-1,-1): + m,n = PERM[i] + state[n] = RotR_64(state[m] ^ state[n], ROT[i+s]) + state[m] = sub64(state[m], state[n]) + + result = list(imap(sub64, state, key)) + result[5] = sub64(result[5], tweak[0]) + result[6] = sub64(result[6], tweak[1]) + return result diff --git a/lib/util_numpy.py b/lib/util_numpy.py new file mode 100644 index 0000000..89dc60e --- /dev/null +++ b/lib/util_numpy.py @@ -0,0 +1,100 @@ +# /usr/bin/env python +# coding=utf-8 + +# Copyright 2010 Jonathan Bowman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""Various helper functions for handling arrays, etc. using numpy""" + +import struct +from operator import xor, add as add64, sub as sub64 +import numpy as np + +words = np.uint64 +bytelist = np.uint8 +bigint = np.uint64 + +# working out some differences between Python 2 and 3 +try: + from itertools import imap, izip +except ImportError: + imap = map + izip = zip +try: + xrange = xrange +except: + xrange = range + +SKEIN_KS_PARITY = np.uint64(0x1BD11BDAA9FC1A22) + +# zeroed out byte string and list for convenience and performance +zero_bytes = struct.pack('64B', *[0] * 64) +zero_words = np.zeros(8, dtype=np.uint64) + +# Build structs for conversion appropriate to this system, favoring +# native formats if possible for slight performance benefit +words_format_tpl = "%dQ" +if struct.pack('2B', 0, 1) == struct.pack('=H', 1): # big endian? + words_format_tpl = "<" + words_format_tpl # force little endian +else: + try: # is 64-bit integer native? + struct.unpack(words_format_tpl % 2, zero_bytes[:16]) + except(struct.error): # Use standard instead of native + words_format_tpl = "=" + words_format_tpl + +# build structs for one-, two- and eight-word sequences +words_format = dict( + (i,struct.Struct(words_format_tpl % i)) for i in (1,2,8)) + +def bytes2words(data, length=8): + """Return a list of `length` 64-bit words from `data`. + + `data` must consist of `length` * 8 bytes. + `length` must be 1, 2, or 8. + + """ + return(np.fromstring(data, dtype=np.uint64)) + +def words2bytes(data, length=8): + """Return a `length` * 8 byte string from `data`. + + + `data` must be a list of `length` 64-bit words + `length` must be 1, 2, or 8. + + """ + try: + return(data.tostring()) + except AttributeError: + return(np.uint64(data).tostring()) + +def RotL_64(x, N): + """Return `x` rotated left by `N`.""" + #return (x << np.uint64(N & 63)) | (x >> np.uint64((64-N) & 63)) + return(np.left_shift(x, (N & 63), dtype=np.uint64) | + np.right_shift(x, ((64-N) & 63), dtype=np.uint64)) + +def RotR_64(x, N): + """Return `x` rotated right by `N`.""" + return(np.right_shift(x, (N & 63), dtype=np.uint64) | + np.left_shift(x, ((64-N) & 63), dtype=np.uint64)) + +def add64(a,b): + """Return a 64-bit integer sum of `a` and `b`.""" + return(np.add(a, b, dtype=np.uint64)) + +def sub64(a,b): + """Return a 64-bit integer difference of `a` and `b`.""" + return(np.subtract(a, b, dtype=np.uint64)) +