[Pkg-privacy-commits] [libotr] 134/225: Imported Upstream version 3.0.0

Ximin Luo infinity0 at moszumanska.debian.org
Sat Aug 22 12:45:13 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 2fc878e4f104d4278051c697b90ea464840b13ba
Author: intrigeri <intrigeri at boum.org>
Date:   Tue Feb 18 20:12:51 2014 +0000

    Imported Upstream version 3.0.0
---
 ChangeLog                      |  157 +++++
 Makefile.am                    |    2 +-
 Makefile.in                    |    2 +-
 NEWS                           |   36 +-
 Protocol                       |  512 ---------------
 Protocol-v2.html               | 1196 ++++++++++++++++++++++++++++++++++
 README                         |   12 +-
 configure                      |    4 +-
 configure.ac                   |    4 +-
 libotr.m4                      |   18 +-
 packaging/fedora/libotr.spec   |  113 ++--
 src/Makefile.am                |    4 +-
 src/Makefile.in                |   17 +-
 src/auth.c                     | 1413 ++++++++++++++++++++++++++++++++++++++++
 src/auth.h                     |  157 +++++
 src/b64.c                      |   63 ++
 src/b64.h                      |   15 +
 src/context.c                  |  107 ++-
 src/context.h                  |   88 ++-
 src/dh.c                       |  237 ++++++-
 src/dh.h                       |   42 +-
 src/mem.c                      |   10 +-
 src/message.c                  | 1200 +++++++++++++++++++---------------
 src/message.h                  |   48 +-
 src/{version.h => privkey-t.h} |   22 +-
 src/privkey.c                  |  250 ++++++-
 src/privkey.h                  |   33 +-
 src/proto.c                    |  778 ++++++++--------------
 src/proto.h                    |  153 ++---
 src/serial.h                   |   85 +++
 src/tlv.c                      |   11 +-
 src/tlv.h                      |   11 +-
 src/userstate.h                |    4 +-
 src/version.h                  |    6 +-
 toolkit/otr_modify.c           |    2 +-
 toolkit/otr_parse.c            |   77 ++-
 toolkit/otr_readforge.c        |    2 +-
 toolkit/otr_remac.c            |   28 +-
 toolkit/otr_toolkit.1          |    8 +-
 toolkit/parse.c                |  222 ++++++-
 toolkit/parse.h                |   63 +-
 41 files changed, 5291 insertions(+), 1921 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index e38b42f..cf826f9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,160 @@
