Skip to content

Instantly share code, notes, and snippets.

@BobWay
Last active August 29, 2015 14:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BobWay/f48e682665c239e75b9a to your computer and use it in GitHub Desktop.
Save BobWay/f48e682665c239e75b9a to your computer and use it in GitHub Desktop.

Gateway Services Overview

+--------+             +---------+             +---------+
|        |             |         |<------------| Remote  |
| Client |------------>| Gateway |             | Gateway |
|        |             |         |------------>|         |
+--------+             +---------+             +---------+

Introduction

The Gateway Services API is a set of REST end-points implemented by gateways to provide common Ripple functionality to both Ripple clients and other Gateways. Features include:

  • Gateway Deposits
  • Gateway Withdrawals
  • Inbound Bridge Payments
  • Outbound Bridge Payments
  • Multi-Bridge Remittance Payments
  • Identifier Resolution (lookup)
  • KYC Registration
  • Sender and Receiver KYC Gating
  • Opaque KYC Attestation

The Gateway Services API replaces the Federation name lookup and Bridge quote API calls. It unites those features with the functionality proposed for Manifests & Profiles. It supports Stefan's new road-map, by utilizing:

Core API Calls

The Gateway Services API extends the gatewayd data model. The core functionality can be implemented with only four API calls.

GET		bridge_payment/quotes					(returns transaction templates)
POST	bridge_payment							(returns gateway_tx_id)
GET		bridge_payment/{gateway_tx_id}			(transaction status)
POST	bridge_payment/{gateway_tx_id}/cancel	(cancel transaction)

Bridge Payments

A bridge payment is any payment between a Ripple account and an External account. Generally a "bridge payment" moves funds from one entity to another. "Deposits" and "withdrawals" move funds between accounts belonging to the same entity. This makes bridge payments a superset of deposits/withdrawals and allows the latter to be implemented using the former's routines.

Bridge payments are implemented analogously to REST payments:

  1. GET a set of possible "payment template" transactions
  2. CHOOSE the best payment to send
  3. SET any missing required properties, including KYC info
  4. POST the payment request to the gateway
  5. SEND a Ripple or External payment, including the ripple_invoice_id
  6. CHECK payment status to assure successful completion

Example Scenario

Send money from an independent Ripple wallet to a "gated" Fidor bank account.

Fidor doesn't want its user's transactions indentifiable in the public ledger. As such it cannot use static destination tags to implement hosted wallets. Fidor also needs to be able to "gate"/refuse incoming payments from non-KYCed senders. Fidor implements this using bridge-payments.

Simplest Flow

Alice (a Fidor.ru known Ripple user) sending to Bob (a Fidor.de bank account holder)

1. GET bridge_payment/quotes

Like the REST API's GET payment/paths, this call returns a list of bridge_payments that all meet the payment requirements specified in the URL parameters.

Request:
https://{gateway domain}/gateway/v1/{sender}/bridge_payment/quotes/{receiver}/{amount}
Example Request:
https://fidor.de/gateway/v1/alice@ripple.com/bridge_payment/quotes/bob@fidor.de/100+EUR
Example Response:
{
	"bridge_payments": [
	{
	// Gateway Transaction Section
		"gateway_tx_id": "9876",
		"gateway_tx_type": "out",
		"gateway_tx_state": "quote",
		"gateway_tx_message": "Inactive, must be posted.",

	// Acceptance Criterian
		"destination_account": "ripple:r12345",
		"destination_amount": {"amount":"100", "currency":"EUR", "issuer":"r12345"},
		"ripple_invoice_id": "8765",
		"expiration": "1311280970",
		
	// URL Specified by End-user
		"url_sender": "alice@ripple.com",
		"url_receiver": "bob@fidor.de",
		"url_receiver_amount": {"amount":"100", "currency":"EUR"},

	// Parties, KYC and Contract Terms
		"sender": "acct:alice@fidor.ru",
		"sender_account": "acct:ripple:alice@fidor.ru",
		"outbound_bridge": "https://fidor.de",
		"receiver": "acct:bob@fidor.de",
		"receiver_account": "acct:bank:bob@fidor.eu",
		"receiver_amount": {"amount":"100", "currency":"EUR"},
	}]
}

