Signing as one member of a set of keys

Anonymous User anonymous at remailer.havenco.com
Thu Aug 8 20:52:56 PDT 2002


This program can be used by anonymous contributors to release partial
information about their identity - they can show that they are someone
from a list of PGP key holders, without revealing which member of the
list they are.  Maybe it can help in the recent controvery over the
identity of anonymous posters.  It's a fairly low-level program that
should be wrapped in a nicer UI.  I'll send a couple of perl scripts
later that make it easier to use.

===

/* Implementation of ring signatures from
 * http://theory.lcs.mit.edu/~rivest/RivestShamirTauman-HowToLeakASecret.pdf
 * by Rivest, Shamir and Tauman
 *
 * This creates and verifies a signature such that it was produced from
 * one of a fixed set of RSA keys.
 *
 * It requires the openssl library to build, which is available from
 * www.openssl.org.
 *
 * This program takes a PGP public key ring file which holds a set of
 * old-style RSA public keys.  It creates and verifies signatures which
 * are such that they were issued by one of the keys in that file, but
 * there is no way to tell which one did it.  In this way the signer can
 * leak partial information about his identity - that he is one member
 * of a selected set of signers.
 *
 * To sign, the signer must also give a PGP secret key file which holds
 * one key (actually the program ignores any keys past the first).
 * That key should be the secret part of one of the keys in the public
 * key file.  Also, it should be set to have no passphrase - it is too
 * complicated for a simple program like this to try to untangle PGP
 * passphrases.  So set your key to have no passphrase, then run this
 * program, then set it back.
 *
 * The program outputs the signature in the form of a list of big numbers,
 * base64 encoded.  There will be as many numbers as there were keys in
 * the public key file.  So signatures are quite large in this scheme,
 * proportional to the number of keys in the group that the signature
 * comes from.  They are also proportional to the largest key in the
 * group, so all else being equal try not to include really big keys if
 * you care about size.
 *
 * The signature is not appended to the text being signed, it is just
 * output separately.  The signer can combine them manually with some kind
 * of cut marks so that the recipient can separate out the signature from
 * the file being signed.  Some perl scripts that do this are supposed
 * to be distributed with the program.  (That is what is used to verify
 * the signature in this file itself.)
 *
 * The recipient must use the same PGP public key file that the signer
 * used.  So that may have to be sent along as well.  He runs the program
 * with the PGP file and the file to be verified, and sends the signature
 * data into stdin (using the "<" character).  The program will print
 * whether the signature is good or not.
 *
 * This program was written in just a couple of evenings so it is
 * a little rough.  This is version 0.9 or so - at least it works.
 * It has only been tested on my Linux system.
 *
 * The program is released into the public domain.  See the end for
 * authorship information.
 */


#include <stdio.h>
#include <stdlib.h>
#include "openssl/bn.h"
#include "openssl/rsa.h"
#include "openssl/sha.h"
#include "openssl/evp.h"

/* Cipher block size; we use Blowfish */
#define CIPHERBLOCK 8

typedef unsigned char uchar;

enum {
	ERR_OK = 0,
	ERR_BADPKT=-100,
	ERR_EOF,
	ERR_SECNOTFOUND,
	ERR_BADSIG,
};


/************************** PGP FILE PARSING ***************************/

/* Read the N and E values from a PGP public key packet */
int
rdpgppub( BIGNUM *n, BIGNUM *e, unsigned *bytesused, uchar *buf, unsigned len )
{
	int nbits, nlen, ebits, elen;
	unsigned o=2;

	if (len < 10)
		return ERR_BADPKT;
	if (buf[0] == 4)					/* Check version 4, 3, or 2 */
		o = 0;
	else if (buf[0] != 2 && buf[0] != 3) /* V2&3 have 2 extra bytes */
		return ERR_BADPKT;
	if (buf[5+o] != 1)					/* Check alg - 1 is RSA */
		return ERR_BADPKT;
	nbits = (buf[6+o] << 8) | buf[7+o];	/* Read modulus */
	nlen = (nbits + 7)/8;
	if (len < 10+o+nlen)
		return ERR_BADPKT;
	BN_bin2bn(buf+o+8, nlen, n);
	ebits = (buf[8+o+nlen] << 8) | buf[9+o+nlen];	/* Read exponent */
	elen = (ebits + 7)/8;
	if (len < 10+o+nlen+elen)
		return ERR_BADPKT;
	BN_bin2bn(buf+10+o+nlen, elen, e);
	if (bytesused)
		*bytesused = 10+o+nlen+elen;
	return ERR_OK;
}

/* Read the N, E, D values from a PGP secret key packet with no passphrase */
int
rdpgpsec( BIGNUM *n, BIGNUM *e, BIGNUM *d, uchar *buf, unsigned len )
{
	int err;
	int nbits, nlen, ebits, elen, dbits, dlen;
	unsigned o;

	if ((err = rdpgppub(n, e, &o, buf, len)) < 0)
		return err;
	if (len < o+3)
		return ERR_BADPKT;
	if (buf[o] != 0)					/* Check that packet is unencrypted */
		return ERR_BADPKT;
	dbits = (buf[o+1] << 8) | buf[o+2];	/* Read private exponent */
	dlen = (dbits + 7)/8;
	if (len < o+3+dlen)
		return ERR_BADPKT;
	BN_bin2bn(buf+o+3, dlen, d);
	return ERR_OK;
}

/* Read the next PGP packet into malloc'd memory */
int
getpgppkt( uchar **pbuf, unsigned *plen, int *type, FILE *f )
{
	int c, c1;
	uchar *buf;
	unsigned len;
	int llen;
	uchar lbuf[4];

	c = fgetc(f);
	if (c == EOF)
		return ERR_EOF;
	if ((c & 0xc0) == 0x80) {
		/* Old PGP packet */
		*type = (c >> 2) & 0xf;
		llen = c & 0x3;
		if (llen++==3)
			return ERR_BADPKT;
rdllen:
		if (llen==3)
			llen=4;
		memset (lbuf, 0, 4);
		if (fread(lbuf+4-llen, 1, llen, f) != llen)
			return ERR_BADPKT;
		len = (lbuf[0]<<24) | (lbuf[1]<<16) | (lbuf[2]<<8) | lbuf[3];
	} else if ((c & 0xc0) == 0xc0) {
		/* New PGP packet */
		*type = c & 0x3f;
		c = fgetc(f);
		if (c == EOF)
			return ERR_BADPKT;
		if (c == 0xff) {
			llen = 4;
			goto rdllen;
		}
		if (c >= 0xe0)
			return ERR_BADPKT;
		if (c >= 0xc0) {
			/* Two byte length */
			c1 = fgetc(f);
			if (c1 == EOF)
				return ERR_BADPKT;
			len = (c<<8) + c1 - 0xc000 + 0xc0;
		} else {
			/* One byte length */
			len = c;
		}
	} else {
		/* Non PGP packet */
		return ERR_BADPKT;
	}
	buf = malloc(len);
	if (buf==NULL)
		return ERR_BADPKT;
	if (fread(buf, 1, len, f) != len)
		return ERR_BADPKT;
	*pbuf = buf;
	*plen = len;
	return ERR_OK;
}


/* Read a PGP key ring and create arrays of all the n, e values */
int
rdpgppubring(BIGNUM ***n_arr_ptr, BIGNUM ***e_arr_ptr, int *nkeys_ptr, FILE *f)
{
	int err = ERR_OK;
	uchar *buf;
	unsigned len;
	int type;
	int nkeys = 0;
	BIGNUM **n_arr = NULL;
	BIGNUM **e_arr = NULL;
	BIGNUM *n, *e;

	n_arr = malloc(sizeof(BIGNUM *));
	e_arr = malloc(sizeof(BIGNUM *));

	while (err == ERR_OK)
	{
		err = getpgppkt (&buf, &len, &type, f);
		if (err != ERR_OK)
			break;
		if (type == 6)			/* public key packet */
		{
			n = BN_new();
			e = BN_new();
			err = rdpgppub(n, e, NULL, buf, len);
			if (err != ERR_OK)
				break;
			++nkeys;
			n_arr = realloc(n_arr, sizeof(BIGNUM *) * nkeys);
			e_arr = realloc(e_arr, sizeof(BIGNUM *) * nkeys);
			n_arr[nkeys-1] = n;
			e_arr[nkeys-1] = e;
		}
		free (buf);
	}
	if (err != ERR_EOF)
		return err;
	err = ERR_OK;

	*n_arr_ptr = n_arr;
	*e_arr_ptr = e_arr;
	*nkeys_ptr = nkeys;

	return err;
}

