/*
    $Source: /local/data/cvs/yellowbank/lib/c/clib/y_clib.c,v $
    $Revision: 1.5 $
    $State: Exp $
    $Date: 2007/10/26 21:15:42 $
    $Author: yrp001 $
    $Locker:  $

    Copyright 2007 (Y) Yellowbank
    https://www.yellowbank.com/
    Ronald Peterson

    All rights reserved.

    This file is part of y_clib.

    y_clib is free software; you can redistribute it and/or modify it
    under the terms of the GNU Affero GPL version 3.0.  These license
    terms can be found in the included file agpl-3.0.txt.
*/

#include "y_clib.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <sys/stat.h>

// according to strerror man page
#define _XOPEN_SOURCE 600

#define Y_MAX_READLEN 2048

void
y_octstr_init( y_octstr os ) {
   Y_OS_LEN(os) = 0;
   Y_OS_STR(os) = NULL;
}

static
void
y_octstr_zero( y_octstr os ) {
   y_octstr_set( os, '\0', Y_OS_LEN( os ) );
}

void
y_octstr_clear( y_octstr os ) {
   if( os ) {
      if( Y_OS_STR(os) ) {
         // zero memory
         y_octstr_zero( os );
         free( Y_OS_STR(os) );
         Y_OS_STR(os) = NULL;
      }
      Y_OS_LEN(os) = 0;
   }
}

void
y_octstr_reinit( y_octstr os ) {
   y_octstr_clear( os );
}

void
y_full_key_init( y_full_key *key ) {
   mpz_init( Y_FULL_KEY_MOD(key) );
   mpz_init( Y_FULL_KEY_PUB(key) );
   mpz_init( Y_FULL_KEY_PRIV(key) );
}

void
y_full_key_clear( y_full_key *key ) {
   mpz_clear( Y_FULL_KEY_MOD(key) );
   mpz_clear( Y_FULL_KEY_PUB(key) );
   mpz_clear( Y_FULL_KEY_PRIV(key) );
}

void
y_part_key_init( y_part_key * key ) {
   mpz_init( Y_PART_KEY_MOD(key) );
   mpz_init( Y_PART_KEY_EXP(key) );
}

void
y_part_key_clear( y_part_key * key ) {
   mpz_clear( Y_PART_KEY_MOD(key) );
   mpz_clear( Y_PART_KEY_EXP(key) );
}

static
char
hexchar2bin( char h ) {
   char r;
   r = ('0' <= h) && (h <= '9') ? h - '0' : 0xFF;
   r = ('A' <= h) && (h <= 'Z') ? h - 'A' + 10 : r;
   r = ('a' <= h) && (h <= 'z') ? h - 'a' + 10 : r;
   if( r < 0 || r > 15 ) {
      perror( "hexchar2bin: invalid hex character" );
      exit( EXIT_FAILURE );
   }
   return( r );
}

void
y_octstr_set_from_cp_hex( y_octstr os, char *hex ) {
   int i, o, hexlen, binlen;
   char l, r, h;
   y_oct_bin bin = NULL;
   y_oct_bin newbin = NULL;
   if( ! hex ) {
      return;
   }
   hexlen = strlen( hex );
   if( hexlen == 0 ) {
      y_octstr_clear( os );
      y_octstr_init( os );
      return;
   }
   binlen = (hexlen / 2) + (hexlen % 2);

   bin = Y_OS_STR(os);

   newbin = realloc( bin, binlen );
   if( newbin == NULL ) {
      perror( "y_octstr_set_from_cp_hex: realloc failure" );
      exit( EXIT_FAILURE );
   }
   bin = newbin;
   Y_OS_STR( os ) = newbin;
   newbin = NULL;

   Y_OS_LEN(os) = binlen;
   o = 0; // nibble offset
   if( hexlen % 2 ) {
      // if we have an odd number of hex characters, get the first
      // nibble
      o = 1;
      l = 0;
      h = hex[0];
      r = hexchar2bin( h );
      bin[0] = l | r;
   }
   for( i = 0; i < binlen - o; i++ ) {
      h = hex[(i*2)+o];
      l = (hexchar2bin( h ) << 4) & 0xF0;

      h = hex[(i*2)+1+o];
      r = hexchar2bin( h ) & 0x0F;

      bin[i+o] = l | r;
   }
   return;
}

void
y_octstr_set( y_octstr os, int c, uint n ) {
   uint len;
   y_oct_bin str = NULL;
   y_oct_bin tmp = NULL;
   len = Y_OS_LEN( os );
   str = Y_OS_STR( os );
   if( n <= len ) {
      memset( str, c, n );
      return;
   }
   // zero any existing string before reallocating
   if( len > 0 ) {
      memset( str, '\0', len );
   }
   tmp = realloc( str, n );
   if( tmp == NULL ) {
      perror( "y_octstr_set: error allocating new octstr" );
      exit( EXIT_FAILURE );
   }
   str = tmp;
   memset( str, c, n );
   Y_OS_STR( os ) = str;
   Y_OS_LEN( os ) = n;
}

void
y_octstr_set_count( y_octstr os, int val, uint start, uint count ) {
   uint len;
   y_oct_bin str = NULL;
   y_oct_bin tmp = NULL;
   len = Y_OS_LEN( os );
   str = Y_OS_STR( os );
   if( start + count <= len ) {
      memset( str + start, val, count );
      return;
   }
   // zero any existing string before reallocating
   if( len > 0 ) {
      memset( str, '\0', len );
   }
   tmp = realloc( str, start + count );
   if( tmp == NULL ) {
      perror( "y_octstr_set: error allocating new octstr" );
      exit( EXIT_FAILURE );
   }
   str = tmp;
   memset( str + start, val, count );
   Y_OS_STR( os ) = str;
   Y_OS_LEN( os ) = start + count;
}

void
y_octstr_set_mask( y_octstr os, uint bit_count ) {
   uint i;
   uint octets;
   uint bits;
   y_oct_bin_t oct;
   y_oct_bin os_str;
   y_oct_bin os_tmp;
   
   if( bit_count == 0 ) {
      octets = 1;
   } else {
      octets = bit_count / 8;
      if( bit_count % 8 ) {
         octets += 1;
      }
   }
   os_str = Y_OS_STR( os );
   os_tmp = realloc( os_str, octets );
   if( os_tmp == NULL ) {
      perror( "y_octstr_set_mask: error allocating string" );
      exit( EXIT_FAILURE );
   }
   os_str = os_tmp;

   // fill with one's
   for( i = 0; i < octets; i++ ) {
      memset( os_str + i, 0xFF, 1 );
   }

   oct = '\0';
   bits = 8 - ((octets * 8) - bit_count);
   for( i = 0; i < bits; i++ ) {
      oct = (oct >> 1) | 0x80;
   }

   memset( os_str + octets - 1, oct, 1 );

   Y_OS_STR( os ) = os_str;
   Y_OS_LEN( os ) = octets;
}

void
y_octstr_mask_out( y_octstr os, y_octstr mask ) {
   uint i;
   uint minLen;
   y_oct_bin os_str;
   y_oct_bin mask_str;

   minLen = Y_OS_LEN(mask) < Y_OS_LEN(os) ? Y_OS_LEN(mask) : Y_OS_LEN(os);

   os_str = Y_OS_STR( os );
   mask_str = Y_OS_STR( mask );
   
   for( i = 0; i < minLen; i++ ) {
      *(os_str + i) &= ~*(mask_str + i);
   }
}