2. CHOOSE the best payment to send

In this example there is only one payment option. That will be the case much of the time. However, in remittance situations their are often multiple possible sending or receiving agents. Each of these may have different terms and fees. As such, each is returned as a different possible transaction. The client should display the list to the end-user in order to choose the most appropriate option.

3. SET any missing required properties, including KYC info

In this example there are no additional required claims. The gateway has been able to resolve all necessary KYC information based on the identifiers supplied in the URL.

4. POST the payment request to the gateway

In this example, to activate the quote the response is posted back as-is. Posting back the whole transaction allows the gateway to be stateless up to this point. This avoids storing quote resources that will never be activated. It also allows the client to implement a "GatewayPayment" class and serialize/deserialize it to JSON the same way every time.

Request:
https://{gateway domain}/gateway/v1/{sender}/bridge_payment
Example Request:
https://fidor.de/gateway/v1/alice@ripple.com/bridge_payment
{
// Gateway Transaction Section
	"gateway_tx_id": "9876",
	"gateway_tx_type": "out",
	"gateway_tx_state": "quote",
	"gateway_tx_message": "Inactive, must be posted.",

// Acceptance Criterian
	"destination_account": "ripple:r12345",
	"destination_amount": {"amount":"100", "currency":"EUR", "issuer":"r12345"},
	"ripple_invoice_id": "8765",
	"expiration": "1311280970",
	
// URL Specified by End-user
	"url_sender": "alice@ripple.com",
	"url_receiver": "bob@fidor.de",
	"url_receiver_amount": {"amount":"100", "currency":"EUR"},

// Parties, KYC and Contract Terms
	"sender": "acct:alice@fidor.ru",
	"sender_account": "acct:ripple:alice@fidor.ru",
	"outbound_bridge": "https://fidor.de",
	"receiver": "acct:bob@fidor.de",
	"receiver_account": "acct:bank:bob@fidor.eu",
	"receiver_amount": {"amount":"100", "currency":"EUR"},
}
Example Response:
{
// Gateway Transaction Section
	"gateway_tx_id": "9876",
	"gateway_tx_type": "out",
	"gateway_tx_state": "pending",
	"gateway_tx_message": "Active, awaiting Ripple payment.",

// Acceptance Criterian
	"destination_account": "ripple:r12345",
	"destination_amount": {"amount":"100", "currency":"EUR", "issuer":"r12345"},
	"ripple_invoice_id": "8765",
	"expiration": "1311280970",
	
// URL Specified by End-user
	"url_sender": "alice@ripple.com",
	"url_receiver": "bob@fidor.de",
	"url_receiver_amount": {"amount":"100", "currency":"EUR"},

// Parties, KYC and Contract Terms
	"sender": "acct:alice@fidor.ru",
	"sender_account": "acct:ripple:alice@fidor.ru",
	"outbound_bridge": "https://fidor.de",
	"receiver": "acct:bob@fidor.de",
	"receiver_account": "acct:bank:bob@fidor.eu",
	"receiver_amount": {"amount":"100", "currency":"EUR"},
}

A successful post changes the gateway_transaction's state to "pending".

5. SEND a Ripple or External payment, including the ripple_invoice_id

At this point the gateway transaction is in the "pending" state awaiting to accept the specified incoming payment. Before the expiration date, the user should send a ripple payment to:

	"destination_account": "ripple:r12345",
	"destination_amount": {"amount":"100", "currency":"EUR", "issuer":"r12345"},
	"ripple_invoice_id": "8765",

The incoming Ripple payment will identify the gateway transaction by setting the Ripple payment's invoice_id field.

6. CHECK payment status to assure successful completion

Since this is an asychronous process, the client must check the transaction state and report the progress to the end-user.

