1. Research & development
  2. > Blog >
  3. Cortez security by using the Spending Limit contract

Cortez security by using the Spending Limit contract

cortez
12 September 2019
Nomadic Labs
share:

My wallet, my crypto

When holding a cryptocurrency, one needs a safe and secure wallet to store it. Wallets are used to store, receive, or spend cryptocurrencies. They work by keeping private keys to themselves and using them to sign transaction from the corresponding public keys.

A public key is used to send assets to the corresponding destination, whereas a private key is used to send assets from your corresponding wallet. Addresses are uniquely defined as a public function of the public key.

The most important component of a cryptocurrency wallet is the private key: if one loses it, then the corresponding tokens are no longer accessible. Cryptocurrency holders must take really good care of where and how they store their private keys. To make things easier, one can use a seed phrase, which is a randomly generated list of words used to deterministically generate a secret key. Therefore the seed phrase gives the ability to recover an account and the corresponding funds.

This is typically what cryptocurrency wallet software tends to do - they randomly generate seed phrases and ask users to write them down on a piece of paper. In the unfortunate event that something were to happen, like losing a phone or breaking a computer, a user can download the wallet software on a different device and use a seed phrase to recover the associated account.

Keep it safe, stupid*

If one would rather not leave their private key on a mobile wallet, a hardware wallet is a good alternative. This little gadget has no network capabilities, for obvious security reasons, and therefore must be used in conjunction with a computer/phone in order to transact.

Even so, some phones use the same level of security one can expect from a hardware wallet like a Ledger Nano S. Apple and its secure enclave and Samsung, which relies on ARM TrustZone technology, both provide hardware isolation (“hardware security modules”) to prevent malicious software from extracting private keys.

Peg-Leg Pete got me

You might feel safe using a private key, but what if someone like Peg-Leg Pete asks you for your cryptocurrency wallet in a way that you can’t refuse? What if he commands you to transfer all your cryptocurrencies to him, or else… How unfortunate it would be to lose everything despite taking so much care of keeping your private key safe!

pete

Peg-Leg Pete threatens Mickey © Disney

A similar situation with a credit card would not have been as critical. At most you would have lost what your daily limit allows for before calling your bank to file a report/chargeback.

Cortez proposal

The Secure enclave

A few words about the secure enclave on mobile devices.

Some smartphones (iOS/Android) possess special security chips (HSM) which are basically tiny computers themselves. One of these HSMs has its own operating system running on top of its own processor and memory. With this, an attacker will likely never be able to access the contents of this secure area even if he/she is able to compromise the main operating system of the phone.

When using Apple Pay, Google Pay and/or Samsung Pay, payment details are stored securely in an HSM to prevent access by malicious software.

Enclave

Secure enclave inside a mobile phone

Something really interesting about the Trusted World (referenced above) on mobile is that it allows users to generate cryptographic keys and encrypt/decrypt/sign/verify and so on, without private keys being exposed. Private keys stay in the secure enclave, the Trusted World.

Signing Tezos transactions with a key generated in a secure enclave would be perfect to avoid the threat of malicious actors. It’s actually already possible to do that.

Mobile operating systems like iOS and Android allow us to generate a NIST P-256 (aka secp256r1) key pair for ECDSA and store the private key in the Trusted World. This elliptic curve is the one used to generate tz3 addresses for Tezos accounts.

We could then have a tz3 public key hash and sign transactions using the corresponding secure enclave, that would be perfect.

The problem with secure enclave key generations is that you cannot extract a private key and use it later on via another device. You can sign, verify, encrypt, and decrypt data, but exports are not allowed.

This is the whole point of a secure enclave - Nobody ever has access to a private key itself. If you lose your phone with this configuration, the wallet on your phone (and all its contents) is lost forever inside the phone’s secure enclave.

Secure enclave using a smart contract

Our goal is to use a phone’s secure enclave to generate a tz3 address to be able to sign tezos transactions with it, while at the same time facilitating a great user experience to enable actions such as account recovery. For this, we can use a smart contract.

The contract is created from a main account (tz1…) simply by originating a KT1 account with the Spending Limit Contract code, preferably with some ꜩ in it.

The visual below summarizes the contract:

Schema

The contract keeps two pieces of information in its storage:

  • a public key hash (tz3…), aka the “signatory.”
  • a spending limit (explained in the next paragraph).

The contract will send transactions from the KT1 account to another address if, and only if, the transaction is signed using the secure enclave private key used to generate the tz3 in its storage.

The Spending Limit Contract storage is amendable using a 24-word mnemonic (seed phrase). Thus, it is possible to change a contract’s public key hash and spending limit.

Once again, keep your mnemonic/seed phrase safe.

Limit from traditional bank system

This contract also has another benefit, a spending limit. The first version of the contract we developed at Nomadic Labs is called the Daily Spending Limit.

This feature is inspired from traditional banking systems. Daily limits are placed for security reasons so that a bank account can’t be drained of an amount of funds in excess of a limit if theft or fraud occurs.

Many banks set ATM withdrawal limits around $500

The Daily Spending Limit contract works on a rolling 24h basis. In the same way as a traditional bank, using the Daily Spending Limit contract, a transaction will be declined if one tries to transfer more than the contract specifies for a 24h window.

Example: your spending limit is set to 2,000ꜩ per 24 rolling hours. If you transfer 1,500ꜩ at 10.07 am, you can only transfer 500ꜩ until the next day at 10.07 am, at which point the 2,000ꜩ limit is reset.

Unless you amend the contract storage using the corresponding 24-word mnemonic, there is no way to transfer more than the specified spending limit from the contract. You can also set the daily spending limit to 0.00ꜩ to be sure that no tez will be sent from the KT1 contract.

In practice

You can test this way of using tez via the Cortez wallet for Alphanet*

  1. Create a new wallet and send it some ꜩ (remember this is the alphanet test network).
  2. Create a Daily Spending Limit Contract and add a few ꜩ from your main account to it.
  3. Go to “Key Management” then remove the master key.

You cannot spend tez from your main account (tz1…) anymore because the master key was removed from your phone. However, you can still spend ꜩ from the newly created Daily Spending Limit contract (KT1…).

If you want to amend the contract’s storage, go to “Key Management” and restore the master key by entering the associated 24-word mnemonic. This way, you will also be able to transfer from your main account (your tz1). Don’t forget to remove the master key to maximise security.

I need to make a big transfer

You can always change your Spending Limit as long as you have the master key. In the first alphanet version, users can set their daily spending limits up to 10,000ꜩ per 24h.

This also means that if you do not have the master key encrypted in your phone, or if you do not have access to the 24-word mnemonic, then you cannot update the contract’s storage to spend more than what is indicated in it.

What if I lose my phone?

All you need is a 24-word mnemonic and a new phone. Download the last version of Cortez, then restore your wallet with the mnemonic.

Once restored, you will be able to amend the storage of your Spending Limit Contract and assign your new secure enclave key to it (the tz3 generated by your new phone).

Once the contract is assigned to your new phone/tz3 address, you can make transfers from it. Do not forget to remove the master key from your phone in the key management section to maximize security.

Peg-Leg Pete stole my phone with my password.

There are two possible situations:

  • You previously removed the masterkey from your phone in Key Management section of the app.

Peg-Leg Pete can then only make transfers from your Spending Limit Contract, not your main account (your tz1). He will probably spend up to the specified limit. Without the 24-word mnemonic, and no matter how much he wants to, Pete cannot steal more than the spending limit in a 24h window, and not more than the balance of the contract.

Pete_attack

Peg-Leg Pete attacks Mickey © Disney

Assuming the contract is a Daily Spending Limit contract, you now have 24h to obtain the 24-word mnemonic, restore the wallet, and assign the Daily Spending Limit contract to the new secure enclave key (tz3…) before Peg-Leg Pete can once again spend the daily spending limit amount.

  • You didn’t remove the masterkey from your phone.

If you can find another phone and the 24-word mnemonic very quickly, you can restore your wallet and amend the contract storage to assign a new tz3. This must be done before Peg-Leg Pete transfers the whole balance to his own wallet.

Status of the implementation

The first version of the Daily Spending Limit (“DSL”) smart contract is available on Cortez for Alphanet. Even if it already works, the feature is still experimental and there are some flaws.

The DSL version won’t be deployed on mainnet until it is well-tested, fully functional, and formally verified. You can find the current version of the DSL code below, however the code will evolve.

For free Tezzies follow these instructions. If you have any issues or if you want us to send you Tezzies feel free to ask for help at cortez@nomadic-labs.com.

Formal verification

You can find a link below to the very first version of the DSL, currently implemented in the Android version of Cortez for alphanet.

The contract has been partially verified using Mi-Cho-Coq, a specification of Michelson in Coq to prove properties about smart contracts in Tezos. There is more work to be done.

As stated above, the contract is meant to evolve and improve with subsequent versions until it is sufficiently flawless before it will be deployed on mainnet.

Michelson code

This is the current version of the Daily Spending Limit (DSL) contract code. Note that this version will change over time.

storage (pair
           (pair
              (pair
                 (nat %salt)
                 (key_hash %daily_key_hash))
              (pair
                 (pair
                    (mutez %remaining_funds)
                    (int %blocking_period))
                 (pair %file
                    (list (pair timestamp mutez))
                    (list (pair timestamp mutez)))))
           (pair
              (key_hash %master_key_hash)
              (nat %salt)));
parameter (option
             (or
                (pair %contract_renewal
                   (pair
                      (key %public_key)
                      signature)
                   (pair
                      (pair
                         (pair
                            (nat %salt)
                            (key_hash %daily_key_hash))
                         (pair
                            (pair
                               (mutez %remaining_funds)
                               (int %blocking_period))
                            (pair %file
                               (list (pair timestamp mutez))
                               (list (pair timestamp mutez)))))
                      (key_hash %new_master_key_hash)))
                (pair %transfer
                   (pair
                      (list %beneficiaries
                         (pair
                            (mutez %amount)
                            (contract %beneficiary unit)))
                      (key_hash %new_daily_key_hash))
                   (pair
                      (key %public_key)
                      signature))));
code { UNPAPAIR;
       IF_SOME{ IF_LEFT{ DIP{DROP}; # call by master key
                         SWAP;
                         UNPAIR;
                         DIP{ SWAP;
                              UNPAIR;
                              DIP{ DUP;
                                   PACK;
                                   DIP{ DIP{ DUP;
                                             PUSH nat 1;
                                             ADD;
                                             SWAP;
                                             PACK};
                                        SWAP
                                      };
                                   CONCAT;
                                   DUP};
                              UNPAIR;
                              DUP;
                              HASH_KEY;
                            };
                         ASSERT_CMPEQ;
                         CHECK_SIGNATURE;
                         IF{DROP}{FAILWITH};
                         UNPAIR;
                         DIP{PAIR};
                         NIL operation
                       }
                       { DIP {UNPAPAIR}; # transaction call
                         UNPAIR;
                         DUP;
                         PACK;
                         DIP{ UNPAIR;
                              DIP{SWAP}};
                         SWAP;
                         DIP{ DIP{ DIP{SWAP};
                                   SWAP;
                                   DIP{ UNPAIR;
                                        DUP;
                                        HASH_KEY};
                                   UNPAIR;
                                   DIP{ ASSERT_CMPEQ}; # keys compatibility verification
                                   DUP;
                                   PUSH nat 1;
                                   ADD;
                                   SWAP;
                                   PACK;
                                 };
                              CONCAT;
                              SWAP;
                              DIP{ SWAP;
                                   DIP{ SWAP;
                                        DIP{DUP}};
                                   CHECK_SIGNATURE; # signature verification
                                   IF{DROP}{FAILWITH}};
                              PAIR;
                              DIP{ UNPAIR;
                                   SWAP;
                                   DIP{ SWAP;
                                        UNPAIR;
                                        PUSH bool True;
                                        LOOP{ # search for funds to unlock
                                              IF_CONS{ DUP;
                                                       CAR;
                                                       NOW;
                                                       IFCMPGE{ CDR;
                                                                SWAP;
                                                                DIP{ SWAP;
                                                                     DIP{ ADD}};
                                                                PUSH bool True
                                                              }
                                                              { CONS;
                                                                PUSH bool False
                                                              };
                                                     }
                                                     { IF_CONS{ NIL (pair timestamp mutez);
                                                                SWAP;
                                                                CONS;
                                                                SWAP;
                                                                ITER{ CONS};
                                                                NIL (pair timestamp mutez);
                                                                SWAP;
                                                                PUSH bool True}
                                                              { NIL (pair timestamp mutez);
                                                                DUP;
                                                                PUSH bool False}
                                                     };
                                            };
                                        DIP{ SWAP};
                                        SWAP;
                                      };
                                 };
                              PUSH mutez 0;
                              NIL operation;
                            };
                         ITER{ UNPAIR; # transactions production
                               DUP;
                               DIP{
                                    UNIT;
                                    TRANSFER_TOKENS;
                                    CONS;
                                  };
                               SWAP;
                               DIP{ SWAP;
                                    ADD} # blocking period computation
                             };
                         DIP{ SWAP; # spent funds lock
                              DIP{ SWAP;
                                   DUP;
                                   NOW;
                                   ADD;
                                   SWAP;
                                   DIP{ DIP{SWAP};
                                        SWAP;
                                        DIP{ SWAP;
                                             DUP;
                                             DIP{ SWAP;
                                                  PAIR;
                                                  SWAP;
                                                  DIP{ CONS}; # add to the queue
                                                  PAIR}};
                                        SUB}; # unlocked remaining funds computation
                                   SWAP;
                                   PAIR;
                                   PAIR;
                                 };
                              PAIR};
                       }
              }
              { NIL  operation; # simple fund addition (parameter: None)
                };
       PAPAIR;
     }