+static int donequery(struct query *q, struct server *s,
+ const void *k, size_t sz)
+{
+ octet *tt;
+ ghash *h = 0;
+ int diffp;
+
+ /* If we have a hash, check that the fragment matches it. */
+ if (s && s->h) {
+ h = GH_INIT(s->k.hc);
+ GH_HASH(h, k, sz);
+ tt = GH_DONE(h, 0);
+ diffp = memcmp(tt, s->h, h->ops->c->hashsz);
+ GH_DESTROY(h);
+ if (diffp) {
+ moan("response from %s:%d doesn't match hash",
+ inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port));
+ return (-1);
+ }
+ }
+
+ /* Stash a copy of the key fragment for later. */
+ q->k = xmalloc(sz);
+ memcpy(q->k, k, sz);
+ q->sz = sz; nq--;
+
+ /* All good. */
+ return (0);
+}
+
+static int setup_v0(struct query *q, struct server *s)
+{
+ /* Choose an ephemeral private key u. Let x be our private key. We
+ * compute U = u P and transmit this.
+ */
+ s->u = mprand_range(MP_NEW, s->k.g->r, &rand_global, 0);
+ s->U = G_CREATE(s->k.g);
+ G_EXP(s->k.g, s->U, s->k.g->g, s->u);
+ D( debug_mp("u", s->u); debug_ge("U", s->k.g, s->U); )
+
+ return (0);
+}
+
+static int retransmit_v0(struct query *q, struct server *s, buf *bout)
+{
+ buf_putstrz(bout, q->tag);
+ G_TOBUF(s->k.g, bout, s->U);
+ return (0);
+}
+
+static int receive_v0(struct query *q, struct server *s, buf *bin, buf *bout)
+{
+ ge *R, *V = 0, *W = 0, *Y = 0, *Z = 0;
+ octet *kk, *t, *tt;
+ gcipher *c = 0;
+ gmac *m = 0;
+ ghash *h = 0;
+ size_t n, ksz;
+ octet *p;
+ int rc = -1;
+
+ R = G_CREATE(s->k.g);
+ V = G_CREATE(s->k.g); W = G_CREATE(s->k.g);
+ Y = G_CREATE(s->k.g); Z = G_CREATE(s->k.g);
+ if (G_FROMBUF(s->k.g, bin, V)) {
+ moan("invalid Diffie--Hellman vector from %s:%d",
+ inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port));
+ goto done;
+ }
+ if (G_FROMBUF(s->k.g, bin, W)) {
+ moan("invalid clue vector from %s:%d",
+ inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port));
+ goto done;
+ }
+ D( debug_ge("V", s->k.g, V); debug_ge("W", s->k.g, W); )
+
+ /* We have V and W from the server; determine Y = u V, R = W + Y and
+ * Z = x R, and then derive the symmetric keys.
+ */
+ G_EXP(s->k.g, Y, V, s->u);
+ G_MUL(s->k.g, R, W, Y);
+ G_EXP(s->k.g, Z, R, s->k.x);
+ D( debug_ge("R", s->k.g, R);
+ debug_ge("Y", s->k.g, Y);
+ debug_ge("Z", s->k.g, Z); )
+ derive(&s->k, R, Z, "cipher", s->k.cc->name, s->k.cc->keysz, &kk, &ksz);
+ c = GC_INIT(s->k.cc, kk, ksz);
+ derive(&s->k, R, Z, "mac", s->k.cc->name, s->k.cc->keysz, &kk, &ksz);
+ m = GM_KEY(s->k.mc, kk, ksz);
+
+ /* Find where the MAC tag is. */
+ if ((t = buf_get(bin, s->k.tagsz)) == 0) {
+ moan("missing tag from %s:%d",
+ inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port));
+ goto done;
+ }
+
+ /* Check the integrity of the ciphertext against the tag. */
+ p = BCUR(bin); n = BLEFT(bin);
+ h = GM_INIT(m);
+ GH_HASH(h, p, n);
+ tt = GH_DONE(h, 0);
+ if (!ct_memeq(t, tt, s->k.tagsz)) {
+ moan("incorrect tag from %s:%d",
+ inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port));
+ goto done;
+ }
+
+ /* Decrypt the result and declare this server done. */
+ GC_DECRYPT(c, p, p, n);
+ rc = donequery(q, s, p, n);
+
+done:
+ /* Clear up and go home. */
+ if (R) G_DESTROY(s->k.g, R);
+ if (V) G_DESTROY(s->k.g, V);
+ if (W) G_DESTROY(s->k.g, W);
+ if (Y) G_DESTROY(s->k.g, Y);
+ if (Z) G_DESTROY(s->k.g, Z);
+ if (c) GC_DESTROY(c);
+ if (m) GM_DESTROY(m);
+ if (h) GH_DESTROY(h);
+ return (rc);
+}
+
+static const struct client_protocol prototab[] = {
+ { "v0", setup_v0, receive_v0, retransmit_v0 },
+ { 0 }
+};