Request:
https://{gateway domain}/gateway/v1/{sender}/bridge_payment/{gateway_tx_id}
Example Request:
https://fidor.de/gateway/v1/alice@ripple.com/bridge_payment/9876
Example Response:
{
// Gateway Transaction Section
	"gateway_tx_id": "9876",
	"gateway_tx_type": "out",
	"gateway_tx_state": "delivered",
	"gateway_tx_message": "Complete, payment has arrived in the destination account.",

// Acceptance Criterian
	"destination_account": "ripple:r12345",
	"destination_amount": {"amount":"100", "currency":"EUR", "issuer":"r12345"},
	"ripple_invoice_id": "8765",
	"expiration": "1311280970",
	
// URL Specified by End-user
	"url_sender": "alice@ripple.com",
	"url_receiver": "bob@fidor.de",
	"url_receiver_amount": {"amount":"100", "currency":"EUR"},

// Parties, KYC and Contract Terms
	"sender": "acct:alice@fidor.ru",
	"sender_account": "acct:ripple:alice@fidor.ru",
	"outbound_bridge": "https://fidor.de",
	"receiver": "acct:bob@fidor.de",
	"receiver_account": "acct:bank:bob@fidor.eu",
	"receiver_amount": {"amount":"100", "currency":"EUR"},
}

Complications

Sometimes the process will not go as smoothy as above. For example, perhaps the bridge doesn't know the sender.

Alice (unknown to Fidor) sending to Bob (a Fidor.de bank account holder)

1. GET bridge_payment/quotes

If one or both of the parties is unknown, the gateway will include a list of the required KYC information to be submitted with the request. Notice the following properties:

"sender": "",
"sender_claims_required": ["given_name", "family_name", "address.formatted", "email", "phone_number"],
"sender_claims_jwt": [],

Here the bridge was not able to resolve the sender's identity internally or by using webfinger.

The bridge will not process the payment without the sender supplying additional information about the sender. This information is submitted as OpenID format claims. These claims can be provided and signed a third-party or by the subject him/herself.

These claims can also include "attestations" from the issuer about the level of verification that was done on the claims. That allows the receiver to decide if they are willing to trust the issuers verification in leu of doing their own extended verification.

Example Third-Party Claims
{
	"sub":	"acct:alice@snapswap.com",
	"iss":	"acct:snapswap.com",
	"iat":	"1311280970",

	"given_name":"Bob",
	"family_name":"White", 
	"address.formatted":"candyland",
	"email":"bob@gmail.com",
	"ripple_address": "ripple:r45678"

	"email_verified":"true",
	"phone_number_verified":"true",
	"id_verified":"true",
	"id_face_matched":"true",
}
Example Web Token

Each set of claims is encapulated into a web token signed by the claim issuer.

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogI
mh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

3. SET any missing required properties, including KYC info

In this example, the end-user "alice" is submitting her own identity claims. She is also including claims and attestations provide by her Ripple gateway.

These claims are submitted as an array of signed web token claims.

"sender_claims_jwt": [
	// end-user issued claims
	eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogI
	mh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk,
	// gateway issued claims
	eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogI
	mh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk,
],

4. POST the payment request to the gateway

If the client fails to submit all the required fields, the response code is 400 Bad Request and the result will contain the gateway-payment transaction updated to list the still missing fields.

"sender": "acct:alice@fidor.de",
"sender_claims_required": ["phone_number"],
"sender_claims_jwt": [],

Convenience Calls

A gateway deposit is really just an outbound bridge call in which the sending external account and receiving ripple account owners are the same person. A gateway withdrawal is an equivalent outbound bridge call.

Normally, the gateway establishes a standing "policy" that ties the two accounts together any fees. A user's policy is used as the template for each new gateway_transaction. Because a user is KYCed when establishing their gateway policy, no additional KYC is needed for deposits or withdrawals.

Gateway Deposits

  • GET gateway_deposit/quotes
  • POST gateway_deposit
  • GET gateway_deposit/{gateway_tx_id} (transaction status)
  • POST gateway_deposit/{gateway_tx_id}/cancel (cancel transaction)

Gateway Withdrawals

  • GET gateway_withdrawal/quotes
  • POST gateway_withdrawal
  • GET gateway_withdrawal/{gateway_tx_id} (transaction status)
  • POST gateway_withdrawal/{gateway_tx_id}/cancel (cancel transaction)

Bridge Discovery and KYC Resolution

Client Side