/* Read a PGP secret key file and find the corresponding value in the
 * array of public key values.  Return the d value and the index in the
 * public key array.
 */
int
rdpgpsecring(BIGNUM *d, int *secindex, BIGNUM **n_arr, BIGNUM **e_arr,
	int nkeys, FILE *f)
{
	int err = ERR_OK;
	BIGNUM *n, *e;
	uchar *buf;
	unsigned len;
	int i;
	int type;

	err = getpgppkt (&buf, &len, &type, f);
	if (err != ERR_OK)
		return err;
	if (type != 5)					/* Secret key packet */
		return ERR_BADPKT;
	n = BN_new();
	e = BN_new();
	err = rdpgpsec(n, e, d, buf, len);
	if (err != ERR_OK)
		return err;
	for (i=0; i<nkeys; i++)			/* Find corresponding public key */
	{
		if (BN_cmp (n, n_arr[i]) == 0  &&  BN_cmp (e, e_arr[i]) == 0)
			break;
	}
	if (i == nkeys)
		return ERR_SECNOTFOUND;
	*secindex = i;
	return ERR_OK;
}



/************************** UTILITY FUNCTIONS ***************************/


/* Sort the n and e arrays by increasing n */
int
sortkeys (BIGNUM **n, BIGNUM **e, int nkeys)
{
	int i, j;
	BIGNUM *t;

	/* Do a bubble sort as the number of keys is not usually very large */
	for (i=0; i<nkeys-1; i++)
	{
		for (j=i+1; j<nkeys; j++)
		{
			if (BN_cmp( n[i], n[j] ) > 0)
			{
				t = n[i]; n[i] = n[j]; n[j] = t;
				t = e[i]; e[i] = e[j]; e[j] = t;
			}
		}
	}
	return ERR_OK;
}


/* Hash the file.  Should have opened it in ASCII mode so that we have
 * standard Unix line endings (newlines only).
 */
int
hashfile( uchar md[5], FILE *f )
{
	char buf[1024];
	SHA_CTX sha;

	SHA_Init(&sha);

	for ( ; ; )
	{
		if (fgets (buf, sizeof(buf), f) == NULL)
			break;
		SHA_Update(&sha, buf, strlen(buf));
	}
	SHA_Final (md, &sha);
	return ERR_OK;
}


/* Do an RSA enc/dec, where the input/output value may be larger
 * than n.  In fact, val should be much larger than n or this may fail
 * to keep val within the desired range.
 * To decrypt pass d in place of e.
 */
int
rsaencdec( BIGNUM *rslt, BIGNUM *val, BIGNUM *n, BIGNUM *e, BN_CTX *ctx )
{
	BIGNUM *rem = BN_new();
	BIGNUM *newrem = BN_new();

	BN_div (NULL, rem, val, n, ctx);
	BN_mod_exp (newrem, rem, e, n, ctx);
	BN_sub (rslt, val, rem);
	BN_add (rslt, rslt, newrem);
	BN_free (rem);
	BN_free (newrem);
}



/************************** SIG CREATE/VERIFY ***************************/


/* Verify a signature.  sigs holds the per-key signature values,
 * hashval is the hash of the data which was signed, valbytes is the
 * length of the values we work with, several bytes longer than the longest
 * modulus, and n_arr and e_arr are the RSA public key values, of whic
 * there are nkeys of them.  (There are also nkeys of sigs.)
 */
