# JoinMarket messaging protocol

The purpose of this document is to unambiguously define how joinmarket participants communicate. The messaging substrate allows for communication over multiple MessageChannel objects, managed by a single MessageChannelCollection object; see this module (opens new window).

All messages are split into fields. Fields are currently separated with a single whitespace character (more than one whitespace char is not allowed.

Messages have format:

!command [[msg field 1] [msg field 2] ...]

Messages are sent in two modes: public (broadcast to all other agents) or private (directed to a specific agent and not seen by others).

The first field always starts with the command prefix (opens new window), currently '!' and is completed with one of the following commands:

Command word mode
reloffer public or private
absoffer public or private
orderbook public
cancel public
hp2 public
fill private
pubkey private
auth private
ioauth private
tx private
sig private
error private
push private

Private messages not starting with a command from this list are to be explicitly rejected. Public messages not starting with a command from this list are to be ignored. The initial command field is followed with zero or more message fields.

# Multi-part messages

An unencrypted message may optionally contain more than one command. The message is first split into sub-messages on the command prefix, then each "sub-message" is treated as a distinct message, applying the rules as listed above.

Note that this is not allowed for encrypted messages, which are only allowed to send a single command field at the start of the message. It is currently used for the reloffer and absoffer commands.

# IRC-specific feature: chunks

Messages are split into chunks for passing over an IRC messaging channel; this code is found therefore in the irc module (opens new window). Chunks are terminated with the chunk delimiter ';', unless it is the final chunk, which case the chunk delimiter is '~'.

Messages are split into chunks before sending over the messaging channel, and the decision for chunk size is set globally as a function of the messaging implementation (currently: 450 characters (opens new window)).

Future non-IRC message channel implementations thus may or may not use this feature, depending on their characteristics, but all IRC implementations must use it.

# Encryption

Public messages (broadcast to all other participants) are never encrypted.

Private messages of command-type fill, pubkey, error, orderbook, push, reloffer and absoffer are sent in plaintext. Messages of command-type ioauth, auth, tx and sig are sent encrypted. These rules are enforced here (opens new window) and here (opens new window).

All encrypted messages are base64 encoded (opens new window) before being passed onto the message channel.

For encrypted messages, the entire set of message fields are sent as a single encrypted block (including the whitespace delimiters between the message fields). The command field and the chunk indicator field (for IRC) are sent in plaintext. (TODO: pad messages to improve privacy (opens new window); eg, to combat MITM correlation of ioauth and sig messages with the inputs belonging to a liquidity provider).

For clarification, the sequence for sending of encrypted messages is therefore plaintext-->encryption-->base64encoding-->chunking--> prepend !command to first chunk and add chunk delimiters -->send to message channel (private message for encryption). And the reverse for receiving.

# For multiple message channels: message signatures for anti-spoofing

# Pubkey-based nicks

The username/nick/nym for the Joinmarket bot is ephemeral-per-session, but is constructed in such a way that it can be signed for, so that it is not possibly to successfully spoof that user by registering the same nick on another active message channel. The nick is constructed as follows:

A 32 byte private key is generated at random.

A Bitcoin secp256k1 public key is created (opens new window) using it.

nick = one "type" byte (currently "J") + one version byte (current JM_VERSION protocol value, here (opens new window)) + Base58 (not Base58Check) of: first joinmarket.message_channel.NICK_HASH_LEN bytes of sha256 of : ephemeral per-bot-process public key as just described.

If length(nick) < joinmarket.message_channel.NICK_MAX_ENCODED, right pad with 'O' char to that length.

Thus according to current rules the nick is 16 characters in length, consisting of one type byte, one version byte and 14 bytes of pubkey-hash (right padded if necessary to fix length).

# Applying signatures to private messages.

All private messages (whether encrypted or not) are extended with the additional fields <pubkey> <signature> where pubkey is the Bitcoin pubkey mentioned above, hex encoded, and the signature is a Bitcoin signature, also hex encoded (TODO: this could be substantially improved in encoding size with pubkey recovery, and more compact encoding). See here (opens new window).

As defence against replaying the same signature on different message channels, note specifically that the plaintext which is signed is message+hostid, see here (opens new window), where hostid is the hostid of this specific MessageChannel object, for IRC this is the "network" field passed from the IRC server.

Note that since this extension to the message occurs in the abstract message class MessageChannel, any chunking as described above occurs after it. The pubkey and sig are just 2 additional fields for any private message sent.

All received private messages are verified against the nick of the sender here (opens new window) to prevent the threat of spoofing nicks.

# Valid conversation sequences:

(Announcements, not interactive/conversation):

TAKER MAKER
<<< ![rel|abs]offer (public)
<<< !cancel(public)
<<< !hp2(public)
TAKER MAKER
!orderbook(public) >>>
<<< ![rel|abs]order (private)
TAKER MAKER
!fill(private) >>>
<<< !pubkey(private)
!auth(private) >>>
<<< !ioauth(private)
!tx(private) >>>
<<< !sig(private)

Each taker may speak to many makers simultaneously and each maker may speak to many takers simultaneously in private.

# Private conversation in detail:

See "Definitions" section below for the key for the abbreviations.

1: M: !ordertype [order details]!ordertype [orderdetails]... (NS)
2: T: !fill order-id amount tencpubkey C (NS)
3: M: !pubkey mencpubkey (NS)
4: T*: !auth O (NS)
5: M*: !ioauth ulist maker_auth_pub coinjoinA changeA B(mencpubkey) (NS)
6: T*: !tx txhex (NS)
7: M*: !sig txsig (NS)

# Notes:

After step 2, if provided commitment is blacklisted according to maker's policy, maker quits. Current implementation stores utxo commitments (note that there are taker_utxo_retries possible commitments per utxo, and that the commitments do not reveal the utxo, thus effectively the same utxo can be tried that many times) in a file named blacklist.

After step 4, if PoDLE verification fails, the maker quits before sending !ioauth, thereby disallowing receipt of owned utxos.

For PODLE calculation see here (opens new window)

In 5, !ioauth ulist maker_auth_pub coinjoinA changeA B(mencpubkey) (NS), returned by maker, it's to be noted here that maker_auth_pub is needed to use an input , rather than an output, pubkey, as the anti-MITM key for the reason that a maker might not always own the coinjoin output key (see e.g. patientsendpayment mixing of maker/taker role).

Nick signature: currently re-calculates expected nick from given pubkey then validates signature against the entire message before NS. In future could cut out ~ 66 bytes by making use of secp256k1 pubkey recovery. The purpose of this feature is to prevent impersonation of a running bot by using its nick on a different message channel (see issue 568).

Taker side auth of utxo has been REMOVED, see overview of argument here (opens new window) and see also earlier background (opens new window).

# Definitions

T: taker

M: maker

* : indicates message not including NS is encrypted

[t,m]encpubkey : taker and maker encryption pubkeys, per transaction, for ECDH setup

C: single data field for commitment to a utxo by taker, by default for PoDLE (C=H(P2)). First byte is commitment type, default "P" for PoDLE.

B(p): bitcoin signature of an encryption pubkey p (signed message format standardised as for Bitcoin Core etc.)

Commitment data for default PoDLE construct:

O: commitment opening serialization, consists of the following fields separated by a field separator (default '|'):

U: utxo used for PoDLE (not necessarily from wallet)

P: pubkey used for PoDLE (not necessarily from wallet)

P2: shifted base point pubkey for PoDLE

s, e: schnorr sig values for PoDLE

maker_auth_pub: pubkey of keypair used to authorize and setup encryption by maker (must correspond to one of maker's input addresses)

coinjoinA: coinjoin address used by maker

changeA: change address used by maker

ulist: list of utxos that maker proposes to be used in transaction

(NS): nick signature (either of form pub, sig or from pubkey recovery, bitcoin type) : message to be signed is the whole message to be sent + message channel identifier str(serverport) (the latter to prevent cross-channel replay).

This document is not yet complete. Edit proposals welcomed.