Alice is logged in to her Ripple client and she wants to send 100 EUR to Bob. Bob is either 1) a Ripple account holder, 2) a Bank account holder, or 3) unbanked. In all three cases Alice needs an identifer for Bob to enter into her clients's "send to:" field.

She can enter Bob's:

  1. Ripple name, federation name, or Ripple address.
  2. Federation name, or federation style bridge address
  3. Domain name, of the remittance location/network where Bob can pickup the money

The client then needs to resolve the identifiers in order to determine the identities of the account owners and to query the proper bridge.

Resolve ~alice

Given Alice is logged in as ~alice (ripple:r56789), the client will:

  1. Resolve ~alice to r56789 using httpid.ripple.com

  2. Query r56789's ledger account_root for its domain property "host.com"

  3. Query the webfinger service @ "domain", e.g. https://host.com/.well-known/webfinger?resource=ripple:r56789

  4. Read Alice's preferred identifier from the webfinger JRD "properties" section.

  5. If no preferred_username field, hash each aliases and compare to account_root's email_hash

     "properties": {
     	"preferred_username": "alice@host.com",
     },
    

Resolve bob@fidor.de

Given Alice entered "bob@fidor.de", the client will:

  1. Query the webfinger service, e.g. https://fidor.de/.well-known/webfinger?resource=acct%3Abob%40fidor.de

  2. Read Bob's preferred identifier from the webfinger JRD "properties" section.

  3. Read Bob's public claims from JRD "properties".

  4. Read the gateway api location from the JRD "links".

     "links": [{
         "rel": "https://ripple.com/gateway_services",
         "href": "https://api.fidor.de/gateway_services",
     }]
    

Derive the cannonical bridge URL:

https://api.fidor.de/gateway_services/v1/alice@host.com/bridge_payment/quotes/bob@fidor.de/100+EUR

Bridge Side

Resolve bob@fidor.de

  1. Query the webfinger service, e.g. https://fidor.de/.well-known/webfinger?resource=acct%3Abob%40fidor.de

  2. Determine the account owner".

     "properties": {
     	"owner": "acct:123456890",
     },
    
  3. Read Bob's private claims from JRD "properties".


DISCUSS

  • KYC Registration
  • Sender and Receiver KYC Gating
  • Opaque KYC Attestation

Quoted Bridge Payments Gated Hosted Wallets(pre_tx gating by tag or inv_id) Gated Withdrawals (pre_tx gating by tag or inv_id)

Unquoted Bridge Payments Hosted Wallets (post_tx gating by tag or sending address) Direct withdrawals (post_tx gating by tag or sending address)

Gateway Account Creation and Login

1.1. Requirements Notation and Conventions

http://openid.net/specs/openid-connect-core-1_0.html#rnc

1.2. Terminology

http://openid.net/specs/openid-connect-core-1_0.html#Terminology

1.3. Overview

The Ripple Service Layer implements a distributed identity resolution. Rather then maintaing different user/pass combinations at each gateway, or relying on a centralized server, gateways relys on Ripple clients implementing a "self-issuing" OpenID identity provider.

This allows each end-user to maintain their own user profile and control submission of their own personally identifying (KYC) information. It also gives each end-user a single sign-on process that works with both with their Ripple client and any Ripple gateway.

The OpenID Connect "self-issued" protocol, in abstract, follows the following steps.

The RP (Client) sends a request to the OpenID Provider (OP). The OP authenticates the End-User and obtains authorization. The OP responds with an ID Token containing Claims about the End-User. These steps are illustrated in the following diagram:

+--------+                                   +--------+
|        |                                   |        |
|        |---------(1) AuthN Request-------->|        |
|        |                                   |        |
|        |  +--------+                       |        |
|        |  |        |                       |        |
|        |  |  End-  |<--(2) AuthN & AuthZ-->|        |
|        |  |  User  |                       |        |
|   RP   |  |        |                       |   OP   |
|        |  +--------+                       |        |
|        |                                   |        |
|        |<--------(3) AuthN Response--------|        |
|        |                                   |        |
+--------+                                   +--------+

http://openid.net/specs/openid-connect-core-1_0.html#Overview

2.0. User Tokens

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment