add skein-sha256 algo support

This commit is contained in:
root 2014-01-30 08:34:26 -05:00 committed by Ahmed Bodiwala
parent 41f11d9ac4
commit ce26572267
6 changed files with 478 additions and 1 deletions

View File

@ -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'

206
lib/skein.py Normal file
View File

@ -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()

20
lib/skeinhash.py Normal file
View File

@ -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')

View File

@ -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) ]))

147
lib/threefish.py Normal file
View File

@ -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

100
lib/util_numpy.py Normal file
View File

@ -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))