A generic “Claim and Endorse” Contract

Did you already endorse someone at LinkedIn? For instance, someone claims that he knows C++ and you endorse this claim because you know it’s true.

A large number of processes can be modelled in this way:

A simple Solidity contract for managing claims and endorsements could look like below.

contract ClaimAndEndorse {
struct ENDORSEMENT {
uint creationTime;
}

struct CLAIM {
uint creationTime;
uint claimHash;
mapping (address => ENDORSEMENT) endorsements;
}

mapping (address =>
mapping (uint /* CLAIM GUID */ => CLAIM)) claims;

function setClaim(uint claimGuid, uint claimHash) {
CLAIM c = claims[msg.sender][claimGuid];
if(c.claimHash > 0) throw; // unset first!
c.creationTime = now;
c.claimHash = claimHash;
}

function unsetClaim(uint claimGuid) {
delete claims[msg.sender][claimGuid];
}

function setEndorsement(
address claimer, uint claimGuid, uint expectedClaimHash
) {
CLAIM c = claims[claimer][claimGuid];
if(c.claimHash != expectedClaimHash) throw;
ENDORSEMENT e = c.endorsements[msg.sender];
e.creationTime = now;
}

function unsetEndorsement(address claimer, uint claimGuid) {
delete claims[claimer][claimGuid]
.endorsements[msg.sender];
}

function checkClaim(
address claimer, uint claimGuid, uint expectedClaimHash
) constant returns (bool) {
return claims[claimer][claimGuid].claimHash
== expectedClaimHash;
}

function checkEndorsement(
address claimer, uint claimGuid, address endorsedBy
) constant returns (bool) {
return claims[claimer][claimGuid]
.endorsements[endorsedBy].creationTime > 0;
}
}

The fantastic thing about this very simple contract is that we now can answer the following question:

Who claims what and who endorses it?

Usecase – Skill/Degree Endorsements

John claims that he holds a PhD in computer science at the Stanford university.

contract.setClaim(
3000010 /* GUID for PhD in CS */,
HASH("PhD in Computer Science at Stanford University"))

The Stanford university confirms this fact.

contract.setEndorsement(JOHN, 3000010)
Usecase – Identity Verification

Step 1. John claims facts about his personal data by binding hashes to his ethereum address. The corresponding pseudo code is:

// RND is a random sequence introduced for making it impossible to restore the initial data from the hash by brute force lookups.

contract.setClaim(1000010 /*guid firstname*/,
HASH("John"+RND))

contract.setClaim(1000011 /*guid surename */,
HASH("Smith"+RND))

contract.setClaim(1000012 /*guid bday */,
HASH("1965-01-01"+RND))

contract.setClaim(1000013 /*gender */,
HASH("m"+RND"))

Step 2. John visits his bank, which endorses these facts about his identity:

contract.setEndorsement(JOHN, 1000010 /*guid firstname*/);
contract.setEndorsement(JOHN, 1000011 /*guid surename */);
contract.setEndorsement(JOHN, 1000012 /*guid bday */);
contract.setEndorsement(JOHN, 1000013 /*guid gender */);

Now consider that JOHN wants to open A) an  account at ACME Inc, B) buy alcohol in the bar and C) register at a dating site. If all three trust John’s bank, he’ll be able to digitally prove his claims on his personal data. Moreover, he only needs to present the relevant pieces of data. For instance at the bar, he only has to prove the claim that he’s older than 18.

Related: ShoCard, uPort 

Usecase – Approving existence of documents

ACME Inc. wants to publish a new financial product. The hashes of the required documents are stored on the blockchain. The authorities and the exchanges are confirming the existence and the correctness of these documents.

Related: Luxembourg Stock Exchange OAM

Usecase – Managing Memberships

John wants to become a member in his local bowling club. He stores this fact on the blockchain and the club confirms this fact. With the first step, John manifests his will to enter the club. In the second step, the club confirms that they are accepting John as a member.

Try it yourself on Ropsten Testnet

https://testnet.etherscan.io/address/0x65ec6e00e336a96972987ee25386a3090f38a27d#code

 

KW 47 – Weekly Blockchainers Recap

There are lot of news in the BC universe and it’s not easy to achieve a balance between repeating everything and doing nothing. Our target is to log things that we found interesting. Here we go:

One news is overdue: the R3 (what ever it is) is coming down to earth. The first members are leaving the ship. Nobody knows if it is sinking or just pivoting. Goldman and Santander will leave the R3 consortium. If you follow the news, you can also find a lot of articles about their own BC activities. It seems that they didn’t see a benefit of being a part of this consortium. R3 also announced their own open source strategy, so they appreciate that open source is the key for building stable, transparent and secure BC solutions. 

JPM delivers the promissed open source code for Quorom, an Ethereum based blockchain implementation with support for permissioned networks, privacy and a faster consensus algorithm able of processing dozens to hundreds transactions per second. Let us hope that JPM’s  activities will help the Ethereum community to grow faster and become more robust. 

Siemens is also waking up. They started a collaboration with the LO3 startup for a decentralized energy micro trading platform. 

Rebuilding Ripple on Ethereum

Ripple is a P2P payment network with an integrated foreign exchange market. It supports any possible currency. In it’s core it is based on a public distributed ledger containing liabilities between individuals and organisations (IOUs). The network depends on the trust relations between its members. Transferring a value within the network between A and B requires a direct or indirect path in this web of trust. Moreover, the ledger  contains a distributed foreign exchange market, which makes it possible to convert between currencies in real-time.

In this blog post, I want to sketch how a Ripple-like implementation could look like in Ethereum.

Asset Contract

First of all we need a contract to represent an asset that network participants can agree on. This could be a fiat or a crypto currency, but it also could be bonus miles, loyalty points or similar.

contract Asset {
string public description;
string public id;
uint public decimalUnits;

mapping (address => bool) public accepted;

function Asset(string _description, string _id, uint _decimalUnits) {
description = _description;
id = _id;
decimalUnits = _decimalUnits;
}

function accept() {
accepted[msg.sender] = true;
}

function reject() {
delete accepted[msg.sender];
}
}

An asset has a description, an id, and how many decimal units are used. For instance, we would model US Dollar and European Euro as:

Asset USD = new Asset("USD Currency", "USD", 2)
Asset EUR = new Asset("EUR Currency", "EUR", 2)

With the accept function, network participants are agreeing upon a specific Asset instance. Network participants can only use assets that they have accepted.

EthRipple Contract

data model

After defining the Asset contract, we can now specify the data model for the EthRipple contract itself. We’ll need the following model elements:

ACCOUNT

Every participant in the network needs an ACCOUNT struct storing his ASSETs. The assets are identified with their contract addresses.

struct ACCOUNT {
mapping (address /* of an Asset */ => ASSET) assets;
}

mapping (address => ACCOUNT) accounts;
ASSET

An ASSET consists of all IOUs that a participant holds and of all his asset exchange offers (XCHG).

struct ASSET {
mapping (address => IOU) ious;
mapping (address /* of an target Asset */ => XCHG) xchgs;
}

xchgs – Offers for exchanging this asset for another asset.
ious – list of debtors for this asset.

IOU – “I owe you”
struct IOU {
uint amountOwed;
uint maxAllowed;
}

The IOU struct describes how much of a specific asset (e.g. USD) a debtor owes to the lender (amountOwed). Moreover, it describes how much a lender trusts that a potential debtor is going to pay him back (maxAllowed). During a transfer, amountOwed will always be less than or equal to maxAllowed.

E.g.:

IOU iou = accounts[JOHN].assets[EUR].ious[ANDY];
iou.maxAllowed = 100;
iou.amountOwed = 10;

iou.maxAllowed = 100 – JOHN trusts ANDY that he’ll pay his debts up to 100 units of the EUR asset.

iou.amountOwed = 10 – currently ANDY owes to JOHN 10 units of the EUR asset.

XCHG – Asset Exchange
struct XCHG {
uint validUntil;
uint exchangeRateInMillionth;
}

