[Pkg-privacy-commits] [libotr] 15/225: All-new implementation of version 2 AKE.

Ximin Luo infinity0 at moszumanska.debian.org
Sat Aug 22 12:44:45 UTC 2015


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch master
in repository libotr.

commit e1b401341e7b3ecad7a01f8c1822eb9c4cd05249
Author: cypherpunk <cypherpunk>
Date:   Sun Oct 16 15:51:11 2005 +0000

    All-new implementation of version 2 AKE.
---
 Protocol => Protocol-v1.txt    |    0
 Protocol-v2.html               | 1187 +++++++++++++++++++++++++++++++++
 README                         |    2 +-
 src/Makefile.am                |    4 +-
 src/auth.c                     | 1427 ++++++++++++++++++++++++++++++++++++++++
 src/auth.h                     |  160 +++++
 src/b64.c                      |   63 ++
 src/b64.h                      |   15 +
 src/context.c                  |   59 +-
 src/context.h                  |   53 +-
 src/dh.c                       |  232 ++++++-
 src/dh.h                       |   40 +-
 src/message.c                  | 1131 +++++++++++++++++--------------
 src/message.h                  |   34 +-
 src/{version.h => privkey-t.h} |   22 +-
 src/privkey.c                  |  191 +++++-
 src/privkey.h                  |   32 +-
 src/proto.c                    |  610 ++++-------------
 src/proto.h                    |  128 ++--
 src/serial.h                   |   85 +++
 src/tests.c                    |  199 ++++++
 src/userstate.h                |    4 +-
 src/version.h                  |    2 +-
 toolkit/otr_modify.c           |    2 +-
 toolkit/otr_parse.c            |   72 +-
 toolkit/otr_readforge.c        |    2 +-
 toolkit/otr_toolkit.1          |    4 +-
 toolkit/parse.c                |  186 ++++++
 toolkit/parse.h                |   54 ++
 29 files changed, 4808 insertions(+), 1192 deletions(-)

