Better Smart Contract Security With Fine-Grained Permissions in Cadence
Fine-grained access is a way of providing access that follows the principle of least privilege. This makes smart contracts highly secure and fault-resistant.
Join the DZone community and get the full member experience.
Join For FreeFlow 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 way of providing fine-grained permissions to objects within the blockchain. Fine-grained access is a new method of providing access to users that follows the principle of least privilege. This makes it highly secure and fault-resistant. Flow is one of the unique blockchains to have this, as it is built on the fundamentals of Capability-based security.
But how does this help you as a developer? If you’re writing smart contracts, then you likely have to regularly define access to objects and functions within your contract. Understanding the different types of access better will help you to define access types in code more accurately. This will allow you to write highly secure and efficient smart contracts.
Let’s dive in.
Understanding Fine-Grained Permissions
As opposed to Role-based access control (RBAC), where permissions are defined loosely for every role and not specifically for every individual, fine-grained permissions allow developers to define permissions for objects at the lowest level. This allows permissions to be defined for every object. This also allows you to add new permissions types, such as time-expiry and geography-based permissions.
Let’s understand the core principle behind fine-grained permissions.
The Principle of Least Privilege
The concept of least privilege means that in a computing environment, any module (such as a process, a user, or a programmer) only has the necessary access that it needs to do its specific job. Any other unnecessary permissions for its specific job are either revoked or unavailable.
For example, a programmer whose job is to create backups does not need to make user data updates, so it should only have read permissions on the database of users. This principle is the core principle behind fine-grained permissions.
Capability-Based Security
A capability is an unforgeable token of authority. In capability-based authorization, your identity does not matter. If you’ve been sent an access token by the owner/admin that grants you the capability to access a resource, and you can execute that capability, then you will have access. Hence, at runtime, the application does not check what your identity is but only that you can access the requested resource. Thus, fine-grained access can be implemented on the foundations of capability-based security.
History of Fine-Grained Access
In the 1980s, operating systems would define permissions on objects as “read,” “write,” and “execute.” This was called Access Control Lists. In the 1990s, Role-based Access Control was introduced, which allowed you to create groups and assign users to groups. Then, the concepts of attribute-based access control and capability-based access control arrived, which brought fine-grained access into the picture. In the world of blockchains, however, fine-grained access is new and brought by Cadence, the language used to write Flow smart contracts.
Fine-Grained Access in Cadence
Cadence, the language used to write flow smart contracts, has built-in special keywords to allow for fine-grained permissions to resources. Below, we dive into the common keywords and the usage of these keywords to provide fine-grained access.
The Access Keywords
Flow defines certain keywords, known as access keywords, with different parameters to define the access privileges of a certain resource. Below are the four primary keywords:
Pub
or Access(all)
Using this keyword means the declaration is accessible in all scopes. However, this also means that the declaration is not updatable except when you’re updating the value at an index of an array. Pub
or access(all)
are the least restrictive types of access and should generally be avoided. Below is an example of using the pub keyword:
pub contract Car {
pub let carBrand: String
pub let carNameByBrand: {String: String}
pub fun returnCarBrand(): String {
return self.carBrand
}
}
}
Following the above code example, if there’s an object called CarObject
of type Car
, then the statement below invocation is valid.
CarObject.carBrand //Equals the value of greeting in the object
The values with access type pub
are generally not updatable except when updating the value at a certain index of an array.
CarObject.carNameByBrand = {} //Not Valid
CarObject.carNameByBrand[“Porsche”] = “Carrera” //Valid
Access(self)
access(self)
means that the declaration is only visible in the current and inner scopes. For example, an access(self)
field can only be accessed by the functions of the type it is a part of.
When using access(self)
, it is common practice to define setters and getter methods to allow other objects and contracts to access this field.
This keyword makes the usage very safe but also restrictive. Below is an example of using access(self)
:
pub contract Car {
access(self) let carBrand: String
access(self) let carNameByBrand: {String: String}
pub fun returnCarBrand(): String {
return self.carBrand
}
pub fun returnCarName(_ brand: String): String? {
return self.carNameByBrand[brand]
}
}
}
Access(contract)
access(contract)
means that the declaration is only accessible in the scope of the contract that defines it. Hence, functions defined in other contracts under the same account or functions defined in contracts under other accounts will not be able to access it. This keyword is unique to Cadence and comes in handy when you have any contract-specific variables you don’t want to expose to other contracts.
Below is an example of using the access(contract)
keyword:
pub contract CarContract {
pub struct Car {
access(contract) var carBrand: String
}
pub fun returnCarBrand(_brand: Car): String {
return Car.carBrand
}
}
}
In the above example, if you have a method in a different contract that has an object of the Car
struct, you cannot get the carBrand
variable directly. However, you can call the returnCarBrand
function to get the value of carBrand.
Access(account)
This keyword means that the declaration is only accessible inside the entire account owned by the current smart contract. Hence, other contracts in the same account can access a variable freely. This type of access is very powerful, allowing you to keep a variable private to your account. You are also able to deploy a smart contract that needs to use the variable in the future, if needed. This way, several contracts in the same account can share information seamlessly with one another, promoting the modularity of code.
Below is an example of how to use it:
pub contract CarContract {
pub struct Car {
access(account) var carBrand: String
}
pub fun returnCarBrand(_brand: Car): String {
return Car.carBrand
}
}
}
In the above example, if you have a method in a different contract that has an object of the Car
struct, you can get the carBrand
variable directly.
Conclusion
The concept of least privilege and capability-based security plays a significant role in Flow's precise permissions. The access keywords provide clear definitions of variable and function access, as we have covered in this article.
One of the most noteworthy features that fundamentally strengthens the security of the Flow blockchain is its fine-grained access control. Blockchain technology has long been plagued by bugs and hacks, most of which occur because of ill-conceived access controls. However, fine-grained access control provides developers with a remarkable opportunity to effortlessly and smoothly secure their smart contracts. Ultimately, this is what sets Flow apart in the world of blockchain.
On the topic of fine-grained permissions, hopefully, you found this in-depth exploration valuable and helpful. For more information, refer to the official documentation. Or explore the Flow Docs and get some hands-on experience!
Published at DZone with permission of Michael Bogan. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments