Moving to Capability-Based Security with Flow

Moving to Capability-Based Security with Flow

A Critical Evolution in Blockchain Security

·

9 min read

Flow is a permissionless layer-1 blockchain built to support the high-scale use cases of games, virtual worlds, and the digital assets that power them. The blockchain was created by the team behind Cryptokitties, Dapper Labs, and NBA Top Shot.

One core attribute that differentiates Flow from the other blockchains is its usage of capability-based access control.

At a high level, this means instead of the typical model where sets of permissions are given to users through roles, permissions instead are granted by issuing capabilities. These capabilities can be seen as digital keys that unlock specific pieces of functionality, such as access to a specific resource (object or function). Capabilities make it possible to grant users dynamic and fine-grained access.

But why is this important for you as a Flow developer? With capabilities, you can define a user’s granular-level access privileges. So if you’re building a music app and want to give the ability to access top playlists to only premium users, you can control that very easily with the built-in capabilities functionality of Flow.

(Source)

What is Blockchain authorization?

Blockchain authorization is the methodology by which access to information and execution permissions are granted on a blockchain system. Several operations that a user may need to perform within a blockchain may include the following:

  • Creating new smart contracts

  • Updating smart contract code

  • Executing smart contract functions

  • Being a validator node

  • Updating smart contract data

If the methodology used to manage the execute permissions of above operations is not fault-tolerant (or, able to handle errors and unexpected conditions without causing a security breach), then it could be a huge security risk for the application.

There are several major types of authorization that have the same objectives but slightly different implementations.

Access Control Lists (ACLs)

Access Control Lists (ACLs) refer to the authentication mechanism that works on maintaining individual lists for managing access to different objects. ACLs are like guest lists for a resource. Those participants that are on the guest list of a certain object are allowed to access and others are not.

Role-based access control (RBAC)

Role-based access control refers to the idea of assigning permissions to network participants on the basis of their role within an organization. The access rules are mapped to the roles rather than the individual identities. This is the most common form of authorization used in popular products like AWS.

Capability-based authorization

A capability is an unforgeable token of authority. In Capability-based authorization, your identity does not matter. If you receive an access token by the owner/admin that grants you the capability to access a resource, and you are able to execute that capability, then you will have access. At runtime, the application does not check what your identity is but only that you have the capability to access the requested resource.

ACLs versus Capability-based authorization

There are certain drawbacks of implementing ACLs, especially in the context of decentralization, which we’ll discuss below.

Ambient Authority problem

Let’s say that as a user, you have received several different types of access and privileges to an app on your operating system. At some point, you request the app to fetch certain data for you. You would want to make sure that the app fetches only the data that’s absolutely necessary and doesn’t access anything else. However, in the case of ACL systems, there is no way to make sure this happens since the app has “ambient” authority. This can only be solved by using capability-based security systems. Watch this video to learn more.

Confused Deputy problem

Let’s say there’s a program A, requesting a program B to perform certain actions. There might be instances where only program B has access to perform some of those actions, but program A does not. Program B still performs them because it didn’t double check. In this case, program B was tricked into misusing its privileges by program A. This can be solved by using capabilities. Watch this video to learn more. Here’s how the company Tymshare faced this problem 25 years ago.

ACL attack vector

Because ACL lists are usually maintained with a centralized owner, it is prone to malicious updates at any time. Using capabilities takes away the power of performing malicious updates from a centralized owner and hence makes the system secure from the large ACL attack vector.

(Source)

About Capabilities

A capability (also known as the ‘key’) is a hash that designates both the resource and access to it. This is also the model implemented in Bitcoin where “your key is your money”, and in Ethereum where “your key is gas for EVM computations”. In the Flow blockchain, “your keys are your data” and hence data access is controlled directly by keys instead of identities.

By tying access to key, capability-based models push security to the edge, decentralizing large attack vectors. Capabilities also make it very easy to write code that defines security privileges in a granular fashion.

There are two major types of capabilities in Flow blockchain:

Public capabilities

Public capabilities are created using public paths and hence have the domain “public”. After creation, users can access them with authorized accounts (“AuthAccount”) and public accounts (“PublicAccount”).

Private capabilities

Private capabilities are created using private paths and hence have the domain “private”. After creation, they can only be accessed by authorized accounts (“AuthAccount”) and not by public accounts (“PublicAccount”).

3 Tenets of capability-based security

  1. Encryption-based - Capability-based security always has an unforgeable key to go with a particular access. This means that just the identity of a participant is not enough to get access.

  2. Decentralized - Capability-based security is totally decentralized. This means that the success of the security system is not dependent on a single owner.

  3. Granular - It is easier to define fine-grained access to data and resources using capability-based security.

