paint-brush
Moving to Capability-Based Security with Flow: A Critical Evolution in Blockchain Securityby@alvinslee
1,349 reads
1,349 reads

Moving to Capability-Based Security with Flow: A Critical Evolution in Blockchain Security

by Alvin LeeNovember 1st, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Blockchain authorization through granular-level access privileges to resources
featured image - Moving to Capability-Based Security with Flow: A Critical Evolution in Blockchain Security
Alvin Lee HackerNoon profile picture

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 its core, capability-based access control creates tokens of authority to give stakeholders selective access to certain resources without sharing everything.


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 capability 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 the above operations is not fault-tolerant, 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 from 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 to 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 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 the 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.

Creating capability using a link function

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 the 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

Removing capability using the unlink function

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 to 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:

  1. Creating the capability to access the resource from CarAsset
  2. Creating a reference by borrowing the capability
  3. 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 into 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.


Also published here.