Skip to content

Instantly share code, notes, and snippets.

@EnoF
Last active December 12, 2024 15:56
Show Gist options
  • Save EnoF/fdf5b4e783f8e55a4ece078b1d215fc9 to your computer and use it in GitHub Desktop.
Save EnoF/fdf5b4e783f8e55a4ece078b1d215fc9 to your computer and use it in GitHub Desktop.

What are Principals in pact?

An account has many different forms and can be used in different ways. When users create an account an attacker could try to frontrun the transaction. The attacker would mutate the guard associated with the account, hoping the user won't notice or worse tokens are already on it's way to this newly created account. We can prevent such attacks by providing a way to pin an account to it's guard. If the attackes decides to mutate the guard now, the account associated will result in a different account and therefore will fail the transaction. An account that has their guard pinned in their name is called a principal account.

Guards

To better understand principals in pact, you need to understand guards. Every guard serves as a way to limit access. It defines what conditions have to be met before granting access. So what type of guards exists and what do they do?

Keysets

The most used guards and closest to other blockchains way of guarding access are keysets. A keyset holds a collection of 1 or more public keys and defines how many of those keys need to sign in order for the guard to pass; the predicate of a keyset

Keysets holding single keys with the keys-all predicate result in a k:account. Any other type of keyset combination will result in a w:account.

User guards

In some cases you'd want to customize the guard to allow for example one two keysets to sign, i.e. in one keyset the board of directors are registered, while in the other keyset the union stakeholders are registered. This type of guard can be very flexible and powerful. It however does not allow access to a database during evaluation of the guard. A user guard is in essence a pure function that will be evaluated on runtime to guard an account.

Such accounts result in a u:account.

Capability guards

Since user guards are required to be pure, they don't have the ability to take database state into account. To gain this dynamic property one could retrieve such data while bringing a capability into scope. This way you can define a guard that requires a capability to be brought into scope. This can be achieved using a user guard, but a capability guard is more convienient and a more explicit way to achieve the same.

Accounts guarded by a capability result in a c:account.

Accounts recap

Every type of guard can be represented in a unique way. For every type of guard you have a different protocol name. Here is a short overview of the protocols:

  1. k for single key keysets
  2. w for multiple keys keysets
  3. c for capability guards
  4. u for user guards

There are more principals, but I'll leave it at these as these are the most likely guards you'll use for creating an account.

You can see some examples below.

(begin-tx)
(module test G
(defcap G() true)
(defcap SOME-CAPABILITY(account:string) true)
(defun enforce-my-guard(guard:guard)
(enforce-keyset guard)))
(commit-tx)
(env-data
{ "ks": ["pubkey"]
, "mks": ["pubkey1", "pubkey2"]
, "amks": { "keys": ["pubkey1", "pubkey2"], "pred": "keys-any" }
})
(begin-tx)
(expect "Principal name to match"
"k:pubkey"
(create-principal (read-keyset "ks")))
(expect "Principal name to match"
"w:XJKZlqax4U3SYulCyboYGInyn33ycPFS2wJCVybYsjw:keys-all"
(create-principal (read-keyset "mks")))
(expect "Principal name to match"
"w:XJKZlqax4U3SYulCyboYGInyn33ycPFS2wJCVybYsjw:keys-any"
(create-principal (read-keyset "amks")))
(expect "Principal name to match"
"c:b7iZJRzXJZqY5gun9HLuKGwiRf8ZJICLvunggRWzlRs"
(create-principal (create-capability-guard (test.SOME-CAPABILITY "my-account"))))
(expect "Principal name to match"
"c:jDL5hzYaJEQiw-6x8Hd6_H1w7KrjCCubPSehxt500hM"
(create-principal (create-capability-guard (test.SOME-CAPABILITY "my-other-account"))))
(expect "Principal name to match"
"u:test.enforce-my-guard:Xv7uRrsqw9iCQIbGLudMQ7t_DB3xPzki5jqnAupaHoc"
(create-principal (create-user-guard (test.enforce-my-guard (read-keyset "ks")))))
(expect "Principal name to match"
"u:test.enforce-my-guard:39DM3OJqENcyQB3--8AuHxPsUTjgCsW-1jh-MFZv--U"
(create-principal (create-user-guard (test.enforce-my-guard (read-keyset "mks")))))
(commit-tx)
(begin-tx)
(module test G
(defcap G() true)
(defcap SOME-CAPABILITY(accounts:string)
(enforce true "This is a test")))
(commit-tx)
(begin-tx)
(expect "the body of defcap to have no affect on the principal name"
"c:b7iZJRzXJZqY5gun9HLuKGwiRf8ZJICLvunggRWzlRs"
(create-principal (create-capability-guard (test.SOME-CAPABILITY "my-account"))))
(expect "the body of defcap to have no affect on the principal name"
"c:jDL5hzYaJEQiw-6x8Hd6_H1w7KrjCCubPSehxt500hM"
(create-principal (create-capability-guard (test.SOME-CAPABILITY "my-other-account"))))
(commit-tx)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment