🗝️Keysets
Now that we are inside the "free" namespace, we can begin defining keysets for use in our module.
Public-key authorization is widely used in smart contracts to ensure that only the holders of specific keys can take certain actions (such as transferring funds from their account). Pact integrates single- and multi-signature public-key authorization into smart contracts directly via the concept of keysets.
Pact has other tools for authorization as well as a whole, authorization in Pact is handled via guards or capabilities (we’ll learn about both later), and a keyset is a specific kind of guard.
So what, concretely, is a keyset? A keyset pairs a set of public keys with a predicate function. In JSON form it looks like this:
// js
{“keys”: [ “abc123”],“pred”: “keys-all”}
Pact will check the predicate function against the set of keys when the keyset is used as a guard. If the predicate fails then access is denied. There are a few built-in predicate functions, such as the “keys-all” function above this predicate means means that all keys in the set must have signed the transaction. You can also write your own predicate functions (for example, to authorize access according to a vote).
Keysets are defined via the (define-keyset) function. This function takes a name and a keyset as arguments. When evaluated, Pact will either register the keyset at the given name on NuChain Consensus or, if the name is already registered, then it will “rotate” (ie. update) the keyset to the new value.
When registering a keyset in a smart contract it’s a common practice to send the keyset in the deployment transaction data instead of hardcoding it into the contract. That’s because keyset references can be rotated (ie. upgraded) once rotated, the keyset name won’t refer to the value written in the contract anymore. If you ever want to see the current value of a keyset reference, you can look it up by name by sending this code to a NuChain node:
// pact
(describe-keyset "free.my-keyset")
Let’s proceed with defining the “free.nucredit-faucet-keyset” using the keyset provided via transaction data. You can parse data from the transaction using the (read-*) family of functions:
#read-msg
#read-keyset
#read-string
#read-integer
#read-decimal
Our deployment transaction will be sent with two pieces of data:
‘upgrade’: a boolean indicating whether we intend this as a deployment or as an upgrade to the already-deployed module, if we are upgrading then we can skip the keyset definition and initialization steps.
‘nucredit-faucet-faucet-contract’: a keyset that should be registered as the “free.nucredit-faucet-keyset” keyset on-chain.
Below, we read the nucredit-faucet faucet keyset from the transaction data and register it, but only if we are deploying (not upgrading) this contract. Once the keyset is registered, our Pact module can refer to it when guarding sensitive information. To see how to provide a keyset in transaction data please refer to the relevant doc and the deploy-faucet-contract.yaml file.
// pact
(if (read-msg “upgrade”) [ (enforce-keyset (read-keyset “nucredit-faucet-faucet-keyset”)) (define-keyset “free.nucredit-faucet-faucet-keyset” (read-keyset “nucredit-faucet-faucet-keyset”)) ] "Upgrading contract")
If reading the ‘upgrade’ field yields ‘true’, then this isn’t our initial deployment and therefore we should skip registering the keyset.
Otherwise, this is our initial deployment, so we should register the keyset. Just one more thing before we proceed: we should verify our keyset. There are multiple reasons to do this.
First, what if we have a typo in the keyset sent in the transaction data? The typo keyset will be registered and we’ll be unable to access anything guarded by it!
Second, you don’t have to define a keyset inside your smart contract. You may wish to reuse the same keyset reference in multiple contracts, and so you simply reuse the keyset reference in your contract. This can be dangerous, however. If you deploy a contract referring to a keyset but you forgot to register that keyset, then someone else can register the keyset with their keys and gain access to your guarded data. To prevent these risks it’s a best practice to enforce a keyset guard on the transaction that deploys the contract. This guard should ensure that any keysets passed to the contract were also used to sign the transaction that deploys the contract. If the enforcement fails, the deployment is aborted, and you can fix the keyset and try again.
Last updated