+2005-11-02
+
+	* README:
+	* src/version.h: Release version 3.0.0
+
+2005-10-30
+
+	* Protocol-v2.html: Clarified the uniqueness conditions for the
+	counter.
+
+	* src/auth.c (otrl_auth_handle_v1_key_exchange): Clear the auth
+	structure when we receive an unexpected v1 Key Exchange Message.
+
+2005-10-27
+
+	* src/auth.h:
+	* src/auth.c:
+	* src/message.c: Ensure version 2 AKEs are always done with
+	fresh D-H parameters.
+
+	* src/proto.h:
+	* src/proto.c:
+	* src/message.c: Add a "flags" field to the version 2 Data
+	Message, which can indicate that the Data Message should be
+	ignored if unreadable (as opposed to displaying an error).
+
+	* toolkit/parse.h:
+	* toolkit/parse.c:
+	* toolkit/otr_parse.c:
+	* toolkit/otr_remac.c: Deal with the new kind of Data Message.
+
+	* src/message.c: Use the gone_secure callback instead of the
+	still_secure callback if the other side changes its fingerprint.
+
+2005-10-19
+
+	* src/context.h:
+	* src/context.c: Added protocol_version as an explicit field in
+	the ConnContext.
+
+	* src/message.h:
+	* src/message.c: protocol_version no longer needs to be
+	explicitly passed to the gone_secure() and still_secure()
+	callbacks.
+
+	* packaging/fedora/libotr.spec: Patches from Paul
+
+	* src/proto.c (rotate_dh_keys): Avoid potential double
+	gcry_cipher_close().
+
+	* src/tests.c: Regression test for double gcry_cipher_close().
+
+2005-10-16
+
+	* Major overhaul with implementation of version 2 AKE.
+
+2005-08-08
+
+	* toolkit/otr_parse.c (parse): Ignore MACs that are too short,
+	rather than going into an infinite loop.
+
+2005-08-04
+
+	* Protocol: Added section describing fragments.
+
+	* src/proto.h:
+	* src/proto.c (otrl_proto_fragment_accumulate): 
+	* src/context.h:
+	* src/context.c (new_context, otrl_context_force_setup): Keep
+	track of fragments in the ConnContext structure.
+
+	* src/message.c (otrl_message_receiving): Handle fragments in
+	received messages.
+
+	* src/mem.c: Don't do arithmetic on void pointers.
+
+2005-07-29
+
+	* src/message.h:
+	* src/message.c: Move ops to be the first param of
+	new_fingerprint, as it is with all the other callbacks.
+
+	* src/context.h:
+	* src/context.c (otrl_context_set_preshared_secret):
+	* src/dh.h:
+	* src/dh.c (otrl_dh_session, otrl_dh_cmpctr):
+	* src/message.h:
+	* src/message.c (otrl_message_sending, send_or_error, process_kem)
+	(otrl_message_receiving, otrl_message_disconnect):
+	* src/privkey.h:
+	* src/privkey.c (otrl_privkey_hash_to_human):
+	* src/proto.h:
+	* src/proto.c (otrl_proto_create_data):
+	* src/tlv.h:
+	* src/tlv.c (otrl_tlv_new, otrl_tlv_parse, otrl_tlv_seriallen)
+	(otrl_tlv_serialize): Add missing "const"s.  (Closes #1243963)
+
+2005-06-24
+
+	* README:
+	* configure.ac:
+	* packaging/fedora/libotr.spec:
+	* src/version.h: Change version to 3.0.0 (but don't yet release)
+
+	* Protocol: Clarify that, if the user requests to see the secure
+	session id in the middle of the conversation, the value
+	displayed should be the one calculated at the time the private
+	connection was established (the last Key Exchange Message that
+	caused a rekeying), _not_ the DH secure id calculated from DH
+	keys in more recent Data Messages.
+
+	* libotr.m4: Have the version check require an exact match on
+	the major version, since, for example, source that expects
+	libotr 2.0.0 won't work with libotr 3.0.0.
+
+	* libotr.m4: Add #include <stdlib.h> to the version test so that
+	it compiles cleanly with -Wall -Werror.
+
+	* src/proto.c:
+	* src/dh.h:
+	* src/dh.c:
+	* src/context.h:
+	* src/context.c: Save the secure session id so that it can be
+	displayed to the user upon request, instead of only when the
+	private session is initially set up.
+
+	* src/privkey.c:
+	* src/context.h:
+	* src/context.c: Allow the app to set a "trust level" for
+	fingerprints.  This is an arbitrary string, intended to indicate
+	whether (or possibly by what means) the user has verified that
+	this fingerprint is accurate.
+
+	* src/context.h:
+	* src/context.c: Allow the app to set an arbitrary binary
+	"preshared secret" for the ConnContext.  This is currently
+	unused, but in the future it would allow for users to exchange a
+	secret _before_ they generate their fingerprints.  [But the
+	protocol would have to be extended to support this.]
+
+	* src/message.h:
+	* src/message.c: Remove the "confirm_fingerprint" callback
+	which requires the user to acknowledge the new fingerprint
+	before it can be used.  Replace it with a "new_fingerprint"
+	callback which merely informs the user that a new fingerprint
+	has been received.
+
+2005-05-13
+
+	* libotr.m4: Fixed a bug which made configure fail to find the
+	libotr header files if they weren't in the standard place.
+
+2005-05-09
+
+	* src/privkey.c (otrl_privkey_read_fingerprints): Allow fields,
+	particularly accountnames, to contain spaces.  Closes #1198379.
+
 2005-05-03
 
 	* README:
diff --git a/Makefile.am b/Makefile.am
index b06e542..bc190c3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
 SUBDIRS = src toolkit
 
-EXTRA_DIST = Protocol packaging libotr.m4
+EXTRA_DIST = Protocol-v2.html packaging libotr.m4
 
 aclocaldir = $(datadir)/aclocal
 aclocal_DATA = libotr.m4
diff --git a/Makefile.in b/Makefile.in
index 2fa3d6e..2b06cc9 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -174,7 +174,7 @@ sharedstatedir = @sharedstatedir@
 sysconfdir = @sysconfdir@
 target_alias = @target_alias@
 SUBDIRS = src toolkit
-EXTRA_DIST = Protocol packaging libotr.m4
+EXTRA_DIST = Protocol-v2.html packaging libotr.m4
 aclocaldir = $(datadir)/aclocal
 aclocal_DATA = libotr.m4
 all: config.h
diff --git a/NEWS b/NEWS
index 0fd4081..d2df6be 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,24 @@
-3 May 2005:
+02 Nov 2005:
+- Released 3.0.0
+
+16 Oct 2005:
+- Major overhaul with implementation of version 2 of the protocol.
+
+24 Jun 2005:
+- Remove the "confirm_fingerprint" callback which requires the user to
+  acknowledge the new fingerprint before it can be used.  Replace it
+  with a "new_fingerprint" callback which merely informs the user that a
+  new fingerprint has been received.
+- Allow the app to set a "trust level" for fingerprints.  This is an
+  arbitrary string, intended to indicate whether (or possibly by what
+  means) the user has verified that this fingerprint is accurate.
+- Clarify that, if the user requests to see the secure session id in
+  the middle of the conversation, the value displayed should be the one
+  calculated at the time the private connection was established (the
+  last Key Exchange Message that caused a rekeying), _not_ the DH secure
+  id calculated from DH keys in more recent Data Messages.
+
+03 May 2005:
 - Released 2.0.2
 
 16 Feb 2005:
@@ -10,11 +30,11 @@
 - Fix a crash bug that happened when messages were retransmitted under
   certain circumstances.
 
-8 Feb 2005:
+08 Feb 2005:
 - Released 2.0.0
 - Keep track of whether a given message is eligible for retransmission
 
-2 Feb 2005:
+02 Feb 2005:
 - Released 1.99.0, the first preview release of 2.0.0
 
 31 Jan 2005:
@@ -84,13 +104,13 @@
 - Heartbeats now only get sent if (1) we have just received a message,
   and (2) we haven't sent one to that user in over a minute.
 
-9 Dec 2004:
+09 Dec 2004:
 - Back out of the sending of heartbeats.  They were causing too many
   problems.  It seems some networks don't let buddies know when you
   log out, and then you get a dialog box "unable to send message" each
   minute.  :-(
 
-8 Dec 2004:
+08 Dec 2004:
 - Released 0.9.9rc1
 - Removed the 100 private connection limit, by not using a fixed amount
   of secure memory.  Unfortuantely, this means that *no* memory is
@@ -106,15 +126,15 @@
   or both sides aren't actively typing.  This aids perfect forward
   secrecy.
 
-4 Dec 2004:
+04 Dec 2004:
 - Fixed a bug wherein multi-person chat windows would get the OTR button
   in their button bar if the OTR plugin was enabled when one of them was
   active.
 
-3 Dec 2004:
+03 Dec 2004:
 - Released 0.9.1
 
-2 Dec 2004:
+02 Dec 2004:
 - Clicking "OTR: Private" when you're already private will display an
   info dialog letting you know the connection was refreshed (assuming it
   actually is; if the other side isn't running OTR at all, the dialog
diff --git a/Protocol b/Protocol
deleted file mode 100644
index f236882..0000000
--- a/Protocol
+++ /dev/null
@@ -1,512 +0,0 @@
-	       Off-The-Record Wire Protocol Documentation
-	       ------------------------------------------
-
-		    Nikita Borisov and Ian Goldberg
-			  <otr at cypherpunks.ca>
-
-Definitions
------------
-
-Data encodings:
-
-    Bytes (BYTE):
-      1 byte unsigned value
-    Shorts (SHORT):
-      2 byte unsigned value, big-endian
-    Ints (INT):
-      4 byte unsigned value, big-endian
-    Multi-precision integers (MPI):
-      4 byte unsigned len, big-endian
-      len byte unsigned value, big-endian
-      (MPIs must use the minimum-length encoding; i.e. no leading 0x00
-      bytes.  This is important when calculating public key fingerprints.)
-    Opaque variable-length data (DATA):
-      4 byte unsigned len, big-endian
-      len byte data
-    DSA signature (DSASIG):
-      (len is the length of the DSA public parameter q)
-      len byte unsigned r, big-endian
-      len byte unsigned s, big-endian
-    Initial CTR-mode counter value (CTR):
-      8 bytes data
-    Message Authentication Code (MAC):
-      20 bytes MAC data
-
-Policies:
-
-    Clients can set one of four OTR policies.  This can be done either
-    on a per-correspondent basis, or globally, or both.  The policies
-    are:
-
-    NEVER:
-	Never perform OTR with this correspondent.
-    MANUAL:
-	Only start OTR if one of you specifically requests it.
-    OPPORTUNISTIC:
-	Start OTR if there's any indication the correspondent supports it.
-    ALWAYS:
-	Only use OTR with this correspondent; it is an error to send an
-	unencrypted message to him.
-
-    The default setting should be OPPORTUNISTIC.
-
-Whitespace Tag:
-
-    There is an OTR_MESSAGE_TAG, which is a 24-byte string of whitespace
-    that clients can optionally append to (or insert in) messages to
-    unobtrusively indicate that they understand the OTR protocol.  The
-    string is as follows (in C notation):
-
-	\x20\x09\x20\x20\x09\x09\x09\x09
-	\x20\x09\x20\x09\x20\x09\x20\x20
-	\x20\x09\x20\x09\x20\x20\x09\x20
-
-    [This is the bit pattern of the string "OTR" as expressed in spaces and
-    tabs.]
-
-Protocol
---------
-
-An OTR client maintains a separate state with each of its
-correspondents.  Initially, all correspondents start in the UNCONNECTED
-state.
-
-This is the state machine for the default (OPPORTUNISTIC) policy.  The
-modifications for other policies are below.
-
-In the UNCONNECTED state:
-
- - It may, at the user's instigation or otherwise (for example, if the
-   correspondent's AIM Capability list indicates he supports OTR), send
-   an OTR Query message, and remain in the UNCONNECTED state.
-
- - If it receives an OTR Query message, it replies with an OTR Key
-   Exchange message, and moves to the SETUP state.
-
- - If it receives an OTR Key Exchange message, it:
-   - verifies the information in the Key Exchange message
-     - If the verification fails, send an OTR Error message, and remain
-       in the UNCONNECTED state.
-     - If the verification succeeds, reply with an OTR Key Exchange
-       message with the Reply field set to 0x01 (_even if_ the received
-       Key Exchange message itself had the Reply field set), inform the
-       user that private communication has been established, and move to
-       the CONNECTED state.
-
- - If it receives an OTR Data message, it replies with an OTR Error
-   message, sends a Key Exchange message (with Reply set to 0x00), and
-   moves to the SETUP state.
-
- - If it receives an OTR Error message, it should display the message to
-   the user, send a Key Exchange message (with Reply set to 0x00), and
-   move to the SETUP state.
-
- - If it receives an non-OTR message:
-   - If the message contains the OTR_MESSAGE_TAG, it should reply with
-     an OTR Key Exchange message, strip the OTR_MESSAGE_TAG from the
-     message, display the message to the user, and move to the SETUP state.
-   - Otherwise, it should display the message to the user, and remain in
-     the UNCONNECTED state.
-
-In the SETUP state:
-
- - The user, through a UI action, may elect to reset the connection
-   to the UNCONNECTED state.
-
- - If it receives an OTR Query message, it replies with an OTR Key
-   Exchange message, and remains in the SETUP state.
-
- - If it receives an OTR Key Exchange message, it:
-   - verifies the information in the Key Exchange message
-     - If the verification fails, send an OTR Error message, display a
-       notice of the error to the user, and remain in the SETUP state.
-     - If the verification succeeds:
-       - If the received Key Exchange message did not have the Reply field
-         set to 0x01, reply with an OTR Key Exchange (with the Reply field
-         set to 0x01).
-       - In any event,  inform the user that private communication has
-	 been established, and move to the CONNECTED state.
-
- - If it receives an OTR Data message, it replies with an OTR Error
-   message, sends a Key Exchange message (with Reply set to 0x00), and
-   remains in the SETUP state.
-
- - If it receives an OTR Error message, it should display the message to
-   the user, send a Key Exchange message (with Reply set to 0x00), and
-   remain in the SETUP state.
-
- - If it receives an non-OTR message:
-   - If the message contains the OTR_MESSAGE_TAG, it should reply with
-     an OTR Key Exchange message, and strip the OTR_MESSAGE_TAG from the
-     message, display the message to the user, and remain to the SETUP state.
-   - Otherwise, it should display the message to the user, and remain
-     in the SETUP state.
-
-In the CONNECTED state:
-
- - The user, through a UI action, may elect to reset the connection
-   to the UNCONNECTED state.
-
- - If it receives an OTR Query message, it replies with an OTR Key
-   Exchange message, and remains in the CONNECTED state.
-
- - If it receives an OTR Key Exchange message, it:
-   - verifies the information in the Key Exchange message
-     - If the verification fails, send an OTR Error message, display a
-       notice of the error to the user, and remain in the CONNECTED
-       state.
-     - If the verification succeeds:
-       - If the received Key Exchange message did not have the Reply field
-         set to 0x01, reply with an OTR Key Exchange (with the Reply field
-         set to 0x01).
-       - In any event, remain in the CONNECTED state.
-
- - If it receives an OTR Data message, it:
-   - verifies the information in the Data message
-     - If the verification fails, send an OTR Error message, display a
-       notice of the error to the user, and remain in the CONNECTED
-       state.
-     - If the verification succeeds, display the (decrypted) message to
-       the user, and remain in the CONNECTED state.
-
- - If it receives an OTR Error message, it should display the message to
-   the user, and remain in the CONNECTED state.
-
- - If it receives an non-OTR message, it should reply with an OTR Error
-   message, and remain in the CONNECTED state.
-
-Other policies:
-
-    The above decribes what to do in the default (OPPORTUNISTIC) policy.
-
-    If the policy is NEVER: behave as if OTR is not enabled at all.
-    Pass all received messages to the user, and send all of the user's
-    messages out untouched.
-
-    If the policy is MANUAL: never send the OTR_MESSAGE_TAG in messages,
-    and never respond to one you receive.  Don't send a Key Exchange
-    Message (or change state) in response to receiving a Data or Error
-    Message in the UNCONNECTED or SETUP states.
-
-    If the policy is ALWAYS: never send an unencrypted message.  Either
-    report an error to the user, or else hold on to the message, send an
-    OTR Query Message instead, and once you enter the CONNECTED state,
-    send the original message.  Warn the user if you receive an
-    unencrypted message.
-
-Protocol messages
------------------
-
-There are four types of messages in the OTR protocol:
- - OTR Query
- - OTR Key Exchange
- - OTR Data
- - OTR Error
-
-OTR Query
----------
-
-This message is sent to inquire if the correspondent supports the OTR
-protocol.
-
-Format:
-
-Any message containing the string "?OTR?" is considered an OTR Query.
-
-Since this message will be visible to the correspondent in the event
-that he does not support the OTR protocol, it may also contain
-human-readable information after this initial string.
-
-Example:
-
-?OTR?\nYour client does not support the OTR Private Messaging
-Protocol.\nSee http://www.cypherpunks.ca/otr/ for more information.
-
-OTR Key Exchange
-----------------
-
-This message is sent to inform the correspondent of your public
-signature key, and your current DH encryption key.
-
-Format:
-
-The message must contain the five bytes "?OTR:".  After that is the
-base-64 encoding of the following, followed by the byte ".":
-
-  - Protocol version (SHORT)
-    - The version number of this protocol is 0x0001.
-  - Message type (BYTE)
-    - OTR Key Exchange has message type 0x0a.
-  - Reply (BYTE)
-    - 0x01 if this Key Exchange message is being sent in reply to a Key
-      Exchange message that was just received.  0x00 otherwise.
-  - DSA p (MPI)
-  - DSA q (MPI)
-  - DSA g (MPI)
-  - DSA e (MPI)
-    - The DSA public key (p,q,g,e).
-      [The parameter 'e' is usually called 'y', but that name's taken by
-      the DH public key, below.]
-  - Sender keyid (INT)
-    - The keyid for this initial key.  Must be greater than 0.
-  - DH y (MPI)
-    - The initial DH public encryption key.  The DH group is the one
-      defined in RFC 3526 with 1536-bit modulus (hex, big-endian):
-	    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
-      and generator 2.
-  - Signature (DSASIG)
-    - A signature with the private DSA key corresponding to (p,q,g,e).
-      Take a SHA-1 hash of everything from the protocol version to the
-      end of the value of y, and sign that value.
-
-  The DSA key given in this message has a "Fingerprint", which is the
-  SHA-1 hash of the portion of the message from the beginning of the "p"
-  field (including the MPI length) to the end of the "e" field.  This
-  fingerprint should be displayed to the recipient so that he may verify
-  the sender's key.
-
-OTR Data
---------
-
-This message is used to transmit a private message to the correspondent.
-It is also used to reveal old MAC keys.
-
-The plaintext message (either before encryption, or after decryption)
-consists of a human-readable message, optionally followed by:
-   - a single NUL (a BYTE with value 0x00)
-   AND
-   - zero or more TLV (type/length/value) records (with no padding
-     between them)
-
-Each TLV record is of the form:
-   - Type (SHORT)
-     - The type of this record.  Records with unrecognized types should
-       be ignored.
-   - Length (SHORT)
-     - The length of the following field
-   - Value (len BYTEs)  [where len is the value of the Length field]
-     - Any pertinent data for the record type.
-
-Some TLV examples:
-
-    \x00\x01\x00\x00
-       A TLV of type 1, containing no data
-
-    \x00\x00\x00\x05\x68\x65\x6c\x6c\x6f
-       A TLV of type 0, containing the value "hello"
-
-The currently defined TLV record types are:
-
-    Type 0: Padding
-    	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.
-
-    Type 1: Disconnected
-	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.  If you receive a TLV record of this type,
-	you may inform the user that his correspondent has closed his
-	end of the private connection, and the user should do the same.
-
-Format:
-
-The message must contain the five bytes "?OTR:".  After that is the
-base-64 encoding of the following, followed by the byte ".":
-
-  - Protocol version (SHORT)
-    - The version number of this protocol is 0x0001.
-  - Message type (BYTE)
-    - OTR Data has message type 0x03.
-  - Sender keyid (INT)
-    - Must be strictly greater than 0, and increment by 1 with each key
-      change
-  - Recipient keyid (INT)
-    - Must therefore be strictly greater than 0, as the receiver has no
-      key with id 0
-    - The sender and recipient keyids are those used to encrypt and MAC
-      this message.
-  - DH y (MPI)
-    - The *next* [i.e. sender_keyid+1] public key for the sender
-  - Top half of counter init (CTR)
-    - 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.
-  - Encrypted message (DATA)
-    - 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), pad with NULs (bytes of 0x00).  Upon receiving
-      and successfully decrypting an OTR Data Message, the decrypted
-      payload should be truncated just before the first NUL (if any).
-  - SHA1-HMAC, using the appropriate MAC key (see below) of everything
-    from the Protocol version to the end of the encrypted message (MAC)
-  - Old MAC keys to be revealed (DATA)
-    - See "Revealing MAC Keys", below.
-
-OTR Error
----------
-
-This message is sent when a problem has occurred in the protocol.
-
-Format:
-
-Any message containing "?OTR Error:" is an OTR Error message.  The
-following part of the message should contain human-readable details of
-the error.
-
-Key Management
---------------
-
-For each correspondent, keep track of:
- - My two most recent DH public/private key pairs
-   - our_dh[our_keyid] (most recent) and our_dh[our_keyid-1] (previous)
- - His two most recent DH public keys
-   - their_y[their_keyid] (most recent) and their_y[their_keyid-1] (previous)
-
-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.
-
-When you send an OTR Key Exchange message:
-    Send the public part of our_dh[our_keyid-1], with the keyid field,
-    of course, set to (our_keyid-1).
-
-When you receive an OTR Key Exchange message:
-    If the specified keyid equals either their_keyid or their_keyid-1,
-    and the DH pubkey contained in the Key Exchange message matches the
-    one we'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 Key Exchange message, and
-    their_y[their_keyid] to the DH pubkey value given in the Key
-    Exchange message.  their_y[their_keyid-1] should be set to NULL.
-
-    In any event, if the Reply field of the Key Exchange message was set
-    to 0x00, send a Key Exchange message with the Reply field set to
-    0x01.
-
-When you send an OTR Data message:
-    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.
-
-When you receive an OTR Data message:
-    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.
-
-    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.
-
-    Check that the counter in the Data message is strictly larger than the
-    last counter we saw using this pair of keys.  If not, reject the
-    message.
-
-    If the MAC verifies, decrypt the message using the "receiving AES key".
-
-    Finally, check if keys need rotation:
-     - 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 we
-       securely forget our_dh[our_keyid-1], increment our_keyid, and set
-       our_dh[our_keyid] to a new DH key pair which we generate.
-     - 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.
-
-    If the message you get after decryption is of zero length, this is a
-    "heartbeat" packet.  Don't display it to the user.  (But it's still
-    useful to effect key rotations.)
-
-Calculating session keys:
-    Given one of our DH key pairs, and one of his DH pubkeys, we
-    calculate a session id, two AES keys, and two MAC keys as follows:
-
-    Let (our_x, our_y) be the private and public parts of our DH
-    key pair.  Let their_y be his DH pubkey.
-
-    First, calculate the shared secret:
-      secret = their_y ^ our_x mod DH_MODULUS
-    (^ denotes exponentiation, and DH_MODULUS is the 1536-bit DH modulus
-    from RFC 3526, as specified above).
-
-    Write the value of secret as a minimum-length MPI, as specific above
-    (4-byte big-endian len, len-byte big-endian value).  Let this
-    (4+len)-byte value be "secbytes".
-
-    Next, determine if we are the "low" end or the "high" end of this
-    key exchange.  If our_y > their_y, then we are the "high" end.
-    Otherwise, we are the "low" end.  Note that who is the "low" end and
-    who is the "high" end can change every time a new DH pubkey is
-    exchanged.
-
-    Calculate the session id as the SHA-1 hash of the (5+len)-byte value
-    composed of the byte 0x00, followed by the (4+len) bytes of
-    secbytes.  When a new private connection is established, display
-    these 8 bytes to the user as two 4-byte (big-endian) values, in C
-    "%08x" format.  If we are the "low" end of the key exchange, display
-    the first 10 bytes in bold, and the second 10 bytes in non-bold.  If
-    we are the "high" end of the key exchange, display the first 10 bytes
-    in non-bold, and the second 10 bytes 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.
-
-    Now set the values of "sendbyte" and "recvbyte" according to whether
-    we are the "low" or the "high" end of the key exchange:
-     - If we are the "high" end, set "sendbyte" to 0x01 and "recvbyte"
-       to 0x02.
-     - If we are the "low" end, set "sendbyte" to 0x02 and "recvbyte"
-       to 0x01.
-
-    Calculate the "sending AES key" to be the first 16 bytes of the
-    SHA-1 hash of the (5+len)-byte value composed of the byte
-    (sendbyte), followed by the (4+len) bytes of secbytes.
-
-    Calculate the "sending MAC key" to be all 20 bytes of the SHA-1 hash
-    of the 16-byte sending AES key.
-
-    Calculate the "receiving AES key" to be the first 16 bytes of the
-    SHA-1 hash of the (5+len)-byte value composed of the byte
-    (recvbyte), followed by the (4+len) bytes of secbytes.
-
-    Calculate the "receiving MAC key" to be all 20 bytes of the SHA-1 hash
-    of the 16-byte receiving AES key.
-
-Revealing MAC keys
-------------------
-
-Whenever you are about to forget either one of your old DH key pairs, or
-one of your correspondent's old DH pubkeys, 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.  We do this 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 us accepting a message as valid which
-uses a MAC key which has already been revealed.
diff --git a/Protocol-v2.html b/Protocol-v2.html
new file mode 100644
index 0000000..b24dad0
--- /dev/null
+++ b/Protocol-v2.html
@@ -0,0 +1,1196 @@
+<!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>
+<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 key<sub>A</sub> by that key, and let keyid<sub>A</sub>
+be its serial number.</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
+key<sub>B</sub> by that key, and let keyid<sub>B</sub> be its serial
+number.</li>
+<li>Uses Diffie-Hellman to compute a shared secret from the two keys
+key<sub>A</sub> and key<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
+(key<sub>A</sub>, key<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>The version number of this protocol is 0x0002.</dd>
+<dt>Message type (BYTE)</dt>
+<dd>The Data Message has type 0x03.</dd>
+<dt>Flags (BYTE)</dt>
+<dd>The bitwise-OR of the flags for this message.  Usually you should
+set this to 0x00.  The only currently defined flag is:<dl>
+<dt>IGNORE_UNREADABLE (0x01)</dt>
+<dd>If you receive a Data Message with this flag set, and you are unable
+to decrypt the message or verify the MAC (because, for example, you
+don't have the right keys), just ignore the message instead of producing
+some kind of error or notification to the user.</dd>
+</dl></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.  The heartbeat message should have
+the IGNORE_UNREADABLE flag set.</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 0be6d4f..19a3034 100644
--- a/README
+++ b/README
@@ -1,5 +1,5 @@
 	      Off-the-Record Messaging Library and Toolkit
-			  v2.0.2,  3 May 2005
+			  v3.0.0,  2 Nov 2005
 
 This is a library and toolkit which implements Off-the-Record (OTR) Messaging.
 
@@ -74,12 +74,6 @@ pointer you pass to the library, and it will pass back (opaquely) to the
 UI functions when it calls them.  You can use this to keep track of
 state or any other information.
 
-NOTE: if you use automatic (stack) variables for the opdata, or if you
-use heap variables that may get free()d, be extra careful in the
-confirm_fingerprint function.  You need to make sure the opdata is not
-deallocated before the user chooses whether to accept the fingerprint or
-not.
-
 You will need to include proto.h and message.h, and you can find a list
 of the UI functions in message.h.
 
@@ -185,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
@@ -213,7 +207,7 @@ Here are the six programs in the toolkit:
      you can still forge messages of your choice using the otr_readforge
      command, above.
 
- - otr_remac mackey keyid keyid pubkey counter encdata revealed_mackeys
+ - otr_remac mackey flags keyid keyid pubkey counter encdata revealed_mackeys
    - Make a new OTR Data Message, with the given pieces (note that the
      data part is already encrypted).  MAC it with the given mackey.
 
diff --git a/configure b/configure
index 56d0838..4b942e7 100755
--- a/configure
+++ b/configure
@@ -1800,7 +1800,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE=libotr
- VERSION=2.0.2
+ VERSION=3.0.0
 
 
 cat >>confdefs.h <<_ACEOF
@@ -1926,7 +1926,7 @@ INSTALL_STRIP_PROGRAM="\${SHELL} \$(install_sh) -c -s"
 
 
 
-LIBOTR_LIBTOOL_VERSION="1:2:0"
+LIBOTR_LIBTOOL_VERSION="2:0:0"
 
 
 
diff --git a/configure.ac b/configure.ac
index 16dc114..97300a0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -16,8 +16,8 @@ dnl   For a backwards-incompatible API change (e.g. changing data structures):
 dnl     Change the libotr package version from a.b.c to (a+1).0.0
 dnl     Change the libotr libtool version from x:y:z to (x+1):0:0
 
-AM_INIT_AUTOMAKE(libotr, 2.0.2)
-LIBOTR_LIBTOOL_VERSION="1:2:0"
+AM_INIT_AUTOMAKE(libotr, 3.0.0)
+LIBOTR_LIBTOOL_VERSION="2:0:0"
 
 AC_SUBST(LIBOTR_LIBTOOL_VERSION)
 
diff --git a/libotr.m4 b/libotr.m4
index 34e9c21..ec6d998 100644
--- a/libotr.m4
+++ b/libotr.m4
@@ -54,7 +54,6 @@ if test "$libotr_inc_prefix" != "" ; then
 	CFLAGS="$CFLAGS $LIBOTR_CFLAGS"
 fi
 AC_MSG_RESULT($LIBOTR_CFLAGS)
-CFLAGS="$libotr_save_CFLAGS"
 
 dnl add any special lib dirs
 AC_MSG_CHECKING(for libotr LIBS)
@@ -68,9 +67,8 @@ LIBOTR_LIBS="$LIBOTR_LIBS -lotr"
 LIBS="$LIBOTR_LIBS $LIBS"
 AC_MSG_RESULT($LIBOTR_LIBS)
 
-dnl Check for a working version of libasound that is of the right version.
-min_libotr_version=ifelse([$1], ,2.0.0,$1)
-AC_MSG_CHECKING(for libotr headers version >= $min_libotr_version)
+dnl Check for a working version of libotr that is of the right version.
+min_libotr_version=ifelse([$1], ,3.0.0,$1)
 no_libotr=""
     libotr_min_major_version=`echo $min_libotr_version | \
            sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
@@ -78,22 +76,20 @@ no_libotr=""
            sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
     libotr_min_sub_version=`echo $min_libotr_version | \
            sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
+AC_MSG_CHECKING(for libotr headers version $libotr_min_major_version.x >= $min_libotr_version)
 
 AC_LANG_SAVE
 AC_LANG_C
 AC_TRY_COMPILE([
-#include <libotr/proto.h>
+#include <stdlib.h>
 #include <libotr/version.h>
 ], [
-#  if(OTRL_VERSION_MAJOR > $libotr_min_major_version)
-  exit(0);
+#  if(OTRL_VERSION_MAJOR != $libotr_min_major_version)
+#    error not present
 #  else
-#    if(OTRL_VERSION_MAJOR < $libotr_min_major_version)
-#      error not present
-#    endif
 
 #    if(OTRL_VERSION_MINOR > $libotr_min_minor_version)
-  exit(0);
+       exit(0);
 #    else
 #      if(OTRL_VERSION_MINOR < $libotr_min_minor_version)
 #        error not present
diff --git a/packaging/fedora/libotr.spec b/packaging/fedora/libotr.spec
index 80a3dd1..2a617c1 100644
--- a/packaging/fedora/libotr.spec
+++ b/packaging/fedora/libotr.spec
@@ -1,107 +1,82 @@
-Summary: Off-The-Record Messaging libraray and toolkit
+Summary: Off-The-Record Messaging library and toolkit
 Name: libotr
-%define majver 2
-%define minver 0.2
-Version: %{majver}.%{minver}
-%define debug_package %{nil}
-%define ourrelease 1
-Release: %{ourrelease}
-Source: http://www.cypherpunks.ca/otr/libotr-%{majver}.%{minver}.tar.gz
-BuildRoot: %{_tmppath}/%{name}-buildroot
+Version: 3.0.0
+Release: 1%{?dist}
+License: GPL, LGPL
+Group: System Environment/Libraries
+Source0: http://www.cypherpunks.ca/otr/%{name}-%{version}.tar.gz
 Url: http://www.cypherpunks.ca/otr/
-Vendor: Nikita Borisov and Ian Goldberg <otr at cypherpunks.ca>
-Packager: Paul Wouters <paul at cypherpunks.ca>
-License: GPL
-Group: Applications/Internet
-%define __spec_install_post /usr/lib/rpm/brp-compress || :
-
-%package toolkit
-Summary: the otr toolkit
-Group: Applications/Internet
-Provides: libotr
-Obsoletes: gaim-otr <= 1.0.2
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+Provides: libotr-toolkit = %{version}
+Obsoletes: libotr-toolkit < %{version}
+Requires: libgcrypt >= 1.2.0, libgpg-error
 BuildRequires: libgcrypt-devel >= 1.2.0, libgpg-error-devel 
-Requires: libgcrypt >= 1.2.0
-Release: %{ourrelease}
-
-%package devel
-Summary: the otr library and include files
-Group: Applications/Internet
-Release: %{ourrelease}
-Requires: libotr = 2.0.2
-
-
-%description toolkit
-
-              Off-the-Record Messaging Library and Toolkit
-                          v2.0.2,  3 May 2005
 
+%description
+Off-the-Record Messaging Library and Toolkit
 This is a library and toolkit which implements Off-the-Record (OTR) Messaging.
+OTR allows you to have private conversations over IM by providing Encryption,
+Authentication, Deniability and Perfect forward secrecy.
 
-OTR allows you to have private conversations over IM by providing:
- - Encryption
-   - No one else can read your instant messages.
- - Authentication
-   - You are assured the correspondent is who you think it is.
- - Deniability
-   - The messages you send do _not_ have digital signatures that are
-     checkable by a third party.  Anyone can forge messages after a
-     conversation to make them look like they came from you.  However,
-     _during_ a conversation, your correspondent is assured the messages
-     he sees are authentic and unmodified.
- - Perfect forward secrecy
-   - If you lose control of your private keys, no previous conversation
-     is compromised.
-
-For more information on Off-the-Record Messaging, see
-http://www.cypherpunks.ca/otr/
-
+%package devel
+Summary: Development library and include files for libotr
+Group: Development/Libraries
+Requires: %{name} = %{version}-%{release}, libgcrypt-devel >= 1.2.0
 
 %description devel
-              Off-the-Record Messaging Library and Toolkit
-			  v2.0.2,  3 May 2005
 
 The devel package contains the libotr library and the include files
 
-%description
-A dummy to satisfy rpm 
-
 %prep
-%setup -q -n libotr-%{majver}.%{minver}
+%setup -q
 
 %build
-%configure --with-pic --prefix=%{_prefix} --libdir=%{_libdir} --mandir=%{_mandir}
-%{__make} \
-	CFLAGS="${RPM_OPT_FLAGS}" \
-	all
+
+%configure --with-pic
+make %{?_smp_mflags} all
 
 %install
-rm -rf ${RPM_BUILD_ROOT}
-%{__make} \
-	DESTDIR=${RPM_BUILD_ROOT} \
+rm -rf $RPM_BUILD_ROOT
+make \
+	DESTDIR=$RPM_BUILD_ROOT \
 	LIBINSTDIR=%{_libdir} \
 	install
+rm -rf $RPM_BUILD_ROOT%{_libdir}/*.la
 
 %clean
-rm -rf ${RPM_BUILD_ROOT}
+rm -rf $RPM_BUILD_ROOT
+
+%post -p /sbin/ldconfig
+
+%postun -p /sbin/ldconfig
 
 %files 
 %defattr(-,root,root)
-%doc README COPYING COPYING.LIB Protocol
+%doc AUTHORS README COPYING COPYING.LIB NEWS Protocol*
 %{_libdir}/libotr.so.*
 %{_bindir}/*
 %{_mandir}/man1/*
 
 %files devel
-%doc README COPYING.LIB 
+%defattr(-,root,root,-)
+%doc ChangeLog
 %{_libdir}/libotr.so
 %{_libdir}/libotr.a
-%{_libdir}/libotr.la
+%dir %{_includedir}/libotr
 %{_includedir}/libotr/*
 %{_datadir}/aclocal/*
 
 
 %changelog
+* Mon Oct 17 2005 Paul Wouters <paul at cypherpunks.ca> 3.0.0
+- Minor change to allow for new documentation files. Fixed Requires:
+
+* Sat Jun 19 2005 Paul Wouters <paul at cypherpunks.ca>
+- Fixed defattr, groups, description and duplicate files in devel
+
+* Fri Jun 17 2005 Tom "spot" Callaway <tcallawa at redhat.com>
+- reworked for Fedora Extras
+
 * Tue May  3 2005 Ian Goldberg <ian at cypherpunks.ca>
 - Bumped version number to 2.0.2
 * Wed Feb 16 2005 Ian Goldberg <ian at cypherpunks.ca>
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/Makefile.in b/src/Makefile.in
index 557e9a9..bbe74aa 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -53,16 +53,16 @@ libLTLIBRARIES_INSTALL = $(INSTALL)
 LTLIBRARIES = $(lib_LTLIBRARIES)
 libotr_la_LIBADD =
 am_libotr_la_OBJECTS = privkey.lo context.lo proto.lo b64.lo dh.lo \
-	mem.lo message.lo userstate.lo tlv.lo
+	mem.lo message.lo userstate.lo tlv.lo auth.lo
 libotr_la_OBJECTS = $(am_libotr_la_OBJECTS)
 DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir)
 depcomp = $(SHELL) $(top_srcdir)/depcomp
 am__depfiles_maybe = depfiles
- at AMDEP_TRUE@DEP_FILES = ./$(DEPDIR)/b64.Plo ./$(DEPDIR)/context.Plo \
- at AMDEP_TRUE@	./$(DEPDIR)/dh.Plo ./$(DEPDIR)/mem.Plo \
- at AMDEP_TRUE@	./$(DEPDIR)/message.Plo ./$(DEPDIR)/privkey.Plo \
- at AMDEP_TRUE@	./$(DEPDIR)/proto.Plo ./$(DEPDIR)/tlv.Plo \
- at AMDEP_TRUE@	./$(DEPDIR)/userstate.Plo
+ at AMDEP_TRUE@DEP_FILES = ./$(DEPDIR)/auth.Plo ./$(DEPDIR)/b64.Plo \
+ at AMDEP_TRUE@	./$(DEPDIR)/context.Plo ./$(DEPDIR)/dh.Plo \
+ at AMDEP_TRUE@	./$(DEPDIR)/mem.Plo ./$(DEPDIR)/message.Plo \
+ at AMDEP_TRUE@	./$(DEPDIR)/privkey.Plo ./$(DEPDIR)/proto.Plo \
+ at AMDEP_TRUE@	./$(DEPDIR)/tlv.Plo ./$(DEPDIR)/userstate.Plo
 COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
 	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
 LTCOMPILE = $(LIBTOOL) --mode=compile $(CC) $(DEFS) \
@@ -179,12 +179,12 @@ target_alias = @target_alias@
 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
 
 all: all-am
 
@@ -255,6 +255,7 @@ mostlyclean-compile:
 distclean-compile:
 	-rm -f *.tab.c
 
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/auth.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/b64.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/context.Plo at am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/dh.Plo at am__quote@
diff --git a/src/auth.c b/src/auth.c
new file mode 100644
index 0000000..28f2928
--- /dev/null
+++ b/src/auth.c
@@ -0,0 +1,1413 @@
+/*
+ *  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.  Generate
+ * a fresh DH keypair to use.  If no error is returned, the message to
+ * transmit will be contained in auth->lastauthmsg.
+ */
+gcry_error_t otrl_auth_start_v2(OtrlAuthInfo *auth)
+{
+    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;
+
+    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.  Generate a fresh
+ * keypair to use.
+ */
+gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth,
+	const char *commitmsg)
+{
+    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);
+	    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);
+		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;
+    }
+
+    if (auth->authstate != OTRL_AUTHSTATE_V1_SETUP) {
+	/* Clear the auth and start over */
+	otrl_auth_clear(auth);
+    }
+
+    /* 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..8fa936c
--- /dev/null
+++ b/src/auth.h
@@ -0,0 +1,157 @@
+/*
+ *  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.  Generate
+ * a fresh DH keypair to use.  If no error is returned, the message to
+ * transmit will be contained in auth->lastauthmsg.
+ */
+gcry_error_t otrl_auth_start_v2(OtrlAuthInfo *auth);
+
+/*
+ * Handle an incoming D-H Commit Message.  If no error is returned, the
+ * message to send will be left in auth->lastauthmsg.  Generate a fresh
+ * keypair to use.
+ */
+gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth,
+	const char *commitmsg);
+
+/*
+ * 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 54a93b3..3205d3b 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)
@@ -41,7 +37,12 @@ static ConnContext * new_context(const char * user, const char * accountname,
     context->username = strdup(user);
     context->accountname = strdup(accountname);
     context->protocol = strdup(protocol);
-    context->state = CONN_UNCONNECTED;
+    context->fragment = NULL;
+    context->fragment_len = 0;
+    context->fragment_n = 0;
+    context->fragment_k = 0;
+    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;
@@ -61,7 +62,12 @@ static ConnContext * new_context(const char * user, const char * accountname,
     otrl_dh_session_blank(&(context->sesskeys[0][1]));
     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->protocol_version = 0;
     context->numsavedkeys = 0;
+    context->preshared_secret = NULL;
+    context->preshared_secret_len = 0;
     context->saved_mac_keys = NULL;
     context->generation = 0;
     context->lastsent = 0;
@@ -140,6 +146,7 @@ Fingerprint *otrl_context_find_fingerprint(ConnContext *context,
 	assert(f->fingerprint != NULL);
 	memmove(f->fingerprint, fingerprint, 20);
 	f->context = context;
+	f->trust = NULL;
 	f->next = context->fingerprint_root.next;
 	if (f->next) {
 	    f->next->tous = &(f->next);
@@ -151,21 +158,63 @@ Fingerprint *otrl_context_find_fingerprint(ConnContext *context,
     return NULL;
 }
 
-/* Force a context into the CONN_SETUP state (so that it only has local
- * DH keys). */
-void otrl_context_force_setup(ConnContext *context)
+/* Set the trust level for a given fingerprint */
+void otrl_context_set_trust(Fingerprint *fprint, const char *trust)
+{
+    if (fprint == NULL) return;
+
+    free(fprint->trust);
+    fprint->trust = trust ? strdup(trust) : NULL;
+}
+
+/* Set the preshared secret for a given fingerprint.  Note that this
+ * currently only stores the secret in the ConnContext structure, but
+ * doesn't yet do anything with it. */
+void otrl_context_set_preshared_secret(ConnContext *context,
+	const unsigned char *secret, size_t secret_len)
 {
-    context->state = CONN_SETUP;
+    free(context->preshared_secret);
+    context->preshared_secret = NULL;
+    context->preshared_secret_len = 0;
+
+    if (secret_len) {
+	context->preshared_secret = malloc(secret_len);
+	if (context->preshared_secret) {
+	    memmove(context->preshared_secret, secret, secret_len);
+	    context->preshared_secret_len = secret_len;
+	}
+    }
+}
+
+/* Force a context into the OTRL_MSGSTATE_FINISHED state. */
+void otrl_context_force_finished(ConnContext *context)
+{
+    context->msgstate = OTRL_MSGSTATE_FINISHED;
+    otrl_auth_clear(&(context->auth));
+    free(context->fragment);
+    context->fragment = NULL;
+    context->fragment_len = 0;
+    context->fragment_n = 0;
+    context->fragment_k = 0;
     context->active_fingerprint = NULL;
     context->their_keyid = 0;
     gcry_mpi_release(context->their_y);
     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;
+    context->protocol_version = 0;
     context->numsavedkeys = 0;
     free(context->saved_mac_keys);
     context->saved_mac_keys = NULL;
@@ -174,42 +223,41 @@ 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);
 	    *(fprint->tous) = fprint->next;
 	    if (fprint->next) {
 		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
@@ -220,14 +268,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) {
@@ -256,12 +304,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 12278f3..c71f95d 100644
--- a/src/context.h
+++ b/src/context.h
@@ -20,29 +20,55 @@
 #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 {
-    unsigned char *fingerprint;        /* The fingerprint, or NULL */
-    struct context *context;           /* The context to which we belong */
     struct fingerprint *next;          /* The next fingerprint in the list */
     struct fingerprint **tous;         /* A pointer to the pointer to us */
+    unsigned char *fingerprint;        /* The fingerprint, or NULL */
+    struct context *context;           /* The context to which we belong */
+    char *trust;                       /* The trust level of the fingerprint */
 } Fingerprint;
 
 typedef struct context {
+    struct context * next;             /* Linked list pointer */
+    struct context ** tous;            /* A pointer to the pointer to us */
+
     char * username;                   /* The user this context is for */
     char * accountname;                /* The username is relative to
 					  this account... */
     char * protocol;                   /* ... and this protocol */
-    ConnectionState state;             /* The state of our connection to this
-					  user */
+
+    char *fragment;                    /* The part of the fragmented message
+					  we've seen so far */
+    size_t fragment_len;               /* The length of fragment */
+    unsigned short fragment_n;         /* The total number of fragments
+					  in this message */
+    unsigned short fragment_k;         /* The highest fragment number
+					  we've seen so far for this
+					  message */
+
+    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?
@@ -62,6 +88,17 @@ typedef struct context {
 					  derived from DH key[our_keyid-i]
 					  and mpi Y[their_keyid-j] */
 
+    unsigned char sessionid[20];       /* The sessionid and bold half */
+    size_t sessionid_len;              /* determined when this private */
+    OtrlSessionIdHalf sessionid_half;  /* connection was established. */
+
+    unsigned int protocol_version;     /* The version of OTR in use */
+
+    unsigned char *preshared_secret;   /* A secret you share with this
+					  user, in order to do
+					  authentication. */
+    size_t preshared_secret_len;       /* The length of the above secret. */
+
     /* saved mac keys to be revealed later */
     unsigned int numsavedkeys;
     unsigned char *saved_mac_keys;
@@ -87,16 +124,10 @@ typedef struct context {
     void *app_data;
     /* A function to free the above data when we forget this context */
     void (*app_data_free)(void *);
-
-    struct context * next;             /* Linked list pointer */
-    struct context ** tous;            /* A pointer to the pointer to us */
 } ConnContext;
 
 #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
@@ -112,26 +143,33 @@ ConnContext * otrl_context_find(OtrlUserState us, const char *user,
 Fingerprint *otrl_context_find_fingerprint(ConnContext *context,
 	unsigned char fingerprint[20], int add_if_missing, int *addedp);
 
-/* Force a context into the CONN_SETUP state (so that it only has local
- * DH keys). */
-void otrl_context_force_setup(ConnContext *context);
+/* Set the trust level for a given fingerprint */
+void otrl_context_set_trust(Fingerprint *fprint, const char *trust);
+
+/* Set the preshared secret for a given fingerprint.  Note that this
+ * currently only stores the secret in the ConnContext structure, but
+ * doesn't yet do anything with it. */
+void otrl_context_set_preshared_secret(ConnContext *context,
+	const unsigned char *secret, size_t secret_len);
+
+/* 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 522c29f..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);
 }
 
 /*
@@ -94,7 +117,8 @@ gcry_error_t otrl_dh_gen_keypair(unsigned int groupid, DH_keypair *kp)
  * Construct session keys from a DH keypair and someone else's public
  * key.
  */
-gcry_error_t otrl_dh_session(DH_sesskeys *sess, DH_keypair *kp, gcry_mpi_t y)
+gcry_error_t otrl_dh_session(DH_sesskeys *sess, const DH_keypair *kp,
+	gcry_mpi_t y)
 {
     gcry_mpi_t gab;
     size_t gablen;
@@ -128,22 +152,17 @@ gcry_error_t otrl_dh_session(DH_sesskeys *sess, DH_keypair *kp, gcry_mpi_t y)
     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->sessionid, 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;
     }
@@ -192,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)
  */
@@ -214,7 +436,6 @@ void otrl_dh_session_blank(DH_sesskeys *sess)
     sess->sendmac = NULL;
     sess->rcvenc = NULL;
     sess->rcvmac = NULL;
-    memset(sess->sessionid, 0, 20);
     memset(sess->sendctr, 0, 16);
     memset(sess->rcvctr, 0, 16);
     memset(sess->sendmackey, 0, 20);
@@ -234,7 +455,7 @@ void otrl_dh_incctr(unsigned char *ctr)
 
 /* Compare two counter values (8 bytes each).  Return 0 if ctr1 == ctr2,
  * < 0 if ctr1 < ctr2 (as unsigned 64-bit values), > 0 if ctr1 > ctr2. */
-int otrl_dh_cmpctr(unsigned char *ctr1, unsigned char *ctr2)
+int otrl_dh_cmpctr(const unsigned char *ctr1, const unsigned char *ctr2)
 {
     int i;
     for (i=0;i<8;++i) {
diff --git a/src/dh.h b/src/dh.h
index a64c84b..f41b023 100644
--- a/src/dh.h
+++ b/src/dh.h
@@ -27,14 +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 sessionid[20];
     unsigned char sendctr[16];
     unsigned char rcvctr[16];
     gcry_cipher_hd_t sendenc;
@@ -54,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)
  */
@@ -68,7 +77,26 @@ gcry_error_t otrl_dh_gen_keypair(unsigned int groupid, DH_keypair *kp);
  * Construct session keys from a DH keypair and someone else's public
  * key.
  */
-gcry_error_t otrl_dh_session(DH_sesskeys *sess, DH_keypair *kp, gcry_mpi_t y);
+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
@@ -86,6 +114,6 @@ void otrl_dh_incctr(unsigned char *ctr);
 
 /* Compare two counter values (8 bytes each).  Return 0 if ctr1 == ctr2,
  * < 0 if ctr1 < ctr2 (as unsigned 64-bit values), > 0 if ctr1 > ctr2. */
-int otrl_dh_cmpctr(unsigned char *ctr1, unsigned char *ctr2);
+int otrl_dh_cmpctr(const unsigned char *ctr1, const unsigned char *ctr2);
 
 #endif
diff --git a/src/mem.c b/src/mem.c
index 574258e..d525577 100644
--- a/src/mem.c
+++ b/src/mem.c
@@ -63,7 +63,7 @@ static void *otrl_mem_malloc(size_t n)
     ((size_t *)p)[1] = OTRL_MEM_MAGIC;
 #endif
 
-    return (p + header_size);
+    return (void *)((char *)p + header_size);
 }
 
 static int otrl_mem_is_secure(const void *p)
@@ -73,7 +73,7 @@ static int otrl_mem_is_secure(const void *p)
 
 static void otrl_mem_free(void *p)
 {
-    void *real_p = p - header_size;
+    void *real_p = (void *)((char *)p - header_size);
     size_t n = ((size_t *)real_p)[0];
 #ifdef OTRL_MEM_MAGIC
     if (((size_t *)real_p)[1] != OTRL_MEM_MAGIC) {
@@ -100,7 +100,7 @@ static void *otrl_mem_realloc(void *p, size_t n)
 	otrl_mem_free(p);
 	return NULL;
     } else {
-	void *real_p = p - header_size;
+	void *real_p = (void *)((char *)p - header_size);
 	void *new_p;
 	size_t old_n = ((size_t *)real_p)[0];
 #ifdef OTRL_MEM_MAGIC
@@ -121,7 +121,7 @@ static void *otrl_mem_realloc(void *p, size_t n)
 
 	if (new_n < old_n) {
 	    /* Overwrite the space we're about to stop using */
-	    void *p = real_p + new_n;
+	    void *p = (void *)((char *)real_p + new_n);
 	    size_t excess = old_n - new_n;
 	    memset(p, 0xff, excess);
 	    memset(p, 0xaa, excess);
@@ -136,7 +136,7 @@ static void *otrl_mem_realloc(void *p, size_t n)
 	}
 
 	((size_t *)new_p)[0] = new_n;  /* Includes header size */
-	return (new_p + header_size);
+	return (void *)((char *)new_p + header_size);
     }
 }
 
diff --git a/src/message.c b/src/message.c
index 6179318..7ac99f8 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? */
@@ -37,11 +38,6 @@
  * resending in response to a rekey? */
 #define RESEND_INTERVAL 60
 
-struct s_OTRConfirmResponse {
-    OTRKeyExchangeMsg kem;
-    ConnContext *context;
-};
-
 /* Deallocate a message allocated by other otrl_message_* routines. */
 void otrl_message_free(char *message)
 {
@@ -68,8 +64,9 @@ void otrl_message_free(char *message)
  * should replace your message with the contents of *messagep, and
  * send that instead.  Call otrl_message_free(*messagep) when you're
  * done with it. */
-gcry_error_t otrl_message_sending(OtrlUserState us, OtrlMessageAppOps
-	*ops, void *opdata, const char *accountname, const char *protocol,
+gcry_error_t otrl_message_sending(OtrlUserState us,
+	const OtrlMessageAppOps *ops,
+	void *opdata, const char *accountname, const char *protocol,
 	const char *recipient, const char *message, OtrlTLV *tlvs,
 	char **messagep,
 	void (*add_appdata)(void *data, ConnContext *context),
@@ -101,255 +98,400 @@ gcry_error_t otrl_message_sending(OtrlUserState us, OtrlMessageAppOps
     }
 
     /* 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,
+		    0);
+	    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(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, 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;
+    OtrlMessageState oldstate = edata->context->msgstate;
+    Fingerprint *oldprint = edata->context->active_fingerprint;
+
+    /* 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 &&
+	    oldprint == found_print &&
+	    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->ignore_message = 1;
+	return gcry_error(GPG_ERR_NO_ERROR);
+    }
+
+    /* 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->protocol_version =
+	edata->context->auth.protocol_version;
+
+    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;
     }
 
-    /* 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);
+    /* 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 (oldstate == OTRL_MSGSTATE_ENCRYPTED && oldprint == found_print) {
+	if (edata->ops->still_secure) {
+	    edata->ops->still_secure(edata->opdata, edata->context,
+		    edata->context->auth.initiated);
+	}
+    } else {
+	if (edata->ops->gone_secure) {
+	    edata->ops->gone_secure(edata->opdata, edata->context);
 	}
     }
 
-    return retval;
+    edata->gone_encrypted = 1;
+
+    return gpg_error(GPG_ERR_NO_ERROR);
 }
 
-static void process_confresp(OtrlUserState us, OtrlMessageAppOps *ops,
-	void *opdata, OTRConfirmResponse *confresp, int resp)
+static void maybe_resend(EncrData *edata)
 {
-    if (resp == 1) {
-	process_kem(us, ops, opdata, confresp->context, NULL, confresp->kem);
+    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, 0);
+	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);
+		    }
+		}
+	    }
+	}
     }
-    otrl_proto_free_key_exchange(confresp->kem);
-    free(confresp);
 }
 
 /* Handle a message just received from the network.  It is safe to pass
@@ -376,19 +518,21 @@ static void process_confresp(OtrlUserState us, OtrlMessageAppOps *ops,
  * If otrl_message_receiving returns 0 and *messagep is NULL, then this
  * was an ordinary, non-OTR message, which should just be delivered to
  * the user without modification. */
-int otrl_message_receiving(OtrlUserState us, OtrlMessageAppOps *ops,
+int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	void *opdata, const char *accountname, const char *protocol,
 	const char *sender, const char *message, char **newmessagep,
 	OtrlTLV **tlvsp,
 	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;
@@ -411,212 +555,232 @@ int otrl_message_receiving(OtrlUserState us, OtrlMessageAppOps *ops,
     }
 
     /* Should we go on at all? */
-    if (policy == OTRL_POLICY_NEVER) {
+    if ((policy & OTRL_POLICY_VERSION_MASK) == 0) {
         return 0;
     }
 
+    /* See if we have a fragment */
+    switch(otrl_proto_fragment_accumulate(&unfragmessage, context, message)) {
+	case OTRL_FRAGMENT_UNFRAGMENTED:
+	    /* Do nothing */
+	    break;
+	case OTRL_FRAGMENT_INCOMPLETE:
+	    /* We've accumulated this fragment, but we don't have a
+	     * complete message yet */
+	    return 1;
+	case OTRL_FRAGMENT_COMPLETE:
+	    /* We've got a new complete message, in unfragmessage. */
+	    fragment_assembled = 1;
+	    message = unfragmessage;
+	    break;
+    }
+
     /* 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));
+		    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)) {
+		err = otrl_auth_handle_commit(&(context->auth), message);
+		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 (!found_print) {
-			/* Warn the user about the new fingerprint */
-			OTRConfirmResponse *confresp =
-			    malloc(sizeof(OTRConfirmResponse));
-			if (confresp) {
-			    confresp->kem = kem;
-			    confresp->context = context;
-			    if (ops->confirm_fingerprint) {
-				ops->confirm_fingerprint(us, opdata,
-					accountname, protocol, sender, kem,
-					process_confresp, confresp);
-			    } else {
-				process_confresp(us, ops, opdata, confresp,
-					-1);
-			    }
-			} else {
-			    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;
+	    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_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;
 
-				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);
-					}
-				    }
-				}
-			    }
-			}
+	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;
+		}
+
+		/* 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:
+		unsigned char flags;
+		case OTRL_MSGSTATE_PLAINTEXT:
+		case OTRL_MSGSTATE_FINISHED:
+		    /* See if we're supposed to ignore this message in
+		     * the event it's unreadable. */
+		    err = otrl_proto_data_read_flags(message, &flags);
+		    if ((flags & OTRL_MSGFLAGS_IGNORE_UNREADABLE)) {
+			edata.ignore_message = 1;
+			break;
+		    }
+
 		    /* Don't use g_strdup_printf here, because someone
 		     * (not us) is going to free() the *newmessagep pointer,
 		     * not g_free() it. */
@@ -630,12 +794,12 @@ int otrl_message_receiving(OtrlUserState us, 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);
 			}
@@ -653,33 +817,28 @@ int otrl_message_receiving(OtrlUserState us, 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);
+			    message, &flags);
 		    if (err) {
-			format = "We received a malformed "
-				"data message from %s.";
+			int is_conflict =
+			    (gpg_err_code(err) == GPG_ERR_CONFLICT);
+			if ((flags & OTRL_MSGFLAGS_IGNORE_UNREADABLE)) {
+			    edata.ignore_message = 1;
+			    break;
+			}
+			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);
@@ -688,10 +847,13 @@ int otrl_message_receiving(OtrlUserState us, 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;
 		    }
 
@@ -699,7 +861,7 @@ int otrl_message_receiving(OtrlUserState us, 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') {
@@ -714,8 +876,8 @@ int otrl_message_receiving(OtrlUserState us, 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
@@ -726,7 +888,8 @@ int otrl_message_receiving(OtrlUserState us, OtrlMessageAppOps *ops,
 
 			    /* Create the heartbeat message */
 			    err = otrl_proto_create_data(&heartbeat,
-				    context, "", NULL);
+				    context, "", NULL,
+				    OTRL_MSGFLAGS_IGNORE_UNREADABLE);
 			    if (!err) {
 				/* Send it, and log a debug message */
 				if (ops->inject_message) {
@@ -760,43 +923,34 @@ int otrl_message_receiving(OtrlUserState us, 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) {
@@ -809,99 +963,97 @@ int otrl_message_receiving(OtrlUserState us, 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));
+			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) {
@@ -914,18 +1066,24 @@ int otrl_message_receiving(OtrlUserState us, OtrlMessageAppOps *ops,
 		    free(buf);
 		}
 	    }
-	    if (ignore_message == -1) ignore_message = 1;
+	    if (edata.ignore_message == -1) edata.ignore_message = 1;
 	    break;
     }
 
-    if (ignore_message == -1) ignore_message = 0;
-    return ignore_message;
+    /* If we reassembled a fragmented message, we need to free the
+     * allocated memory now. */
+    if (fragment_assembled) {
+	free(unfragmessage);
+    }
+
+    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, OtrlMessageAppOps *ops,
+void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
 	void *opdata, const char *accountname, const char *protocol,
 	const char *username)
 {
@@ -934,7 +1092,8 @@ void otrl_message_disconnect(OtrlUserState us, 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) {
@@ -942,7 +1101,8 @@ void otrl_message_disconnect(OtrlUserState us, OtrlMessageAppOps *ops,
 	    gcry_error_t err;
 	    OtrlTLV *tlv = otrl_tlv_new(OTRL_TLV_DISCONNECTED, 0, NULL);
 
-	    err = otrl_proto_create_data(&encmsg, context, "", tlv);
+	    err = otrl_proto_create_data(&encmsg, context, "", tlv,
+		    OTRL_MSGFLAGS_IGNORE_UNREADABLE);
 	    if (!err) {
 		ops->inject_message(opdata, accountname, protocol,
 			username, encmsg);
@@ -951,7 +1111,7 @@ void otrl_message_disconnect(OtrlUserState us, 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 ad20361..56c8d04 100644
--- a/src/message.h
+++ b/src/message.h
@@ -26,17 +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_OTRConfirmResponse OTRConfirmResponse;
-
 typedef struct s_OtrlMessageAppOps {
     /* Return the OTR policy for the given context. */
     OtrlPolicy (*policy)(void *opdata, ConnContext *context);
@@ -71,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);
 
@@ -86,20 +76,10 @@ typedef struct s_OtrlMessageAppOps {
     /* Deallocate a string allocated by protocol_name */
     void (*protocol_name_free)(void *opdata, const char *protocol_name);
 
-    /* Ask the user of the given accountname to confirm an unknown
-     * fingerprint (contained in kem) for the given username of the
-     * given protocol.  When the user has decided, call
-     * response_cb(us, ops, opdata, response_data, resp) where resp is 1
-     * to accept the fingerprint, 0 to reject it, and -1 if the user
-     * didn't make a choice (say, by destroying the dialog window).
-     * BE SURE to call response_cb no matter what happens. */
-    void (*confirm_fingerprint)(OtrlUserState us, void *opdata,
+    /* 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,
-	    void (*response_cb)(OtrlUserState us,
-		struct s_OtrlMessageAppOps *ops, void *opdata,
-		OTRConfirmResponse *response_data, int resp),
-	    OTRConfirmResponse *response_data);
+	    const char *username, unsigned char fingerprint[20]);
 
     /* The list of known fingerprints has changed.  Write them to disk. */
     void (*write_fingerprints)(void *opdata);
@@ -110,9 +90,8 @@ typedef struct s_OtrlMessageAppOps {
     /* 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. */
+    /* We have completed an authentication, using the D-H keys we
+     * already knew.  is_reply indicates whether we initiated the AKE. */
     void (*still_secure)(void *opdata, ConnContext *context, int is_reply);
 
     /* Log a message.  The passed message will end in "\n". */
@@ -143,8 +122,9 @@ void otrl_message_free(char *message);
  * should replace your message with the contents of *messagep, and
  * send that instead.  Call otrl_message_free(*messagep) when you're
  * done with it. */
-gcry_error_t otrl_message_sending(OtrlUserState us, OtrlMessageAppOps
-	*ops, void *opdata, const char *accountname, const char *protocol,
+gcry_error_t otrl_message_sending(OtrlUserState us,
+	const OtrlMessageAppOps *ops,
+	void *opdata, const char *accountname, const char *protocol,
 	const char *recipient, const char *message, OtrlTLV *tlvs,
 	char **messagep,
 	void (*add_appdata)(void *data, ConnContext *context),
@@ -174,17 +154,17 @@ gcry_error_t otrl_message_sending(OtrlUserState us, OtrlMessageAppOps
  * If otrl_message_receiving returns 0 and *messagep is NULL, then this
  * was an ordinary, non-OTR message, which should just be delivered to
  * the user without modification. */
-int otrl_message_receiving(OtrlUserState us, OtrlMessageAppOps *ops,
+int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
 	void *opdata, const char *accountname, const char *protocol,
 	const char *sender, const char *message, char **newmessagep,
 	OtrlTLV **tlvsp,
 	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, OtrlMessageAppOps *ops,
+void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
 	void *opdata, const char *accountname, const char *protocol,
 	const char *username);
 
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 1096bbb..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 "2.0.2"
+#include <gcrypt.h>
 
-#define OTRL_VERSION_MAJOR 2
-#define OTRL_VERSION_MINOR 0
-#define OTRL_VERSION_SUB 2
+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 828c90c..2d4a79d 100644
--- a/src/privkey.c
+++ b/src/privkey.c
@@ -27,11 +27,11 @@
 #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], unsigned char hash[20])
+void otrl_privkey_hash_to_human(char human[45], const unsigned char hash[20])
 {
     int word, byte;
     char *p = human;
@@ -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);
@@ -359,16 +449,49 @@ gcry_error_t otrl_privkey_read_fingerprints(OtrlUserState us,
 	return err;
     }
     while(fgets(storeline, maxsize, storef)) {
-	char username[sizeof(storeline)];
-	char accountname[sizeof(storeline)];
-	char protocol[sizeof(storeline)];
-	char hex[sizeof(storeline)];
-	int res, i, j;
+	char *username;
+	char *accountname;
+	char *protocol;
+	char *hex;
+	char *trust;
+	char *tab;
+	char *eol;
+	Fingerprint *fng;
+	int i, j;
 	/* Parse the line, which should be of the form:
 	 *    username\taccountname\tprotocol\t40_hex_nybbles\n          */
-	res = sscanf(storeline, "%s %s %s %s", username, accountname,
-		protocol, hex);
-	if (res != 4) continue;
+	username = storeline;
+	tab = strchr(username, '\t');
+	if (!tab) continue;
+	*tab = '\0';
+
+	accountname = tab + 1;
+	tab = strchr(accountname, '\t');
+	if (!tab) continue;
+	*tab = '\0';
+
+	protocol = tab + 1;
+	tab = strchr(protocol, '\t');
+	if (!tab) continue;
+	*tab = '\0';
+
+	hex = tab + 1;
+	tab = strchr(hex, '\t');
+	if (!tab) {
+	    eol = strchr(hex, '\r');
+	    if (!eol) eol = strchr(hex, '\n');
+	    if (!eol) continue;
+	    *eol = '\0';
+	    trust = NULL;
+	} else {
+	    *tab = '\0';
+	    trust = tab + 1;
+	    eol = strchr(trust, '\r');
+	    if (!eol) eol = strchr(trust, '\n');
+	    if (!eol) continue;
+	    *eol = '\0';
+	}
+
 	if (strlen(hex) != 40) continue;
 	for(j=0, i=0; i<40; i+=2) {
 	    fingerprint[j++] = (ctoh(hex[i]) << 4) + (ctoh(hex[i+1]));
@@ -377,7 +500,8 @@ gcry_error_t otrl_privkey_read_fingerprints(OtrlUserState us,
 	context = otrl_context_find(us, username, accountname, protocol,
 		1, NULL, add_app_data, data);
 	/* Add the fingerprint if not already there */
-	otrl_context_find_fingerprint(context, fingerprint, 1, NULL);
+	fng = otrl_context_find_fingerprint(context, fingerprint, 1, NULL);
+	otrl_context_set_trust(fng, trust);
     }
     fclose(storef);
 
@@ -405,9 +529,10 @@ gcry_error_t otrl_privkey_write_fingerprints(OtrlUserState us,
 	    int i;
 	    fprintf(storef, "%s\t%s\t%s\t", context->username,
 		    context->accountname, context->protocol);
-	    for(i=0;i<20;++i)
+	    for(i=0;i<20;++i) {
 		fprintf(storef, "%02x", fprint->fingerprint[i]);
-	    fprintf(storef, "\n");
+	    }
+	    fprintf(storef, "\t%s\n", fprint->trust ? fprint->trust : "");
 	}
     }
     fclose(storef);
@@ -417,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) {
@@ -433,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);
@@ -457,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 1fe0608..7b63e46 100644
--- a/src/privkey.h
+++ b/src/privkey.h
@@ -20,23 +20,11 @@
 #ifndef __PRIVKEY_H__
 #define __PRIVKEY_H__
 
-#include <gcrypt.h>
-
-typedef struct s_PrivKey {
-    char *accountname;
-    char *protocol;
-    gcry_sexp_t privkey;
-    unsigned char *pubkey_data;
-    size_t pubkey_datalen;
-    struct s_PrivKey *next;
-    struct s_PrivKey **tous;
-} PrivKey;
-
-#include "context.h"
+#include "privkey-t.h"
 #include "userstate.h"
 
 /* Convert a 20-byte hash value to a 45-byte human-readable value */
-void otrl_privkey_hash_to_human(char human[45], unsigned char hash[20]);
+void otrl_privkey_hash_to_human(char human[45], const unsigned char hash[20]);
 
 /* Calculate a human-readable hash of our DSA public key.  Return it in
  * the passed fingerprint buffer.  Return NULL on error, or a pointer to
@@ -68,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 349e101..fdd2a99 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)
@@ -274,11 +136,15 @@ static gcry_error_t rotate_dh_keys(ConnContext *context)
 	err = otrl_dh_session(&(context->sesskeys[0][0]),
 		&(context->our_dh_key), context->their_y);
 	if (err) return err;
+    } else {
+	otrl_dh_session_blank(&(context->sesskeys[0][0]));
     }
     if (context->their_old_y) {
 	err = otrl_dh_session(&(context->sesskeys[0][1]),
 		&(context->our_dh_key), context->their_old_y);
 	if (err) return err;
+    } else {
+	otrl_dh_session_blank(&(context->sesskeys[0][1]));
     }
     return gcry_error(GPG_ERR_NO_ERROR);
 }
@@ -322,404 +188,165 @@ 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);
-    }
+    starttag = strstr(msg, OTRL_MESSAGE_TAG_BASE);
+    if (!starttag) return 0;
 
-    /* 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 */
+    endtag = starttag + strlen(OTRL_MESSAGE_TAG_BASE);
 
-    bufp = rawmsg;
-    lenp = rawlen;
-
-    signaturestart = bufp;
-
-    require_len(3);
-    if (memcmp(bufp, "\x00\x01\x0a", 3)) {
-	/* Invalid header */
-	goto invval;
-    }
-    bufp += 3; lenp -= 3;
-
-    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);
+    *starttagp = starttag;
+    *endtagp = endtag;
 
-    /* That should be everything */
-    if (lenp != 0) goto invval;
-
-    /* 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;
-	    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:AAID", 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
  * optional chain of TLVs.  A newly-allocated string will be returned in
  * *encmessagep. */
 gcry_error_t otrl_proto_create_data(char **encmessagep, ConnContext *context,
-	const char *msg, OtrlTLV *tlvs)
+	const char *msg, const OtrlTLV *tlvs, unsigned char flags)
 {
     size_t justmsglen = strlen(msg);
     size_t msglen = justmsglen + 1 + otrl_tlv_seriallen(tlvs);
@@ -736,9 +363,11 @@ gcry_error_t otrl_proto_create_data(char **encmessagep, ConnContext *context,
     char *msgbuf = NULL;
     enum gcry_mpi_format format = GCRYMPI_FMT_USG;
     char *msgdup;
+    int version = context->protocol_version;
 
     /* 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);
     }
 
@@ -754,7 +383,8 @@ gcry_error_t otrl_proto_create_data(char **encmessagep, ConnContext *context,
 
     /* Header, send keyid, recv keyid, counter, msg len, msg
      * len of revealed mac keys, revealed mac keys, MAC */
-    buflen = 3 + 4 + 4 + 8 + 4 + msglen + 4 + reveallen + 20;
+    buflen = 3 + (version == 2 ? 1 : 0) + 4 + 4 + 8 + 4 + msglen +
+	4 + reveallen + 20;
     gcry_mpi_print(format, NULL, 0, &pubkeylen, context->our_dh_key.pub);
     buflen += pubkeylen + 4;
     buf = malloc(buflen);
@@ -770,9 +400,17 @@ gcry_error_t otrl_proto_create_data(char **encmessagep, ConnContext *context,
     otrl_tlv_serialize(msgbuf + justmsglen + 1, tlvs);
     bufp = buf;
     lenp = buflen;
-    memmove(bufp, "\x00\x01\x03", 3);  /* header */
+    if (version == 1) {
+	memmove(bufp, "\x00\x01\x03", 3);  /* header */
+    } else {
+	memmove(bufp, "\x00\x02\x03", 3);  /* header */
+    }
     debug_data("Header", bufp, 3);
     bufp += 3; lenp -= 3;
+    if (version == 2) {
+	bufp[0] = flags;
+	bufp += 1; lenp -= 1;
+    }
     write_int(context->our_keyid-1);                    /* sender keyid */
     debug_int("Sender keyid", bufp-4);
     write_int(context->their_keyid);                    /* recipient keyid */
@@ -861,10 +499,66 @@ err:
     return err;
 }
 
+/* Extract the flags from an otherwise unreadable Data Message. */
+gcry_error_t otrl_proto_data_read_flags(const char *datamsg,
+	unsigned char *flagsp)
+{
+    char *otrtag, *endtag;
+    unsigned char *rawmsg = NULL;
+    unsigned char *bufp;
+    size_t msglen, rawlen, lenp;
+    unsigned char version;
+
+    if (flagsp) *flagsp = 0;
+    otrtag = strstr(datamsg, "?OTR:");
+    if (!otrtag) {
+	goto invval;
+    }
+    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) {
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    rawlen = otrl_base64_decode(rawmsg, otrtag+5, msglen-5);  /* actual size */
+
+    bufp = rawmsg;
+    lenp = rawlen;
+
+    require_len(3);
+    if (memcmp(bufp, "\x00\x01\x03", 3) && memcmp(bufp, "\x00\x02\x03", 3)) {
+	/* Invalid header */
+	goto invval;
+    }
+    version = bufp[1];
+    bufp += 3; lenp -= 3;
+
+    if (version == 2) {
+	require_len(1);
+	if (flagsp) *flagsp = bufp[0];
+	bufp += 1; lenp -= 1;
+    }
+
+    free(rawmsg);
+    return gcry_error(GPG_ERR_NO_ERROR);
+
+invval:
+    free(rawmsg);
+    return gcry_error(GPG_ERR_INV_VALUE);
+}
+
 /* Accept an OTR Data Message in datamsg.  Decrypt it and put the
- * plaintext into *plaintextp, and any TLVs into tlvsp. */
+ * plaintext into *plaintextp, and any TLVs into tlvsp.  Put any
+ * received flags into *flagsp (if non-NULL). */
 gcry_error_t otrl_proto_accept_data(char **plaintextp, OtrlTLV **tlvsp,
-	ConnContext *context, const char *datamsg)
+	ConnContext *context, const char *datamsg, unsigned char *flagsp)
 {
     char *otrtag, *endtag;
     gcry_error_t err;
@@ -880,9 +574,11 @@ gcry_error_t otrl_proto_accept_data(char **plaintextp, OtrlTLV **tlvsp,
     unsigned char *nul = NULL;
     unsigned char givenmac[20];
     DH_sesskeys *sess;
+    unsigned char version;
 
     *plaintextp = NULL;
     *tlvsp = NULL;
+    if (flagsp) *flagsp = 0;
     otrtag = strstr(datamsg, "?OTR:");
     if (!otrtag) {
 	goto invval;
@@ -908,12 +604,18 @@ gcry_error_t otrl_proto_accept_data(char **plaintextp, OtrlTLV **tlvsp,
 
     macstart = bufp;
     require_len(3);
-    if (memcmp(bufp, "\x00\x01\x03", 3)) {
+    if (memcmp(bufp, "\x00\x01\x03", 3) && memcmp(bufp, "\x00\x02\x03", 3)) {
 	/* Invalid header */
 	goto invval;
     }
+    version = bufp[1];
     bufp += 3; lenp -= 3;
 
+    if (version == 2) {
+	require_len(1);
+	if (flagsp) *flagsp = bufp[0];
+	bufp += 1; lenp -= 1;
+    }
     read_int(sender_keyid);
     read_int(recipient_keyid);
     read_mpi(sender_next_y);
@@ -1027,3 +729,85 @@ err:
     free(rawmsg);
     return err;
 }
+
+/* Accumulate a potential fragment into the current context. */
+OtrlFragmentResult otrl_proto_fragment_accumulate(char **unfragmessagep,
+	ConnContext *context, const char *msg)
+{
+    OtrlFragmentResult res = OTRL_FRAGMENT_INCOMPLETE;
+    const char *tag;
+
+    tag = strstr(msg, "?OTR,");
+    if (tag) {
+	unsigned short n = 0, k = 0;
+	int start = 0, end = 0;
+
+	sscanf(tag, "?OTR,%hu,%hu,%n%*[^,],%n", &k, &n, &start, &end);
+	if (k > 0 && n > 0 && k <= n && start > 0 && end > 0 && start < end) {
+	    if (k == 1) {
+		int fraglen = end - start - 1;
+		free(context->fragment);
+		context->fragment = malloc(fraglen + 1);
+		if (fraglen + 1 > fraglen && context->fragment) {
+		    memmove(context->fragment, tag + start, fraglen);
+		    context->fragment_len = fraglen;
+		    context->fragment[context->fragment_len] = '\0';
+		    context->fragment_n = n;
+		    context->fragment_k = k;
+		} else {
+		    free(context->fragment);
+		    context->fragment = NULL;
+		    context->fragment_len = 0;
+		    context->fragment_n = 0;
+		    context->fragment_k = 0;
+		}
+	    } else if (n == context->fragment_n &&
+		    k == context->fragment_k + 1) {
+		int fraglen = end - start - 1;
+		char *newfrag = realloc(context->fragment,
+			context->fragment_len + fraglen + 1);
+		if (context->fragment_len + fraglen + 1 > fraglen && newfrag) {
+		    context->fragment = newfrag;
+		    memmove(context->fragment + context->fragment_len,
+			    tag + start, fraglen);
+		    context->fragment_len += fraglen;
+		    context->fragment[context->fragment_len] = '\0';
+		    context->fragment_k = k;
+		} else {
+		    free(context->fragment);
+		    context->fragment = NULL;
+		    context->fragment_len = 0;
+		    context->fragment_n = 0;
+		    context->fragment_k = 0;
+		}
+	    } else {
+		free(context->fragment);
+		context->fragment = NULL;
+		context->fragment_len = 0;
+		context->fragment_n = 0;
+		context->fragment_k = 0;
+	    }
+	}
+
+	if (context->fragment_n > 0 &&
+		context->fragment_n == context->fragment_k) {
+	    /* We've got a complete message */
+	    *unfragmessagep = context->fragment;
+	    context->fragment = NULL;
+	    context->fragment_len = 0;
+	    context->fragment_n = 0;
+	    context->fragment_k = 0;
+	    res = OTRL_FRAGMENT_COMPLETE;
+	}
+    } else {
+	/* Unfragmented message, so discard any fragment we may have */
+	free(context->fragment);
+	context->fragment = NULL;
+	context->fragment_len = 0;
+	context->fragment_n = 0;
+	context->fragment_k = 0;
+	res = OTRL_FRAGMENT_UNFRAGMENTED;
+    }
+
+    return res;
+}
diff --git a/src/proto.h b/src/proto.h
index 546b6a4..5fd2d3a 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -26,33 +26,63 @@
 
 /* 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 "
-    /* This is the bit sequence of the string "OTR", encoded in tabs and
-     * spaces. */
+#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 "
+
+/* The possible flags contained in a Data Message */
+#define OTRL_MSGFLAGS_IGNORE_UNREADABLE		0x01
+
+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;
-
-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;
+    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,
+    OTRL_FRAGMENT_INCOMPLETE,
+    OTRL_FRAGMENT_COMPLETE
+} OtrlFragmentResult;
 
 /* Initialize the OTR library.  Pass the version of the API you are
  * using. */
@@ -68,66 +98,43 @@ 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
  * *encmessagep. */
 gcry_error_t otrl_proto_create_data(char **encmessagep, ConnContext *context,
-	const char *msg, OtrlTLV *tlvs);
+	const char *msg, const OtrlTLV *tlvs, unsigned char flags);
+
+/* Extract the flags from an otherwise unreadable Data Message. */
+gcry_error_t otrl_proto_data_read_flags(const char *datamsg,
+	unsigned char *flagsp);
 
 /* Accept an OTR Data Message in datamsg.  Decrypt it and put the
- * plaintext into *plaintextp, and any TLVs into tlvsp. */
+ * plaintext into *plaintextp, and any TLVs into tlvsp.  Put any
+ * received flags into *flagsp (if non-NULL). */
 gcry_error_t otrl_proto_accept_data(char **plaintextp, OtrlTLV **tlvsp,
-	ConnContext *context, const char *datamsg);
+	ConnContext *context, const char *datamsg, unsigned char *flagsp);
+
+/* Accumulate a potential fragment into the current context. */
+OtrlFragmentResult otrl_proto_fragment_accumulate(char **unfragmessagep,
+	ConnContext *context, const char *msg);
 
 #endif
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/tlv.c b/src/tlv.c
index 3da549f..d840ad1 100644
--- a/src/tlv.c
+++ b/src/tlv.c
@@ -25,7 +25,7 @@
 
 /* Make a single TLV, copying the supplied data */
 OtrlTLV *otrl_tlv_new(unsigned short type, unsigned short len,
-	unsigned char *data)
+	const unsigned char *data)
 {
     OtrlTLV *tlv = malloc(sizeof(OtrlTLV));
     assert(tlv != NULL);
@@ -40,7 +40,7 @@ OtrlTLV *otrl_tlv_new(unsigned short type, unsigned short len,
 }
 
 /* Construct a chain of TLVs from the given data */
-OtrlTLV *otrl_tlv_parse(unsigned char *serialized, size_t seriallen)
+OtrlTLV *otrl_tlv_parse(const unsigned char *serialized, size_t seriallen)
 {
     OtrlTLV *tlv = NULL;
     OtrlTLV **tlvp = &tlv;
@@ -69,7 +69,7 @@ void otrl_tlv_free(OtrlTLV *tlv)
 }
 
 /* Find the serialized length of a chain of TLVs */
-size_t otrl_tlv_seriallen(OtrlTLV *tlv)
+size_t otrl_tlv_seriallen(const OtrlTLV *tlv)
 {
     size_t totlen = 0;
     while (tlv) {
@@ -81,7 +81,7 @@ size_t otrl_tlv_seriallen(OtrlTLV *tlv)
 
 /* Serialize a chain of TLVs.  The supplied buffer must already be large
  * enough. */
-void otrl_tlv_serialize(unsigned char *buf, OtrlTLV *tlv)
+void otrl_tlv_serialize(unsigned char *buf, const OtrlTLV *tlv)
 {
     while (tlv) {
 	buf[0] = (tlv->type >> 8) & 0xff;
@@ -96,7 +96,8 @@ void otrl_tlv_serialize(unsigned char *buf, OtrlTLV *tlv)
 }
 
 /* Return the first TLV with the given type in the chain, or NULL if one
- * isn't found.*/
+ * isn't found.  (The tlvs argument isn't const because the return type
+ * needs to be non-const.) */
 OtrlTLV *otrl_tlv_find(OtrlTLV *tlvs, unsigned short type)
 {
     while (tlvs) {
diff --git a/src/tlv.h b/src/tlv.h
index dbbdc51..ee30370 100644
--- a/src/tlv.h
+++ b/src/tlv.h
@@ -38,23 +38,24 @@ typedef struct s_OtrlTLV {
 
 /* Make a single TLV, copying the supplied data */
 OtrlTLV *otrl_tlv_new(unsigned short type, unsigned short len,
-	unsigned char *data);
+	const unsigned char *data);
 
 /* Construct a chain of TLVs from the given data */
-OtrlTLV *otrl_tlv_parse(unsigned char *serialized, size_t seriallen);
+OtrlTLV *otrl_tlv_parse(const unsigned char *serialized, size_t seriallen);
 
 /* Deallocate a chain of TLVs */
 void otrl_tlv_free(OtrlTLV *tlv);
 
 /* Find the serialized length of a chain of TLVs */
-size_t otrl_tlv_seriallen(OtrlTLV *tlv);
+size_t otrl_tlv_seriallen(const OtrlTLV *tlv);
 
 /* Serialize a chain of TLVs.  The supplied buffer must already be large
  * enough. */
-void otrl_tlv_serialize(unsigned char *buf, OtrlTLV *tlv);
+void otrl_tlv_serialize(unsigned char *buf, const OtrlTLV *tlv);
 
 /* Return the first TLV with the given type in the chain, or NULL if one
- * isn't found.*/
+ * isn't found.  (The tlvs argument isn't const because the return type
+ * needs to be non-const.) */
 OtrlTLV *otrl_tlv_find(OtrlTLV *tlvs, unsigned short type);
 
 #endif
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 1096bbb..77187c1 100644
--- a/src/version.h
+++ b/src/version.h
@@ -20,10 +20,10 @@
 #ifndef __VERSION_H__
 #define __VERSION_H__
 
-#define OTRL_VERSION "2.0.2"
+#define OTRL_VERSION "3.0.0"
 
-#define OTRL_VERSION_MAJOR 2
+#define OTRL_VERSION_MAJOR 3
 #define OTRL_VERSION_MINOR 0
-#define OTRL_VERSION_SUB 2
+#define OTRL_VERSION_SUB 0
 
 #endif
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 75ff9a8..afdac72 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,13 +113,16 @@ 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");
 		break;
 	    }
 	    printf("Data Message:\n");
+	    if (datamsg->flags >= 0) {
+		dump_int(stdout, "\tFlags", datamsg->flags);
+	    }
 	    dump_int(stdout, "\tSender keyid", datamsg->sender_keyid);
 	    dump_int(stdout, "\tRcpt keyid", datamsg->rcpt_keyid);
 	    dump_mpi(stdout, "\tDH y", datamsg->y);
@@ -77,7 +136,7 @@ static void parse(const char *msg)
 		unsigned int i = 0;
 		printf("\tRevealed MAC keys:\n");
 
-		while(len > 0) {
+		while(len > 19) {
 		    char title[20];
 		    sprintf(title, "\t\tKey %u", ++i);
 		    dump_data(stdout, title, mks, 20);
@@ -88,16 +147,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_remac.c b/toolkit/otr_remac.c
index e644c4d..0ffe90b 100644
--- a/toolkit/otr_remac.c
+++ b/toolkit/otr_remac.c
@@ -30,8 +30,8 @@
 
 static void usage(const char *progname)
 {
-    fprintf(stderr, "Usage: %s mackey snd_keyid rcp_keyid pubkey counter "
-	    "encdata revealed_mackeys\n"
+    fprintf(stderr, "Usage: %s mackey flags snd_keyid rcp_keyid pubkey "
+	    "counter encdata revealed_mackeys\n"
 "Make a new Data message, with the given pieces (note that the\n"
 "data part is already encrypted).  MAC it with the given mackey.\n"
 "mackey, pubkey, counter, encdata, and revealed_mackeys are given\n"
@@ -44,6 +44,7 @@ int main(int argc, char **argv)
     unsigned char *mackey;
     size_t mackeylen;
     unsigned int snd_keyid, rcp_keyid;
+    int flags;
     unsigned char *pubkey;
     size_t pubkeylen;
     gcry_mpi_t pubv;
@@ -55,7 +56,7 @@ int main(int argc, char **argv)
     size_t mackeyslen;
     char *newdatamsg;
 
-    if (argc != 8) {
+    if (argc != 9) {
 	usage(argv[0]);
     }
 
@@ -69,24 +70,29 @@ int main(int argc, char **argv)
 	usage(argv[0]);
     }
 
-    if (sscanf(argv[2], "%u", &snd_keyid) != 1) {
+    if (sscanf(argv[2], "%d", &flags) != 1) {
+	fprintf(stderr, "Unparseable flags given.\n");
+	usage(argv[0]);
+    }
+
+    if (sscanf(argv[3], "%u", &snd_keyid) != 1) {
 	fprintf(stderr, "Unparseable snd_keyid given.\n");
 	usage(argv[0]);
     }
 
-    if (sscanf(argv[3], "%u", &rcp_keyid) != 1) {
+    if (sscanf(argv[4], "%u", &rcp_keyid) != 1) {
 	fprintf(stderr, "Unparseable rcp_keyid given.\n");
 	usage(argv[0]);
     }
 
-    argv_to_buf(&pubkey, &pubkeylen, argv[4]);
+    argv_to_buf(&pubkey, &pubkeylen, argv[5]);
     if (!pubkey) {
 	usage(argv[0]);
     }
     gcry_mpi_scan(&pubv, GCRYMPI_FMT_USG, pubkey, pubkeylen, NULL);
     free(pubkey);
     
-    argv_to_buf(&ctr, &ctrlen, argv[5]);
+    argv_to_buf(&ctr, &ctrlen, argv[6]);
     if (!ctr) {
 	usage(argv[0]);
     }
@@ -96,18 +102,18 @@ int main(int argc, char **argv)
 	usage(argv[0]);
     }
 
-    argv_to_buf(&encdata, &encdatalen, argv[6]);
+    argv_to_buf(&encdata, &encdatalen, argv[7]);
     if (!encdata) {
 	usage(argv[0]);
     }
 
-    argv_to_buf(&mackeys, &mackeyslen, argv[7]);
+    argv_to_buf(&mackeys, &mackeyslen, argv[8]);
     if (!mackeys) {
 	usage(argv[0]);
     }
 
-    newdatamsg = assemble_datamsg(mackey, snd_keyid, rcp_keyid, pubv, ctr,
-	    encdata, encdatalen, mackeys, mackeyslen);
+    newdatamsg = assemble_datamsg(mackey, flags, snd_keyid, rcp_keyid,
+	    pubv, ctr, encdata, encdatalen, mackeys, mackeyslen);
     printf("%s\n", newdatamsg);
     free(newdatamsg);
 
diff --git a/toolkit/otr_toolkit.1 b/toolkit/otr_toolkit.1
index d684e58..45a8e72 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 27, 2005"
 .\" Please adjust this date whenever revising the manpage.
 .\"
 .\" Some roff macros, for reference:
@@ -33,7 +33,7 @@ otr_parse, otr_sesskeys, otr_mackey, otr_readforge, otr_modify, otr_remac \- Pro
 .I mackey old_text new_text offset
 .br
 .B otr_remac
-.I mackey snd_keyid rcv_keyid pubkey counter encdata revealed_mackeys
+.I mackey flags snd_keyid rcv_keyid pubkey counter encdata revealed_mackeys
 .SH DESCRIPTION
 Off-the-Record (OTR) Messaging allows you to have private conversations
 over IM by providing:
@@ -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
@@ -95,7 +95,7 @@ Here are the six programs in the toolkit:
      you can still forge messages of your choice using the
      otr_readforge command, above.
 
- - otr_remac mackey snd_keyid rcv_keyid pubkey counter encdata revealed_mackeys
+ - otr_remac mackey flags snd_keyid rcv_keyid pubkey counter encdata revealed_mackeys
    - Make a new OTR Data Message, with the given pieces (note that the
      data part is already encrypted).  MAC it with the given mackey.
 
diff --git a/toolkit/parse.c b/toolkit/parse.c
index 0f58fbb..e07c86a 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)
 {
@@ -189,6 +375,7 @@ DataMsg parse_datamsg(const char *msg)
     size_t lenp;
     unsigned char *raw = decode(msg, &lenp);
     unsigned char *bufp = raw;
+    unsigned char version;
     if (!raw) goto inv;
 
     datam = calloc(1, sizeof(struct s_DataMsg));
@@ -202,9 +389,18 @@ DataMsg parse_datamsg(const char *msg)
     datam->macstart = bufp;
 
     require_len(3);
-    if (memcmp(bufp, "\x00\x01\x03", 3)) goto inv;
+    if (memcmp(bufp, "\x00\x01\x03", 3) && memcmp(bufp, "\x00\x02\x03", 3))
+	goto inv;
+    version = bufp[1];
     bufp += 3; lenp -= 3;
 
+    if (version == 2) {
+	require_len(1);
+	datam->flags = bufp[0];
+	bufp += 1; lenp -= 1;
+    } else {
+	datam->flags = -1;
+    }
     read_int(datam->sender_keyid);
     read_int(datam->rcpt_keyid);
     read_mpi(datam->y);
@@ -239,11 +435,12 @@ char *remac_datamsg(DataMsg datamsg, unsigned char mackey[20])
     size_t base64len;
     char *outmsg;
     unsigned char *raw, *bufp;
+    unsigned char version = (datamsg->flags >= 0 ? 2 : 1);
     
     /* Calculate the size of the message that will result */
     gcry_mpi_print(GCRYMPI_FMT_USG, NULL, 0, &ylen, datamsg->y);
-    rawlen = 3 + 4 + 4 + 4 + ylen + 8 + 4 + datamsg->encmsglen + 20 +
-	4 + datamsg->mackeyslen;
+    rawlen = 3 + (version == 2 ? 1 : 0) + 4 + 4 + 4 + ylen + 8 + 4 +
+	datamsg->encmsglen + 20 + 4 + datamsg->mackeyslen;
 
     /* Construct the new raw message (note that some of the pieces may
      * have been altered, so we construct it from scratch). */
@@ -260,8 +457,16 @@ char *remac_datamsg(DataMsg datamsg, unsigned char mackey[20])
     datamsg->raw = raw;
     datamsg->rawlen = rawlen;
 
-    memmove(bufp, "\x00\x01\x03", 3);
+    if (version == 1) {
+	memmove(bufp, "\x00\x01\x03", 3);
+    } else {
+	memmove(bufp, "\x00\x02\x03", 3);
+    }
     bufp += 3; lenp -= 3;
+    if (version == 2) {
+	bufp[0] = datamsg->flags;
+	bufp += 1; lenp -= 1;
+    }
     write_int(datamsg->sender_keyid);
     write_int(datamsg->rcpt_keyid);
     write_mpi(datamsg->y, ylen);
@@ -295,14 +500,15 @@ char *remac_datamsg(DataMsg datamsg, unsigned char mackey[20])
 
 /* Assemble a new Data Message from its pieces.  Return a
  * newly-allocated string containing the base64 representation. */
-char *assemble_datamsg(unsigned char mackey[20], unsigned int sender_keyid,
-	unsigned int rcpt_keyid, gcry_mpi_t y, unsigned char ctr[8],
-	unsigned char *encmsg, size_t encmsglen, unsigned char *mackeys,
-	size_t mackeyslen)
+char *assemble_datamsg(unsigned char mackey[20], int flags,
+	unsigned int sender_keyid, unsigned int rcpt_keyid, gcry_mpi_t y,
+	unsigned char ctr[8], unsigned char *encmsg, size_t encmsglen,
+	unsigned char *mackeys, size_t mackeyslen)
 {
     DataMsg datam = calloc(1, sizeof(struct s_DataMsg));
     char *newmsg = NULL;
     if (!datam) goto inv;
+    datam->flags = flags;
     datam->sender_keyid = sender_keyid;
     datam->rcpt_keyid = rcpt_keyid;
     datam->y = gcry_mpi_copy(y);
diff --git a/toolkit/parse.h b/toolkit/parse.h
index f5362d4..1db65bc 100644
--- a/toolkit/parse.h
+++ b/toolkit/parse.h
@@ -36,6 +36,7 @@ typedef struct s_KeyExchMsg {
 typedef struct s_DataMsg {
     unsigned char *raw;         /* The base64-decoded data; must be free()d */
     size_t rawlen;
+    int flags;
     unsigned int sender_keyid;
     unsigned int rcpt_keyid;
     gcry_mpi_t y;
@@ -49,6 +50,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 +95,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
@@ -76,10 +131,10 @@ char *remac_datamsg(DataMsg datamsg, unsigned char mackey[20]);
 
 /* Assemble a new Data Message from its pieces.  Return a
  * newly-allocated string containing the base64 representation. */
-char *assemble_datamsg(unsigned char mackey[20], unsigned int sender_keyid,
-	unsigned int rcpt_keyid, gcry_mpi_t y, unsigned char ctr[8],
-	unsigned char *encmsg, size_t encmsglen, unsigned char *mackeys,
-	size_t mackeyslen);
+char *assemble_datamsg(unsigned char mackey[20], int flags,
+	unsigned int sender_keyid, unsigned int rcpt_keyid, gcry_mpi_t y,
+	unsigned char ctr[8], unsigned char *encmsg, size_t encmsglen,
+	unsigned char *mackeys, size_t mackeyslen);
 
 /* Deallocate a DataMsg and all of the data it points to */
 void free_datamsg(DataMsg datamsg);

-- 
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