git: 9front

Download patch

ref: c1326b376f0fadef81ab1cd3b7113af0644235d5
parent: 839b4044d3ba438219701ce029f5e662c27c9387
author: aiju <aiju@phicode.de>
date: Fri Jun 8 19:48:41 EDT 2012

more bitcoin

--- a/sys/src/cmd/auth/factotum/ecdsa.c
+++ b/sys/src/cmd/auth/factotum/ecdsa.c
@@ -15,7 +15,8 @@
 
 struct State {
 	ECpriv p;
-	char *sign;
+	uchar buf[100];
+	int n;
 };
 
 static int
@@ -36,11 +37,11 @@
 	memset(buf, 0, sizeof buf);
 	base58enc(keyenc, buf, 37);
 	if(keyenc[0] != 0x80)
-		return failure(fss, "invalid key '%s'", buf);
+		return RpcNeedkey;
 	sha2_256(keyenc, 33, hash, nil);
 	sha2_256(hash, 32, hash, nil);
 	if(memcmp(keyenc + 33, hash, 4) != 0)
-		return failure(fss, "checksum error");
+		return RpcNeedkey;
 	st = fss->ps;
 	st->p.d = betomp(keyenc + 1, 32, nil);
 	st->p.x = mpnew(0);
@@ -67,6 +68,7 @@
 		dom.h = uitomp(1, nil);
 		dom.G = strtoec(&dom, "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", nil, nil);
 	}
+	fss->ps = nil;
 	if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
 		return failure(fss, nil);
 	if(iscli==0)
@@ -91,7 +93,7 @@
 	}
 	if(key == nil || password == nil)
 		return RpcNeedkey;
-	fss->ps = malloc(sizeof(State));
+	fss->ps = emalloc(sizeof(State));
 	ret = decryptkey(fss, key, password);
 	if(ret != RpcOk)
 		return ret;
@@ -103,12 +105,11 @@
 	return RpcOk;
 }
 
-static char *
-derencode(mpint *r, mpint *s)
+static void
+derencode(mpint *r, mpint *s, uchar *buf, int *n)
 {
-	uchar buf[100], rk[33], sk[33];
-	char *str;
-	int rl, sl, i;
+	uchar rk[33], sk[33];
+	int rl, sl;
 	
 	mptobe(r, rk, 32, nil);
 	mptobe(s, sk, 32, nil);
@@ -132,10 +133,7 @@
 	buf[4 + rl] = 0x02;
 	buf[5 + rl] = sl;
 	memmove(buf + 6 + rl, sk, sl);
-	str = malloc(1024);
-	for(i = 0; i < 6 + rl + sl; i++)
-		sprint(str + 2 * i, "%.2x", buf[i]);
-	return str;
+	*n = 6 + rl + sl;
 }
 
 static int
