Ripple Distributed Protocol v0.5

A protocol for establishing mutual credit accounts between users on different servers, and transmitting value through the resulting credit network in real-time.

Contents

0. Definitions

1. Introduction

2. Message Transport

3. Message Format

4. Core Messages

5. Account Messages

6. Broadcast Messages

7. Transaction Messages

8. Error Messages

0. Definitions

Terms

  • Credit network - the abstract network of financial trust connections over which payments are routed in Ripple.
    • also trust network, payment network
  • Data network - the network of data connections that serves to pass messages between different servers
  • Node - an address in the credit network that can send and/or receive payment. Nodes are exchange hubs that transmit value between their incoming and outgoing lines of credit.
    • also address
  • Server - an software program speaking the Ripple protocol running on a host machine, or the host itself running the software. A server hosts one or more nodes.
  • Line of credit - a credit relationship between two nodes in which one node offers to accept the other's IOUs up to a certain limit, enabling value to flow in one direction between the two nodes.
  • Mutual credit account - a credit relationship between two users, each of which may control multiple nodes, consisting of either one or two lines of credit between their nodes, depending on the routing needs.

Cryptographic Algorithms

1. Introduction

Ripple is a protocol for transmitting value through a credit network formed by mutual credit accounts between "nodes". The protocol consists of several parts:

  • a message transport mechanism for passing messages directly between servers
  • a message tunnelling mechanism for sending messages indirectly between servers
  • a broadcast mechanism for distributing messages across the entire network
  • a basic public key infrastructure (PKI)
  • a mechanism for establishing and managing mutual credit accounts between nodes
  • a path-discovery mechanism for discovering paths through the credit network that can send payment from a selected payer node to a selected recipient node
  • a transaction mechanism for committing payments over paths through the credit network

Ripple servers can pass messages directly to each other over TCP connections, secured by TLS. Messages can also be encrypted to a node on an unknown server and relayed there via one or more intermediaries using the Relay message. Broadcast messages are passed across the data network between servers: the Inventory message notifies a neighbouring server of pending broadcast messages, which it may then request using an InventoryRequest.

Ripple makes heavy use of public key cryptography, and public keys are used to identify nodes, transactions, and other entities in the network. To distribute and manage these keys, Ripple has a basic public key infrastructure consisting of KeyCertificate and KeyRevocation messages.

Ripple servers can declare the existence of nodes to other servers, and then establish lines of credit with nodes on other servers. They can then pass and request IOUs over established lines of credit.

Servers can then broadcast details of their nodes' lines of credit across the network so they can be used in multi-hop transactions in which a series of connected of nodes, called a path or chain of intermediaries, is asked to pass forward IOUs on specific lines of credit simultaneously in order to transfer value between two nodes that may not have a line of credit between them.

The Ripple credit network consists of nodes and lines of credit between them, forming a directed graph of potential value flow. A node is represents an exchange hub that can transmit value from any of its incoming lines of credit to any of its outgoing lines of credit. In general, a Ripple user will have multiple nodes. Arbitrary exchanges between a user's connections can be accomplished by assigning a separate node for each connection and a line of credit for each exchange that the user wishes to perform.

Multi-hop transactions use a two-phase commit protocol. The originating node, called the payer, determines a suitable path through the credit network connecting it and the destination node, called the recipient. The payer informs the recipient of the payment. If the recipient approves, the payer begins a two-phase commit by sending a commit-ready message called a Promise to the first node(s) on the path, who in turn send their own promises to the next node(s), until all promises reach the recipient, who then, in concert with the payer, creates a cryptographic Commit message that can be used to trigger IOU passing by redeeming promises backwards down the path from recipient to payer.

At every step, the protocol ensures that messages committing entities to particular actions are signed by their identifying public keys, so they may be later held to those commitments.

2. Message Transport

Ripple messages are passed over TCP connections, secured by TLS. Connected Ripple servers are considered peers in that they can each pass the same set of messages to each other, as opposed to client and server, where one sends requests and the other responds. For efficiency, connections should preferably be long-lasting, rather than set up for each message and then closed immediately.