int
checksig( BIGNUM **sigs, uchar *hashval, int hashvalbytes, int valbytes,
	BIGNUM **n_arr, BIGNUM **e_arr, int nkeys, BN_CTX *ctx )
{
	BIGNUM *val = BN_new();
	uchar ivec[CIPHERBLOCK];
	BF_KEY bf;
	uchar *sigbuf;
	uchar *xorbuf;
	int vallen;
	int i, j;

	/* Key cipher with the hash value */
	BF_set_key (&bf, hashvalbytes, hashval);

	/* Init xorbuf to 0's */
	xorbuf = malloc(valbytes);
	memset (xorbuf, 0, valbytes);

	sigbuf = malloc(valbytes);

	for (i=0; i<nkeys; i++)
	{
		rsaencdec (val, sigs[i], n_arr[i], e_arr[i], ctx);
		vallen = BN_num_bytes(val);
		if (vallen > valbytes) {
			fprintf (stderr, "Bad signature created by signer\n");
			exit (3);
		}
		/* XOR into buffer */
		memset (sigbuf, 0, valbytes);
		BN_bn2bin (val, sigbuf+valbytes-vallen);
		for (j=0; j<valbytes; j++)
			xorbuf[j] ^= sigbuf[j];
		memset (ivec, 0, sizeof(ivec));
		BF_cbc_encrypt (xorbuf, xorbuf, valbytes, &bf, ivec, BF_ENCRYPT);
	}
	BN_free (val);

	/* xorbuf should be all 0's for the sig to verify */
	for (j=0; j<valbytes; j++)
		if (xorbuf[j] != 0)
			break;

	free (xorbuf);
	free (sigbuf);

	if (j < valbytes)
		return ERR_BADSIG;

	return ERR_OK;
}


/* Create a signature.  psigarray is the returned array of bignums,
 * of which there are nkeys.  hashval is the hash of the data being
 * signed.  d is the RSA private key value for the key at position
 * secindex.  valbytes is the size of the values we are working with,
 * several bytes greater than the length of the longest modulus.
 * And n_arr and e_arr are the RSA public key values, of which there
 * are nkeys.
 */
int
createsig( BIGNUM ***psigarray, uchar *hashval, int hashvalbytes, BIGNUM *d,
	int secindex, int valbytes, BIGNUM **n_arr, BIGNUM **e_arr, int nkeys,
	BN_CTX *ctx )
{
	BIGNUM *val = BN_new();
	BIGNUM *bigval = BN_new();
	BIGNUM **sigs;
	BF_KEY bf;
	uchar ivec[CIPHERBLOCK];
	uchar *sigbuf;
	uchar *lxorbuf;
	uchar *rxorbuf;
	int vallen;
	int i, j;

	/* Key cipher with the hash value */
	BF_set_key (&bf, hashvalbytes, hashval);

	BN_lshift (bigval, BN_value_one(), valbytes*8);

	/* Init two xorbufs we will make meet in the middle */
	rxorbuf = malloc(valbytes);
	memset (rxorbuf, 0, valbytes);
	lxorbuf = malloc(valbytes);
	memset (lxorbuf, 0, valbytes);
	memset (ivec, 0, sizeof(ivec));

	/* Start the MITM process on the left xorbuf */
	BF_cbc_encrypt (lxorbuf, lxorbuf, valbytes, &bf, ivec, BF_DECRYPT);

	sigbuf = malloc(valbytes);
	sigs = (BIGNUM **)malloc(nkeys * sizeof(BIGNUM *));

	for (i=0; i<secindex; i++)
	{
		/* For other keys do a fake value */
		sigs[i] = BN_new();
		BN_rand_range (sigs[i], bigval);
		rsaencdec (val, sigs[i], n_arr[i], e_arr[i], ctx);
		/* Infinitisimal chance that vallen > valbytes with random val */
		vallen = BN_num_bytes(val);
		/* XOR into right xor buf and encrypt */
		memset (sigbuf, 0, valbytes);
		BN_bn2bin (val, sigbuf+valbytes-vallen);
		for (j=0; j<valbytes; j++)
			rxorbuf[j] ^= sigbuf[j];
		memset (ivec, 0, sizeof(ivec));
		BF_cbc_encrypt (rxorbuf, rxorbuf, valbytes, &bf, ivec, BF_ENCRYPT);
	}

	for (i=nkeys-1; i>secindex; i--)
	{
		/* For other keys do a fake value */
		sigs[i] = BN_new();
		BN_rand_range (sigs[i], bigval);
		rsaencdec (val, sigs[i], n_arr[i], e_arr[i], ctx);
		/* Infinitisimal chance that vallen > valbytes with random val */
		vallen = BN_num_bytes(val);
		/* XOR into left xor buf and decrypt */
		memset (sigbuf, 0, valbytes);
		BN_bn2bin (val, sigbuf+valbytes-vallen);
		for (j=0; j<valbytes; j++)
			lxorbuf[j] ^= sigbuf[j];
		memset (ivec, 0, sizeof(ivec));
		BF_cbc_encrypt (lxorbuf, lxorbuf, valbytes, &bf, ivec, BF_DECRYPT);
	}

	/* XOR the two buffers to get the value we must RSA sign */
	for (j=0; j<valbytes; j++)
		lxorbuf[j] ^= rxorbuf[j];

	/* Get val as the value we need to do the RSA signature to */
	sigs[secindex] = BN_new();
	BN_bin2bn (lxorbuf, valbytes, val);
	rsaencdec (sigs[secindex], val, n_arr[secindex], d, ctx);
	BN_free (val);
	BN_free (bigval);
	free (sigbuf);
	free (lxorbuf);
	free (rxorbuf);
	*psigarray = sigs;
	return ERR_OK;
}



