// hashplay framework
// (c) 2022 lvd^mhm
/*
This file is part of hashplay framework.
hashplay framework is free software:
you can redistribute it and/or modify it under the terms of
the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
hashplay framework is distributed in the hope that
it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with hashplay framework.
If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "hash-common.h"
#include "sphash-opt.h"
#define DIGEST_SIZE 8
#define BLK_SIZE 32
static const uint32_t x0 = 0x01180011; // hex-encoded numbers 71680, 70908, 69888
static const uint32_t y0 = 0x4fc11100; //
// instantiate speck_uint32_t_8_29
#define ROL(w,n) (((w)<<(n))|((w)>>32-(n)))
#define ROUND(x,y,k) \
do { \
x = ROL(x,24); \
x += y; \
x ^= k; \
y = ROL(y,3); \
y ^= x; \
} while(0)
struct my_sphash
{
uint64_t pos;
uint8_t buf[BLK_SIZE];
uint32_t state[2];
};
static void sphash_compress( const uint32_t * m, uint32_t * state)
{
uint32_t x,y;
uint32_t k,k1,k2,k3,k4,k5,k6,k7;
x = state[0];
y = state[1];
//speck_encrypt_uint32_t_8_29(tmp,m);
k = m[0];
k1 = m[1];
k2 = m[2];
k3 = m[3];
k4 = m[4];
k5 = m[5];
k6 = m[6];
k7 = m[7];
ROUND(x,y,k); ROUND(k1,k, 0);
ROUND(x,y,k); ROUND(k2,k, 1);
ROUND(x,y,k); ROUND(k3,k, 2);
ROUND(x,y,k); ROUND(k4,k, 3);
ROUND(x,y,k); ROUND(k5,k, 4);
ROUND(x,y,k); ROUND(k6,k, 5);
ROUND(x,y,k); ROUND(k7,k, 6);
ROUND(x,y,k); ROUND(k1,k, 7);
ROUND(x,y,k); ROUND(k2,k, 8);
ROUND(x,y,k); ROUND(k3,k, 9);
ROUND(x,y,k); ROUND(k4,k,10);
ROUND(x,y,k); ROUND(k5,k,11);
ROUND(x,y,k); ROUND(k6,k,12);
ROUND(x,y,k); ROUND(k7,k,13);
ROUND(x,y,k); ROUND(k1,k,14);
ROUND(x,y,k); ROUND(k2,k,15);
ROUND(x,y,k); ROUND(k3,k,16);
ROUND(x,y,k); ROUND(k4,k,17);
ROUND(x,y,k); ROUND(k5,k,18);
ROUND(x,y,k); ROUND(k6,k,19);
ROUND(x,y,k); ROUND(k7,k,20);
ROUND(x,y,k); ROUND(k1,k,21);
ROUND(x,y,k); ROUND(k2,k,22);
ROUND(x,y,k); ROUND(k3,k,23);
ROUND(x,y,k); ROUND(k4,k,24);
ROUND(x,y,k); ROUND(k5,k,25);
ROUND(x,y,k); ROUND(k6,k,26);
ROUND(x,y,k); ROUND(k7,k,27);
ROUND(x,y,k);
state[0] += x;
state[1] += y;
}
static inline size_t my_min(size_t a, size_t b)
{
if( a<=b ) return a; else return b;
}
struct hash_iface * make_sphash_opt(void)
{
static const char name[]="speck-based hash: sphash64, optimized impl.";
struct hash_iface
* hash
= malloc(sizeof(struct hash_iface
));
if( !hash )
{
fprintf(stderr
,"%s: %d, %s: can't allocate memory for hash_iface!\n",__FILE__
,__LINE__
,__func__
);
}
hash->hash_specific_data = NULL;
hash->name = name;
hash->hash_init = &sphash_opt_hash_init;
hash->hash_start = &sphash_opt_hash_start;
hash->hash_addbytes = &sphash_opt_hash_addbytes;
hash->hash_getsize = &sphash_opt_hash_getsize;
hash->hash_result = &sphash_opt_hash_result;
hash->hash_deinit = &sphash_opt_hash_deinit;
return hash;
}
int sphash_opt_hash_init (struct hash_iface * hash)
{
struct my_sphash
* sphash
= (struct my_sphash
*)malloc(sizeof(struct my_sphash
));
if( !sphash )
{
fprintf(stderr
,"%s: %d, %s: can't allocate memory for struct my_sphash!\n",__FILE__
,__LINE__
,__func__
);
}
hash->hash_specific_data = (void *)sphash;
return 1;
}
void sphash_opt_hash_deinit (struct hash_iface * hash)
{
struct my_sphash * sphash = (struct my_sphash *)hash->hash_specific_data;
if( !sphash )
{
fprintf(stderr
,"%s: %d, %s: hash_specific_data was NULL!\n",__FILE__
,__LINE__
,__func__
);
}
hash->hash_specific_data = NULL;
}
size_t sphash_opt_hash_getsize (struct hash_iface * hash)
{
return DIGEST_SIZE;
}
int sphash_opt_hash_start (struct hash_iface * hash)
{
struct my_sphash * sphash = (struct my_sphash *)hash->hash_specific_data;
if( !sphash )
{
fprintf(stderr
,"%s: %d, %s: hash_specific_data was NULL!\n",__FILE__
,__LINE__
,__func__
);
}
sphash->pos = 0;
for(int i=0;i<BLK_SIZE;i++)
sphash->buf[i] = 0;
sphash->state[0] = x0;
sphash->state[1] = y0;
return 1;
}
int sphash_opt_hash_addbytes(struct hash_iface * hash, const uint8_t * message, size_t size)
{
struct my_sphash * sphash = (struct my_sphash *)hash->hash_specific_data;
if( !sphash )
{
fprintf(stderr
,"%s: %d, %s: hash_specific_data was NULL!\n",__FILE__
,__LINE__
,__func__
);
}
const uint8_t * ptr = message;
size_t remaining_size = size;
while( remaining_size > 0 )
{
// do full buffers
if( !(sphash->pos & (BLK_SIZE-1)) )
{
if( remaining_size>=BLK_SIZE )
{
sphash_compress( (uint32_t *)ptr, sphash->state );
sphash->pos += BLK_SIZE;
ptr += BLK_SIZE;
remaining_size -= BLK_SIZE;
continue;
}
}
// otherwise fill with all available data up to the end of buffer, if possible
size_t size_to_add = my_min( (BLK_SIZE-(sphash->pos & (BLK_SIZE-1))), remaining_size );
do
{
sphash->buf[sphash->pos & (BLK_SIZE-1)] = *ptr;
ptr++;
sphash->pos++;
remaining_size--;
} while( (--size_to_add) );
if( !(sphash->pos & (BLK_SIZE-1)) )
sphash_compress( (uint32_t *)sphash->buf, sphash->state );
}
return 1;
}
int sphash_opt_hash_result (struct hash_iface * hash, uint8_t * result)
{
struct my_sphash * sphash = (struct my_sphash *)hash->hash_specific_data;
if( !sphash )
{
fprintf(stderr
,"%s: %d, %s: hash_specific_data was NULL!\n",__FILE__
,__LINE__
,__func__
);
}
/*
* We're gonna using Nandi padding rule, as per following paper:
*
* ===
* Nandi M. (2009) Characterizing Padding Rules of MD Hash Functions Preserving Collision Security.
* In: Boyd C., González Nieto J. (eds) Information Security and Privacy. ACISP 2009.
* Lecture Notes in Computer Science, vol 5594. Springer, Berlin, Heidelberg.
* https://doi.org/10.1007/978-3-642-02620-1_12
*
* also https://eprint.iacr.org/2009/325.pdf
* or use sci-hub.
* ===
*
* Specifically, the padding is as per section 3.2, remark 2, s=16
*/
uint8_t padding[BLK_SIZE+8]; // suitable for up to 4 chunks of 16 bits, total 4*15 = 60 bits
// for length IN BLOCKS. Block is BLK_SIZE (min 32 bytes), so
// 2^64 / 32 = 2^59.
for(int i=0;i<sizeof(padding);i++)
padding[i]=0;
uint64_t blk_size = (sphash->pos+BLK_SIZE-1)/BLK_SIZE; // number of blocks incl. partially filled last one
int num_chunks = 1;
uint64_t t = blk_size;
while( t>>=15 ) num_chunks++;
// fill length part of padding
t = blk_size;
for(int i=num_chunks-1;i>=0;i--)
{
uint16_t chunk = (t & 0x7FFF) | (i ? 0x8000 : 0);
padding[BLK_SIZE+i*2 ] = chunk>>8;
padding[BLK_SIZE+i*2+1] = chunk & 0xFF;
t>>=15;
}
int num_pad_bytes = (BLK_SIZE-2*num_chunks) - (sphash->pos & (BLK_SIZE-1));
if( num_pad_bytes<=0 ) num_pad_bytes += BLK_SIZE;
padding[BLK_SIZE-num_pad_bytes] = 0x80;
sphash_opt_hash_addbytes(hash, &padding[BLK_SIZE-num_pad_bytes], 2*num_chunks+num_pad_bytes);
assert( !(sphash
->pos
& (BLK_SIZE
-1)) );
for(int i=0;i<DIGEST_SIZE/4;i++)
{
*(((uint32_t *)result)+i) = sphash->state[i];
}
return 1;
}