diff --git a/Protocol b/Protocol-v1.txt
similarity index 100%
rename from Protocol
rename to Protocol-v1.txt
diff --git a/Protocol-v2.html b/Protocol-v2.html
new file mode 100644
index 0000000..5ce2d1a
--- /dev/null
+++ b/Protocol-v2.html
@@ -0,0 +1,1187 @@
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html><head>
+<title>Off-the-Record Messaging Protocol version 2 - DRAFT</title>
+<style type="text/css">
+    body { background: white; color: black }
+    h1 { text-align: center }
+    dd ul.note { list-style: none }
+    dl.doublespace dd { margin-bottom: 2ex }
+</style>
+</head><body>
+<h1>Off-the-Record Messaging Protocol version 2</h1>
+<h1>DRAFT 2005101601</h1>
+<p>This document describes version 2 of the Off-the-Record Messaging
+protocol.  The main changes over version 1 include:</p>
+<ul>
+<li>Resolving the identity-binding flaw identified by Di Raimondo,
+Gennaro, and Krawczyk</li>
+<li>Not revealing the users' public keys to passive eavesdroppers; this
+could be useful if the application sending the OTR messages is also
+privacy-preserving</li>
+<li>Supporting fragmentation of OTR messages, to support IM networks
+whose maximum message size is very small.</li>
+<li>Better protocol version control, for future extensibility.</li>
+</ul>
+<h2>Very high level overview</h2>
+<p>OTR assumes a network model which provides in-order delivery of
+messages, but that some messages may not get delivered at all
+(for example, if the user disconnects).  There may be
+an active attacker, who is allowed to perform a Denial of
+Service attack, but not to learn the contents of messages.</p>
+<ol>
+<li>Alice signals to Bob that she would like (using an OTR Query Message)
+or is willing (using a whitespace-tagged plaintext message) to use OTR
+to communicate.  Either mechanism should convey the version(s) of OTR
+that Alice is willing to use.</li>
+<li>Bob initiates the authenticated key exchange (AKE) with Alice.
+Version 2 of OTR uses a variant of the SIGMA protocol as its AKE.</li>
+<li>Alice and Bob exchange Data Messages to send information to each
+other.</li>
+</ol>
+<h2>High level overview</h2>
+<h3>Requesting an OTR conversation</h3>
+<p>There are two ways Alice can inform Bob that she is willing to use
+the OTR protocol to speak with him: by sending him the OTR Query Message, 
+or by including a special "tag" consisting of whitespace characters in
+one of her messages to him.  Each method also includes a way for Alice
+to communicate to Bob which versions of the OTR protocol she is willing
+to speak with him.</p>
+<p>The semantics of the OTR Query Message are that Alice is
+<em>requesting</em> that Bob start an OTR conversation with her (if, of
+course, he is willing and able to do so).  On the other hand, the
+semantics of the whitespace tag are that Alice is merely
+<em>indicating</em> to Bob that she is willing and able to have an OTR
+conversation with him.  If Bob has a policy of "only use OTR when it's
+explicitly requested", for example, then he <em>would</em> start an OTR
+conversation upon receiving an OTR Query Message, but <em>would not</em>
+upon receiving the whitespace tag.</p>
+<h3>Authenticated Key Exchange (AKE)</h3>
+<p>This section outlines the version of the SIGMA protocol used as the
+AKE.  All exponentiations are done modulo a particular 1536-bit prime,
+and g is a generator of that group, as indicated in the detailed
+description below.  Alice and Bob's long-term authentication public keys
+are pub<sub>A</sub> and pub<sub>B</sub>, respectively.</p>
+<p>The general idea is that Alice and Bob do an <em>unauthenticated</em>
+Diffie-Hellman (D-H) key exchange to set up an encrypted channel, and
+then do mutual authentication <em>inside</em> that channel.</p>
+<p>Bob will be initiating the AKE with Alice.</p>
+<ul>
+<li>Bob:
+<ol>
+<li>Picks a random value r (128 bits)</li>
+<li>Picks a random value x (at least 320 bits)</li>
+<li>Sends Alice AES<sub>r</sub>(g<sup>x</sup>), HASH(g<sup>x</sup>)</li>
+</ol></li>
+<li>Alice:
+<ol>
+<li>Picks a random value y (at least 320 bits)</li>
+<li>Sends Bob g<sup>y</sup></li>
+</ol></li>
+<li>Bob:
+<ol>
+<li>Verifies that Alice's g<sup>y</sup> is a legal value (2 <=
+g<sup>y</sup> <= modulus-2)</li>
+<li>Computes s = (g<sup>y</sup>)<sup>x</sup></li>
+<li>Computes two AES keys c, c' and four MAC keys m1, m1', m2, m2' by
+hashing s in various ways</li>
+<li>Picks keyid<sub>B</sub>, a serial number for his D-H key
+g<sup>x</sup></li>
+<li>Computes M<sub>B</sub> = MAC<sub>m1</sub>(g<sup>x</sup>, g<sup>y</sup>,
+pub<sub>B</sub>, keyid<sub>B</sub>)</li>
+<li>Computes X<sub>B</sub> = pub<sub>B</sub>, keyid<sub>B</sub>,
+sig<sub>B</sub>(M<sub>B</sub>)</li>
+<li>Sends Alice r, AES<sub>c</sub>(X<sub>B</sub>),
+MAC<sub>m2</sub>(AES<sub>c</sub>(X<sub>B</sub>))</li>
+</ol></li>
+<li>Alice:
+<ol>
+<li>Uses r to decrypt the value of g<sup>x</sup> sent earlier</li>
+<li>Verifies that HASH(g<sup>x</sup>) matches the value sent earlier</li>
+<li>Verifies that Bob's g<sup>x</sup> is a legal value (2 <=
+g<sup>x</sup> <= modulus-2)</li>
+<li>Computes s = (g<sup>x</sup>)<sup>y</sup> (note that this will be the
+same as the value of s Bob calculated)</li>
+<li>Computes two AES keys c, c' and four MAC keys m1, m1', m2, m2' by
+hashing s in various ways (the same as Bob)</li>
+<li>Uses m2 to verify MAC<sub>m2</sub>(AES<sub>c</sub>(X<sub>B</sub>))</li>
+<li>Uses c to decrypt AES<sub>c</sub>(X<sub>B</sub>) to obtain
+X<sub>B</sub> = pub<sub>B</sub>, keyid<sub>B</sub>,
+sig<sub>B</sub>(M<sub>B</sub>)</li>
+<li>Computes M<sub>B</sub> = MAC<sub>m1</sub>(g<sup>x</sup>,
+g<sup>y</sup>, pub<sub>B</sub>, keyid<sub>B</sub>)</li>
+<li>Uses pub<sub>B</sub> to verify sig<sub>B</sub>(M<sub>B</sub>)</li>
+
+<li>Picks keyid<sub>A</sub>, a serial number for her D-H key
+g<sup>y</sup></li>
+<li>Computes M<sub>A</sub> = MAC<sub>m1'</sub>(g<sup>y</sup>, g<sup>x</sup>,
+pub<sub>A</sub>, keyid<sub>A</sub>)</li>
+<li>Computes X<sub>A</sub> = pub<sub>A</sub>, keyid<sub>A</sub>,
+sig<sub>A</sub>(M<sub>A</sub>)</li>
+<li>Sends Bob AES<sub>c'</sub>(X<sub>A</sub>),
+MAC<sub>m2'</sub>(AES<sub>c'</sub>(X<sub>A</sub>))</li>
+</ol></li>
+<li>Bob:
+<ol>
+<li>Uses m2' to verify MAC<sub>m2'</sub>(AES<sub>c'</sub>(X<sub>A</sub>))</li>
+<li>Uses c' to decrypt AES<sub>c'</sub>(X<sub>A</sub>) to obtain
+X<sub>A</sub> = pub<sub>A</sub>, keyid<sub>A</sub>,
+sig<sub>A</sub>(M<sub>A</sub>)</li>
+<li>Computes M<sub>A</sub> = MAC<sub>m1'</sub>(g<sup>y</sup>,
+g<sup>x</sup>, pub<sub>A</sub>, keyid<sub>A</sub>)</li>
+<li>Uses pub<sub>A</sub> to verify sig<sub>A</sub>(M<sub>A</sub>)</li>
+</ol></li>
+<li>If all of the verifications succeeded, Alice and Bob now know each
+other's Diffie-Hellman public keys, and share the value s.  Alice is
+assured that s is known by someone with access to the private key
+corresponding to pub<sub>B</sub>, and similarly for Bob.</li>
+</ul>
+<h3>Exchanging data</h3>
+<p>This section outlines the method used to protect data being exchanged
+between Alice and Bob.  As above, all exponentiations are done modulo
+a particular 1536-bit prime, and g is a generator of
+that group, as indicated in the detailed description below.</p>
+<p>Suppose Alice has a message (msg) to send to Bob.</p>
+<ul>
+<li>Alice:
+<ol>
+<li>Picks the most recent of her own D-H encryption keys that Bob has
+acknowledged receiving (by using it in a Data Message, or failing that,
+in the AKE).  Let keyid<sub>A</sub> be the serial number of that
+key.</li>
+<li>If the above key is Alice's most recent key, she generates a new D-H key 
+(next_dh), to get the serial number keyid<sub>A</sub>+1.</li>
+<li>Picks the most recent of Bob's D-H encryption keys that she has
+received from him (either in a Data Message or in the AKE).  Let
+keyid<sub>B</sub> be the serial number of that key.</li>
+<li>Uses Diffie-Hellman to compute a shared secret from the two keys
+labelled by keyid<sub>A</sub> and keyid<sub>B</sub>, and generates the
+sending AES key, ek, and the sending MAC key, mk, as detailed
+below.</li>
+<li>Collects any old MAC keys that were used in previous messages, but
+will never again be used (because their associated D-H keys are no
+longer the most recent ones) into a list, oldmackeys.</li>
+<li>Picks a value of the counter, ctr, so that the triple
+(keyid<sub>A</sub>, keyid<sub>B</sub>, ctr) is never the same for more
+than one Data Message Alice sends to Bob.</li>
+<li>Computes T<sub>A</sub> = (keyid<sub>A</sub>, keyid<sub>B</sub>, next_dh,
+ctr, AES-CTR<sub>ek,ctr</sub>(msg))</li>
+<li>Sends Bob T<sub>A</sub>, MAC<sub>mk</sub>(T<sub>A</sub>),
+oldmackeys</li>
+</ol></li>
+<li>Bob:
+<ol>
+<li>Uses Diffie-Hellman to compute a shared secret from the two keys
+labelled by keyid<sub>A</sub> and keyid<sub>B</sub>, and generates the
+receiving AES key, ek, and the receiving MAC key, mk, as detailed
+below.  (These will be the same as the keys Alice generated, above.)</li>
+<li>Uses mk to verify MAC<sub>mk</sub>(T<sub>A</sub>).</li>
+<li>Uses ek and ctr to decrypt
+AES-CTR<sub>ek,ctr</sub>(T<sub>A</sub>).</li>
+</ol>
+</li>
+</ul>
+<h2>Details of the protocol</h2>
+<h3>Unencoded messages</h3>
+<p>This section describes the messages in the OTR protocol that are not
+base-64 encoded binary.</p>
+<h4>OTR Query Messages</h4>
+<p>If Alice wishes to communicate to Bob that she would like to use OTR,
+she sends a message containing the string "?OTR" followed by an
+indication of what versions of OTR she is willing to use with Bob.  The
+version string is constructed as follows:</p>
+<ul>
+<li>If she is willing to use OTR version 1, the version string must
+start with "?".</li>
+<li>If she is willing to use OTR versions other than 1, a "v" followed
+by the byte identifiers for the versions in question, followed by "?".
+The byte identifier for OTR version 2 is "2".  The order of the
+identifiers between the "v" and the "?" does not matter, but none should
+be listed more than once.</li>
+</ul>
+<p>For example:</p>
+<dl>
+<dt>"?OTR?"</dt>
+<dd>Version 1 only</dd>
+<dt>"?OTRv2?"</dt>
+<dd>Version 2 only</dd>
+<dt>"?OTR?v2?"</dt>
+<dd>Versions 1 and 2</dd>
+<dt>"?OTRv24x?"</dt>
+<dd>Version 2, and hypothetical future versions identified by "4" and
+"x"</dd>
+<dt>"?OTR?v24x?"</dt>
+<dd>Versions 1, 2, and hypothetical future versions identified by "4" and
+"x"</dd>
+<dt>"?OTR?v?"</dt>
+<dd>Also version 1 only</dd>
+<dt>"?OTRv?"</dt>
+<dd>A bizarre claim that Alice would like to start an OTR conversation,
+but is unwilling to speak any version of the protocol</dd>
+</dl>
+<p>These strings may be hidden from the user (for example, in
+an attribute of an HTML tag), and/or may be accompanied by an
+explanitory message ("Alice has requested an Off-the-Record private
+conversation.").  If Bob is willing to use OTR with Alice (with a
+protocol version that Alice has offered), he should start the AKE.</p>
+<h4>Tagged plaintext messages</h4>
+<p>If Alice wishes to communicate to Bob that she is willing to use OTR,
+she can attach a special whitespace tag to any plaintext message she
+sends him.  This tag may occur anywhere in the message, and may be
+hidden from the user (as in the Query Messages, above).</p>
+<p>The tag consists of the following 16 bytes, followed by one or more
+sets of 8 bytes indicating the version of OTR Alice is willing to
+use:</p>
+<ul>
+<li>Always send "\x20\x09\x20\x20\x09\x09\x09\x09"
+"\x20\x09\x20\x09\x20\x09\x20\x20", followed by one or more of:</li>
+<li>"\x20\x09\x20\x09\x20\x20\x09\x20" to indicate a willingness to use
+OTR version 1 with Bob (note: this string must come before all other
+whitespace version tags, if it is present, for backwards
+compatibility)</li>
+<li>"\x20\x20\x09\x09\x20\x20\x09\x20" to indicate a willingness to use
+OTR version 2 with Bob</li>
+</ul>
+<p>If Bob is willing to use OTR with Alice (with a protocol version that
+Alice has offered), he should start the AKE.  On the other hand, if
+Alice receives a plaintext message from Bob (rather than an initiation
+of the AKE), she should stop sending him the whitespace tag.</p>
+<h4>OTR Error Messages</h4>
+<p>Any message containing the string "?OTR Error:" is an OTR Error
+Message.  The following part of the message should contain
+human-readable details of the error.</p>
+<h3>Encoded messages</h3>
+<p>This section describes the byte-level format of the base-64 encoded
+binary OTR messages.  The binary form of each of the messages is
+described below.  To transmit one of these messages, construct the ASCII
+string consisting of the five bytes "?OTR:", followed by the base-64
+encoding of the binary form of the message, followed by the byte
+".".</p>
+<p>For the Diffie-Hellman group computations, the group is the one
+defined in RFC 3526 with 1536-bit modulus (hex, big-endian):</p>
+<blockquote><pre>
+FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
+29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
+EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
+E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
+EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
+C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
+83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
+670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF
+</pre></blockquote>
+<p>and a generator (g) of 2.  Note that this means that whenever you see a
+Diffie-Hellman exponentiation in this document, it always means that the
+exponentiation is done modulo the above 1536-bit number.</p>
+<h4>Data types</h4>
+<dl>
+<dt>Bytes (BYTE):</dt>
+<dd>      1 byte unsigned value</dd>
+<dt>Shorts (SHORT):</dt>
+<dd>      2 byte unsigned value, big-endian</dd>
+<dt>Ints (INT):</dt>
+<dd>      4 byte unsigned value, big-endian</dd>
+<dt>Multi-precision integers (MPI):</dt>
+<dd>      4 byte unsigned len, big-endian
+<br />      len byte unsigned value, big-endian
+<br />      (MPIs must use the minimum-length encoding; i.e. no leading 0x00
+      bytes.  This is important when calculating public key
+      fingerprints.)</dd>
+<dt>Opaque variable-length data (DATA):</dt>
+<dd>      4 byte unsigned len, big-endian
+<br />      len byte data</dd>
+<dt>Initial CTR-mode counter value (CTR):</dt>
+<dd>      8 bytes data</dd>
+<dt>Message Authentication Code (MAC):</dt>
+<dd>      20 bytes MAC data</dd>
+</dl>
+<h4>Public keys, signatures, and fingerprints</h4>
+<p>OTR users have long-lived public keys that they use for
+authentication (but <em>not</em> encryption).  The current version of
+the OTR protocol only supports DSA public keys, but there is a key type
+marker for future extensibility.</p>
+<dl>
+<dt>OTR public authentication DSA key (PUBKEY):</dt>
+<dd>Pubkey type (SHORT)
+<ul class="note"><li>DSA public keys have type 0x0000</li></ul>
+p (MPI)
+<br />q (MPI)
+<br />g (MPI)
+<br />y (MPI)
+<ul class="note"><li>(p,q,g,y) are the DSA public key parameters</li></ul>
+</dd>
+</dl>
+<p>OTR public keys are used to generate <b>signatures</b>; different
+types of keys produce signatures in different formats.  The format for a
+signature made by a DSA public key is as follows:</p>
+<dl>
+<dt>DSA signature (SIG):</dt>
+<dd>      (len is the length of the DSA public parameter q)
+<br />      len byte unsigned r, big-endian
+<br />      len byte unsigned s, big-endian</dd>
+</dl>
+<p>OTR public keys have <b>fingerprints</b>, which are hex strings that
+serve as identifiers for the public key.  The fingerprint is calculated
+by taking the SHA-1 hash of the byte-level representation of the public
+key.  However, there is an exception for backwards compatibility: if the
+pubkey type is 0x0000, those two leading 0x00 bytes are omitted from the
+data to be hashed.  The encoding assures that, assuming the hash
+function itself has no useful collisions, and DSA keys have length less
+than 524281 bits (500 times larger than most DSA keys), no two public
+keys will have the same fingerprint.</p>
+<h4>D-H Commit Message</h4>
+<p>This is the first message of the AKE.  Bob sends it to Alice to
+commit to a choice of D-H encryption key (but the key itself is not yet
+revealed).  This allows the secure session id to be much shorter than in
+OTR version 1, while still preventing a man-in-the-middle attack on
+it.</p>
+<dl>
+<dt>Protocol version (SHORT)</dt>
+<dd>The version number of this protocol is 0x0002.</dd>
+<dt>Message type (BYTE)</dt>
+<dd>The D-H Commit Message has type 0x02.</dd>
+<dt>Encrypted g<sup>x</sup> (DATA)</dt>
+<dd>Produce this field as follows:
+<ul>
+<li>Choose a random value r (128 bits)</li>
+<li>Choose a random value x (at least 320 bits)</li>
+<li>Serialize g<sup>x</sup> as an MPI, gxmpi.  [gxmpi will probably be
+196 bytes long, starting with "\x00\x00\x00\xc0".]</li>
+<li>Encrypt gxmpi using AES128-CTR, with key r and initial counter value
+0.  The result will be the same length as gxmpi.</li>
+<li>Encode this encrypted value as the DATA field.</li>
+</ul></dd>
+<dt>Hashed g<sup>x</sup> (DATA)</dt>
+<dd>This is the SHA-256 hash of gxmpi.</dd>
+</dl>
+<h4>D-H Key Message</h4>
+<p>This is the second message of the AKE.  Alice sends it to Bob, and it
+simply consists of Alice's D-H encryption key.</p>
+<dl>
+<dt>Protocol version (SHORT)</dt>
+<dd>The version number of this protocol is 0x0002.</dd>
+<dt>Message type (BYTE)</dt>
+<dd>The D-H Key Message has type 0x0a.</dd>
+<dt>g<sup>y</sup> (MPI)</dt>
+<dd>Choose a random value y (at least 320 bits), and calculate
+g<sup>y</sup>.</dd>
+</dl>
+<h4>Reveal Signature Message</h4>
+<p>This is the third message of the AKE.  Bob sends it to Alice,
+revealing his D-H encryption key (and thus opening an encrypted
+channel), and also authenticating himself (and the parameters of the
+channel, preventing a man-in-the-middle attack on the channel itself) to
+Alice.</p>
+<dl>
+<dt>Protocol version (SHORT)</dt>
+<dd>The version number of this protocol is 0x0002.</dd>
+<dt>Message type (BYTE)</dt>
+<dd>The Reveal Signature Message has type 0x11.</dd>
+<dt>Revealed key (DATA)</dt>
+<dd>This is the value r picked earlier.</dd>
+<dt>Encrypted signature (DATA)</dt>
+<dd>This field is calculated as follows:
+<ul>
+<li>Compute the Diffie-Hellman shared secret s.</li>
+<li>Use s to compute an AES key c and two MAC keys m1 and m2, as specified below.</li>
+<li>Select keyid<sub>B</sub>, a serial number for the D-H key computed
+earlier.  It is an INT, and must be greater than 0.</li>
+<li>Compute the 32-byte value M<sub>B</sub> to be the SHA256-HMAC of the
+following data, using the key m1:<dl>
+<dt>g<sup>x</sup> (MPI)</dt>
+<dt>g<sup>y</sup> (MPI)</dt>
+<dt>pub<sub>B</sub> (PUBKEY)</dt>
+<dt>keyid<sub>B</sub> (INT)</dt>
+</dl></li>
+<li>Let X<sub>B</sub> be the following structure:<dl>
+<dt>pub<sub>B</sub> (PUBKEY)</dt>
+<dt>keyid<sub>B</sub> (INT)</dt>
+<dt>sig<sub>B</sub>(M<sub>B</sub>) (SIG)</dt>
+<dd>This is the signature, using the private part of the key
+pub<sub>B</sub>, of the 32-byte M<sub>B</sub> (which does not need to be
+hashed again to produce the signature).</dd>
+</dl></li>
+<li>Encrypt X<sub>B</sub> using AES128-CTR with key c and initial
+counter value 0.</li>
+<li>Encode this encrypted value as the DATA field.</li>
+</ul></dd>
+<dt>MAC'd signature (MAC)</dt>
+<dd>This is the SHA256-HMAC-160 (that is, the first 160 bits of the
+SHA256-HMAC) of the encrypted signature field (including the four-byte
+length), using the key m2.</dd>
+</dl>
+<h4>Signature Message</h4>
+<p>This is the final message of the AKE.  Alice sends it to Bob,
+authenticating herself and the channel parameters to him.</p>
+<dl>
+<dt>Protocol version (SHORT)</dt>
+<dd>The version number of this protocol is 0x0002.</dd>
+<dt>Message type (BYTE)</dt>
+<dd>The Signature Message has type 0x12.</dd>
+<dt>Encrypted signature (DATA)</dt>
+<dd>This field is calculated as follows:
+<ul>
+<li>Compute the Diffie-Hellman shared secret s.</li>
+<li>Use s to compute an AES key c' and two MAC keys m1' and m2', as specified below.</li>
+<li>Select keyid<sub>A</sub>, a serial number for the D-H key computed
+earlier.  It is an INT, and must be greater than 0.</li>
+<li>Compute the 32-byte value M<sub>A</sub> to be the SHA256-HMAC of the
+following data, using the key m1':<dl>
+<dt>g<sup>y</sup> (MPI)</dt>
+<dt>g<sup>x</sup> (MPI)</dt>
+<dt>pub<sub>A</sub> (PUBKEY)</dt>
+<dt>keyid<sub>A</sub> (INT)</dt>
+</dl></li>
+<li>Let X<sub>A</sub> be the following structure:<dl>
+<dt>pub<sub>A</sub> (PUBKEY)</dt>
+<dt>keyid<sub>A</sub> (INT)</dt>
+<dt>sig<sub>A</sub>(M<sub>A</sub>) (SIG)</dt>
+<dd>This is the signature, using the private part of the key
+pub<sub>A</sub>, of the 32-byte M<sub>A</sub> (which does not need to be
+hashed again to produce the signature).</dd>
+</dl></li>
+<li>Encrypt X<sub>A</sub> using AES128-CTR with key c' and initial
+counter value 0.</li>
+<li>Encode this encrypted value as the DATA field.</li>
+</ul></dd>
+<dt>MAC'd signature (MAC)</dt>
+<dd>This is the SHA256-HMAC-160 (that is, the first 160 bits of the
+SHA256-HMAC) of the encrypted signature field (including the four-byte
+length), using the key m2'.</dd>
+</dl>
+<h4>Data Message</h4>
+<p>This message is used to transmit a private message to the
+correspondent.  It is also used to reveal old MAC keys.</p>
+<p>The plaintext message (either before encryption, or after decryption)
+consists of a human-readable message (encoded in UTF-8, optionally with
+HTML markup), optionally followed by:</p>
+<ul>
+<li>a single NUL (a BYTE with value 0x00), <b>and</b></li>
+<li>zero or more TLV (type/length/value) records (with no padding
+between them)</li>
+</ul>
+<p>Each TLV record is of the form:</p>
+<dl>
+<dt>Type (SHORT)</dt>
+<dd>The type of this record.  Records with unrecognized types should be
+ignored.</dd>
+<dt>Length (SHORT)</dt>
+<dd>The length of the following field</dd>
+<dt>Value (len BYTEs)  [where len is the value of the Length field]</dt>
+<dd>Any pertinent data for the record type.</dd>
+</dl>
+<p>Some TLV examples:</p>
+<dl>
+<dt>\x00\x01\x00\x00</dt>
+<dd>A TLV of type 1, containing no data</dd>
+<dt>\x00\x00\x00\x05\x68\x65\x6c\x6c\x6f</dt>
+<dd>A TLV of type 0, containing the value "hello"</dd>
+</dl>
+<p>The currently defined TLV record types are:</p>
+<dl>
+<dt>Type 0: Padding</dt>
+<dd>The value may be an arbitrary amount of data, which should be
+ignored.  This type can be used to disguise the length of the plaintext
+message.</dd>
+<dt>Type 1: Disconnected</dt>
+<dd>If the user requests to close the private connection, you may send a
+message (possibly with empty human-readable part) containing a record
+with this TLV type just before you discard the session keys, and
+transition to MSGSTATE_PLAINTEXT (see below).  If you receive a TLV
+record of this type, you should transition to MSGSTATE_FINISHED (see
+below), and inform the user that his correspondent has closed his end of
+the private connection, and the user should do the same.</dd>
+</dl>
+<p>A message with an empty human-readable part (the plaintext is of zero
+length, or starts with a NUL) is a "heartbeat" packet, and should not
+be displayed to the user.  (But it's still useful to effect key
+rotations.)</p>
+<p>Data Message format:</p>
+<dl>
+<dt>Protocol version (SHORT)</dt>
+<dd>This message has not changed since OTR version 1, so this value
+should be 0x0001.</dd>
+<dt>Message type (BYTE)</dt>
+<dd>The Data Message has type 0x03.</dd>
+<dt>Sender keyid (INT)</dt>
+<dd>Must be strictly greater than 0, and increment by 1 with each key
+change</dd>
+<dt>Recipient keyid (INT)</dt>
+<dd>Must therefore be strictly greater than 0, as the receiver has no
+key with id 0.
+<br />The sender and recipient keyids are those used to encrypt and MAC
+this message.</dd>
+<dt>DH y (MPI)</dt>
+<dd>The *next* [i.e. sender_keyid+1] public key for the sender</dd>
+<dt>Top half of counter init (CTR)</dt>
+<dd>This should monotonically increase (as a big-endian value) for
+      each message sent with the same (sender keyid, recipient keyid)
+      pair, and must not be all 0x00.</dd>
+<dt>Encrypted message (DATA)</dt>
+<dd>Using the appropriate encryption key (see below) derived from the
+      sender's and recipient's DH public keys (with the keyids given in
+      this message), perform AES128 counter-mode (CTR) encryption of the
+      message.  The initial counter is a 16-byte value whose first 8
+      bytes are the above "top half of counter init" value, and whose
+      last 8 bytes are all 0x00.  Note that counter mode does not change
+      the length of the message, so no message padding needs to be done.
+      If you *want* to do message padding (to disguise the length of
+      your message), use the above TLV of type 0.</dd>
+<dt>Authenticator (MAC)</dt>
+<dd>The SHA1-HMAC, using the appropriate MAC key (see below) of everything
+    from the Protocol version to the end of the encrypted message</dd>
+<dt>Old MAC keys to be revealed (DATA)</dt>
+<dd>See "Revealing MAC Keys", below.</dd>
+</dl>
+<h4>Key Management</h4>
+<p>For each correspondent, keep track of:</p>
+<dl>
+<dt>Your two most recent DH public/private key pairs</dt>
+<dd>our_dh[our_keyid] (most recent) and our_dh[our_keyid-1] (previous)</dd>
+<dt>His two most recent DH public keys</dt>
+<dd>their_y[their_keyid] (most recent) and their_y[their_keyid-1]
+(previous)</dd>
+</dl>
+
+<p>When starting a private conversation with a correspondent, generate
+two DH key pairs for yourself, and set our_keyid = 2.  Note that all DH
+key pairs should have a private part that is at least 320 bits long.</p>
+
+<dl class="doublespace">
+<dt>When you send AKE messages:</dt>
+<dd>Send the public part of our_dh[our_keyid-1], with the keyid field,
+    of course, set to (our_keyid-1).</dd>
+
+<dt>Upon completing the AKE:</dt>
+<dd>If the specified keyid equals either their_keyid or their_keyid-1,
+    and the DH pubkey contained in the AKE messages matches the
+    one you've stored for that keyid, that's great.  Otherwise, forget
+    all values of their_y[], and of their_keyid, and set their_keyid to
+    the keyid value given in the AKE messages, and
+    their_y[their_keyid] to the DH pubkey value given in the AKE
+    messages.  their_y[their_keyid-1] should be set to NULL.</dd>
+
+<dt>When you send a Data Message:</dt>
+<dd>Set the sender keyid to (our_keyid-1), and the recipient keyid to
+    (their_keyid).  Set the DH pubkey in the Data message to the public
+    part of our_dh[our_keyid].  Use our_dh[our_keyid-1] and
+    their_y[their_keyid] to calculate session keys, as outlined below.
+    Use the "sending AES key" to encrypt the message, and the "sending
+    MAC key" to calculate its MAC.</dd>
+
+<dt>When you receive a Data Message:</dt>
+<dd>Use the keyids in the message to select which of your DH key pairs
+    and which of his DH pubkeys to use to verify the MAC.  If the keyids
+    do not represent either the most recent key or the previous key (for
+    either the sender or receiver), reject the message.  Also reject the
+    message if the sender keyid is their_keyid-1, but
+    their_y[their_keyid-1] is NULL.
+
+    <p>Otherwise, calculate the session keys as outlined below.  Use the
+    "receiving MAC key" to verify the MAC on the message.  If it does not
+    verify, reject the message.</p>
+
+    <p>Check that the counter in the Data message is strictly larger than the
+    last counter you saw using this pair of keys.  If not, reject the
+    message.</p>
+
+    <p>If the MAC verifies, decrypt the message using the "receiving AES
+    key".</p>
+
+    <p>Finally, check if keys need rotation:</p>
+    <ul>
+    <li>If the "recipient keyid" in the Data message equals our_keyid, then
+       he's seen the public part of our most recent DH key pair, so you
+       must securely forget our_dh[our_keyid-1], increment our_keyid, and set
+       our_dh[our_keyid] to a new DH key pair which you generate.</li>
+    <li>If the "sender keyid" in the Data message equals their_keyid,
+       increment their_keyid, and set their_y[their_keyid] to the new DH
+       pubkey specified in the Data message.</li>
+    </ul></dd>
+</dl>
+
+<h4>Computing AES keys, MAC keys, and the secure session id</h4>
+<p>OTR uses Diffie-Hellman to calculate shared secrets in the usual way:
+if Bob knows x, and tells Alice g<sup>x</sup>, and Alice knows y, and
+tells Bob g<sup>y</sup>, then they each can calculate s =
+g<sup>xy</sup>: Alice calculates (g<sup>x</sup>)<sup>y</sup>, and Bob
+calculates (g<sup>y</sup>)<sup>x</sup>.</p>
+<p>During the AKE, Alice and Bob each calculate s in this way, and then
+they each compute seven values based on s:</p>
+<ul>
+<li>A 64-bit secure session id, ssid</li>
+<li>Two 128-bit AES encryption keys, c and c'</li>
+<li>Four 256-bit SHA256-HMAC keys, m1, m2, m1', and m2'</li>
+</ul>
+<p>This is done in the following way:</p>
+<ul>
+<li>Write the value of s as a minimum-length MPI, as specified above
+(4-byte big-endian len, len-byte big-endian value).  Let this
+(4+len)-byte value be "secbytes".</li>
+<li>For a given byte b, define h2(b) to be the 256-bit output of the
+SHA256 hash of the (5+len) bytes consisting of the byte b, followed by
+secbytes.</li>
+<li>Let ssid be the first 64 bits of h2(0x00).</li>
+<li>Let c be the first 128 bits of h2(0x01), and let c' be the second
+128 bits of h2(0x01).</li>
+<li>Let m1 be h2(0x02).</li>
+<li>Let m2 be h2(0x03).</li>
+<li>Let m1' be h2(0x04).</li>
+<li>Let m2' be h2(0x05).</li>
+</ul>
+<p>c, m1, and m2 are used to create and verify the Reveal Signature
+Message; c', m1', and m2' are used to create and verify the Signature
+message.</p>
+<p>If the user requests to see the secure session id, it should be
+displayed as two 32-bit bigendian unsigned values, in C "%08x" format.
+If the user transmitted the Reveal Signature message during the AKE that
+produced this ssid, then display the first 32 bits in bold, and the
+second 32 bits in non-bold.  If the user transmitted the Signature
+message instead, display the first 32 bits in non-bold, and the
+second 32 bits in bold.  This session id can be used by the parties to
+verify (say, over the telephone, assuming the parties recognize each
+others' voices) that there is no man-in-the-middle by having each side
+read his bold part to the other.  [Note that this only needs to be done
+in the event that the users do not trust that their long-term signature
+keys have not been compromised.]</p>
+<p>During the exchange of Data Messages, Alice and Bob use the keyids
+listed in the Data Message to select Diffie-Hellman keys to use to
+compute s, and the (4+len)-byte value of secbytes, as above.</p>
+<p>From this, they calculate four values:</p>
+<ul>
+<li>Two 128-bit AES encryption keys, the "sending AES key", and the
+"receiving AES key"</li>
+<li>Two 160-bit SHA1-HMAC keys, the "sending MAC key", and the
+"receiving MAC key"</li>
+</ul>
+<p>These keys are calculated as follows:</p>
+<ul>
+<li>Alice (and similarly for Bob) determines if she is the "low" end
+or the "high" end of this Data Message.  If Alice's public key is
+numerically greater than Bob's public key, then she
+is the "high" end.  Otherwise, she is the "low" end.  Note that who is the
+"low" end and who is the "high" end can change every time a new D-H
+public key is exchanged in a Data Message.</li>
+<li>She sets the values of "sendbyte" and "recvbyte" according to
+whether she is the the "low" or the "high" end of the Data Message:
+<ul>
+<li>If she is the "high" end, she sets "sendbyte" to 0x01 and "recvbyte"
+to 0x02.</li>
+<li>If she is the "low" end, she sets "sendbyte" to 0x02 and "recvbyte"
+to 0x01.</li>
+</ul></li>
+<li>For a given byte b, define h1(b) to be the 160-bit output of the
+SHA-1 hash of the (5+len) bytes consisting of the byte b, followed by
+secbytes.</li>
+<li>The "sending AES key" is the first 16 bytes of h1(sendbyte).</li>
+<li>The "sending MAC key" is the 20-byte SHA-1 hash of the 16-byte
+sending AES key.</li>
+<li>The "receiving AES key" is the first 16 bytes of h1(recvbyte).</li>
+<li>The "receiving MAC key" is the 20-byte SHA-1 hash of the 16-byte
+receiving AES key.</li>
+</ul>
+<h4>Revealing MAC keys</h4>
+<p>Whenever you are about to forget either one of your old D-H key pairs, or
+one of your correspondent's old D-H public keys, take all of the MAC keys
+that were generated by that key (note that there are up to four: the
+sending and receiving MAC keys produced by the pairings of that key with
+each of two of the other side's keys; but note that you only need to
+take MAC keys that were actually used to either create a MAC on a
+message, or verify a MAC on a message), and put them (as a set of
+concatenated 20-byte values) into the "Old MAC keys to be revealed"
+section of the next Data Message you send.  This in done to allow the
+forgeability of OTR transcripts: once the MAC keys are revealed, anyone
+can modify an OTR message and still have it appear valid.  But since we
+don't reveal the MAC keys until their corresponding pubkeys are being
+discarded, there is no danger of accepting a message as valid which
+uses a MAC key which has already been revealed.</p>
+<h4>Fragmentation</h4>
+<p>Some networks may have a maximum message size that is too small to
+contain an encoded OTR message.  In that event, the sender may choose
+to split the message into a number of <em>fragments</em>.  This section
+describes the format of the fragments.  All OTR version 2 clients must
+be able to assemble received fragments, but performing fragmentation on
+outgoing messages is optional.</p>
+
+<dl class="doublespace">
+<dt>Transmitting Fragments</dt>
+<dd>If you have information about the maximum size of message you are
+    able to send (the different IM networks have different limits), you
+    can fragment an encoded OTR message as follows:
+    <ul>
+    <li>Start with the OTR message as you would normally transmit it.  For
+      example, a Data Message would start with "?OTR:AAED" and end
+      with ".".</li>
+    <li>Break it up into sufficiently small pieces.  Let the number of
+      pieces be (n), and the pieces be
+      piece[1],piece[2],...,piece[n].</li>
+    <li>Transmit (n) messages with the following (printf-like) structure
+      (as k runs from 1 to n inclusive):
+
+      <p>"?OTR,%hu,%hu,%s," , k , n , piece[k]</p></li>
+
+    <li>Note that k and n are unsigned short ints (2 bytes), and each has
+      a maximum value of 65535.  Also, each piece[k] must be
+      non-empty.</li>
+    </ul></dd>
+
+<dt>Receiving Fragments:</dt>
+
+<dd>If you receive a message containing "?OTR," (note that you'll need
+    to check for this _before_ checking for any of the other "?OTR:"
+    markers):
+
+    <ul>
+    <li>Parse it as the printf statement above into k, n, and
+    piece.</li>
+    <li>Let (K,N) be your currently stored fragment number, and F be your
+      currently stored fragment.  [If you have no currently stored
+      fragment, then K = N = 0 and F = "".]</li>
+
+    <li>If k == 0 or n == 0 or k > n, discard this (illegal)
+    fragment.</li>
+
+    <li>If k == 1:
+    <ul>
+      <li>Forget any stored fragment you may have</li>
+      <li>Store (piece) as F.</li>
+      <li>Store (k,n) as (K,N).</li>
+    </ul></li>
+
+    <li>If n == N and k == K+1:
+    <ul>
+      <li>Append (piece) to F.</li>
+      <li>Store (k,n) as (K,N).</li>
+    </ul></li>
+
+    <li>Otherwise:
+    <ul>
+      <li>Forget any stored fragment you may have</li>
+      <li>Store "" as F.</li>
+      <li>Store (0,0) as (K,N).</li>
+    </ul></li>
+    </ul>
+
+    <p>After this, if N > 0 and K == N, treat F as the received
+    message.</p>
+
+    <p>If you receive a non-OTR message, or an unfragmented message,
+    forget any stored fragment you may have, store "" as F and store
+    (0,0) as (K,N).</p></dd>
+</dl>
+
+<p>For example, here is a Data Message we would like to transmit over a
+network with an unreasonably small maximum message size:</p>
+
+<blockquote><pre>
+?OTR:AAEDAAAAAQAAAAEAAADAVf3Ei72ZgFeKqWvLMnuVPVCwxktsOZ1Qdje
+Lp6jn62mCVtlY9nS6sRkecpjuLYHRxyTdRu2iEVtSsjZqK55ovZ35SfkOPHe
+FYa9BIuxWi9djHMVKQ8KOVGAVLibjZ6P8LreDSKtWDv9YQjIEnkwFVGCPfpB
+q2SX4VTQfJAQXHggR8izKxPvluXUdG9rIPh4cac98++VLdIuFMiEXjUIoTX2
+rEzunaCLMy0VIfowlRsgsKGrwhCCv7hBWyglbzwz+AAAAAAAAAAQAAAF2SOr
+JvPUerB9mtf4bqQDFthfoz/XepysnYuReHHEXKe+BFkaEoMNGiBl4TCLZx72
+DvmZwKCewWRH1+W66ggrXKw2VdVl+vLsmzxNyWChGLfBTL5/3SUF09BfmCEl
+03Ckk7htAgyAQcBf90RJznZndv7HwVAi3syupi0sQDdOKNPyObR5FRtqyqud
+ttWmSdmGCGFcZ/fZqxQNsHB8QuYaBiGL7CDusES+wwfn8Q7BGtoJzOPDDx6K
+yIyox/flPx2DZDJIZrMz9b0V70a9kqKLo/wcGhvHO6coCyMxenBAacLJ1DiI
+NLKoYOoJTM7zcxsGnvCxaDZCvsmjx3j8Yc5r3i3ylllCQH2/lpr/xCvXFarG
+tG7+wts+UqstS9SThLBQ9Ojq4oPsX7HBHKvq19XU3/ChIgWMy+bczc5gpkC/
+eLAIGfJ0D5DJsl68vMXSmCoFK0HTwzzNa7lnZK4IutYPBNBCv0pWORQqDpsk
+Ez96YOGyB8+gtpFgCrkuV1bSB9SRVmEBfDtKPQFhKowAAAAA=.
+</pre></blockquote>
+
+    <p>We could fragment this message into (for example) three
+    pieces:</p>
+
+<blockquote><pre>
+?OTR,1,3,?OTR:AAEDAAAAAQAAAAEAAADAVf3Ei72ZgFeKqWvLMnuVPVCwxk
+tsOZ1QdjeLp6jn62mCVtlY9nS6sRkecpjuLYHRxyTdRu2iEVtSsjZqK55ovZ
+35SfkOPHeFYa9BIuxWi9djHMVKQ8KOVGAVLibjZ6P8LreDSKtWDv9YQjIEnk
+wFVGCPfpBq2SX4VTQfJAQXHggR8izKxPvluXUdG9rIPh4cac98++VLdIuFMi
+EXjUIoTX2rEzunaCLMy0VIfowlRsgsKGrwhCCv7hBWyglbzwz+AAAAAAAAAA
+QAAAF2SOr,
+</pre></blockquote>
+
+<blockquote><pre>
+?OTR,2,3,JvPUerB9mtf4bqQDFthfoz/XepysnYuReHHEXKe+BFkaEoMNGiB
+l4TCLZx72DvmZwKCewWRH1+W66ggrXKw2VdVl+vLsmzxNyWChGLfBTL5/3SU
+F09BfmCEl03Ckk7htAgyAQcBf90RJznZndv7HwVAi3syupi0sQDdOKNPyObR
+5FRtqyqudttWmSdmGCGFcZ/fZqxQNsHB8QuYaBiGL7CDusES+wwfn8Q7BGto
+JzOPDDx6KyIyox/flPx2DZDJIZrMz9b0V70a9kqKLo/wcGhvHO6coCyMxenB
+AacLJ1DiI,
+</pre></blockquote>
+
+<blockquote><pre>
+?OTR,3,3,NLKoYOoJTM7zcxsGnvCxaDZCvsmjx3j8Yc5r3i3ylllCQH2/lpr
+/xCvXFarGtG7+wts+UqstS9SThLBQ9Ojq4oPsX7HBHKvq19XU3/ChIgWMy+b
+czc5gpkC/eLAIGfJ0D5DJsl68vMXSmCoFK0HTwzzNa7lnZK4IutYPBNBCv0p
+WORQqDpskEz96YOGyB8+gtpFgCrkuV1bSB9SRVmEBfDtKPQFhKowAAAAA=.,
+</pre></blockquote>
+<h3>The protocol state machine</h3>
+<p>An OTR client maintains separate state for every correspondent.  For
+example, Alice may have an active OTR conversation with Bob, while
+having an unprotected conversation with Charlie.  This state consists of
+two main state variables, as well as some other information (such as
+encryption keys).  The two main state variables are:</p>
+<h4>Message state</h4>
+<p>The message state variable, msgstate, controls what happens to
+outgoing messages typed by the user.  It can take one of three
+values:</p>
+<dl>
+<dt>MSGSTATE_PLAINTEXT</dt>
+<dd>This state indicates that outgoing messages are sent without
+encryption.  This is the state that is used before an OTR conversation
+is initiated.  This is the initial state, and the only way to
+subsequently enter this state is for the user to explicitly request to
+do so via some UI operation.</dd>
+<dt>MSGSTATE_ENCRYPTED</dt>
+<dd>This state indicates that outgoing messages are sent encrypted.
+This is the state that is used during an OTR conversation.  The only way
+to enter this state is for the authentication state machine (below) to
+successfully complete.</dd>
+<dt>MSGSTATE_FINISHED</dt>
+<dd>This state indicates that outgoing messages are not delivered at
+all.  This state is entered only when the other party indicates he has
+terminated his side of the OTR conversation.  For example, if Alice and
+Bob are having an OTR conversation, and Bob instructs his OTR client to
+end its private session with Alice (for example, by logging out), Alice
+will be notified of this, and <em>her</em> client will switch to
+MSGSTATE_FINISHED mode.  This prevents Alice from accidentally sending a
+message to Bob in plaintext.  (Consider what happens if Alice was in the
+middle of typing a private message to Bob when he suddenly logs out,
+just as Alice hits Enter.)</dd>
+</dl>
+<h4>Authentication state</h4>
+<p>The authentication state variable, authstate, can take one of four
+values (plus one extra for OTR version 1 compatibility):</p>
+<dl>
+<dt>AUTHSTATE_NONE</dt>
+<dd>This state indicates that the authentication protocol is not
+currently in progress.  This is the initial state.</dd>
+<dt>AUTHSTATE_AWAITING_DHKEY</dt>
+<dd>After Bob initiates the authentication protocol by sending Alice
+the D-H Commit Message, he enters this state to await Alice's reply.</dd>
+<dt>AUTHSTATE_AWAITING_REVEALSIG</dt>
+<dd>After Alice receives Bob's D-H Commit Message, and replies with her
+own D-H Key Message, she enters this state to await Bob's reply.</dd>
+<dt>AUTHSTATE_AWAITING_SIG</dt>
+<dd>After Bob receives Alice's D-H Key Message, and replies with his own
+Reveal Signature Message, he enters this state to await Alice's reply.</dd>
+<dt>AUTHSTATE_V1_SETUP</dt>
+<dd>For OTR version 1 compatibility, if Bob sends a version 1 Key
+Exchange Message to Alice, he enters this state to await Alice's
+reply.</dd>
+</dl>
+<p>After:</p>
+<ul>
+<li>Alice (in AUTHSTATE_AWAITING_REVEALSIG) receives Bob's Reveal
+Signature Message (and replies with her own Signature Message),</li>
+<li>Alice (in AUTHSTATE_NONE) receives Bob's Version 1 Key Exchange
+Message (and replies with her own Key Exchange Message),</li>
+<li>Bob (in AUTHSTATE_AWAITING_SIG) receives Alice's Signature Message,
+<b>or</b></li>
+<li>Bob (in AUTHSTATE_V1_SETUP) receives Alice's Version 1 Key Exchange
+Message,</li>
+</ul>
+<p>then,
+assuming the signature verifications succeed, the msgstate
+variable is transitioned to MSGSTATE_ENCRYPTED.  Regardless of whether
+the signature verifications succeed, the authstate variable is
+transitioned to AUTHSTATE_NONE.</p>
+<h4>Policies</h4>
+<p>OTR clients can set different <b>policies</b> for different
+correspondents.  For example, Alice could set up her client so that it
+speaks only OTR version 2, except with Charlie, who she knows has only
+an old client; so that it will opportunistically start an OTR conversation
+whenever it detects the correspondent supports it; or so that it refuses
+to send non-encrypted messages to Bob, ever.</p>
+<p>The policies that can be set (on a global or per-correspondent basis)
+are any combination of the following boolean flags:</p>
+<dl>
+<dt>ALLOW_V1</dt>
+<dd>Allow version 1 of the OTR protocol to be used.</dd>
+<dt>ALLOW_V2</dt>
+<dd>Allow version 2 of the OTR protocol to be used.</dd>
+<dt>REQUIRE_ENCRYPTION</dt>
+<dd>Refuse to send unencrypted messages.</dd>
+<dt>SEND_WHITESPACE_TAG</dt>
+<dd>Advertise your support of OTR using the whitespace tag.</dd>
+<dt>WHITESPACE_START_AKE</dt>
+<dd>Start the OTR AKE when you receive a whitespace tag.</dd>
+<dt>ERROR_START_AKE</dt>
+<dd>Start the OTR AKE when you receive an OTR Error Message.</dd>
+</dl>
+<p>The four old version 1 policies correspond to the following
+combinations of flags (adding an allowance for version 2 of the
+protocol):</p>
+<dl>
+<dt>NEVER</dt>
+<dd>No flags set</dd>
+<dt>MANUAL</dt>
+<dd>ALLOW_V1 | ALLOW_V2</dd>
+<dt>OPPORTUNISTIC</dt>
+<dd>ALLOW_V1 | ALLOW_V2 | SEND_WHITESPACE_TAG | WHITESPACE_START_AKE |
+ERROR_START_AKE</dd>
+<dt>ALWAYS</dt>
+<dd>ALLOW_V1 | ALLOW_V2 | REQUIRE_ENCRYPTION | WHITESPACE_START_AKE |
+ERROR_START_AKE</dd>
+</dl>
+<p>Note that it is possible for UIs simply to offer the old
+"combinations" of options, and not ask about each one separately.</p>
+<h4>State transitions</h4>
+<p>There are thirteen actions an OTR client must handle:</p>
+<ul>
+<li>Received messages:
+<ul>
+<li>Plaintext without the whitespace tag</li>
+<li>Plaintext with the whitespace tag</li>
+<li>Query Message</li>
+<li>Error Message</li>
+<li>D-H Commit Message</li>
+<li>D-H Key Message</li>
+<li>Reveal Signature Message</li>
+<li>Signature Message</li>
+<li>Version 1 Key Exchange Message</li>
+<li>Data Message</li>
+</ul></li>
+<li>User actions:
+<ul>
+<li>User requests to start an OTR conversation</li>
+<li>User requests to end an OTR conversation</li>
+<li>User types a message to be sent</li>
+</ul></li>
+</ul>
+<p>The following sections will outline what actions to take in each
+case.  They all assume that at least one of ALLOW_V1 or ALLOW_V2 is set;
+if not, then OTR is completely disabled, and no special handling of
+messages should be done at all.</p>
+<h4>Receiving plaintext without the whitespace tag</h4>
+<dl>
+<dt>If msgstate is MSGSTATE_PLAINTEXT:</dt>
+<dd>Simply display the message to the user.  If REQUIRE_ENCRYPTION is
+set, warn him that the message was received unencrypted.</dd>
+<dt>If msgstate is MSGSTATE_ENCRYPTED or MSGSTATE_FINISHED:</dt>
+<dd>Display the message to the user, but warn him that the message was
+received unencrypted.</dd>
+</dl>
+<h4>Receiving plaintext with the whitespace tag</h4>
+<dl>
+<dt>If msgstate is MSGSTATE_PLAINTEXT:</dt>
+<dd>Remove the whitespace tag and display the message to the user.  If
+REQUIRE_ENCRYPTION is set, warn him that the message was received
+unencrypted.</dd>
+<dt>If msgstate is MSGSTATE_ENCRYPTED or MSGSTATE_FINISHED:</dt>
+<dd>Remove the whitespace tag and display the message to the user, but
+warn him that the message was received unencrypted.</dd>
+</dl>
+<p>In any event, if WHITESPACE_START_AKE is set:</p>
+<dl>
+<dt>If the tag offers OTR version 2 and ALLOW_V2 is set:</dt>
+<dd>Send a D-H Commit Message, and transition authstate to
+AUTHSTATE_AWAITING_DHKEY.</dd>
+<dt>Otherwise, if the tag offers OTR version 1 and ALLOW_V1 is set:</dt>
+<dd>Send a Version 1 Key Exchange Message, and transition authstate to
+AUTHSTATE_V1_SETUP.</dd>
+</dl>
+<h4>Receiving a Query Message</h4>
+<dl>
+<dt>If the Query Message offers OTR version 2 and ALLOW_V2 is set:</dt>
+<dd>Send a D-H Commit Message, and transition authstate to
+AUTHSTATE_AWAITING_DHKEY.</dd>
+<dt>Otherwise, if the message offers OTR version 1 and ALLOW_V1 is set:</dt>
+<dd>Send a Version 1 Key Exchange Message, and transition authstate to
+AUTHSTATE_V1_SETUP.</dd>
+</dl>
+<h4>Receiving an Error Message</h4>
+<p>Display the message to the user.  If ERROR_START_AKE is set, reply
+with a Query Message.</p>
+<h4>User requests to start an OTR conversation</h4>
+<p>Send an OTR Query Message to the correspondent.</p>
+<h4>Receiving a D-H Commit Message</h4>
+<p>If ALLOW_V2 is not set, ignore this message.  Otherwise:</p>
+<dl>
+<dt>If authstate is AUTHSTATE_NONE:</dt>
+<dd>Reply with a D-H Key Message, and transition authstate to
+AUTHSTATE_AWAITING_REVEALSIG.</dd>
+<dt>If authstate is AUTHSTATE_AWAITING_DHKEY:</dt>
+<dd>This is the trickiest transition in the whole protocol.  It
+indicates that you have already sent a D-H Commit message to your
+correspondent, but that he either didn't receive it, or just didn't
+receive it <em>yet</em>, and has sent you one as well.  The symmetry
+will be broken by comparing the hashed g<sup>x</sup> you sent in your
+D-H Commit Message with the one you received, considered as 32-byte
+unsigned big-endian values.
+<dl>
+<dt>If yours is the higher hash value:</dt>
+<dd>Ignore the incoming D-H Commit message, but resend your D-H
+Commit message.</dd>
+<dt>Otherwise:</dt>
+<dd>Forget your old g<sup>x</sup> value that you sent (encrypted)
+earlier, and pretend you're in AUTHSTATE_NONE; i.e. reply with a D-H Key
+Message, and transition authstate to AUTHSTATE_AWAITING_REVEALSIG.</dd>
+</dl></dd>
+<dt>If authstate is AUTHSTATE_AWAITING_REVEALSIG:</dt>
+<dd>Retransmit your D-H Key Message (the same
+one as you sent when you entered AUTHSTATE_AWAITING_REVEALSIG).  Forget
+the old D-H Commit message, and use this new one instead.  There
+are a number of reasons this might happen, including:
+<ul>
+<li>Your correspondent simply started a new AKE.</li>
+<li>Your correspondent resent his D-H Commit message, as specified
+above.</li>
+<li>On some networks, like AIM, if your correspondent is logged in
+multiple times, each of his clients will send a D-H Commit Message in
+response to a Query Message; resending the same D-H Key Message in
+response to each of those messages will prevent compounded confusion,
+since each of his clients will see each of the D-H Key Messages you
+send.  [And the problem gets even worse if you are <em>each</em> logged
+in multiple times.]</li>
+</ul></dd>
+<dt>If authstate is AUTHSTATE_AWAITING_SIG or AUTHSTATE_V1_SETUP:</dt>
+<dd>Reply with a new D-H Key message, and transition authstate to
+AUTHSTATE_AWAITING_REVEALSIG.</dd>
+</dl>
+<h4>Receiving a D-H Key Message</h4>
+<p>If ALLOW_V2 is not set, ignore this message.  Otherwise:</p>
+<dl>
+<dt>If authstate is AUTHSTATE_AWAITING_DHKEY:</dt>
+<dd>Reply with a Reveal Signature Message and transition authstate to
+AUTHSTATE_AWAITING_SIG.</dd>
+<dt>If authstate is AUTHSTATE_AWAITING_SIG:</dt>
+<dd>
+<dl>
+<dt>If this D-H Key message is the same the one you received earlier
+(when you entered AUTHSTATE_AWAITING_SIG):</dt>
+<dd>Retransmit your Reveal Signature Message.</dd>
+<dt>Otherwise:</dt>
+<dd>Ignore the message.</dd>
+</dl></dd>
+<dt>If authstate is AUTHSTATE_NONE, AUTHSTATE_AWAITING_REVEALSIG, or
+AUTHSTATE_V1_SETUP:</dt>
+<dd>Ignore the message.</dd>
+</dl>
+<h4>Receiving a Reveal Signature Message</h4>
+<p>If ALLOW_V2 is not set, ignore this message.  Otherwise:</p>
+<dl>
+<dt>If authstate is AUTHSTATE_AWAITING_REVEALSIG:</dt>
+<dd>Use the received value of r to decrypt the value of g<sup>x</sup>
+received in the D-H Commit Message, and verify the hash therein.
+Decrypt the encrypted signature, and verify the signature and the MACs.
+If everything checks out:
+<ul>
+<li>Reply with a Signature Message.</li>
+<li>Transition authstate to AUTHSTATE_NONE.</li>
+<li>Transition msgstate to MSGSTATE_ENCRYPTED.</li>
+<li>If there is a recent stored message, encrypt it and send it as a
+Data Message.</li>
+</ul>
+Otherwise, ignore the message.</dd>
+<dt>If authstate is AUTHSTATE_NONE, AUTHSTATE_AWAITING_DHKEY, 
+AUTHSTATE_AWAITING_SIG, or AUTHSTATE_V1_SETUP:</dt>
+<dd>Ignore the message.</dd>
+</dl>
+<h4>Receiving a Signature Message</h4>
+<p>If ALLOW_V2 is not set, ignore this message.  Otherwise:</p>
+<dl>
+<dt>If authstate is AUTHSTATE_AWAITING_SIG:</dt>
+<dd>Decrypt the encrypted signature, and verify the signature and the MACs.
+If everything checks out:
+<ul>
+<li>Transition authstate to AUTHSTATE_NONE.</li>
+<li>Transition msgstate to MSGSTATE_ENCRYPTED.</li>
+<li>If there is a recent stored message, encrypt it and send it as a
+Data Message.</li>
+</ul>
+Otherwise, ignore the message.</dd>
+<dt>If authstate is AUTHSTATE_NONE, AUTHSTATE_AWAITING_DHKEY, 
+AUTHSTATE_AWAITING_REVEALSIG, or AUTHSTATE_V1_SETUP:</dt>
+<dd>Ignore the message.</dd>
+</dl>
+<h4>Receiving a Version 1 Key Exchange Message</h4>
+<p>If ALLOW_V1 is not set, ignore this message.  Otherwise:</p>
+<dl>
+<dt>If authstate is AUTHSTATE_NONE, AUTHSTATE_AWAITING_DHKEY,
+AUTHSTATE_AWAITING_REVEALSIG, or AUTHSTATE_AWAITING_SIG:</dt>
+<dd><dl><dt>If the reply field is not set to 0x01:</dt>
+<dd>Verify the information in the Key Exchange Message.  If the
+verification succeeds:
+<ul>
+<li>Reply with a Key Exchange Message with the reply field set to
+0x01.</li>
+<li>Transition authstate to AUTHSTATE_NONE.</li>
+<li>Transition msgstate to MSGSTATE_ENCRYPTED.</li>
+<li>If there is a recent stored message, encrypt it and send it as a
+Data Message.</li>
+</ul>
+Otherwise, ignore the message.</dd>
+<dt>Otherwise, ignore the message.</dt></dl></dd>
+<dt>If authstate is AUTHSTATE_V1_SETUP:</dt>
+<dd>Verify the information in the Key Exchange Message.  If the
+verification succeeds:
+<ul>
+<li>If the received Key Exchange Message did not have the reply field
+set to 0x01, reply with a Key Exchange Message with the reply field set
+to 0x01.</li>
+<li>Transition authstate to AUTHSTATE_NONE.</li>
+<li>Transition msgstate to MSGSTATE_ENCRYPTED.</li>
+<li>If there is a recent stored message, encrypt it and send it as a
+Data Message.</li>
+</ul>
+Otherwise, ignore the message.</dd>
+</dl>
+<h4>User types a message to be sent</h4>
+<dl>
+<dt>If msgstate is MSGSTATE_PLAINTEXT:</dt>
+<dd><dl><dt>If REQUIRE_ENCRYPTION is set:</dt>
+<dd>Store the plaintext message for possible retransmission, and send a
+Query Message.</dd>
+<dt>Otherwise:</dt>
+<dd>If SEND_WHITESPACE_TAG is set, and you have not received a plaintext
+message from this correspondent since last entering MSGSTATE_PLAINTEXT,
+attach the whitespace tag to the message.  Send the (possibly modified)
+message as plaintext.</dd></dl></dd>
+<dt>If msgstate is MSGSTATE_ENCRYPTED:</dt>
+<dd>Encrypt the message, and send it as a Data Message.  Store the
+plaintext message for possible retransmission.</dd>
+<dt>If msgstate is MSGSTATE_FINISHED:</dt>
+<dd>Inform the user that the message cannot be sent at this time.  Store
+the plaintext message for possible retransmission.</dd>
+</dl>
+<h4>Receiving a Data Message</h4>
+<dl>
+<dt>If msgstate is MSGSTATE_ENCRYPTED:</dt>
+<dd>Verify the information (MAC, keyids, ctr value, etc.) in the
+message.
+<dl>
+<dt>If the verification succeeds:</dt>
+<dd>
+<ul>
+<li>Decrypt the message and display the human-readable part (if
+non-empty) to the user.</li>
+<li>Update the D-H encryption keys, if necessary.</li>
+<li>If you have not sent a message to this correspondent in some
+(configurable) time, send a "heartbeat" message, consisting of a Data
+Message encoding an empty plaintext.</li>
+<li>If the received message contains a TLV type 1, forget all encryption
+keys for this correspondent, and transition msgstate to
+MSGSTATE_FINISHED.</li>
+</ul>
+</dd>
+<dt>Otherwise, inform the user that an unreadable encrypted message was
+received, and reply with an Error Message.</dt>
+</dl></dd>
+<dt>If msgstate is MSGSTATE_PLAINTEXT or MSGSTATE_FINISHED:</dt>
+<dd>Inform the user that an unreadable encrypted message was received,
+and reply with an Error Message.</dd>
+</dl>
+<h4>User requests to end an OTR conversation</h4>
+<dl>
+<dt>If msgstate is MSGSTATE_PLAINTEXT:</dt>
+<dd>Do nothing.</dd>
+<dt>If msgstate is MSGSTATE_ENCRYPTED:</dt>
+<dd>Send a Data Message, encoding a message with an empty human-readable
+part, and TLV type 1.  Transition msgstate to MSGSTATE_PLAINTEXT.</dd>
+<dt>If msgstate is MSGSTATE_FINISHED:</dt>
+<dd>Transition msgstate to MSGSTATE_PLAINTEXT.</dd>
+</dl>
+</body></html>
diff --git a/README b/README
index 1879d1a..2dfb4c1 100644
--- a/README
+++ b/README
@@ -179,7 +179,7 @@ Here are the six programs in the toolkit:
 
  - otr_parse
    - Parse OTR messages given on stdin, showing the values of all the
-     fields in OTR Key Exchange Messages and OTR Data Messages.
+     fields in OTR protocol messages.
 
  - otr_sesskeys our_privkey their_pubkey
    - Shows our public key, the session id, two AES and two MAC keys
diff --git a/src/Makefile.am b/src/Makefile.am
index 04d7d05..933aa11 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -3,11 +3,11 @@ INCLUDES = @LIBGCRYPT_CFLAGS@
 lib_LTLIBRARIES = libotr.la
 
 libotr_la_SOURCES = privkey.c context.c proto.c b64.c dh.c mem.c message.c \
-		    userstate.c tlv.c
+		    userstate.c tlv.c auth.c
 
 libotr_la_LDFLAGS = -version-info @LIBOTR_LIBTOOL_VERSION@ @LIBS@ @LIBGCRYPT_LIBS@
 
 otrincdir = $(includedir)/libotr
 
 otrinc_HEADERS = b64.h context.h dh.h mem.h message.h privkey.h proto.h \
-		 version.h userstate.h tlv.h
+		 version.h userstate.h tlv.h serial.h auth.h privkey-t.h
diff --git a/src/auth.c b/src/auth.c
new file mode 100644
index 0000000..067beb7
--- /dev/null
+++ b/src/auth.c
@@ -0,0 +1,1427 @@
+/*
+ *  Off-the-Record Messaging library
+ *  Copyright (C) 2004-2005  Nikita Borisov and Ian Goldberg
+ *                           <otr at cypherpunks.ca>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of version 2.1 of the GNU Lesser General
+ *  Public License as published by the Free Software Foundation.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* system headers */
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/* libotr headers */
+#include "b64.h"
+#include "privkey.h"
+#include "auth.h"
+#include "serial.h"
+
+/*
+ * Initialize the fields of an OtrlAuthInfo (already allocated).
+ */
+void otrl_auth_new(OtrlAuthInfo *auth)
+{
+    auth->authstate = OTRL_AUTHSTATE_NONE;
+    otrl_dh_keypair_init(&(auth->our_dh));
+    auth->our_keyid = 0;
+    auth->encgx = NULL;
+    auth->encgx_len = 0;
+    memset(auth->r, 0, 16);
+    memset(auth->hashgx, 0, 32);
+    auth->their_pub = NULL;
+    auth->their_keyid = 0;
+    auth->enc_c = NULL;
+    auth->enc_cp = NULL;
+    auth->mac_m1 = NULL;
+    auth->mac_m1p = NULL;
+    auth->mac_m2 = NULL;
+    auth->mac_m2p = NULL;
+    memset(auth->their_fingerprint, 0, 20);
+    auth->initiated = 0;
+    auth->protocol_version = 0;
+    memset(auth->secure_session_id, 0, 20);
+    auth->secure_session_id_len = 0;
+    auth->lastauthmsg = NULL;
+}
+
+/*
+ * Clear the fields of an OtrlAuthInfo (but leave it allocated).
+ */
+void otrl_auth_clear(OtrlAuthInfo *auth)
+{
+    auth->authstate = OTRL_AUTHSTATE_NONE;
+    otrl_dh_keypair_free(&(auth->our_dh));
+    auth->our_keyid = 0;
+    free(auth->encgx);
+    auth->encgx = NULL;
+    auth->encgx_len = 0;
+    memset(auth->r, 0, 16);
+    memset(auth->hashgx, 0, 32);
+    gcry_mpi_release(auth->their_pub);
+    auth->their_pub = NULL;
+    auth->their_keyid = 0;
+    gcry_cipher_close(auth->enc_c);
+    gcry_cipher_close(auth->enc_cp);
+    gcry_md_close(auth->mac_m1);
+    gcry_md_close(auth->mac_m1p);
+    gcry_md_close(auth->mac_m2);
+    gcry_md_close(auth->mac_m2p);
+    auth->enc_c = NULL;
+    auth->enc_cp = NULL;
+    auth->mac_m1 = NULL;
+    auth->mac_m1p = NULL;
+    auth->mac_m2 = NULL;
+    auth->mac_m2p = NULL;
+    memset(auth->their_fingerprint, 0, 20);
+    auth->initiated = 0;
+    auth->protocol_version = 0;
+    memset(auth->secure_session_id, 0, 20);
+    auth->secure_session_id_len = 0;
+    free(auth->lastauthmsg);
+    auth->lastauthmsg = NULL;
+}
+
+/*
+ * Start a fresh AKE (version 2) using the given OtrlAuthInfo.  If
+ * our_dh is NULL, generate a fresh DH keypair to use.  Otherwise, use a
+ * copy of the one passed (with the given keyid).  If no error is
+ * returned, the message to transmit will be contained in
+ * auth->lastauthmsg.
+ */
+gcry_error_t otrl_auth_start_v2(OtrlAuthInfo *auth, DH_keypair *our_dh,
+	unsigned int our_keyid)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    size_t npub;
+    gcry_cipher_hd_t enc = NULL;
+    unsigned char ctr[16];
+    unsigned char *buf, *bufp;
+    size_t buflen, lenp;
+
+    /* Clear out this OtrlAuthInfo and start over */
+    otrl_auth_clear(auth);
+    auth->initiated = 1;
+
+    /* Import the given DH keypair, or else create a fresh one */
+    if (our_dh) {
+	otrl_dh_keypair_copy(&(auth->our_dh), our_dh);
+	auth->our_keyid = our_keyid;
+    } else {
+	otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
+	auth->our_keyid = 1;
+    }
+
+    /* Pick an encryption key */
+    gcry_randomize(auth->r, 16, GCRY_STRONG_RANDOM);
+
+    /* Allocate space for the encrypted g^x */
+    gcry_mpi_print(format, NULL, 0, &npub, auth->our_dh.pub);
+    auth->encgx = malloc(4+npub);
+    if (auth->encgx == NULL) goto memerr;
+    auth->encgx_len = 4+npub;
+    bufp = auth->encgx;
+    lenp = auth->encgx_len;
+    write_mpi(auth->our_dh.pub, npub, "g^x");
+    assert(lenp == 0);
+
+    /* Hash g^x */
+    gcry_md_hash_buffer(GCRY_MD_SHA256, auth->hashgx, auth->encgx,
+	    auth->encgx_len);
+
+    /* Encrypt g^x using the key r */
+    err = gcry_cipher_open(&enc, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CTR,
+	    GCRY_CIPHER_SECURE);
+    if (err) goto err;
+
+    err = gcry_cipher_setkey(enc, auth->r, 16);
+    if (err) goto err;
+
+    memset(ctr, 0, 16);
+    err = gcry_cipher_setctr(enc, ctr, 16);
+    if (err) goto err;
+
+    err = gcry_cipher_encrypt(enc, auth->encgx, auth->encgx_len, NULL, 0);
+    if (err) goto err;
+
+    gcry_cipher_close(enc);
+    enc = NULL;
+
+    /* Now serialize the message */
+    lenp = 3 + 4 + auth->encgx_len + 4 + 32;
+    bufp = malloc(lenp);
+    if (bufp == NULL) goto memerr;
+    buf = bufp;
+    buflen = lenp;
+
+    memmove(bufp, "\x00\x02\x02", 3); /* header */
+    debug_data("Header", bufp, 3);
+    bufp += 3; lenp -= 3;
+
+    /* Encrypted g^x */
+    write_int(auth->encgx_len);
+    debug_int("Enc gx len", bufp-4);
+    memmove(bufp, auth->encgx, auth->encgx_len);
+    debug_data("Enc gx", bufp, auth->encgx_len);
+    bufp += auth->encgx_len; lenp -= auth->encgx_len;
+
+    /* Hashed g^x */
+    write_int(32);
+    debug_int("hashgx len", bufp-4);
+    memmove(bufp, auth->hashgx, 32);
+    debug_data("hashgx", bufp, 32);
+    bufp += 32; lenp -= 32;
+
+    assert(lenp == 0);
+
+    auth->lastauthmsg = otrl_base64_otr_encode(buf, buflen);
+    free(buf);
+    if (auth->lastauthmsg == NULL) goto memerr;
+    auth->authstate = OTRL_AUTHSTATE_AWAITING_DHKEY;
+
+    return err;
+
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    otrl_auth_clear(auth);
+    gcry_cipher_close(enc);
+    return err;
+}
+
+/*
+ * Create a D-H Key Message using the our_dh value in the given auth,
+ * and store it in auth->lastauthmsg.
+ */
+static gcry_error_t create_key_message(OtrlAuthInfo *auth)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    unsigned char *buf, *bufp;
+    size_t buflen, lenp;
+    size_t npub;
+
+    gcry_mpi_print(format, NULL, 0, &npub, auth->our_dh.pub);
+    buflen = 3 + 4 + npub;
+    buf = malloc(buflen);
+    if (buf == NULL) goto memerr;
+    bufp = buf;
+    lenp = buflen;
+
+    memmove(bufp, "\x00\x02\x0a", 3); /* header */
+    debug_data("Header", bufp, 3);
+    bufp += 3; lenp -= 3;
+
+    /* g^y */
+    write_mpi(auth->our_dh.pub, npub, "g^y");
+
+    assert(lenp == 0);
+
+    free(auth->lastauthmsg);
+    auth->lastauthmsg = otrl_base64_otr_encode(buf, buflen);
+    free(buf);
+    if (auth->lastauthmsg == NULL) goto memerr;
+
+    return err;
+
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+    return err;
+}
+
+/*
+ * Handle an incoming D-H Commit Message.  If no error is returned, the
+ * message to send will be left in auth->lastauthmsg.  If non-NULL, use
+ * a copy of the given D-H keypair, with the given keyid.
+ */
+gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth, const char *commitmsg,
+	DH_keypair *our_dh, unsigned int our_keyid)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp = NULL, *encbuf = NULL;
+    unsigned char hashbuf[32];
+    size_t buflen, lenp, enclen, hashlen;
+    int res;
+
+    res = otrl_base64_otr_decode(commitmsg, &buf, &buflen);
+    if (res == -1) goto memerr;
+    if (res == -2) goto invval;
+
+    bufp = buf;
+    lenp = buflen;
+
+    /* Header */
+    require_len(3);
+    if (memcmp(bufp, "\x00\x02\x02", 3)) goto invval;
+    bufp += 3; lenp -= 3;
+
+    /* Encrypted g^x */
+    read_int(enclen);
+    require_len(enclen);
+    encbuf = malloc(enclen);
+    if (encbuf == NULL && enclen > 0) goto memerr;
+    memmove(encbuf, bufp, enclen);
+    bufp += enclen; lenp -= enclen;
+
+    /* Hashed g^x */
+    read_int(hashlen);
+    if (hashlen != 32) goto invval;
+    require_len(32);
+    memmove(hashbuf, bufp, 32);
+    bufp += 32; lenp -= 32;
+
+    if (lenp != 0) goto invval;
+    free(buf);
+    buf = NULL;
+
+    switch(auth->authstate) {
+	case OTRL_AUTHSTATE_NONE:
+	case OTRL_AUTHSTATE_AWAITING_SIG:
+	case OTRL_AUTHSTATE_V1_SETUP:
+
+	    /* Store the incoming information */
+	    otrl_auth_clear(auth);
+	    if (our_dh) {
+		otrl_dh_keypair_copy(&(auth->our_dh), our_dh);
+		auth->our_keyid = our_keyid;
+	    } else {
+		otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
+		auth->our_keyid = 1;
+	    }
+	    auth->encgx = encbuf;
+	    encbuf = NULL;
+	    auth->encgx_len = enclen;
+	    memmove(auth->hashgx, hashbuf, 32);
+
+	    /* Create a D-H Key Message */
+	    err = create_key_message(auth);
+	    if (err) goto err;
+	    auth->authstate = OTRL_AUTHSTATE_AWAITING_REVEALSIG;
+
+	    break;
+
+	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	    /* We sent a D-H Commit Message, and we also received one
+	     * back.  Compare the hashgx values to see which one wins. */
+	    if (memcmp(auth->hashgx, hashbuf, 32) > 0) {
+		/* Ours wins.  Ignore the message we received, and just
+		 * resend the same D-H Commit message again. */
+		free(encbuf);
+		encbuf = NULL;
+	    } else {
+		/* Ours loses.  Use the incoming parameters instead. */
+		otrl_auth_clear(auth);
+		if (our_dh) {
+		    otrl_dh_keypair_copy(&(auth->our_dh), our_dh);
+		    auth->our_keyid = our_keyid;
+		} else {
+		    otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
+		    auth->our_keyid = 1;
+		}
+		auth->encgx = encbuf;
+		encbuf = NULL;
+		auth->encgx_len = enclen;
+		memmove(auth->hashgx, hashbuf, 32);
+
+		/* Create a D-H Key Message */
+		err = create_key_message(auth);
+		if (err) goto err;
+		auth->authstate = OTRL_AUTHSTATE_AWAITING_REVEALSIG;
+	    }
+	    break;
+	case OTRL_AUTHSTATE_AWAITING_REVEALSIG:
+	    /* Use the incoming parameters, but just retransmit the old
+	     * D-H Key Message. */
+	    free(auth->encgx);
+	    auth->encgx = encbuf;
+	    encbuf = NULL;
+	    auth->encgx_len = enclen;
+	    memmove(auth->hashgx, hashbuf, 32);
+	    break;
+    }
+
+    return err;
+
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(encbuf);
+    return err;
+}
+
+/*
+ * Calculate the encrypted part of the Reveal Signature and Signature
+ * Messages, given a MAC key, an encryption key, two DH public keys, an
+ * authentication public key (contained in an OtrlPrivKey structure),
+ * and a keyid.  If no error is returned, *authbufp will point to the
+ * result, and *authlenp will point to its length.
+ */
+static gcry_error_t calculate_pubkey_auth(unsigned char **authbufp,
+	size_t *authlenp, gcry_md_hd_t mackey, gcry_cipher_hd_t enckey,
+	gcry_mpi_t our_dh_pub, gcry_mpi_t their_dh_pub,
+	OtrlPrivKey *privkey, unsigned int keyid)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    size_t ourpublen, theirpublen, totallen, lenp;
+    unsigned char *buf = NULL, *bufp = NULL;
+    unsigned char macbuf[32];
+    unsigned char *sigbuf = NULL;
+    size_t siglen;
+
+    /* How big are the DH public keys? */
+    gcry_mpi_print(format, NULL, 0, &ourpublen, our_dh_pub);
+    gcry_mpi_print(format, NULL, 0, &theirpublen, their_dh_pub);
+
+    /* How big is the total structure to be MAC'd? */
+    totallen = 4 + ourpublen + 4 + theirpublen + 2 + privkey->pubkey_datalen
+	+ 4;
+    buf = malloc(totallen);
+    if (buf == NULL) goto memerr;
+
+    bufp = buf;
+    lenp = totallen;
+
+    /* Write the data to be MAC'd */
+    write_mpi(our_dh_pub, ourpublen, "Our DH pubkey");
+    write_mpi(their_dh_pub, theirpublen, "Their DH pubkey");
+    bufp[0] = ((privkey->pubkey_type) >> 16) & 0xff;
+    bufp[1] = (privkey->pubkey_type) & 0xff;
+    bufp += 2; lenp -= 2;
+    memmove(bufp, privkey->pubkey_data, privkey->pubkey_datalen);
+    debug_data("Pubkey", bufp, privkey->pubkey_datalen);
+    bufp += privkey->pubkey_datalen; lenp -= privkey->pubkey_datalen;
+    write_int(keyid);
+    debug_int("Keyid", bufp-4);
+
+    assert(lenp == 0);
+
+    /* Do the MAC */
+    gcry_md_reset(mackey);
+    gcry_md_write(mackey, buf, totallen);
+    memmove(macbuf, gcry_md_read(mackey, GCRY_MD_SHA256), 32);
+
+    free(buf);
+    buf = NULL;
+
+    /* Sign the MAC */
+    err = otrl_privkey_sign(&sigbuf, &siglen, privkey, macbuf, 32);
+    if (err) goto err;
+
+    /* Calculate the total size of the structure to be encrypted */
+    totallen = 2 + privkey->pubkey_datalen + 4 + siglen;
+    buf = malloc(totallen);
+    if (buf == NULL) goto memerr;
+    bufp = buf;
+    lenp = totallen;
+
+    /* Write the data to be encrypted */
+    bufp[0] = ((privkey->pubkey_type) >> 16) & 0xff;
+    bufp[1] = (privkey->pubkey_type) & 0xff;
+    bufp += 2; lenp -= 2;
+    memmove(bufp, privkey->pubkey_data, privkey->pubkey_datalen);
+    debug_data("Pubkey", bufp, privkey->pubkey_datalen);
+    bufp += privkey->pubkey_datalen; lenp -= privkey->pubkey_datalen;
+    write_int(keyid);
+    debug_int("Keyid", bufp-4);
+    memmove(bufp, sigbuf, siglen);
+    debug_data("Signature", bufp, siglen);
+    bufp += siglen; lenp -= siglen;
+    free(sigbuf);
+    sigbuf = NULL;
+
+    assert(lenp == 0);
+
+    /* Now do the encryption */
+    err = gcry_cipher_encrypt(enckey, buf, totallen, NULL, 0);
+    if (err) goto err;
+
+    *authbufp = buf;
+    buf = NULL;
+    *authlenp = totallen;
+
+    return err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(sigbuf);
+    return err;
+}
+
+/*
+ * Decrypt the authenticator in the Reveal Signature and Signature
+ * Messages, given a MAC key, and encryption key, and two DH public
+ * keys.  The fingerprint of the received public key will get put into
+ * fingerprintbufp, and the received keyid will get put in *keyidp.
+ * The encrypted data pointed to by authbuf will be decrypted in place.
+ */
+static gcry_error_t check_pubkey_auth(unsigned char fingerprintbufp[20],
+	unsigned int *keyidp, unsigned char *authbuf, size_t authlen,
+	gcry_md_hd_t mackey, gcry_cipher_hd_t enckey,
+	gcry_mpi_t our_dh_pub, gcry_mpi_t their_dh_pub)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    size_t ourpublen, theirpublen, totallen, lenp;
+    unsigned char *buf = NULL, *bufp = NULL;
+    unsigned char macbuf[32];
+    unsigned short pubkey_type;
+    gcry_mpi_t p,q,g,y;
+    gcry_sexp_t pubs = NULL;
+    unsigned int received_keyid;
+    unsigned char *fingerprintstart, *fingerprintend, *sigbuf;
+    size_t siglen;
+
+    /* Start by decrypting it */
+    err = gcry_cipher_decrypt(enckey, authbuf, authlen, NULL, 0);
+    if (err) goto err;
+
+    bufp = authbuf;
+    lenp = authlen;
+
+    /* Get the public key and calculate its fingerprint */
+    require_len(2);
+    pubkey_type = (bufp[0] << 8) + bufp[1];
+    bufp += 2; lenp -= 2;
+    if (pubkey_type != OTRL_PUBKEY_TYPE_DSA) goto invval;
+    fingerprintstart = bufp;
+    read_mpi(p);
+    read_mpi(q);
+    read_mpi(g);
+    read_mpi(y);
+    fingerprintend = bufp;
+    gcry_md_hash_buffer(GCRY_MD_SHA1, fingerprintbufp,
+	    fingerprintstart, fingerprintend-fingerprintstart);
+    gcry_sexp_build(&pubs, NULL,
+	"(public-key (dsa (p %m)(q %m)(g %m)(y %m)))", p, q, g, y);
+    gcry_mpi_release(p);
+    gcry_mpi_release(q);
+    gcry_mpi_release(g);
+    gcry_mpi_release(y);
+
+    /* Get the keyid */
+    read_int(received_keyid);
+    if (received_keyid == 0) goto invval;
+
+    /* Get the signature */
+    sigbuf = bufp;
+    siglen = lenp;
+
+    /* How big are the DH public keys? */
+    gcry_mpi_print(format, NULL, 0, &ourpublen, our_dh_pub);
+    gcry_mpi_print(format, NULL, 0, &theirpublen, their_dh_pub);
+
+    /* Now calculate the message to be MAC'd. */
+    totallen = 4 + ourpublen + 4 + theirpublen + 2 +
+	(fingerprintend - fingerprintstart) + 4;
+    buf = malloc(totallen);
+    if (buf == NULL) goto memerr;
+
+    bufp = buf;
+    lenp = totallen;
+
+    write_mpi(their_dh_pub, theirpublen, "Their DH pubkey");
+    write_mpi(our_dh_pub, ourpublen, "Our DH pubkey");
+    bufp[0] = (pubkey_type >> 16) & 0xff;
+    bufp[1] = pubkey_type & 0xff;
+    bufp += 2; lenp -= 2;
+    memmove(bufp, fingerprintstart, fingerprintend - fingerprintstart);
+    debug_data("Pubkey", bufp, fingerprintend - fingerprintstart);
+    bufp += fingerprintend - fingerprintstart;
+    lenp -= fingerprintend - fingerprintstart;
+    write_int(received_keyid);
+    debug_int("Keyid", bufp-4);
+
+    assert(lenp == 0);
+
+    /* Do the MAC */
+    gcry_md_reset(mackey);
+    gcry_md_write(mackey, buf, totallen);
+    memmove(macbuf, gcry_md_read(mackey, GCRY_MD_SHA256), 32);
+
+    free(buf);
+    buf = NULL;
+
+    /* Verify the signature on the MAC */
+    err = otrl_privkey_verify(sigbuf, siglen, pubkey_type, pubs, macbuf, 32);
+    if (err) goto err;
+    gcry_sexp_release(pubs);
+    pubs = NULL;
+
+    /* Everything checked out */
+    *keyidp = received_keyid;
+
+    return err;
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    gcry_sexp_release(pubs);
+    return err;
+}
+
+/*
+ * Create a Reveal Signature Message using the values in the given auth,
+ * and store it in auth->lastauthmsg.  Use the given privkey to sign the
+ * message.
+ */
+static gcry_error_t create_revealsig_message(OtrlAuthInfo *auth,
+	OtrlPrivKey *privkey)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp, *startmac;
+    size_t buflen, lenp;
+
+    unsigned char *authbuf = NULL;
+    size_t authlen;
+
+    /* Get the encrypted authenticator */
+    err = calculate_pubkey_auth(&authbuf, &authlen, auth->mac_m1, auth->enc_c,
+	    auth->our_dh.pub, auth->their_pub, privkey, auth->our_keyid);
+    if (err) goto err;
+
+    buflen = 3 + 4 + 16 + 4 + authlen + 20;
+    buf = malloc(buflen);
+    if (buf == NULL) goto memerr;
+
+    bufp = buf;
+    lenp = buflen;
+
+    memmove(bufp, "\x00\x02\x11", 3); /* header */
+    debug_data("Header", bufp, 3);
+    bufp += 3; lenp -= 3;
+
+    /* r */
+    write_int(16);
+    memmove(bufp, auth->r, 16);
+    debug_data("r", bufp, 16);
+    bufp += 16; lenp -= 16;
+
+    /* Encrypted authenticator */
+    startmac = bufp;
+    write_int(authlen);
+    memmove(bufp, authbuf, authlen);
+    debug_data("auth", bufp, authlen);
+    bufp += authlen; lenp -= authlen;
+    free(authbuf);
+    authbuf = NULL;
+
+    /* MAC it, but only take the first 20 bytes */
+    gcry_md_reset(auth->mac_m2);
+    gcry_md_write(auth->mac_m2, startmac, bufp - startmac);
+    memmove(bufp, gcry_md_read(auth->mac_m2, GCRY_MD_SHA256), 20);
+    debug_data("MAC", bufp, 20);
+    bufp += 20; lenp -= 20;
+
+    assert(lenp == 0);
+
+    free(auth->lastauthmsg);
+    auth->lastauthmsg = otrl_base64_otr_encode(buf, buflen);
+    if (auth->lastauthmsg == NULL) goto memerr;
+    free(buf);
+    buf = NULL;
+
+    return err;
+
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(authbuf);
+    return err;
+}
+
+/*
+ * Create a Signature Message using the values in the given auth, and
+ * store it in auth->lastauthmsg.  Use the given privkey to sign the
+ * message.
+ */
+static gcry_error_t create_signature_message(OtrlAuthInfo *auth,
+	OtrlPrivKey *privkey)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp, *startmac;
+    size_t buflen, lenp;
+
+    unsigned char *authbuf = NULL;
+    size_t authlen;
+
+    /* Get the encrypted authenticator */
+    err = calculate_pubkey_auth(&authbuf, &authlen, auth->mac_m1p,
+	    auth->enc_cp, auth->our_dh.pub, auth->their_pub, privkey,
+	    auth->our_keyid);
+    if (err) goto err;
+
+    buflen = 3 + 4 + authlen + 20;
+    buf = malloc(buflen);
+    if (buf == NULL) goto memerr;
+
+    bufp = buf;
+    lenp = buflen;
+
+    memmove(bufp, "\x00\x02\x12", 3); /* header */
+    debug_data("Header", bufp, 3);
+    bufp += 3; lenp -= 3;
+
+    /* Encrypted authenticator */
+    startmac = bufp;
+    write_int(authlen);
+    memmove(bufp, authbuf, authlen);
+    debug_data("auth", bufp, authlen);
+    bufp += authlen; lenp -= authlen;
+    free(authbuf);
+    authbuf = NULL;
+
+    /* MAC it, but only take the first 20 bytes */
+    gcry_md_reset(auth->mac_m2p);
+    gcry_md_write(auth->mac_m2p, startmac, bufp - startmac);
+    memmove(bufp, gcry_md_read(auth->mac_m2p, GCRY_MD_SHA256), 20);
+    debug_data("MAC", bufp, 20);
+    bufp += 20; lenp -= 20;
+
+    assert(lenp == 0);
+
+    free(auth->lastauthmsg);
+    auth->lastauthmsg = otrl_base64_otr_encode(buf, buflen);
+    if (auth->lastauthmsg == NULL) goto memerr;
+    free(buf);
+    buf = NULL;
+
+    return err;
+
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(authbuf);
+    return err;
+}
+
+/*
+ * Handle an incoming D-H Key Message.  If no error is returned, and
+ * *havemsgp is 1, the message to sent will be left in auth->lastauthmsg.
+ * Use the given private authentication key to sign messages.
+ */
+gcry_error_t otrl_auth_handle_key(OtrlAuthInfo *auth, const char *keymsg,
+	int *havemsgp, OtrlPrivKey *privkey)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp = NULL;
+    size_t buflen, lenp;
+    gcry_mpi_t incoming_pub = NULL;
+    int res;
+
+    *havemsgp = 0;
+
+    res = otrl_base64_otr_decode(keymsg, &buf, &buflen);
+    if (res == -1) goto memerr;
+    if (res == -2) goto invval;
+
+    bufp = buf;
+    lenp = buflen;
+
+    /* Header */
+    if (memcmp(bufp, "\x00\x02\x0a", 3)) goto invval;
+    bufp += 3; lenp -= 3;
+
+    /* g^y */
+    read_mpi(incoming_pub);
+
+    if (lenp != 0) goto invval;
+    free(buf);
+    buf = NULL;
+
+    switch(auth->authstate) {
+	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	    /* Store the incoming public key */
+	    gcry_mpi_release(auth->their_pub);
+	    auth->their_pub = incoming_pub;
+	    incoming_pub = NULL;
+
+	    /* Compute the encryption and MAC keys */
+	    err = otrl_dh_compute_v2_auth_keys(&(auth->our_dh),
+		    auth->their_pub, auth->secure_session_id,
+		    &(auth->secure_session_id_len),
+		    &(auth->enc_c), &(auth->enc_cp),
+		    &(auth->mac_m1), &(auth->mac_m1p),
+		    &(auth->mac_m2), &(auth->mac_m2p));
+	    if (err) goto err;
+
+	    /* Create the Reveal Signature Message */
+	    err = create_revealsig_message(auth, privkey);
+	    if (err) goto err;
+	    *havemsgp = 1;
+	    auth->authstate = OTRL_AUTHSTATE_AWAITING_SIG;
+
+	    break;
+
+	case OTRL_AUTHSTATE_AWAITING_SIG:
+	    if (gcry_mpi_cmp(incoming_pub, auth->their_pub) == 0) {
+		/* Retransmit the Reveal Signature Message */
+		*havemsgp = 1;
+	    } else {
+		/* Ignore this message */
+		*havemsgp = 0;
+	    }
+	    break;
+	case OTRL_AUTHSTATE_NONE:
+	case OTRL_AUTHSTATE_AWAITING_REVEALSIG:
+	case OTRL_AUTHSTATE_V1_SETUP:
+	    /* Ignore this message */
+	    *havemsgp = 0;
+	    break;
+    }
+
+    gcry_mpi_release(incoming_pub);
+    return err;
+
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    gcry_mpi_release(incoming_pub);
+    return err;
+}
+
+/*
+ * Handle an incoming Reveal Signature Message.  If no error is
+ * returned, and *havemsgp is 1, the message to be sent will be left in
+ * auth->lastauthmsg.  Use the given private authentication key to sign
+ * messages.  Call the auth_succeeded callback if authentication is
+ * successful.
+ */
+gcry_error_t otrl_auth_handle_revealsig(OtrlAuthInfo *auth,
+	const char *revealmsg, int *havemsgp, OtrlPrivKey *privkey,
+	gcry_error_t (*auth_succeeded)(const OtrlAuthInfo *auth, void *asdata),
+	void *asdata)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp = NULL, *gxbuf = NULL;
+    unsigned char *authstart, *authend, *macstart;
+    size_t buflen, lenp, rlen, authlen;
+    gcry_cipher_hd_t enc = NULL;
+    gcry_mpi_t incoming_pub = NULL;
+    unsigned char ctr[16], hashbuf[32];
+    int res;
+
+    *havemsgp = 0;
+
+    res = otrl_base64_otr_decode(revealmsg, &buf, &buflen);
+    if (res == -1) goto memerr;
+    if (res == -2) goto invval;
+
+    bufp = buf;
+    lenp = buflen;
+
+    /* Header */
+    if (memcmp(bufp, "\x00\x02\x11", 3)) goto invval;
+    bufp += 3; lenp -= 3;
+
+    /* r */
+    read_int(rlen);
+    if (rlen != 16) goto invval;
+    require_len(rlen);
+    memmove(auth->r, bufp, rlen);
+    bufp += rlen; lenp -= rlen;
+
+    /* auth */
+    authstart = bufp;
+    read_int(authlen);
+    require_len(authlen);
+    bufp += authlen; lenp -= authlen;
+    authend = bufp;
+
+    /* MAC */
+    require_len(20);
+    macstart = bufp;
+    bufp += 20; lenp -= 20;
+
+    if (lenp != 0) goto invval;
+
+    switch(auth->authstate) {
+	case OTRL_AUTHSTATE_AWAITING_REVEALSIG:
+	    gxbuf = malloc(auth->encgx_len);
+	    if (auth->encgx_len && gxbuf == NULL) goto memerr;
+
+	    /* Use r to decrypt the value of g^x we received earlier */
+	    err = gcry_cipher_open(&enc, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CTR,
+		    GCRY_CIPHER_SECURE);
+	    if (err) goto err;
+
+	    err = gcry_cipher_setkey(enc, auth->r, 16);
+	    if (err) goto err;
+
+	    memset(ctr, 0, 16);
+	    err = gcry_cipher_setctr(enc, ctr, 16);
+	    if (err) goto err;
+
+	    err = gcry_cipher_decrypt(enc, gxbuf, auth->encgx_len,
+		    auth->encgx, auth->encgx_len);
+	    if (err) goto err;
+
+	    gcry_cipher_close(enc);
+	    enc = NULL;
+
+	    /* Check the hash */
+	    gcry_md_hash_buffer(GCRY_MD_SHA256, hashbuf, gxbuf,
+		    auth->encgx_len);
+	    if (memcmp(hashbuf, auth->hashgx, 32)) goto decfail;
+
+	    /* Extract g^x */
+	    bufp = gxbuf;
+	    lenp = auth->encgx_len;
+
+	    read_mpi(incoming_pub);
+	    free(gxbuf);
+	    gxbuf = NULL;
+
+	    if (lenp != 0) goto invval;
+
+	    gcry_mpi_release(auth->their_pub);
+	    auth->their_pub = incoming_pub;
+	    incoming_pub = NULL;
+
+	    /* Compute the encryption and MAC keys */
+	    err = otrl_dh_compute_v2_auth_keys(&(auth->our_dh),
+		    auth->their_pub, auth->secure_session_id,
+		    &(auth->secure_session_id_len),
+		    &(auth->enc_c), &(auth->enc_cp),
+		    &(auth->mac_m1), &(auth->mac_m1p),
+		    &(auth->mac_m2), &(auth->mac_m2p));
+	    if (err) goto err;
+
+	    /* Check the MAC */
+	    gcry_md_reset(auth->mac_m2);
+	    gcry_md_write(auth->mac_m2, authstart, authend - authstart);
+	    if (memcmp(macstart,
+			gcry_md_read(auth->mac_m2, GCRY_MD_SHA256),
+			20)) goto invval;
+
+	    /* Check the auth */
+	    err = check_pubkey_auth(auth->their_fingerprint,
+		    &(auth->their_keyid), authstart + 4,
+		    authend - authstart - 4, auth->mac_m1, auth->enc_c,
+		    auth->our_dh.pub, auth->their_pub);
+	    if (err) goto err;
+
+	    authstart = NULL;
+	    authend = NULL;
+	    macstart = NULL;
+	    free(buf);
+	    buf = NULL;
+
+	    /* Create the Signature Message */
+	    err = create_signature_message(auth, privkey);
+	    if (err) goto err;
+
+	    /* No error?  Then we've completed our end of the
+	     * authentication. */
+	    auth->protocol_version = 2;
+	    auth->session_id_half = OTRL_SESSIONID_SECOND_HALF_BOLD;
+	    if (auth_succeeded) err = auth_succeeded(auth, asdata);
+	    *havemsgp = 1;
+	    auth->our_keyid = 0;
+	    auth->authstate = OTRL_AUTHSTATE_NONE;
+
+	    break;
+	case OTRL_AUTHSTATE_NONE:
+	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	case OTRL_AUTHSTATE_AWAITING_SIG:
+	case OTRL_AUTHSTATE_V1_SETUP:
+	    /* Ignore this message */
+	    *havemsgp = 0;
+	    free(buf);
+	    buf = NULL;
+	    break;
+    }
+
+    return err;
+
+decfail:
+    err = gcry_error(GPG_ERR_NO_ERROR);
+    goto err;
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(gxbuf);
+    gcry_cipher_close(enc);
+    gcry_mpi_release(incoming_pub);
+    return err;
+}
+
+/*
+ * Handle an incoming Signature Message.  If no error is returned, and
+ * *havemsgp is 1, the message to be sent will be left in
+ * auth->lastauthmsg.  Call the auth_succeeded callback if
+ * authentication is successful.
+ */
+gcry_error_t otrl_auth_handle_signature(OtrlAuthInfo *auth,
+	const char *sigmsg, int *havemsgp,
+	gcry_error_t (*auth_succeeded)(const OtrlAuthInfo *auth, void *asdata),
+	void *asdata)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp = NULL;
+    unsigned char *authstart, *authend, *macstart;
+    size_t buflen, lenp, authlen;
+    int res;
+
+    *havemsgp = 0;
+
+    res = otrl_base64_otr_decode(sigmsg, &buf, &buflen);
+    if (res == -1) goto memerr;
+    if (res == -2) goto invval;
+
+    bufp = buf;
+    lenp = buflen;
+
+    /* Header */
+    if (memcmp(bufp, "\x00\x02\x12", 3)) goto invval;
+    bufp += 3; lenp -= 3;
+
+    /* auth */
+    authstart = bufp;
+    read_int(authlen);
+    require_len(authlen);
+    bufp += authlen; lenp -= authlen;
+    authend = bufp;
+
+    /* MAC */
+    require_len(20);
+    macstart = bufp;
+    bufp += 20; lenp -= 20;
+
+    if (lenp != 0) goto invval;
+
+    switch(auth->authstate) {
+	case OTRL_AUTHSTATE_AWAITING_SIG:
+	    /* Check the MAC */
+	    gcry_md_reset(auth->mac_m2p);
+	    gcry_md_write(auth->mac_m2p, authstart, authend - authstart);
+	    if (memcmp(macstart,
+			gcry_md_read(auth->mac_m2p, GCRY_MD_SHA256),
+			20)) goto invval;
+
+	    /* Check the auth */
+	    err = check_pubkey_auth(auth->their_fingerprint,
+		    &(auth->their_keyid), authstart + 4,
+		    authend - authstart - 4, auth->mac_m1p, auth->enc_cp,
+		    auth->our_dh.pub, auth->their_pub);
+	    if (err) goto err;
+
+	    authstart = NULL;
+	    authend = NULL;
+	    macstart = NULL;
+	    free(buf);
+	    buf = NULL;
+
+	    /* No error?  Then we've completed our end of the
+	     * authentication. */
+	    auth->protocol_version = 2;
+	    auth->session_id_half = OTRL_SESSIONID_FIRST_HALF_BOLD;
+	    if (auth_succeeded) err = auth_succeeded(auth, asdata);
+	    free(auth->lastauthmsg);
+	    auth->lastauthmsg = NULL;
+	    *havemsgp = 1;
+	    auth->our_keyid = 0;
+	    auth->authstate = OTRL_AUTHSTATE_NONE;
+
+	    break;
+	case OTRL_AUTHSTATE_NONE:
+	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	case OTRL_AUTHSTATE_AWAITING_REVEALSIG:
+	case OTRL_AUTHSTATE_V1_SETUP:
+	    /* Ignore this message */
+	    *havemsgp = 0;
+	    break;
+    }
+
+    return err;
+
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    return err;
+}
+
+/* Version 1 routines, for compatibility */
+
+/*
+ * Create a verion 1 Key Exchange Message using the values in the given
+ * auth, and store it in auth->lastauthmsg.  Set the Reply field to the
+ * given value, and use the given privkey to sign the message.
+ */
+static gcry_error_t create_v1_key_exchange_message(OtrlAuthInfo *auth,
+	unsigned char reply, OtrlPrivKey *privkey)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    unsigned char *buf = NULL, *bufp = NULL, *sigbuf = NULL;
+    size_t lenp, ourpublen, totallen, siglen;
+    unsigned char hashbuf[20];
+
+    if (privkey->pubkey_type != OTRL_PUBKEY_TYPE_DSA) {
+	return gpg_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* How big is the DH public key? */
+    gcry_mpi_print(format, NULL, 0, &ourpublen, auth->our_dh.pub);
+
+    totallen = 3 + 1 + privkey->pubkey_datalen + 4 + 4 + ourpublen + 40;
+    buf = malloc(totallen);
+    if (buf == NULL) goto memerr;
+
+    bufp = buf;
+    lenp = totallen;
+
+    memmove(bufp, "\x00\x01\x0a", 3); /* header */
+    debug_data("Header", bufp, 3);
+    bufp += 3; lenp -= 3;
+
+    bufp[0] = reply;
+    debug_data("Reply", bufp, 1);
+    bufp += 1; lenp -= 1;
+
+    memmove(bufp, privkey->pubkey_data, privkey->pubkey_datalen);
+    debug_data("Pubkey", bufp, privkey->pubkey_datalen);
+    bufp += privkey->pubkey_datalen; lenp -= privkey->pubkey_datalen;
+
+    write_int(auth->our_keyid);
+    debug_int("Keyid", bufp-4);
+
+    write_mpi(auth->our_dh.pub, ourpublen, "D-H y");
+
+    /* Hash all the data written so far, and sign the hash */
+    gcry_md_hash_buffer(GCRY_MD_SHA1, hashbuf, buf, bufp - buf);
+
+    err = otrl_privkey_sign(&sigbuf, &siglen, privkey, hashbuf, 20);
+    if (err) goto err;
+
+    if (siglen != 40) goto invval;
+    memmove(bufp, sigbuf, 40);
+    debug_data("Signature", bufp, 40);
+    bufp += 40; lenp -= 40;
+    free(sigbuf);
+    sigbuf = NULL;
+
+    assert(lenp == 0);
+
+    free(auth->lastauthmsg);
+    auth->lastauthmsg = otrl_base64_otr_encode(buf, totallen);
+    if (auth->lastauthmsg == NULL) goto memerr;
+    free(buf);
+    buf = NULL;
+
+    return err;
+
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(sigbuf);
+    return err;
+}
+
+/*
+ * Start a fresh AKE (version 1) using the given OtrlAuthInfo.  If
+ * our_dh is NULL, generate a fresh DH keypair to use.  Otherwise, use a
+ * copy of the one passed (with the given keyid).  Use the given private
+ * key to sign the message.  If no error is returned, the message to
+ * transmit will be contained in auth->lastauthmsg.
+ */
+gcry_error_t otrl_auth_start_v1(OtrlAuthInfo *auth, DH_keypair *our_dh,
+	unsigned int our_keyid, OtrlPrivKey *privkey)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+
+    /* Clear out this OtrlAuthInfo and start over */
+    otrl_auth_clear(auth);
+    auth->initiated = 1;
+
+    /* Import the given DH keypair, or else create a fresh one */
+    if (our_dh) {
+	otrl_dh_keypair_copy(&(auth->our_dh), our_dh);
+	auth->our_keyid = our_keyid;
+    } else {
+	otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
+	auth->our_keyid = 1;
+    }
+
+    err = create_v1_key_exchange_message(auth, 0, privkey);
+    if (!err) {
+	auth->authstate = OTRL_AUTHSTATE_V1_SETUP;
+    }
+
+    return err;
+}
+
+/*
+ * Handle an incoming v1 Key Exchange Message.  If no error is returned,
+ * and *havemsgp is 1, the message to be sent will be left in
+ * auth->lastauthmsg.  Use the given private authentication key to sign
+ * messages.  Call the auth_secceeded callback if authentication is
+ * successful.  If non-NULL, use a copy of the given D-H keypair, with
+ * the given keyid.
+ */
+gcry_error_t otrl_auth_handle_v1_key_exchange(OtrlAuthInfo *auth,
+	const char *keyexchmsg, int *havemsgp, OtrlPrivKey *privkey,
+	DH_keypair *our_dh, unsigned int our_keyid,
+	gcry_error_t (*auth_succeeded)(const OtrlAuthInfo *auth, void *asdata),
+	void *asdata)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp = NULL;
+    unsigned char *fingerprintstart, *fingerprintend;
+    unsigned char fingerprintbuf[20], hashbuf[20];
+    gcry_mpi_t p, q, g, y, received_pub;
+    gcry_sexp_t pubs = NULL;
+    size_t buflen, lenp;
+    unsigned char received_reply;
+    unsigned int received_keyid;
+    int res;
+
+    *havemsgp = 0;
+
+    res = otrl_base64_otr_decode(keyexchmsg, &buf, &buflen);
+    if (res == -1) goto memerr;
+    if (res == -2) goto invval;
+
+    bufp = buf;
+    lenp = buflen;
+
+    /* Header */
+    require_len(3);
+    if (memcmp(bufp, "\x00\x01\x0a", 3)) goto invval;
+    bufp += 3; lenp -= 3;
+
+    /* Reply */
+    require_len(1);
+    received_reply = bufp[0];
+    bufp += 1; lenp -= 1;
+
+    /* Public Key */
+    fingerprintstart = bufp;
+    read_mpi(p);
+    read_mpi(q);
+    read_mpi(g);
+    read_mpi(y);
+    fingerprintend = bufp;
+    gcry_md_hash_buffer(GCRY_MD_SHA1, fingerprintbuf,
+	    fingerprintstart, fingerprintend-fingerprintstart);
+    gcry_sexp_build(&pubs, NULL,
+	"(public-key (dsa (p %m)(q %m)(g %m)(y %m)))", p, q, g, y);
+    gcry_mpi_release(p);
+    gcry_mpi_release(q);
+    gcry_mpi_release(g);
+    gcry_mpi_release(y);
+
+    /* keyid */
+    read_int(received_keyid);
+    if (received_keyid == 0) goto invval;
+
+    /* D-H pubkey */
+    read_mpi(received_pub);
+
+    /* Verify the signature */
+    if (lenp != 40) goto invval;
+    gcry_md_hash_buffer(GCRY_MD_SHA1, hashbuf, buf, bufp - buf);
+    err = otrl_privkey_verify(bufp, lenp, OTRL_PUBKEY_TYPE_DSA,
+	    pubs, hashbuf, 20);
+    if (err) goto err;
+    gcry_sexp_release(pubs);
+    pubs = NULL;
+    free(buf);
+    buf = NULL;
+    
+    if (auth->authstate != OTRL_AUTHSTATE_V1_SETUP && received_reply == 0x01) {
+	/* They're replying to something we never sent.  We must be
+	 * logged in more than once; ignore the message. */
+	err = gpg_error(GPG_ERR_NO_ERROR);
+	goto err;
+    }
+
+    /* Everything checked out */
+    auth->their_keyid = received_keyid;
+    gcry_mpi_release(auth->their_pub);
+    auth->their_pub = received_pub;
+    received_pub = NULL;
+    memmove(auth->their_fingerprint, fingerprintbuf, 20);
+
+    if (received_reply == 0x01) {
+	/* Don't send a reply to this. */
+	*havemsgp = 0;
+    } else {
+	/* Import the given DH keypair, or else create a fresh one */
+	if (our_dh) {
+	    otrl_dh_keypair_copy(&(auth->our_dh), our_dh);
+	    auth->our_keyid = our_keyid;
+	} else if (auth->our_keyid == 0) {
+	    otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
+	    auth->our_keyid = 1;
+	}
+
+	/* Reply with our own Key Exchange Message */
+	err = create_v1_key_exchange_message(auth, 1, privkey);
+	if (err) goto err;
+	*havemsgp = 1;
+    }
+
+    /* Compute the session id */
+    err = otrl_dh_compute_v1_session_id(&(auth->our_dh),
+	    auth->their_pub, auth->secure_session_id,
+	    &(auth->secure_session_id_len),
+	    &(auth->session_id_half));
+    if (err) goto err;
+
+    /* We've completed our end of the authentication */
+    auth->protocol_version = 1;
+    if (auth_succeeded) err = auth_succeeded(auth, asdata);
+    auth->our_keyid = 0;
+    auth->authstate = OTRL_AUTHSTATE_NONE;
+
+    return err;
+
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    gcry_sexp_release(pubs);
+    gcry_mpi_release(received_pub);
+    return err;
+}
+
+#ifdef OTRL_TESTING_AUTH
+#include "mem.h"
+#include "privkey.h"
+
+#define CHECK_ERR if (err) { printf("Error: %s\n", gcry_strerror(err)); return 1; }
+
+static gcry_error_t starting(const OtrlAuthInfo *auth, void *asdata)
+{
+    char *name = asdata;
+
+    fprintf(stderr, "\nStarting ENCRYPTED mode for %s (v%d).\n", name, auth->protocol_version);
+
+    fprintf(stderr, "\nour_dh (%d):", auth->our_keyid);
+    gcry_mpi_dump(auth->our_dh.pub);
+    fprintf(stderr, "\ntheir_pub (%d):", auth->their_keyid);
+    gcry_mpi_dump(auth->their_pub);
+
+    debug_data("\nTheir fingerprint", auth->their_fingerprint, 20);
+    debug_data("\nSecure session id", auth->secure_session_id,
+	    auth->secure_session_id_len);
+    fprintf(stderr, "Sessionid half: %d\n\n", auth->session_id_half);
+
+    return gpg_error(GPG_ERR_NO_ERROR);
+}
+
+int main(int argc, char **argv)
+{
+    OtrlAuthInfo alice, bob;
+    gcry_error_t err;
+    int havemsg;
+    OtrlUserState us;
+    OtrlPrivKey *alicepriv, *bobpriv;
+
+    otrl_mem_init();
+    otrl_dh_init();
+    otrl_auth_new(&alice);
+    otrl_auth_new(&bob);
+
+    us = otrl_userstate_create();
+    otrl_privkey_read(us, "/home/iang/.gaim/otr.private_key");
+    alicepriv = otrl_privkey_find(us, "oneeyedian", "prpl-oscar");
+    bobpriv = otrl_privkey_find(us, "otr4ian", "prpl-oscar");
+
+    printf("\n\n  ***** V2 *****\n\n");
+
+    err = otrl_auth_start_v2(&bob, NULL, 0);
+    CHECK_ERR
+    printf("\nBob: %d\n%s\n\n", strlen(bob.lastauthmsg), bob.lastauthmsg);
+    err = otrl_auth_handle_commit(&alice, bob.lastauthmsg, NULL, 0);
+    CHECK_ERR
+    printf("\nAlice: %d\n%s\n\n", strlen(alice.lastauthmsg), alice.lastauthmsg);
+    err = otrl_auth_handle_key(&bob, alice.lastauthmsg, &havemsg, bobpriv);
+    CHECK_ERR
+    if (havemsg) {
+	printf("\nBob: %d\n%s\n\n", strlen(bob.lastauthmsg), bob.lastauthmsg);
+    } else {
+	printf("\nIGNORE\n\n");
+    }
+    err = otrl_auth_handle_revealsig(&alice, bob.lastauthmsg, &havemsg,
+	    alicepriv, starting, "Alice");
+    CHECK_ERR
+    if (havemsg) {
+	printf("\nAlice: %d\n%s\n\n", strlen(alice.lastauthmsg), alice.lastauthmsg);
+    } else {
+	printf("\nIGNORE\n\n");
+    }
+    err = otrl_auth_handle_signature(&bob, alice.lastauthmsg, &havemsg,
+	    starting, "Bob");
+    CHECK_ERR
+
+    printf("\n\n  ***** V1 *****\n\n");
+
+    err = otrl_auth_start_v1(&bob, NULL, 0, bobpriv);
+    CHECK_ERR
+    printf("\nBob: %d\n%s\n\n", strlen(bob.lastauthmsg), bob.lastauthmsg);
+    err = otrl_auth_handle_v1_key_exchange(&alice, bob.lastauthmsg,
+	    &havemsg, alicepriv, NULL, 0, starting, "Alice");
+    CHECK_ERR
+    if (havemsg) {
+	printf("\nAlice: %d\n%s\n\n", strlen(alice.lastauthmsg), alice.lastauthmsg);
+    } else {
+	printf("\nIGNORE\n\n");
+    }
+    err = otrl_auth_handle_v1_key_exchange(&bob, alice.lastauthmsg,
+	    &havemsg, bobpriv, NULL, 0, starting, "Bob");
+    CHECK_ERR
+    if (havemsg) {
+	printf("\nBob: %d\n%s\n\n", strlen(bob.lastauthmsg), bob.lastauthmsg);
+    } else {
+	printf("\nIGNORE\n\n");
+    }
+
+    otrl_userstate_free(us);
+    otrl_auth_clear(&alice);
+    otrl_auth_clear(&bob);
+    return 0;
+}
+#endif
diff --git a/src/auth.h b/src/auth.h
new file mode 100644
index 0000000..08744c0
--- /dev/null
+++ b/src/auth.h
@@ -0,0 +1,160 @@
+/*
+ *  Off-the-Record Messaging library
+ *  Copyright (C) 2004-2005  Nikita Borisov and Ian Goldberg
+ *                           <otr at cypherpunks.ca>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of version 2.1 of the GNU Lesser General
+ *  Public License as published by the Free Software Foundation.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __AUTH_H__
+#define __AUTH_H__
+
+#include <gcrypt.h>
+#include "dh.h"
+
+typedef enum {
+    OTRL_AUTHSTATE_NONE,
+    OTRL_AUTHSTATE_AWAITING_DHKEY,
+    OTRL_AUTHSTATE_AWAITING_REVEALSIG,
+    OTRL_AUTHSTATE_AWAITING_SIG,
+    OTRL_AUTHSTATE_V1_SETUP
+} OtrlAuthState;
+
+typedef struct {
+    OtrlAuthState authstate;              /* Our state */
+
+    DH_keypair our_dh;                    /* Our D-H key */
+    unsigned int our_keyid;               /* ...and its keyid */
+
+    unsigned char *encgx;                 /* The encrypted value of g^x */
+    size_t encgx_len;                     /*  ...and its length */
+    unsigned char r[16];                  /* The encryption key */
+
+    unsigned char hashgx[32];             /* SHA256(g^x) */
+
+    gcry_mpi_t their_pub;                 /* Their D-H public key */
+    unsigned int their_keyid;             /*  ...and its keyid */
+
+    gcry_cipher_hd_t enc_c, enc_cp;       /* c and c' encryption keys */
+    gcry_md_hd_t mac_m1, mac_m1p;         /* m1 and m1' MAC keys */
+    gcry_md_hd_t mac_m2, mac_m2p;         /* m2 and m2' MAC keys */
+
+    unsigned char their_fingerprint[20];  /* The fingerprint of their
+					     long-term signing key */
+
+    int initiated;                        /* Did we initiate this
+					     authentication? */
+
+    unsigned int protocol_version;        /* The protocol version number
+					     used to authenticate. */
+
+    unsigned char secure_session_id[20];  /* The secure session id */
+    size_t secure_session_id_len;         /* And its actual length,
+					     which may be either 20 (for
+					     v1) or 8 (for v2) */
+    OtrlSessionIdHalf session_id_half;    /* Which half of the session
+					     id gets shown in bold */
+
+    char *lastauthmsg;                    /* The last auth message
+					     (base-64 encoded) we sent,
+					     in case we need to
+					     retransmit it. */
+} OtrlAuthInfo;
+
+#include "privkey-t.h"
+
+/*
+ * Initialize the fields of an OtrlAuthInfo (already allocated).
+ */
+void otrl_auth_new(OtrlAuthInfo *auth);
+
+/*
+ * Clear the fields of an OtrlAuthInfo (but leave it allocated).
+ */
+void otrl_auth_clear(OtrlAuthInfo *auth);
+
+/*
+ * Start a fresh AKE (version 2) using the given OtrlAuthInfo.  If
+ * our_dh is NULL, generate a fresh DH keypair to use.  Otherwise, use a
+ * copy of the one passed (with the given keyid).  If no error is
+ * returned, the message to transmit will be contained in
+ * auth->lastauthmsg.
+ */
+gcry_error_t otrl_auth_start_v2(OtrlAuthInfo *auth, DH_keypair *our_dh,
+	unsigned int our_keyid);
+
+/*
+ * Handle an incoming D-H Commit Message.  If no error is returned, the
+ * message to send will be left in auth->lastauthmsg.  If non-NULL, use
+ * a copy of the given D-H keypair, with the given keyid.
+ */
+gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth, const char *commitmsg,
+	DH_keypair *our_dh, unsigned int our_keyid);
+
+/*
+ * Handle an incoming D-H Key Message.  If no error is returned, and
+ * *havemsgp is 1, the message to sent will be left in auth->lastauthmsg.
+ * Use the given private authentication key to sign messages.
+ */
+gcry_error_t otrl_auth_handle_key(OtrlAuthInfo *auth, const char *keymsg,
+	int *havemsgp, OtrlPrivKey *privkey);
+
+/*
+ * Handle an incoming Reveal Signature Message.  If no error is
+ * returned, and *havemsgp is 1, the message to be sent will be left in
+ * auth->lastauthmsg.  Use the given private authentication key to sign
+ * messages.  Call the auth_succeeded callback if authentication is
+ * successful.
+ */
+gcry_error_t otrl_auth_handle_revealsig(OtrlAuthInfo *auth,
+	const char *revealmsg, int *havemsgp, OtrlPrivKey *privkey,
+	gcry_error_t (*auth_succeeded)(const OtrlAuthInfo *auth, void *asdata),
+	void *asdata);
+
+/*
+ * Handle an incoming Signature Message.  If no error is returned, and
+ * *havemsgp is 1, the message to be sent will be left in
+ * auth->lastauthmsg.  Call the auth_succeeded callback if
+ * authentication is successful.
+ */
+gcry_error_t otrl_auth_handle_signature(OtrlAuthInfo *auth,
+	const char *sigmsg, int *havemsgp,
+	gcry_error_t (*auth_succeeded)(const OtrlAuthInfo *auth, void *asdata),
+	void *asdata);
+
+/*
+ * Start a fresh AKE (version 1) using the given OtrlAuthInfo.  If
+ * our_dh is NULL, generate a fresh DH keypair to use.  Otherwise, use a
+ * copy of the one passed (with the given keyid).  Use the given private
+ * key to sign the message.  If no error is returned, the message to
+ * transmit will be contained in auth->lastauthmsg.
+ */
+gcry_error_t otrl_auth_start_v1(OtrlAuthInfo *auth, DH_keypair *our_dh,
+	unsigned int our_keyid, OtrlPrivKey *privkey);
+
+/*
+ * Handle an incoming v1 Key Exchange Message.  If no error is returned,
+ * and *havemsgp is 1, the message to be sent will be left in
+ * auth->lastauthmsg.  Use the given private authentication key to sign
+ * messages.  Call the auth_secceeded callback if authentication is
+ * successful.  If non-NULL, use a copy of the given D-H keypair, with
+ * the given keyid.
+ */
+gcry_error_t otrl_auth_handle_v1_key_exchange(OtrlAuthInfo *auth,
+	const char *keyexchmsg, int *havemsgp, OtrlPrivKey *privkey,
+	DH_keypair *our_dh, unsigned int our_keyid,
+	gcry_error_t (*auth_succeeded)(const OtrlAuthInfo *auth, void *asdata),
+	void *asdata);
+
+#endif
diff --git a/src/b64.c b/src/b64.c
index d1bb487..a461c88 100644
--- a/src/b64.c
+++ b/src/b64.c
@@ -56,6 +56,7 @@ VERSION HISTORY:
 
 /* system headers */
 #include <stdlib.h>
+#include <string.h>
 
 /* libotr headers */
 #include "b64.h"
@@ -185,3 +186,65 @@ size_t otrl_base64_decode(char *data, const unsigned char *base64data,
 
     return datalen;
 }
+
+/*
+ * Base64-encode a block of data, stick "?OTR:" and "." around it, and
+ * return the result, or NULL in the event of a memory error.  The
+ * caller must free() the return value.
+ */
+char *otrl_base64_otr_encode(const unsigned char *buf, size_t buflen)
+{
+    char *base64buf;
+    size_t base64len;
+
+    /* Make the base64-encoding. */
+    base64len = ((buflen + 2) / 3) * 4;
+    base64buf = malloc(5 + base64len + 1 + 1);
+    if (base64buf == NULL) {
+	return NULL;
+    }
+    memmove(base64buf, "?OTR:", 5);
+    otrl_base64_encode(base64buf+5, buf, buflen);
+    base64buf[5 + base64len] = '.';
+    base64buf[5 + base64len + 1] = '\0';
+
+    return base64buf;
+}
+
+/*
+ * Base64-decode the portion of the given message between "?OTR:" and
+ * ".".  Set *bufp to the decoded data, and set *lenp to its length.
+ * The caller must free() the result.  Return 0 on success, -1 on a
+ * memory error, or -2 on invalid input.
+ */
+int otrl_base64_otr_decode(const char *msg, unsigned char **bufp,
+	size_t *lenp)
+{
+    char *otrtag, *endtag;
+    size_t msglen, rawlen;
+    unsigned char *rawmsg;
+
+    otrtag = strstr(msg, "?OTR:");
+    if (!otrtag) {
+	return -2;
+    }
+    endtag = strchr(otrtag, '.');
+    if (endtag) {
+        msglen = endtag-otrtag;
+    } else {
+	return -2;
+    }
+
+    /* Base64-decode the message */
+    rawlen = ((msglen-5) / 4) * 3;   /* maximum possible */
+    rawmsg = malloc(rawlen);
+    if (!rawmsg && rawlen > 0) {
+	return -1;
+    }
+    rawlen = otrl_base64_decode(rawmsg, otrtag+5, msglen-5);  /* actual size */
+
+    *bufp = rawmsg;
+    *lenp = rawlen;
+
+    return 0;
+}
diff --git a/src/b64.h b/src/b64.h
index 5bd4f80..03d9b21 100644
--- a/src/b64.h
+++ b/src/b64.h
@@ -39,4 +39,19 @@ size_t otrl_base64_encode(char *base64data, const unsigned char *data,
 size_t otrl_base64_decode(char *data, const unsigned char *base64data,
 	size_t base64len);
 
+/*
+ * Base64-encode a block of data, stick "?OTR:" and "." around it, and
+ * return the result, or NULL in the event of a memory error.
+ */
+char *otrl_base64_otr_encode(const unsigned char *buf, size_t buflen);
+
+/*
+ * Base64-decode the portion of the given message between "?OTR:" and
+ * ".".  Set *bufp to the decoded data, and set *lenp to its length.
+ * The caller must free() the result.  Return 0 on success, -1 on a
+ * memory error, or -2 on invalid input.
+ */
+int otrl_base64_otr_decode(const char *msg, unsigned char **bufp,
+	size_t *lenp);
+
 #endif
diff --git a/src/context.c b/src/context.c
index 6d53210..2e699c6 100644
--- a/src/context.c
+++ b/src/context.c
@@ -27,10 +27,6 @@
 /* libotr headers */
 #include "context.h"
 
-/* Strings describing the connection states */
-const char *otrl_context_statestr[] =
-    { "Not private", "Setting up", "Private" };
-
 /* Create a new connection context. */
 static ConnContext * new_context(const char * user, const char * accountname,
 	const char * protocol)
@@ -45,7 +41,8 @@ static ConnContext * new_context(const char * user, const char * accountname,
     context->fragment_len = 0;
     context->fragment_n = 0;
     context->fragment_k = 0;
-    context->state = CONN_UNCONNECTED;
+    context->msgstate = OTRL_MSGSTATE_PLAINTEXT;
+    otrl_auth_new(&(context->auth));
     context->fingerprint_root.fingerprint = NULL;
     context->fingerprint_root.context = context;
     context->fingerprint_root.next = NULL;
@@ -66,6 +63,7 @@ static ConnContext * new_context(const char * user, const char * accountname,
     otrl_dh_session_blank(&(context->sesskeys[1][0]));
     otrl_dh_session_blank(&(context->sesskeys[1][1]));
     memset(context->sessionid, 0, 20);
+    context->sessionid_len = 0;
     context->numsavedkeys = 0;
     context->preshared_secret = NULL;
     context->preshared_secret_len = 0;
@@ -187,11 +185,11 @@ void otrl_context_set_preshared_secret(ConnContext *context,
     }
 }
 
-/* Force a context into the CONN_SETUP state (so that it only has local
- * DH keys). */
-void otrl_context_force_setup(ConnContext *context)
+/* Force a context into the OTRL_MSGSTATE_FINISHED state. */
+void otrl_context_force_finished(ConnContext *context)
 {
-    context->state = CONN_SETUP;
+    context->msgstate = OTRL_MSGSTATE_FINISHED;
+    otrl_auth_clear(&(context->auth));
     free(context->fragment);
     context->fragment = NULL;
     context->fragment_len = 0;
@@ -203,11 +201,15 @@ void otrl_context_force_setup(ConnContext *context)
     context->their_y = NULL;
     gcry_mpi_release(context->their_old_y);
     context->their_old_y = NULL;
+    context->our_keyid = 0;
+    otrl_dh_keypair_free(&(context->our_dh_key));
+    otrl_dh_keypair_free(&(context->our_old_dh_key));
     otrl_dh_session_free(&(context->sesskeys[0][0]));
     otrl_dh_session_free(&(context->sesskeys[0][1]));
     otrl_dh_session_free(&(context->sesskeys[1][0]));
     otrl_dh_session_free(&(context->sesskeys[1][1]));
     memset(context->sessionid, 0, 20);
+    context->sessionid_len = 0;
     free(context->preshared_secret);
     context->preshared_secret = NULL;
     context->preshared_secret_len = 0;
@@ -219,34 +221,32 @@ void otrl_context_force_setup(ConnContext *context)
     context->may_retransmit = 0;
 }
 
-/* Force a context into the CONN_UNCONNECTED state. */
-void otrl_context_force_disconnect(ConnContext *context)
+/* Force a context into the OTRL_MSGSTATE_PLAINTEXT state. */
+void otrl_context_force_plaintext(ConnContext *context)
 {
-    /* First clean up everything we'd need to do for the SETUP state */
-    otrl_context_force_setup(context);
+    /* First clean up everything we'd need to do for the FINISHED state */
+    otrl_context_force_finished(context);
 
-    /* Now clean up our pubkeys, too */
-    context->state = CONN_UNCONNECTED;
-    context->our_keyid = 0;
-    otrl_dh_keypair_free(&(context->our_dh_key));
-    otrl_dh_keypair_free(&(context->our_old_dh_key));
+    /* And just set the state properly */
+    context->msgstate = OTRL_MSGSTATE_PLAINTEXT;
 }
 
 /* Forget a fingerprint (so long as it's not the active one.  If it's a
  * fingerprint_root, forget the whole context (as long as
- * and_maybe_context is set, and it's UNCONNECTED).  Also, if it's not
+ * and_maybe_context is set, and it's PLAINTEXT).  Also, if it's not
  * the fingerprint_root, but it's the only fingerprint, and we're
- * UNCONNECTED, forget the whole context if and_maybe_context is set. */
+ * PLAINTEXT, forget the whole context if and_maybe_context is set. */
 void otrl_context_forget_fingerprint(Fingerprint *fprint,
 	int and_maybe_context)
 {
     ConnContext *context = fprint->context;
     if (fprint == &(context->fingerprint_root)) {
-	if (context->state == CONN_UNCONNECTED && and_maybe_context) {
+	if (context->msgstate == OTRL_MSGSTATE_PLAINTEXT &&
+		and_maybe_context) {
 	    otrl_context_forget(context);
 	}
     } else {
-	if (context->state != CONN_CONNECTED ||
+	if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT ||
 		context->active_fingerprint != fprint) {
 	    free(fprint->fingerprint);
 	    free(fprint->trust);
@@ -255,7 +255,7 @@ void otrl_context_forget_fingerprint(Fingerprint *fprint,
 		fprint->next->tous = fprint->tous;
 	    }
 	    free(fprint);
-	    if (context->state == CONN_UNCONNECTED &&
+	    if (context->msgstate == OTRL_MSGSTATE_PLAINTEXT &&
 		    context->fingerprint_root.next == NULL &&
 		    and_maybe_context) {
 		/* We just deleted the only fingerprint.  Forget the
@@ -266,14 +266,14 @@ void otrl_context_forget_fingerprint(Fingerprint *fprint,
     }
 }
 
-/* Forget a whole context, so long as it's UNCONNECTED. */
+/* Forget a whole context, so long as it's PLAINTEXT. */
 void otrl_context_forget(ConnContext *context)
 {
-    if (context->state != CONN_UNCONNECTED) return;
+    if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT) return;
 
-    /* Just to be safe, force a disconnect.  This also frees any
+    /* Just to be safe, force to plaintext.  This also frees any
      * extraneous data lying around. */
-    otrl_context_force_disconnect(context);
+    otrl_context_force_plaintext(context);
 
     /* First free all the Fingerprints */
     while(context->fingerprint_root.next) {
@@ -302,12 +302,11 @@ void otrl_context_forget(ConnContext *context)
     free(context);
 }
 
-/* Forget all the contexts in a given OtrlUserState, forcing them to
- * UNCONNECTED. */
+/* Forget all the contexts in a given OtrlUserState. */
 void otrl_context_forget_all(OtrlUserState us)
 {
     while (us->context_root) {
-	otrl_context_force_disconnect(us->context_root);
+	otrl_context_force_plaintext(us->context_root);
 	otrl_context_forget(us->context_root);
     }
 }
diff --git a/src/context.h b/src/context.h
index fbe095a..640c953 100644
--- a/src/context.h
+++ b/src/context.h
@@ -20,14 +20,23 @@
 #ifndef __CONTEXT_H__
 #define __CONTEXT_H__
 
-#include "gcrypt.h"
+#include <gcrypt.h>
+
 #include "dh.h"
+#include "auth.h"
 
 typedef enum {
-    CONN_UNCONNECTED,
-    CONN_SETUP,
-    CONN_CONNECTED
-} ConnectionState;
+    OTRL_MSGSTATE_PLAINTEXT,           /* Not yet started an encrypted
+					  conversation */
+    OTRL_MSGSTATE_ENCRYPTED,           /* Currently in an encrypted
+					  conversation */
+    OTRL_MSGSTATE_FINISHED             /* The remote side has sent us a
+					  notification that he has ended
+					  his end of the encrypted
+					  conversation; prevent any
+					  further messages from being
+					  sent to him. */
+} OtrlMessageState;
 
 typedef struct fingerprint {
     struct fingerprint *next;          /* The next fingerprint in the list */
@@ -55,8 +64,11 @@ typedef struct context {
 					  we've seen so far for this
 					  message */
 
-    ConnectionState state;             /* The state of our connection to this
-					  user */
+    OtrlMessageState msgstate;         /* The state of message disposition
+					  with this user */
+    OtrlAuthInfo auth;                 /* The state of ongoing
+					  authentication with this user */
+
     Fingerprint fingerprint_root;      /* The root of a linked list of
 					  Fingerprints entries */
     Fingerprint *active_fingerprint;   /* Which fingerprint is in use now?
@@ -76,9 +88,9 @@ typedef struct context {
 					  derived from DH key[our_keyid-i]
 					  and mpi Y[their_keyid-j] */
 
-    unsigned char sessionid[20];       /* The sessionid and direction */
-    SessionDirection sessiondir;       /* determined when this private
-					  connection was established. */
+    unsigned char sessionid[20];       /* The sessionid and bold half */
+    size_t sessionid_len;              /* determined when this private */
+    OtrlSessionIdHalf sessionid_half;  /* connection was established. */
 
     unsigned char *preshared_secret;   /* A secret you share with this
 					  user, in order to do
@@ -114,9 +126,6 @@ typedef struct context {
 
 #include "userstate.h"
 
-/* Strings describing the connection states */
-extern const char *otrl_context_statestr[];
-
 /* Look up a connection context by name/account/protocol from the given
  * OtrlUserState.  If add_if_missing is true, allocate and return a new
  * context if one does not currently exist.  In that event, call
@@ -141,26 +150,24 @@ void otrl_context_set_trust(Fingerprint *fprint, const char *trust);
 void otrl_context_set_preshared_secret(ConnContext *context,
 	const unsigned char *secret, size_t secret_len);
 
-/* Force a context into the CONN_SETUP state (so that it only has local
- * DH keys). */
-void otrl_context_force_setup(ConnContext *context);
+/* Force a context into the OTRL_MSGSTATE_FINISHED state. */
+void otrl_context_force_finished(ConnContext *context);
 
-/* Force a context into the CONN_UNCONNECTED state. */
-void otrl_context_force_disconnect(ConnContext *context);
+/* Force a context into the OTRL_MSGSTATE_PLAINTEXT state. */
+void otrl_context_force_plaintext(ConnContext *context);
 
 /* Forget a fingerprint (so long as it's not the active one.  If it's a
  * fingerprint_root, forget the whole context (as long as
- * and_maybe_context is set, and it's UNCONNECTED).  Also, if it's not
+ * and_maybe_context is set, and it's PLAINTEXT).  Also, if it's not
  * the fingerprint_root, but it's the only fingerprint, and we're
- * UNCONNECTED, forget the whole context if and_maybe_context is set. */
+ * PLAINTEXT, forget the whole context if and_maybe_context is set. */
 void otrl_context_forget_fingerprint(Fingerprint *fprint,
 	int and_maybe_context);
 
-/* Forget a whole context, so long as it's UNCONNECTED. */
+/* Forget a whole context, so long as it's PLAINTEXT. */
 void otrl_context_forget(ConnContext *context);
 
-/* Forget all the contexts in a given OtrlUserState, forcing them to
- * UNCONNECTED. */
+/* Forget all the contexts in a given OtrlUserState. */
 void otrl_context_forget_all(OtrlUserState us);
 
 #endif
diff --git a/src/dh.c b/src/dh.c
index a159b5f..9c22601 100644
--- a/src/dh.c
+++ b/src/dh.c
@@ -40,6 +40,7 @@ static const int DH1536_MOD_LEN_BITS = 1536;
 static const int DH1536_MOD_LEN_BYTES = 192;
 
 static gcry_mpi_t DH1536_MODULUS = NULL;
+static gcry_mpi_t DH1536_MODULUS_MINUS_2 = NULL;
 static gcry_mpi_t DH1536_GENERATOR = NULL;
 
 /*
@@ -51,6 +52,28 @@ void otrl_dh_init(void)
     gcry_mpi_scan(&DH1536_MODULUS, GCRYMPI_FMT_HEX, DH1536_MODULUS_S, 0, NULL);
     gcry_mpi_scan(&DH1536_GENERATOR, GCRYMPI_FMT_HEX, DH1536_GENERATOR_S,
 	    0, NULL);
+    DH1536_MODULUS_MINUS_2 = gcry_mpi_new(DH1536_MOD_LEN_BITS);
+    gcry_mpi_sub_ui(DH1536_MODULUS_MINUS_2, DH1536_MODULUS, 2);
+}
+
+/*
+ * Initialize the fields of a DH keypair.
+ */
+void otrl_dh_keypair_init(DH_keypair *kp)
+{
+    kp->groupid = 0;
+    kp->priv = NULL;
+    kp->pub = NULL;
+}
+
+/*
+ * Copy a DH_keypair.
+ */
+void otrl_dh_keypair_copy(DH_keypair *dst, const DH_keypair *src)
+{
+    dst->groupid = src->groupid;
+    dst->priv = gcry_mpi_copy(src->priv);
+    dst->pub = gcry_mpi_copy(src->pub);
 }
 
 /*
@@ -129,22 +152,17 @@ gcry_error_t otrl_dh_session(DH_sesskeys *sess, const DH_keypair *kp,
     gcry_mpi_print(GCRYMPI_FMT_USG, gabdata+5, gablen, NULL, gab);
     gcry_mpi_release(gab);
 
-    /* Calculate the session id */
     hashdata = gcry_malloc_secure(20);
     if (!hashdata) {
 	gcry_free(gabdata);
 	return gcry_error(GPG_ERR_ENOMEM);
     }
-    gabdata[0] = 0x00;
-    gcry_md_hash_buffer(GCRY_MD_SHA1, sess->dhsecureid, gabdata, gablen+5);
 
     /* Are we the "high" or "low" end of the connection? */
     if ( gcry_mpi_cmp(kp->pub, y) > 0 ) {
-	sess->dir = SESS_DIR_HIGH;
 	sendbyte = 0x01;
 	rcvbyte = 0x02;
     } else {
-	sess->dir = SESS_DIR_LOW;
 	sendbyte = 0x02;
 	rcvbyte = 0x01;
     }
@@ -193,6 +211,209 @@ err:
 }
 
 /*
+ * Compute the secure session id, two encryption keys, and four MAC keys
+ * given our DH key and their DH public key.
+ */
+gcry_error_t otrl_dh_compute_v2_auth_keys(const DH_keypair *our_dh,
+	gcry_mpi_t their_pub, unsigned char *sessionid, size_t *sessionidlenp,
+	gcry_cipher_hd_t *enc_c, gcry_cipher_hd_t *enc_cp,
+	gcry_md_hd_t *mac_m1, gcry_md_hd_t *mac_m1p,
+	gcry_md_hd_t *mac_m2, gcry_md_hd_t *mac_m2p)
+{
+    gcry_mpi_t s;
+    size_t slen;
+    unsigned char *sdata;
+    unsigned char *hashdata;
+    unsigned char ctr[16];
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+
+    *enc_c = NULL;
+    *enc_cp = NULL;
+    *mac_m1 = NULL;
+    *mac_m1p = NULL;
+    *mac_m2 = NULL;
+    *mac_m2p = NULL;
+    memset(ctr, 0, 16);
+
+    if (our_dh->groupid != DH1536_GROUP_ID) {
+	/* Invalid group id */
+	return gcry_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* Check that their_pub is in range */
+    if (gcry_mpi_cmp_ui(their_pub, 2) < 0 ||
+	    gcry_mpi_cmp(their_pub, DH1536_MODULUS_MINUS_2) > 0) {
+	/* Invalid pubkey */
+	return gcry_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* Calculate the shared secret MPI */
+    s = gcry_mpi_new(DH1536_MOD_LEN_BITS);
+    gcry_mpi_powm(s, their_pub, our_dh->priv, DH1536_MODULUS);
+
+    /* Output it in the right format */
+    gcry_mpi_print(GCRYMPI_FMT_USG, NULL, 0, &slen, s);
+    sdata = gcry_malloc_secure(slen + 5);
+    if (!sdata) {
+	gcry_mpi_release(s);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    sdata[1] = (slen >> 24) & 0xff;
+    sdata[2] = (slen >> 16) & 0xff;
+    sdata[3] = (slen >> 8) & 0xff;
+    sdata[4] = slen & 0xff;
+    gcry_mpi_print(GCRYMPI_FMT_USG, sdata+5, slen, NULL, s);
+    gcry_mpi_release(s);
+
+    /* Calculate the session id */
+    hashdata = gcry_malloc_secure(32);
+    if (!hashdata) {
+	gcry_free(sdata);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    sdata[0] = 0x00;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+    memmove(sessionid, hashdata, 8);
+    *sessionidlenp = 8;
+
+    /* Calculate the encryption keys */
+    sdata[0] = 0x01;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+
+    err = gcry_cipher_open(enc_c, GCRY_CIPHER_AES,
+	    GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE);
+    if (err) goto err;
+    err = gcry_cipher_setkey(*enc_c, hashdata, 16);
+    if (err) goto err;
+    err = gcry_cipher_setctr(*enc_c, ctr, 16);
+    if (err) goto err;
+
+    err = gcry_cipher_open(enc_cp, GCRY_CIPHER_AES,
+	    GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE);
+    if (err) goto err;
+    err = gcry_cipher_setkey(*enc_cp, hashdata+16, 16);
+    if (err) goto err;
+    err = gcry_cipher_setctr(*enc_cp, ctr, 16);
+    if (err) goto err;
+
+    /* Calculate the MAC keys */
+    sdata[0] = 0x02;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+    err = gcry_md_open(mac_m1, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+    if (err) goto err;
+    err = gcry_md_setkey(*mac_m1, hashdata, 32);
+    if (err) goto err;
+
+    sdata[0] = 0x03;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+    err = gcry_md_open(mac_m2, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+    if (err) goto err;
+    err = gcry_md_setkey(*mac_m2, hashdata, 32);
+    if (err) goto err;
+
+    sdata[0] = 0x04;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+    err = gcry_md_open(mac_m1p, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+    if (err) goto err;
+    err = gcry_md_setkey(*mac_m1p, hashdata, 32);
+    if (err) goto err;
+
+    sdata[0] = 0x05;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+    err = gcry_md_open(mac_m2p, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+    if (err) goto err;
+    err = gcry_md_setkey(*mac_m2p, hashdata, 32);
+    if (err) goto err;
+
+    gcry_free(sdata);
+    gcry_free(hashdata);
+    return gcry_error(GPG_ERR_NO_ERROR);
+
+err:
+    gcry_cipher_close(*enc_c);
+    gcry_cipher_close(*enc_cp);
+    gcry_md_close(*mac_m1);
+    gcry_md_close(*mac_m1p);
+    gcry_md_close(*mac_m2);
+    gcry_md_close(*mac_m2p);
+    *enc_c = NULL;
+    *enc_cp = NULL;
+    *mac_m1 = NULL;
+    *mac_m1p = NULL;
+    *mac_m2 = NULL;
+    *mac_m2p = NULL;
+    gcry_free(sdata);
+    gcry_free(hashdata);
+    return err;
+}
+
+/*
+ * Compute the secure session id, given our DH key and their DH public
+ * key.
+ */
+gcry_error_t otrl_dh_compute_v1_session_id(const DH_keypair *our_dh,
+	gcry_mpi_t their_pub, unsigned char *sessionid, size_t *sessionidlenp,
+	OtrlSessionIdHalf *halfp)
+{
+    gcry_mpi_t s;
+    size_t slen;
+    unsigned char *sdata;
+    unsigned char *hashdata;
+
+    if (our_dh->groupid != DH1536_GROUP_ID) {
+	/* Invalid group id */
+	return gcry_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* Check that their_pub is in range */
+    if (gcry_mpi_cmp_ui(their_pub, 2) < 0 ||
+	    gcry_mpi_cmp(their_pub, DH1536_MODULUS_MINUS_2) > 0) {
+	/* Invalid pubkey */
+	return gcry_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* Calculate the shared secret MPI */
+    s = gcry_mpi_new(DH1536_MOD_LEN_BITS);
+    gcry_mpi_powm(s, their_pub, our_dh->priv, DH1536_MODULUS);
+
+    /* Output it in the right format */
+    gcry_mpi_print(GCRYMPI_FMT_USG, NULL, 0, &slen, s);
+    sdata = gcry_malloc_secure(slen + 5);
+    if (!sdata) {
+	gcry_mpi_release(s);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    sdata[1] = (slen >> 24) & 0xff;
+    sdata[2] = (slen >> 16) & 0xff;
+    sdata[3] = (slen >> 8) & 0xff;
+    sdata[4] = slen & 0xff;
+    gcry_mpi_print(GCRYMPI_FMT_USG, sdata+5, slen, NULL, s);
+    gcry_mpi_release(s);
+
+    /* Calculate the session id */
+    hashdata = gcry_malloc_secure(20);
+    if (!hashdata) {
+	gcry_free(sdata);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    sdata[0] = 0x00;
+    gcry_md_hash_buffer(GCRY_MD_SHA1, hashdata, sdata, slen+5);
+    memmove(sessionid, hashdata, 20);
+    *sessionidlenp = 20;
+
+    /* Which half should be bold? */
+    if (gcry_mpi_cmp(our_dh->pub, their_pub) > 0) {
+	*halfp = OTRL_SESSIONID_SECOND_HALF_BOLD;
+    } else {
+	*halfp = OTRL_SESSIONID_FIRST_HALF_BOLD;
+    }
+
+    gcry_free(hashdata);
+    gcry_free(sdata);
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/*
  * Deallocate the contents of a DH_sesskeys (but not the DH_sesskeys
  * itself)
  */
@@ -215,7 +436,6 @@ void otrl_dh_session_blank(DH_sesskeys *sess)
     sess->sendmac = NULL;
     sess->rcvenc = NULL;
     sess->rcvmac = NULL;
-    memset(sess->dhsecureid, 0, 20);
     memset(sess->sendctr, 0, 16);
     memset(sess->rcvctr, 0, 16);
     memset(sess->sendmackey, 0, 20);
diff --git a/src/dh.h b/src/dh.h
index 1ed1982..f41b023 100644
--- a/src/dh.h
+++ b/src/dh.h
@@ -27,17 +27,13 @@ typedef struct {
     gcry_mpi_t priv, pub;
 } DH_keypair;
 
+/* Which half of the secure session id should be shown in bold? */
 typedef enum {
-    SESS_DIR_LOW,
-    SESS_DIR_HIGH
-} SessionDirection;
+    OTRL_SESSIONID_FIRST_HALF_BOLD,
+    OTRL_SESSIONID_SECOND_HALF_BOLD
+} OtrlSessionIdHalf;
 
 typedef struct {
-    SessionDirection dir;
-    unsigned char dhsecureid[20];  /* Don't display this value to the
-				      user when she asks to see the
-				      secure session id.  Display
-				      context->sessionid instead. */
     unsigned char sendctr[16];
     unsigned char rcvctr[16];
     gcry_cipher_hd_t sendenc;
@@ -57,6 +53,16 @@ typedef struct {
 void otrl_dh_init(void);
 
 /*
+ * Initialize the fields of a DH keypair.
+ */
+void otrl_dh_keypair_init(DH_keypair *kp);
+
+/*
+ * Copy a DH_keypair.
+ */
+void otrl_dh_keypair_copy(DH_keypair *dst, const DH_keypair *src);
+
+/*
  * Deallocate the contents of a DH_keypair (but not the DH_keypair
  * itself)
  */
@@ -75,6 +81,24 @@ gcry_error_t otrl_dh_session(DH_sesskeys *sess, const DH_keypair *kp,
 	gcry_mpi_t y);
 
 /*
+ * Compute the secure session id, two encryption keys, and four MAC keys
+ * given our DH key and their DH public key.
+ */
+gcry_error_t otrl_dh_compute_v2_auth_keys(const DH_keypair *our_dh,
+	gcry_mpi_t their_pub, unsigned char *sessionid, size_t *sessionidlenp,
+	gcry_cipher_hd_t *enc_c, gcry_cipher_hd_t *enc_cp,
+	gcry_md_hd_t *mac_m1, gcry_md_hd_t *mac_m1p,
+	gcry_md_hd_t *mac_m2, gcry_md_hd_t *mac_m2p);
+
+/*
+ * Compute the secure session id, given our DH key and their DH public
+ * key.
+ */
+gcry_error_t otrl_dh_compute_v1_session_id(const DH_keypair *our_dh,
+	gcry_mpi_t their_pub, unsigned char *sessionid, size_t *sessionidlenp,
+	OtrlSessionIdHalf *halfp);
+
+/*
  * Deallocate the contents of a DH_sesskeys (but not the DH_sesskeys
  * itself)
  */
diff --git a/src/message.c b/src/message.c
index 8c6ec92..a3fe279 100644
--- a/src/message.c
+++ b/src/message.c
@@ -28,6 +28,7 @@
 /* libotr headers */
 #include "privkey.h"
 #include "proto.h"
+#include "auth.h"
 #include "message.h"
 
 /* How long after sending a packet should we wait to send a heartbeat? */
@@ -97,246 +98,389 @@ gcry_error_t otrl_message_sending(OtrlUserState us,
     }
 
     /* Should we go on at all? */
-    if (policy == OTRL_POLICY_NEVER) {
+    if ((policy & OTRL_POLICY_VERSION_MASK) == 0) {
         return gcry_error(GPG_ERR_NO_ERROR);
     }
 
     /* If this is an OTR Query message, don't encrypt it. */
-    if (otrl_proto_message_type(message) == OTR_QUERY) {
+    if (otrl_proto_message_type(message) == OTRL_MSGTYPE_QUERY) {
 	/* Replace the "?OTR?" with a custom message */
-	char *bettermsg = otrl_proto_default_query_msg(accountname);
+	char *bettermsg = otrl_proto_default_query_msg(accountname, policy);
 	if (bettermsg) {
 	    *messagep = bettermsg;
 	}
 	return gcry_error(GPG_ERR_NO_ERROR);
     }
 
-    if (policy == OTRL_POLICY_ALWAYS && context->state != CONN_CONNECTED) {
-	/* We're trying to send an unencrypted message with policy
-	 * ALWAYS.  Don't do that, but try to start up OTR instead. */
-	if (context->lastmessage) {
-	    gcry_free(context->lastmessage);
-	    if (ops->notify) {
-		const char *format = "You attempted to send another "
-		    "unencrypted message to %s";
-		char *primary = malloc(strlen(format) + strlen(recipient) - 1);
-		if (primary) {
-		    sprintf(primary, format, recipient);
-		    ops->notify(opdata, OTRL_NOTIFY_ERROR, accountname,
-			    protocol, recipient, "OTR Policy Violation",
-			    primary,
-			    "Unencrypted messages to this recipient are not "
-			    "allowed.  Attempting to start a private "
-			    "conversation.\n\nYour message will be "
-			    "retransmitted when the private conversation "
-			    "starts, but the previously saved message has "
-			    "been discarded.");
-		    free(primary);
+    /* What is the current message disposition? */
+    switch(context->msgstate) {
+	case OTRL_MSGSTATE_PLAINTEXT:
+	    if ((policy & OTRL_POLICY_REQUIRE_ENCRYPTION)) {
+		/* We're trying to send an unencrypted message with a policy
+		 * that disallows that.  Don't do that, but try to start
+		 * up OTR instead. */
+		if ((!(ops->display_otr_message) ||
+			ops->display_otr_message(opdata, accountname,
+			    protocol, recipient, "Attempting to start a "
+			    "private conversation...")) && ops->notify) {
+		    const char *format = "You attempted to send an "
+			"unencrypted message to %s";
+		    char *primary = malloc(strlen(format) +
+			    strlen(recipient) - 1);
+		    if (primary) {
+			sprintf(primary, format, recipient);
+			ops->notify(opdata, OTRL_NOTIFY_WARNING, accountname,
+				protocol, recipient, "OTR Policy Violation",
+				primary,
+				"Unencrypted messages to this recipient are "
+				"not allowed.  Attempting to start a private "
+				"conversation.\n\nYour message will be "
+				"retransmitted when the private conversation "
+				"starts.");
+			free(primary);
+		    }
 		}
-	    }
-	} else {
-	    if (ops->notify) {
-		const char *format = "You attempted to send an "
-		    "unencrypted message to %s";
-		char *primary = malloc(strlen(format) + strlen(recipient) - 1);
-		if (primary) {
-		    sprintf(primary, format, recipient);
-		    ops->notify(opdata, OTRL_NOTIFY_WARNING, accountname,
-			    protocol, recipient, "OTR Policy Violation",
-			    primary,
-			    "Unencrypted messages to this recipient are not "
-			    "allowed.  Attempting to start a private "
-			    "conversation.\n\nYour message will be "
-			    "retransmitted when the private conversation "
-			    "starts.");
-		    free(primary);
+		context->lastmessage = gcry_malloc_secure(strlen(message) + 1);
+		if (context->lastmessage) {
+		    char *bettermsg = otrl_proto_default_query_msg(accountname,
+			    policy);
+		    strcpy(context->lastmessage, message);
+		    context->lastsent = time(NULL);
+		    context->may_retransmit = 2;
+		    if (bettermsg) {
+			*messagep = bettermsg;
+		    } else {
+			return gcry_error(GPG_ERR_ENOMEM);
+		    }
+		}
+	    } else {
+		if ((policy & OTRL_POLICY_SEND_WHITESPACE_TAG) &&
+			context->otr_offer != OFFER_REJECTED) {
+		    /* See if this user can speak OTR.  Append the
+		     * OTR_MESSAGE_TAG to the plaintext message, and see
+		     * if he responds. */
+		    size_t msglen = strlen(message);
+		    size_t basetaglen = strlen(OTRL_MESSAGE_TAG_BASE);
+		    size_t v1taglen = (policy & OTRL_POLICY_ALLOW_V1) ?
+			strlen(OTRL_MESSAGE_TAG_V1) : 0;
+		    size_t v2taglen = (policy & OTRL_POLICY_ALLOW_V2) ?
+			strlen(OTRL_MESSAGE_TAG_V2) : 0;
+		    char *taggedmsg = malloc(msglen + basetaglen + v1taglen
+			    +v2taglen + 1);
+		    if (taggedmsg) {
+			strcpy(taggedmsg, message);
+			strcpy(taggedmsg + msglen, OTRL_MESSAGE_TAG_BASE);
+			if (v1taglen) {
+			    strcpy(taggedmsg + msglen + basetaglen,
+				    OTRL_MESSAGE_TAG_V1);
+			}
+			if (v2taglen) {
+			    strcpy(taggedmsg + msglen + basetaglen + v1taglen,
+				    OTRL_MESSAGE_TAG_V2);
+			}
+			*messagep = taggedmsg;
+			if (context) {
+			    context->otr_offer = OFFER_SENT;
+			}
+		    }
 		}
 	    }
-	}
-	context->lastmessage = gcry_malloc_secure(strlen(message) + 1);
-	if (context->lastmessage) {
-	    char *bettermsg = otrl_proto_default_query_msg(accountname);
-	    strcpy(context->lastmessage, message);
-	    context->lastsent = time(NULL);
-	    context->may_retransmit = 2;
-	    if (bettermsg) {
-		*messagep = bettermsg;
-		return gcry_error(GPG_ERR_NO_ERROR);
+	    break;
+	case OTRL_MSGSTATE_ENCRYPTED:
+	    /* Create the new, encrypted message */
+	    err = otrl_proto_create_data(&msgtosend, context, message, tlvs);
+	    if (!err) {
+		context->lastsent = time(NULL);
+		*messagep = msgtosend;
 	    } else {
-		return gcry_error(GPG_ERR_ENOMEM);
+		/* Uh, oh.  Whatever we do, *don't* send the message in the
+		 * clear. */
+		*messagep = strdup("?OTR Error: Error occurred encrypting "
+			"message");
+		if ((!(ops->display_otr_message) ||
+			ops->display_otr_message(opdata, accountname,
+			    protocol, recipient, "An error occurred when "
+			    "encrypting your message.  The message was not "
+			    "sent.")) && ops->notify) {
+		    ops->notify(opdata, OTRL_NOTIFY_ERROR, 
+			    accountname, protocol, recipient,
+			    "Error encrypting message",
+			    "An error occurred when encrypting your message",
+			    "The message was not sent.");
+		}
+		if (!(*messagep)) {
+		    return gcry_error(GPG_ERR_ENOMEM);
+		}
 	    }
-	}
-    }
-
-    if (policy == OTRL_POLICY_OPPORTUNISTIC || policy == OTRL_POLICY_ALWAYS) {
-	if (context->state == CONN_UNCONNECTED &&
-		context->otr_offer != OFFER_REJECTED) {
-	    /* See if this user can speak OTR.  Append the OTR_MESSAGE_TAG
-	     * to the plaintext message, and see if he responds. */
-	    size_t msglen = strlen(message);
-	    char *taggedmsg = malloc(msglen + strlen(OTR_MESSAGE_TAG) + 1);
-	    if (taggedmsg) {
-		strcpy(taggedmsg, message);
-		strcpy(taggedmsg + msglen, OTR_MESSAGE_TAG);
-		*messagep = taggedmsg;
-		if (context) {
-		    context->otr_offer = OFFER_SENT;
+	    break;
+	case OTRL_MSGSTATE_FINISHED:
+	    *messagep = strdup("");
+	    if ((!(ops->display_otr_message) ||
+		    ops->display_otr_message(opdata, accountname,
+			protocol, recipient, "Your message was not sent.  "
+			"Either end your private conversation, or restart "
+			"it.")) && ops->notify) {
+		const char *fmt = "%s has already closed his private "
+		    "connection to you";
+		char *primary = malloc(strlen(fmt) + strlen(recipient) - 1);
+		if (primary) {
+		    sprintf(primary, fmt, recipient);
+		    ops->notify(opdata, OTRL_NOTIFY_ERROR, 
+			    accountname, protocol, recipient,
+			    "Private connection closed", primary,
+			    "Your message was not sent.  Either close your "
+			    "private connection to him, or refresh it.");
 		}
 	    }
-	}
-    }
-
-    /* If we're not going to encrypt anything, just return here. */
-    if (context->state != CONN_CONNECTED) {
-        return gcry_error(GPG_ERR_NO_ERROR);
-    }
-
-    /* If the other side has disconnected, inform the user and don't
-     * send the message. */
-    if (context->their_keyid == 0) {
-	*messagep = strdup("");
-	if (ops->notify) {
-	    const char *fmt = "%s has already closed his private connection "
-		"to you";
-	    char *primary = malloc(strlen(fmt) + strlen(recipient) - 1);
-	    if (primary) {
-		sprintf(primary, fmt, recipient);
-		ops->notify(opdata, OTRL_NOTIFY_ERROR, 
-			accountname, protocol, recipient,
-			"Private connection closed", primary,
-			"Your message was not sent.  Either close your "
-			"private connection to him, or refresh it.");
+	    if (!(*messagep)) {
+		return gcry_error(GPG_ERR_ENOMEM);
 	    }
-	}
-	if (!(*messagep)) {
-	    return gcry_error(GPG_ERR_ENOMEM);
-	}
-        return gcry_error(GPG_ERR_NO_ERROR);
+	    break;
     }
 
-    /* Create the new, encrypted message */
-    err = otrl_proto_create_data(&msgtosend, context, message, tlvs);
-    if (!err) {
-	context->lastsent = time(NULL);
-	*messagep = msgtosend;
-    } else {
-	/* Uh, oh.  Whatever we do, *don't* send the message in the
-	 * clear. */
-	*messagep = strdup("?OTR Error: Error occurred encrypting message");
-	if (ops->notify) {
-	    ops->notify(opdata, OTRL_NOTIFY_ERROR, 
-		    accountname, protocol, recipient,
-		    "Error encrypting message",
-		    "An error occurred when encrypting your message",
-		    "The message was not sent.");
-	}
-	if (!(*messagep)) {
-	    return gcry_error(GPG_ERR_ENOMEM);
-	}
-    }
-    return err;
+    return gcry_error(GPG_ERR_NO_ERROR);
 }
 
-/* If err == 0, send the message to the given user.  Otherwise, display
- * an appripriate error dialog.  Return the value of err that was
- * passed. */
-static gcry_error_t send_or_error(const OtrlMessageAppOps *ops, void *opdata,
-	gcry_error_t err, const char *accountname, const char *protocol,
-	const char *who, const char *msg)
+/* If err == 0, send the last auth message for the given context to the
+ * appropriate user.  Otherwise, display an appripriate error dialog.
+ * Return the value of err that was passed. */
+static gcry_error_t send_or_error_auth(const OtrlMessageAppOps *ops,
+	void *opdata, gcry_error_t err, ConnContext *context)
 {
     if (!err) {
+	const char *msg = context->auth.lastauthmsg;
 	if (msg && *msg) {
 	    if (ops->inject_message) {
-		ops->inject_message(opdata, accountname, protocol, who, msg);
+		ops->inject_message(opdata, context->accountname,
+			context->protocol, context->username, msg);
 	    }
 	}
     } else {
-	const char *buf_format = "Error creating OTR Key "
-		"Exchange Message: %s";
-	const char *strerr = gcry_strerror(err);
-	char *buf = malloc(strlen(buf_format) + strlen(strerr) - 1);
+	const char *buf_format = "Error setting up private conversation: %s";
+	const char *strerr;
+	char *buf;
+	
+	switch(gcry_err_code(err)) {
+	    case GPG_ERR_INV_VALUE:
+		strerr = "Malformed message received";
+		break;
+	    default:
+		strerr = gcry_strerror(err);
+		break;
+	}
+	buf = malloc(strlen(buf_format) + strlen(strerr) - 1);
 	if (buf) {
 	    sprintf(buf, buf_format, strerr);
 	}
-	if (ops->notify) {
-	    ops->notify(opdata, OTRL_NOTIFY_ERROR, accountname, protocol,
-		    who, "OTR error", buf, NULL);
+	if ((!(ops->display_otr_message) ||
+		ops->display_otr_message(opdata, context->accountname,
+		    context->protocol, context->username, buf))
+		&& ops->notify) {
+	    ops->notify(opdata, OTRL_NOTIFY_ERROR, context->accountname,
+		    context->protocol, context->username, "OTR error",
+		    buf, NULL);
 	}
 	free(buf);
     }
     return err;
 }
 
-/* Return 1 if this Key Exchange Message caused us to rekey; that is,
- * either we were in CONN_CONNECTED, and we're again in CONN_CONNECTED,
- * but with new keys, or else we weren't in CONN_CONNECTED before and
- * now we are. */
-static int process_kem(OtrlUserState us, const OtrlMessageAppOps *ops,
-	void *opdata, ConnContext *context, Fingerprint *fprint,
-	OTRKeyExchangeMsg kem)
+typedef struct {
+    int gone_encrypted;
+    OtrlUserState us;
+    const OtrlMessageAppOps *ops;
+    void *opdata;
+    ConnContext *context;
+    int ignore_message;
+    char **messagep;
+} EncrData;
+
+static gcry_error_t go_encrypted(const OtrlAuthInfo *auth, void *asdata)
 {
-    gcry_error_t err;
-    char *msgtosend;
-    ConnectionState state = context->state;
-    unsigned int generation = context->generation;
-    int retval = 0;
-
-    if (fprint == NULL) {
-	/* We now need to add this fingerprint */
-	int added = 0;
-	fprint = otrl_context_find_fingerprint(context, kem->key_fingerprint,
-		1, &added);
-	if (added) {
-	    /* This may not be the case, in the event that multiple
-	     * dialogs for the same fingerprint are open at the same
-	     * time. */
-	    if (ops->write_fingerprints) {
-		ops->write_fingerprints(opdata);
-	    }
+    EncrData *edata = asdata;
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    Fingerprint *found_print = NULL;
+    int fprint_added = 0;
+
+    /* See if we're talking to ourselves */
+    if (!gcry_mpi_cmp(auth->their_pub, auth->our_dh.pub)) {
+	/* Yes, we are. */
+	if ((!(edata->ops->display_otr_message) ||
+		edata->ops->display_otr_message(edata->opdata,
+		    edata->context->accountname, edata->context->protocol,
+		    edata->context->username,
+		    "We are receiving our own OTR messages.  "
+		    "You are either trying to talk to yourself, "
+		    "or someone is reflecting your messages back "
+		    "at you.")) && edata->ops->notify) {
+	    edata->ops->notify(edata->opdata, OTRL_NOTIFY_ERROR,
+		edata->context->accountname, edata->context->protocol,
+		edata->context->username, "OTR Error",
+		"We are receiving our own OTR messages.",
+		"You are either trying to talk to yourself, "
+		"or someone is reflecting your messages back "
+		"at you.");
 	}
-    }
-    
-    /* OK, we've received a Key Exchange Message, with a known
-     * fingerprint. */
-    err = otrl_proto_accept_key_exchange(us, context, fprint, kem, &msgtosend,
-	    ops->create_privkey, opdata);
-    send_or_error(ops, opdata, err, context->accountname, context->protocol,
-	    context->username, msgtosend);
-    free(msgtosend);
-    if (ops->update_context_list) {
-	ops->update_context_list(opdata);
+	edata->ignore_message = 1;
+	return gcry_error(GPG_ERR_NO_ERROR);
     }
 
-    /* See if we need to inform the user of a change to a secure state */
-    if ((state != CONN_CONNECTED && context->state == CONN_CONNECTED)
-	    || generation != context->generation) {
-	if (ops->gone_secure) {
-	    ops->gone_secure(opdata, context);
+    found_print = otrl_context_find_fingerprint(edata->context,
+	    edata->context->auth.their_fingerprint, 1, &fprint_added);
+
+    if (fprint_added) {
+	/* Inform the user of the new fingerprint */
+	if (edata->ops->new_fingerprint) {
+	    edata->ops->new_fingerprint(edata->opdata, edata->us,
+		    edata->context->accountname, edata->context->protocol,
+		    edata->context->username,
+		    edata->context->auth.their_fingerprint);
+	}
+	/* Arrange that the new fingerprint be written to disk */
+	if (edata->ops->write_fingerprints) {
+	    edata->ops->write_fingerprints(edata->opdata);
 	}
-	retval = 1;
     }
 
-    /* See if we need to inform the user of a change out of a secure
-     * state.  (This should only happen if we're in the CONNECTED state,
-     * the correspondent has disconnected (and lost session state), we
-     * receive a new Key Exchange message from him, but there's some
-     * sort of error when setting up our new connection state. */
-    if (state == CONN_CONNECTED && context->state != CONN_CONNECTED) {
-	if (ops->gone_insecure) {
-	    ops->gone_insecure(opdata, context);
+    /* Is this a new session or just a refresh of an existing one? */
+    if (edata->context->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
+	    edata->context->our_keyid - 1 == edata->context->auth.our_keyid &&
+	    !gcry_mpi_cmp(edata->context->our_old_dh_key.pub,
+		edata->context->auth.our_dh.pub) &&
+	    ((edata->context->their_keyid > 0 &&
+	      edata->context->their_keyid ==
+		    edata->context->auth.their_keyid &&
+	      !gcry_mpi_cmp(edata->context->their_y,
+		  edata->context->auth.their_pub)) ||
+	    (edata->context->their_keyid > 1 &&
+	     edata->context->their_keyid - 1 ==
+		    edata->context->auth.their_keyid &&
+	     edata->context->their_old_y != NULL &&
+	     !gcry_mpi_cmp(edata->context->their_old_y,
+		 edata->context->auth.their_pub)))) {
+	/* This is just a refresh of the existing session. */
+	if (edata->ops->still_secure) {
+	    edata->ops->still_secure(edata->opdata, edata->context,
+		    edata->context->auth.initiated,
+		    edata->context->auth.protocol_version);
 	}
+	edata->ignore_message = 1;
+	return gcry_error(GPG_ERR_NO_ERROR);
     }
 
-    /* See if we need to inform the user that a Key Exchange has been
-     * received, but it's just for the (unchanged) old connection. */
-    if (state == CONN_CONNECTED && context->state == CONN_CONNECTED &&
-	    generation == context->generation) {
-	if (ops->still_secure) {
-	    ops->still_secure(opdata, context, kem->is_reply);
-	}
+    /* Copy the information from the auth into the context */
+    memmove(edata->context->sessionid,
+	    edata->context->auth.secure_session_id, 20);
+    edata->context->sessionid_len =
+	edata->context->auth.secure_session_id_len;
+    edata->context->sessionid_half =
+	edata->context->auth.session_id_half;
+
+    edata->context->their_keyid = edata->context->auth.their_keyid;
+    gcry_mpi_release(edata->context->their_y);
+    gcry_mpi_release(edata->context->their_old_y);
+    edata->context->their_y = gcry_mpi_copy(edata->context->auth.their_pub);
+    edata->context->their_old_y = NULL;
+
+    if (edata->context->our_keyid - 1 != edata->context->auth.our_keyid ||
+	gcry_mpi_cmp(edata->context->our_old_dh_key.pub,
+	    edata->context->auth.our_dh.pub)) {
+	otrl_dh_keypair_free(&(edata->context->our_dh_key));
+	otrl_dh_keypair_free(&(edata->context->our_old_dh_key));
+	otrl_dh_keypair_copy(&(edata->context->our_old_dh_key),
+		&(edata->context->auth.our_dh));
+	otrl_dh_gen_keypair(edata->context->our_old_dh_key.groupid,
+		&(edata->context->our_dh_key));
+	edata->context->our_keyid = edata->context->auth.our_keyid + 1;
+    }
+
+    /* Create the session keys from the DH keys */
+    otrl_dh_session_free(&(edata->context->sesskeys[0][0]));
+    err = otrl_dh_session(&(edata->context->sesskeys[0][0]),
+	&(edata->context->our_dh_key), edata->context->their_y);
+    if (err) return err;
+    otrl_dh_session_free(&(edata->context->sesskeys[1][0]));
+    err = otrl_dh_session(&(edata->context->sesskeys[1][0]),
+	&(edata->context->our_old_dh_key), edata->context->their_y);
+    if (err) return err;
+
+    edata->context->generation++;
+    edata->context->active_fingerprint = found_print;
+    edata->context->msgstate = OTRL_MSGSTATE_ENCRYPTED;
+
+    if (edata->ops->update_context_list) {
+	edata->ops->update_context_list(edata->opdata);
     }
+    if (edata->ops->gone_secure) {
+	edata->ops->gone_secure(edata->opdata, edata->context,
+		edata->context->auth.protocol_version);
+    }
+
+    edata->gone_encrypted = 1;
+
+    return gpg_error(GPG_ERR_NO_ERROR);
+}
 
-    return retval;
+static void maybe_resend(EncrData *edata)
+{
+    gcry_error_t err;
+    time_t now;
+
+    if (!edata->gone_encrypted) return;
+
+    /* See if there's a message we sent recently that should be resent. */
+    now = time(NULL);
+    if (edata->context->lastmessage != NULL &&
+	    edata->context->may_retransmit &&
+	    edata->context->lastsent >= (now - RESEND_INTERVAL)) {
+	char *resendmsg;
+	int resending = (edata->context->may_retransmit == 1);
+
+	/* Re-encrypt the message with the new keys */
+	err = otrl_proto_create_data(&resendmsg,
+		edata->context, edata->context->lastmessage, NULL);
+	if (!err) {
+	    const char *format = "<b>The last message "
+		"to %s was resent.</b>";
+	    char *buf;
+
+	    /* Resend the message */
+	    if (edata->ops->inject_message) {
+		edata->ops->inject_message(edata->opdata,
+			edata->context->accountname, edata->context->protocol,
+			edata->context->username, resendmsg);
+	    }
+	    free(resendmsg);
+	    edata->context->lastsent = now;
+
+	    if (!resending) {
+		/* We're actually just sending it
+		 * for the first time. */
+		edata->ignore_message = 1;
+	    } else {
+		/* Let the user know we resent it */
+		buf = malloc(strlen(format) +
+			strlen(edata->context->username) - 1);
+		if (buf) {
+		    sprintf(buf, format, edata->context->username);
+		    if (edata->ops->display_otr_message) {
+			if (!edata->ops->display_otr_message(
+				    edata->opdata, edata->context->accountname,
+				    edata->context->protocol,
+				    edata->context->username, buf)) {
+			    edata->ignore_message = 1;
+			}
+		    }
+		    if (edata->ignore_message != 1) {
+			*(edata->messagep) = buf;
+			edata->ignore_message = 0;
+		    } else {
+			free(buf);
+		    }
+		}
+	    }
+	}
+    }
 }
 
 /* Handle a message just received from the network.  It is safe to pass
@@ -370,14 +514,14 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	void (*add_appdata)(void *data, ConnContext *context),
 	void *data)
 {
-    ConnContext * context;
-    OTRMessageType msgtype;
+    ConnContext *context;
+    OtrlMessageType msgtype;
     int context_added = 0;
-    ConnectionState state;
+    OtrlMessageState msgstate;
     OtrlPolicy policy = OTRL_POLICY_DEFAULT;
-    int ignore_message = -1;
     int fragment_assembled = 0;
     char *unfragmessage = NULL;
+    EncrData edata;
 
     if (!accountname || !protocol || !sender || !message || !newmessagep)
         return 0;
@@ -400,7 +544,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
     }
 
     /* Should we go on at all? */
-    if (policy == OTRL_POLICY_NEVER) {
+    if ((policy & OTRL_POLICY_VERSION_MASK) == 0) {
         return 0;
     }
 
@@ -423,195 +567,212 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
     /* What type of message is it?  Note that this just checks the
      * header; it's not necessarily a _valid_ message of this type. */
     msgtype = otrl_proto_message_type(message);
-    state = context->state;
+    msgstate = context->msgstate;
 
     /* See if they responded to our OTR offer */
-    if (policy == OTRL_POLICY_OPPORTUNISTIC || policy == OTRL_POLICY_ALWAYS) {
-	if (msgtype != OTR_NOTOTR) {
+    if ((policy & OTRL_POLICY_SEND_WHITESPACE_TAG)) {
+	if (msgtype != OTRL_MSGTYPE_NOTOTR) {
 	    context->otr_offer = OFFER_ACCEPTED;
 	} else if (context->otr_offer == OFFER_SENT) {
 	    context->otr_offer = OFFER_REJECTED;
 	}
     }
 
+    edata.gone_encrypted = 0;
+    edata.us = us;
+    edata.context = context;
+    edata.ops = ops;
+    edata.opdata = opdata;
+    edata.ignore_message = -1;
+    edata.messagep = newmessagep;
+
     switch(msgtype) {
-	char *tag;
-	case OTR_QUERY:
-	    switch(state) {
-		char *msgtosend;
-		gcry_error_t err;
-		case CONN_UNCONNECTED:
-		case CONN_SETUP:
-		    err = otrl_proto_create_key_exchange(us, &msgtosend,
-			    context, 0, ops->create_privkey, opdata);
-
-		    if (!send_or_error(ops, opdata, err, accountname,
-				protocol, sender, msgtosend)) {
-			context->state = CONN_SETUP;
-			if (ops->update_context_list) {
-			    ops->update_context_list(opdata);
+	unsigned int bestversion;
+	const char *startwhite, *endwhite;
+	DH_keypair *our_dh;
+	unsigned int our_keyid;
+	OtrlPrivKey *privkey;
+	gcry_error_t err;
+	int haveauthmsg;
+	case OTRL_MSGTYPE_QUERY:
+	    /* See if we should use an existing DH keypair, or generate
+	     * a fresh one. */
+	    if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+		our_dh = &(context->our_old_dh_key);
+		our_keyid = context->our_keyid - 1;
+	    } else {
+		our_dh = NULL;
+		our_keyid = 0;
+	    }
+
+	    /* Find the best version of OTR that we both speak */
+	    switch(otrl_proto_query_bestversion(message, policy)) {
+		case 2:
+		    err = otrl_auth_start_v2(&(context->auth), our_dh,
+			    our_keyid);
+		    send_or_error_auth(ops, opdata, err, context);
+		    break;
+		case 1:
+		    /* Get our private key */
+		    privkey = otrl_privkey_find(us, context->accountname,
+			    context->protocol);
+		    if (privkey == NULL) {
+			/* We've got no private key! */
+			if (ops->create_privkey) {
+			    ops->create_privkey(opdata, context->accountname,
+				    context->protocol);
+			    privkey = otrl_privkey_find(us,
+				    context->accountname, context->protocol);
 			}
 		    }
-		    free(msgtosend);
+		    if (privkey) {
+			err = otrl_auth_start_v1(&(context->auth), our_dh,
+				our_keyid, privkey);
+			send_or_error_auth(ops, opdata, err, context);
+		    }
 		    break;
-		case CONN_CONNECTED:
-		    /* Just reply with a Key Exchange message, but stay
-		     * in the CONNECTED state. */
-		    err = otrl_proto_create_key_exchange(us, &msgtosend,
-			    context, 0, ops->create_privkey, opdata);
-		    send_or_error(ops, opdata, err, accountname, protocol,
-			    sender, msgtosend);
-		    free(msgtosend);
+		default:
+		    /* Just ignore this message */
 		    break;
 	    }
 	    /* Don't display the Query message to the user. */
-	    if (ignore_message == -1) ignore_message = 1;
+	    if (edata.ignore_message == -1) edata.ignore_message = 1;
 	    break;
-	case OTR_KEYEXCH:
-	    switch(state) {
-		gcry_error_t err;
-		OTRKeyExchangeMsg kem;
-		Fingerprint *found_print;
-		case CONN_UNCONNECTED:
-		case CONN_SETUP:
-		case CONN_CONNECTED:
-		    /* Even if we're currently CONNECTED, receiving a
-		     * Key Exchange message means the other side has
-		     * lost the connection for some reason.  (Or else
-		     * that we sent them an explicit OTR Query message.)
-		     * Just accept the message as usual (with all the
-		     * fingerprint checks, and
-		     * otrl_proto_accept_key_exchange() will deal with
-		     * keeping the state consistent. */
-		    err = otrl_proto_parse_key_exchange(&kem, message);
-		    found_print = NULL;
 
-		    if (err) {
-			const char *buf_format = "We received a malformed "
-				"Key Exchange message from %s.";
-			char *buf = malloc(strlen(buf_format) + strlen(sender)
-				- 1);
-			if (buf) {
-			    sprintf(buf, buf_format, sender);
-			}
-			if (ops->notify) {
-			    ops->notify(opdata, OTRL_NOTIFY_ERROR,
-				    accountname, protocol, sender,
-				    "OTR Error", buf, NULL);
-			}
-			free(buf);
-			ignore_message = 1;
-			break;
+	case OTRL_MSGTYPE_DH_COMMIT:
+	    if ((policy & OTRL_POLICY_ALLOW_V2)) {
+		/* See if we should use an existing DH keypair, or generate
+		 * a fresh one. */
+		if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+		    our_dh = &(context->our_old_dh_key);
+		    our_keyid = context->our_keyid - 1;
+		} else {
+		    our_dh = NULL;
+		    our_keyid = 0;
+		}
+
+		err = otrl_auth_handle_commit(&(context->auth), message,
+			our_dh, our_keyid);
+		send_or_error_auth(ops, opdata, err, context);
+	    }
+
+	    if (edata.ignore_message == -1) edata.ignore_message = 1;
+	    break;
+
+	case OTRL_MSGTYPE_DH_KEY:
+	    if ((policy & OTRL_POLICY_ALLOW_V2)) {
+		/* Get our private key */
+		privkey = otrl_privkey_find(us, context->accountname,
+			context->protocol);
+		if (privkey == NULL) {
+		    /* We've got no private key! */
+		    if (ops->create_privkey) {
+			ops->create_privkey(opdata, context->accountname,
+				context->protocol);
+			privkey = otrl_privkey_find(us,
+				context->accountname, context->protocol);
 		    }
+		}
+		if (privkey) {
+		    err = otrl_auth_handle_key(&(context->auth), message,
+			    &haveauthmsg, privkey);
+		    if (err || haveauthmsg) {
+			send_or_error_auth(ops, opdata, err, context);
+		    }
+		}
+	    }
 
-		    /* See if we're talking to ourselves */
-		    if ((context->state == CONN_SETUP ||
-				context->state == CONN_CONNECTED) &&
-			    (!gcry_mpi_cmp(kem->dh_pubkey,
-					   context->our_old_dh_key.pub)))
-		    {
-			/* Yes, we are. */
-			if (ops->notify) {
-			    ops->notify(opdata, OTRL_NOTIFY_ERROR,
-				accountname, protocol, sender, "OTR Error",
-				"We are receiving our own OTR messages.",
-				"You are either trying to talk to yourself, "
-				"or someone is reflecting your messages back "
-				"at you.");
-			}
-			ignore_message = 1;
-			break;
+	    if (edata.ignore_message == -1) edata.ignore_message = 1;
+	    break;
+
+	case OTRL_MSGTYPE_REVEALSIG:
+	    if ((policy & OTRL_POLICY_ALLOW_V2)) {
+		/* Get our private key */
+		privkey = otrl_privkey_find(us, context->accountname,
+			context->protocol);
+		if (privkey == NULL) {
+		    /* We've got no private key! */
+		    if (ops->create_privkey) {
+			ops->create_privkey(opdata, context->accountname,
+				context->protocol);
+			privkey = otrl_privkey_find(us,
+				context->accountname, context->protocol);
+		    }
+		}
+		if (privkey) {
+		    err = otrl_auth_handle_revealsig(&(context->auth),
+			    message, &haveauthmsg, privkey, go_encrypted,
+			    &edata);
+		    if (err || haveauthmsg) {
+			send_or_error_auth(ops, opdata, err, context);
+			maybe_resend(&edata);
 		    }
+		}
+	    }
 
-		    found_print = otrl_context_find_fingerprint(context,
-			    kem->key_fingerprint, 0, NULL);
+	    if (edata.ignore_message == -1) edata.ignore_message = 1;
+	    break;
 
-		    if (!found_print) {
-			/* Inform the user of the new fingerprint */
-			if (ops->new_fingerprint) {
-			    ops->new_fingerprint(opdata, us,
-				    accountname, protocol, sender, kem);
-			}
-			process_kem(us, ops, opdata, context, NULL, kem);
-			otrl_proto_free_key_exchange(kem);
-		    } else {
-			time_t now;
-			int rekeyed = process_kem(us, ops, opdata,
-				context, found_print, kem);
-			otrl_proto_free_key_exchange(kem);
-
-			/* If we just rekeyed in response to a Key
-			 * Exchange Message, see if there's a message
-			 * we sent recently that should be resent. */
-			if (rekeyed) now = time(NULL);
-			if (rekeyed && context->lastmessage != NULL &&
-				context->their_keyid > 0 &&
-				context->may_retransmit &&
-				context->lastsent >= (now - RESEND_INTERVAL)) {
-			    char *resendmsg;
-			    int resending = (context->may_retransmit == 1);
-
-			    /* Re-encrypt the message with the new keys */
-			    err = otrl_proto_create_data(&resendmsg,
-				    context, context->lastmessage, NULL);
-			    if (!err) {
-				const char *format = "<b>The last message "
-				    "to %s was resent.</b>";
-				char *buf;
+	case OTRL_MSGTYPE_SIGNATURE:
+	    if ((policy & OTRL_POLICY_ALLOW_V2)) {
+		err = otrl_auth_handle_signature(&(context->auth),
+			message, &haveauthmsg, go_encrypted, &edata);
+		if (err || haveauthmsg) {
+		    send_or_error_auth(ops, opdata, err, context);
+		    maybe_resend(&edata);
+		}
+	    }
+	    
+	    if (edata.ignore_message == -1) edata.ignore_message = 1;
+	    break;
 
-				/* Resend the message */
-				if (ops->inject_message) {
-				    ops->inject_message(opdata, accountname,
-					    protocol, sender, resendmsg);
-				}
-				free(resendmsg);
-				context->lastsent = now;
+	case OTRL_MSGTYPE_V1_KEYEXCH:
+	    if ((policy & OTRL_POLICY_ALLOW_V1)) {
+		/* See if we should use an existing DH keypair, or generate
+		 * a fresh one. */
+		if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+		    our_dh = &(context->our_old_dh_key);
+		    our_keyid = context->our_keyid - 1;
+		} else {
+		    our_dh = NULL;
+		    our_keyid = 0;
+		}
 
-				if (!resending) {
-				    /* We're actually just sending it
-				     * for the first time. */
-				    ignore_message = 1;
-				} else {
-				    /* Let the user know we resent it */
-				    buf = malloc(strlen(format) +
-					    strlen(context->username) - 1);
-				    if (buf) {
-					sprintf(buf, format,
-						context->username);
-					if (ops->display_otr_message) {
-					    if (!ops->display_otr_message(
-							opdata, accountname,
-							protocol, sender,
-							buf)) {
-						ignore_message = 1;
-					    }
-					}
-					if (ignore_message != 1) {
-					    *newmessagep = buf;
-					    ignore_message = 0;
-					} else {
-					    free(buf);
-					}
-				    }
-				}
-			    }
-			}
+		/* Get our private key */
+		privkey = otrl_privkey_find(us, context->accountname,
+			context->protocol);
+		if (privkey == NULL) {
+		    /* We've got no private key! */
+		    if (ops->create_privkey) {
+			ops->create_privkey(opdata, context->accountname,
+				context->protocol);
+			privkey = otrl_privkey_find(us,
+				context->accountname, context->protocol);
 		    }
-		    break;
+		}
+		if (privkey) {
+		    err = otrl_auth_handle_v1_key_exchange(&(context->auth),
+			    message, &haveauthmsg, privkey, our_dh, our_keyid,
+			    go_encrypted, &edata);
+		    if (err || haveauthmsg) {
+			send_or_error_auth(ops, opdata, err, context);
+			maybe_resend(&edata);
+		    }
+		}
 	    }
-	    /* Don't deliver the Key Exchange message to the user */
-	    if (ignore_message == -1) ignore_message = 1;
+	    
+	    if (edata.ignore_message == -1) edata.ignore_message = 1;
 	    break;
-	case OTR_DATA:
-	    switch(state) {
+
+	case OTRL_MSGTYPE_DATA:
+	    switch(context->msgstate) {
 		gcry_error_t err;
 		OtrlTLV *tlvs;
 		char *plaintext;
 		char *buf;
 		char *format;
-		char *msgtosend;
-		case CONN_UNCONNECTED:
-		case CONN_SETUP:
+		case OTRL_MSGSTATE_PLAINTEXT:
+		case OTRL_MSGSTATE_FINISHED:
 		    /* Don't use g_strdup_printf here, because someone
 		     * (not us) is going to free() the *newmessagep pointer,
 		     * not g_free() it. */
@@ -625,12 +786,12 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 			if (ops->display_otr_message) {
 			    if (!ops->display_otr_message(opdata, accountname,
 					protocol, sender, buf)) {
-				ignore_message = 1;
+				edata.ignore_message = 1;
 			    }
 			}
-			if (ignore_message != 1) {
+			if (edata.ignore_message != 1) {
 			    *newmessagep = buf;
-			    ignore_message = 0;
+			    edata.ignore_message = 0;
 			} else {
 			    free(buf);
 			}
@@ -648,33 +809,24 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 			free(buf);
 		    }
 
-		    if (policy == OTRL_POLICY_OPPORTUNISTIC ||
-			    policy == OTRL_POLICY_ALWAYS) {
-			/* Send a key exchange message to try to start up
-			 * the secure conversation */
-			err = otrl_proto_create_key_exchange(us, &msgtosend,
-				context, 0, ops->create_privkey, opdata);
-			if (!send_or_error(ops, opdata, err, accountname,
-				    protocol, sender, msgtosend)) {
-			    context->state = CONN_SETUP;
-			    if (ops->update_context_list) {
-				ops->update_context_list(opdata);
-			    }
-			}
-			free(msgtosend);
-		    }
-
 		    break;
-		case CONN_CONNECTED:
+
+		case OTRL_MSGSTATE_ENCRYPTED:
 		    err = otrl_proto_accept_data(&plaintext, &tlvs, context,
 			    message);
 		    if (err) {
-			format = "We received a malformed "
-				"data message from %s.";
+			int is_conflict =
+			    (gpg_err_code(err) == GPG_ERR_CONFLICT);
+			format = is_conflict ? "We received an unreadable "
+			    "encrypted messahe from %s." :
+			    "We received a malformed data message from %s.";
 			buf = malloc(strlen(format) + strlen(sender) - 1);
 			if (buf) {
 			    sprintf(buf, format, sender);
-			    if (ops->notify) {
+			    if ((!(ops->display_otr_message) ||
+				    ops->display_otr_message(opdata,
+					accountname, protocol, sender,
+					buf)) && ops->notify) {
 				ops->notify(opdata, OTRL_NOTIFY_ERROR,
 					accountname, protocol, sender,
 					"OTR Error", buf, NULL);
@@ -683,10 +835,13 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 			}
 			if (ops->inject_message) {
 			    ops->inject_message(opdata, accountname, protocol,
-				    sender, "?OTR Error: You transmitted "
+				    sender, is_conflict ? "?OTR Error: "
+					    "You transmitted an unreadable "
+					    "encrypted message." :
+					    "?OTR Error: You transmitted "
 					    "a malformed data message");
 			}
-			ignore_message = 1;
+			edata.ignore_message = 1;
 			break;
 		    }
 
@@ -694,7 +849,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 		     * private connection, make a note of that so we
 		     * don't try sending anything else to him. */
 		    if (otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED)) {
-			context->their_keyid = 0;
+			otrl_context_force_finished(context);
 		    }
 		    
 		    if (plaintext[0] == '\0') {
@@ -709,8 +864,8 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 			    }
 			    free(buf);
 			}
-			ignore_message = 1;
-		    } else if (ignore_message == 0 &&
+			edata.ignore_message = 1;
+		    } else if (edata.ignore_message == 0 &&
 			    context->their_keyid > 0) {
 			/* If it's *not* a heartbeat, and we haven't
 			 * sent anything in a while, also send a
@@ -755,43 +910,34 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 			otrl_tlv_free(tlvs);
 		    }
 
-		    if (ignore_message != 1) {
+		    if (edata.ignore_message != 1) {
 			*newmessagep = plaintext;
-			ignore_message = 0;
+			edata.ignore_message = 0;
 		    } else {
 			free(plaintext);
 		    }
 		    break;
 	    }
 	    break;
-	case OTR_ERROR:
-	    switch(state) {
-		gcry_error_t err;
-		char *msgtosend;
-		case CONN_UNCONNECTED:
-		case CONN_SETUP:
-		    if (policy == OTRL_POLICY_OPPORTUNISTIC ||
-			    policy == OTRL_POLICY_ALWAYS) {
-			/* The other end clearly supports OTR, so try to
-			 * start up a private conversation */
-			err = otrl_proto_create_key_exchange(us, &msgtosend,
-				context, 0, ops->create_privkey, opdata);
-			if (!send_or_error(ops, opdata, err, accountname,
-				    protocol, sender, msgtosend)) {
-			    context->state = CONN_SETUP;
-			    if (ops->update_context_list) {
-				ops->update_context_list(opdata);
-			    }
-			}
-			free(msgtosend);
-		    }
-		    break;
-		case CONN_CONNECTED:
-		    /* Mark the last message we sent as eligible for
-		     * retransmission */
-		    context->may_retransmit = 1;
-		    break;
+
+	case OTRL_MSGTYPE_ERROR:
+	    if ((policy & OTRL_POLICY_ERROR_START_AKE)) {
+		char *msgtosend = otrl_proto_default_query_msg(
+			context->accountname, policy);
+		if (msgtosend && ops->inject_message) {
+		    ops->inject_message(opdata, context->accountname,
+			    context->protocol, context->username,
+			    msgtosend);
+		}
+		free(msgtosend);
+	    }
+
+	    if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+		/* Mark the last message we sent as eligible for
+		 * retransmission */
+		context->may_retransmit = 1;
 	    }
+
 	    /* In any event, display the error message, with the
 	     * display_otr_message callback, if possible */
 	    if (ops->display_otr_message) {
@@ -804,99 +950,97 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 		}
 		if (!ops->display_otr_message(opdata, accountname, protocol,
 			    sender, otrerror)) {
-		    ignore_message = 1;
+		    edata.ignore_message = 1;
 		}
 	    }
 	    break;
-	case OTR_TAGGEDPLAINTEXT:
+
+	case OTRL_MSGTYPE_TAGGEDPLAINTEXT:
 	    /* Strip the tag from the message */
-	    tag = strstr(message, OTR_MESSAGE_TAG);
-	    if (tag) {
-		size_t taglen = strlen(OTR_MESSAGE_TAG);
-		size_t restlen = strlen(tag + taglen);
-		memmove(tag, tag+taglen, restlen+1);
+	    bestversion = otrl_proto_whitespace_bestversion(message,
+		    &startwhite, &endwhite, policy);
+	    if (startwhite && endwhite) {
+		size_t restlen = strlen(endwhite);
+		char *strippedmsg = strdup(message);
+
+		if (strippedmsg) {
+		    memmove(strippedmsg + (startwhite - message),
+			    strippedmsg + (endwhite - message), restlen+1);
+		    *newmessagep = strippedmsg;
+		    edata.ignore_message = 0;
+		}
 	    }
-	    /* FALLTHROUGH */
-	case OTR_NOTOTR:
-	    switch(state) {
-		char *buf;
-		char *format;
-		case CONN_UNCONNECTED:
-		case CONN_SETUP:
-		    if (policy == OTRL_POLICY_OPPORTUNISTIC ||
-			    policy == OTRL_POLICY_ALWAYS) {
-			if (msgtype == OTR_TAGGEDPLAINTEXT) {
-			    /* Send a Key Exchange in response */
-
-			    char *msgtosend;
-			    gcry_error_t err;
-
-			    err = otrl_proto_create_key_exchange(us,
-				    &msgtosend, context, 0,
-				    ops->create_privkey, opdata);
-
-			    if (!send_or_error(ops, opdata, err, accountname,
-					protocol, sender, msgtosend)) {
-				context->state = CONN_SETUP;
-				if (ops->update_context_list) {
-				    ops->update_context_list(opdata);
-				}
+	    if (bestversion && context->msgstate != OTRL_MSGSTATE_ENCRYPTED
+		    && (policy & OTRL_POLICY_WHITESPACE_START_AKE)) {
+		switch(bestversion) {
+		    case 2:
+			err = otrl_auth_start_v2(&(context->auth), NULL, 0);
+			send_or_error_auth(ops, opdata, err, context);
+			break;
+		    case 1:
+			/* Get our private key */
+			privkey = otrl_privkey_find(us, context->accountname,
+				context->protocol);
+			if (privkey == NULL) {
+			    /* We've got no private key! */
+			    if (ops->create_privkey) {
+				ops->create_privkey(opdata,
+					context->accountname,
+					context->protocol);
+				privkey = otrl_privkey_find(us,
+					context->accountname,
+					context->protocol);
 			    }
-			    free(msgtosend);
 			}
-		    }
-
-		    /* If the policy is ALWAYS, we must warn about
-		     * receiving an unencrypted message, so just
-		     * FALLTHROUGH. */
-
-		    if (policy != OTRL_POLICY_ALWAYS) {
-			/* Just display the message. */
+			if (privkey) {
+			    err = otrl_auth_start_v1(&(context->auth), NULL, 0,
+				    privkey);
+			    send_or_error_auth(ops, opdata, err, context);
+			}
 			break;
-		    }
-		case CONN_CONNECTED:
-		    /* Not fine.  Let both users know. */
+		    default:
+			/* Don't start the AKE */
+			break;
+		}
+	    }
 
-		    /* Don't use g_strdup_printf here, because someone
-		     * (not us) is going to free() the *message pointer,
-		     * not g_free() it. */
-		    format = "<b>The following message received from %s was "
-			"<i>not</i> encrypted: [</b>%s<b>]</b>";
-		    buf = malloc(strlen(format) + strlen(context->username)
-			    + strlen(message) - 3);
-			    /* Remove "%s%s", add username + message + '\0' */
-		    if (buf) {
-			sprintf(buf, format, context->username, message);
-			if (ops->display_otr_message) {
-			    if (!ops->display_otr_message(opdata, accountname,
-					protocol, sender, buf)) {
-				ignore_message = 1;
-			    }
-			}
-			if (ignore_message != 1) {
-			    *newmessagep = buf;
-			    ignore_message = 0;
-			} else {
-			    free(buf);
+	    /* FALLTHROUGH */
+	case OTRL_MSGTYPE_NOTOTR:
+	    if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT ||
+		    (policy & OTRL_POLICY_REQUIRE_ENCRYPTION)) {
+		/* Not fine.  Let the user know. */
+
+		/* Don't use g_strdup_printf here, because someone
+		 * (not us) is going to free() the *message pointer,
+		 * not g_free() it. */
+		const char *plainmsg = (*newmessagep) ? *newmessagep : message;
+		const char *format = "<b>The following message received "
+		    "from %s was <i>not</i> encrypted: [</b>%s<b>]</b>";
+		char *buf = malloc(strlen(format) + strlen(context->username)
+			+ strlen(plainmsg) - 3);
+			/* Remove "%s%s", add username + message + '\0' */
+		if (buf) {
+		    sprintf(buf, format, context->username, plainmsg);
+		    if (ops->display_otr_message) {
+			if (!ops->display_otr_message(opdata, accountname,
+				    protocol, sender, buf)) {
+			    free(*newmessagep);
+			    *newmessagep = NULL;
+			    edata.ignore_message = 1;
 			}
 		    }
-		    format = "?OTR Error: You sent unencrypted data to %s, "
-			    "who was expecting encrypted messages from you.";
-		    buf = malloc(strlen(format) + strlen(context->accountname)
-			    - 1);
-		    if (buf) {
-			sprintf(buf, format, context->accountname);
-			if (ops->inject_message) {
-			    ops->inject_message(opdata, accountname, protocol,
-				    sender, buf);
-			}
+		    if (edata.ignore_message != 1) {
+			free(*newmessagep);
+			*newmessagep = buf;
+			edata.ignore_message = 0;
+		    } else {
 			free(buf);
 		    }
-
-		    break;
+		}
 	    }
 	    break;
-	case OTR_UNKNOWN:
+
+	case OTRL_MSGTYPE_UNKNOWN:
 	    /* We received an OTR message we didn't recognize.  Ignore
 	     * it, but make a log entry. */
 	    if (ops->log_message) {
@@ -909,7 +1053,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 		    free(buf);
 		}
 	    }
-	    if (ignore_message == -1) ignore_message = 1;
+	    if (edata.ignore_message == -1) edata.ignore_message = 1;
 	    break;
     }
 
@@ -919,12 +1063,12 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	free(unfragmessage);
     }
 
-    if (ignore_message == -1) ignore_message = 0;
-    return ignore_message;
+    if (edata.ignore_message == -1) edata.ignore_message = 0;
+    return edata.ignore_message;
 }
 
-/* Put a connection into the UNCONNECTED state, first sending the
- * other side a notice that we're doing so if we're currently CONNECTED,
+/* Put a connection into the PLAINTEXT state, first sending the
+ * other side a notice that we're doing so if we're currently ENCRYPTED,
  * and we think he's logged in. */
 void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
 	void *opdata, const char *accountname, const char *protocol,
@@ -935,7 +1079,8 @@ void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
 
     if (!context) return;
 
-    if (context->state == CONN_CONNECTED && context->their_keyid > 0 &&
+    if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
+	    context->their_keyid > 0 &&
 	    ops->is_logged_in &&
 	    ops->is_logged_in(opdata, accountname, protocol, username) == 1) {
 	if (ops->inject_message) {
@@ -952,7 +1097,7 @@ void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
 	}
     }
 
-    otrl_context_force_disconnect(context);
+    otrl_context_force_plaintext(context);
     if (ops->update_context_list) {
 	ops->update_context_list(opdata);
     }
diff --git a/src/message.h b/src/message.h
index 2a47ec3..986aa80 100644
--- a/src/message.h
+++ b/src/message.h
@@ -26,15 +26,6 @@ typedef enum {
     OTRL_NOTIFY_INFO
 } OtrlNotifyLevel;
 
-typedef enum {
-    OTRL_POLICY_OPPORTUNISTIC,
-    OTRL_POLICY_NEVER,
-    OTRL_POLICY_MANUAL,
-    OTRL_POLICY_ALWAYS
-} OtrlPolicy;
-
-#define OTRL_POLICY_DEFAULT OTRL_POLICY_OPPORTUNISTIC
-
 typedef struct s_OtrlMessageAppOps {
     /* Return the OTR policy for the given context. */
     OtrlPolicy (*policy)(void *opdata, ConnContext *context);
@@ -69,7 +60,8 @@ typedef struct s_OtrlMessageAppOps {
      * protocol / username conversation.  Return 0 if you are able to
      * successfully display it.  If you return non-0 (or if this
      * function is NULL), the control message will be displayed inline,
-     * as a received message. */
+     * as a received message, or else by using the above notify()
+     * callback. */
     int (*display_otr_message)(void *opdata, const char *accountname,
 	    const char *protocol, const char *username, const char *msg);
 
@@ -87,21 +79,25 @@ typedef struct s_OtrlMessageAppOps {
     /* A new fingerprint for the given user has been received. */
     void (*new_fingerprint)(void *opdata, OtrlUserState us,
 	    const char *accountname, const char *protocol,
-	    const char *username, OTRKeyExchangeMsg kem);
+	    const char *username, unsigned char fingerprint[20]);
 
     /* The list of known fingerprints has changed.  Write them to disk. */
     void (*write_fingerprints)(void *opdata);
 
-    /* A ConnContext has entered a secure state. */
-    void (*gone_secure)(void *opdata, ConnContext *context);
+    /* A ConnContext has entered a secure state.  protocol_version is
+     * the version of the OTR protocol used for the authentication. */
+    void (*gone_secure)(void *opdata, ConnContext *context,
+	    int protocol_version);
 
     /* A ConnContext has left a secure state. */
     void (*gone_insecure)(void *opdata, ConnContext *context);
 
-    /* A ConnContext has received a Key Exchange Message, which is the
-     * same as the one we already knew.  is_reply indicates whether the
-     * Key Exchange Message is a reply to one that we sent to them. */
-    void (*still_secure)(void *opdata, ConnContext *context, int is_reply);
+    /* We have completed an authentication, using the D-H keys we
+     * already knew.  is_reply indicates whether we initiated the AKE.
+     * protocol_version is the version of the OTR protocol used for the
+     * authentication. */
+    void (*still_secure)(void *opdata, ConnContext *context, int is_reply,
+	    int protocol_version);
 
     /* Log a message.  The passed message will end in "\n". */
     void (*log_message)(void *opdata, const char *message);
@@ -170,8 +166,8 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	void (*add_appdata)(void *data, ConnContext *context),
 	void *data);
 
-/* Put a connection into the DISCONNECTED state, first sending the
- * other side a notice that we're doing so if we're currently CONNECTED,
+/* Put a connection into the PLAINTEXT state, first sending the
+ * other side a notice that we're doing so if we're currently ENCRYPTED,
  * and we think he's logged in. */
 void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
 	void *opdata, const char *accountname, const char *protocol,
diff --git a/src/version.h b/src/privkey-t.h
similarity index 69%
copy from src/version.h
copy to src/privkey-t.h
index 77187c1..57dc116 100644
--- a/src/version.h
+++ b/src/privkey-t.h
@@ -17,13 +17,23 @@
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-#ifndef __VERSION_H__
-#define __VERSION_H__
+#ifndef __PRIVKEY_T_H__
+#define __PRIVKEY_T_H__
 
-#define OTRL_VERSION "3.0.0"
+#include <gcrypt.h>
 
-#define OTRL_VERSION_MAJOR 3
-#define OTRL_VERSION_MINOR 0
-#define OTRL_VERSION_SUB 0
+typedef struct s_OtrlPrivKey {
+    struct s_OtrlPrivKey *next;
+    struct s_OtrlPrivKey **tous;
+
+    char *accountname;
+    char *protocol;
+    unsigned short pubkey_type;
+    gcry_sexp_t privkey;
+    unsigned char *pubkey_data;
+    size_t pubkey_datalen;
+} OtrlPrivKey;
+
+#define OTRL_PUBKEY_TYPE_DSA 0x0000
 
 #endif
diff --git a/src/privkey.c b/src/privkey.c
index 80879a7..2d4a79d 100644
--- a/src/privkey.c
+++ b/src/privkey.c
@@ -27,8 +27,8 @@
 #include <gcrypt.h>
 
 /* libotr headers */
-#include "proto.h"
 #include "privkey.h"
+#include "serial.h"
 
 /* Convert a 20-byte hash value to a 45-byte human-readable value */
 void otrl_privkey_hash_to_human(char human[45], const unsigned char hash[20])
@@ -55,7 +55,7 @@ char *otrl_privkey_fingerprint(OtrlUserState us, char fingerprint[45],
 	const char *accountname, const char *protocol)
 {
     unsigned char hash[20];
-    PrivKey *p = otrl_privkey_find(us, accountname, protocol);
+    OtrlPrivKey *p = otrl_privkey_find(us, accountname, protocol);
 
     if (p) {
 	/* Calculate the hash */
@@ -71,6 +71,87 @@ char *otrl_privkey_fingerprint(OtrlUserState us, char fingerprint[45],
     return fingerprint;
 }
 
+/* Create a public key block from a private key */
+static gcry_error_t make_pubkey(unsigned char **pubbufp, size_t *publenp,
+	gcry_sexp_t privkey)
+{
+    gcry_mpi_t p,q,g,y;
+    gcry_sexp_t dsas,ps,qs,gs,ys;
+    size_t np,nq,ng,ny;
+    enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    unsigned char *bufp;
+    size_t lenp;
+
+    *pubbufp = NULL;
+    *publenp = 0;
+
+    /* Extract the public parameters */
+    dsas = gcry_sexp_find_token(privkey, "dsa", 0);
+    if (dsas == NULL) {
+	return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+    }
+    ps = gcry_sexp_find_token(dsas, "p", 0);
+    qs = gcry_sexp_find_token(dsas, "q", 0);
+    gs = gcry_sexp_find_token(dsas, "g", 0);
+    ys = gcry_sexp_find_token(dsas, "y", 0);
+    gcry_sexp_release(dsas);
+    if (!ps || !qs || !gs || !ys) {
+	gcry_sexp_release(ps);
+	gcry_sexp_release(qs);
+	gcry_sexp_release(gs);
+	gcry_sexp_release(ys);
+	return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+    }
+    p = gcry_sexp_nth_mpi(ps, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(ps);
+    q = gcry_sexp_nth_mpi(qs, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(qs);
+    g = gcry_sexp_nth_mpi(gs, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(gs);
+    y = gcry_sexp_nth_mpi(ys, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(ys);
+    if (!p || !q || !g || !y) {
+	gcry_mpi_release(p);
+	gcry_mpi_release(q);
+	gcry_mpi_release(g);
+	gcry_mpi_release(y);
+	return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+    }
+
+    *publenp = 0;
+    gcry_mpi_print(format, NULL, 0, &np, p);
+    *publenp += np + 4;
+    gcry_mpi_print(format, NULL, 0, &nq, q);
+    *publenp += nq + 4;
+    gcry_mpi_print(format, NULL, 0, &ng, g);
+    *publenp += ng + 4;
+    gcry_mpi_print(format, NULL, 0, &ny, y);
+    *publenp += ny + 4;
+
+    *pubbufp = malloc(*publenp);
+    if (*pubbufp == NULL) {
+	gcry_mpi_release(p);
+	gcry_mpi_release(q);
+	gcry_mpi_release(g);
+	gcry_mpi_release(y);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    bufp = *pubbufp;
+    lenp = *publenp;
+
+    write_mpi(p,np,"P");
+    write_mpi(q,nq,"Q");
+    write_mpi(g,ng,"G");
+    write_mpi(y,ny,"Y");
+
+    gcry_mpi_release(p);
+    gcry_mpi_release(q);
+    gcry_mpi_release(g);
+    gcry_mpi_release(y);
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
 /* Read a sets of private DSA keys from a file on disk into the given
  * OtrlUserState. */
 gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
@@ -134,7 +215,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
 	gcry_sexp_t names, protos, privs;
 	char *name, *proto;
 	gcry_sexp_t accounts;
-	PrivKey *p;
+	OtrlPrivKey *p;
 	
 	/* Get the ith "account" S-exp */
 	accounts = gcry_sexp_nth(allkeys, i);
@@ -143,6 +224,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
 	token = gcry_sexp_nth_data(accounts, 0, &tokenlen);
 	if (tokenlen != 7 || strncmp(token, "account", 7)) {
 	    gcry_sexp_release(accounts);
+	    gcry_sexp_release(allkeys);
 	    return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
 	}
 	/* Extract the name, protocol, and privkey S-exps */
@@ -154,6 +236,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
 	    gcry_sexp_release(names);
 	    gcry_sexp_release(protos);
 	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
 	    return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
 	}
 	/* Extract the actual name and protocol */
@@ -162,6 +245,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
 	    gcry_sexp_release(names);
 	    gcry_sexp_release(protos);
 	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
 	    return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
 	}
 	name = malloc(tokenlen + 1);
@@ -169,6 +253,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
 	    gcry_sexp_release(names);
 	    gcry_sexp_release(protos);
 	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
 	    return gcry_error(GPG_ERR_ENOMEM);
 	}
 	memmove(name, token, tokenlen);
@@ -180,6 +265,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
 	    free(name);
 	    gcry_sexp_release(protos);
 	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
 	    return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
 	}
 	proto = malloc(tokenlen + 1);
@@ -187,24 +273,27 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
 	    free(name);
 	    gcry_sexp_release(protos);
 	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
 	    return gcry_error(GPG_ERR_ENOMEM);
 	}
 	memmove(proto, token, tokenlen);
 	proto[tokenlen] = '\0';
 	gcry_sexp_release(protos);
 
-	/* Make a new PrivKey entry */
+	/* Make a new OtrlPrivKey entry */
 	p = malloc(sizeof(*p));
 	if (!p) {
 	    free(name);
 	    free(proto);
 	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
 	    return gcry_error(GPG_ERR_ENOMEM);
 	}
 
 	/* Fill it in and link it up */
 	p->accountname = name;
 	p->protocol = proto;
+	p->pubkey_type = OTRL_PUBKEY_TYPE_DSA;
 	p->privkey = privs;
 	p->next = us->privkey_root;
 	if (p->next) {
@@ -212,13 +301,14 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
 	}
 	p->tous = &(us->privkey_root);
 	us->privkey_root = p;
-	err = otrl_proto_make_pubkey(&(p->pubkey_data), &(p->pubkey_datalen),
-	    p->privkey);
+	err = make_pubkey(&(p->pubkey_data), &(p->pubkey_datalen), p->privkey);
 	if (err) {
+	    gcry_sexp_release(allkeys);
 	    otrl_privkey_forget(p);
 	    return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
 	}
     }
+    gcry_sexp_release(allkeys);
 
     return gcry_error(GPG_ERR_NO_ERROR);
 }
@@ -279,7 +369,7 @@ gcry_error_t otrl_privkey_generate(OtrlUserState us, const char *filename,
     mode_t oldmask;
 #endif
     static const char *parmstr = "(genkey (dsa (nbits 4:1024)))";
-    PrivKey *p;
+    OtrlPrivKey *p;
 
     /* Create a DSA key */
     err = gcry_sexp_new(&parms, parmstr, strlen(parmstr), 0);
@@ -452,10 +542,10 @@ gcry_error_t otrl_privkey_write_fingerprints(OtrlUserState us,
 
 /* Fetch the private key from the given OtrlUserState associated with
  * the given account */
-PrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname,
+OtrlPrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname,
 	const char *protocol)
 {
-    PrivKey *p;
+    OtrlPrivKey *p;
     if (!accountname || !protocol) return NULL;
 
     for(p=us->privkey_root; p; p=p->next) {
@@ -468,7 +558,7 @@ PrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname,
 }
 
 /* Forget a private key */
-void otrl_privkey_forget(PrivKey *privkey)
+void otrl_privkey_forget(OtrlPrivKey *privkey)
 {
     free(privkey->accountname);
     free(privkey->protocol);
@@ -492,3 +582,84 @@ void otrl_privkey_forget_all(OtrlUserState us)
 	otrl_privkey_forget(us->privkey_root);
     }
 }
+
+/* Sign data using a private key.  The data must be small enough to be
+ * signed (i.e. already hashed, if necessary).  The signature will be
+ * returned in *sigp, which the caller must free().  Its length will be
+ * returned in *siglenp. */
+gcry_error_t otrl_privkey_sign(unsigned char **sigp, size_t *siglenp,
+	OtrlPrivKey *privkey, const unsigned char *data, size_t len)
+{
+    gcry_mpi_t r,s, datampi;
+    gcry_sexp_t dsas, rs, ss, sigs, datas;
+    size_t nr, ns;
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+
+    if (privkey->pubkey_type != OTRL_PUBKEY_TYPE_DSA)
+	return gcry_error(GPG_ERR_INV_VALUE);
+
+    *sigp = malloc(40);
+    if (sigp == NULL) return gcry_error(GPG_ERR_ENOMEM);
+    *siglenp = 40;
+
+    if (len) {
+	gcry_mpi_scan(&datampi, GCRYMPI_FMT_USG, data, len, NULL);
+    } else {
+	datampi = gcry_mpi_set_ui(NULL, 0);
+    }
+    gcry_sexp_build(&datas, NULL, "(%m)", datampi);
+    gcry_mpi_release(datampi);
+    gcry_pk_sign(&sigs, datas, privkey->privkey);
+    gcry_sexp_release(datas);
+    dsas = gcry_sexp_find_token(sigs, "dsa", 0);
+    gcry_sexp_release(sigs);
+    rs = gcry_sexp_find_token(dsas, "r", 0);
+    ss = gcry_sexp_find_token(dsas, "s", 0);
+    gcry_sexp_release(dsas);
+    r = gcry_sexp_nth_mpi(rs, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(rs);
+    s = gcry_sexp_nth_mpi(ss, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(ss);
+    gcry_mpi_print(format, NULL, 0, &nr, r);
+    gcry_mpi_print(format, NULL, 0, &ns, s);
+    memset(*sigp, 0, 40);
+    gcry_mpi_print(format, (*sigp)+(20-nr), nr, NULL, r);
+    gcry_mpi_print(format, (*sigp)+20+(20-ns), ns, NULL, s);
+    gcry_mpi_release(r);
+    gcry_mpi_release(s);
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/* Verify a signature on data using a public key.  The data must be
+ * small enough to be signed (i.e. already hashed, if necessary). */
+gcry_error_t otrl_privkey_verify(const unsigned char *sigbuf, size_t siglen,
+	unsigned short pubkey_type, gcry_sexp_t pubs,
+	const unsigned char *data, size_t len)
+{
+    gcry_error_t err;
+    gcry_mpi_t datampi,r,s;
+    gcry_sexp_t datas, sigs;
+
+    if (pubkey_type != OTRL_PUBKEY_TYPE_DSA || siglen != 40)
+	return gcry_error(GPG_ERR_INV_VALUE);
+
+    if (len) {
+	gcry_mpi_scan(&datampi, GCRYMPI_FMT_USG, data, len, NULL);
+    } else {
+	datampi = gcry_mpi_set_ui(NULL, 0);
+    }
+    gcry_sexp_build(&datas, NULL, "(%m)", datampi);
+    gcry_mpi_release(datampi);
+    gcry_mpi_scan(&r, GCRYMPI_FMT_USG, sigbuf, 20, NULL);
+    gcry_mpi_scan(&s, GCRYMPI_FMT_USG, sigbuf+20, 20, NULL);
+    gcry_sexp_build(&sigs, NULL, "(sig-val (dsa (r %m)(s %m)))", r, s);
+    gcry_mpi_release(r);
+    gcry_mpi_release(s);
+
+    err = gcry_pk_verify(sigs, datas, pubs);
+    gcry_sexp_release(datas);
+    gcry_sexp_release(sigs);
+
+    return err;
+}
diff --git a/src/privkey.h b/src/privkey.h
index d4a399d..7b63e46 100644
--- a/src/privkey.h
+++ b/src/privkey.h
@@ -20,20 +20,7 @@
 #ifndef __PRIVKEY_H__
 #define __PRIVKEY_H__
 
-#include <gcrypt.h>
-
-typedef struct s_PrivKey {
-    struct s_PrivKey *next;
-    struct s_PrivKey **tous;
-
-    char *accountname;
-    char *protocol;
-    gcry_sexp_t privkey;
-    unsigned char *pubkey_data;
-    size_t pubkey_datalen;
-} PrivKey;
-
-#include "context.h"
+#include "privkey-t.h"
 #include "userstate.h"
 
 /* Convert a 20-byte hash value to a 45-byte human-readable value */
@@ -69,13 +56,26 @@ gcry_error_t otrl_privkey_write_fingerprints(OtrlUserState us,
 
 /* Fetch the private key from the given OtrlUserState associated with
  * the given account */
-PrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname,
+OtrlPrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname,
 	const char *protocol);
 
 /* Forget a private key */
-void otrl_privkey_forget(PrivKey *privkey);
+void otrl_privkey_forget(OtrlPrivKey *privkey);
 
 /* Forget all private keys in a given OtrlUserState. */
 void otrl_privkey_forget_all(OtrlUserState us);
 
+/* Sign data using a private key.  The data must be small enough to be
+ * signed (i.e. already hashed, if necessary).  The signature will be
+ * returned in *sigp, which the caller must free().  Its length will be
+ * returned in *siglenp. */
+gcry_error_t otrl_privkey_sign(unsigned char **sigp, size_t *siglenp,
+	OtrlPrivKey *privkey, const unsigned char *data, size_t len);
+
+/* Verify a signature on data using a public key.  The data must be
+ * small enough to be signed (i.e. already hashed, if necessary). */
+gcry_error_t otrl_privkey_verify(const unsigned char *sigbuf, size_t siglen,
+	unsigned short pubkey_type, gcry_sexp_t pubs,
+	const unsigned char *data, size_t len);
+
 #endif
diff --git a/src/proto.c b/src/proto.c
index 2ecd24b..53fa11e 100644
--- a/src/proto.c
+++ b/src/proto.c
@@ -35,64 +35,7 @@
 #include "mem.h"
 #include "version.h"
 #include "tlv.h"
-
-#undef DEBUG
-
-#ifdef DEBUG
-static void debug_data(const char *title, const unsigned char *data,
-	size_t len)
-{
-    size_t i;
-    fprintf(stderr, "%s: ", title);
-    for(i=0;i<len;++i) {
-	fprintf(stderr, "%02x", data[i]);
-    }
-    fprintf(stderr, "\n");
-}
-
-static void debug_int(const char *title, const unsigned char *data)
-{
-    unsigned int v =
-	(data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
-    fprintf(stderr, "%s: %u (0x%x)\n", title, v, v);
-}
-#else
-#define debug_data(t,b,l)
-#define debug_int(t,b)
-#endif
-
-#define write_int(x) do { \
-	bufp[0] = ((x) >> 24) & 0xff; \
-	bufp[1] = ((x) >> 16) & 0xff; \
-	bufp[2] = ((x) >> 8) & 0xff; \
-	bufp[3] = (x) & 0xff; \
-	bufp += 4; lenp -= 4; \
-    } while(0)
-
-#define write_mpi(x,nx,dx) do { \
-	write_int(nx); \
-	gcry_mpi_print(format, bufp, lenp, NULL, (x)); \
-	debug_data((dx), bufp, (nx)); \
-	bufp += (nx); lenp -= (nx); \
-    } while(0)
-
-#define require_len(l) do { \
-	if (lenp < (l)) goto invval; \
-    } while(0)
-
-#define read_int(x) do { \
-	require_len(4); \
-	(x) = (bufp[0] << 24) | (bufp[1] << 16) | (bufp[2] << 8) | bufp[3]; \
-	bufp += 4; lenp -= 4; \
-    } while(0)
-
-#define read_mpi(x) do { \
-	size_t mpilen; \
-	read_int(mpilen); \
-	require_len(mpilen); \
-	gcry_mpi_scan(&(x), GCRYMPI_FMT_USG, bufp, mpilen, NULL); \
-	bufp += mpilen; lenp -= mpilen; \
-    } while(0)
+#include "serial.h"
 
 /* Initialize the OTR library.  Pass the version of the API you are
  * using. */
@@ -123,87 +66,6 @@ const char *otrl_version(void)
     return OTRL_VERSION;
 }
 
-/* Create a public key block from a private key */
-gcry_error_t otrl_proto_make_pubkey(unsigned char **pubbufp, size_t *publenp,
-	gcry_sexp_t privkey)
-{
-    gcry_mpi_t p,q,g,y;
-    gcry_sexp_t dsas,ps,qs,gs,ys;
-    size_t np,nq,ng,ny;
-    enum gcry_mpi_format format = GCRYMPI_FMT_USG;
-    unsigned char *bufp;
-    size_t lenp;
-
-    *pubbufp = NULL;
-    *publenp = 0;
-
-    /* Extract the public parameters */
-    dsas = gcry_sexp_find_token(privkey, "dsa", 0);
-    if (dsas == NULL) {
-	return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
-    }
-    ps = gcry_sexp_find_token(dsas, "p", 0);
-    qs = gcry_sexp_find_token(dsas, "q", 0);
-    gs = gcry_sexp_find_token(dsas, "g", 0);
-    ys = gcry_sexp_find_token(dsas, "y", 0);
-    gcry_sexp_release(dsas);
-    if (!ps || !qs || !gs || !ys) {
-	gcry_sexp_release(ps);
-	gcry_sexp_release(qs);
-	gcry_sexp_release(gs);
-	gcry_sexp_release(ys);
-	return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
-    }
-    p = gcry_sexp_nth_mpi(ps, 1, GCRYMPI_FMT_USG);
-    gcry_sexp_release(ps);
-    q = gcry_sexp_nth_mpi(qs, 1, GCRYMPI_FMT_USG);
-    gcry_sexp_release(qs);
-    g = gcry_sexp_nth_mpi(gs, 1, GCRYMPI_FMT_USG);
-    gcry_sexp_release(gs);
-    y = gcry_sexp_nth_mpi(ys, 1, GCRYMPI_FMT_USG);
-    gcry_sexp_release(ys);
-    if (!p || !q || !g || !y) {
-	gcry_mpi_release(p);
-	gcry_mpi_release(q);
-	gcry_mpi_release(g);
-	gcry_mpi_release(y);
-	return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
-    }
-
-    *publenp = 0;
-    gcry_mpi_print(format, NULL, 0, &np, p);
-    *publenp += np + 4;
-    gcry_mpi_print(format, NULL, 0, &nq, q);
-    *publenp += nq + 4;
-    gcry_mpi_print(format, NULL, 0, &ng, g);
-    *publenp += ng + 4;
-    gcry_mpi_print(format, NULL, 0, &ny, y);
-    *publenp += ny + 4;
-
-    *pubbufp = malloc(*publenp);
-    if (*pubbufp == NULL) {
-	gcry_mpi_release(p);
-	gcry_mpi_release(q);
-	gcry_mpi_release(g);
-	gcry_mpi_release(y);
-	return gcry_error(GPG_ERR_ENOMEM);
-    }
-    bufp = *pubbufp;
-    lenp = *publenp;
-
-    write_mpi(p,np,"P");
-    write_mpi(q,nq,"Q");
-    write_mpi(g,ng,"G");
-    write_mpi(y,ny,"Y");
-
-    gcry_mpi_release(p);
-    gcry_mpi_release(q);
-    gcry_mpi_release(g);
-    gcry_mpi_release(y);
-
-    return gcry_error(GPG_ERR_NO_ERROR);
-}
-
 /* Store some MAC keys to be revealed later */
 static gcry_error_t reveal_macs(ConnContext *context,
 	DH_sesskeys *sess1, DH_sesskeys *sess2)
@@ -322,400 +184,157 @@ static gcry_error_t rotate_y_keys(ConnContext *context, gcry_mpi_t new_y)
 /* Return a pointer to a newly-allocated OTR query message, customized
  * with our name.  The caller should free() the result when he's done
  * with it. */
-char *otrl_proto_default_query_msg(const char *ourname)
+char *otrl_proto_default_query_msg(const char *ourname, OtrlPolicy policy)
 {
     char *msg;
+    int v1_supported, v2_supported;
+    const char *version_tag;
     /* Don't use g_strdup_printf here, because someone (not us) is going
      * to free() the *message pointer, not g_free() it.  We can't
      * require that they g_free() it, because this pointer will probably
      * get passed to the main IM application for processing (and
      * free()ing). */
-    const char *format = "?OTR?\n<b>%s</b> has requested an "
+    const char *format = "?OTR%s\n<b>%s</b> has requested an "
 	    "<a href=\"http://www.cypherpunks.ca/otr/\">Off-the-Record "
 	    "private conversation</a>.  However, you do not have a plugin "
 	    "to support that.\nSee <a href=\"http://www.cypherpunks.ca/otr/\">"
 	    "http://www.cypherpunks.ca/otr/</a> for more information.";
 
-    /* Remove "%s", add '\0' */
-    msg = malloc(strlen(format) + strlen(ourname) - 1);
-    if (!msg) return NULL;
-    sprintf(msg, format, ourname);
-    return msg;
-}
-
-/* Return the Message type of the given message. */
-OTRMessageType otrl_proto_message_type(const char *message)
-{
-    char *otrtag;
-
-    otrtag = strstr(message, "?OTR");
-
-    if (!otrtag) {
-	if (strstr(message, OTR_MESSAGE_TAG)) {
-	    return OTR_TAGGEDPLAINTEXT;
+    /* Figure out the version tag */
+    v1_supported = (policy & OTRL_POLICY_ALLOW_V1);
+    v2_supported = (policy & OTRL_POLICY_ALLOW_V2);
+    if (v1_supported) {
+	if (v2_supported) {
+	    version_tag = "?v2?";
 	} else {
-	    return OTR_NOTOTR;
+	    version_tag = "?";
+	}
+    } else {
+	if (v2_supported) {
+	    version_tag = "v2?";
+	} else {
+	    version_tag = "v?";
 	}
     }
 
-    if (!strncmp(otrtag, "?OTR?", 5)) return OTR_QUERY;
-    if (!strncmp(otrtag, "?OTR:AAEK", 9)) return OTR_KEYEXCH;
-    if (!strncmp(otrtag, "?OTR:AAED", 9)) return OTR_DATA;
-    if (!strncmp(otrtag, "?OTR Error:", 11)) return OTR_ERROR;
-
-    return OTR_UNKNOWN;
+    /* Remove two "%s", add '\0' */
+    msg = malloc(strlen(format) + strlen(version_tag) + strlen(ourname) - 3);
+    if (!msg) return NULL;
+    sprintf(msg, format, version_tag, ourname);
+    return msg;
 }
 
-/* Create a Key Exchange message for our correspondent.  If we need a
- * private key and don't have one, create_privkey will be called.  Use
- * the privkeys from the given OtrlUserState. */
-gcry_error_t otrl_proto_create_key_exchange(OtrlUserState us,
-	char **messagep, ConnContext *context, unsigned char is_reply,
-	void (*create_privkey)(void *create_privkey_data,
-	    const char *accountname, const char *protocol),
-	void *create_privkey_data)
+/* Return the best version of OTR support by both sides, given an OTR
+ * Query Message and the local policy. */
+unsigned int otrl_proto_query_bestversion(const char *querymsg,
+	OtrlPolicy policy)
 {
-    gcry_mpi_t r, s;
-    gcry_sexp_t dsas, rs, ss;
-    gcry_sexp_t sigs, hashs;
-    size_t nr, ns, buflen, lenp;
-    unsigned char *buf, *bufp;
-    enum gcry_mpi_format format = GCRYMPI_FMT_USG;
-    unsigned char digest[20];
-    gcry_mpi_t digestmpi;
-    char *base64buf;
-    size_t base64len;
-    size_t pubkeylen;
-    PrivKey *privkey =
-	otrl_privkey_find(us, context->accountname, context->protocol);
-
-    *messagep = NULL;
-
-    if (privkey == NULL) {
-	/* We've got no private key! */
-	if (create_privkey) {
-	    create_privkey(create_privkey_data, context->accountname,
-		    context->protocol);
-	    privkey =
-		otrl_privkey_find(us, context->accountname, context->protocol);
+    char *otrtag;
+    unsigned int query_versions = 0;
+
+    otrtag = strstr(querymsg, "?OTR");
+    otrtag += 4;
+    if (*otrtag == '?') {
+	query_versions = (1<<0);
+	++otrtag;
+    }
+    if (*otrtag == 'v') {
+	for(++otrtag; *otrtag && *otrtag != '?'; ++otrtag) {
+	    switch(*otrtag) {
+		case '2':
+		    query_versions |= (1<<1);
+		    break;
+	    }
 	}
     }
-    if (privkey == NULL) {
-	/* We've still got no private key! */
-	return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
-    }
-    
-    /* Make sure we have two keys */
-    while (context->our_keyid < 2) {
-	rotate_dh_keys(context);
-    }
 
-    buflen = 3 + 1 + privkey->pubkey_datalen + 4 + 40;
-	/* header, is_reply, pubkey, keyid, sig */
-    gcry_mpi_print(format, NULL, 0, &pubkeylen, context->our_old_dh_key.pub);
-    buflen += pubkeylen + 4;
-    buf = malloc(buflen);
-    if (buf == NULL) {
-	return gcry_error(GPG_ERR_ENOMEM);
+    if ((policy & OTRL_POLICY_ALLOW_V2) && (query_versions & (1<<1))) {
+	return 2;
     }
-    bufp = buf;
-    lenp = buflen;
-    memmove(bufp, "\x00\x01\x0a", 3);  /* header */
-    debug_data("Header", bufp, 3);
-    bufp += 3; lenp -= 3;
-
-    *bufp = is_reply;                  /* is_reply */
-    debug_data("Reply", bufp, 1);
-    bufp += 1; lenp -= 1;
-
-                                       /* DSA pubkey */
-    memmove(bufp, privkey->pubkey_data, privkey->pubkey_datalen);
-    debug_data("DSA key", bufp, privkey->pubkey_datalen);
-    bufp += privkey->pubkey_datalen; lenp -= privkey->pubkey_datalen;
-
-                                       /* keyid */
-    write_int(context->our_keyid - 1);
-    debug_int("Keyid", bufp - 4);
-
-                                       /* DH pubkey */
-    write_mpi(context->our_old_dh_key.pub, pubkeylen, "Pubkey");
-
-    /* Get a hash of the data to be signed */
-    gcry_md_hash_buffer(GCRY_MD_SHA1, digest, buf, bufp-buf);
-    gcry_mpi_scan(&digestmpi, GCRYMPI_FMT_USG, digest, 20, NULL);
-
-    /* Calculate the sig */
-    gcry_sexp_build(&hashs, NULL, "(%m)", digestmpi);
-    gcry_mpi_release(digestmpi);
-    gcry_pk_sign(&sigs, hashs, privkey->privkey);
-    gcry_sexp_release(hashs);
-    dsas = gcry_sexp_find_token(sigs, "dsa", 0);
-    gcry_sexp_release(sigs);
-    rs = gcry_sexp_find_token(dsas, "r", 0);
-    ss = gcry_sexp_find_token(dsas, "s", 0);
-    gcry_sexp_release(dsas);
-    r = gcry_sexp_nth_mpi(rs, 1, GCRYMPI_FMT_USG);
-    gcry_sexp_release(rs);
-    s = gcry_sexp_nth_mpi(ss, 1, GCRYMPI_FMT_USG);
-    gcry_sexp_release(ss);
-    gcry_mpi_print(format, NULL, 0, &nr, r);
-    gcry_mpi_print(format, NULL, 0, &ns, s);
-    memset(bufp, 0, 40);
-    gcry_mpi_print(format, bufp+(20-nr), lenp, NULL, r);
-    debug_data("R", bufp, 20);
-    bufp += 20; lenp -= 20;
-    gcry_mpi_print(format, bufp+(20-ns), lenp, NULL, s);
-    debug_data("S", bufp, 20);
-    bufp += 20; lenp -= 20;
-
-    assert(lenp == 0);
-
-    gcry_mpi_release(r);
-    gcry_mpi_release(s);
-
-    /* Make the base64-encoding. */
-    base64len = ((buflen + 2) / 3) * 4;
-    base64buf = malloc(5 + base64len + 1 + 1);
-    assert(base64buf != NULL);
-    memmove(base64buf, "?OTR:", 5);
-    otrl_base64_encode(base64buf+5, buf, buflen);
-    base64buf[5 + base64len] = '.';
-    base64buf[5 + base64len + 1] = '\0';
-
-    free(buf);
-
-    *messagep = base64buf;
-
-    return gcry_error(GPG_ERR_NO_ERROR);
+    if ((policy & OTRL_POLICY_ALLOW_V1) && (query_versions & (1<<0))) {
+	return 1;
+    }
+    return 0;
 }
 
-/* Deallocate an OTRKeyExchangeMsg returned from proto_parse_key_exchange */
-void otrl_proto_free_key_exchange(OTRKeyExchangeMsg kem)
+/* Locate any whitespace tag in this message, and return the best
+ * version of OTR support on both sides.  Set *starttagp and *endtagp to
+ * the start and end of the located tag, so that it can be snipped out. */
+unsigned int otrl_proto_whitespace_bestversion(const char *msg,
+	const char **starttagp, const char **endtagp, OtrlPolicy policy)
 {
-    if (!kem) return;
-    gcry_sexp_release(kem->digest_sexp);
-    gcry_sexp_release(kem->dsa_pubkey);
-    gcry_mpi_release(kem->dh_pubkey);
-    gcry_sexp_release(kem->dsa_sig);
-    free(kem);
-}
+    const char *starttag, *endtag;
+    unsigned int query_versions = 0;
 
-/* Parse a purported Key Exchange message.  Possible error code portions
- * of the return value:
- *   GPG_ERR_NO_ERROR:      Success
- *   GPG_ERR_ENOMEM:        Out of memory condition
- *   GPG_ERR_INV_VALUE:     The message was not a well-formed Key Exchange
- *                          message
- *   GPG_ERR_BAD_SIGNATURE: The signature on the message didn't verify
- */
-gcry_error_t otrl_proto_parse_key_exchange(OTRKeyExchangeMsg *kemp,
-	const char *msg)
-{
-    char *otrtag, *endtag;
-    unsigned char *rawmsg, *bufp;
-    size_t msglen, rawlen, lenp;
-    gcry_mpi_t p,q,g,e,r,s;
-    unsigned char hash_of_msg[20];
-    gcry_mpi_t hashmpi;
-    const unsigned char *fingerprintstart, *fingerprintend;
-    const unsigned char *signaturestart, *signatureend;
-    OTRKeyExchangeMsg kem = calloc(1, sizeof(struct s_OTRKeyExchangeMsg));
-
-    if (!kem) {
-	*kemp = NULL;
-	return gcry_error(GPG_ERR_ENOMEM);
-    }
+    *starttagp = NULL;
+    *endtagp = NULL;
 
-    otrtag = strstr(msg, "?OTR:");
-    if (!otrtag) {
-	*kemp = NULL;
-	otrl_proto_free_key_exchange(kem);
-	return gcry_error(GPG_ERR_INV_VALUE);
-    }
-    endtag = strchr(otrtag, '.');
-    if (endtag) {
-	msglen = endtag-otrtag;
-    } else {
-	msglen = strlen(otrtag);
-    }
-
-    /* Base64-decode the message */
-    rawlen = ((msglen-5) / 4) * 3;   /* maximum possible */
-    rawmsg = malloc(rawlen);
-    if (!rawmsg && rawlen > 0) {
-	*kemp = NULL;
-	otrl_proto_free_key_exchange(kem);
-	return gcry_error(GPG_ERR_ENOMEM);
-    }
-    rawlen = otrl_base64_decode(rawmsg, otrtag+5, msglen-5);  /* actual size */
+    starttag = strstr(msg, OTRL_MESSAGE_TAG_BASE);
+    if (!starttag) return 0;
 
-    bufp = rawmsg;
-    lenp = rawlen;
-
-    signaturestart = bufp;
-
-    require_len(3);
-    if (memcmp(bufp, "\x00\x01\x0a", 3)) {
-	/* Invalid header */
-	goto invval;
-    }
-    bufp += 3; lenp -= 3;
+    endtag = starttag + strlen(OTRL_MESSAGE_TAG_BASE);
 
-    require_len(1);
-    kem->is_reply = *bufp;
-    if (kem->is_reply != 0 && kem->is_reply != 1) {
-	/* Malformed is_reply field */
-	goto invval;
-    }
-    bufp += 1; lenp -= 1;
-
-    fingerprintstart = bufp;
-    /* Read the DSA public key and calculate its fingerprint. */
-    read_mpi(p);
-    read_mpi(q);
-    read_mpi(g);
-    read_mpi(e);
-    fingerprintend = bufp;
-    gcry_md_hash_buffer(GCRY_MD_SHA1, kem->key_fingerprint,
-	    fingerprintstart, fingerprintend-fingerprintstart);
-
-    /* Create the pubkey S-expression. */
-    gcry_sexp_build(&(kem->dsa_pubkey), NULL,
-	    "(public-key (dsa (p %m)(q %m)(g %m)(y %m)))",
-	    p, q, g, e);
-    gcry_mpi_release(p);
-    gcry_mpi_release(q);
-    gcry_mpi_release(g);
-    gcry_mpi_release(e);
-
-    /* Read the key id */
-    read_int(kem->keyid);
-    if (kem->keyid == 0) {
-	/* Not a legal value */
-	goto invval;
+    /* Look for groups of 8 spaces and/or tabs */
+    while(1) {
+	int i;
+	int allwhite = 1;
+	for(i=0;i<8;++i) {
+	    if (endtag[i] != ' ' && endtag[i] != '\t') {
+		allwhite = 0;
+		break;
+	    }
+	}
+	if (allwhite) {
+	    if (!strncmp(endtag, OTRL_MESSAGE_TAG_V1, 8)) {
+		query_versions |= (1<<0);
+	    }
+	    if (!strncmp(endtag, OTRL_MESSAGE_TAG_V2, 8)) {
+		query_versions |= (1<<1);
+	    }
+	    endtag += 8;
+	} else {
+	    break;
+	}
     }
 
-    /* Read the DH public key */
-    read_mpi(kem->dh_pubkey);
-
-    /* Hash the message so we can check the signature */
-    signatureend = bufp;
-    gcry_md_hash_buffer(GCRY_MD_SHA1, hash_of_msg,
-	    signaturestart, signatureend-signaturestart);
-    /* Turn the hash into an mpi, then into a sexp */
-    gcry_mpi_scan(&hashmpi, GCRYMPI_FMT_USG, hash_of_msg, 20, NULL);
-    gcry_sexp_build(&(kem->digest_sexp), NULL, "(%m)", hashmpi);
-    gcry_mpi_release(hashmpi);
-
-    /* Read the signature */
-    require_len(40);
-    gcry_mpi_scan(&r, GCRYMPI_FMT_USG, bufp, 20, NULL);
-    gcry_mpi_scan(&s, GCRYMPI_FMT_USG, bufp+20, 20, NULL);
-    lenp -= 40;
-    gcry_sexp_build(&(kem->dsa_sig), NULL, "(sig-val (dsa (r %m)(s %m)))",
-	    r, s);
-    gcry_mpi_release(r);
-    gcry_mpi_release(s);
-
-    /* That should be everything */
-    if (lenp != 0) goto invval;
+    *starttagp = starttag;
+    *endtagp = endtag;
 
-    /* Verify the signature */
-    if (gcry_pk_verify(kem->dsa_sig, kem->digest_sexp, kem->dsa_pubkey)) {
-	/* It failed! */
-	otrl_proto_free_key_exchange(kem);
-	free(rawmsg);
-	*kemp = NULL;
-	return gcry_error(GPG_ERR_BAD_SIGNATURE);
+    if ((policy & OTRL_POLICY_ALLOW_V2) && (query_versions & (1<<1))) {
+	return 2;
     }
-
-    free(rawmsg);
-    *kemp = kem;
-    return gcry_error(GPG_ERR_NO_ERROR);
-invval:
-    otrl_proto_free_key_exchange(kem);
-    free(rawmsg);
-    *kemp = NULL;
-    return gcry_error(GPG_ERR_INV_VALUE);
+    if ((policy & OTRL_POLICY_ALLOW_V1) && (query_versions & (1<<0))) {
+	return 1;
+    }
+    return 0;
 }
 
-/* Deal with a Key Exchange Message once it's been received and passed
- * all the validity and UI ("accept this fingerprint?") tests.
- * context/fprint is the ConnContext and Fingerprint to which it
- * belongs.  Use the given OtrlUserState to look up any necessary
- * private keys.  It is the caller's responsibility to
- * otrl_proto_free_key_exchange(kem) when we're done.  If *messagep gets
- * set to non-NULL by this function, then it's a message that needs to
- * get sent to the correspondent.  If we need a private key and don't
- * have one, create_privkey will be called. */
-gcry_error_t otrl_proto_accept_key_exchange(OtrlUserState us,
-	ConnContext *context, Fingerprint *fprint, OTRKeyExchangeMsg kem,
-	char **messagep,
-	void (*create_privkey)(void *create_privkey_data,
-	    const char *accountname, const char *protocol),
-	void *create_privkey_data)
+/* Return the Message type of the given message. */
+OtrlMessageType otrl_proto_message_type(const char *message)
 {
-    gcry_error_t err;
-    char *savedmessage = context->lastmessage;
-    int savedmay_retransmit = context->may_retransmit;
-    time_t savedtime = context->lastsent;
-    ConnectionState state = context->state;
-    *messagep = NULL;
-    context->lastmessage = NULL;
+    char *otrtag;
 
-    switch(state) {
-	case CONN_CONNECTED:
-	    if (kem->is_reply == 0) {
-		/* Send a Key Exchange message to the correspondent */
-		err = otrl_proto_create_key_exchange(us, messagep, context, 1,
-			create_privkey, create_privkey_data);
-		if (err) return err;
-	    }
-	    if (context->their_keyid > 0 &&
-		    ((kem->keyid == context->their_keyid &&
-			!gcry_mpi_cmp(kem->dh_pubkey, context->their_y))
-		    || (kem->keyid == (context->their_keyid - 1) &&
-			!gcry_mpi_cmp(kem->dh_pubkey, context->their_old_y)))) {
-		/* We've already seen this key of theirs, so all is
-		 * good. */
-		break;
-	    }
-	    /* It's an entirely different session; our correspondent has
-	     * gone away and come back. */
-	    otrl_context_force_setup(context);
-
-	    /* FALLTHROUGH */
-	case CONN_UNCONNECTED:
-	case CONN_SETUP:
-	    if (state == CONN_UNCONNECTED ||
-		    (state == CONN_SETUP && kem->is_reply == 0)) {
-		/* Send a Key Exchange message to the correspondent */
-		err = otrl_proto_create_key_exchange(us, messagep, context, 1,
-			create_privkey, create_privkey_data);
-		if (err) return err;
-	    }
-	    context->their_keyid = kem->keyid;
-	    gcry_mpi_release(context->their_y);
-	    context->their_y = gcry_mpi_copy(kem->dh_pubkey);
-	    err = otrl_dh_session(&(context->sesskeys[0][0]),
-		    &(context->our_dh_key), context->their_y);
-	    if (err) return err;
-	    err = otrl_dh_session(&(context->sesskeys[1][0]),
-		    &(context->our_old_dh_key), context->their_y);
-	    if (err) return err;
-	    context->state = CONN_CONNECTED;
-	    memmove(context->sessionid, context->sesskeys[1][0].dhsecureid,
-		    20);
-	    context->sessiondir = context->sesskeys[1][0].dir;
-	    context->active_fingerprint = fprint;
-	    context->generation++;
-	    context->lastmessage = savedmessage;
-	    context->may_retransmit = savedmay_retransmit;
-	    context->lastsent = savedtime;
-	    break;
+    otrtag = strstr(message, "?OTR");
+
+    if (!otrtag) {
+	if (strstr(message, OTRL_MESSAGE_TAG_BASE)) {
+	    return OTRL_MSGTYPE_TAGGEDPLAINTEXT;
+	} else {
+	    return OTRL_MSGTYPE_NOTOTR;
+	}
     }
 
-    return gcry_error(GPG_ERR_NO_ERROR);
+    if (!strncmp(otrtag, "?OTR?", 5)) return OTRL_MSGTYPE_QUERY;
+    if (!strncmp(otrtag, "?OTRv", 5)) return OTRL_MSGTYPE_QUERY;
+    if (!strncmp(otrtag, "?OTR:AAIC", 9)) return OTRL_MSGTYPE_DH_COMMIT;
+    if (!strncmp(otrtag, "?OTR:AAIK", 9)) return OTRL_MSGTYPE_DH_KEY;
+    if (!strncmp(otrtag, "?OTR:AAIR", 9)) return OTRL_MSGTYPE_REVEALSIG;
+    if (!strncmp(otrtag, "?OTR:AAIS", 9)) return OTRL_MSGTYPE_SIGNATURE;
+    if (!strncmp(otrtag, "?OTR:AAEK", 9)) return OTRL_MSGTYPE_V1_KEYEXCH;
+    if (!strncmp(otrtag, "?OTR:AAED", 9)) return OTRL_MSGTYPE_DATA;
+    if (!strncmp(otrtag, "?OTR Error:", 11)) return OTRL_MSGTYPE_ERROR;
+
+    return OTRL_MSGTYPE_UNKNOWN;
 }
 
 /* Create an OTR Data message.  Pass the plaintext as msg, and an
@@ -741,7 +360,8 @@ gcry_error_t otrl_proto_create_data(char **encmessagep, ConnContext *context,
     char *msgdup;
 
     /* Make sure we're actually supposed to be able to encrypt */
-    if (context->state != CONN_CONNECTED || context->their_keyid == 0) {
+    if (context->msgstate != OTRL_MSGSTATE_ENCRYPTED ||
+	    context->their_keyid == 0) {
 	return gcry_error(GPG_ERR_CONFLICT);
     }
 
@@ -1101,7 +721,7 @@ OtrlFragmentResult otrl_proto_fragment_accumulate(char **unfragmessagep,
 	    res = OTRL_FRAGMENT_COMPLETE;
 	}
     } else {
-	/* Unfragmented message, so discard anyy fragment we may have */
+	/* Unfragmented message, so discard any fragment we may have */
 	free(context->fragment);
 	context->fragment = NULL;
 	context->fragment_len = 0;
diff --git a/src/proto.h b/src/proto.h
index 86f5fba..03adecf 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -26,19 +26,57 @@
 
 /* If we ever see this sequence in a plaintext message, we'll assume the
  * other side speaks OTR, and try to establish a connection. */
-#define OTR_MESSAGE_TAG " \t  \t\t\t\t \t \t \t   \t \t  \t "
+#define OTRL_MESSAGE_TAG_BASE " \t  \t\t\t\t \t \t \t  "
+/* The following must each be of length 8 */
+#define OTRL_MESSAGE_TAG_V1 " \t \t  \t "
+#define OTRL_MESSAGE_TAG_V2 "  \t\t  \t "
+
     /* This is the bit sequence of the string "OTR", encoded in tabs and
      * spaces. */
 
+typedef unsigned int OtrlPolicy;
+
+#define OTRL_POLICY_ALLOW_V1			0x01
+#define OTRL_POLICY_ALLOW_V2			0x02
+#define OTRL_POLICY_REQUIRE_ENCRYPTION		0x04
+#define OTRL_POLICY_SEND_WHITESPACE_TAG		0x08
+#define OTRL_POLICY_WHITESPACE_START_AKE	0x08
+#define OTRL_POLICY_ERROR_START_AKE		0x10
+
+#define OTRL_POLICY_VERSION_MASK (OTRL_POLICY_ALLOW_V1 | OTRL_POLICY_ALLOW_V2)
+
+/* For v1 compatibility */
+#define OTRL_POLICY_NEVER			0x00
+#define OTRL_POLICY_OPPORTUNISTIC \
+	    ( OTRL_POLICY_ALLOW_V1 | \
+	    OTRL_POLICY_ALLOW_V2 | \
+	    OTRL_POLICY_SEND_WHITESPACE_TAG | \
+	    OTRL_POLICY_WHITESPACE_START_AKE | \
+	    OTRL_POLICY_ERROR_START_AKE )
+#define OTRL_POLICY_MANUAL \
+	    ( OTRL_POLICY_ALLOW_V1 | \
+	    OTRL_POLICY_ALLOW_V2 )
+#define OTRL_POLICY_ALWAYS \
+	    ( OTRL_POLICY_ALLOW_V1 | \
+	    OTRL_POLICY_ALLOW_V2 | \
+	    OTRL_POLICY_REQUIRE_ENCRYPTION | \
+	    OTRL_POLICY_WHITESPACE_START_AKE | \
+	    OTRL_POLICY_ERROR_START_AKE )
+#define OTRL_POLICY_DEFAULT OTRL_POLICY_OPPORTUNISTIC
+
 typedef enum {
-    OTR_NOTOTR,
-    OTR_TAGGEDPLAINTEXT,
-    OTR_QUERY,
-    OTR_KEYEXCH,
-    OTR_DATA,
-    OTR_ERROR,
-    OTR_UNKNOWN
-} OTRMessageType;
+    OTRL_MSGTYPE_NOTOTR,
+    OTRL_MSGTYPE_TAGGEDPLAINTEXT,
+    OTRL_MSGTYPE_QUERY,
+    OTRL_MSGTYPE_DH_COMMIT,
+    OTRL_MSGTYPE_DH_KEY,
+    OTRL_MSGTYPE_REVEALSIG,
+    OTRL_MSGTYPE_SIGNATURE,
+    OTRL_MSGTYPE_V1_KEYEXCH,
+    OTRL_MSGTYPE_DATA,
+    OTRL_MSGTYPE_ERROR,
+    OTRL_MSGTYPE_UNKNOWN
+} OtrlMessageType;
 
 typedef enum {
     OTRL_FRAGMENT_UNFRAGMENTED,
@@ -46,20 +84,6 @@ typedef enum {
     OTRL_FRAGMENT_COMPLETE
 } OtrlFragmentResult;
 
-typedef struct s_OTRKeyExchangeMsg {
-    gcry_sexp_t digest_sexp;              /* SHA-1 hash of the raw message,
-					     except for the DSA sig; used
-					     for checking the sig */
-    unsigned char is_reply;               /* Was this a reply to a Key
-					     Exchange Message we sent
-					     them? */
-    unsigned char key_fingerprint[20];    /* The key fingerprint */
-    gcry_sexp_t dsa_pubkey;               /* DSA public key */
-    unsigned int keyid;                   /* DH key id */
-    gcry_mpi_t dh_pubkey;                 /* DH public key */
-    gcry_sexp_t dsa_sig;                  /* Signature on packet */
-} * OTRKeyExchangeMsg;
-
 /* Initialize the OTR library.  Pass the version of the API you are
  * using. */
 void otrl_init(unsigned int ver_major, unsigned int ver_minor,
@@ -74,56 +98,24 @@ void otrl_init(unsigned int ver_major, unsigned int ver_minor,
  * the OTR library. */
 const char *otrl_version(void);
 
-/* Create a public key block from a private key */
-gcry_error_t otrl_proto_make_pubkey(unsigned char **pubbufp, size_t *publenp,
-	gcry_sexp_t privkey);
-
 /* Return a pointer to a newly-allocated OTR query message, customized
  * with our name.  The caller should free() the result when he's done
  * with it. */
-char *otrl_proto_default_query_msg(const char *ourname);
+char *otrl_proto_default_query_msg(const char *ourname, OtrlPolicy policy);
+
+/* Return the best version of OTR support by both sides, given an OTR
+ * Query Message and the local policy. */
+unsigned int otrl_proto_query_bestversion(const char *querymsg,
+	OtrlPolicy policy);
+
+/* Locate any whitespace tag in this message, and return the best
+ * version of OTR support on both sides.  Set *starttagp and *endtagp to
+ * the start and end of the located tag, so that it can be snipped out. */
+unsigned int otrl_proto_whitespace_bestversion(const char *msg,
+	const char **starttagp, const char **endtagp, OtrlPolicy policy);
 
 /* Return the Message type of the given message. */
-OTRMessageType otrl_proto_message_type(const char *message);
-
-/* Create a Key Exchange message for our correspondent.  If we need a
- * private key and don't have one, create_privkey will be called.  Use
- * the privkeys from the given OtrlUserState. */
-gcry_error_t otrl_proto_create_key_exchange(OtrlUserState us,
-	char **messagep, ConnContext *context, unsigned char is_reply,
-	void (*create_privkey)(void *create_privkey_data,
-	    const char *accountname, const char *protocol),
-	void *create_privkey_data);
-
-/* Deallocate an OTRKeyExchangeMsg returned from proto_parse_key_exchange */
-void otrl_proto_free_key_exchange(OTRKeyExchangeMsg kem);
-
-/* Parse a purported Key Exchange message.  Possible error code portions
- * of the return value:
- *   GPG_ERR_NO_ERROR:      Success
- *   GPG_ERR_ENOMEM:        Out of memory condition
- *   GPG_ERR_INV_VALUE:     The message was not a well-formed Key Exchange
- *                          message
- *   GPG_ERR_BAD_SIGNATURE: The signature on the message didn't verify
- */
-gcry_error_t otrl_proto_parse_key_exchange(OTRKeyExchangeMsg *kemp,
-	const char *msg);
-
-/* Deal with a Key Exchange Message once it's been received and passed
- * all the validity and UI ("accept this fingerprint?") tests.
- * context/fprint is the ConnContext and Fingerprint to which it
- * belongs.  Use the given OtrlUserState to look up any necessary
- * private keys.  It is the caller's responsibility to
- * otrl_proto_free_key_exchange(kem) when we're done.  If *messagep gets
- * set to non-NULL by this function, then it's a message that needs to
- * get sent to the correspondent.  If we need a private key and don't
- * have one, create_privkey will be called. */
-gcry_error_t otrl_proto_accept_key_exchange(OtrlUserState us,
-	ConnContext *context, Fingerprint *fprint, OTRKeyExchangeMsg kem,
-	char **messagep,
-	void (*create_privkey)(void *create_privkey_data,
-	    const char *accountname, const char *protocol),
-	void *create_privkey_data);
+OtrlMessageType otrl_proto_message_type(const char *message);
 
 /* Create an OTR Data message.  Pass the plaintext as msg, and an
  * optional chain of TLVs.  A newly-allocated string will be returned in
diff --git a/src/serial.h b/src/serial.h
new file mode 100644
index 0000000..981a9e4
--- /dev/null
+++ b/src/serial.h
@@ -0,0 +1,85 @@
+/*
+ *  Off-the-Record Messaging library
+ *  Copyright (C) 2004-2005  Nikita Borisov and Ian Goldberg
+ *                           <otr at cypherpunks.ca>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of version 2.1 of the GNU Lesser General
+ *  Public License as published by the Free Software Foundation.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __SERIAL_H__
+#define __SERIAL_H__
+
+#undef DEBUG
+
+#ifdef DEBUG
+
+#include <stdio.h>
+
+#define debug_data(t,b,l) do { const unsigned char *data = (b); size_t i; \
+	fprintf(stderr, "%s: ", (t)); \
+	for(i=0;i<(l);++i) { \
+	    fprintf(stderr, "%02x", data[i]); \
+	} \
+	fprintf(stderr, "\n"); \
+    } while(0)
+
+#define debug_int(t,b) do { const unsigned char *data = (b); \
+	unsigned int v = \
+	    (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; \
+	fprintf(stderr, "%s: %u (0x%x)\n", (t), v, v); \
+    } while(0)
+
+#else
+#define debug_data(t,b,l)
+#define debug_int(t,b)
+#endif
+
+#define write_int(x) do { \
+	bufp[0] = ((x) >> 24) & 0xff; \
+	bufp[1] = ((x) >> 16) & 0xff; \
+	bufp[2] = ((x) >> 8) & 0xff; \
+	bufp[3] = (x) & 0xff; \
+	bufp += 4; lenp -= 4; \
+    } while(0)
+
+#define write_mpi(x,nx,dx) do { \
+	write_int(nx); \
+	gcry_mpi_print(format, bufp, lenp, NULL, (x)); \
+	debug_data((dx), bufp, (nx)); \
+	bufp += (nx); lenp -= (nx); \
+    } while(0)
+
+#define require_len(l) do { \
+	if (lenp < (l)) goto invval; \
+    } while(0)
+
+#define read_int(x) do { \
+	require_len(4); \
+	(x) = (bufp[0] << 24) | (bufp[1] << 16) | (bufp[2] << 8) | bufp[3]; \
+	bufp += 4; lenp -= 4; \
+    } while(0)
+
+#define read_mpi(x) do { \
+	size_t mpilen; \
+	read_int(mpilen); \
+	if (mpilen) { \
+	    require_len(mpilen); \
+	    gcry_mpi_scan(&(x), GCRYMPI_FMT_USG, bufp, mpilen, NULL); \
+	} else { \
+	    (x) = gcry_mpi_set_ui(NULL, 0); \
+	} \
+	bufp += mpilen; lenp -= mpilen; \
+    } while(0)
+
+#endif
diff --git a/src/tests.c b/src/tests.c
new file mode 100644
index 0000000..ef6e72a
--- /dev/null
+++ b/src/tests.c
@@ -0,0 +1,199 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "proto.h"
+#include "privkey.h"
+#include "message.h"
+
+#define ALICE "oneeyedian"
+#define BOB "otr4ian"
+#define PROTO "prpl-oscar"
+
+static OtrlPolicy ALICEPOLICY = OTRL_POLICY_DEFAULT &~ OTRL_POLICY_ALLOW_V1;
+static OtrlPolicy BOBPOLICY = OTRL_POLICY_DEFAULT;
+
+void receiving(const char *from, const char *to, const char *msg);
+
+typedef struct s_node {
+    struct s_node *next;
+    char *from, *to, *msg;
+} MsgNode;
+
+static MsgNode *noderoot = NULL;
+static MsgNode **nodeend = &noderoot;
+
+static void dispatch(void)
+{
+    while(noderoot) {
+	MsgNode *node = noderoot;
+
+	receiving(node->from, node->to, node->msg);
+	free(node->from);
+	free(node->to);
+	free(node->msg);
+	noderoot = node->next;
+	free(node);
+	if (noderoot == NULL) nodeend = &noderoot;
+    }
+}
+
+static void inject(const char *from, const char *to, const char *msg)
+{
+    MsgNode *node = malloc(sizeof(*node));
+    node->from = strdup(from);
+    node->to = strdup(to);
+    node->msg = strdup(msg);
+    node->next = NULL;
+    *nodeend = node;
+    nodeend = &(node->next);
+    printf("[%s->%s: %s]\n\n", from, to, msg);
+}
+
+static OtrlPolicy op_policy(void *opdata, ConnContext *context)
+{
+    if (!strcmp(context->accountname, ALICE)) return ALICEPOLICY;
+    if (!strcmp(context->accountname, BOB)) return BOBPOLICY;
+    return OTRL_POLICY_DEFAULT;
+}
+
+static void op_inject(void *opdata, const char *accountname,
+	const char *protocol, const char *recipient, const char *message)
+{
+    inject(accountname, recipient, message);
+}
+
+static void op_notify(void *opdata, OtrlNotifyLevel level,
+	    const char *accountname, const char *protocol,
+	    const char *username, const char *title,
+	    const char *primary, const char *secondary)
+{
+}
+
+static int op_display_otr_message(void *opdata, const char *accountname,
+	    const char *protocol, const char *username, const char *msg)
+{
+    return -1;
+}
+
+static void op_gone_secure(void *opdata, ConnContext *context,
+	    int protocol_version)
+{
+    printf("SECURE (%d): %s / %s\n\n", protocol_version, context->accountname,
+	    context->username);
+}
+
+static void op_gone_insecure(void *opdata, ConnContext *context)
+{
+    printf("INSECURE: %s / %s\n\n", context->accountname, context->username);
+}
+
+static void op_still_secure(void *opdata, ConnContext *context, int is_reply,
+	    int protocol_version)
+{
+    printf("REFRESH (%d/%d): %s / %s\n\n", is_reply, protocol_version,
+	    context->accountname, context->username);
+}
+
+static OtrlMessageAppOps ops = {
+    op_policy,
+    NULL,
+    NULL,
+    op_inject,
+    op_notify,
+    op_display_otr_message,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    op_gone_secure,
+    op_gone_insecure,
+    op_still_secure,
+    NULL
+};
+
+static OtrlUserState us;
+
+void receiving(const char *from, const char *to, const char *msg)
+{
+    int ignore;
+    char *newmsg;
+    OtrlTLV *tlvs;
+
+    ignore = otrl_message_receiving(us, &ops, NULL, to, PROTO, from, msg,
+	    &newmsg, &tlvs, NULL, NULL);
+
+    if (!ignore) {
+	printf("%s> %s\n\n", from, newmsg ? newmsg : msg);
+    }
+
+    otrl_message_free(newmsg);
+    otrl_tlv_free(tlvs);
+}
+
+static void sending(const char *from, const char *to, const char *msg)
+{
+    gcry_error_t err;
+    OtrlTLV *tlvs = NULL;
+    char *newmsg;
+
+    err = otrl_message_sending(us, &ops, NULL, from, PROTO, to, msg,
+	    tlvs, &newmsg, NULL, NULL);
+
+    if (!err) {
+	inject(from, to, newmsg ? newmsg : msg);
+    }
+
+    otrl_message_free(newmsg);
+    otrl_tlv_free(tlvs);
+}
+
+void test(int vers, int both)
+{
+    printf("\n\n*** Testing version %d, %s ***\n\n", vers,
+	    both ? "simultaneous start" : "Alice start");
+
+    otrl_context_forget_all(us);
+    ALICEPOLICY = vers == 1 ? (OTRL_POLICY_DEFAULT &~ OTRL_POLICY_ALLOW_V2) :
+	OTRL_POLICY_DEFAULT;
+    sending(ALICE, BOB, "?OTR?");
+    if (both) {
+	sending(BOB, ALICE, "?OTR?");
+    }
+    dispatch();
+    sending(ALICE, BOB, "Hi there");
+    dispatch();
+}
+
+static void test_unreadable(void)
+{
+    ConnContext *bobcontext;
+
+    printf("\n\n*** Testing Bob receiving unreadable messages from "
+	    "Alice ***\n\n");
+
+    bobcontext = otrl_context_find(us, ALICE, BOB, PROTO, 0, NULL, NULL, NULL);
+    otrl_context_force_plaintext(bobcontext);
+    sending(ALICE, BOB, "unreadable text");
+    dispatch();
+
+}
+
+int main(int argc, char **argv)
+{
+    OTRL_INIT;
+    us = otrl_userstate_create();
+
+    otrl_privkey_read(us, "/home/iang/.gaim/otr.private_key");
+
+    test(1,0);
+    test(2,0);
+    test(1,1);
+    test(2,1);
+    test_unreadable();
+
+
+    otrl_userstate_free(us);
+
+    return 0;
+}
diff --git a/src/userstate.h b/src/userstate.h
index 31df2f4..2a69376 100644
--- a/src/userstate.h
+++ b/src/userstate.h
@@ -23,11 +23,11 @@
 typedef struct s_OtrlUserState* OtrlUserState;
 
 #include "context.h"
-#include "privkey.h"
+#include "privkey-t.h"
 
 struct s_OtrlUserState {
     ConnContext *context_root;
-    PrivKey *privkey_root;
+    OtrlPrivKey *privkey_root;
 };
 
 /* Create a new OtrlUserState.  Most clients will only need one of
diff --git a/src/version.h b/src/version.h
index 77187c1..2020e97 100644
--- a/src/version.h
+++ b/src/version.h
@@ -20,7 +20,7 @@
 #ifndef __VERSION_H__
 #define __VERSION_H__
 
-#define OTRL_VERSION "3.0.0"
+#define OTRL_VERSION "3.0.0 beta 1"
 
 #define OTRL_VERSION_MAJOR 3
 #define OTRL_VERSION_MINOR 0
diff --git a/toolkit/otr_modify.c b/toolkit/otr_modify.c
index fe8fe98..776889e 100644
--- a/toolkit/otr_modify.c
+++ b/toolkit/otr_modify.c
@@ -89,7 +89,7 @@ int main(int argc, char **argv)
 	exit(1);
     }
     
-    if (otrl_proto_message_type(otrmsg) != OTR_DATA) {
+    if (otrl_proto_message_type(otrmsg) != OTRL_MSGTYPE_DATA) {
 	fprintf(stderr, "OTR Non-Data Message found on stdin.\n");
 	exit(1);
     }
diff --git a/toolkit/otr_parse.c b/toolkit/otr_parse.c
index ae92d38..9a88659 100644
--- a/toolkit/otr_parse.c
+++ b/toolkit/otr_parse.c
@@ -30,15 +30,71 @@
 
 static void parse(const char *msg)
 {
-    OTRMessageType mtype = otrl_proto_message_type(msg);
+    OtrlMessageType mtype = otrl_proto_message_type(msg);
+    CommitMsg cmsg;
+    KeyMsg kmsg;
+    RevealSigMsg rmsg;
+    SignatureMsg smsg;
     KeyExchMsg keyexch;
     DataMsg datamsg;
 
     switch(mtype) {
-	case OTR_QUERY:
+	case OTRL_MSGTYPE_QUERY:
 	    printf("OTR Query:\n\t%s\n\n", msg);
 	    break;
-	case OTR_KEYEXCH:
+	case OTRL_MSGTYPE_DH_COMMIT:
+	    cmsg = parse_commit(msg);
+	    if (!cmsg) {
+		printf("Invalid D-H Commit Message\n\n");
+		break;
+	    }
+	    printf("D-H Commit Message:\n");
+	    dump_data(stdout, "\tEncrypted Key", cmsg->enckey,
+		    cmsg->enckeylen);
+	    dump_data(stdout, "\tHashed Key", cmsg->hashkey,
+		    cmsg->hashkeylen);
+	    printf("\n");
+	    free_commit(cmsg);
+	    break;
+	case OTRL_MSGTYPE_DH_KEY:
+	    kmsg = parse_key(msg);
+	    if (!kmsg) {
+		printf("Invalid D-H Key Message\n\n");
+		break;
+	    }
+	    printf("D-H Key Message:\n");
+	    dump_mpi(stdout, "\tD-H Key", kmsg->y);
+	    printf("\n");
+	    free_key(kmsg);
+	    break;
+	case OTRL_MSGTYPE_REVEALSIG:
+	    rmsg = parse_revealsig(msg);
+	    if (!rmsg) {
+		printf("Invalid Reveal Signature Message\n\n");
+		break;
+	    }
+	    printf("Reveal Signature Message:\n");
+	    dump_data(stdout, "\tKey", rmsg->key, rmsg->keylen);
+	    dump_data(stdout, "\tEncrypted Signature",
+		    rmsg->encsig, rmsg->encsiglen);
+	    dump_data(stdout, "\tMAC", rmsg->mac, 20);
+	    printf("\n");
+	    free_revealsig(rmsg);
+	    break;
+	case OTRL_MSGTYPE_SIGNATURE:
+	    smsg = parse_signature(msg);
+	    if (!smsg) {
+		printf("Invalid Signature Message\n\n");
+		break;
+	    }
+	    printf("Signature Message:\n");
+	    dump_data(stdout, "\tEncrypted Signature",
+		    smsg->encsig, smsg->encsiglen);
+	    dump_data(stdout, "\tMAC", smsg->mac, 20);
+	    printf("\n");
+	    free_signature(smsg);
+	    break;
+	case OTRL_MSGTYPE_V1_KEYEXCH:
 	    keyexch = parse_keyexch(msg);
 	    if (!keyexch) {
 		printf("Invalid Key Exchange Message\n\n");
@@ -57,7 +113,7 @@ static void parse(const char *msg)
 	    printf("\n");
 	    free_keyexch(keyexch);
 	    break;
-	case OTR_DATA:
+	case OTRL_MSGTYPE_DATA:
 	    datamsg = parse_datamsg(msg);
 	    if (!datamsg) {
 		printf("Invalid Data Message\n\n");
@@ -88,16 +144,16 @@ static void parse(const char *msg)
 	    printf("\n");
 	    free_datamsg(datamsg);
 	    break;
-	case OTR_ERROR:
+	case OTRL_MSGTYPE_ERROR:
 	    printf("OTR Error:\n\t%s\n\n", msg);
 	    break;
-	case OTR_TAGGEDPLAINTEXT:
+	case OTRL_MSGTYPE_TAGGEDPLAINTEXT:
 	    printf("Tagged plaintext message:\n\t%s\n\n", msg);
 	    break;
-	case OTR_NOTOTR:
+	case OTRL_MSGTYPE_NOTOTR:
 	    printf("Not an OTR message:\n\t%s\n\n", msg);
 	    break;
-	case OTR_UNKNOWN:
+	case OTRL_MSGTYPE_UNKNOWN:
 	    printf("Unrecognized OTR message:\n\t%s\n\n", msg);
 	    break;
     }
diff --git a/toolkit/otr_readforge.c b/toolkit/otr_readforge.c
index 61b98f9..edf4b07 100644
--- a/toolkit/otr_readforge.c
+++ b/toolkit/otr_readforge.c
@@ -71,7 +71,7 @@ int main(int argc, char **argv)
 	exit(1);
     }
     
-    if (otrl_proto_message_type(otrmsg) != OTR_DATA) {
+    if (otrl_proto_message_type(otrmsg) != OTRL_MSGTYPE_DATA) {
 	fprintf(stderr, "OTR Non-Data Message found on stdin.\n");
 	exit(1);
     }
diff --git a/toolkit/otr_toolkit.1 b/toolkit/otr_toolkit.1
index d684e58..6224918 100644
--- a/toolkit/otr_toolkit.1
+++ b/toolkit/otr_toolkit.1
@@ -2,7 +2,7 @@
 .\" First parameter, NAME, should be all caps
 .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
 .\" other parameters are allowed: see man(7), man(1)
-.TH OTR_PARSE 1 "December  2, 2004"
+.TH OTR_PARSE 1 "October 16, 2005"
 .\" Please adjust this date whenever revising the manpage.
 .\"
 .\" Some roff macros, for reference:
@@ -67,7 +67,7 @@ Here are the six programs in the toolkit:
 
  - otr_parse
    - Parse OTR messages given on stdin, showing the values of all the
-     fields in OTR Key Exchange Messages and OTR Data Messages.
+     fields in OTR protocol messages.
 
  - otr_sesskeys our_privkey their_pubkey
    - Shows our public key, the session id, two AES and two MAC keys
diff --git a/toolkit/parse.c b/toolkit/parse.c
index 0f58fbb..456754c 100644
--- a/toolkit/parse.c
+++ b/toolkit/parse.c
@@ -182,6 +182,192 @@ void free_keyexch(KeyExchMsg keyexch)
     free(keyexch);
 }
 
+/* Parse a D-H Commit Message into a newly-allocated CommitMsg structure */
+CommitMsg parse_commit(const char *msg)
+{
+    CommitMsg cmsg = NULL;
+    size_t lenp;
+    unsigned char *raw = decode(msg, &lenp);
+    unsigned char *bufp = raw;
+    if (!raw) goto inv;
+
+    cmsg = calloc(1, sizeof(struct s_CommitMsg));
+    if (!cmsg) {
+	free(raw);
+	goto inv;
+    }
+
+    cmsg->raw = raw;
+
+    require_len(3);
+    if (memcmp(bufp, "\x00\x02\x02", 3)) goto inv;
+    bufp += 3; lenp -= 3;
+
+    read_int(cmsg->enckeylen);
+    cmsg->enckey = malloc(cmsg->enckeylen);
+    if (!cmsg->enckey && cmsg->enckeylen > 0) goto inv;
+    read_raw(cmsg->enckey, cmsg->enckeylen);
+
+    read_int(cmsg->hashkeylen);
+    cmsg->hashkey = malloc(cmsg->hashkeylen);
+    if (!cmsg->hashkey && cmsg->hashkeylen > 0) goto inv;
+    read_raw(cmsg->hashkey, cmsg->hashkeylen);
+
+    if (lenp != 0) goto inv;
+
+    return cmsg;
+inv:
+    free_commit(cmsg);
+    return NULL;
+}
+
+/* Deallocate a CommitMsg and all of the data it points to */
+void free_commit(CommitMsg cmsg)
+{
+    if (!cmsg) return;
+    free(cmsg->raw);
+    free(cmsg->enckey);
+    free(cmsg->hashkey);
+    free(cmsg);
+}
+
+/* Parse a D-H Key Message into a newly-allocated KeyMsg structure */
+KeyMsg parse_key(const char *msg)
+{
+    KeyMsg kmsg = NULL;
+    size_t lenp;
+    unsigned char *raw = decode(msg, &lenp);
+    unsigned char *bufp = raw;
+    if (!raw) goto inv;
+
+    kmsg = calloc(1, sizeof(struct s_KeyMsg));
+    if (!kmsg) {
+	free(raw);
+	goto inv;
+    }
+
+    kmsg->raw = raw;
+
+    require_len(3);
+    if (memcmp(bufp, "\x00\x02\x0a", 3)) goto inv;
+    bufp += 3; lenp -= 3;
+
+    read_mpi(kmsg->y);
+
+    if (lenp != 0) goto inv;
+
+    return kmsg;
+inv:
+    free_key(kmsg);
+    return NULL;
+}
+
+/* Deallocate a KeyMsg and all of the data it points to */
+void free_key(KeyMsg kmsg)
+{
+    if (!kmsg) return;
+    free(kmsg->raw);
+    gcry_mpi_release(kmsg->y);
+    free(kmsg);
+}
+
+/* Parse a Reveal Signature Message into a newly-allocated RevealSigMsg
+ * structure */
+RevealSigMsg parse_revealsig(const char *msg)
+{
+    RevealSigMsg rmsg = NULL;
+    size_t lenp;
+    unsigned char *raw = decode(msg, &lenp);
+    unsigned char *bufp = raw;
+    if (!raw) goto inv;
+
+    rmsg = calloc(1, sizeof(struct s_RevealSigMsg));
+    if (!rmsg) {
+	free(raw);
+	goto inv;
+    }
+
+    rmsg->raw = raw;
+
+    require_len(3);
+    if (memcmp(bufp, "\x00\x02\x11", 3)) goto inv;
+    bufp += 3; lenp -= 3;
+
+    read_int(rmsg->keylen);
+    rmsg->key = malloc(rmsg->keylen);
+    if (!rmsg->key && rmsg->keylen > 0) goto inv;
+    read_raw(rmsg->key, rmsg->keylen);
+
+    read_int(rmsg->encsiglen);
+    rmsg->encsig = malloc(rmsg->encsiglen);
+    if (!rmsg->encsig && rmsg->encsiglen > 0) goto inv;
+    read_raw(rmsg->encsig, rmsg->encsiglen);
+
+    read_raw(rmsg->mac, 20);
+
+    if (lenp != 0) goto inv;
+
+    return rmsg;
+inv:
+    free_revealsig(rmsg);
+    return NULL;
+}
+
+/* Deallocate a RevealSigMsg and all of the data it points to */
+void free_revealsig(RevealSigMsg rmsg)
+{
+    if (!rmsg) return;
+    free(rmsg->raw);
+    free(rmsg->key);
+    free(rmsg->encsig);
+    free(rmsg);
+}
+
+/* Parse a Signature Message into a newly-allocated SignatureMsg structure */
+SignatureMsg parse_signature(const char *msg)
+{
+    SignatureMsg smsg = NULL;
+    size_t lenp;
+    unsigned char *raw = decode(msg, &lenp);
+    unsigned char *bufp = raw;
+    if (!raw) goto inv;
+
+    smsg = calloc(1, sizeof(struct s_SignatureMsg));
+    if (!smsg) {
+	free(raw);
+	goto inv;
+    }
+
+    smsg->raw = raw;
+
+    require_len(3);
+    if (memcmp(bufp, "\x00\x02\x12", 3)) goto inv;
+    bufp += 3; lenp -= 3;
+
+    read_int(smsg->encsiglen);
+    smsg->encsig = malloc(smsg->encsiglen);
+    if (!smsg->encsig && smsg->encsiglen > 0) goto inv;
+    read_raw(smsg->encsig, smsg->encsiglen);
+
+    read_raw(smsg->mac, 20);
+
+    if (lenp != 0) goto inv;
+
+    return smsg;
+inv:
+    free_signature(smsg);
+    return NULL;
+}
+
+/* Deallocate a SignatureMsg and all of the data it points to */
+void free_signature(SignatureMsg smsg)
+{
+    if (!smsg) return;
+    free(smsg->raw);
+    free(smsg->encsig);
+    free(smsg);
+}
+
 /* Parse a Data Message into a newly-allocated DataMsg structure */
 DataMsg parse_datamsg(const char *msg)
 {
diff --git a/toolkit/parse.h b/toolkit/parse.h
index f5362d4..9329289 100644
--- a/toolkit/parse.h
+++ b/toolkit/parse.h
@@ -49,6 +49,35 @@ typedef struct s_DataMsg {
     unsigned char *macend;      /*   free() these. */
 } * DataMsg;
 
+typedef struct s_CommitMsg {
+    unsigned char *raw;         /* The base64-decoded data; must be free()d */
+    unsigned char *enckey;
+    size_t enckeylen;
+    unsigned char *hashkey;
+    size_t hashkeylen;
+} * CommitMsg;
+
+typedef struct s_KeyMsg {
+    unsigned char *raw;         /* The base64-decoded data; must be free()d */
+    gcry_mpi_t y;
+} * KeyMsg;
+
+typedef struct s_RevealSigMsg {
+    unsigned char *raw;         /* The base64-decoded data; must be free()d */
+    unsigned char *key;
+    size_t keylen;
+    unsigned char *encsig;
+    size_t encsiglen;
+    unsigned char mac[20];
+} * RevealSigMsg;
+
+typedef struct s_SignatureMsg {
+    unsigned char *raw;         /* The base64-decoded data; must be free()d */
+    unsigned char *encsig;
+    size_t encsiglen;
+    unsigned char mac[20];
+} * SignatureMsg;
+
 /* Dump an unsigned int to a FILE * */
 void dump_int(FILE *stream, const char *title, unsigned int val);
 
@@ -65,9 +94,34 @@ KeyExchMsg parse_keyexch(const char *msg);
 /* Deallocate a KeyExchMsg and all of the data it points to */
 void free_keyexch(KeyExchMsg keyexch);
 
+/* Parse a D-H Commit Message into a newly-allocated CommitMsg structure */
+CommitMsg parse_commit(const char *msg);
+
 /* Parse a Data Message into a newly-allocated DataMsg structure */
 DataMsg parse_datamsg(const char *msg);
 
+/* Deallocate a CommitMsg and all of the data it points to */
+void free_commit(CommitMsg cmsg);
+
+/* Parse a Reveal Signature Message into a newly-allocated RevealSigMsg
+ * structure */
+RevealSigMsg parse_revealsig(const char *msg);
+
+/* Deallocate a RevealSigMsg and all of the data it points to */
+void free_revealsig(RevealSigMsg rmsg);
+
+/* Parse a Signature Message into a newly-allocated SignatureMsg structure */
+SignatureMsg parse_signature(const char *msg);
+
+/* Deallocate a SignatureMsg and all of the data it points to */
+void free_signature(SignatureMsg smsg);
+
+/* Parse a D-H Key Message into a newly-allocated KeyMsg structure */
+KeyMsg parse_key(const char *msg);
+
+/* Deallocate a KeyMsg and all of the data it points to */
+void free_key(KeyMsg cmsg);
+
 /* Recalculate the MAC on the message, base64-encode the resulting MAC'd
  * message, and put on the appropriate header and footer.  Return a
  * newly-allocated pointer to the result, which the caller will have to

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/libotr.git



More information about the Pkg-privacy-commits mailing list