Creating a capability in Cadence

Cadence is the programming language used to create Flow contracts. Below we’ll talk about the code used to create capabilities in Cadence.

The link function of an authorized account (“AuthAccount”) is used to create a capability.

fun link<T: &Any>(_ newCapabilityPath: CapabilityPath, target: Path): Capability<T>?
  • newCapabilityPath is the public or private path identifying the capability

  • target is any public, private, or storage path that leads to the object that will provide the function defined by this capability

  • T is the type parameter for capability type

The above function will:

  • return nil if the link for a given capability path already exists

  • return the capability link if the link doesn’t already exist

The unlink function of an authorized account (“AuthAccount”) is used to remove a capability.

fun unlink(_ path: CapabilityPath)
  • path is the public or private path of the capability that should be removed

Other important functions

getLinkTarget

This function can be used to get the target path of a capability.

fun getLinkTarget(_ path: CapabilityPath): Path?

getCapability

This function can be used to get the link of existing capabilities.

fun getCapability<T>(_ at: CapabilityPath): 
Capability<T>

check

This function is used to check if the target currently exists and can be borrowed.

fun check<T: &Any>(): Bool

borrow

This function is used to borrow the capability and get a reference to a stored object.

fun borrow<T: &Any>(): T?

Code examples of creating a capability with Cadence

We will follow a simple example where:

Step 1: We will create a smart contract with a function as a resource.

Step 2: We will access the resource.

  • Create a capability to access the resource in that smart contract.

  • Create a reference to that capability using the borrow function.

  • Execute the function resource.

Step 1: Creating a car smart contract

Explanations are in the comments below:

pub contract Car {
    // Declare a resource that only includes one function.
    pub resource CarAsset {
        // A transaction can call this function to get the "Honk Honk!"
        // message from the resource.
        pub fun honkHorn(): String {
            return "Honk Horn!"
        }
    }
    // We're going to use the built-in create function to create a new instance
    // of the Car resource
    pub fun createCarAsset(): @CarAsset {
        return <-create CarAsset()
    }
    init() {
        log("Creating CarAsset")
    }
}

Step 2: Accessing the honkHorn() function in the CarAsset resource of Car smart contract

The code below includes all three steps:

  • Creating the capability to access the resource from CarAsset

  • Creating a reference by borrowing the capability

  • Executing the HonkHorn function

Explanations are in the comments below:

import Car from 0x01

// This transaction creates a new capability
// for the CarAsset resource in storage
// and adds it to the account's public area.
//
// Other accounts and scripts can use this capability
// to create a reference to the private object to be able to
// access its fields and call its methods.

transaction {
  prepare(account: AuthAccount) {

    // Create a public capability by linking the capability to
    // a `target` object in account storage.
    // The capability allows access to the object through an
    // interface defined by the owner.
    // This does not check if the link is valid or if the target exists.
    // It just creates the capability.
    // The capability is created and stored at /public/CarAssetTutorial, and is
    // also returned from the function.
    let capability = account.link<&Car.CarAsset>(/public/CarAssetTutorial, target: /storage/CarAssetTutorial)

    // Use the capability's borrow method to create a new reference
    // to the object that the capability links to
    // We use optional chaining "??" to get the value because
    // result of the borrow could fail, so it is an optional.
    // If the optional is nil,
    // the panic will happen with a descriptive error message
    let CarReference = capability.borrow()
      ?? panic("Could not borrow a reference to the Car capability")

    // Call the honkHorn function using the reference
    // to the CarAsset resource.
    //
    log(CarReference.honkHorn())
  }
}

If you execute the above function, you should see the message “Honk Honk!” in your console. Refer to this tutorial to learn how to deploy a contract and execute Cadence code.

Conclusion

In this article, we learned the types of blockchain authorization and the added advantage of using capabilities over other methods of authorization. We also learned how to create, execute, and transfer capabilities in Cadence, the smart contract language of Flow blockchain. These learnings will help you as a developer to write highly secure code when building on Flow.

In essence, capabilities open up a new paradigm of blockchain authorization, making it very easy for developers to define access at a granular level. The fact that Flow blockchain uses this paradigm for data access makes it one of the most secure blockchain options out there. And as you probably noticed, it is super easy to create capabilities in Flow.

I hope that you enjoyed this deep dive on capability-based security in Flow blockchain. You can refer to the official docs page on Capabilities to learn more. I also recommend that you get your hands dirty by going through Flow Docs and this tutorial on Capabilities.