#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "fairtoss.c,v 1.1.1.1 1995/06/16 17:42:41 seth Exp";
static char sos__copyright[] = "Copyright (c) 1994, 1995 SOS Corporation";
static char sos__contact[] = "SOS Corporation <sos-info@soscorp.com> +1 800 SOS UNIX";
#endif /* not lint */

/*
 * ++Copyright Released Product++
 *
 * Copyright (c) 1994, 1995 Sources of Supply Corporation ("SOS").
 * All rights reserved.
 *
 * The SOS Released Product License Agreement specifies the terms and
 * conditions for redistribution.  You may find the License Agreement
 * in the file LICENSE.
 *
 * SOS Corporation
 * 461 5th Ave.; 16th floor
 * New York, NY 10017
 *
 * +1 800 SOS UNIX
 * <sos-info@soscorp.com>
 *
 * --Copyright Released Product--
 */


/*
 * This function implements a modified version of the fair toss
 * algorithm as outlined in Bruce Schneier's inestimable _Applied
 * Cryptology_ (pp. 75-76). We use the MD5 for the bit-committal
 * mechinism.
 *
 * We modified the fairtoss protocol slightly; we make it symmetrical
 * for easier usage.  The way it works is this: *Both* sides select a
 * random number and commit it (using MD5 to obscure the value) to the
 * peer.  Once each side receives the commital, they will send the
 * original random number. Both sides verify the peer's committed blob,
 * and then XOR the least significant bit of the two numbers.  The
 * result, one or zero, is the fair tossed coin.  Fairtoss returns -1
 * if there is an error.  (This could be trivially extended to obtain
 * one fair bit for every bit in the random number).
 *
 * UPDATE: Each side now generates an additional random number which it sends 
 * out to the other. This random number is hashed into peer's commit blob in
 * order to thwart a birthday attack on the md5 algorithm. This second random
 * value is called an `authenticator' for the lack of better term, but please
 * take note, the authenticator `authenticates' the peer's commit blod (perhaps
 * sanitizer would a better term...), it does not autheticate the peer.
 *
 * For Win/Lose semantics, we put a wrapper around fairtoss.  Each
 * peer chooses and random number and sends it to the other. The lower
 * of the two unsigned values assumes the role of Alice, the greater,
 * Bob.  (ties cause a repeat of the above). The purpose is simply to allow
 * deterministic resolution of
 * SOS_FAIRTOSS_WIN vs SOS_FAIRTOSS_LOSE at the end.  It does not
 * matter if one of the parties cheats in order to become Alice or Bob--the 
 * roles just have to be mutually agreed.  (Fairness is assured through
 * fairtoss, described above).
 *
 * XXX -- Should an ACK in the final message be implemented as verfication
 * of who is the winner, just so both sides can feel confident the other is
 * sane.  This is not a protocol question.
 */

#include "sos.h"
#include "md5.h"


/* User visible debug flag. */
int sos_fairdebug = 0;



/*
 * Toss one (or 32) fair coins with peer.
 *
 * Input: writepeer -- File descriptor open for writing to peer connection
 *	  readpeer -- File descriptor open for reading from peer connection
 *	  wfun -- Write function. 
 *	  rfun -- Read function. 
 *
 * Return: 1, 0, or -1
 */
int 
sos_fairtoss(unsigned long *fairword,
	     int readpeer, 
	     int (*rfun)(int fd, caddr_t buf, __SIZE_TYPE__ len),
	     int writepeer,
	     int (*wfun)(int fd, caddr_t buf, __SIZE_TYPE__ len) )
{
  unsigned long word;		/* Fairly tossed word */
  long localrandom;		/* local random number for commital */
  long peerrandom;		/* peer random number for commital */
  long localauth;		/* Local authenticator */
  long peerauth;		/* Peer authenticator */
  MD5_CTX localcommit;		/* Local commit value */
  char  peercommit[16];		/* Peer commit buffer */
  MD5_CTX peercheck;		/* Peer check value */
  int count;			/* Dumb loop counter */
  int peerok = 1;		/* Boolean 1 if peer's commital and number OK*/
  int peersaysok = 1;		/* Boolean == 1 if peer says OK */

  /* Get random number */
  sos_get_rand((unsigned char *)&localrandom, sizeof(localrandom));
  sos_get_rand((unsigned char *)&localauth, sizeof(localauth));

  if ( sos_fairdebug )
    fprintf(stderr,"sos_fairtoss(): Local authenticator: %d -- Local random: %d\n", 
	    localauth,localrandom);
  /*
   * Send and receive `authenticators'
   */
  if ( sos_xdr_wencode (writepeer, wfun, "l", localauth ) <= 0 )
    {
      return -1;
    }
  if ( sos_xdr_wdecode (readpeer, rfun, "l", &peerauth) != 1 )
    {
      return -1;
    }

  if ( sos_fairdebug )
    fprintf(stderr,"sos_fairtoss(): Peer authenticator: %d\n", 
	    peerauth);

  /* Commit to my random number */
  MD5Init(&localcommit);
  MD5Update(&localcommit, (unsigned char *)&localrandom, sizeof(localrandom));
  MD5Update(&localcommit, (unsigned char *)&peerauth, sizeof(peerauth));
  MD5Final(&localcommit);
  if ( sos_xdr_wencode(writepeer, wfun, "cccccccccccccccc",
		      localcommit.digest[0],
		      localcommit.digest[1],
		      localcommit.digest[2],
		      localcommit.digest[3],
		      localcommit.digest[4],
		      localcommit.digest[5],
		      localcommit.digest[6],
		      localcommit.digest[7],
		      localcommit.digest[8],
		      localcommit.digest[9],
		      localcommit.digest[10],
		      localcommit.digest[11],
		      localcommit.digest[12],
		      localcommit.digest[13],
		      localcommit.digest[14],
		      localcommit.digest[15]) <= 0 )
    {
      return -1;
    }
  

  /* 
   * Get peer's committed number
   *
   * Well this certainly doesn't have to be put in a MD5_CTX but it makes
   * the algorithm seem more symetric.
   */
  if ( sos_xdr_wdecode(readpeer, rfun, "cccccccccccccccc",
		      &peercommit[0],
		      &peercommit[1],
		      &peercommit[2],
		      &peercommit[3],
		      &peercommit[4],
		      &peercommit[5],
		      &peercommit[6],
		      &peercommit[7],
		      &peercommit[8],
		      &peercommit[9],
		      &peercommit[10],
		      &peercommit[11],
		      &peercommit[12],
		      &peercommit[13],
		      &peercommit[14],
		      &peercommit[15]) != 16 )
    {
      return -1;
    }


  if ( sos_fairdebug )
    {
      int i;
      fprintf(stderr,"sos_fairtoss(): Peercommit blob: ");
      for (i=0; i < 15; i++ )
	fprintf(stderr,"%2x", peercommit[i]);
      fprintf(stderr,"\n");
    }

  /* Send over my random number */
  if ( sos_xdr_wencode (writepeer, wfun, "l", localrandom ) <= 0 )
    {
      return -1;
    }


  /* Read peer's random number */
  if ( sos_xdr_wdecode (readpeer, rfun, "l", &peerrandom ) != 1 )
    {
      return -1;
    }
  
  if  ( sos_fairdebug )
    fprintf(stderr,"sos_fairtoss(): Peer random: %d\n", peerrandom);

  /* Make sure peer did not lie during committal */
  MD5Init(&peercheck);
  MD5Update (&peercheck, (unsigned char *)&peerrandom, sizeof(peerrandom));
  MD5Update (&peercheck, (unsigned char *)&localauth, sizeof(localauth));
  MD5Final(&peercheck);

  if ( sos_fairdebug )
    {
      int i;
      fprintf(stderr,"sos_fairtoss(): Peercheck blob: ");
      for (i=0; i < 15; i++ )
	fprintf(stderr,"%2x", peercheck.digest[i]);
      fprintf(stderr,"\n");
    }
  
  for (count=0; count < 16; count++ )
    if ( (unsigned char)peercommit[count] != peercheck.digest[count] )
      {
	if  (sos_fairdebug)
	  fprintf(stderr,"sos_fairtoss(): Peercheck failed\n");
	peerok = 0;
	break;
      }

  if ( sos_fairdebug)
    fprintf(stderr,"sos_fairtoss(): Peer check passed\n");

  /* Tell peer if there were any errors */
  if ( sos_xdr_wencode(writepeer, wfun, "i", peerok ) <= 0 )
    {
      return -1;
    }

  /* See if peer thought there were any errors */
  if ( sos_xdr_wdecode(readpeer, rfun, "i", &peersaysok ) != 1 ) 
    {
      return -1;
    }

  if ( sos_fairdebug)
    {
      if (peersaysok )
	fprintf(stderr,"sos_fairtoss(): Peer says YES\n");
      else
	fprintf(stderr,"sos_fairtoss(): Peer says NO\n");
    }


  /* Return if either side thought there were errors */
  if ( !peerok || !peersaysok )
    {
      return -1;
    }

  /*
   * If we want to toss a handfull of coins, XOR the value
   * a(note that low order bit is same as what is returned)
   */
  if (fairword)
    {
      *fairword = localrandom ^ peerrandom;
    }

  /* Return one fair tossed bit */
  return  ((localrandom & 0x1) ^ (peerrandom & 0x1));
}



/*
 * Wrapper around sos_fairtoss(). Same input but acutally assigns identies 
 * to each peer so they can determin a winner and a loser.
 *
 * Returns: SOS_FAIRTOSS_WIN, SOS_FAIRTOSS_LOSE, or -1.
 */
int 
sos_fairtoss_win_lose(int readpeer, 
	     int (*rfun)(int fd, caddr_t buf, __SIZE_TYPE__ len),
	     int writepeer,
	     int (*wfun)(int fd, caddr_t buf, __SIZE_TYPE__ len) )
{
  int ident;			/* My identity.  */
  int ret_val;			/* Return value. */

  if ( (ident=sos_alice_bob(readpeer, rfun, writepeer, wfun)) < 0 )
    {
      return -1;
    }
    
  /* Toss a coin to determin win/loss status */
  switch (sos_fairtoss(NULL, readpeer, rfun, writepeer, wfun))
    {
    case 0:
      if ( ident == SOS_IDENT_BOB )
	ret_val = SOS_FAIRTOSS_WIN;
      else
	ret_val = SOS_FAIRTOSS_LOSE;
      break;

    case 1:
      if ( ident == SOS_IDENT_ALICE )
	ret_val = SOS_FAIRTOSS_WIN;
      else
	ret_val = SOS_FAIRTOSS_LOSE;
      break;

    default:
      ret_val = -1;
      break;
    }

  return (ret_val);
}