@@ -142,7 +140,6 @@
 ecdsawrite(Fsstate *fss, void *va, uint n)
 {
 	State *st;
-	uchar hash[32];
 	mpint *r, *s;
 	
 	st = fss->ps;
@@ -150,12 +147,10 @@
 	default:
 		return phaseerror(fss, "write");
 	case CHaveKey:
-		sha2_256(va, n, hash, nil);
-		sha2_256(hash, 32, hash, nil);
 		r = mpnew(0);
 		s = mpnew(0);
-		ecdsasign(&dom, &st->p, hash, 32, r, s);
-		st->sign = derencode(r, s);
+		ecdsasign(&dom, &st->p, va, n, r, s);
+		derencode(r, s, st->buf, &st->n);
 		mpfree(r);
 		mpfree(s);
 		fss->phase = CHaveText;
@@ -166,11 +161,16 @@
 static int
 ecdsaread(Fsstate *fss, void *va, uint *n)
 {
+	State *st;
+	
+	st = fss->ps;
 	switch(fss->phase){
 	default:
 		return phaseerror(fss, "read");
 	case CHaveText:
-		*n = snprint(va, *n, ((State *)fss->ps)->sign);
+		if(*n > st->n)
+			*n = st->n;
+		memcpy(va, st->buf, *n);
 		fss->phase = Established;
 		return RpcOk;
 	}
@@ -182,13 +182,13 @@
 	State *st;
 	
 	st = fss->ps;
+	if(st == nil)
+		return;
 	if(st->p.x != nil){
 		mpfree(st->p.x);
 		mpfree(st->p.y);
 		mpfree(st->p.d);
 	}
-	if(st->sign != nil)
-		free(st->sign);
 	free(st);
 	fss->ps = nil;
 }
--- /dev/null
+++ b/sys/src/cmd/btc/dat.h
@@ -1,0 +1,28 @@
+typedef struct Aux Aux;
+typedef struct DirEntry DirEntry;
+
+enum
+{
+	TROOT,
+	TADDR,
+	TADDRSUB,
+	TADDRTX,
+	TADDRBALANCE,
+};
+
+struct DirEntry
+{
+	char *name;
+	Qid qid;
+	int par;
+	char *(*walk)(Fid *, char *, Qid *);
+	char *(*str)(DirEntry *, Aux *);
+	void (*write)(Req *);
+	int sub[20];
+};
+
+struct Aux
+{
+	char *addr;
+	char *str;
+};
--- /dev/null
+++ b/sys/src/cmd/btc/fs.c
@@ -1,0 +1,254 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "dat.h"
+
+Reqqueue *queue;
+
+static char *addrwalk(Fid *, char *, Qid *);
+char *balancestr(DirEntry *, Aux *);
+char *txstr(DirEntry *, Aux *);
+
+DirEntry entr[] = {
+	[TROOT] = {
+		.name = "",
+		.qid = {TROOT, 0, QTDIR},
+		.par = TROOT,
+		.sub = {TADDR},
+	},
+	[TADDR] = {
+		.name = "addr",
+		.qid = {TADDR, 0, QTDIR},
+		.walk = addrwalk,
+		.par = TROOT,
+	},
+	[TADDRSUB] = {
+		.qid = {TADDRSUB, 0, QTDIR},
+		.par = TADDR,
+		.sub = {TADDRBALANCE, TADDRTX},
+	},
+	[TADDRBALANCE] = {
+		.name = "balance",
+		.qid = {TADDRBALANCE, 0, 0},
+		.par = TADDRSUB,
+		.str = balancestr,
+	},
+	[TADDRTX] = {
+		.name = "tx",
+		.qid = {TADDRTX, 0, 0},
+		.par = TADDRSUB,
+		.str = txstr,
+	},
+};
+
+void
+pfree(void **v)
+{
+	if(*v != nil)
+		free(*v);
+	*v = nil;
+}
+
+static void
+btcattach(Req *req)
+{
+	req->ofcall.qid = (Qid){0, 0, QTDIR};
+	req->fid->qid = req->ofcall.qid;
+	req->fid->aux = emalloc9p(sizeof(Aux));
+	respond(req, nil);
+}
+
+static char *
+addrwalk(Fid *fid, char *name, Qid *qid)
+{
+	Aux *a;
+
+	a = fid->aux;
+	pfree(&a->addr);
+	a->addr = strdup(name);
+	fid->qid = entr[TADDRSUB].qid;
+	*qid = fid->qid;
+	return nil;
+}
+
+static char *
+btcwalk1(Fid *fid, char *name, Qid *qid)
+{
+	DirEntry *d;
+	int *s;
+	
+	d = entr + (int)fid->qid.path;
+	if(strcmp(name, "..") == 0){
+		fid->qid = entr[d->par].qid;
+		*qid = fid->qid;
+		return nil;
+	}
+	if(d->walk)
+		return d->walk(fid, name, qid);
+	for(s = d->sub; *s; s++)
+		if(strcmp(entr[*s].name, name) == 0){
+			fid->qid = entr[*s].qid;
+			*qid = fid->qid;
+			return nil;
+		}
+	return "directory entry not found";
+}
+
+static char *
+btcclone(Fid *oldfid, Fid *newfid)
+{
+	Aux *a, *b;
+	
+	a = oldfid->aux;
+	b = emalloc9p(sizeof(Aux));
+	memcpy(b, a, sizeof(Aux));
+	if(b->addr)
+		b->addr = strdup(b->addr);
+	newfid->aux = b;
+	return nil;
+}
+
+static void
+btcopenread(Req *req)
+{
+	DirEntry *d;
+	Aux *a;
+	
+	d = entr + (int)req->fid->qid.path;
+	a = req->fid->aux;
+	a->str = d->str(d, a);
+	if(a->str == nil)
+		responderror(req);
+	else
+		respond(req, nil);
+}
+
+static void
+btcopen(Req *req)
+{
+	DirEntry *d;
+	
+	d = entr + (int)req->fid->qid.path;
+	switch(req->ifcall.mode & 3){
+	case OREAD:
+		if(d->str == nil && (req->fid->qid.type & QTDIR) == 0)
+			goto noperm;
+		reqqueuepush(queue, req, btcopenread);
+		return;
+	case OWRITE:
+		if(d->write == nil)
+			goto noperm;
+		break;
+	case ORDWR:
+		if(d->str == nil || d->write == nil)
+			goto noperm;
+		break;
+	case OEXEC:
+		goto noperm;
+	}
+	respond(req, nil);
+	return;
+noperm:
+	respond(req, "permission denied");
+}
+
+static void
+fill(Dir *de, int t, Aux *a)
+{
+	DirEntry *d;
+
+	d = entr + t;
+	de->qid = d->qid;
+	if(d->qid.type & QTDIR)
+		de->mode = 0555;
+	else
+		de->mode = (d->str ? 0444 : 0) | (d->write ? 0222 : 0);
+	if(d->name != nil)
+		de->name = strdup(d->name);
+	else if(a->addr != nil)
+		de->name = strdup(a->addr);
+	else
+		de->name = strdup("");
+	de->uid = strdup("satoshi");
+	de->gid = strdup("satoshi");
+	de->muid = strdup("satoshi");
+	de->atime = de->mtime = time(0);
+}
+
+static void
+btcstat(Req *req)
+{
+	fill(&req->d, (int)req->fid->qid.path, req->fid->aux);
+	respond(req, nil);
+}
+
+static int
+btcdirgen(int n, Dir *dir, void *aux)
+{
+	DirEntry *d;
+	
+	d = entr + (int)((Req*)aux)->fid->qid.path;
+	if(n >= nelem(d->sub) || d->sub[n] == 0)
+		return -1;
+	fill(dir, d->sub[n], ((Req*)aux)->fid->aux);
+	return 0;
+}
+
+static void
+btcread(Req *req)
+{
+	DirEntry *d;
+	Aux *a;
+	
+	d = entr + (int)req->fid->qid.path;
+	a = req->fid->aux;
+	if(req->fid->qid.type & QTDIR){
+		dirread9p(req, btcdirgen, req);
+		respond(req, nil);
+	}else if(d->str && a->str){
+		readstr(req, a->str);
+		respond(req, nil);
+	}else
+		respond(req, "permission denied");	
+}
+
+static void
+btcflush(Req *req)
+{
+	reqqueueflush(queue, req->oldreq);
+	respond(req, nil);
+}
+
+static void
+btcdestroyfid(Fid *fid)
+{
+	Aux *a;
+	
+	a = fid->aux;
+	if(a != nil){
+		pfree(&a->addr);
+		pfree(&a->str);
+		free(a);
+	}
+	fid->aux = nil;
+}
+
+Srv btcsrv = {
+	.attach = btcattach,
+	.walk1 = btcwalk1,
+	.clone = btcclone,
+	.stat = btcstat,
+	.open = btcopen,
+	.read = btcread,
+	.flush = btcflush,
+	.destroyfid = btcdestroyfid,
+};
+
+void
+gofs(void)
+{
+	queue = reqqueuecreate();
+	threadpostmountsrv(&btcsrv, nil, "/mnt/btc", 0);
+}
--- /dev/null
+++ b/sys/src/cmd/btc/httpfs.c
@@ -1,0 +1,129 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <String.h>
+#include "dat.h"
+#include "json.h"
+
+void gofs(void);
+
+char *
+graburl(char *url)
+{
+	int fd, fd2, n, rc, size;
+	char buf[2048];
+	char *res;
+	
+	fd = open("/mnt/web/clone", ORDWR);
+	if(fd < 0)
+		return nil;
+	if(read(fd, buf, 512) < 0){
+		close(fd);
+		return nil;
+	}
+	n = atoi(buf);
+	sprint(buf, "url %s", url);
+	if(write(fd, buf, strlen(buf)) < 0){
+		close(fd);
+		return nil;
+	}
+	sprint(buf, "/mnt/web/%d/body", n);
+	fd2 = open(buf, OREAD);
+	if(fd2 < 0){
+		close(fd);
+		return nil;
+	}
+	size = 0;
+	res = nil;
+	while((rc = readn(fd2, buf, sizeof buf)) > 0){
+		res = realloc(res, size + rc + 1);
+		memcpy(res + size, buf, rc);
+		size += rc;
+		res[size] = 0;
+	}
+	close(fd);
+	close(fd2);
+	if(rc < 0){
+		free(res);
+		return nil;
+	}
+	return res;
+}
+
+static void
+parsetx(String *str, JSON *j, JSON *l)
+{
+	JSONEl *e;
+	JSON *k;
+	char buf[512];
+
+	for(e = j->first; e != nil; e = e->next){
+		k = jsonbyname(e->val, "prev_out");
+		sprint(buf, "%s %lld ", jsonstr(jsonbyname(k, "addr")), (vlong)jsonbyname(k, "value")->n);
+		s_append(str, buf);
+	}
+	s_append(str, "| ");
+	for(e = l->first; e != nil; e = e->next){
+		sprint(buf, "%s %lld ", jsonstr(jsonbyname(e->val, "addr")), (vlong)jsonbyname(e->val, "value")->n);
+		s_append(str, buf);
+	}
+}
+
+char *
+balancestr(DirEntry *, Aux *a)
+{
+	char *s;
+	char buf[512];
+
+	sprint(buf, "http://blockchain.info/q/addressbalance/%s", a->addr);
+	s = graburl(buf);
+	if(s == nil)
+		return nil;
+	return s;
+}
+
+char *
+txstr(DirEntry *, Aux *a)
+{
+	char *s;
+	char buf[512];
+	JSON *j, *k;
+	JSONEl *e;
+	String *str;
+
+	sprint(buf, "http://blockchain.info/rawaddr/%s", a->addr);
+	s = graburl(buf);
+	if(s == nil)
+		return nil;
+	j = jsonparse(s);
+	free(s);
+	if(j == nil)
+		return nil;
+	str = s_new();
+	k = jsonbyname(j, "txs");
+	if(k == nil)
+		goto err;
+	for(e = k->first; e != nil; e = e->next){
+		sprint(buf, "%d %s %d ", (int)(jsonbyname(e->val, "time")->n), jsonstr(jsonbyname(e->val, "hash")), (int)(jsonbyname(e->val, "block_height")->n));
+		s_append(str, buf);
+		parsetx(str, jsonbyname(e->val, "inputs"), jsonbyname(e->val, "out"));
+		s_putc(str, '\n');
+	}
+	s_terminate(str);
+	s = str->base;
+	free(str);
+	jsonfree(j);
+	return s;
+err:
+	s_free(str);
+	jsonfree(j);
+	return nil;
+}
+
+void
+threadmain()
+{
+	gofs();
+}
--- /dev/null
+++ b/sys/src/cmd/btc/json.c
@@ -1,0 +1,326 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include "json.h"
+
+typedef struct Lex Lex;
+
+enum {
+	TEOF,
+	TSTRING = (1<<(8*sizeof(Rune)))+1,
+	TNUM,
+	TNULL,
+	TFALSE,
+	TTRUE,
+};
+
+struct Lex
+{
+	char *s;
+	int t;
+	double n;
+	char buf[4096];
+	Rune peeked;
+	jmp_buf jmp;
+	int canjmp;
+};
+
+static Rune
+getch(Lex *l)
+{
+	Rune r;
+
+	if(l->peeked){
+		r = l->peeked;
+		l->peeked = 0;
+		return r;
+	}
+	l->s += chartorune(&r, l->s);
+	return r;
+}
+
+static Rune
+peekch(Lex *l)
+{
+	if(!l->peeked)
+		l->peeked = getch(l);
+	return l->peeked;
+}
+
+static int
+lex(Lex *l)
+{
+	Rune r;
+	char *t;
+
+	for(;;){
+		r = peekch(l);
+		if(r != 0x20 && r != 0x09 && r != 0x0A && r != 0x0D)
+			break;
+		getch(l);
+	}
+	r = getch(l);
+	if(r == ']' && l->canjmp)
+		longjmp(l->jmp, 1);
+	l->canjmp = 0;
+	if(r == 0 || r == '{' || r == '[' || r == ']' || r == '}' || r == ':' || r == ','){
+		l->t = r;
+		return 0;
+	}
+	if(r >= 0x80 || isalpha(r)){
+		t = l->buf;
+		for(;;){
+			t += runetochar(t, &r);
+			if(t >= l->buf + sizeof(l->buf)){
+				werrstr("json: literal too long");
+				return -1;
+			}
+			r = peekch(l);
+			if(r < 0x80 && !isalpha(r))
+				break;
+			getch(l);
+		}
+		*t = 0;
+		if(strcmp(l->buf, "true") == 0)
+			l->t = TTRUE;
+		else if(strcmp(l->buf, "false") == 0)
+			l->t = TFALSE;
+		else if(strcmp(l->buf, "null") == 0)
+			l->t = TNULL;
+		else{
+			werrstr("json: invalid literal");
+			return -1;
+		}
+		return 0;
+	}
+	if(isdigit(r) || r == '-'){
+		l->n = strtod(l->s-1, &l->s);
+		l->t = TNUM;
+		return 0;
+	}
+	if(r == '"'){
+		t = l->buf;
+		for(;;){
+			r = getch(l);
+			if(r == '"')
+				break;
+			if(r < ' '){
+				werrstr("json: invalid char in string %x", r);
+				return -1;
+			}
+			if(r == '\\'){
+				r = getch(l);
+				switch(r){
+				case 'n':
+					r = '\n';
+					break;
+				case 'r':
+					r = '\r';
+					break;
+				case 't':
+					r = '\t';
+					break;
+				case 'f':
+					r = '\f';
+					break;
+				case 'b':
+					r = '\b';
+					break;
+				case '"': case '/': case '\\':
+					break;
+				default:
+					werrstr("json: invalid escape sequence \\%C", r);
+					return -1;
+				}
+			}
+			t += runetochar(t, &r);
+			if(t >= l->buf + sizeof(l->buf)){
+				werrstr("json: string too long");
+				return -1;
+			}
+		}
+		*t = 0;
+		l->t = TSTRING;
+		return 0;
+	}
+	werrstr("json: invalid char %C", peekch(l));
+	return -1;
+}
+
+static JSON*
+jsonobj(Lex *l)
+{
+	JSON *j;
+	JSONEl *e;
+	JSONEl **ln;
+	int obj;
+	
+	j = mallocz(sizeof(*j), 1);
+	if(j == nil)
+		return nil;
+	if(lex(l) < 0){
+error:
+		free(j);
+		return nil;
+	}
+	switch(l->t){
+	case TEOF:
+		werrstr("json: unexpected eof");
+		goto error;
+	case TNULL:
+		j->t = JSONNull;
+		break;
+	case TTRUE:
+		j->t = JSONBool;
+		j->n = 1;
+		break;
+	case TFALSE:
+		j->t = JSONBool;
+		j->n = 0;
+		break;
+	case TSTRING:
+		j->t = JSONString;
+		j->s = strdup(l->buf);
+		if(j->s == nil)
+			goto error;
+		break;
+	case TNUM:
+		j->t = JSONNumber;
+		j->n = l->n;
+		break;
+	case '{':
+	case '[':
+		obj = l->t == '{';
+		ln = &j->first;
+		e = nil;
+		if(obj){
+			j->t = JSONObject;
+			if(lex(l) < 0)
+				goto abort;
+			if(l->t == '}')
+				return j;
+			goto firstobj;
+		}else{
+			j->t = JSONArray;
+			l->canjmp = 1;
+			if(setjmp(l->jmp) > 0){
+				free(e);
+				return j;
+			}
+		}
+		for(;;){
+			if(obj){
+				if(lex(l) < 0)
+					goto abort;
+			firstobj:
+				if(l->t != TSTRING){
+					werrstr("json: syntax error, not string");
+					goto abort;
+				}
+				e = mallocz(sizeof(*e), 1);
+				if(e == nil)
+					goto abort;
+				e->name = strdup(l->buf);
+				if(e->name == nil || lex(l) < 0){
+					free(e);
+					goto abort;
+				}
+				if(l->t != ':'){
+					werrstr("json: syntax error, not colon");
+					free(e);
+					goto abort;
+				}
+			}else{
+				e = mallocz(sizeof(*e), 1);
+				if(e == nil)
+					goto abort;
+			}
+			e->val = jsonobj(l);
+			if(e->val == nil){
+				free(e);
+				goto abort;
+			}
+			*ln = e;
+			ln = &e->next;
+			if(lex(l) < 0)
+				goto abort;
+			if(l->t == (obj ? '}' : ']'))
+				break;
+			if(l->t != ','){
+				werrstr("json: syntax error, neither comma nor ending paren");
+				goto abort;
+			}
+		}
+		break;
+	abort:
+		jsonfree(j);
+		return nil;
+	case ']': case '}': case ',': case ':':
+		werrstr("json: unexpected %C", l->t);
+		goto error;
+	default:
+		werrstr("json: the front fell off");
+		goto error;
+	}
+	return j;
+}
+
+JSON*
+jsonparse(char *s)
+{
+	Lex l;
+
+	memset(&l, 0, sizeof(l));
+	l.s = s;
+	return jsonobj(&l);
+}
+
+void
+jsonfree(JSON *j)
+{
+	JSONEl *e, *f;
+
+	switch(j->t){
+	case JSONString:
+		if(j->s)
+			free(j->s);
+		break;
+	case JSONArray: case JSONObject:
+		for(e = j->first; e != nil; e = f){
+			if(e->name)
+				free(e->name);
+			jsonfree(e->val);
+			f = e->next;
+			free(e);
+		}
+	}
+	free(j);
+}
+
+JSON *
+jsonbyname(JSON *j, char *n)
+{
+	JSONEl *e;
+	
+	if(j->t != JSONObject){
+		werrstr("not an object");
+		return nil;
+	}
+	for(e = j->first; e != nil; e = e->next)
+		if(strcmp(e->name, n) == 0)
+			return e->val;
+	werrstr("key '%s' not found", n);
+	return nil;
+}
+
+char *
+jsonstr(JSON *j)
+{
+	if(j == nil)
+		return nil;
+	if(j->t != JSONString){
+		werrstr("not a string");
+		return nil;
+	}
+	return j->s;
+}
--- /dev/null
+++ b/sys/src/cmd/btc/json.h
@@ -1,0 +1,32 @@
+typedef struct JSONEl JSONEl;
+typedef struct JSON JSON;
+
+enum {
+	JSONNull,
+	JSONBool,
+	JSONNumber,
+	JSONString,
+	JSONArray,
+	JSONObject,
+};
+
+struct JSONEl {
+	char *name;
+	JSON *val;
+	JSONEl *next;
+};
+
+struct JSON
+{
+	int t;
+	union {
+		double n;
+		char *s;
+		JSONEl *first;
+	};
+};
+
+JSON*	jsonparse(char *);
+void	jsonfree(JSON *);
+JSON*	jsonbyname(JSON *, char *);
+char*	jsonstr(JSON *);
--- /dev/null
+++ b/sys/src/cmd/btc/mkfile
@@ -1,0 +1,17 @@
+</$objtype/mkfile
+
+TARG=httpfs sign
+
+BIN=/$objtype/bin/btc
+
+OFILES=\
+	json.$O\
+
+HFILES=\
+	dat.h\
+
+default:V: all
+
+</sys/src/cmd/mkmany
+
+$O.httpfs: fs.$O
--- /dev/null
+++ b/sys/src/cmd/btc/sign.c
@@ -1,0 +1,412 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <mp.h>
+#include <ctype.h>
+#include <libsec.h>
+#include <auth.h>
+
+typedef struct TxIn TxIn;
+typedef struct TxOut TxOut;
+typedef struct Sig Sig;
+typedef struct Word Word;
+
+int afd;
+AuthRpc *rpc;
+
+struct Word {
+	char *name;
+	int val;
+} words[];
+
+struct Sig {
+	char *priv;
+	int loc;
+	Sig *n;
+};
+
+struct TxIn {
+	uchar prev[36];
+	int scoldlen, sclen;
+	uchar scold[10000];
+	uchar sc[10000];
+	Sig *sig;
+};
+
+struct TxOut {
+	uvlong val;
+	int sclen;
+	uchar sc[10000];
+};
+
+Biobuf *bp;
+
+int nin, nout;
+TxIn *in[0xFD];
+TxOut *out[0xFD];
+uchar buf[65536];
+
+void
+varenc(uint i, uchar **b)
+{
+	if(i < 0xfd)
+		*(*b)++ = i;
+	else if(i <= 0xffff){
+		*(*b)++ = 0xfd;
+		*(*b)++ = i;
+		*(*b)++ = i >> 8;
+	}else{
+		*(*b)++ = 0xfe;
+		*(*b)++ = i;
+		*(*b)++ = i >> 8;
+		*(*b)++ = i >> 16;
+		*(*b)++ = i >> 24;
+	}
+}
+
+
+int
+hexdec(char *c, uchar *u, int n)
+{
+	int i, v;
+	char *b;
+	static char *hexdig = "0123456789abcdef";
+	
+	memset(u, 0, n);
+	for(i = 0; i < 2 * n; i++){
+		b = strchr(hexdig, c[i]);
+		if(b == nil)
+			return -1;
+		v = b - hexdig;
+		if(i & 1)
+			u[i>>1] |= v;
+		else
+			u[i>>1] |= v << 4;
+	}
+	return 0;
+}
+
+void
+pushdat(uchar **scr, uchar *d, int n)
+{
+	if(n <= 0x4b)
+		*(*scr)++ = n;
+	else if(n <= 0xff){
+		*(*scr)++ = 0x4c;
+		*(*scr)++ = n;
+	}else if(n <= 0xffff){
+		*(*scr)++ = 0x4d;
+		*(*scr)++ = n;
+		*(*scr)++ = n >> 8;
+	}else{
+		*(*scr)++ = 0x4e;
+		*(*scr)++ = n;
+		*(*scr)++ = n >> 8;
+		*(*scr)++ = n >> 16;
+		*(*scr)++ = n >> 24;
+	}
+	memcpy(*scr, d, n);
+	*scr += n;
+}
+
+void
+doscript(char **args, int n, uchar *script, int *len, TxIn *ti)
+{
+	int i, k;
+	Word *w;
+	uchar *scr;
+	uchar *b;
+	char *s;
+	Sig *si;
+	
+	scr = script;
+	for(i = 0; i < n; i++){
+		for(w = words; w->name; w++)
+			if(strcmp(w->name, args[i]) == 0){
+				*scr++ = w->val;
+				goto next;
+			}
+		if(strncmp(args[i], "sig(", 4) == 0){
+			if(in == nil)
+				sysfatal("sig in out script");
+			si = malloc(sizeof(*si));
+			args[i][strlen(args[i])-1] = 0;
+			si->priv = strdup(args[i] + 4);
+			si->loc = scr - script;
+			si->n = ti->sig;
+			ti->sig = si;
+			continue;
+		}
+		if(strncmp(args[i], "h160(", 5) == 0){
+			b = mallocz(25, 1);
+			args[i][strlen(args[i])-1] = 0;
+			base58dec(args[i] + 5, b, 25);
+			pushdat(&scr, b+1, 20);
+			free(b);
+			continue;
+		}
+		if(args[i][0] == '('){
+			k = strtol(args[i] + 1, &s, 0);
+			b = mallocz(k, 1);
+			base58dec(s+1, b, k);
+			pushdat(&scr, b, k);
+			free(b);
+			continue;
+		}
+		sysfatal("invalid word %s", args[i]);
+next:	;
+	}
+	*len = scr - script;
+}
+
+int
+serialize(uchar *buf, int sig)
+{
+	uchar *s;
+	TxIn *ti;
+	TxOut *to;
+	int i;
+	
+	s = buf;
+	*s++ = 1;
+	*s++ = 0;
+	*s++ = 0;
+	*s++ = 0;
+	*s++ = nin;
+	for(i = 0; i < nin; i++){
+		ti = in[i];
+		memcpy(s, ti->prev, 36);
+		s += 36;
+		if(sig == -1){
+			varenc(ti->sclen, &s);
+			memcpy(s, ti->sc, ti->sclen);
+			s += ti->sclen;
+		}
+		if(sig == i){
+			memcpy(s, ti->scold, ti->scoldlen);
+			s += ti->scoldlen;
+		}
+		*s++ = 0xff;
+		*s++ = 0xff;
+		*s++ = 0xff;
+		*s++ = 0xff;
+	}
+	*s++ = nout;
+	for(i = 0; i < nout; i++){
+		to = out[i];
+		*s++ = to->val;
+		*s++ = to->val >> 8;
+		*s++ = to->val >> 16;
+		*s++ = to->val >> 24;
+		*s++ = to->val >> 32;
+		*s++ = to->val >> 40;
+		*s++ = to->val >> 48;
+		*s++ = to->val >> 56;
+		varenc(to->sclen, &s);
+		memcpy(s, to->sc, to->sclen);
+		s += to->sclen;
+	}
+	*s++ = 0;
+	*s++ = 0;
+	*s++ = 0;
+	*s++ = 0;
+	if(sig != -1){
+		*s++ = 0;
+		*s++ = 0;
+		*s++ = 0;
+		*s++ = 0;
+	}
+	return s - buf;
+}
+
+void
+sign(uchar *hash, char *priv, uchar *tar, uint *n)
+{
+	char buf[512];
+	int rc;
+
+again:
+	sprint(buf, "proto=ecdsa role=client key=%s", priv);
+	rc = auth_rpc(rpc, "start", buf, strlen(buf));
+	if(rc == ARneedkey || rc == ARbadkey){
+		rerrstr(buf, sizeof buf);
+		if(auth_getkey(buf + 8) < 0)
+			sysfatal("auth_getkey: %r");
+		goto again;
+	}
+	if(rc != ARok)
+		sysfatal("factotum start: %r");
+	if(auth_rpc(rpc, "write", hash, 32) != ARok)
+		sysfatal("factotum write: %r");
+	if(auth_rpc(rpc, "read", "", 0) != ARok)
+		sysfatal("factotum read: %r");
+	memcpy(tar, rpc->arg, *n = rpc->narg);
+}
+
+void
+main()
+{
+	char *line;
+	int linenum;
+	uint i, n;
+	char *args[256];
+	TxOut *to;
+	TxIn *ti;
+	Sig *si;
+	uchar hash[32];
+	uchar sig[100];
+
+	afd = open("/mnt/factotum/rpc", ORDWR);
+	if(afd < 0)
+		sysfatal("open: %r");
+	rpc = auth_allocrpc(afd);
+
+	bp = malloc(sizeof(*bp));
+	Binit(bp, 0, OREAD);
+	linenum = 0;
+	for(;;){
+		line = Brdstr(bp, '\n', 1);
+		linenum++;
+		if(strcmp(line, "-") == 0)
+			break;
+		if(++nin >= 0xFD)
+			sysfatal("too many inputs");
+		ti = malloc(sizeof(*ti));
+		in[nin-1] = ti;
+		if(tokenize(line, args, nelem(args)) != 2)
+			sysfatal("line %d: invalid data", linenum);
+		hexdec(args[0], ti->prev, 32);
+		i = atoi(args[1]);
+		ti->prev[32] = i;
+		ti->prev[33] = i >> 8;
+		ti->prev[34] = i >> 16;
+		ti->prev[35] = i >> 24;
+		line = Brdstr(bp, '\n', 1);
+		linenum++;
+		i = tokenize(line, args, nelem(args));
+		doscript(args, i, ti->scold, &ti->scoldlen, nil);
+		line = Brdstr(bp, '\n', 1);
+		linenum++;
+		i = tokenize(line, args, nelem(args));
+		doscript(args, i, ti->sc, &ti->sclen, ti);
+	}
+	for(;;){
+		line = Brdstr(bp, '\n', 1);
+		if(line == nil)
+			break;
+		linenum++;
+		if(++nout >= 0xFD)
+			sysfatal("too many outputs");
+		to = malloc(sizeof(*to));
+		out[nout-1] = to;
+		to->val = atoll(line);
+		line = Brdstr(bp, '\n', 1);
+		linenum++;
+		i = tokenize(line, args, nelem(args));
+		doscript(args, i, to->sc, &to->sclen, nil);
+	}
+	for(i = 0; i < nin; i++){
+		ti = in[i];
+		if(ti->sig == nil)
+			continue;
+		n = serialize(buf, i);
+		sha2_256(buf, n, hash, nil);
+		sha2_256(hash, 32, hash, nil);
+		for(si = ti->sig; si != nil; si = si->n){
+			sign(hash, ti->sig->priv, sig + 1, &n);
+			print("%d\n", n);
+			sig[0] = n++;
+			memmove(ti->sc + si->loc + n, ti->sc + si->loc, ti->sclen - si->loc);
+			memmove(ti->sc + si->loc, sig, n);
+			ti->sclen += n;
+		}
+	}
+	n = serialize(buf, -1);
+	for(i = 0; i < n; i++){
+		print("%.2x", buf[i]);
+		if((i%4)==3)
+			print(" ");
+		if((i%32)==31)
+			print("\n");
+	}
+	if((i%16)!=0)
+		print("\n");
+}
+
+Word words[] = {
+	{"nop", 97},
+	{"if", 99},
+	{"notif", 100},
+	{"else", 103},
+	{"endif", 104},
+	{"verify", 105},
+	{"return", 106},
+	{"toaltstack", 107},
+	{"fromaltstack", 108},
+	{"2drop", 109},
+	{"2dup", 110},
+	{"3dup", 111},
+	{"2over", 112},
+	{"2rot", 113},
+	{"2swap", 114},
+	{"ifdup", 115},
+	{"depth", 116},
+	{"drop", 117},
+	{"dup", 118},
+	{"nip", 119},
+	{"over", 120},
+	{"pick", 121},
+	{"roll", 122},
+	{"rot", 123},
+	{"swap", 124},
+	{"tuck", 125},
+	{"cat", 126},
+	{"substr", 127},
+	{"left", 128},
+	{"right", 129},
+	{"size", 130},
+	{"invert", 131},
+	{"and", 132},
+	{"or", 133},
+	{"xor", 134},
+	{"equal", 135},
+	{"equalverify", 136},
+	{"1add", 139},
+	{"1sub", 140},
+	{"2mul", 141},
+	{"2div", 142},
+	{"negate", 143},
+	{"abs", 144},
+	{"not", 145},
+	{"0notequal", 146},
+	{"add", 147},
+	{"sub", 148},
+	{"mul", 149},
+	{"div", 150},
+	{"mod", 151},
+	{"lshift", 152},
+	{"rshift", 153},
+	{"booland", 154},
+	{"boolor", 155},
+	{"numequal", 156},
+	{"numequalverify", 157},
+	{"numnotequal", 158},
+	{"lessthan", 159},
+	{"greaterthan", 160},
+	{"lessthanorequal", 161},
+	{"greaterthanorequal", 162},
+	{"min", 163},
+	{"max", 164},
+	{"within", 165},
+	{"ripemd160", 166},
+	{"sha1", 167},
+	{"sha256", 168},
+	{"hash160", 169},
+	{"hash256", 170},
+	{"codeseparator", 171},
+	{"checksig", 172},
+	{"checksigverify", 173},
+	{"checkmultisig", 174},
+	{"checkmultisigverify", 175},
+	{nil, 0},
+};
--