void
y_octstr_replace_substr( y_octstr os, y_octstr sub, uint pos ) {
   uint os_len;
   uint sub_len;
   y_oct_bin os_str = NULL;
   y_oct_bin sub_str = NULL;
   y_oct_bin tmp = NULL;
   os_len = Y_OS_LEN( os );
   os_str = Y_OS_STR( os );
   sub_len = Y_OS_LEN( sub );
   sub_str = Y_OS_STR( sub );
   if( pos > os_len ) {
      perror( "y_octstr_replace_substr: start past end of string" );
      exit( EXIT_FAILURE );
   }
   if( pos + sub_len <= os_len ) {
      memcpy( os_str + pos, sub_str, sub_len );
      return;
   }
   tmp = realloc( os_str, pos + sub_len );
   if( tmp == NULL ) {
      perror( "y_octstr_set: error allocating new octstr" );
      exit( EXIT_FAILURE );
   }
   os_str = tmp;
   memcpy( os_str + pos, sub_str, sub_len );
   Y_OS_STR( os ) = os_str;
   Y_OS_LEN( os ) = pos + sub_len;
}

char *
y_cp_hex_realloc_from_octstr( char *hex, y_octstr os ) {
   int i, binlen;
   char *newhex = NULL;
   char c, h, l, r;
   y_oct_bin bin;
   bin = Y_OS_STR(os);
   if( ! bin ) {
      return NULL;
   }
   binlen = Y_OS_LEN(os);

   newhex = realloc( hex, binlen * 2 + 1 );
   if( newhex == NULL ) {
      free( hex );
      hex = NULL;
      perror( "Error: y_cp_alloc_from_octstr: realloc failure" );
      return NULL;
      // exit( EXIT_FAILURE );
   }
   hex = newhex;
   newhex = NULL;

   for( i = 0; i < binlen; i++ ) {
      c = *(bin + i);
      l = (c >> 4) & 0x0F;
      r = (c & 0x0F);
      h = (int)l < 10 ? '0' : 'a' - 10;
      hex[i*2] = l + h;
      h = (int)r < 10 ? '0' : 'a' - 10;
      hex[i*2+1] = r + h;
   }
   memset( hex + (binlen * 2), '\0', 1 );
   return hex;
}

bool
y_are_octstr_equal( y_octstr left, y_octstr right ) {
   uint llen, rlen;
   uint count = 0;
   y_oct_bin ls, rs;
   llen = Y_OS_LEN( left );
   rlen = Y_OS_LEN( right );
   ls = Y_OS_STR( left );
   rs = Y_OS_STR( right );
   if( llen != rlen ) {
      return false;
   }
   while( *(ls++) == *(rs++) && ++count < llen ) {}
   if( count == llen ) {
      return true;
   }
   return false;
}

void
y_print_os_as_hex( y_octstr os ) {
   char *test = NULL;
   char *hex = NULL;
   test = y_cp_hex_realloc_from_octstr( hex, os );
//   if( test == NULL ) {
//      perror( "error allocating hex" );
//      exit( EXIT_FAILURE );
//   }
   hex = test;
   printf( "%s", hex );
   free( test );
}

uint
y_octets_required_by_bigint( mpz_t bigint ) {
   uint size = 0;
   size = mpz_sizeinbase( bigint, 16 );
   size = (size / 2) + (size % 2);
   return size;
}

void
y_octstr_set_from_gmp_int( y_octstr os, mpz_t bigint ) {
   char *hex = NULL;
   uint size, hexlen;
   int ret;

   size = y_octets_required_by_bigint( bigint );
   hexlen = size * 2;
   hex = malloc( hexlen );
   if( hex == NULL ) {
      perror( "y_octstr_set_from_gmp_int: error allocating hex" );
      exit( EXIT_FAILURE );
   }
   ret = gmp_snprintf( hex, hexlen + 1, "%0*Zx", hexlen, bigint );
   y_octstr_set_from_cp_hex( os, hex );
   free( hex );
}

void
y_octstr_set_from_gmp_int_xlen( y_octstr os, mpz_t bigint, uint xLen ) {
   mpz_t maxsize;
   char *hex = NULL;
   int hexsize, ret;
   
   mpz_init( maxsize );
   mpz_ui_pow_ui( maxsize, 256, xLen );

   if( mpz_cmp( bigint, maxsize ) >= 0 ) {
      mpz_clear( maxsize );
      perror( "y_octstr_set_from_gmp_int_xlen: integer too large" );
      exit( EXIT_FAILURE );
   }
   mpz_clear( maxsize );

   hexsize = xLen * 2;
   hex = malloc( hexsize + 1 );
   if( hex == NULL ) {
      perror( "y_octstr_set_from_gmp_int: error allocating hex" );
      exit( EXIT_FAILURE );
   }
   // Create zero padded hex string representation of bigint
   // with xLen characters (+ 1 terminating null);
   ret = gmp_snprintf( hex, hexsize + 1, "%0*Zx", hexsize, bigint );

   y_octstr_set_from_cp_hex( os, hex );
}

void
y_gmp_int_set_from_octstr( mpz_t bigint, y_octstr os ) {
   int valid;
   char *hex = NULL;
   char *newhex = NULL;
   newhex = y_cp_hex_realloc_from_octstr( hex, os );
   // this should really return NaN somehow
   if( newhex == NULL ) {
      mpz_set_ui( bigint, 0 );
      return;
   }
   hex = newhex;
   valid = mpz_set_str( bigint, hex, 16 );
//    if( ! valid ) {
//       perror( "y_gmp_int_set_from_octstr: invalid hex" );
//       exit( EXIT_FAILURE );
//    }
}

void
y_octstr_copy( y_octstr dst, y_octstr src ) {
   uint slen;
   y_oct_bin tmp;
   y_oct_bin dbin;
   y_oct_bin sbin;

   sbin = Y_OS_STR( src );
   if( sbin == NULL ) {
      y_octstr_clear( dst );
      return;
   }

   y_octstr_zero( dst );

   slen = Y_OS_LEN( src );
   dbin = Y_OS_STR( dst );
   tmp = realloc( dbin, slen );
   if( tmp == NULL ) {
      perror( "y_octstr_copy: error allocating destination octet string" );
      exit( EXIT_FAILURE );
   }
   dbin = tmp;
   memcpy( dbin, sbin, slen );
   Y_OS_STR( dst ) = dbin;
   Y_OS_LEN( dst ) = slen;
}

void
y_octstr_concat( y_octstr dest, y_octstr left, y_octstr right ) {
   y_octstr tmp_dest;
   uint len, llen, rlen;
   y_oct_bin str = NULL;
   y_oct_bin tmp = NULL;

   // dest might be also be left or right, so we concatenate
   // to temporary octet string, then copy to dest.
   // An optimization would be to check whether dest is
   // actually equal to left or right, and skip the copy
   // step if not.
   y_octstr_init( tmp_dest );

   llen = Y_OS_LEN( left );
   rlen = Y_OS_LEN( right );
   len = llen + rlen;
   str = Y_OS_STR( tmp_dest );
   tmp = realloc( str, len );
   if( tmp == NULL ) {
      perror( "y_octstr_concat: error allocating octet string" );
      exit( EXIT_FAILURE );
   }
   str = tmp;

   memcpy( str, Y_OS_STR( left ), llen );
   memcpy( str + llen, Y_OS_STR( right ), rlen );

   Y_OS_STR( tmp_dest ) = str;
   Y_OS_LEN( tmp_dest ) = len;
   y_octstr_copy( dest, tmp_dest );

   y_octstr_clear( tmp_dest );
}

static
int
_y_powm( mpz_t *ret, y_part_key *key, mpz_t base ) {
   mpz_t tmp;
   mpz_init( tmp );
   int lrc, urc; // lower and upper range check
   // message between 0 and n-1?
   lrc = mpz_cmp_ui( Y_PART_KEY_MOD(key), (uint)0 );
   mpz_sub_ui( tmp, Y_PART_KEY_MOD(key), 1 );
   urc = mpz_cmp( base, tmp );
   mpz_clear( tmp );
   if( lrc <= 0 || urc >= 0 ) {
      // if base is less than or equal to zero, or greater
      // than or equal to n-1
      return( Y_OUT_OF_RANGE );
   }
   mpz_powm( *ret, base, Y_PART_KEY_EXP(key), Y_PART_KEY_MOD(key) );
   return( 0 );
}