/*************************** USER INTERFACE ****************************/

static char *prog;

void
userr()
{
	fprintf (stderr, "Usage:\n"
		"To sign (signature data to stdout):\n"
		"   %s -s textfile pubkeyfile seckeyfile\n"
		"To verify a signature (signature data from stdin):\n"
		"   %s -v textfile pubkeyfile\n", prog, prog);
	exit (1);
}

int
main (int ac, char **av)
{
	FILE *fpub, *fsec, *fdata;
	BN_CTX *ctx;
	BIGNUM *d;
	BIGNUM **n_arr;
	BIGNUM **e_arr;
	BIGNUM **sigarray;
	uchar md[SHA_DIGEST_LENGTH];
	int secindex;
	int valbytes;
	int nkeys;
	int i;
	int dosign;
	int err;

	ctx = BN_CTX_new();
	d = BN_new();

	prog = av[0];
	if (ac < 2)
		userr();
	if (strcmp( av[1], "-s" ) == 0)
		dosign = 1;
	else if (strcmp( av[1], "-v" ) == 0)
		dosign = 0;
	else
		userr();

	if (ac < (dosign ? 5 : 4) )
		userr();
	fdata = fopen (av[2], "r");	/* text mode */
	if (fdata==NULL)
		userr();
	fpub = fopen (av[3], "rb");
	if (fpub==NULL)
		userr();
	if (dosign)
	{
		fsec = fopen (av[4], "rb");
		if (fsec==NULL)
			userr();
	}
	err = rdpgppubring(&n_arr, &e_arr, &nkeys, fpub);
	if (err != ERR_OK)
		goto error;
	fclose (fpub);
	sortkeys (n_arr, e_arr, nkeys);
	if (dosign)
	{
		err = rdpgpsecring(d, &secindex, n_arr, e_arr, nkeys, fsec);
		if (err != ERR_OK)
			goto error;
		fclose (fsec);
	}

	/* For our values we use 2^128 times the largest n; mult of CIPHERBLOCK */
	valbytes = BN_num_bytes (n_arr[nkeys-1]) + 16;
	valbytes = ((valbytes+CIPHERBLOCK-1)/CIPHERBLOCK)*CIPHERBLOCK;

	/* Hash the file to sign/verify */
	err = hashfile (md, fdata);

	if (dosign)
	{
		BIO *bio, *b64;
		uchar *sigbuf;

		err = createsig (&sigarray, md, sizeof(md), d, secindex, valbytes,
					n_arr, e_arr, nkeys, ctx);
		if (err != ERR_OK)
			goto error;

		b64 = BIO_new(BIO_f_base64());
		bio = BIO_new_fp(stdout, BIO_NOCLOSE);
		bio = BIO_push(b64, bio);
		sigbuf = (uchar *)malloc(valbytes);
		for (i=0; i<nkeys; i++)
		{
			memset (sigbuf, 0, valbytes);
			BN_bn2bin(sigarray[i], sigbuf+valbytes-BN_num_bytes(sigarray[i]));
			BIO_write(bio, sigbuf, valbytes);
		}
		BIO_flush(bio);
		BIO_free_all(bio);
		free (sigbuf);
	} else {
		/* Read sig data from stdin */
		BIO *bio, *b64;
		uchar *sigbuf;
		int inlen;

		b64 = BIO_new(BIO_f_base64());
		bio = BIO_new_fp(stdin, BIO_NOCLOSE);
		bio = BIO_push(b64, bio);

		sigarray = (BIGNUM **)malloc(nkeys * sizeof(BIGNUM *));
		sigbuf = (uchar *)malloc(valbytes);
		for (i=0; i<nkeys; i++)
		{
			if ((inlen = BIO_read(bio, sigbuf, valbytes)) < valbytes)
				userr();
			sigarray[i] = BN_bin2bn (sigbuf, valbytes, NULL);
		}
		free (sigbuf);
		BIO_free_all(bio);

		err = checksig (sigarray, md, sizeof(md), valbytes, n_arr, e_arr,
			nkeys, ctx);
		if (err == ERR_OK)
		{
			printf ("Good signature\n");
		} else if (err = ERR_BADSIG) {
			printf ("ERROR: Bad signature\n");
			exit (2);
		} else {
			goto error;
		}
	}

error:
	if (err != ERR_OK)
	{
		fprintf (stderr, "Error %d\n", err);
		exit (1);
	}
	exit (0);
}

