/*
  Copyright 2009
  (Y) Yellowbank
  Ronald Peterson

  https://www.yellowbank.com/

  This file is part of y_pgcrypto.

  y_pgcrypto 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.
*/

/*
  Functions for:
  - creating cryptographically strong random
  numbers (gmp library, using /dev/random for seed data)
  - creating RSA keypairs (nettle)
  - RSA encryption / decryption (nettle)
  - RSA signatures / verification (nettle)
*/

//________________________________________________________________________
// link w/ -ly_clib
#include <y_clib.h>

#include <mhash.h>
#include <nettle/rsa.h>
#include <nettle/sha.h>

#include <postgres.h>
#include <fmgr.h>
#include <utils/datetime.h>
// tuple building functions and macros
#include <access/heapam.h>
#include <funcapi.h>

// 8.2 -> 8.3 macro change
#ifndef SET_VARSIZE
#define SET_VARSIZE(v,l) (VARATT_SIZEP(v) = (l))
#endif

#define RANDOM_SEED_OCTETS 24
#define MAX_SEED_OCTETS 64

static gmp_randstate_t GMP_RANDOM_STATE;
static y_bbs_state BBS_RANDOM_STATE;

//________________________________________________________________________
// PostgreSQL specific utility functions.  Generic utility functions are
// in y_clib.

// static
// bytea *
// y_bytea_set_from_mpz_t( bytea *b, mpz_t m ) {
//     int octets;
//     octets = (mpz_sizeinbase( m, 2 ) + 7) / 8;
//     b = (bytea *)palloc( octets + VARHDRSZ );
//     SET_VARSIZE( b, octets + VARHDRSZ );
//     mpz_export( VARDATA(b), NULL, 1, 1, 0, 0, m );
//     return b;
// }

static
char *
y_cp_set_from_mpz_t( char *c, mpz_t m ) {
    int octets;
    int len;
    octets = (mpz_sizeinbase( m, 2 ) + 7) / 8;
    len = 2 * octets + 1;
    c = (char *)palloc( len );
    gmp_snprintf( c, len, "%Zx", m );
    return c;
}

static
text *
y_tp_set_from_mpz_t( text *tp, mpz_t m ) {
    int octets;
    int len;
    char *buf;

    octets = (mpz_sizeinbase( m, 2 ) + 7) / 8;
    len = 2 * octets;

    buf = (char *)palloc( len + 1 );
    gmp_snprintf( buf, len + 1, "%Zx", m );

    tp = (text *)palloc( len + VARHDRSZ );
    SET_VARSIZE( tp, len + VARHDRSZ );
    memcpy( VARDATA(tp), buf, len );

    pfree( buf );

    return tp;
}

static
char
*tp2cp_repalloc( char *cp, const text *tp ) {
    int len;
    if( tp ) {
        len = VARSIZE(tp) - VARHDRSZ;
    } else {
        return NULL;
    }
    if( cp ) {
        memset( cp, '\0', strlen( cp ) + 1 );
        pfree( cp );
    }
    cp = (char *)palloc( len + 1 );
    if( cp == NULL ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("tp2cp_repalloc: "
                        "error reallocating character string")));
        return NULL;
    }
    if( ! memcpy( cp, VARDATA(tp), len ) ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("tp2cp_repalloc: "
                        "error copying data")));
        return NULL;
    }
    if( ! memset( cp + len, '\0', 1 ) ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("tp2cp_repalloc: "
                        "error setting terminal null")));
        return NULL;
    }
    return cp;
}

// expects null terminated string input
// static
// text
// *cp2tp_repalloc( text *tp, const char *cp ) {
//     int len;
//     text *tmp;
//     len = strlen( cp );
//     if( tp ) {
//         memset( VARDATA(tp), '\0', VARSIZE(tp) - VARHDRSZ );
//         pfree( tp );
//     }
//     tmp = (text *)palloc( len + VARHDRSZ );
//     if( tmp == NULL ) {
//         ereport(ERROR,
//                 (errcode(ERRCODE_INTERNAL_ERROR),
//                  errmsg("cp2tp_repalloc: "
//                         "error reallocating text")));
//         return NULL;
//     }
//     tp = tmp;
//     SET_VARSIZE(tp, len + VARHDRSZ);
//     if( ! memcpy( VARDATA(tp), cp, len ) ) {
//         ereport(ERROR,
//                 (errcode(ERRCODE_INTERNAL_ERROR),
//                  errmsg("cp2tp_repalloc: "
//                         "error copying data")));
//         return NULL;
//     }
// 
//     return tp;
// }

// Initialize random number context with random seed obtained from
// /dev/random.
void
_PG_init()
{
    int retval;
    y_octstr seed;
    mpz_t mpz_seed;

    y_octstr_init( seed );
    mpz_init( mpz_seed );


    ////////////////////////////////////////////////////////////////////////
    // Blum Blum Shub initialization
    ////////////////////////////////////////////////////////////////////////

    y_bbs_rand_init( BBS_RANDOM_STATE );

    ereport(WARNING,
            ( errcode(ERRCODE_SUCCESSFUL_COMPLETION),
              errmsg( "y_crypto: WARNING: Call y_bbs_random_seed!!!" )));

    // If you want to ensure BBS_RANDOM_STATE is properly seeded before use,
    // uncomment this.  Otherwise, be sure to properly seed BBS_RANDOM_STATE
    // with user space function before expecting anything really random.
    //
    // ereport(INFO,
    //         ( errcode(ERRCODE_SUCCESSFUL_COMPLETION),
    //           errmsg( "y_crypto: _PG_init initializing random state.  This might take a while..." )));
    // y_bbs_rand_seed( BBS_RANDOM_STATE, 32, 32 );

    ////////////////////////////////////////////////////////////////////////
    // Mersenne Twister initialization
    ////////////////////////////////////////////////////////////////////////

    // We're not using the Mersenne Twister to produce strong random number (right?)
    // so don't waste time waiting for /dev/random to collect entropy.
    retval = y_get_os_dev_random( seed, RANDOM_SEED_OCTETS, "/dev/urandom" );

    if( retval == Y_GOOD_RANDOM ) {
        y_gmp_int_set_from_octstr( mpz_seed, seed );
        gmp_randinit_mt( GMP_RANDOM_STATE );
        gmp_randseed( GMP_RANDOM_STATE, mpz_seed ); 
        ereport(INFO,
                ( errcode(ERRCODE_SUCCESSFUL_COMPLETION),
                  errmsg( "y_crypto: _PG_init successfully intialized Mersenne Twister random state" )));
    } else {
        ereport(ERROR,(
                    errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg( "Error in y_crypto: _PG_init failed to properly intialize Mersenne Twister state" )));
    }

    y_octstr_clear( seed );
    mpz_clear( mpz_seed );
}

//________________________________________________________________________
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

//________________________________________________________________________
// Forward declarations
Datum y_mt_random_range( PG_FUNCTION_ARGS );
Datum y_bbs_random_seed( PG_FUNCTION_ARGS );
Datum y_bbs_random_octets( PG_FUNCTION_ARGS );
Datum y_bbs_random_range( PG_FUNCTION_ARGS );
//
Datum y_mhash( PG_FUNCTION_ARGS );
//
// Datum y_generate_rsa_keypair_bin( PG_FUNCTION_ARGS );
Datum y_generate_rsa_keypair( PG_FUNCTION_ARGS );
Datum y_rsa_encrypt( PG_FUNCTION_ARGS );
Datum y_rsa_decrypt( PG_FUNCTION_ARGS );
Datum y_rsa_sign( PG_FUNCTION_ARGS );
Datum y_rsa_sign_sha256( PG_FUNCTION_ARGS );
Datum y_rsa_verify( PG_FUNCTION_ARGS );
Datum y_rsa_verify_sha256( PG_FUNCTION_ARGS );
//

// Should someday port all of GMP, but for now, implement a few
// convenience functions.
//
Datum y_mpz_add_c( PG_FUNCTION_ARGS );
Datum y_mpz_sub_c( PG_FUNCTION_ARGS );
Datum y_mpz_mul_c( PG_FUNCTION_ARGS );

//________________________________________________________________________
// Main function definitions begin here


//________________________________________________________________________
// This version uses the Mersenne Twister alogorithm provided by GMP to
// calculate random numbers.  It shouldn't be used for strong cryptography,
// but may have application in other contexts.
PG_FUNCTION_INFO_V1( y_mt_random_range );
Datum
y_mt_random_range( PG_FUNCTION_ARGS )
{
    mpz_t mpz_random;
    mpz_t max;
    char *max_cp = NULL;
    text *max_tp = NULL;
    int random_octets;
    bytea *random_ret;
    int invalid;

    if( PG_ARGISNULL(0) )
    {
        PG_RETURN_NULL();
    }
    max_tp = PG_GETARG_TEXT_P(0);
    max_cp = tp2cp_repalloc( max_cp, max_tp );

    mpz_init( mpz_random );
    mpz_init( max );
   
    invalid = mpz_set_str( max, max_cp, 16 );
    if( invalid ) {
        ereport(ERROR,(
                    errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("Error in y_random_hex: invalid argument" ),
                    errhint("Argument (max) must be hexadecimal string.  "
                            "Result in the range 0 to max-1, inclusive.")));
        pfree( max_cp );
        mpz_clear( mpz_random );
        mpz_clear( max );
        PG_RETURN_NULL();
    }

    mpz_urandomm( mpz_random, GMP_RANDOM_STATE, max );

    random_octets = (mpz_sizeinbase( mpz_random, 2 ) + 7) / 8;
    random_ret = (bytea *)palloc( random_octets + VARHDRSZ );
    SET_VARSIZE( random_ret, random_octets + VARHDRSZ );
    mpz_export( VARDATA(random_ret), NULL, 1, 1, 0, 0, mpz_random );

    pfree( max_cp );
    mpz_clear( mpz_random );
    mpz_clear( max );

    PG_RETURN_BYTEA_P( random_ret );
}

//________________________________________________________________________
// Blum Blum Shub
PG_FUNCTION_INFO_V1( y_bbs_random_seed );
Datum
y_bbs_random_seed( PG_FUNCTION_ARGS )
{
    int32 x_octets;
    int32 key_octets;

    if( PG_ARGISNULL(0) )
    {
        PG_RETURN_BOOL( false );
    }
    x_octets = PG_GETARG_INT32(0);
    key_octets = PG_GETARG_INT32(1);

    if( x_octets > MAX_SEED_OCTETS ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("y_bbs_random_seed: x_octets out of range (%d)", MAX_SEED_OCTETS)));
        PG_RETURN_BOOL( false );
    }
    if( key_octets > MAX_SEED_OCTETS ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("y_bbs_random_seed: key_octets out of range (%d)", MAX_SEED_OCTETS)));
        PG_RETURN_BOOL( false );
    }

    ereport(INFO,
            ( errcode(ERRCODE_SUCCESSFUL_COMPLETION),
              errmsg( "Seeding random state.  This might take a while..." )));

    y_bbs_rand_seed( BBS_RANDOM_STATE, x_octets, key_octets );

    PG_RETURN_BOOL( true );
}


//________________________________________________________________________
// Blum Blum Shub
PG_FUNCTION_INFO_V1( y_bbs_random_octets );
Datum
y_bbs_random_octets( PG_FUNCTION_ARGS )
{
    uint8_t *random;
    int32 random_octets;
    bytea *random_ret;

    if( PG_ARGISNULL(0) )
    {
        PG_RETURN_NULL();
    }
    random_octets = PG_GETARG_INT32(0);

    random = (uint8_t *)palloc( random_octets );
    y_bbs_rand_octets( BBS_RANDOM_STATE, random_octets, random );

    random_ret = (bytea *)palloc( random_octets + VARHDRSZ );
    SET_VARSIZE( random_ret, random_octets + VARHDRSZ );
    memcpy( VARDATA(random_ret), random, random_octets );

    PG_RETURN_BYTEA_P( random_ret );
}