void
RSAEP( mpz_t *cipher, y_part_key *pubkey, mpz_t message ) {
   int ret;
   ret = _y_powm( cipher, pubkey, message );
   if( ret == Y_OUT_OF_RANGE ) {
      perror( "RSAEP: message representative out of range" );
      exit( EXIT_FAILURE );
   }
}

void
RSADP( mpz_t *message, y_part_key *privkey, mpz_t cipher ) {
   int ret;
   ret = _y_powm( message, privkey, cipher );
   if( ret == Y_OUT_OF_RANGE ) {
      perror( "RSADP: ciphertext representative out of range" );
      exit( EXIT_FAILURE );
   }
}

void
RSASP1( mpz_t *signature, y_part_key *privkey, mpz_t message ) {
   int ret;
   ret = _y_powm( signature, privkey, message );
   if( ret == Y_OUT_OF_RANGE ) {
      perror( "RSASP1: message representative out of range" );
      exit( EXIT_FAILURE );
   }
}

void
RSAVP1( mpz_t *message, y_part_key *pubkey, mpz_t signature ) {
   int ret;
   ret = _y_powm( message, pubkey, signature );
   if( ret == Y_OUT_OF_RANGE ) {
      perror( "RSAVP1: signature representative out of range" );
      exit( EXIT_FAILURE );
   }
}

/*

uint
y_get_uint_random( uint min, uint max ) {

   // Return random number between min and max inclusive.
   // Linux specific code for now.

   uint rand;
   int octets;
   uint ret;
   y_octstr rand_os;

//   octets = ((max - min) / 8) + ...;

   ret = y_get_os_dev_random( rand_os, octets, "urandom" );
   return ret;
}

*/

int
y_get_os_dev_random( y_octstr os, uint octet_count ) {

   // Fill os with byte_count bytes of random data.
   // Linux specific code for now.

   int ret = 0;
   int fd;
   int total = 0;

   y_oct_bin bin;

   bin = realloc( Y_OS_STR(os), octet_count );
   if( bin == NULL ) {
      perror( "y_get_os_dev_random: error allocating octet string" );
      return( Y_BAD_RANDOM );
   }
   Y_OS_STR( os ) = bin;
   Y_OS_LEN( os ) = octet_count;
   
   // note: RANDDEV is currently defined in Makefile.  When this library
   // defines it's own CSPRNG, then this will be more elegant.
   fd = open( RANDDEV, O_RDONLY );

//    if( strcmp( random_source, "random" ) == 0 ) {
//       fd = open( "/dev/random", O_RDONLY );
//    } else if( strcmp( random_source, "urandom" ) == 0 )	{
//       fd = open( "/dev/urandom", O_RDONLY );
//    } else {
//       return( Y_NO_RANDOM );
//   }

   if( fd == -1 ) {
      perror( "get_dev_random: random source must be 'random' or 'urandom'" );
      return( Y_NO_RANDOM );
   }

   while( (ret = read( fd, bin, octet_count - total ) > 0) &&
          (ret != (octet_count - total)) ) {
      total += ret;
      bin += ret;
   }

   close( fd );

   if( ret == -1 ) {
      return( Y_BAD_RANDOM );
   }
   return( Y_GOOD_RANDOM );
}

char *
y_realloc_random_hex( char *hex, uint octets ) {
   y_octstr os;
   int ret;
   char *rand;

   y_octstr_init( os );

   ret = y_get_os_dev_random( os, octets );

   if( (ret = Y_GOOD_RANDOM) != 0 ) {
      rand = y_cp_hex_realloc_from_octstr( hex, os );
   }

   y_octstr_clear( os );

   if( rand ) {
      return rand;
   }
   return NULL;
}

static
void
_y_set_gmp_random_state( gmp_randstate_t state,
                  uint octets )
{
   char *rand_hex = NULL;
   char *ret;
   mpz_t seed;

   mpz_init( seed );

   ret = y_realloc_random_hex( rand_hex, octets );
   if( ret == NULL ) {
      perror( "y_set_gmp_random_state: error generating random hex" );
      exit( EXIT_FAILURE );
   }
   rand_hex = ret;

   if( mpz_set_str( seed, rand_hex, 16 ) != 0 ) {
      perror( "y_set_gmp_random_state: not valid hex" );
      exit( EXIT_FAILURE );
   }

   gmp_randseed( state, seed );


   mpz_clear( seed );
}

void
y_gen_mpz_random( mpz_t rand,
                  const char* random_source,
                  uint seed_len,
                  uint max_bits )
{
   gmp_randstate_t state;
   gmp_randinit_default( state );
   _y_set_gmp_random_state( state, seed_len );
   // random number in the range 0 to 2^max_bits-1, inclusive
   mpz_urandomb( rand, state, max_bits );
   gmp_randclear( state );
}

void
y_gen_mpz_random_of_bit_len( mpz_t rand,
                             uint seed_len,
                             uint bits )
{
   mpz_t min;
   mpz_t max;
   mpz_t range;
   mpz_init( min );
   mpz_init( max );
   mpz_init( range );
// e.g. 1000 for bits = 4.
   mpz_ui_pow_ui( min, 2, bits - 1); 
// e.g. 10000 for bits = 4.  We want to subtract 1 to
// get 1111 for bits = 4, but mpz_urandomm below takes
// care of that for us.
   mpz_ui_pow_ui( max, 2, bits );
   mpz_sub( range, max, min );

   gmp_randstate_t state;
   gmp_randinit_default( state );
   _y_set_gmp_random_state( state, seed_len );

// random number in the range 0 to 2^range-1, inclusive
   mpz_urandomm( rand, state, range );
// e.g. between 1000 and 1111 inclusive for bits = 4.
   mpz_add( rand, min, rand );

   gmp_randclear( state );
   mpz_clear( min );
   mpz_clear( max );
   mpz_clear( range );
}

void
y_generate_random_prime( mpz_t prime,
                         uint seed_len,
                         uint bits )
{
   mpz_t rand;
   mpz_t max;
   mpz_init( rand );
   mpz_init( max );

   mpz_ui_pow_ui( prime, 2, bits + 1 );
   mpz_ui_pow_ui ( max, 2, bits);
   mpz_sub_ui( max, max, 1 );

   // Eliminate the extremely small chance that our prime falls
   // out of the range 0 to 2^max_bits - 1, inclusive.
   //
   // while prime > max (exceedingly unlikely for large
   // primes, but just to be sure)
   while( mpz_cmp( prime, max ) > 0 ) {
	  y_gen_mpz_random_of_bit_len( rand, seed_len, bits );
	  mpz_nextprime( prime, rand );
   }

   mpz_clear( rand );
   mpz_clear( max );
}

void 
y_generate_rsa_keys( y_full_key * key,
                     uint seed_len,
                     uint bits )
{
   mpz_t p;
   mpz_t q;
   mpz_t n;
   mpz_t min;
   mpz_t phi; // Eucilidean exponential period (totient)
   mpz_t pm, qm;
//   uint part;
   uint plen;
   uint qlen;

   mpz_init( p );
   mpz_init( q );
   mpz_init_set_ui( n, 0 );
   mpz_init( min );
   mpz_init( phi );
   mpz_init( pm );
   mpz_init( qm );

   plen = bits / 2;
   qlen = bits - plen;
   mpz_ui_pow_ui( min, 2, bits - 1 );

   // want to do more random partitioning, but this will do for now

   while( mpz_cmp( n, min ) < 0 ) {
      // while n < min
      y_generate_random_prime( p, seed_len, plen );
      y_generate_random_prime( q, seed_len, qlen );
      mpz_mul( n, p, q );
   }
   mpz_set( Y_FULL_KEY_MOD(key), n );

   y_generate_random_prime( Y_FULL_KEY_PUB(key),
                            seed_len,
                            bits );

   mpz_sub_ui( pm, p, 1 );
   mpz_sub_ui( qm, q, 1 );
   mpz_mul( phi, pm, qm );
   mpz_invert( Y_FULL_KEY_PRIV(key), Y_FULL_KEY_PUB(key), phi );

   mpz_clear( p );
   mpz_clear( q );
   mpz_clear( n );
   mpz_clear( min );
   mpz_clear( phi );
   mpz_clear( pm );
   mpz_clear( qm );
}

void
y_set_pub_key( y_part_key *pub, y_full_key key ) {
      mpz_set( Y_PART_KEY_MOD(pub), Y_FULL_KEY_MOD(&key) );
      mpz_set( Y_PART_KEY_EXP(pub), Y_FULL_KEY_PUB(&key) );
}

void
y_set_priv_key( y_part_key *priv, y_full_key key ) {
      mpz_set( Y_PART_KEY_MOD(priv), Y_FULL_KEY_MOD(&key) );
      mpz_set( Y_PART_KEY_EXP(priv), Y_FULL_KEY_PRIV(&key) );
}

// On success, return length in octets of selected hash type, else return 0.
static
int
_y_get_hash_id( hashid* hash_id, const char* hash_type )
{
   if      (strcmp( hash_type, "TIGER" ) == 0 ) { *hash_id = MHASH_TIGER; }
   else if (strcmp( hash_type, "TIGER160" ) == 0) { *hash_id = MHASH_TIGER160; }
   else if (strcmp( hash_type, "TIGER128" ) == 0) { *hash_id = MHASH_TIGER128; }
   else if (strcmp( hash_type, "WHIRLPOOL" ) == 0) { *hash_id = MHASH_WHIRLPOOL; }
   else if (strcmp( hash_type, "RIPEMD320" ) == 0) { *hash_id = MHASH_RIPEMD320; }
   else if (strcmp( hash_type, "RIPEMD256" ) == 0) { *hash_id = MHASH_RIPEMD256; }
   else if (strcmp( hash_type, "RIPEMD128" ) == 0) { *hash_id = MHASH_RIPEMD128; }
   else if (strcmp( hash_type, "SHA512" ) == 0) { *hash_id = MHASH_SHA512; }
   else if (strcmp( hash_type, "SHA384" ) == 0) { *hash_id = MHASH_SHA384; }
   else if (strcmp( hash_type, "SHA256" ) == 0) { *hash_id = MHASH_SHA256; }
   else if (strcmp( hash_type, "SHA224" ) == 0) { *hash_id = MHASH_SHA224; }
   else if (strcmp( hash_type, "SHA1" ) == 0) { *hash_id = MHASH_SHA1; }
   else if (strcmp( hash_type, "HAVAL256" ) == 0) { *hash_id = MHASH_HAVAL256; }
   else if (strcmp( hash_type, "HAVAL224" ) == 0) { *hash_id = MHASH_HAVAL224; }
   else if (strcmp( hash_type, "HAVAL192" ) == 0) { *hash_id = MHASH_HAVAL192; }
   else if (strcmp( hash_type, "HAVAL160" ) == 0) { *hash_id = MHASH_HAVAL160; }
   else if (strcmp( hash_type, "HAVAL128" ) == 0) { *hash_id = MHASH_HAVAL128; }
   else if (strcmp( hash_type, "GOST" ) == 0) { *hash_id = MHASH_GOST; }
   else if (strcmp( hash_type, "SNEFRU256" ) == 0) { *hash_id = MHASH_SNEFRU256; }
   else if (strcmp( hash_type, "SNEFRU128" ) == 0) { *hash_id = MHASH_SNEFRU128; }
   else if (strcmp( hash_type, "MD5" ) == 0) { *hash_id = MHASH_MD5; }
   else if (strcmp( hash_type, "MD4" ) == 0) { *hash_id = MHASH_MD4; }
   else if (strcmp( hash_type, "CRC32" ) == 0) { *hash_id = MHASH_CRC32; }
   else if (strcmp( hash_type, "ADLER32" ) == 0) { *hash_id = MHASH_ADLER32; }
   else {
      return( 0 );
   }
   return( mhash_get_block_size(*hash_id) );
}

// Calculate hash of data according to algorithm specified by
// hash_type; 'hash' argument should be initialized to NULL, or point
// to previously allocated memory (which should be
// mhash_get_block_size(hash_id) + 1 bytes).  Will put null terminated
// hex string representation of hash value into octet string, and
// hash_len will contain the length of the hash indicated by
// hash_type.
//

//              const y_octstr os,

int
y_calc_mhash( y_octstr hash,
              const char *hash_type,
              char *input_data,
              uint data_len,
              uint *hash_len )
{
   hashid hash_id = 0;
   MHASH td;
   uint offset = 0;
   uint chunk_size = 2048;
//   uint os_len;
//   y_oct_bin input_data = NULL;
   y_oct_bin hash_data = NULL;
   unsigned char *buffer = NULL;

//   os_len = Y_OS_LEN( os );
//   input_data = Y_OS_STR( os );
   *hash_len = _y_get_hash_id( &hash_id, hash_type );

   hash_data = realloc( Y_OS_STR( hash ), *hash_len );
   if( hash_data == NULL ) {
      perror( "error allocating octet string for hash" );
      return( Y_HASH_ALLOC_FAILURE );
   }
   Y_OS_STR( hash ) = hash_data;
   Y_OS_LEN( hash ) = *hash_len;

   td = mhash_init( hash_id );
   if (td == MHASH_FAILED) {
      // hash initialization failed
      return( Y_HASH_INIT_FAILURE );
   }
	
   // Chew data up in bites, so that very large data doesn't
   // blow things up.

   buffer = malloc( chunk_size );
   if( data_len < chunk_size ) { chunk_size = data_len; }

   while( (data_len - offset) > 0 ) {
      memcpy( buffer, input_data + offset, chunk_size );
      mhash( td, buffer, chunk_size );
      offset += chunk_size;
      if( (data_len - offset) < chunk_size ) {
         chunk_size = data_len - offset;
      }
   }

   mhash_deinit( td, hash_data );

   free( buffer );

   return( Y_HASH_OK );
}

static
int
_y_calc_mhash( y_octstr hash,
               const char *hash_type,
               y_octstr os )
{
   uint hash_len;
   return y_calc_mhash( hash,
                        hash_type,
                        (char *)Y_OS_STR(os),
                        Y_OS_LEN(os),
                        &hash_len );
}

// args: mask (return), mgfSeed, maskLen, hash type
void
y_mgf1( y_octstr mask,
        y_octstr mgfSeed,
        uint maskLen,
        char *hash_type )
{
   hashid hash_id = 0;
   uint hLen = 0;
   uint ub;
   uint hexlen;
   uint counter;
   mpz_t mLen;
   mpz_t testLen;
   mpz_t tmp;
   mpz_t Tz;
   y_octstr T;
   y_octstr C;
   y_octstr ostmp;
   y_octstr hash;
   char *hex;

   hLen = _y_get_hash_id( &hash_id, hash_type );
   
   mpz_init( mLen );
   mpz_init( testLen );

   mpz_set_ui( mLen, maskLen );
   mpz_ui_pow_ui( testLen, 2, 32 );
   mpz_mul_ui( testLen, testLen, hLen );

   // if maskLen > 2^32 * hLen
   if( (mpz_cmp_ui( testLen, maskLen ) < 0) ) {
      perror( "y_mgf1: error: mask too long" );
      mpz_clear( mLen );
      mpz_clear( testLen );
      exit( EXIT_FAILURE );
   }

   mpz_init( Tz );
   mpz_init( tmp );
   y_octstr_init( T );
   y_octstr_init( C );
   y_octstr_init( ostmp );
   y_octstr_init( hash );

   // Specification is unclear about how to round here.  Should this
   // go to ceiling, floor, ???
   // (It doesn't seem like it can hurt to do one extra round, because
   // we're throwing away any extra bits anyways...)
   // ub = maskLen / hLen - 1;
   ub = maskLen / hLen;
   
//   if( ub < 1 ) {
//      perror( "y_mgf1: mask too short" );
//      exit( EXIT_FAILURE );
//   }

   for( counter = 0; counter <= ub; counter++ ) {
      // T = T || Hash (mgfSeed || C )
      // where C = counter converted to octstr of length 4
      mpz_set_ui( tmp, counter );
      y_octstr_set_from_gmp_int_xlen( C, tmp, 4 );
      y_octstr_concat( ostmp, mgfSeed, C );
      _y_calc_mhash( hash, hash_type, ostmp );
      y_octstr_concat( T, T, hash );
   }

   // output leading maskLen octets of T as mask
   hexlen = maskLen * 2;
   hex = malloc( hexlen + 1 );
   if( hex == NULL ) {
      perror( "y_mgf1: error allocating hex string" );
      exit( EXIT_FAILURE );
   }
   y_gmp_int_set_from_octstr( Tz, T );
   gmp_snprintf( hex, hexlen + 1, "%*Zx", hexlen, Tz );
   y_octstr_set_from_cp_hex( mask, hex );

   mpz_clear( mLen );
   mpz_clear( testLen );
   mpz_clear( Tz );
   mpz_clear( tmp );
   y_octstr_clear( T );
   y_octstr_clear( C );
   y_octstr_clear( ostmp );
   y_octstr_clear( hash );
   free( hex );
}

void
y_exclusive_or( y_octstr ret, y_octstr l, y_octstr r ) {
   uint i;
   uint llen;
   uint rlen;
   y_oct_bin lbin;
   y_oct_bin rbin;
   y_oct_bin bin;
   y_oct_bin tmp = NULL;

   llen = Y_OS_LEN( l );
   rlen = Y_OS_LEN( r );
   if( llen != rlen ) {
      perror( "y_exclusive_or: inconsistent input lengths" );
      exit( EXIT_FAILURE );
   }

   lbin = Y_OS_STR( l );
   rbin = Y_OS_STR( r );
   bin = Y_OS_STR( ret );

   tmp = realloc( bin, llen );
   if( tmp == NULL ) {
      perror( "y_exclusive_or: error allocating return value" );
      exit( EXIT_FAILURE );
   }
   bin = tmp;

   for( i = 0; i < llen; i++ ) {
      *(bin + i) = *(lbin + i) ^ *(rbin + i);
   }
   Y_OS_STR( ret ) = bin;
   Y_OS_LEN( ret ) = llen;
}

void
y_eme_oaep( y_octstr EM,
            y_octstr message,
            char *hash_type,
            char *label,
            uint k )
{
   int mLen;
   int hLen;
   hashid hash_id;
//   char *tmp = NULL;
//   char *rhex = NULL;
   y_octstr label_os;
   y_octstr lHash;
   y_octstr PS;
   y_octstr single;
   y_octstr DB;
   y_octstr seed;
   y_octstr dbMask;
   y_octstr maskedDB;
   y_octstr seedMask;
   y_octstr maskedSeed;
   
   // b. If mLen > k - 2hLen - 2 output "message too long" and stop.
   mLen = Y_OS_LEN( message );
   hLen = _y_get_hash_id( &hash_id, hash_type );   

   if( mLen > (k-(2*hLen)-2) ) {
      perror( "message too long" );
      exit( EXIT_FAILURE );
//      return( Y_MESSAGE_TOO_LONG );
   }

   y_octstr_init( label_os );
   y_octstr_init( lHash );
   y_octstr_init( PS );
   y_octstr_init( single );
   y_octstr_init( DB );
   y_octstr_init( seed );
   y_octstr_init( dbMask );
   y_octstr_init( maskedDB );
   y_octstr_init( seedMask );
   y_octstr_init( maskedSeed );

   y_octstr_set_from_cp_hex( label_os, label );
   if( _y_calc_mhash( lHash, hash_type, label_os ) != Y_HASH_OK ) {
      perror( "y_rsaes-oaep-encrypt: error generating hash" );
      exit( EXIT_FAILURE );
   }
   y_octstr_set( PS, '\0', k - mLen - (2 * hLen) - 2 );
   y_octstr_set_from_cp_hex( single, "01" );

   // DB = lHash || PS || 0x01 || message
   y_octstr_concat( DB, lHash, PS );
   y_octstr_concat( DB, DB, single );
   y_octstr_concat( DB, DB, message );

//   tmp = y_realloc_random_hex( rhex, hLen, "urandom" );
//   if( tmp == NULL ) {
//      perror( "y_eme_oaep: error allocating random hex" );
//      exit( EXIT_FAILURE );
//   }
//   rhex = tmp;
//   y_octstr_set_from_cp_hex( seed, rhex );

   y_get_os_dev_random( seed, hLen );

   // e. dbMask = MGF( seed, k - hLen - 1 );
   y_mgf1( dbMask, seed, k - hLen - 1, hash_type );

   // f. maskedDB = DB (+) dbMask
   y_exclusive_or( maskedDB, DB, dbMask );

   // g. seedMask = MGF( maskedDB, hLen )
   y_mgf1( seedMask, maskedDB, hLen, hash_type );

   // h. maskedSeed = seed (+) seedMask
   y_exclusive_or( maskedSeed, seed, seedMask );

   // i. EM = 0x00 || maskedSeed || maskedDB
   y_octstr_set_from_cp_hex( single, "00" );
   y_octstr_concat( EM, single, maskedSeed );
   y_octstr_concat( EM, EM, maskedDB );

   y_octstr_clear( label_os );
   y_octstr_clear( lHash );
   y_octstr_clear( PS );
   y_octstr_clear( single );
   y_octstr_clear( DB );
   y_octstr_clear( seed );
   y_octstr_clear( dbMask );
   y_octstr_clear( maskedDB );
   y_octstr_clear( seedMask );
   y_octstr_clear( maskedSeed );
//   free( rhex );
}

void
y_rsaes_oaep_encrypt( y_octstr C,
                      y_octstr message,
                      y_part_key *public_key,
                      char *hash_type,
                      char *label )
{
   uint k;
   y_octstr EM;
   mpz_t m;
   mpz_t c;

   y_octstr_init( EM );
   mpz_init( m );
   mpz_init( c );

   // 1. Length checking.
   // 
   // a. If label too long for selected hash function, output "label
   // too long" and stop.

   k = y_octets_required_by_bigint( Y_PART_KEY_MOD( public_key ) );

   y_eme_oaep( EM, message, hash_type, label, k );

   // 3. a.
   y_gmp_int_set_from_octstr( m, EM );

   // b.
   RSAEP( &c, public_key, m );

   y_octstr_set_from_gmp_int_xlen( C, c, k );

   y_octstr_clear( EM );
   mpz_clear( m );
   mpz_clear( c );
}

