
Hi coderpunks,

   When Ian posted his visprint code a few weeks ago, I suggested 
snowflakes as a possible alternative. Here's the code.

   The design was motivated by a number of factors. First and foremost, 
each hash should correspond to a unique snowflake. If any two snowflakes 
can be distinguished on careful inspection, then the cryptographic 
soundness is equivalent to that of the hash. Ian's visprint makes a 
stronger assumption about the hash, because it is possible to construct 
two hash values that lead to very similar visprints (for example, any 
permutation of the equations).

   Thus, the rest of the design is based on the principle of making it as
easy as possible to distinguish any two snowflakes. The most efficient
method, from an information theoretic point of view, would be to just lay
the bits down on a grid. However, this would just look like random bits to
most people. So I added two additional constraints. First, the snowflake
has D6 symmetry (the same as real snowflakes). The second constraint is
that the snowflake is connected - no isolated black triangles. This
encourages the bits to fall into patterns that may be more recognizable to
the eye. 

   Use the code the same way as Ian's visprints, i.e.

      echo string | md5sum | flake | xv -

   The code has two known problems: first, it's a bit slow (written for
simplicity rather than efficiency), and second, it's possible for the
rendered snowflake to exceed the square, thus losing bits at the outer
edge. It's easy enough to fix both problems, but I figured on keeping
things as simple as possible for the first cut. 

   I'm very interested to hear what people think about these snowflakes, 
and especially how they compare to visprints. The visprints may be more 
aesthetically pleasing (especially in color ;-), but I think there is a 
case to be made that snowflakes are more useful in cryptography.

Raph

/* Snowflake code by Raph Levien <raph@c2.org> 
 *
 * Accept a 32 hex digit hash on input, output a PBM of a snowflake
 * unique to that hash value.
 */

#include <math.h>
#include <stdio.h>

typedef char color;
#define gray  0
#define white 1
#define black 2
/* ... should probably be an enum, but I can't remember the syntax */

#define maxa 256
#define maxb 128
color flake[maxa][maxb];

#define maxx 512
#define maxy 512

#define s 7.0

#define s34 0.8660254

void printflake( void )
{
  int    i,
         j;
  int    a,
         b,
         a1;
  double c,
         d;

  printf( "P1\n%d %d\n",maxx,maxy );

  for ( j = 0; j != maxy; j++ )
  {
    for ( i = 0; i != maxx; i++ )
    {
      c  = ( i - ( maxx/2 )     ) / ( s * s34 );
      d  = (     ( maxy/2 ) - j ) /   s;
      d -= c * 0.5;
      a  = 2 * floor( d );
      b  =     floor( c );
      c  = c - floor( c );
      d  = d - floor( d );
      if ( d + c >= 1 )
        a++;

      /* symmetries here - just like folding paper */

      if ( b < 0 )
      {
        a = 1 + a + 2 * b;
	b = -b - 1;
      }
      if ( a < 0 )
	if ( a & 1 )
	{
	  b += ( a+1 ) / 2;
	  a  = -1 - a;
	}
	else
	{
	  b += a / 2;
	  a = -1 - a;
	}
      if ( b < 0 )
      {
        a = 1 + a + 2 * b;
	b = -b - 1;
      }
      if ( 2 * b > a )
	if ( a & 1 )
	{
	  a1 = a;
	  a = 1 + 2 * b;
	  b = ( a1 - 1 ) / 2;
	}
	else
	{
	  a1 = a;
	  a = 2 * b;
	  b = a1/2;
	}

      if ( a < 0 || b < 0 || a >= maxa || b >= maxb )
        printf( "0" );
      else if ( flake[a][b] == black )
        printf( "1" );
      else if ( flake[a][b] == white )
        printf( "0" );
      else
        printf( "%d",( i ^ j ) & 1 );
    }
    printf( "\n" );
  }
}

unsigned char hash[32];
         int  hashptr;

void readhash( void )
{
  int j;

  fread( hash,32,1,stdin );
  for ( j = 0; j < 32; ++j )
  {
    unsigned char c = hash[j];

    if ( c >= '0' && c <= '9' )
      hash[j] = c -   '0';
    else if ( c >= 'a' && c <= 'f' )
      hash[j] = c - ( 'a' - 10 );
    else if ( c >= 'A' && c <= 'F' )
      hash[j] = c - ( 'A' - 10 );
    else
      hash[j] = 0;
  }
  hashptr = 0;
}

int hasheof( void )
{
  return ( hashptr == 128 );
}

int hashbit( void )
{
  int bit;
  
  bit = ( ( hash[ hashptr >> 2 ] & ( 8 >> ( hashptr & 3 ) ) ) != 0 );
  hashptr++;

return bit;
}

int rn;

void seedrand( void )
{
  rn = 0x1234;
}

int nextrand( void )
{
  rn = ( ( rn <<  1 )   & 0x7fff |
       ( ( rn >> 13 ) ^
         ( rn >> 14 ) ) &      1 );

  return rn;
}

typedef struct coord
{
  int a;
  int b;
} coord;

coord frontier[1024];
int frontier_n;

/* Add coord to frontier if not already present. */

void add_to_frontier( int a,int b )
{
  int i;

  for ( i = 0; i < frontier_n; i++ )
    if ( frontier[i].a == a &&
         frontier[i].b == b )
      return;
  frontier[i].a = a;
  frontier[i].b = b;
  frontier_n++;
}

/* Delete coord from frontier, if present. */

void del_from_frontier( int a,int b )
{
  int i;

  for ( i = 0; i < frontier_n; i++ )
    if ( frontier[i].a == a &&
         frontier[i].b == b )
      frontier[i] = frontier[--frontier_n];
}

/* Add to frontier, but only if gray and within the 30 degree slice. */

void add_if_gray( int a,int b )
{
  if ( a < 0 || b < 0 || 2 * b > a )
    return;
  if ( flake[a][b] == gray )
    add_to_frontier( a,b );
}

/* color a gray pixel, based on the value of the given bit */

void fill( int a,int b,int bit )
{
  del_from_frontier( a,b );
  if ( bit )
  {
    flake[a][b] = black;
    add_if_gray( a + 1,b );
    add_if_gray( a - 1,b );
    if ( a & 1 )
      add_if_gray( a - 1,b + 1 );
    else
      add_if_gray( a + 1,b - 1 );
  }
  else
    flake[a][b] = white;
}

int main( int argc,char* argv[] )
{
  int i,
      j;
  int a,
      b;

  for ( a = 0; a != maxa; a++ )
    for ( b = 0; b != maxb; b++ )
      flake[a][b] = gray;

  flake[0][0] = black;

  frontier[0].a = 1;
  frontier[0].b = 0;

  frontier_n = 1;

  readhash ();
  seedrand ();

  j = 0;
  while ( !hasheof() )
  {
    /* choose a random frontier value */
    i = nextrand() % frontier_n;

    /* fprintf (stderr, "(%d, %d)\n", frontier[i].a, frontier[i].b); */
    if ( frontier[i].b && ( ( j < 16 ) || ( j % 3 ) ) )
      fill( frontier[i].a,frontier[i].b,hashbit() );
    else
      fill( frontier[i].a,frontier[i].b,1 );
    j++;
  }

  for ( a = 0; a != maxa; a++ )
    for ( b = 0; b != maxb; b++ )
      if ( flake[a][b] == gray )
        flake[a][b] = white;

#if 0 /* turn on if you want to see the frontier */
  for ( i = 0; i < frontier_n; i++ )
    flake[ frontier[i].a ][ frontier[i].b ] = gray;
#endif

  printflake ();

  return 0;
}
