Protocol Design Considerations
Ripple must support a variety of use cases:
- Internet, paypal-like payments - via web client to Ripple server
- Mobile phone payments - via SMS gateway to Ripple server
- POS payments - paying user must be able to authorize payment using a smartcard to sign a message to their Ripple server, since merchant can't be trusted. Merchant must have internet connection.
Ripple must support existing electronic accounts as well as having a default mechanism for two users on different hosts to create a basic account managed cooperatively by both hosts, so neither user has to trust the other's host. See Account Creation.
Accounts may or may not incorporate interest or demurrage on balances. Basic Ripple shared accounts may not incorporate interest initially for the sake of simplicity.
Making a payment consists of finding a set of paths through the network from payer to recipient with enough available credit, and then sending value down those paths. Before beginning the search for a set of paths, payer and recipient hosts must communicate to agree on exactly what they are searching for. Either payer or recipient should be able to initiate this process, although both obviously have to agree on the parameters for it to proceed.
The main item to be agreed upon is the payment amount, which must be specified in value units understood by both recipient and payer. Since the protocol should allow for intermediaries to charge fees on thru payments, the payer and recipient must decide whether the amount specified is the amount to be received by the recipient or the amount to be sent by the payer.
There should be no need for all accounts involved to be denominated in the same value units, or even for the sender's and recipient's accounts to be denominated in the same units. Each participant should be able to determine their own exchange rates for their portion of the transaction.
For example, Alice wishes to pay 20 USD to Bob, so that Bob receives that amount. They find a path via Carol, with whom Alice has a EUR account, and Bob has an account denominated in grams of gold (gAu). Bob would convert the desired $20 into 0.7 gAu on his account with Carol, which Carol converts into 14 EUR on her account with Alice, which Alice can then accept or reject as the cost of transferring 20 USD to Bob.
Distributed 2-phase transaction model: commit-ready, then commit. Transaction is decentralized (controllerless), since trust is between adjacent user agents, not to any central entity. That means user agents must pass signed messages between them acknowledging the transaction for it to have meaning regardless of what any other entity says.
Commit-ready message is a promise, passed forward down the path (found by some means) from payer to recipient, to accept a receipt message, meeting certain criteria, by adjusting the account balance in question to reflect the amount on the receipt. The promise should freeze the indicated amount of credit.
Commit message is the receipt, passed backwards down the path from recipient to payer so that each intermediary must transmit value before receiving it in exchange, motivating them to continue passing the receipt along the path.
A very useful alternative proposal is to allow intermediary servers to optionally make themselves known explicitly to servers further down the path during the commit-ready phase. Then the commit can be passed to to all known servers further back on the path, instead of just to the one immediately preceding the commit holder. This is more robust in case of an outage, but incentivizes intermediaries to knock out the server of the intermediary directly ahead of them until their promise expires, because they will receive the commit message regardless. Fortunately, the beneficiary of such an attack would be obvious.
A useful set of receipt criteria is:
- path ID
- amount less than or equal to the amount of the promise
- signed by payer-provided key, allowing payer to authorize the final commit decision
- signed by recipient-provided key, so payer can prove that the recipient received the payment. The payer must be able to link this key to the recipient's identity for this to work. One way to do this is to have the recipient sign this transaction key with an known identifying key and present it to the payer at payment initialization.
There is a problem with having a single amount on the receipt in that accounts along the associated path might be denominated in different units from the payment itself. This is remedied by having promises include a path-amount denominated in the units of the overall payment amount agreed between payer and recipient, in addition to an amount in the units of the account in question. Thus a promise message determines an exchange rate between the payment units (which no intermediary need know) and account units. The path-amount is transferred without change on subsequent promises forward on the same path, and is the amount referred to on the receipt.
Simply using a hash to identify the receipt to come doesn't allow for a receipt amount less than the promise amount, which may be useful if either too much is promised for some reason, or if some intermediary doesn't want to accept the full receipt amount for fear of being stuck with it (in that case multiple receipts could be issued for the path, and sent at intervals).
Release promises and promise expiry
Promises must not last forever or else stalled transactions will tie up available credit in perpetuity. Intermediaries holding promises may release them at any time, after which a receipt need not be redeemed. But an intermediary will generally not want to release a promise until it has been released of its subsequent promises by the next intermediary in the path, otherwise it may be forced to redeem a receipt that it is unable to collect on. Intermediaries should be motivated to release promises as soon as they are released from their promises in order to free up credit that they may use to receive payments. But good design dictates that they not be held captive for an indefinite time should a promise not be released for some reason, such as another host going down permanently.
Putting a receipt deadline on promises ensures that credit will never be frozen forever, but it creates a problem if one intermediary receives a receipt but is unable to pass it on before the receipt deadline on the promise it holds: that intermediary will have lost the value of the receipt. That turns acting as an intermediary into a potentially risky endeavour, when the protocol should do everything possible to encourage users to act as transaction intermediaries. To minimize this risk, receipt deadlines might be set very far off into the future. This opens the system to DoS attacks that try to freeze up massive amounts of credit for long periods of time, however.
Another possibility is instead of a hard receipt deadline, have a soft deadline after which promises start to degrade by, say, a certain percentage of the promise per day until a receipt or promise-release message is received. That way, intermediaries can simply have their promises forward degrade faster than on the promises they hold, and be assured that they will not suffer any loss as promises degrade in value gradually over time. With a gradual enough promise degradation rate, this also ensures that hosts need not bicker over a few minutes or seconds timing discrepancy caused by network latency, differing clock synchronization, or sheer maliciousness. (That would be a difficult technical problem to solve at the protocol level, I think, but if there is a big timing discrepancy, the host in error should be obvious.)
As mentioned above, allowing intermediaries to specify maximum receipt amounts is another way to minimize the risk of acting as an intermediary, since it minimizes the amount an intermediary could possibly ever get stuck with if their host went down for an extended period.
One worry with promise degradation might be that an intermediary purposely delays acknowledging a receipt until degradation has kicked in, meaning if they had their forward promise degrade faster than the one they received they would make a profit on the transaction at the expense of the intermediary trying to pass them the receipt. The only way to deal with this would be to blacklist any host that behaved in this way, and refuse to interact with it.
Sometimes the connection between two hosts may be interrupted at inopportune times, causing one or both to suffer a loss by getting stuck with a receipt. If this is because one server crashed, it should acknowledge that fact by taking any losses itself. Sometimes the connection between two hosts may be interrupted due to network outages beyond either's control. If this happens regularly, these two hosts mustn't continue to interact, as a dependable connection is vital for conducting Ripple transactions.
If greater accountability is needed, two hosts may agree on a timestamping intermediary between them that will vouch for the time that time-sensitive messages were attempted to be passed on. This is probably beyond the scope of the protocol for the foreseeable future however.
It seems possible that service providers could provide users with insurance against outages that would cover any losses due to those outages, although this might cost extra.
How paths are found is the most interesting question in the protocol design. One possible model is to have queries passed from host to host, aided by some decentralized routing scheme. Another is to have hosts register their accounts with one or more routing servers, that can be queried to find paths through the accounts they know about. The former is more in keeping with the decentralized, private, and anonymous nature of the protocol, but the latter may ultimately be more feasible, and not that odious, as many independent routing servers may arise.
A broadcast querying model will not scale. Queries need to be targeted.
Routing IDs should not include any identifying characteristics of user or host.
I have noticed similarities between Ripple routing, mobile ad-hoc routing, and QoS routing, including the RSVP protocol.
A decentralized scheme must specify both a logical network topology by which queries are routed, and how the querying itself works.
There are three potential logical topologies I've identified as potentially useful for Ripple:
- Metropolis-Hastings routing
- Pros: scalable, high anonymity, no hierarchical organization required, proven (used in Freenet 0.7)
- Cons: non-optimal paths, continual shuffling overhead required, may be vulnerable to certain attacks
- Grouped hazy-sighted link-state routing
- Pros: optimal paths, no hierarchical organization, somewhat proven (used in some mobile adhoc networks)
- Cons: may have limited scalability
- Cell structure routing
- Pros: quite optimal paths, highly scalable
- Cons: unproven, not fully specified yet, may have security vulnerabilities, requires formation of a hierarchical structure
Cell structure routing seems most promising to me at this point, but needs simulation.
The querying model should allow users to fork queries into multiple parallel queries, either to find multiple paths where there is no single path with sufficient credit to make a payment, or to search for several alternative path sets so the payer may choose the most desirable (eg, least expensive). It is important to be able to distinguish between the two, because two queries on alternative pathsets can use the same available credit, where two queries in the same pathset cannot. A possible model is to have a pathset ID, as well as a path ID, on queries, and have both be lists that are extended by appending whenever a fork occurs. That way each path has a unique ID when it reaches its target, but
Queries should have limited scope, either in real-world time-to-live (useful for payers in a hurry), number of hops to search, or both. A useful model might be to have a hops-remaining field that gets decremented at each hop and divided between directions when a query forks.
Ultimately, each host decides for itself how to route queries, but the protocol can make recommendations. An important consideration is a good balance of short paths and few paths, to minimize the resources required. Another consideration is path cost. Possibly the payer should be able to indicate whether she prefers intermediaries to route for speed or for lowest cost. Perhaps the payer should be able to indicate a maximum acceptable cost, although this requires exposing the payment units, and is still somewhat ambiguous insofar as accounts are kept in other units and conversion to payment units is subjective.
Many people have confused the abstract financial trust connections between Ripple users with the abstract data connections over a computer network between their hosts, or even worse, with the physical network (cable) connections between computers. Ripple trust connections take the form of financial accounts between users whose state may at times be modified by messages sent over data connections on the internet or other networks. But the trust connection is not the same thing as a data connection. The trust connection is between people (or legal entities), and the data connection is between computers.
This confusion is often manifested when people believe that Ripple users would only be able to send messages to those other users they are "connected" to in the trust network. This is a misunderstanding. Ripple hosts can connect to any other host in the computer network that will accept the connection, regardless of whether any users on those hosts are connected to each other. This means that in a transaction, the payer's host and the recipient's host can connect to each other and coordinate the transaction by sending messages directly to each other. This simplifies things quite a bit.
Another thing that confuses people is the term P2P. P2P means many things to many people. Ripple is P2P in several ways: Ripple trust connections/accounts are between peers in the payment routing trust network in the sense that every user has the same capability to act as a payment intermediary (contrast with the banking network where some participants have special intermediary privileges). Ripple hosts also send messages to each other as internet/network peers. Whether or not each user runs their own server/host is irrelevant -- Ripple should allow for running your own host, but should certainly permit many users on a single host if desired.
Building on existing application protocols
Since every Ripple host should be able to connect to any other for both handling accounts and making payments, a conventional message transport needs to be specified. Early protocol designs attempted to build on various existing application protocols as message transports:
- HTTP: Not truly p2p. Two persistent connections required for bidirectional request initation.
- XMPP: Designed as client<->server<->server<->client messaging architecture to support presence notifications and offline message delivery. None of this infrastructure is necessary for Ripple.
In the end, plain TCP connections accomplish exactly what Ripple needs: message transport. TLS gives secure channels. BEEP gives a generic application protocol framework to steal some of the basics from.
JSON is much simpler to design for and implement than XML. XML does have a digital signature spec, but it's possible to copy the relevant parts for JSON. Canonical JSON is UTF-8.
YAML is another alternative, but is more complex than JSON without providing anything Ripple needs, as far as I can see.