Tezos applications: automated insurance

January 12, 2018
cryptocurrency tezos michelson blockchain smart contracts applications oracles golang

Introduction

Smart contracts can be applied to a wide range of industries and products, however there are a few areas that are very well suited to enhancement and automation using the blockchain technology. One of those areas is insurance. A lot of work can be automated and smart contracts can even enable completely new product classes to emerge, such as micro insurance.

Let’s consider the most basic reason for the demand for insurance - loss aversion. People hate disappointment and losing something that they had even more than not having it in the first place. Many people face disappointment on a daily basis, e.g. when they have to cancel a trip or a barbecue because of unsuitable weather.

In the previous article, we have used the Tezos smart contract language called Michelson, to build an oracle that provides weather information to other contracts. Today, we are going to create an automated micro insurance provider, that will insure against rainy weather.


System design

How could we design such a system?

What are the properties that we want the system to have? We would like the resolution to be done on the blockchain, so that our customers can be sure that their policies will be evaluated fairly and instantly. However we would also like our pricing logic to be hidden, so it doesn’t get immediately copied by competition.

To accomplish this, we are going to have one server that gives out quotes through a REST API. A provider contract on the blockchain is going to accept the quotes to register a new policy. After the moment specified in the policy has passed, the contract can be called again to request weather information for that moment from a weather oracle. Then it will evaluate the policy and pay out the agreed amount by creating a new account with the money, for which the user owns the private key.

Among other benefits, this allows us to easily swap out the pricing logic without any changes to the contract. This is critical, because changes to an existing smart contract are impossible, whereas the pricing logic needs to be constantly improved and tweaked, to respond to new events and market pressures.


Oracle improvements

We have made a few improvements to the oracle:

Note that in a real world scenario, the oracle would likely be operated by a different party, and making such changes would be much harder.


The server

The quote server exposes a single endpoint, that expects a request with desired parameters – location coordinates, a unix timestamp in the future, desired payout and the public key for which the caller knows the corresponding private key. The server then calls the Dark Sky API, calculates a suitable price for the insurance policy, and gives out a quote, along with the data that the caller should include as an argument to a transaction. An expiration time is included and the whole thing is signed, to ensure the insurance contract can distinguish bona fide transactions (and throw away the rest).

type PolicyQuote struct {
    Arg string `json:"arg"`
    PolicyParams
}

type PolicyParams struct {
    Key        string  `json:"key"`
    Payout     float64 `json:"payout"`
    Price      float64 `json:"price"`
    Expiration int64   `json:"expiration"`
    T          int64   `json:"t"`
    Latitude   float64 `json:"lat"`
    Longitude  float64 `json:"lon"`
    req        darksky.ForecastRequest
}

The main function simply starts the server:

func main() {
    flag.Parse()
    client = darksky.New(*apiKey)

    router := httprouter.New()
    router.GET("/quote", getQuote)

    fmt.Println("Listening for policy requests.")
    log.Fatal(http.ListenAndServe(":11337", router))
}

First, we have to parse the request:

func parseRequest(values url.Values) (PolicyParams, error) {
    var p PolicyParams
    for param, value := range values {
        if len(value) > 1 {
            //expected a single value
            return p, fmt.Errorf("invalid query params")
        }
        switch param {
        case "lat", "lon":
            l, err := strconv.ParseFloat(value[0], 64)
            if err != nil {
                return p, fmt.Errorf("converting location: %v", err)
            }
            l = roundPlaces(l, 4)
            if param == "lat" {
                p.Latitude = l
                p.req.Latitude = darksky.Measurement(l)
            } else if param == "lon" {
                p.Longitude = l
                p.req.Longitude = darksky.Measurement(l)
            }
        case "time":
            ts, err := strconv.ParseInt(value[0], 10, 64)
            if err != nil {
                return p, fmt.Errorf("converting time: %v", err)
            }
            t := time.Unix(ts, 0)
            cutoff := time.Now().Add(time.Minute * 30)
            if cutoff.After(t) {
                //can't insure less then 30 minutes before requested policy time
                return p, fmt.Errorf("requested time too soon")
            }
            p.T = ts
            p.req.Time = darksky.Timestamp(ts)
        case "key":
            p.Key = value[0]
        case "payout":
            payout, err := strconv.ParseFloat(value[0], 64)
            if err != nil {
                return p, fmt.Errorf("converting payout: %v", err)
            }
            p.Payout = ceilTwoPlaces(payout)
        }
    }
    return p, nil
}

Then we get weather information from Dark Sky:

func getProbability(client darksky.DarkSky, request darksky.ForecastRequest) (float64, error) {
    //use metric units
    request.Options.Units = "si"
    resp, err := client.Forecast(request)
    if err != nil {
        return 0, err
    }
    hourly := resp.Hourly.Data
    var d darksky.DataPoint
    for _, h := range hourly {
        if request.Time < h.Time {
            break
        }
        d = h
    }
    prob := float64(d.PrecipProbability)
    return prob, nil
}

Everything comes together in the handler function:

func getQuote(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    var p PolicyParams
    p, err := parseRequest(r.URL.Query())
    if err != nil {
        http.Error(w, fmt.Sprintf("400 %v", err), http.StatusBadRequest)
        return
    }
    prob, err := getProbability(client, p.req)
    if err != nil {
        http.Error(w, fmt.Sprintf("400 %v", err), http.StatusBadRequest)
        return
    }

    //tez values can only have 2 decimal places at the moment
    //always round up so that we don't accidentaly lose money
    p.Price = ceilTwoPlaces(float64((1 + margin) * p.Payout * prob))

    //quote is valid for 15 minutes
    p.Expiration = time.Now().Add(time.Minute * 15).Unix()

    data := prepData(p)
    sig, err := signData(data, *identity)
    if err != nil {
        http.Error(w, "500 internal server error", http.StatusInternalServerError)
        return
    }

    var q PolicyQuote
    q.PolicyParams = p
    q.Arg = fmt.Sprintf(`(Right (Pair %s %s))`, sig, data)

    resp, err := json.Marshal(&q)
    if err != nil {
        http.Error(w, "500 internal server error", http.StatusInternalServerError)
        return
    }
    w.Header().Set(contentType, appJSON)
    w.Write(resp)
}

After calculating the price, we need to format the data so the client can just pass it as an argument to a transaction.

func prepData(p PolicyParams) string {
    tsExp := time.Unix(p.Expiration, 0).UTC().Format(time.RFC3339)
    tsT := time.Unix(p.T, 0).UTC().Format(time.RFC3339)
    tzPayout := formatTez(p.Payout)
    tzPrice := formatTez(p.Price)
    return fmt.Sprintf(`(Pair "%s" (Pair (Pair (Pair "%s" "%s") (Pair (Pair %d %d) "%s")) "%s"))`, tsExp, tzPayout, tzPrice, int(p.Latitude*10000), int(p.Longitude*10000), tsT, p.Key)
}

The UTC() method changes the timezone to UTC, so that the output of Format() doesn’t depend on the timezone of the server. The smart contract converts the time to UTC and signatures wouldn’t match if we used the time of a local timezone here instead.

We sign the resulting quote:

func signData(data string, identity string) (string, error) {
    c := exec.Command("./alphanet.sh", "client", "hash", "and", "sign", "data", data, "for", identity)
    c.Env = append(c.Env, "ALPHANET_EMACS=true")
    b, err := c.CombinedOutput()
    if err != nil {
        fmt.Println("error:", string(b))
        return "", fmt.Errorf("running commands to sign: %v", err)
    }
    lines := strings.Split(string(b), "\n")
    for _, l := range lines {
        l := strings.TrimSpace(l)
        if strings.HasPrefix(l, "Signature:") {
            words := strings.Split(l, " ")
            signature := strings.Trim(words[1], "")
            return signature, nil
        }
    }
    return "", fmt.Errorf("parsing output: signature not found")
}

Finally the handler sends the signed data back to the client, who can then accept the offer by calling the smart contract with the signed data as an argument.


The contracts

This section will be a bit technical. I suggest you read it casually, and then use emacs with the Michelson mode to get deeper understanding of how the contracts work and what each instruction does. You can follow this guide by Kate Sills to set up emacs and the Michelson mode. Both the server and the contract code is available in my git.

Most of the logic will be inside the main contract, its parameter looks like this:

parameter (or
             (or
                (pair @resolve string int)
                (or
                   (string @trigger)
                   (pair @init signature (contract (pair signature (pair string int)) unit))))
             (pair @register
                signature
                (pair
                   timestamp
                   (pair
                      (pair
                         (pair @terms
                            (tez @payout)
                            (tez @price))
                         (pair @params
                            (pair @location
                               int
                               int)
                            timestamp))
                      key))));

As can be gleaned from the parameter type, the contract has 4 different paths through the code. One of them is just for initialization and will be explained later. After the contract is initialized, there are three different kinds of transactions it can accept. The words starting with @ are annotations, and don’t have any effect – they are just comments that make reading the code more convenient for us. If you use emacs with the Michelson mode, you’ll also see the annotations in the stack state visualizations.

A diagram to help you think about what each part of the input is used for:

              Left                     Right: register
            /      \
           /        \
Left: resolve          Right
                      /     \
                     /       \
   Left: trigger evaluation    Right: init

The rest of the contract header:

storage (pair
           (pair
              (pair
                 (key @provider_key)
                 (bool @initialized))
              (pair @contracts
                 (contract @oracle
                    (or
                       (pair
                          signature
                          string)
                       (pair
                          (pair
                             string
                             (contract (pair
                                          signature
                                          (pair
                                             string
                                             int))
                                       unit))
                           (pair
                              (pair
                                 int
                                 int)
                              timestamp)))
                    (option string))
                 (contract @redirector
                    (pair
                       signature
                       (pair
                          string
                          int))
                    unit)))
           (map
              string
              (pair
                 (pair
                    (pair @terms
                       (tez @payout)
                       (tez @price))
                    (pair @params
                       (pair
                          int
                          int)
                       timestamp))
                 (bool key))));
return (option string);

The contract stores a map of insurance policies (a hash table), the public key of the provider identity (to verify authenticity of requests) and a flag that indicates whether the contract has been properly initialized. It also stores addresses of the oracle contract and the redirector contract. A string hash wrapped inside an option is returned when a new request is accepted, so that the user can trigger evaluation later on. The other paths return a NONE, indicating the option type is empty.

Let’s start with handling new requests. Policies are stored in a hash table, so that we can access, create or remove them efficiently. If a request with a valid quote is received, a policy is created and added to the table. In case the policy already exists, the transaction fails. The policy details are stored in a format that facilitates easy communication with the oracle contract. This requires some manipulation of the data at the beginning, but it helps keep the evaluation code simple.

          { #register for a policy
            DUP;
            DUP;
            CAR;
            DIP{CDR;H;};
            PAIR;
            DUUUP;
            CDAAAR;
            CHECK_SIGNATURE;
            ASSERT; # fail if not authentic
            ...

Using the instructions H(stands for hash) and CHECK_SIGNATURE, we can make sure that the request was generated by our quote server (assuming no one else has the private key). CHECK_SIGNATURE outputs a boolean and the ASSERT instruction is a shorthand for IF{}{FAIL};, which simply fails if the boolean at the top of the stack is false.

            CDR;
            DUP;
            CAR;
            NOW;
            ASSERT_CMPLE; # fail if terms are out of date
            ...

In a similar fashion, CMPLE is a shorthand for COMPARE, which produces an integer from two numbers, and LE (“less or even”), which takes and integer and produces a boolean. There are other variations for “greater or even”, “greater than”, “less than” and so on. NOW lets us use the timestamp of the block that contains the current transaction.

            DUP;
            CDAADR;
            AMOUNT;
            ASSERT_CMPGE; # fail if incoming tx amount is lower than quoted price
            ...

AMOUNT will give us the amount of tez of the current transaction.

            DIP{CDR;};
            CDR; # generate hash the same way the oracle does, use as a key
            DUP;
            CDR;
            H;
            DUUUP;
            CADDR;
            SWAP;
            PAIR;
            DIP{ DUP;
                 CADR;
               };
            PAIR;
            H;
            DUP;
            DIIP{ MAP_CDR{ PUSH bool False;
                           PAIR;
                         };
                  SOME;
                  DIP{ DUP;
                       CDR;
                     };
                };
            DUUUUP;DUUP;MEM; # check if policy exists
            NOT;ASSERT; # fail if policy already exists
            DIP{ UPDATE;
                 SWAP;
                 MAP_CDR{ DROP;
                          SWAP;
                        };
               };
            SOME; # return the resulting hash, so user can trigger evaluation
          };

For convenient manipulation of deeper elements of the stack, we can use DIIP, where each I means we are working deeper in the stack – DIP means we dip into the stack 1 level, and DIIP means we dip 2 levels (so we can work, while the top two elements remain unchanged). Similarly, DUUP duplicates the element below the top element. CAR and CDR can work with nested pairs by including more As or Ds in the middle.

The contract relies on users to call and trigger evaluation of their policy at the appropriate time. The evaluation branch uses a policy hash to retrieve the details, and calls the oracle contract.

IF_LEFT{ #trigger evaluation of policy specified by hash
         DIP{CDR};
         DUUP;
         CDR;
         DUUP;
         GET;
         ASSERT_SOME;
         DUP;
         DUP;
         CDAR;
         NOT;
         ASSERT; # fail if already triggered
         CADR;
         DUUP;
         CDDR;
         H;
         DIP{ DUUUUP;
              CADDR;
            };
         PAIR;
         PAIR;
         RIGHT (pair signature string);
         DIP{ #flip triggered flag for this policy
              MAP_CDAR{NOT;};
              SOME;
              SWAP;
              DIIP{ DUP;
                    CDR;
                  };
              UPDATE;
              SWAP;
              MAP_CDR{ DROP;
                       SWAP;
                     };
              # call oracle
              DUP;
              CADAR;
              PUSH tez "1.00";
            };
         TRANSFER_TOKENS;
       }

When retrieving items from a map, they are wrapped in an option. If no item is found, it’s NONE, otherwise it’s SOME. We can use ASSERT_SOME to automatically fail in case there is no policy with the specified hash. We pack the value into an appropriate or type using the RIGHT instruction (since the compiler knows the type of the data on stack, we only have to specify the type of the other part).

The oracle contract then retrieves information about the state of the outside world using Dark Sky, as explained in the previous article. Then the oracle server sends the information to the address specified in the request. Usually, different users will be using the oracle, and their parameter types will differ as well. We can solve this by using a redirector contract that simply takes the information, wraps it in appropriate types and sends it to the main contract.

parameter (pair signature (pair string int));
storage (pair (key @oracle_key) (contract @provider (or (or (pair string int) (or (string @trigger_by_hash) (pair @init signature (contract (pair signature (pair string int)) unit)))) (pair signature (pair timestamp (pair (pair (pair @terms tez tez) (pair @params (pair @location int int) timestamp)) key)))) (option string)));
return unit;
code { DUP;
       CAR;
       DUP;
       CAR;
       DIP{ CDR;
            H;
          };
       PAIR;
       DUUP;
       CDAR;
       CHECK_SIGNATURE;
       ASSERT;
       DUP;
       CDDR;
       PUSH tez "0.00";
       DUUUP;
       CADR;
       LEFT (or (string @trigger) (pair @init signature (contract (pair signature (pair string int)) unit)));
       LEFT (pair @register signature (pair timestamp (pair (pair (pair @terms tez tez) (pair @params (pair @location int int) timestamp)) key)));
       DIIIP{CDR};
       TRANSFER_TOKENS;
       DROP;
       UNIT;
       PAIR;
     }

The redirector contract is very straightforward and just checks whether the transaction is signed by the oracle’s key. Then it wraps the data and calls the resolve path of the provider contract. Any other transactions are dropped.

The resolve path takes a hash and rain probability from the oracle and retrieves the details of a policy associated with the hash. The probability is used to calculate the appropriate payout. Finally, a new default account (a contract with no code) is created and the amount to pay out (if any) is transferred to it. The user controls the private key paired with the public key that’s used to create the account, and can use the paid out tez however they like. By using a default account we make sure that it’s safe to transfer to. If we just transferred to a contract supplied by the user, we would have to take special care to avoid reentrancy vulnerabilities.

IF_LEFT{ #(pair string int)
         SOURCE (pair string int) unit;
         H;
         DUUUP;
         CDADDR;
         H;
         ASSERT_CMPEQ; # fail if not coming from redirector
         ...

The SOURCE instruction is used to work with the contract from which the current transaction was sent. Here, we are using it to make sure the transaction comes from our redirector contract.

         #retrieve terms from map, multiply, create default account based on specified key, with appropriate balance
         DUUP;
         CDDR;
         DUUP;
         CAR;
         GET;
         ASSERT_SOME;
         DUP;
         DUP;
         CDAR;
         ASSERT; # fail if not triggered
         ...

GET lets us retrieve elements from a map.

         DIP{ CAAR;
              CAR;
              DIP{ DUP;
                   CDR;
                   DUP;
                   PUSH int 0;
                   ASSERT_CMPLE;
                   ABS;
                 };
              MUL;
              PUSH nat 100; # result value is multiplied by 100 so we can have 2 decimal places, now we divide by 100
              SWAP;
              EDIV;
              ASSERT_SOME; # EDIV returns NONE in case of division by zero
              CAR; # throw away the remainder
            };
         CDDR;
         HASH_KEY;
         DEFAULT_ACCOUNT;
         SWAP;
         UNIT;
         DIIIP{ DUUP;
                CDDR;
                SWAP;
                CAR;
                DIP{NONE (pair (pair (pair @terms tez tez) (pair @params (pair int int) timestamp)) (pair bool key));};
                UPDATE;
                SWAP;
                MAP_CDDR{ DROP;
                          SWAP;
                        };
                CDR;
              };
         TRANSFER_TOKENS;
         DROP;
         NONE string;
       }