//________________________________________________________________________
// Blum Blum Shub
// Return value in range 0 to max-1, inclusive.
// Single argument 'max' as hexidecimal string.
//
PG_FUNCTION_INFO_V1( y_bbs_random_range );
Datum
y_bbs_random_range( PG_FUNCTION_ARGS )
{
    text *max_tp;
    char *max_cp = NULL;
    mpz_t max;
    mpz_t random;
    int32 random_octets;
    bytea *random_ret;
    int invalid;

    if( PG_ARGISNULL(0) )
    {
        PG_RETURN_NULL();
    }
    max_tp = PG_GETARG_TEXT_P(0);

    mpz_init( max );
    mpz_init( random );

    // 'max_tp' input as hex string
    max_cp = tp2cp_repalloc( max_cp, max_tp );
    invalid = mpz_set_str( max, max_cp, 16 );

    if( invalid ) {
        ereport(ERROR,(
                    errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("Error in y_bbs_random_range: invalid argument" ),
                    errhint("Argument (max) must be hexadecimal string.  "
                            "Result in the range 0 to max-1, inclusive.")));
        mpz_clear( max );
        mpz_clear( random );
        pfree( max_cp );
        PG_RETURN_NULL();
    }

    y_bbs_rand_range( BBS_RANDOM_STATE, max, random );

    random_octets = (mpz_sizeinbase( random, 2 ) + 7) / 8;
    // random_octets = mpz_sizeinbase( random, 8 );
    random_ret = (bytea *)palloc( random_octets + VARHDRSZ );
    SET_VARSIZE( random_ret, random_octets + VARHDRSZ );
    mpz_export( VARDATA(random_ret), NULL, 1, 1, 0, 0, random );

    mpz_clear( random );
    mpz_clear( max );
    pfree( max_cp );
    PG_RETURN_BYTEA_P( random_ret );
}

PG_FUNCTION_INFO_V1( y_mhash );
Datum
y_mhash( PG_FUNCTION_ARGS )
{
    bytea* data;
    text* type;
    int ret;
    int len;
    uint hash_size;
    bytea* result;
    char* hash_type;
    y_octstr hash;
	
    if( PG_ARGISNULL(0) ||
        PG_ARGISNULL(1) )
    {
        PG_RETURN_NULL();
    }
    data = PG_GETARG_BYTEA_P(0);
    type = PG_GETARG_TEXT_P(1);

    len = VARSIZE( type ) - VARHDRSZ;
    hash_type = (char*)palloc( len + 1 );
    memcpy( hash_type, VARDATA(type), len );
    memset( hash_type + len, '\0', 1 );

    y_octstr_init( hash );

    ret = y_calc_mhash( hash,
                        hash_type,
                        VARDATA(data),
                        VARSIZE(data) - VARHDRSZ,
                        &hash_size );

    if( ret != Y_HASH_OK ) {
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("hash type not available")));
        PG_RETURN_NULL();
    }

    result = (bytea*)palloc( VARHDRSZ + hash_size );
    SET_VARSIZE( result, VARHDRSZ + hash_size );
    memcpy( VARDATA( result ), Y_OS_STR( hash ), hash_size );

    y_octstr_clear( hash );
    pfree( hash_type );

    PG_RETURN_BYTEA_P( result );
}

// This version of y_generate_nettle_rsa_keypair returns a tuple
// containing bytea values.  It works, but I've elected to publish
// the hex string version below instead.
//
// PG_FUNCTION_INFO_V1( y_generate_nettle_rsa_keypair_bin );
// Datum
// y_generate_rsa_keypair_bin( PG_FUNCTION_ARGS )
// {
//     int32 size_in_bits;
//     int32 e_size_in_bits;
//     struct rsa_public_key key_pub;
//     struct rsa_private_key key_pri;
//     int ret = 0;
//     int tuplen;
//     bool *nulls;
//     gmp_randstate_t random_ctx;
// 
//     bytea *n = NULL;
//     bytea *e = NULL;
//     bytea *d = NULL;
//     bytea *p = NULL;
//     bytea *q = NULL;
//     bytea *a = NULL;
//     bytea *b = NULL;
//     bytea *c = NULL;
//     
//     TupleDesc tupdesc;
//     Datum values[8];
//     HeapTuple tuple;
// 
//     if( PG_ARGISNULL(0) )
//     {
//         PG_RETURN_NULL();
//     }
//     size_in_bits = PG_GETARG_INT32(0);
//     e_size_in_bits = PG_GETARG_INT32(1);
// 
//     y_gmp_random_init( &random_ctx, 24 );
// 
//     rsa_public_key_init( &key_pub );
//     rsa_private_key_init( &key_pri );
// 
//     ret = rsa_generate_keypair( &key_pub,
//                                 &key_pri,
//                                 &random_ctx,
//                                 (nettle_random_func *) y_gmp_random_func,
//                                 NULL,
//                                 NULL,
//                                 size_in_bits,
//                                 e_size_in_bits );
// 
//     if( ! ret ) {
//         ereport( ERROR,
//                  ( errcode( ERRCODE_INTERNAL_ERROR ),
//                    errmsg( "nettle rsa key generation failed" )));
//         PG_RETURN_NULL();
//     }
// 
//     n = y_bytea_set_from_mpz_t( n, key_pub.n );
//     e = y_bytea_set_from_mpz_t( e, key_pub.e );
//     d = y_bytea_set_from_mpz_t( d, key_pri.d );
//     p = y_bytea_set_from_mpz_t( p, key_pri.p );
//     q = y_bytea_set_from_mpz_t( q, key_pri.q );
//     a = y_bytea_set_from_mpz_t( a, key_pri.a );
//     b = y_bytea_set_from_mpz_t( b, key_pri.b );
//     c = y_bytea_set_from_mpz_t( c, key_pri.c );
// 
//     /* Build a tuple descriptor for our result type */
//     if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
//         ereport(ERROR,
//                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
//                  errmsg("function returning record called in context "
//                         "that cannot accept type record")));
//         PG_RETURN_NULL();
//     }
// 
//     tupdesc = CreateTupleDescCopy( tupdesc );
// 
//     BlessTupleDesc( tupdesc );
// 
//     values[0] = PointerGetDatum( n );
//     values[1] = PointerGetDatum( e );
//     values[2] = PointerGetDatum( d );
//     values[3] = PointerGetDatum( p );
//     values[4] = PointerGetDatum( q );
//     values[5] = PointerGetDatum( a );
//     values[6] = PointerGetDatum( b );
//     values[7] = PointerGetDatum( c );
// 
//     tuplen = tupdesc->natts;
// 
//     nulls = palloc( tuplen * sizeof( bool ) );
//     memset( nulls, false, sizeof( nulls ) );
// 
//     // build tuple from datum array
//     tuple = heap_form_tuple( tupdesc, values, nulls );
// 
//     pfree( nulls );
// 
//     PG_RETURN_DATUM( HeapTupleGetDatum( tuple ) );
// }


PG_FUNCTION_INFO_V1( y_generate_rsa_keypair );
Datum
y_generate_rsa_keypair( PG_FUNCTION_ARGS )
{
    int32 size_in_bits;
    int32 e_size_in_bits;
    struct rsa_public_key key_pub;
    struct rsa_private_key key_pri;
    int ret = 0;

    char **values;
    TupleDesc tupdesc;
    HeapTuple tuple;
    AttInMetadata *aim;
    Datum result;

    if( PG_ARGISNULL(0) )
    {
        PG_RETURN_NULL();
    }
    size_in_bits = PG_GETARG_INT32(0);
    e_size_in_bits = PG_GETARG_INT32(1);

    rsa_public_key_init( &key_pub );
    rsa_private_key_init( &key_pri );

    ret = rsa_generate_keypair( &key_pub,
                                &key_pri,
                                &BBS_RANDOM_STATE,
                                (nettle_random_func *) y_bbs_rand_octets,
                                NULL,
                                NULL,
                                size_in_bits,
                                e_size_in_bits );

    if( ! ret ) {
        ereport( ERROR,
                 ( errcode( ERRCODE_INTERNAL_ERROR ),
                   errmsg( "nettle rsa key generation failed" )));
        PG_RETURN_NULL();
    }

    values = (char **)palloc( sizeof(char*) * 8 );

    values[0] = y_cp_set_from_mpz_t( values[0], key_pub.n );
    values[1] = y_cp_set_from_mpz_t( values[1], key_pub.e );
    values[2] = y_cp_set_from_mpz_t( values[2], key_pri.d );
    values[3] = y_cp_set_from_mpz_t( values[3], key_pri.p );
    values[4] = y_cp_set_from_mpz_t( values[4], key_pri.q );
    values[5] = y_cp_set_from_mpz_t( values[5], key_pri.a );
    values[6] = y_cp_set_from_mpz_t( values[6], key_pri.b );
    values[7] = y_cp_set_from_mpz_t( values[7], key_pri.c );

    /* Build a tuple descriptor for our result type */
    if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("function returning record called in context "
                        "that cannot accept type record")));
        PG_RETURN_NULL();
    }

    tupdesc = CreateTupleDescCopy( tupdesc );
    aim = TupleDescGetAttInMetadata( tupdesc );
    tuple = BuildTupleFromCStrings( aim, values );

    result = HeapTupleGetDatum( tuple );

    rsa_public_key_clear( &key_pub );
    rsa_private_key_clear( &key_pri );

    pfree( values[0] );
    pfree( values[1] );
    pfree( values[2] );
    pfree( values[3] );
    pfree( values[4] );
    pfree( values[5] );
    pfree( values[6] );
    pfree( values[7] );
    pfree( values );

    PG_RETURN_DATUM( result );
}

PG_FUNCTION_INFO_V1( y_rsa_encrypt );
Datum
y_rsa_encrypt( PG_FUNCTION_ARGS )
{
    bytea *messageBin;
    text *nHex;
    text *eHex;
    char* hexstr = NULL;
    text *messageEncrypted = NULL;
    uint8_t *message;
    unsigned message_length;
    Datum publicKey;
    HeapTupleHeader publicKeyIN;
    struct rsa_public_key key_pub;
    mpz_t message_m;
    mpz_t message_max;
    mpz_t messageEncrypted_tmp;
    int ret = 0;
    bool isNull;

    if( PG_ARGISNULL(0) )
    {
        PG_RETURN_NULL();
    }
    messageBin = PG_GETARG_BYTEA_P(0);
    // Use Datum -> HeapTupleHeader, then...
    publicKey = PG_GETARG_DATUM(1);
    publicKeyIN = DatumGetHeapTupleHeader( publicKey );
    // Datum -> DatumGetXXX
    // ...because this process ensures anything toasted
    // gets de-toasted.
    nHex = DatumGetTextP( GetAttributeByNum( publicKeyIN, 1, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("error obtaining modulus")));
        PG_RETURN_NULL();
    }
    eHex = DatumGetTextP( GetAttributeByNum( publicKeyIN, 2, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("error obtaining public exponent")));
        PG_RETURN_NULL();
    }

    message_length = VARSIZE( messageBin ) - VARHDRSZ;
    message = (uint8_t *)palloc( message_length );
    memcpy( message, VARDATA( messageBin ), message_length );

    rsa_public_key_init( &key_pub );

    hexstr = tp2cp_repalloc( hexstr, eHex );
    mpz_set_str( key_pub.e, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, nHex );
    mpz_set_str( key_pub.n, hexstr, 16 );

    pfree( hexstr );

    // Message must be between 0 and n-1.
    mpz_init( message_m );
    mpz_init( message_max );
    mpz_sub_ui( message_max, key_pub.n, 1 );
    mpz_import( message_m, message_length, 1, 1, 0, 0, message );
    if( mpz_cmp( message_max, message_m ) <= 0 ||
        mpz_cmp_ui( message_m, 0 ) <= 0 ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("message representative out of range")));
        mpz_clear( message_m );
        mpz_clear( message_max );
        rsa_public_key_clear( &key_pub );
        PG_RETURN_NULL();
    }
    mpz_clear( message_m );
    mpz_clear( message_max );

    if( ! rsa_public_key_prepare( &key_pub ) ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("invalid public key")));
        rsa_public_key_clear( &key_pub );
        PG_RETURN_NULL();
    }

    mpz_init( messageEncrypted_tmp );

    ret = rsa_encrypt( &key_pub,
                       &BBS_RANDOM_STATE,
                       (nettle_random_func *) y_bbs_rand_octets,
                       message_length,
                       message,
                       messageEncrypted_tmp );

    if( ! ret ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("encryption failed")));
        rsa_public_key_clear( &key_pub );
        mpz_clear( messageEncrypted_tmp );
        PG_RETURN_NULL();
    }

    messageEncrypted = y_tp_set_from_mpz_t( messageEncrypted, messageEncrypted_tmp );

    rsa_public_key_clear( &key_pub );
    mpz_clear( messageEncrypted_tmp );

    PG_RETURN_TEXT_P( messageEncrypted );
}


PG_FUNCTION_INFO_V1( y_rsa_decrypt );
Datum
y_rsa_decrypt( PG_FUNCTION_ARGS )
{
    text *messageEncrypted;
    text *dHex;
    text *pHex;
    text *qHex;
    text *aHex;
    text *bHex;
    text *cHex;
    char* hexstr = NULL;
    bytea *messageBin = NULL;
    uint8_t *message;
    unsigned message_length;
    mpz_t message_max;
    Datum privateKey;
    HeapTupleHeader privateKeyIN;
    struct rsa_private_key key_pri;
    mpz_t messageEncrypted_tmp;
    int ret = 0;
    bool isNull;

    if( PG_ARGISNULL(0) )
    {
        PG_RETURN_NULL();
    }
    messageEncrypted = PG_GETARG_TEXT_P(0);
    privateKey = PG_GETARG_DATUM(1);
    privateKeyIN = DatumGetHeapTupleHeader( privateKey );
    dHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 1, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("private exponent cannot be NULL")));
        PG_RETURN_NULL();
    }
    pHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 2, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("prime factor p cannot be NULL")));
        PG_RETURN_NULL();
    }
    qHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 3, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("prime factor q cannot be NULL")));
        PG_RETURN_NULL();
    }
    aHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 4, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("CRT exponent a (dP) cannot be NULL")));
        PG_RETURN_NULL();
    }
    bHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 5, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("CRT exponent b (dQ) cannot be NULL")));
        PG_RETURN_NULL();
    }
    cHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 6, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("CRT coefficient c (qInv) cannot be NULL")));
        PG_RETURN_NULL();
    }

    rsa_private_key_init( &key_pri );

    hexstr = tp2cp_repalloc( hexstr, dHex );
    mpz_set_str( key_pri.d, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, pHex );
    mpz_set_str( key_pri.p, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, qHex );
    mpz_set_str( key_pri.q, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, aHex );
    mpz_set_str( key_pri.a, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, bHex );
    mpz_set_str( key_pri.b, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, cHex );
    mpz_set_str( key_pri.c, hexstr, 16 );

    if( ! rsa_private_key_prepare( &key_pri ) ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("invalid private key")));
        rsa_private_key_clear( &key_pri );
        PG_RETURN_NULL();
    }

    hexstr = tp2cp_repalloc( hexstr, messageEncrypted );

    mpz_init( messageEncrypted_tmp );
    mpz_set_str( messageEncrypted_tmp, hexstr, 16 );

    // add modulo in case odd number of hex digits
    message_length = strlen( hexstr ) / 2 + strlen( hexstr ) % 2;
    message = (uint8_t *)palloc( message_length );

    pfree( hexstr );

    // Message (encrypted) must be between 0 and n-1.
    // n = p * q
    mpz_init( message_max );
    mpz_mul( message_max, key_pri.p, key_pri.q );
    mpz_sub_ui( message_max, message_max, 1 );
    if( mpz_cmp( message_max, messageEncrypted_tmp ) <= 0 ||
        mpz_cmp_ui( messageEncrypted_tmp, 0 ) <= 0 ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("message representative out of range")));
        mpz_clear( message_max );
        mpz_clear( messageEncrypted_tmp );
        pfree( message );
        rsa_private_key_clear( &key_pri );
        PG_RETURN_NULL();
    }
    mpz_clear( message_max );

    ret = rsa_decrypt( &key_pri,
                       &message_length,
                       message,
                       messageEncrypted_tmp );
    if( ! ret ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("decryption failed")));
        rsa_private_key_clear( &key_pri );
        mpz_clear( messageEncrypted_tmp );
        PG_RETURN_NULL();
    }

    messageBin = (bytea *)palloc( message_length + VARHDRSZ );
    SET_VARSIZE( messageBin, message_length + VARHDRSZ );
    memcpy( VARDATA(messageBin), message, message_length );

    rsa_private_key_clear( &key_pri );
    mpz_clear( messageEncrypted_tmp );

    PG_RETURN_BYTEA_P( messageBin );
}


PG_FUNCTION_INFO_V1( y_rsa_sign );
Datum
y_rsa_sign( PG_FUNCTION_ARGS )
{
    bytea *messageBin;
    text *dHex;
    text *pHex;
    text *qHex;
    text *aHex;
    text *bHex;
    text *cHex;
    text *messageSignature = NULL;
    char *hexstr = NULL;
    uint8_t *message;
    unsigned message_length;
    Datum privateKey;
    HeapTupleHeader privateKeyIN;
    struct rsa_private_key key_pri;
    struct sha256_ctx sha256ctx;
    mpz_t signature;
    mpz_t message_m;
    mpz_t message_max;
    bool isNull = false;

    if( PG_ARGISNULL(0) )
    {
        PG_RETURN_NULL();
    }
    messageBin = PG_GETARG_BYTEA_P(0);
    privateKey = PG_GETARG_DATUM(1);
    privateKeyIN = DatumGetHeapTupleHeader( privateKey );
    dHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 1, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("private exponent cannot be NULL")));
        PG_RETURN_NULL();
    }
    pHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 2, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("prime factor p cannot be NULL")));
        PG_RETURN_NULL();
    }
    qHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 3, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("prime factor q cannot be NULL")));
        PG_RETURN_NULL();
    }
    aHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 4, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("CRT exponent a (dP) cannot be NULL")));
        PG_RETURN_NULL();
    }
    bHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 5, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("CRT exponent b (dQ) cannot be NULL")));
        PG_RETURN_NULL();
    }
    cHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 6, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("CRT coefficient c (qInv) cannot be NULL")));
        PG_RETURN_NULL();
    }

    message_length = VARSIZE( messageBin ) - VARHDRSZ;
    message = (uint8_t *)palloc( message_length );
    message = memcpy( message, VARDATA( messageBin ), message_length );

    rsa_private_key_init( &key_pri );

    // TODO: need to check message length against size of key.

    hexstr = tp2cp_repalloc( hexstr, dHex );
    mpz_set_str( key_pri.d, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, pHex );
    mpz_set_str( key_pri.p, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, qHex );
    mpz_set_str( key_pri.q, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, aHex );
    mpz_set_str( key_pri.a, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, bHex );
    mpz_set_str( key_pri.b, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, cHex );
    mpz_set_str( key_pri.c, hexstr, 16 );

    pfree( hexstr );

    // Message (encrypted) must be between 0 and n-1.
    // n = p * q
    mpz_init( message_m );
    mpz_init( message_max );
    mpz_mul( message_max, key_pri.p, key_pri.q );
    mpz_sub_ui( message_max, message_max, 1 );
    mpz_import( message_m, message_length, 1, 1, 0, 0, message );    
    if( mpz_cmp( message_max, message_m ) <= 0 ||
        mpz_cmp_ui( message_m, 0 ) <= 0 ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("message representative out of range")));
        pfree( message );
        mpz_clear( message_m );
        mpz_clear( message_max );
        rsa_private_key_clear( &key_pri );
        PG_RETURN_NULL();
    }
    mpz_clear( message_m );
    mpz_clear( message_max );

    if( ! rsa_private_key_prepare( &key_pri ) ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("invalid private key")));
        rsa_private_key_clear( &key_pri );
        PG_RETURN_NULL();
    }

    sha256_init( &sha256ctx );
    sha256_update( &sha256ctx, message_length, message );

    mpz_init( signature );
    rsa_sha256_sign( &key_pri,
                     &sha256ctx,
                     signature );

    messageSignature = y_tp_set_from_mpz_t( messageSignature, signature );

    rsa_private_key_clear( &key_pri );
    mpz_clear( signature );

    PG_RETURN_TEXT_P( messageSignature );
}


PG_FUNCTION_INFO_V1( y_rsa_sign_sha256 );
Datum
y_rsa_sign_sha256( PG_FUNCTION_ARGS )
{
    text *messageDigest;
    text *dHex;
    text *pHex;
    text *qHex;
    text *aHex;
    text *bHex;
    text *cHex;
    text *messageSignature = NULL;
    char *hexstr = NULL;
    char *digest_cp = NULL;
    mpz_t digest_m;
    mpz_t message_max;
    uint8_t *digest;
    unsigned digest_octets;
    Datum privateKey;
    HeapTupleHeader privateKeyIN;
    struct rsa_private_key key_pri;
    mpz_t signature;
    bool isNull = false;

    if( PG_ARGISNULL(0) )
    {
        PG_RETURN_NULL();
    }
    messageDigest = PG_GETARG_TEXT_P(0);
    privateKey = PG_GETARG_DATUM(1);
    privateKeyIN = DatumGetHeapTupleHeader( privateKey );
    dHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 1, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("private exponent cannot be NULL")));
        PG_RETURN_NULL();
    }
    pHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 2, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("prime factor p cannot be NULL")));
        PG_RETURN_NULL();
    }
    qHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 3, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("prime factor q cannot be NULL")));
        PG_RETURN_NULL();
    }
    aHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 4, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("CRT exponent a (dP) cannot be NULL")));
        PG_RETURN_NULL();
    }
    bHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 5, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("CRT exponent b (dQ) cannot be NULL")));
        PG_RETURN_NULL();
    }
    cHex = DatumGetTextP( GetAttributeByNum( privateKeyIN, 6, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("CRT coefficient c (qInv) cannot be NULL")));
        PG_RETURN_NULL();
    }

    rsa_private_key_init( &key_pri );

    hexstr = tp2cp_repalloc( hexstr, dHex );
    mpz_set_str( key_pri.d, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, pHex );
    mpz_set_str( key_pri.p, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, qHex );
    mpz_set_str( key_pri.q, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, aHex );
    mpz_set_str( key_pri.a, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, bHex );
    mpz_set_str( key_pri.b, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, cHex );
    mpz_set_str( key_pri.c, hexstr, 16 );

    pfree( hexstr );

    if( ! rsa_private_key_prepare( &key_pri ) ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("invalid private key")));
        rsa_private_key_clear( &key_pri );
        PG_RETURN_NULL();
    }

    // convert digest input as hex string (text_p) into
    // uint8_t *, as req'd by rsa_sha256_sign_digest
    digest_cp = tp2cp_repalloc( digest_cp, messageDigest );
    mpz_init( digest_m );
    if( mpz_set_str( digest_m, digest_cp, 16 ) == -1 ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("y_rsa_sign_sha256: "
                        "digest is not valid hex string")));
        pfree( digest_cp );
        PG_RETURN_NULL();
    }
    digest_octets = (mpz_sizeinbase( digest_m, 2 ) + 7) / 8;
    // sha256 output size = 256 bits.
    if( digest_octets != SHA256_DIGEST_SIZE ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("y_rsa_sign_sha256: "
                        "invalid SHA256 digest")));
        pfree( digest_cp );
        mpz_clear( digest_m );
        PG_RETURN_NULL();
    }
    digest = (uint8_t *)palloc( digest_octets );
    mpz_export( digest, NULL, 1, 1, 0, 0, digest_m );

    // Message (encrypted) must be between 0 and n-1.
    // n = p * q
    mpz_init( message_max );
    mpz_mul( message_max, key_pri.p, key_pri.q );
    mpz_sub_ui( message_max, message_max, 1 );
    if( mpz_cmp( message_max, digest_m ) <= 0 ||
        mpz_cmp_ui( digest_m, 0 ) <= 0 ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("message representative out of range")));
        pfree( digest_cp );
        pfree( digest );
        mpz_clear( digest_m );
        mpz_clear( message_max );
        rsa_private_key_clear( &key_pri );
        PG_RETURN_NULL();
    }
    mpz_clear( message_max );

    mpz_init( signature );
    rsa_sha256_sign_digest( &key_pri,
                            digest,
                            signature );

    messageSignature = y_tp_set_from_mpz_t( messageSignature, signature );

    pfree( digest );
    pfree( digest_cp );
    mpz_clear( signature );
    mpz_clear( digest_m );
    rsa_private_key_clear( &key_pri );

    PG_RETURN_TEXT_P( messageSignature );
}