// Put 'count' octets beginning at 'start' from octet 
// string 'in' into 'out'.  If 'count' goes beyond length of
// input string, return tail of string beginning at 'start'.
// Octet string is zero indexed.
static
void
_y_octstr_substr( y_octstr out, y_octstr in, uint start, uint count ) {
   uint len;
   y_oct_bin bin = NULL;
   y_oct_bin tmp = NULL;
   len = Y_OS_LEN( in );
   if( (start + count) > len ) {
      count = len - start;
   }
   bin = Y_OS_STR( out );
   tmp = realloc( bin, count );
   if( tmp == NULL ) {
      perror( "_y_octstr_substr: error allocating output string" );
      exit( EXIT_FAILURE );
   }
   bin = tmp;

   memcpy( bin, Y_OS_STR( in ) + start, count );
   Y_OS_STR( out ) = bin;
   Y_OS_LEN( out ) = count;
}

// Return octet count from 'start' to first occurance of character 'c'
// in 'os' (not including character at 'start').  If not found, return
// 0.
static
uint
_y_octstr_simple_search( y_octstr os, uint start, y_oct_bin_t c ) {
   uint count = 1;
   uint len;
   y_oct_bin bin;

   len = Y_OS_LEN( os );

   if( (os == NULL) || (len == 0) || ((start + 1) >= len) ) {
      return 0;
   }
   bin = Y_OS_STR( os ) + start;
   while( *(bin+count) != c ) {
      if( start + count >= len ) {
         return 0;
      }
      count++;
   }
   return count;
}

void
y_rsaes_oaep_decrypt( y_octstr M,
                      y_octstr C,
                      y_part_key *K,
                      char *hash_type,
                      char *L )
{
   uint k;
   uint pos;
   hashid hash_id;
   uint hLen;
   mpz_t c;
   mpz_t m;
   y_octstr EM;
   y_octstr L_os;
   y_octstr lHash;
   y_octstr Y;
   y_octstr maskedSeed;
   y_octstr maskedDB;
   y_octstr seedMask;
   y_octstr seed;
   y_octstr dbMask;
   y_octstr DB;
   y_octstr lHash2;
   y_octstr PS;
   y_octstr single;

   // If length of L is longer than input limitation for hash function,
   // (2^61 - 1 octets for SHA-1) output "decryption error" and stop.

   // If length of C is not k octets, output "decryption error" and stop.
   // (where k is length in octets of key modulus)

   k = y_octets_required_by_bigint( Y_PART_KEY_MOD( K ) );

// The implemented RSAEP function may create ciphertext shorter than modulus length
//   if( (Y_OS_LEN( C ) != k) ) {
//      perror( "y_rsaes_oaep_decrypt: decryption error: ciphertext length != modulus length" );
//      exit( EXIT_FAILURE );
//   }

   hLen = _y_get_hash_id( &hash_id, hash_type );
   if( (k < (2 * hLen) + 2) ) {
      perror( "y_rsaes_oaep_decrypt: decryption error: modulus too small" );
      exit( EXIT_FAILURE );
   }

   mpz_init( c );
   mpz_init( m );
   y_octstr_init( EM );
   y_octstr_init( L_os );
   y_octstr_init( lHash );
   y_octstr_init( Y );
   y_octstr_init( maskedSeed );
   y_octstr_init( maskedDB );
   y_octstr_init( seedMask );
   y_octstr_init( seed );
   y_octstr_init( dbMask );
   y_octstr_init( DB );
   y_octstr_init( lHash2 );
   y_octstr_init( PS );
   y_octstr_init( single );

   y_gmp_int_set_from_octstr( c, C );
   RSADP( &m, K, c );

   y_octstr_set_from_gmp_int_xlen( EM, m, k );
   y_octstr_set_from_cp_hex( L_os, L );
   _y_calc_mhash( lHash, hash_type, L_os );

   _y_octstr_substr( Y, EM, 0, 1 );
   if( *(Y_OS_STR( Y )) != 0x00 ) {
      perror( "y_rsaes_oaep_decrypt: error: Y not 0x00" );
      exit( EXIT_FAILURE );
   }
   _y_octstr_substr( maskedSeed, EM, 1, hLen );
   _y_octstr_substr( maskedDB, EM, 1 + hLen, k - hLen - 1 );

   y_mgf1( seedMask, maskedDB, hLen, hash_type );
   y_exclusive_or( seed, maskedSeed, seedMask );

   y_mgf1( dbMask, seed, k - hLen - 1, hash_type );
   y_exclusive_or( DB, maskedDB, dbMask );

   // DB = lHash2 || PS || 0x01 || M
   _y_octstr_substr( lHash2, DB, 0, hLen );
   if( ! y_are_octstr_equal( lHash, lHash2 ) ) {
      perror( "y_rsaes_oaep_decrypt: error: hash digests not equal" );
      exit( EXIT_FAILURE );
   }
   pos = _y_octstr_simple_search( DB, hLen, 0x01 );
   if( pos == 0 ) {
      perror( "y_rsaes_oaep_decrypt: error: delimiter 0x01 not found" );
      exit( EXIT_FAILURE );
   }
   _y_octstr_substr( PS, DB, hLen, hLen + pos );
   _y_octstr_substr( single, DB, hLen + pos, 1 );
   _y_octstr_substr( M, DB, hLen + pos + 1, Y_OS_LEN( DB ) );

   mpz_clear( c );
   mpz_clear( m );
   y_octstr_clear( EM );
   y_octstr_clear( L_os );
   y_octstr_clear( lHash );
   y_octstr_clear( Y );
   y_octstr_clear( maskedSeed );
   y_octstr_clear( maskedDB );
   y_octstr_clear( seedMask );
   y_octstr_clear( seed );
   y_octstr_clear( dbMask );
   y_octstr_clear( DB );
   y_octstr_clear( lHash2 );
   y_octstr_clear( PS );
   y_octstr_clear( single );
}

void
y_emsa_pss_encode( y_octstr EM,
                   y_octstr M,
                   uint emBits,
                   uint emLen,
                   char *hash_type,
                   uint sLen )
{
   y_octstr mHash;
   y_octstr salt;
   y_octstr M2;
   y_octstr PS;
   y_octstr H;
   y_octstr DB;
   y_octstr dbMask;
   y_octstr maskedDB;
   y_octstr single;
   y_octstr bitMask;
   hashid hash_id;
   uint hLen;
   uint zeroBits;

   // 1. If length of M is longer than input limitation for hash
   //    function, output "message too long" and stop.

   y_octstr_init( mHash );
   y_octstr_init( salt );
   y_octstr_init( M2 );
   y_octstr_init( PS );
   y_octstr_init( H );
   y_octstr_init( DB );
   y_octstr_init( dbMask );
   y_octstr_init( maskedDB );
   y_octstr_init( single );
   y_octstr_init( bitMask );

   hLen = _y_get_hash_id( &hash_id, hash_type );

   if( emLen < (hLen + sLen + 2) ) {
      perror( "y_emsa_pss_encode: encoding error" );
      exit( EXIT_FAILURE );
   }

   if( _y_calc_mhash( mHash, hash_type, M ) != Y_HASH_OK ) {
      perror( "y_emsa_pss_encode: error generating hash for mHash" );
      exit( EXIT_FAILURE );
   }

   if( sLen > 0 ) {
      y_get_os_dev_random( salt, sLen );
   }

   y_octstr_concat( M2, mHash, salt );
   // (just using PS as a placeholder here..)
   y_octstr_set_from_cp_hex( PS, "0000000000000000" );
   y_octstr_concat( M2, PS, M2 );

   if( _y_calc_mhash( H, hash_type, M2 ) != Y_HASH_OK ) {
      perror( "y_emsa_pss_encode: error generating hash for H" );
      exit( EXIT_FAILURE );
   }

   y_octstr_set( PS, '\0', emLen - sLen - hLen - 2 );
   y_octstr_set_from_cp_hex( single, "01" );

   y_octstr_concat( DB, single, salt );
   y_octstr_concat( DB, PS, DB );

   // 9.
   y_mgf1( dbMask, H, emLen - hLen - 1, hash_type );

   // 10.
   y_exclusive_or( maskedDB, DB, dbMask );

   // 11. set leftmost 8 * emLen - emBits (should be less than 8) of
   // maskedDB to zero.
   zeroBits = 8 * emLen - emBits;
   y_octstr_set_mask( bitMask, zeroBits );
   y_octstr_mask_out( maskedDB, bitMask );

   // 12. EM = maskedDB || H || 0xBC
   y_octstr_set_from_cp_hex( single, "BC" );
   y_octstr_concat( EM, maskedDB, H );
   y_octstr_concat( EM, EM, single );

   y_octstr_clear( mHash );
   y_octstr_clear( salt );
   y_octstr_clear( M2 );
   y_octstr_clear( PS );
   y_octstr_clear( H );
   y_octstr_clear( DB );
   y_octstr_clear( dbMask );
   y_octstr_clear( maskedDB );
   y_octstr_clear( single );
   y_octstr_clear( bitMask );
}