Messages and their responses are passed asynchronously, meaning that multiple requests may be sent by one server on the same connection before any potential responses to those requests are received back, and the responses may come in any order, so that the responding server may take advantage of its parallel processing capabilities. Servers may open multiple TCP connections with each other if head-of-line blocking becomes a bottleneck for messages on a single connection (or for any other reason).

TODO: Define default port for Ripple servers.

In the future, SCTP has desirable features as a Ripple transport layer, once it becomes more commonplace and easy to use in various networking frameworks and hosting arrangements.

Framing

Since TCP is a byte-stream protocol, Ripple messages must be framed to preserve message boundaries. Ripple's framing semantics are inspired by the BEEP and SPDY protocols. Each frame is defined by:

 +-----+------+---+-------------------------+
 | Ver | Type | M |    Length (24 bits)     |
 +-----+------+---+-------------------------+
 |          Message Number (32 bits)        |
 +------------------------------------------+
 |                  Data                    |
 |                  ...                     |
 +------------------------------------------+
  • Ver (4 bits) - frame encoding version
    • 0000 = frame version 0, this specification
  • Type (3 bits) - message type
    • 000 = MSG (message)
    • 001 = ANS (answer)
    • 010 = OK (ok)
  • M (1 bit) - continuation flag
    • 0 = last frame for this message
    • 1 = more data for this message coming in further frames
  • Length - number of bytes in Data
    • max permitted value for this protocol version is 1048576
  • Message Number - identifier to associate messages with responses
  • Data - contains the actual Ripple message

Each message can be broken up into multiple frames, so high-priority messages do not need to wait in line while long low-priority messages are being transferred, but rather can be sent in between frames of long messages. Therefore it is recommended to break messages into relatively short frames that can be transferred nearly instantaneously.

Each MSG message must be replied to with zero or more a ANS messages with the same Message Number containing any content in direct response to the MSG, followed by an empty OK frame, also with the same Message Number, indicating an end to that message exchange. For example, any error messages resulting from a MSG request would go in an ANS response. Each ANS message must be completely transmitted before another ANS or OK in the same message exchange is begun. Multiple message exchanges can be open simultaneously on the same connection. BEEP and SPDY prescribe even-numbered messages for the connection initiator and odd-numbered messages for the listener, but here either server may use any number, as long as it has not already been used for a message that has not been replied to.

Length and Message Number are in network byte order. The overall size of each frame is Length + 8 bytes.

3. Message Format

Ripple messages are defined using Google's Protocol Buffers mechanism. Fields of type bytes are given in network byte order.

Each Ripple message consists of a Header followed by another message of the type indicated in the header, and optional signatures and proof of work.

message RippleMessage {
    required Header header = 1;
    optional bytes body = 2;
    repeated Signature signatures = 3;
    optional bytes proof_of_work = 4;
}

TODO: Add hop-limit or valid-until for routing broadcasts?

Signatures are to validate the header and body sections; any other signatures and proof_of_work are not signed.

proof_of_work is a string of bytes that causes the SHA256 hash of the entire message to be below a certain acceptability threshold (ie, with at least a certain number of zeroes at the start). This number is meant to be reasonably difficult to calculate in order to prevent spamming of certain types of messages. Any server can require that some types of messages come with a proof of work attached. If a server receives a message with insufficient proof of work, it should indicate in an error the proof of work threshold required for that message.

Header

enum MessageType {
    TIME = 0;
    RELAY = 1;
    INVENTORY = 2;
    INVENTORY_REQUEST = 3;
    NODE = 10;
    CONNECT = 11;
    IOU = 12;
    KEY_CERTIFICATE = 20;
    KEY_REVOCATION = 21;
    EXCHANGE_RATE = 22;
    ATOMICITY_FEE_SET = 23;
    CREDIT = 24;
    CREDIT_CHECK = 30;
    PAYMENT_INIT = 31;
    PAYMENT_ACCEPT = 32;
    PROMISE = 33;
    PROMISE_RELEASE = 34;
    COMMIT = 35;
    PROMISE_RENEW = 36;
    STATUS_QUERY = 40;
    STATUS = 41;
    ERROR = 100;
}

message Header {
    required MessageType type = 1;
    required string version = 2;
    required double time = 3;
    optional bytes message_id = 4;
    optional bytes to_key_id = 5;
    optional string to_alias = 6;
    optional bytes from_key_id = 7;
    optional string from_alias = 8;
}

version is the Ripple protocol version this message is to be interpreted under. This document's protocol version is 0.5. message_id is to identify different broadcast messages from a particular source, so a server needn't receive the same message repeatedly.

to_key_id and from_key_id are key-associated IDs for network entities. (See Identifiers below.) to_alias and from_alias are human-readable node aliases that also specify server domains and ports, eg, user@host.com:1234. Since one user may have multiple nodes, when directing a message to a user rather than to a specific node, such as when creating a line of credit or initiating a payment, use an alias. You should get a return message back with from set to the appropriate node you should be addressing, as well as from_alias set to the alias of the user you sent the initial message to. If this message is signed, it can then be used to provably link the alias to the node.

Time Format

Times are given as 64-bit floating point values (ie, Protocol Buffer double fields) which represent the number of seconds since the Unix epoch at midnight, Jan. 1, 1970, UTC.

Public Key Format

message PublicKey {
    required bytes modulus = 1;
}

Ripple uses RSA keys of any length modulus with a fixed exponent of 65537.

The canonical hash identifier of a key is the result of SHA-256 applied to the modulus.

Identifiers

There are two types of identifiers:

1. Entities associated with a public key are identified by the canonical hash of the key. Fields containing such identifiers usually end in key_id.

2. Entities not associated with a public key are identified by a 16-byte UUID generated by any method (Protocol Buffers bytes), or an integer (Protocol Buffers uint32 or uint64). Fields containing such identifiers usually end in id.

Signatures

message Signature {
    optional bytes signer_key_id = 1;
    required bytes signature = 2;
}

signature is a RSASSA-PSS signature with SHA-256 as the hash function.

signer_key_id is the hash-identifier of the signing key, and may be omitted where it is already given by the context.

Encrypted Messages

message EncryptedMessage {
    required bytes recipient_key_id = 1;
    required bytes encrypted_key = 2;
    required bytes ciphertext = 3;
}

recipient_key_id is a node address (hash of node's signing key).

To encrypt a RippleMessage to the recipient, the sender encrypts a symmetric AES-256 encryption key, using RSAES-OAEP with MGF1 as the mask generation function and SHA-256 as the hash function, and then uses that key to encrypt the message to the ciphertext field using AES-256 encryption in CTR (counter) mode with an IV (initial value) of all zero bytes.

Decimal Numbers

Decimal numbers (ie, monetary amounts), are encoded as Protocol Buffer ASCII/UTF-8 strings to avoid rounding error introduced by binary encoding. A period '.' is used as the decimal separator, and negative numbers are prefixed with '-'.

When rounding is necessary, the round half to even method must be used.

4. Core Messages

Time

Allows connected servers to discover what time the other thinks it is (before transmission delay), so they can compensate appropriately when performing time-sensitive actions involving the other server.

A server can send a Time message at any time during the connection. The other server should respond immediately with its own Time message.

Time has no message body. The actual time is contained in the header.

Relay

message Relay {
    required EncryptedMessage message = 1;
    required hop_limit = 2;
}

Message encrypted to destination node to pass on to message.recipient. Encrypted message may be another Relay. Broadcast messages should not be tunneled using Relay.

Inventory

message InventoryItem {
    required bytes source = 1;
    required bytes message_id = 2;
    required MessageType type = 3;
}

message Inventory {
    repeated InventoryItem items = 1;
}

Broadcast to let neighbouring servers know about new broadcast messages. Another server would be considered a neighbour if one of its nodes is given a credit limit by one of a server's local nodes.

When two servers first become neighbours, the server sending the initial Connect message should also send the first Inventory message. Then the other server need only send an Inventory containing message_ids the initiating server doesn't already have.

Inventory Request

message InventoryRequest {
    repeated InventoryItem items = 1;    
}

Request to receive the listed broadcast messages. The requested messages should then be sent individually as separate messages.

5. Account Messages

Node

TODO: Maybe allow extra keys for signing some types of messages (like Credit?) to enable servers only partial control over a node (can't issue any extra credit, for example)? Maybe a different key for broadcast messages than for neighbour messages?

message Node {
    optional string host = 1;
}

Let another server know about a node that wishes to establish a line of credit with one of its nodes. The node's identifying key is given by from_key_id in the header, and this message must be signed by that key.

host identifies the domain and port of the node's server, eg, host.com:1234, if the node wants other nodes to be able to communicate with it directly. If host is given, this message should be broadcast after the first line of credit is established. This message cannot be relayed unless host is given, since the receiver would have no way of knowing where to address the reply.

Connect

message Connect {
    required bytes line_of_credit_id = 1;
    optional bytes linked_line_of_credit_id = 2;
    required uint32 precision = 3;
    required uint32 scale = 4;
    optional string units = 5;
    optional string credit_offered = 6;
    optional string iou_offered = 7;
    optional string note = 8;
    optional bytes proof_of_id = 9;
    optional bytes old_node_id = 10;
}

Indicates a desire for the sending node to connect and register a line of credit with the receiving node. Must be signed by sender. The receiving node would eventually send back its own Connect message to indicate its reciprocal willingness to maintain the line of credit.

Lines of credit only transfer value in one direction, with the node initiating the connection accepting value from the node receiving the connection invitation. Two lines of credit can be linked together to form a single bidirectional mutual credit account with a single balance, by setting linked_line_of_credit_id when the second line of credit is created. The linked line of credit does not have to be between the same two nodes, but it must have the same units. This would happen if one or both partners wished to process incoming transactions on a different node than outgoing transactions, in order have a different set of exchanges for incoming and outgoing accounts, which can't be done with a single node. See Credit below.

Nodes must agree on how numeric amount values for the line of credit will be stored, so each can maintain identical records of the account. precision specifies the maximum total number of digits in any amount, and scale specifies the maximum digits after the decimal point. Connect messages from both partners must contain the same precision and scale values. Amounts refering to this account must be be rounded to the agreed precision and scale when being considered.

In particular, messages refering to this line of credit may contain values that go beyond the precision and scale specified, and these values must be rounded when being considered.

proof_of_id may be used to associate the sending node with a human identity, by way of, for example, a password, or a GPG key and signature of line_of_credit_id. This mechanism is left unspecified here.

To move a line of credit to a different node, send another Connect message with the existing line_of_credit_id from the new node address, and put the old node ID into old_node_id. Sign the message with both node keys.

IOU

message IOU {
    required bytes iou_id = 1;
    required bytes line_of_credit_id = 2;
    required string amount = 3;
    optional bytes transaction_key_id = 4;
    optional string memo = 5;
}

Signed by sender, indicates change in a line of credit's mutual credit account balance by amount, in favour of the receiving partner. When a mutual credit account comprises two lines of credit, an IOU is only sent on one of them. An IOU with the same ID must only be recognized once on the same mutual credit account.

If an IOU is a consequence of a routed transaction, the transaction_key_id must be included. In this case, the IOU is merely indicating agreement that the corresponding Promise has been committed.

If amount is negative, the message is interpreted as a request for that amount IOU to be sent by the other partner.

6. Broadcast Messages

All broadcast messages must be signed by their source's identifying key.

Key Certificate

message KeyCertificate {
    required bytes key_id = 1;
    required PublicKey key = 2;
    optional double valid_from = 3;
    optional double valid_until = 4;
    optional bytes revocation_hash = 5;
    optional bytes supersedes_key_id = 6;
}

Announces a public key and its validity period. key_id must be the key's canonical hash. revocation_hash is the SHA-256 hash of the concatenation of this key's modulus, the revocation key's modulus, and a secret salt, used to validate a Key Revocation.

If a previously-announced key ID is given in supersedes_key_id, the present key is intended to replace the previous key as the identifier for the entity identified by the previous key's ID. During the period that both certificates are valid, both key IDs are acceptable identifiers for the entity.

Must be signed by the given key, and the key that it supersedes, if any.

Key Revocation

message KeyRevocation {
    required bytes revoked_key_id = 1;
    required KeyCertificate revocation_key = 2;
    required bytes salt = 3;
    optional string reason = 4;
    optional double compromised_since = 5;
    optional KeyCertificate replacement_key = 6;
}

Used in an emergency to revoke a compromised key using a secret revocation key. For example, if a node's private identity key is compromised and used to move the node to another host with a new identity key, a revocation of the old key indicating that it was compromised prior to the move would notify its partners that the new identity is not legitimate and to halt all dealings until the situation is sorted out.

To be eligible for revocation, a PublicKey must contain a revocation_hash value. To be valid, a KeyRevocation's revoked key modulus, revocation key modulus, and salt, concatenated together and SHA-256'd must equal the revocation_hash of the revoked key.

The purpose of the salt is to prevent discovery of which other keys use the same revocation key, once a KeyRevocation has been issued.

Must be signed by the revocation key.

Node (Broadcast)

Node messages may also be broadcast. The broadcast form is identical to the local form used when creating lines of credit. A broadcast Node message is distinguishable from a local Node message, and its meaning the sending server wishes to use the node to connect to a node on the receiving server, by the fact that it is announced by an Inventory message, and received in response to an InventoryRequest.

Exchange Rate

message ExchangeRate {
    required bytes rate_key_id = 1;
    required string rate = 2;
    optional string in_units = 3;
    optional string out_units = 4;
    optional string description = 5;
    optional double valid_until = 6;
}

Signed by the key whose ID is rate_key_id.

Atomicity Fee Set

enum AtomicityMode = {
    BARE = 0;
}

message AtomicityFeeSet {
    required bytes atomicity_fee_set_key_id = 1;
    message AtomicityFee {
        required AtomicityMode atomicity_mode = 1;
        optional string atomicity_rate = 2 [default = "1"];
        optional string atomicity_flat_fee = 3 [default = "0"];
    }
    repeated AtomicityFee atomicity_fees = 2;
}

Allows nodes to define a set of conversion factors and flat fees to be applied to incoming transactions on a line of credit, depending on the type of atomicity offered by the transaction. This document only defines a single atomicity mode BARE, but extensions can define others that may allow for less intermediary risk and therefore lower fees. See Credit message below for how the atomicity rate and flat fee are applied.

Credit

TODO: - Advertise precision/scale of LoC?

message Credit {
    required bytes partner_node_key_id = 1;
    required bytes line_of_credit_id = 2;
    enum Direction = {
        IN = 0;
        OUT = 1;
    }
    required Direction direction = 3;
    message CreditChunk {
        required uint32 chunk_id = 1;
        optional string amount = 2;
        optional string exchange_rate = 3 [default = '1'];
        optional bytes exchange_rate_key_id = 4;
        optional bytes atomicity_fee_set_key_id = 5;
    }
    repeated CreditChunk chunks = 4;
}

Broadcast credit advertisement. Signed by from address. For a line of credit to be valid for use in transactions, both partner nodes must broadcast corresponding credit advertisements. One partner advertises the IN direction, the other advertises the OUT direction.

A node may advertise multiple "chunks" on the same line of credit, in order to, for example, offer a better exchange rate for a portion of credit available in order motivate its use. Exchange rates are set at either end unilaterally, so both partners need not agree on chunks.

amount indicates the maximum value of obligations the source node is willing to accept from (IN) or emit to (OUT) the partner node, at the corresponding exchange rate. Notice that no balance is advertised, meaning that the advertised limits must take into account any non-zero balance between the partner nodes. If amount is missing, it is assumed the node will process unlimited transfers on this line of credit.

Credit amounts set by each partner do not have to agree. When they do not, the lower of the two amounts prevails.

Each node has a conceptual base unit, which line-of-credit (LoC) values can be converted to and from:

  • incoming LoC amount * IM - IFF = incoming amount in node units
  • amount in node units * OM - OFF = outgoing LoC amount
  • IM (incoming multiplier) = IN rate * atomicity fee
  • IFF (incoming flat fee) = atomicity flat fee
  • OM (outgoing multiplier) = OUT rate
  • OFF (outgoing flat fee) = 0 (may be modified by extensions)

Here, IN rate is calculated by multiplying exchange_rate and the rate corresponding to exchange_rate_key_id, if given, for the IN node. Similarly OUT rate is calculated from the OUT node's exchange_rate and rate from exchange_rate_key_id. The atomicity fees are selected from the Atomicity Fee Set corresponding to atomicity_fee_set_key_id, for the appropriate atomicity mode, and only apply to the IN node.

A node is a conceptual entity that performs exchanges between all of its incoming lines of credit and all of its outgoing lines of credit. To indicate that some some exchanges are not performed, use multiple nodes.

For efficiency, nodes may not wish to advertise the exact credit limits, but rather some other lower number that will accomodate enough transactions to be useful, but will not need frequent updating. This behaviour should generate fewer broadcast messages. Payers can still potentially get approval for larger transactions by making Credit Checks prior to initiating payment.

7. Transaction Messages

Credit Check

message Transfer {
    required bytes line_of_credit_id = 1;
    required string amount = 2;
    optional uint32 chunk_id = 3;
    optional EncryptedMessage onion_forward = 4;
}

message Exchange {
    repeated Transfer in_transfers = 1;
    repeated Transfer out_transfers = 2;
    optional bool wait_for_merge = 3;
    optional bytes forward_to_node_key_id = 4;
    optional string forward_to_host = 5;
}

message CreditCheck {
    required bytes check_id = 1;
    required PublicKey checker_key = 2;
    optional EncryptedMessage exchange_onion = 3;
    message CheckResponse {
        required bytes node_key_id = 1;
        required bool transaction_possible = 2;
    }
    repeated EncryptedMessage responses = 4;
}

Used to inquire from one or more nodes whether a particular transaction is possible at the moment. exchange_onion contains an Exchange structure, encrypted to the receiving node, containing the set of incoming and outgoing lines of credit and amounts on each representing that node's part in the transaction. chunk_ids are specified when desired credit has previously been advertised as a non-default chunk. Each outgoing Transfer structure may contain a further Exchange, encrypted to the next intermediary node in the transaction.

The response to a credit check is a boolean transaction_possible, which indicates whether the the node is willing to perform the requested exchange at the moment. Responses must be encrypted against checker_key, appended to responses, and onion_forward copied to exchange_onion for each branch going forward. An affirmative response is no guarantee of credit availability, just a relatively up-to-date credit advertisement.

When a propagating credit check branches and then merges again, only one branch needs to carry responses from the branch point and before, and only one branch needs to contain Exchange queries for the merge point node and beyond. When a node receives a credit check with no exchange_onion, it should expect a further credit check message with the same check_id that does contain an exchange_onion. wait_for_merge indicates to a node to wait for all branches to arrive and concatenate all responses before forwarding them on.

forward_to_node_key_id indicates that the receiving node should add its response, and, after waiting for merge, if requested, forward the credit check to a particular node, generally the node performing the credit check. forward_to_host allows the credit check to be forwarded directly, but if it is missing, or connection to the destination hosts fails, the credit check should be forwarded over the credit network using Relay.

Payment Init

message PaymentInit {
    optional bytes request_id = 1;
    optional bytes transaction_key_id = 2;
    required string amount = 3;
    optional string units = 4;
    optional string memo = 5;
    optional bytes proof_of_id = 6;
    optional bytes data = 7;
}

Sent by either payer or recipient to the other payment endpoint to initiate or request payment, respectively. To request payment, the recipient specifies a request_id, but no transaction_key_id. To initiate payment, the payer specifies a transaction_key_id, and the request_id if it is sending the payment in response to a request from the recipient. transaction_key_id is the canonical hash of a key the payer will use to authenticate its messages to intermediaries when necessary during the transaction, and must be unique.

amount is given in recipient node base units. units is an optional string that can be used to indicate the payer's understanding that the payment amount is being valued by the recipient in some external unit of account. By filling out this field, the payer is asking for the recipient node to reject the payment if the given units value is not the correct base unit of account for the recipient node, to help prevent misunderstandings.

proof_of_id may be used to link the sending node to a human identity. The mechanism is left unspecified here. data may contain arbitrary data which may be used, for example, to embed further payment instructions to the recipient using another protocol.

Payment Accept

message PaymentAccept {
    required bytes transaction_key_id = 1;
    required bytes commit_key_id = 2;
    required PublicKey commit_key = 3;
    required PaymentInit payment_init = 4;
    optional string commit_url = 5;
    optional bytes proof_of_id = 6;
}

Returned by the recipient in response to a payment init, if it chooses to accept the payment. Must be signed by the recipient. The PaymentAccept combined with the Commit will form proof of payment for the payer.

commit_key_id must be the canonical hash of commit_key, which is used by the recipient to sign the Commit message, and must be unique.

payment_init is a copy of the payment init. A recipient that does not understand units should strip the units field from the payment init if it was set, otherwise it is acknowledging that the payment is being received in the stated units.

commit_url is where the recipient will publish the Commit message if and when it is generated.

Promise

message Promise {
    required bytes transaction_key_id = 1;
    required PublicKey transaction_key = 2;
    required bytes commit_key_id = 3;
    required PublicKey commit_key = 4;
    required bytes line_of_credit_id = 5;
    required string amount = 6;
    required double expiry = 7;
    optional bytes exchange_onion = 8;
    optional commit_url = 9;
}

TODO: Ensure promises are valid long enough to have a chance of committing the transaction?

  • payer specifies minimum promise time
  • nodes advertise their max promise time

A signed promise to pass forward an IOU of value amount on the specified line of credit when shown a Commit message with the given transaction_key_id, signed by the commit_key before the expiry time. commit_key_id is included so there is no question as to its value, since it may be used to identify the Commit in atomicity extensions.

exchange_onion consists of Exchange structures (defined in CreditCheck above), and defines the lines of credit and exchanges to carry obligations to the recipient. forward_to_node and forward_to_host are not used here.

Promise Release

message PromiseRelease {
    required bytes transaction_key_id = 1;
}

A promise holder may release the promisor of its promise by sending a PromiseRelease. Must be signed by the promise holder.

Commit

message Commit {
    required bytes commit_key_id = 1;
    required Signature commit_signature = 2;
}

Commit is generated by the recipient and used to trigger promises back through the intermediaries to the payer. A promise holder redeems the promise its holds with its neighbour by sending a Commit message with the same commit_key_id as the Promise, and with its commit_key_id signed in commit_signature by the transaction's commit_key, before the promise's expiry.

In BARE atomicity mode, the only mode described here, there is no authority to enforce the acknowledgement of Commit messages on promisors if they are unable or unwilling to do so by promise expiry time. Therefore each promise holder takes a risk in handling the Commit message: it is bound by its own promise to pass an IOU forward, but it may not be able to use the Commit to redeem a promise that it holds.

To help propagate the Commit in case of an intermediary server outage, the recipient should publish the Commit at the commit_url specified in the Promises.

BARE atomicity mode requires voluntary cooperation from servers to work:

  • A node that receives a Commit, either from a neighbour or from the commit_url, must pass IOUs forward to redeem all promises it has issued for that transaction that have not expired or been released.
  • A node that has received all its IOUs for a transaction must redeem all unreleased promises it has issued, even if they have expired.
  • A node should attempt to redeem any unreleased expired promises it holds by passing the Commit message to the promise issuer(s), even when it causes a line of credit balance to surpass its credit limit. It may, however, decide not to do this if the amount over limit would be unacceptable.
  • Payers must honour unreleased expired promises, even if the Commit message is late.

This behaviour is difficult or impossible to verify or enforce, therefore BARE atomicity mode is only appropriate for use within a cooperating group of servers. Even within a group of cooperating servers, a server outage could cause a transaction to permanently stall partially-committed if committing it would now cause an unacceptable credit limit overage.

Status Query

message StatusQuery {
    required bytes transaction_id = 1;
}

The payer uses this to request information about the current state of the transaction from another participant. Must be signed by the transaction_key.

Status

message Status {
    required bytes transaction_id = 1;
    message StatusChunk {
        required bytes line_of_credit_id = 1;
        optional bytes chunk_id = 2;
        enum StatusCode {
            AWAITING_PROMISE = 0;
            PROMISE_RECEIVED = 1;
            PROMISE_RECEIVED_REJECTED = 2;
            PROMISE_SENT = 3;
            PROMISE_SENT_REJECTED = 4;
            PROMISE_RELEASED = 5;
            PROMISE_EXPIRED = 6;
            COMMIT_RECEIVED = 10;
            COMMIT_SENT = 11;
        }
        repeated StatusCode codes = 3;
    }
    repeated StatusChunk chunks = 2;
}

8. Error Messages

Error

message Error {
    enum ErrorCode {
        // TODO: List all error codes here.
    }
    optional ErrorCode code = 1;
    optional string message = 2;
}
  • Connect: precision/scale too high/low.