This struct represents the offer to exchange an Asset for another Asset at a specific exchangeRate which is equal to exchangeRateInMillionth/1,000,000. Note that here we have to work with unsigned integers since Ethereum’s Solidity Compiler has no support for decimals yet. validUntil is used to limit an offer to a specific period of time.

E.g.

XCHG xchg = accounts[JOHN].assets[EUR].xchgs[USD];
xchg.exchangeRateInMillionth = 1100000;

xchg.exchangeRateInMillionth = 1100000 – JOHN offers to exchange EUR for USD at a rate of 1100000/1000000 (1,10).

Operations

The minimal interface for the contract offers methods to modify IOUs and asset exchange offers. And finally, there is a ripple method for sending assets through the web of trust to a specific destination. Note that sending a value in this case means changing the IOU records along the path in the web of trust. If required, the sent asset can also be exchanged for another asset (e.g. converting EUR to USD).

 function modifyIOU(address debtor,
Asset asset,
uint newAmountOwed,
uint newMaxAllowed);

function modifyXCHG(Asset fromAsset,
Asset toAsset,
uint exchangeRateInMillionth,
uint validUntil);

function ripple(address[] chain,
Asset[] assetFlow,
uint amount);

function modifyIOU(address debtor, Asset asset, uint newAmountOwed, uint newMaxAllowed) – with this function the msg.sender can reduce the amount owed by a debtor or he can change the maxAllowed amount for this asset/debtor. The amountOwned can only be reduced, never increased.

function modifyXCHG(Asset fromAsset, Asset toAsset, uint exchangeRateInMillionth, uint validUntil) – with this function the msg.sender can publish new offers for converting fromAsset to toAsset at an exchangeRate which is exchangeRateInMillionth/1000000.

function ripple(address[] chain, Asset[] assetFlow, uint amount) – this function is the main workhorse. It allows the msg.sender to transfer an asset to a destination address, which is reachable within the web of trust that is encoded via IOU relations.

Considering the relations below, JOHN can send money to ALEX via ANDY.

// JOHN and ANDY trust each other that they'll be paying their debts up to 1000 units of the EUR asset.
accounts[JOHN].assets[EUR].ious[ANDY].maxAllowed = 1000;
accounts[ANDY].assets[EUR].ious[JOHN].maxAllowed = 1000;

// same for ANDY and ALEX
accounts[ANDY].assets[EUR].ious[ALEX].maxAllowed = 1000;
accounts[ALEX].assets[USD].ious[ALEX].maxAllowed = 1000;

If JOHN want to send 10 units of the EUR asset to ALEX, he would call the ripple function like this

ripple([JOHN, ANDY, ALEX], [EUR, EUR], 10) 

The second array parameter means that JOHN is transferring EUR to ANDY and that ANDY is also transferring  EUR to ALEX. There is no conversion between assets. After the transaction has been committed to the blockchain, we would see the following changes in the IOU records.

// JOHN and ANDY trust each other that they'll be paying their debts up to 1000 units of the EUR asset.
accounts[ANDY].assets[EUR].ious[JOHN].amountOwed = 10;
accounts[ALEX].assets[EUR].ious[ANDY].amountOwed = 10;

If JOHN wants to send EUR, but ALEX wants to receive USD, the transfer would work if ANDY would have an active asset exchange offer for exchanging EUR to USD. Moreover, there also has to be an established trust relation between ANDY and ALEX for the USD asset.

The function call would be:

ripple([JOHN, ANDY, ALEX], [EUR, USD], 10) 

Note that the path within the web of trust is calculated off-chain and passed as input to the ripple function. There is no need to do this expensive calculation on-chain.

Try it yourself

I deployed this contract on the Morden Testnet. Feel free to try it yourself.

Contract Addresses

EUR Asset 0x110c1b256c180ddBBFF384cA553Bf7683Ce8a02c
USD Asset 0xFa33639783B5ae93795A4aeCF86985eB95EA0B39
Ripple 0x33f03cea07586f42900fbf46df6a7f596345bec1

Asset Interface

[ { "constant": true, "inputs": [], "name": "decimalUnits", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [], "name": "accept", "outputs": [], "payable": false, "type": "function" }, { "constant": false, "inputs": [], "name": "reject", "outputs": [], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "description", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "id", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [ { "name": "a", "type": "address" } ], "name": "isAcceptedBy", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "type": "function" }, { "inputs": [ { "name": "_description", "type": "string" }, { "name": "_id", "type": "string" }, { "name": "_decimalUnits", "type": "uint256" } ], "type": "constructor" }, { "payable": false, "type": "fallback" } ] 

EthRipple Interface

[ { "constant": false, "inputs": [ { "name": "fromAsset", "type": "address" }, { "name": "toAsset", "type": "address" }, { "name": "exchangeRateInMillionth", "type": "uint256" }, { "name": "validUntil", "type": "uint256" } ], "name": "modifyXCHG", "outputs": [], "payable": false, "type": "function" }, { "constant": true, "inputs": [ { "name": "fxAddr", "type": "address" }, { "name": "fromAsset", "type": "address" }, { "name": "toAsset", "type": "address" } ], "name": "queryXCHG", "outputs": [ { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [ { "name": "fromAsset", "type": "address" }, { "name": "toAsset", "type": "address" } ], "name": "deleteXCHG", "outputs": [], "payable": false, "type": "function" }, { "constant": false, "inputs": [ { "name": "debitor", "type": "address" }, { "name": "asset", "type": "address" } ], "name": "deleteIOU", "outputs": [], "payable": false, "type": "function" }, { "constant": true, "inputs": [ { "name": "lender", "type": "address" }, { "name": "asset", "type": "address" }, { "name": "debitor", "type": "address" } ], "name": "queryIOU", "outputs": [ { "name": "", "type": "uint256" }, { "name": "", "type": "uint256" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [ { "name": "debitor", "type": "address" }, { "name": "asset", "type": "address" }, { "name": "newAmountOwed", "type": "uint256" }, { "name": "newMaxAllowed", "type": "uint256" } ], "name": "modifyIOU", "outputs": [], "payable": false, "type": "function" }, { "constant": false, "inputs": [ { "name": "chain", "type": "address[]" }, { "name": "assetFlow", "type": "address[]" }, { "name": "expectedExchangeRateInMillionth", "type": "uint256[]" }, { "name": "amount", "type": "uint256" } ], "name": "ripple", "outputs": [], "payable": false, "type": "function" }, { "inputs": [], "type": "constructor" }, { "payable": false, "type": "fallback" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "lender", "type": "address" }, { "indexed": false, "name": "debitor", "type": "address" }, { "indexed": false, "name": "asset", "type": "address" }, { "indexed": false, "name": "newCurrent", "type": "uint256" }, { "indexed": false, "name": "newMax", "type": "uint256" } ], "name": "EventUpdateIOU", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "lender", "type": "address" }, { "indexed": false, "name": "debitor", "type": "address" }, { "indexed": false, "name": "asset", "type": "address" } ], "name": "EventDeleteIOU", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "xchgAddr", "type": "address" }, { "indexed": false, "name": "fromAsset", "type": "address" }, { "indexed": false, "name": "toAsset", "type": "address" }, { "indexed": false, "name": "exchangeRateInMillionth", "type": "uint256" }, { "indexed": false, "name": "validUntil", "type": "uint256" } ], "name": "EventUpdateXCHG", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "xchgAddr", "type": "address" }, { "indexed": false, "name": "fromAsset", "type": "address" }, { "indexed": false, "name": "toAsset", "type": "address" } ], "name": "EventDeleteXCHG", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "xchgAddr", "type": "address" }, { "indexed": false, "name": "fromAsset", "type": "address" }, { "indexed": false, "name": "toAsset", "type": "address" }, { "indexed": false, "name": "exchangeRateInMillionth", "type": "uint256" } ], "name": "EventExecuteXCHG", "type": "event" } ]

 

Full Source Code