Digital cash in an asynchronous environment

-- Sun 12 December 2021

Foreward by Christine Lemmer-Webber: Jessica wrote this blogpost within the second week of her working full time as part of Spritely but it took me a while to review it and get it up here. While object-capability based banks are not new (Spritely Goblins ships with a simple example mint based on the Capability-based Financial Instruments paper), these systems have traditionally been shown as examples where we need support for local synchronous operations to keep things simple and understandable. With no prior background in the object capability security community or in previous cryptographic banking systems, Jessica quickly refuted this claim and came up with this example alternative purely-asynchronous object-capability-based bank design. Suffice to say, this is an astounding feat and yet another demonstration of how fortunate we feel to have Jessica join the Spritely team!

Many years ago, I was looking at Sweden's e-krona initiative to build digital cash and it got me thinking of the best way to implement a digital cash based system which has many of the qualities of cash, but with the rigorous safe-guards against printing money and double spending which you might image may come up when trying to make a digital alternative to cash.

The system I came up with has the following qualities:

  • The mint can be run by anyone, providing the parties using it accept the coins have value
  • The cash can be used without giving up your identity
  • Cash can be spent in a fashion which means the buyer no longer has the ability to re-spend the cash
  • New money cannot be created by an unauthorised 3rd party
  • Cash can be used in an asynchronous environment

I'll add that this is not trying to solve centralisation in the same way bitcoin is. I think Bitcoin has it's own set of requirements that aren't always needed and create problems of their own. This is not trying to be a Bitcoin solution.

The pebble mint

Lets quickly define the terms:

  • Mint: A place where coins are created

  • Pebble: In our system coins are called pebbles

So the mint has two main jobs in this system, which are:

  • Allow for new pebbles to be minted
  • Allow for valid coins to be exchanged for a newely minted coin, making the coin you exchanged invalid.

Now you might have twigged how this works by those jobs. First someone authorised created a bunch of coins and then those can be distributed though some means. The people who have a coin can then give this to someone new and they can exchange the coin at the mint for a new coin, invalidating the old coin they had access to meanwhile getting a new coin back from the mint.

So, here's a basic example of how the mint might look written using Spritely's Goblins, courtesy of Christine Lemmer-Webber:

(require goblins
         goblins/actor-lib/methods)