HASH_KEY and DEFAULT_ACCOUNT let us create a contract with no code, so that we can send the payout to it safely (note that we can reuse the same address and the money will simply be added to the balance). After calculating the payout, we remove the resolved policy from the contract’s storage with UPDATE. We could first transfer the payout and remove the policy afterwards, but remember that we need to clear the stack and save anything important to storage before calling TRANSFER_TOKENS, so it would be tedious.

The initialization path is used when setting up the provider contract. The provider needs to contain the address of a redirector contract, while the redirector needs to contain the address of the provider contract. Obviously, one of them needs to be originated first, so we use a placeholder redirector when originating the provider. Then we can originate the redirector, and call the provider to change the address. To make sure only we can make these changes, the transaction is signed. We don’t need to use a counter, because the initialization can only be done once and the effects are not reversible. Subsequent initialization attemps, such as would result from a replay attack are simply dropped.

code { DUP;
       CDAADR;
       IF{}
         { DUP;
           CAR;
           ASSERT_LEFT;
           ASSERT_RIGHT;
           ASSERT_RIGHT;
           DUP;
           CAR;
           DIP{ DUP;
                CDR;
                H;
              };
           PAIR;
           DUUUP;
           CDAAAR;
           CHECK_SIGNATURE;
           ASSERT;
           # initialize the redirector contract address
           CDR;
           SWAP;
           MAP_CDADDR{ DROP;
                       SWAP;
                     };
           # the initialized flag is flipped below
         };

MAP_CAR lets us change an element of a pair conveniently, without having to break up the pair and compose it again. We can use it on nested pairs too. I’m explaining this piece of code last, but it’s actually executed before the contract does anything else. By checking the initialization flag first, we don’t have to worry about it later, and the rest of the code can be simpler.

...
{ IF_LEFT{ #trigger evaluation of policy specified by hash
           ...
         }
         { # init done above already, here we just flip the initialization flag
           # contract can only be initialized once, fail if already initialized
           DROP;
           CDR;
           MAP_CAADR{NOT;ASSERT;PUSH bool True;};
           NONE string;
         };

In the if branch belonging to init, we change the initialization flag if it’s false, or fail if the contract has already been initialized.

Take at a look at the whole contract here.

Let’s see what replay attacks do to the other paths. Attacking the registration path would simply make the transaction fail, as we don’t allow multiple policies for same combination of location, time and payout address. Attacking the trigger path wouldn’t do anything, since there is a boolean flag indicating that the policy has already been triggered. A duplicate transaction from the redirector on the resolve path would simply fail to retrieve any policy, while a transaction from anyone else would be dropped right away.

A good primer to thinking about smart contract security in Tezos is the list of Michelson anti-patterns by Milo Davis.


Usability

In its current form, the system is quite rough around the edges and requires command line interaction to use. The user needs to know how to communicate with servers, how to compose Tezos transactions and how to use the Tezos command line client. This would ideally be handled by an application that hides all the complexity from the end-user, remembers when to trigger policy evaluation, and can cooperate with an external wallet. Building such application is, however, out of scope of this article.

Once cryptocurrencies are widely adopted, one could imagine that micro-insurance products would be offered by a number of providers, and the end-user would have an application select the best offer automatically, along with restaurant reservations (when planning a barbecue outside) or train tickets (when planning a trip). Everyone would simply set their preferred degree of risk exposure, and their virtual assistent would buy a corresponding insurance policy any time they plan an activity. Then, come sudden rain, the disappointed would-be hiker would have a back-up plan ready, in the form of movie tickets or another indoor activity and a nice pile of money to pay for them.


Closing words

This article is already too long, so I’ll wrap it up here. Next time, we’ll have an interactive command line demo that shows how the system can be set up, and explains each step. If you enjoyed the article, share it with people who might be interested :)

If you are interested in building things with Tezos, check out Tezos.help for a list of resources and come to the developer chat room on matrix.

Getting started with Liquidity: coinflip

May 27, 2018
easy liquidity cryptocurrency tezos michelson blockchain smart contracts

Tezos changes: zeronet update

April 15, 2018
cryptocurrency tezos michelson blockchain smart contracts applications oracles

Tezos tools: automated contract set up

March 21, 2018
automation cryptocurrency tezos michelson blockchain smart contracts applications oracles golang