/*
  Signature block!  Who wrote this program?  Someone from the keys below.

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: 2.6.2

mQCNAisbwocAAAED/jitkUnDwPdZk1Rdr0YNT7h7OHd9zbJbTZEQgdEs5LxQ1h4t
S+fCbgM6zxfMvCWqWPendS3ikEStq1PMGQKtt0b0/4BafB6dO1ljSBp+UWkw86FL
BmCcQkED0CmQOFAEy2p2SURvdO4tExerfUgf+oYQg99v03mvYlVkk3dax7hlAAUR
tBBsb2tpQG9ic2N1cmEuY29ttChMYW5jZSBNLiBDb3R0cmVsbCA8bG9raUBuYXRl
bHkudWNzZC5lZHU+tDhMYW5jZSBNLiBDb3R0cmVsbCAoYXQgaG9tZSkgPGxjb3R0
cmVsbEBwb3BtYWlsLnVjc2QuZWR1PpkAjQMxav4LAAABBADJkZ8ZP83waDvuf/Lx
4UB7KFSkm9IKKht2FpW5ztHG22EmCuarCYhSB+R7dzIAN4Wx9UcdS3x724ozuoNm
bQboiSsSKO41efGcn5zbzFKlLU+Im5utXJ1QtYlbjx6PjnfZiajVAIpgwiekwP7I
PxL6V0i4jYlln3dGUYkxK0j29QAFEbQjSWFuIEdvbGRiZXJnIDxpYW5nQGNzLmJl
cmtlbGV5LmVkdT60IUlhbiBHb2xkYmVyZyA8aWFuQGN5cGhlcnB1bmtzLmNhPrQk
SWFuIEdvbGRiZXJnIDxpYW5AemVyb2tub3dsZWRnZS5jb20+mQCNAi0xHTIAAAEE
AKeIU9S010e1AxYy2R379ptHunqM0kRMgWnOwfCnVets8jThr7B87pzFNVj6kBs8
F9TKQdk62JR5Kiq2rVODFSLmN2JThnhfDu/tAYAz8fJsWkxGn5IhcjxkQpfb2LDs
4EBJgWhI9HxIfCvhSkFdrFe9JBfm0KKB5sGoFIWXVYodAAUTtCFQcjBkdWN0IEN5
cGhlciA8YWx0LnNlY3VyaXR5LnBncD6ZAI0CL7SNAQAAAQQAruhfQV42dxod9rW/
5XtIxS+ICbyoHyitYbTO+Nrhm1KsdWD88zCmlSHji3kRED2WW1EF0e7fT5hKHxrV
1i3Vc4PzeeSfOP2uG2kUA+rLLCUtWE1Vjc9UvIxtQvDbtoQAZz5K0WqsOhRDT6P1
BT9gf8jJU32sFonKGwgMRScZrzUABRG0KEJlbiBMYXVyaWUgPGJlbkBnb256by5i
ZW4uYWxncm91cC5jby51az60HEJlbiBMYXVyaWUgPGJlbkBjcnlwdGl4Lm9yZz60
HkJlbiBMYXVyaWUgPGJlbkBhbGdyb3VwLmNvLnVrPpkAjQIqrDZMAAABBADCljOd
puRFotKHCuLtXAtjz8h0+rWr5PTwt0weVwWA3oH93bJXUaZ4yY9a/mjtPBRTvkCx
CPcLQar39Tzz+Yi1+7A9riFZSR9eDD7clbY5vWSm/CTLQlu+NOOQYLRwQvckN1e7
1zVeYzOnRNqHJx+ACP6QRaxiyTyoEwOvWCFMNwAFEbQmSGFsIEZpbm5leSA8NzQw
NzYuMTA0MUBjb21wdXNlcnZlLmNvbT6ZAI0DMxu7SwAAAQQApJs0Xcl4sIUngK32
4f1sQ9H7GGctVkjxynj5b3or0rhiI2u/OowQCj7VwnCdoM/Itqz6HUiVRO8D+eHz
XQRpAXKoeEgxzMN45iNvgneU0/VsN49bCDCM7JPHau2cMm4xWhQUA6s59CxhR+Y2
cMWxLpwcXDJ//+YoNZtIfcgAK9EABRG0HkVyaWMgWW91bmcgPGVheUBjcnlwdHNv
ZnQuY29tPpkAjQIty/OuAAABBACkXvI/UFKi+Dw0ErJYkykNMk5HSy/ARQZu3NN0
QvTpeTz/So/0bZ7HcRTE7Vu8JDME6WNYxdtx0fseeC6zqYvIoOhxJol1VVrjQckY
0jIM/I765zDNP7SDgjwIDTMYgDVFqq3rTxFgmRPbC28pZCa6zneeGSslwU8Pw+wC
+7uKsQAFEbQhQ29saW4gUGx1bWIgPGNvbGluQG55eC5jcy5kdS5lZHU+
=/CJG
-----END PGP PUBLIC KEY BLOCK-----

  ++multisig v1.0
pEsBwalpBRxWyJR8tkYm6qR27UW9IT6Vg8SlOHIsEkk04RJvoSy0cy4ISFCq6vDX
5ub6c+MYi/UoyR6tI7oqpMu1abcXWm2DkfDiCsD6jQddVkiiYdG7Bih8JWdWmp5l
AgzqUoz14671/ezmWSrPNsTNKV96+ZLEanZsqfkpQcnZpLkWVpJzQFe0VgDQ64b2
+e2efrbknLFq0FTdX7Sh3qzAfzNYYgADmeOxDoTm9sb6T0fULf1P7mjiN2LZXuEW
m/8QvksaQi9KGa/0xN2m0heNtS1cfsTa+NJz8XYyG/tnMy7+mvI3c3lrnz+6Dpyp
pbNwaX+12VcqtfNec9faoq8RJgFxmSO/ZfMOGM8cFBQ75ZOaoBJP5ObHZ/63FFh5
Wh5GzwJjQs0vLwpM3iF6G+IixEqAQYisUdCopP1wXCLgltDM6l7jRlXxNDj0AXQ1
eQJolo32vemcy8Z8GAn5tpQHmJwpdzZpboWRQY53pD4mVnEMN4GBC1mhbbI2z+Oh
lPglqmmy3p4D+psNU1rlNv6yH/L0PgcuW7taVpbopjl4HLuJdWcKHJlXish3D/jb
eoQ856fYFZ/omGiO9x1D0BsnGFLZVWob4OIZRzO/Pc49VIhFy5NsV2zuozStId89
[...]
 */





More information about the cypherpunks-legacy mailing list