(define (spawn-pebble-mint-pair)
  (define (^pebble _bcom) (lambda () 'just-a-pebble))
  
  (define (^pebble-mint bcom active-pebbles)
    (methods
     [(mint-new-pebble)
      (define new-pebble (spawn ^pebble))
      ;; become a new version of ourself with the newly minted pebble
      ;; added, return the new pebble
      (bcom (^pebble-mint bcom
                          (set-add active-pebbles new-pebble))
            new-pebble)]
     [(exchange-pebble old-pebble)
      (unless (set-member? active-pebbles old-pebble)
        (error "Can't exchange, not an active pebble!"))
      (define new-pebble (spawn ^pebble))
      (define old-pebble-removed (set-remove active-pebbles old-pebble))
      (define new-pebble-added (set-add old-pebble-removed new-pebble))
      ;; become a new version of ourself with the old pebble exchanged
      ;; for the new pebble, and return new pebble
      (bcom (^pebble-mint bcom new-pebble-added)
            new-pebble)]))

  (define the-mint (spawn ^pebble-mint (seteq)))

  (define ((^pebble-minter _bcom))
    (<- the-mint 'mint-new-pebble))
  (define ((^pebble-exchanger _bcom) old-pebble)
    (<- the-mint 'exchange-pebble old-pebble))
  (define pebble-minter (spawn ^pebble-minter))
  (define pebble-exchanger (spawn ^pebble-exchanger))

  (values pebble-minter pebble-exchanger))

Unfortunately this blog would be exceptionally long if I bootstrap a racket and Goblins tutorial, however there's a basic Goblins tutorial here which will get you up to speed with enough to understand the above.

The function spawn-pebble-mint-pair when called provides two values: a pebble-minter and a pebble-exchange. These are actors in the Goblins system that interface on your behalf with pebble-mint. The minter should be held secret and anyone with access to that function is able to mint new coins, the exchanger should be given to anyone who uses the system.

The pebble-mint has two methods, one being mint-new-pebble which takes no arguments and gives you a pebble and exchange-pebble which takes one pebble and gives you back a new pebble. So, what is a pebble?

(define (^pebble _bcom) (lambda () 'just-a-pebble))

It's one of these which is just an actor that returns 'just-a-pebble. In racket the equality operator eq? will not show two different pebbles as being equal. There is nothing special about the pebble, you just need to be able to discern different pebbles from one another.

The mint itself holds a set called active-pebbles, the contents of which contain all currently valid pebbles. The way exchange-pebble works is:

  1. Check if the given pebble is in active-pebbles, if not, throw an error
  2. Make a new pebble
  3. Make a new set based on active-pebbles with the one we were given removed
  4. Make a new set based on the one we made in step 3, with the new pebble we minted added
  5. Become (that's the bcom thing) a new version of ourselves (the mint) with active-pebble set being the set we made in step 4.

Note that in Goblins it's transactional, either we became a new mint with the new pebble or we didn't. There's no middle ground. If you're wondering now if you've found a double-spend issue where you could call exchange-pebble twice with the same-pebble at the same time and it'd succeed in giving you two pebbles, it wouldn't.

Actors in Goblins work in an event-loop called a vat, each loop (or turn) processes one message and so one of your messages (calls) to the exchange would be processed, doing the above and making a new version of the mint with your new coin and then the second message would be handled and because the coin is no longer valid in the new mint, it would error.

Another important detail is the lack of a is-valid? method. You'd think it'd be handy to check if a pebble is valid, without invalidating it and having to get a new one. This is purposefully not done, you have to validate a coin by exchanging to avoid a nasty race condition.

Spending our pebbles

So what's the use of pebbles/money without the ability to spend them on stuff? How does that look in our system? Well first let's go through a little bootstrap which I've explained in the comments in the code, however it's not important to understand.

;; This is for the define-vat-run stuff.
(require goblins/actor-lib/bootstrap)

;; Make the event-loop for our actors (mint, etc.)
(define-vat-run mint-run (make-vat))

;; Create the pair
(define-values (pebble-minter pebble-exchange)
  (mint-run (spawn-pebble-mint-pair)))

I've just made the one vat here, but it's very important to keep in mind you could have these working on totally different machines around the world just passing messages between each other. If you introduce a system which collects messages and forwards them when a machine is online (a store-and-forward system) then this could even work while all machines aren't even online at the same time.

So here is making a pebble and exchanging it for another and then trying to spend the same coin a second time:

;; Lets make a pebble
(mint-run
 (on (<- pebble-minter)
     (lambda (pebble1)
       ;; We've made a brand new pebble `pebble1`
       (on (<- pebble-exchange pebble1)
           (lambda (pebble2)
             ;; This has given us a new pebble `pebble2`,
             ;; `pebble1` now worthless.
             (on (<- pebble-exchange pebble1)
                 #:catch
                 (lambda (err)
                   ;; We'll get here because we tried to exchange
                   ;; the worthless spent `pebble1`
                   (displayln "Oh right, we already spent pebble1"))))))))

Finally let's think about how this system would work in practice. Imagine we go into a coffee shop to buy two coffees, one for me, the other for you. Lets assume both us and the coffee shop have money from an agreed upon mint.

[we walk into the coffee shop]

Me: Hello, could I get two coffees please?

Coffee shop: Sure! That'll be 6 pebbles.

Me: [sends 6 pebbles to the coffee shop system]

Coffee shop: [asks the mint to exchange all 6 pebbles for new ones]

Mint: [Performs the exchanges and all 6 pebbles are valid so handing back 6 new pebbles]

Coffee shop: Great, here are your two coffees.

We: [Takes our respective coffees and thank the member of staff]

So lets look at what happened here, it's very similar to handing over cash except that instead of the coffee shop checking the security markers on the bill and hoping we're not master forgers, the mint has instead minted new coins and given the coffee shop 6 new pebbles. What this means is those 6 pebbles I had are useless and I never got to see the new pebbles which were minted based on my old one, that happened between the coffee shop and mint.

We could also take this further, maybe we agreed I'd pay and you'd give me your half of the money later in which case you could send me 3 coins and I could ask the mint to mint me 3 new ones.

Use cases

This is a good system for a lot of uses where digital money comes into place. It could indeed be used in the traditional sense of a nation state setting up a mint. I think the place where this system shine is places like games or virtual worlds which have their own ecconomies and monetary systems.

In the above system a pebble has a fixed value, 1 pebble == 1 pebble. This is great and keeps the system quite simple to understand although it would be relatively trivial to add arbitrary value to each pebble. I think outside the scope of this post, the general gist is that pebbles have some value which cannot be changed by the pebble holder, only the mint. And that you would then introduce a third method to the mint called split which takes in the old pebble and a value (which is less than the value of the old pebble) and it gives you two pebbles back, one of the value you passed in and one of the remainder that was left.