void
y_rsassa_pss_sign( y_octstr S,
                   y_octstr M,
                   y_part_key *K,
                   char *hash_type,
                   uint saltLen )
{
   uint k;
   uint modBits;
   uint emLen;
   mpz_t m;
   mpz_t s;
   y_octstr EM;

   mpz_init( m );
   mpz_init( s );
   y_octstr_init( EM );

   k = y_octets_required_by_bigint( Y_PART_KEY_MOD( K ) );
   modBits = k * 8;

   // (modBits - 1) / 8,
   if( ((modBits - 1) / 8) ) {
      emLen = k;
   } else {
      emLen = k - 1;
   }

   y_emsa_pss_encode( EM,
                      M,
                      modBits - 1,
                      emLen,
                      hash_type,
                      saltLen );

   y_gmp_int_set_from_octstr( m, EM );

   RSASP1( &s, K, m );
   y_octstr_set_from_gmp_int_xlen( S, s, k );

   mpz_clear( m );
   mpz_clear( s );
   y_octstr_clear( EM );
}

int
y_emsa_pss_verify( y_octstr M,
                   y_octstr EM,
                   uint emBits,
                   char *hash_type,
                   uint sLen )
{
   uint emLen;
   uint hLen;
   uint zeroBits;
   y_octstr mHash;
   y_octstr maskedDB;
   y_octstr H;
   y_octstr bitMask;
   y_octstr test;
   y_octstr DB;
   y_octstr dbMask;
   y_octstr salt;
   y_octstr M2;
   y_octstr H2;
   y_oct_bin em_str;
   y_oct_bin_t em_octet;
   y_oct_bin_t db_octet;
   hashid hash_id;

   y_octstr_init( mHash );
   y_octstr_init( maskedDB );
   y_octstr_init( H );
   y_octstr_init( bitMask );
   y_octstr_init( test );
   y_octstr_init( DB );
   y_octstr_init( dbMask );
   y_octstr_init( salt );
   y_octstr_init( M2 );
   y_octstr_init( H2 );

   emLen = Y_OS_LEN( EM );

   // 1. If length of M is greater than input limitation for
   //    hash function, return INCONSISTENT.

   // 2. Let mHash = Hash( M ), an octet string of length hLen

   hLen = _y_get_hash_id( &hash_id, hash_type );
   _y_calc_mhash( mHash, hash_type, M );

   // 3.
   if( emLen < hLen + sLen + 2 ) {
      printf( "\ndebug: exit 3." );
      goto exit_inconsistent;
   }

   // 4. If rightmost octet of EM isn't 0xBC, output "inconsistent"
   em_str = Y_OS_STR( EM );
   em_octet = *(em_str + emLen - 1);
   if( em_octet != 0xBC ) {
      printf( "\ndebug: exit 4." );
      goto exit_inconsistent;
   }

   // 5.
   _y_octstr_substr( maskedDB, EM, 0, emLen - hLen - 1 );
   _y_octstr_substr( H, EM, emLen - hLen - 1, hLen );

   zeroBits = 8 * emLen - emBits;
   y_octstr_set_mask( bitMask, zeroBits );

   // 6.
   y_octstr_copy( test, maskedDB );
   y_octstr_mask_out( test, bitMask );
   if( ! y_are_octstr_equal( test, maskedDB ) ) {
      printf( "\ndebug: exit 6." );
      goto exit_inconsistent;
   }

   // 7. Let dbMask = MGF( H, emLen - hLen - 1 )
   y_mgf1( dbMask, H, emLen - hLen - 1, hash_type );

   // 8. Let DB = maskedDB (+) dbMask
   y_exclusive_or( DB, maskedDB, dbMask );
   
   // 9.
   y_octstr_mask_out( DB, bitMask );

   // 10.
   zeroBits = emLen - hLen - sLen - 2;
   y_octstr_set_mask( bitMask, zeroBits );
   y_octstr_copy( test, DB );
   y_octstr_mask_out( test, bitMask );
   if( ! y_are_octstr_equal( test, DB ) ) {
      printf( "\ndebug: exit 10.a" );
      goto exit_inconsistent;
   }

   db_octet = *(Y_OS_STR( DB ) + (emLen - hLen - sLen - 2));

   if( db_octet != 0x01 ) {
      printf( "\ndebug: exit 10.b" );
      goto exit_inconsistent;
   }

   // 11. let 'salt' be the last sLen octets of DB
   _y_octstr_substr( salt,
                     DB,
                     Y_OS_LEN(DB) - sLen,
                     sLen );
   
   // 12.
   y_octstr_set_from_cp_hex( M2, "0000000000000000" );
   y_octstr_concat( M2, M2, mHash );
   y_octstr_concat( M2, M2, salt );

   // 13.
   _y_calc_mhash( H2, hash_type, M2 );
   
   // 14.
   if( y_are_octstr_equal( H, H2 ) ) {
      y_octstr_clear( mHash );
      y_octstr_clear( maskedDB );
      y_octstr_clear( H );
      y_octstr_clear( bitMask );
      y_octstr_clear( test );
      y_octstr_clear( dbMask );
      y_octstr_clear( salt );
      y_octstr_clear( M2 );
      y_octstr_clear( H2 );
      return CONSISTENT;
   }

  exit_inconsistent:

   y_octstr_clear( mHash );
   y_octstr_clear( maskedDB );
   y_octstr_clear( H );
   y_octstr_clear( bitMask );
   y_octstr_clear( test );
   y_octstr_clear( DB );
   y_octstr_clear( dbMask );
   y_octstr_clear( salt );
   y_octstr_clear( M2 );
   y_octstr_clear( H2 );

   return INCONSISTENT;
}

int
y_rsassa_pss_verify( y_octstr M,
                     y_octstr S,
                     y_part_key *K,
                     char *hash_type,
                     uint saltLen )
{
   int result;
   uint k;
   uint modBits;
   uint emLen;
   mpz_t s;
   mpz_t m;
   y_octstr EM;

   mpz_init( s );
   mpz_init( m );
   y_octstr_init( EM );

   k = y_octets_required_by_bigint( Y_PART_KEY_MOD( K ) );
   modBits = k * 8;
   if( ((modBits - 1) / 8) ) {
      emLen = k;
   } else {
      emLen = k - 1;
   }

   // 1.
   if( Y_OS_LEN( S ) != k ) {
      return INVALID_SIGNATURE;
   }

   // 2.
   // a.
   y_gmp_int_set_from_octstr( s, S );

   // b.
   // todo: if returns "...out of range" return INVALID_SIGNATURE
   RSAVP1( &m, K, s );

   y_octstr_set_from_gmp_int_xlen( EM, m, emLen );

   // resume here
   result = y_emsa_pss_verify( M, EM, modBits-1, hash_type, saltLen );

   mpz_clear( s );
   mpz_clear( m );
   y_octstr_clear( EM );

   if( result == CONSISTENT ) {
      return VALID_SIGNATURE;
   }
   return INVALID_SIGNATURE;
}

//______________________________________________________________________
// append 'depth' directories to 'base' using characters from the
// beginning of 'hash'.  E.g. - if 'base' is '/some/place', 'hash' is
// 'abc123', and 'depth' is 2, then the result would be
// '/some/place/a/b'.
//______________________________________________________________________
char
*y_mkdir_and_realloc_hashdir_name( char *hashdir,
                                   const char *basedir,
                                   const char *hash,
                                   uint depth,
                                   char *errstr )
{
   char *tmp = NULL;
   uint blen;
   uint hlen;
   uint i;
   int ret;
   struct stat statBuf;

   assert( basedir );
   assert( hash );
   assert( depth );

   blen = strlen( basedir );
   hlen = strlen( hash );

   if( depth > hlen ) {
      snprintf( errstr, MAX_ERR_LEN, "Depth greater than hash length" );
      return NULL;
   }

   if( (depth + (hlen * 2)) > FLOB_MAX_DIRSTR_LEN ) {
      snprintf( errstr,
                MAX_ERR_LEN,
                "Directory name longer than %d characters",
                FLOB_MAX_DIRSTR_LEN );
      return NULL;
   }

   tmp = realloc( hashdir, blen + (depth * 2) + 1 );
   if( tmp == NULL ) {
      snprintf( errstr, MAX_ERR_LEN, "Error allocating hash directory string" );
      return NULL;
   }
   hashdir = tmp;

   ret = stat( basedir, &statBuf ) == 0;
   if( ! ret ) {
      snprintf( errstr, MAX_ERR_LEN, "Base directory (%s) does not exist", basedir );
      return NULL;
   }
   ret = statBuf.st_mode & S_IFDIR;
   if( ! ret ) {
      snprintf( errstr, MAX_ERR_LEN, "Base (%s) exists, but is not a directory", basedir );
      return NULL;
   }

   memcpy( hashdir, basedir, blen );
   for( i = 0; i < depth; i++ ) {
      memset( hashdir + blen + (i*2), '/', 1 );
      memset( hashdir + blen + (i*2) + 1, hash[i], 1 );
      memset( hashdir + blen + (i*2) + 2, '\0', 1 );
      ret = mkdir( hashdir, S_IRUSR | S_IWUSR | S_IXUSR );
      if( ret == -1 ) {
         switch( errno ) {
            case EEXIST:
               break;
            case EACCES:
            case EMLINK:
            case ENOSPC:
            case EROFS:
               errstr = strerror_r( errno, errstr, MAX_ERR_LEN );
               return NULL;
            default:
               snprintf( errstr, MAX_ERR_LEN, "Unexpected error creating %s", hashdir );
               return NULL;
         }
      }
   }
   return( hashdir );
}

int
y_octstr_from_file( y_octstr filedat,
                    const char *filename,
                    char *errstr )
{
   int fd;
   ssize_t bytes = 0;
   uint total = 0;
   int ret = 0;
   y_oct_bin tmp = NULL;
   y_oct_bin bin = NULL;
   y_oct_bin dat[Y_MAX_READLEN];

   y_octstr_reinit( filedat );

   fd = open( filename, O_RDONLY );
   if( fd == -1 ) {
      switch( errno ) {
         case EACCES:
         case EEXIST:
         case EINTR:
         case EISDIR:
         case EMFILE:
         case ENFILE:
         case ENOENT:
         case ENOSPC:
         case ENXIO:
         case EROFS:
            errstr = strerror_r( errno, errstr, MAX_ERR_LEN );
            ret = -1;
            goto end;
         default:
            snprintf( errstr, MAX_ERR_LEN, "Unexpected error reading %s", filename );
            ret = -1;
            goto end;
      }
   }

   while( ((bytes = read( fd, dat, Y_MAX_READLEN )) > 0) &&
          (total <= MAX_HASH_FILE_SIZE) ) {
      if( bytes == -1 ) {
         switch( errno ) {
            case EAGAIN:
            case EBADF:
            case EINTR:
            case EIO:
            case EINVAL:
               errstr = strerror_r( errno, errstr, MAX_ERR_LEN );
               ret = -1;
               goto end;
            default:
               snprintf( errstr, MAX_ERR_LEN, "Unexpected error reading %s", filename );
               ret = -1;
               goto end;
         }
      }
      total += bytes;
      tmp = realloc( bin, total );
      if( tmp == NULL ) {
         snprintf( errstr, MAX_ERR_LEN, "Error allocating buffer for file data" );
         ret = -1;
         goto end;
      }
      bin = tmp;
      memcpy( bin + total - bytes, dat, bytes );
   }
   Y_OS_LEN( filedat ) = total;
   Y_OS_STR( filedat ) = bin;

  end:
   if( bin && (Y_OS_LEN(filedat) == 0) )
      free( bin );
   if( fd )
      close( fd );
   return ret;
}

char
*y_octstr_to_hashfile( y_octstr os,
                      const char *basedir,
                      const char *hashType,
                      uint depth,
                      char *errstr )
{
   char *tmp = NULL;
   char *hash_hex = NULL;
   char *filename = NULL;
   y_octstr hash;
   uint dirlen;
   uint hashlen;
   FILE *fs = NULL;
   struct stat statBuf;
   int chk;
   int old_mask;

   y_octstr_init( hash );

   if( _y_calc_mhash( hash, hashType, os ) != Y_HASH_OK ) {
      snprintf( errstr, MAX_ERR_LEN, "Error calculating hash" );
      goto end;
   }

   tmp = y_cp_hex_realloc_from_octstr( hash_hex, hash );
   if( tmp == NULL ) {
      snprintf( errstr, MAX_ERR_LEN, "Error allocating hash string" );
      goto end;
   }
   hash_hex = tmp;

   tmp = y_mkdir_and_realloc_hashdir_name( filename,
                                           basedir,
                                           hash_hex,
                                           depth,
                                           errstr );

   if( tmp == NULL ) {
      goto end;
   }
   filename = tmp;

   dirlen = strlen( filename );
   hashlen = strlen( hash_hex );
   tmp = realloc( filename, dirlen + hashlen + 2 );
   if( tmp == NULL ) {
      snprintf( errstr, MAX_ERR_LEN, "Error allocating filename" );
      if( filename )
         free( filename );
      filename = NULL;
      goto end;
   }
   filename = tmp;

   memset( filename + dirlen, '/', 1 );
   memcpy( filename + dirlen + 1, hash_hex, hashlen );
   memset( filename + dirlen + 1 + hashlen, '\0', 1 );

   // Don't waste time writing a file that already exists.
   // We should be able to assume that the content is identical.
   chk = stat( filename, &statBuf ) == 0;
   if( chk ) {
      snprintf( errstr, MAX_ERR_LEN, "File (%s) already exists", filename );
      if( filename )
         free( filename );
      filename = NULL;
      goto end;
   }

   old_mask = umask(077);

   fs = fopen( filename, "wx" );
   if( fs == NULL ) {
      snprintf( errstr, MAX_ERR_LEN, "Error opening file (%s) for write, possibly already exists", filename );
      if( filename )
         free( filename );
      filename = NULL;
      goto end;
   }
   chk = fwrite( Y_OS_STR(os), Y_OS_LEN(os), 1, fs );
   if( chk != 1 ) {
      snprintf( errstr, MAX_ERR_LEN, "Error writing data to file" );
      if( filename )
         free( filename );
      filename = NULL;
      goto end;
   }

  umask( old_mask );

  end:
   y_octstr_clear( hash );
   if( hash_hex ) 
      free( hash_hex );
   if( fs )
      fclose( fs );
   return filename;
}
////////////////////////////////////////////////////////////////////////