PG_FUNCTION_INFO_V1( y_rsa_verify );
Datum
y_rsa_verify( PG_FUNCTION_ARGS )
{
    text *messageSignature;
    bytea *messageBin;
    text *nHex;
    text *eHex;
    char* hexstr = NULL;
    mpz_t signature;
    mpz_t message_m;
    mpz_t message_max;
    uint8_t *message;
    unsigned message_length;
    Datum publicKey;
    HeapTupleHeader publicKeyIN;
    struct rsa_public_key key_pub;
    struct sha256_ctx sha256ctx;
    bool isNull = false;
    bool isOK = false;

    if( PG_ARGISNULL(0) )
    {
        PG_RETURN_NULL();
    }
    messageBin = PG_GETARG_BYTEA_P(0);
    messageSignature = PG_GETARG_TEXT_P(1);
    publicKey = PG_GETARG_DATUM(2);
    publicKeyIN = DatumGetHeapTupleHeader( publicKey );
    nHex = DatumGetTextP( GetAttributeByNum( publicKeyIN, 1, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("error obtaining modulus")));
        PG_RETURN_NULL();
    }
    eHex = DatumGetTextP( GetAttributeByNum( publicKeyIN, 2, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("error obtaining public exponent")));
        PG_RETURN_NULL();
    }

    rsa_public_key_init( &key_pub );

    hexstr = tp2cp_repalloc( hexstr, eHex );
    mpz_set_str( key_pub.e, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, nHex );
    mpz_set_str( key_pub.n, hexstr, 16 );

    if( ! rsa_public_key_prepare( &key_pub ) ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("invalid public key")));
        rsa_public_key_clear( &key_pub );
        PG_RETURN_NULL();
    }

    message_length = VARSIZE( messageBin ) - VARHDRSZ;
    message = (uint8_t *)palloc( message_length );
    message = memcpy( message, VARDATA( messageBin ), message_length );

    // Message must be between 0 and n-1.
    mpz_init( message_m );
    mpz_init( message_max );
    mpz_sub_ui( message_max, key_pub.n, 1 );
    mpz_import( message_m, message_length, 1, 1, 0, 0, message );
    if( mpz_cmp( message_max, message_m ) <= 0 ||
        mpz_cmp_ui( message_m, 0 ) <= 0 ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("message representative out of range")));
        pfree( hexstr );
        pfree( message );
        mpz_clear( message_m );
        mpz_clear( message_max );
        rsa_public_key_clear( &key_pub );
        PG_RETURN_NULL();
    }
    mpz_clear( message_m );
    mpz_clear( message_max );

    sha256_init( &sha256ctx );
    sha256_update( &sha256ctx, message_length, message );

    mpz_init( signature );
    hexstr = tp2cp_repalloc( hexstr, messageSignature );
    mpz_set_str( signature, hexstr, 16 );

    if( rsa_sha256_verify( &key_pub, &sha256ctx, signature ) ) {
        isOK = true;
    }

    pfree( hexstr );
    pfree( message );
    mpz_clear( signature );
    rsa_public_key_clear( &key_pub );

    PG_RETURN_BOOL( isOK );
}


PG_FUNCTION_INFO_V1( y_rsa_verify_sha256 );
Datum
y_rsa_verify_sha256( PG_FUNCTION_ARGS )
{
    text *messageDigest;
    text *messageSignature;
    text *nHex;
    text *eHex;
    char* hexstr = NULL;
    mpz_t signature;
    char *digest_cp = NULL;
    mpz_t digest_m;
    mpz_t message_max;
    uint8_t *digest;
    unsigned digest_octets;
    Datum publicKey;
    HeapTupleHeader publicKeyIN;
    struct rsa_public_key key_pub;
    bool isNull = false;
    bool isOK = false;

    if( PG_ARGISNULL(0) )
    {
        PG_RETURN_NULL();
    }
    messageDigest = PG_GETARG_TEXT_P(0);
    messageSignature = PG_GETARG_TEXT_P(1);
    publicKey = PG_GETARG_DATUM(2);
    publicKeyIN = DatumGetHeapTupleHeader( publicKey );
    nHex = DatumGetTextP( GetAttributeByNum( publicKeyIN, 1, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("error obtaining modulus")));
        PG_RETURN_NULL();
    }
    eHex = DatumGetTextP( GetAttributeByNum( publicKeyIN, 2, &isNull ) );
    if( isNull ) { 
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("error obtaining public exponent")));
        PG_RETURN_NULL();
    }

    rsa_public_key_init( &key_pub );

    hexstr = tp2cp_repalloc( hexstr, eHex );
    mpz_set_str( key_pub.e, hexstr, 16 );
    hexstr = tp2cp_repalloc( hexstr, nHex );
    mpz_set_str( key_pub.n, hexstr, 16 );

    if( ! rsa_public_key_prepare( &key_pub ) ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("invalid public key")));
        rsa_public_key_clear( &key_pub );
        PG_RETURN_NULL();
    }

    // convert digest input as hex string (text_p) into
    // uint8_t *, as req'd by rsa_sha256_verify_digest
    digest_cp = tp2cp_repalloc( digest_cp, messageDigest );
    mpz_init( digest_m );
    if( mpz_set_str( digest_m, digest_cp, 16 ) == -1 ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("y_rsa_verify_sha256: "
                        "digest is not valid hex string")));
        pfree( digest_cp );
        pfree( hexstr );
        PG_RETURN_NULL();
    }
    digest_octets = (mpz_sizeinbase( digest_m, 2 ) + 7) / 8;
    if( digest_octets != SHA256_DIGEST_SIZE ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("y_rsa_verify_sha256: "
                        "invalid SHA256 digest")));
        pfree( digest_cp );
        pfree( hexstr );
        mpz_clear( digest_m );
        PG_RETURN_NULL();
    }
    digest = (uint8_t *)palloc( digest_octets );
    mpz_export( digest, NULL, 1, 1, 0, 0, digest_m );

    mpz_init( signature );
    hexstr = tp2cp_repalloc( hexstr, messageSignature );
    if( mpz_set_str( signature, hexstr, 16 ) == -1 ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("y_rsa_verify_sha256: "
                        "message signature is not a valid hex string")));
        pfree( digest_cp );
        pfree( hexstr );
        PG_RETURN_NULL();
    }
    pfree( hexstr );

    // Message must be between 0 and n-1.
    mpz_init( message_max );
    mpz_sub_ui( message_max, key_pub.n, 1 );
    if( mpz_cmp( message_max, digest_m ) <= 0 ||
        mpz_cmp_ui( digest_m, 0 ) <= 0 ) {
        ereport(ERROR,
                (errcode(ERRCODE_INTERNAL_ERROR),
                 errmsg("message representative out of range")));
        pfree( digest_cp );
        pfree( digest );
        mpz_clear( signature );
        mpz_clear( message_max );
        rsa_public_key_clear( &key_pub );
        PG_RETURN_NULL();
    }
    mpz_clear( message_max );

    if( rsa_sha256_verify_digest( &key_pub, digest, signature ) ) {
        isOK = true;
    }

    pfree( digest );
    pfree( digest_cp );
    mpz_clear( signature );
    mpz_clear( digest_m );
    rsa_public_key_clear( &key_pub );

    PG_RETURN_BOOL( isOK );
}

PG_FUNCTION_INFO_V1( y_mpz_mul_c );
Datum
y_mpz_mul_c( PG_FUNCTION_ARGS )
{
    bytea* op1;
    bytea* op2;
    bytea* result;
    int octets;
    mpz_t op1_m;
    mpz_t op2_m;
    mpz_t rop;

    if( PG_ARGISNULL(0) ||
        PG_ARGISNULL(1) )
    {
        PG_RETURN_NULL();
    }
    op1 = PG_GETARG_BYTEA_P(0);
    op2 = PG_GETARG_BYTEA_P(1);

    mpz_init( op1_m );
    mpz_init( op2_m );
    mpz_init( rop );

    octets = VARSIZE(op1) - VARHDRSZ;
    mpz_import( op1_m, octets, 1, 1, 0, 0, VARDATA(op1) );
    octets = VARSIZE(op2) - VARHDRSZ;
    mpz_import( op2_m, octets, 1, 1, 0, 0, VARDATA(op2) );

    mpz_mul( rop, op1_m, op2_m );
    octets = (mpz_sizeinbase( rop, 2 ) + 7) / 8;
    result = (bytea *)palloc( octets + VARHDRSZ );
    SET_VARSIZE( result, octets + VARHDRSZ );
    mpz_export( VARDATA(result), NULL, 1, 1, 0, 0, rop );

    mpz_clear( op1_m );
    mpz_clear( op2_m );
    mpz_clear( rop );

    PG_RETURN_BYTEA_P( result );
}

PG_FUNCTION_INFO_V1( y_mpz_add_c );
Datum
y_mpz_add_c( PG_FUNCTION_ARGS )
{
    bytea* op1;
    bytea* op2;
    bytea* result;
    int octets;
    mpz_t op1_m;
    mpz_t op2_m;
    mpz_t rop;

    if( PG_ARGISNULL(0) ||
        PG_ARGISNULL(1) )
    {
        PG_RETURN_NULL();
    }
    op1 = PG_GETARG_BYTEA_P(0);
    op2 = PG_GETARG_BYTEA_P(1);

    mpz_init( op1_m );
    mpz_init( op2_m );
    mpz_init( rop );

    octets = VARSIZE(op1) - VARHDRSZ;
    mpz_import( op1_m, octets, 1, 1, 0, 0, VARDATA(op1) );
    octets = VARSIZE(op2) - VARHDRSZ;
    mpz_import( op2_m, octets, 1, 1, 0, 0, VARDATA(op2) );

    mpz_add( rop, op1_m, op2_m );
    octets = (mpz_sizeinbase( rop, 2 ) + 7) / 8;
    result = (bytea *)palloc( octets + VARHDRSZ );
    SET_VARSIZE( result, octets + VARHDRSZ );
    mpz_export( VARDATA(result), NULL, 1, 1, 0, 0, rop );

    mpz_clear( op1_m );
    mpz_clear( op2_m );
    mpz_clear( rop );

    PG_RETURN_BYTEA_P( result );
}

PG_FUNCTION_INFO_V1( y_mpz_sub_c );
Datum
y_mpz_sub_c( PG_FUNCTION_ARGS )
{
    bytea* op1;
    bytea* op2;
    bytea* result;
    int octets;
    mpz_t op1_m;
    mpz_t op2_m;
    mpz_t rop;

    if( PG_ARGISNULL(0) ||
        PG_ARGISNULL(1) )
    {
        PG_RETURN_NULL();
    }
    op1 = PG_GETARG_BYTEA_P(0);
    op2 = PG_GETARG_BYTEA_P(1);

    mpz_init( op1_m );
    mpz_init( op2_m );
    mpz_init( rop );

    octets = VARSIZE(op1) - VARHDRSZ;
    mpz_import( op1_m, octets, 1, 1, 0, 0, VARDATA(op1) );
    octets = VARSIZE(op2) - VARHDRSZ;
    mpz_import( op2_m, octets, 1, 1, 0, 0, VARDATA(op2) );

    mpz_sub( rop, op1_m, op2_m );
    octets = (mpz_sizeinbase( rop, 2 ) + 7) / 8;
    result = (bytea *)palloc( octets + VARHDRSZ );
    SET_VARSIZE( result, octets + VARHDRSZ );
    mpz_export( VARDATA(result), NULL, 1, 1, 0, 0, rop );

    mpz_clear( op1_m );
    mpz_clear( op2_m );
    mpz_clear( rop );

    PG_RETURN_BYTEA_P( result );
}

