Skip to content

Commit

Permalink
KIP 0028: Pact Verifier Plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundnoble committed Apr 4, 2024
1 parent 1126878 commit 69f7f55
Showing 1 changed file with 169 additions and 0 deletions.
169 changes: 169 additions & 0 deletions kip-0028.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
KIP: "0028"
Title: Pact Verifier Plugins
Author: Edmund Noble @edmundnoble
Status: Draft
Type: Standard
Category: Interface
Created: 2023-12-13

# Motivation

Pact allows signers to grant specific capabilities, thereby limiting the *scope* of their signing authorization to only those capabilities that have been granted. For example, a signer can sign a transaction for the capability `(coin.TRANSFER "src" "dest" 1.0)` and their signature can only be taken to authorize a transfer from the "src" account to the "dest" account in the amount of 1.0 KDA; there are no other actions which the signature authorizes. Any attempts within the Pact code of that transaction to grant other capabilities which require signature from the same signer will fail, due to the signature not being *scoped* to those capabilities.

The Pact container, for example chainweb-node, verifies signatures, and informs the Pact environment only that certain keys have signed for certain capabilities. In this way, signatures are invisible to Pact code.

Relatedly, key data is opaque to Pact code; it’s conventionally loaded from per-transaction data, rather than being included literally in Pact source code. A key is opaque data which is associated to capabilities via the Pact container's signature checking.

Put another way, Pact code knows nothing about public key cryptography per se. Pact code instead deals with *capabilities* in the abstract, which can be *attested to* by the Pact container layer as being granted by some *authority*, here the public key of the signer or more generally some keyset. The Pact code in the body of a capability only ensures that it's been granted by the necessary authority.

Other cryptographic algorithms, for example different forms of signature verification, have still occasionally been implemented with Pact code - though there are performance and correctness issues usually associated with implementing them in such a high level language. There has been a steady demand for novel cryptography in Pact driving these efforts. Recently, Pact implemented WebAuthn signature verification for transactions (in addition to the existing ED25519 signature scheme) which fit neatly into Pact's signer/capability paradigm. But verifying signatures on data other than Pact transactions, which may originate from outside Pact - for example from bridges - doesn't easily fit Pact's signer/capability paradigm. Zero-knowledge proofs are another area where new cryptography is useful, which are roughly analogous to signatures for data other than Pact transactions.

Because new cryptography is needed, an interface for developers to easily integrate their own cryptographic algorithms with the capability-oriented paradigm of Pact would be very valuable.

Such an interface is proposed here.

# Design

We propose defining a *verifier plugin* as a new form of authority in the Pact container layer that can grant capabilities. More specifically, we propose defining it as a named function which, given a proof value and a set of capabilities, either consents to grant that set of capabilities exactly or produces an error.

A transaction will be allowed include a list of verifiers, similarly to a list of signers, which for each used verifier will include the name of that verifier, the proof being passed to that verifier, and the capabilities it's being asked to grant. **Note that this means that the creator of a transaction must already know exactly what capabilities a proof grants, to include it into the capability list of a verifier.** This is essential for showing that the things a transaction is allowed to do are included verbatim in the transaction in a human-readable format.

In Pact versions above 4, a capability can require that it was signed for by a given keyset via `enforce-keyset`; analogously, a capability can require that it was granted by a particular verifier via the proposed built-in function `enforce-verifier`, for example `(enforce-verifier 'ZK)`.

Some verifiers follow, defined abstractly, which demonstrate how this verifier mechanism is general enough to subsume some existing cryptographic functionality.

## Example verifiers

### SPV

We define the **SPV** verifier as follows. Its use is to verify Simple Payment Verification proofs against the Kadena blockchain.

Given a chain ID `cid`,
an SPV proof subject `subj`, which is a pact event `(M.X a b c)`,
an SPV proof object `obj`, which is a merkle proof of the subject event's emission in a block on the chain `cid`:

**SPV** is a verifier which has as input the pair `(subj, obj)`. Its output is
the set of all Pact capabilities of the form `(N.Y M X a b c)`, where `M` and `X` have been converted to string literals. Note that the granted capability
name `Y` and its containing module `N` are not determined by the proof, i.e. `subj`; furthermore, there is no nonce preventing replays. In general, SPV proofs are valid to use in any module, on any chain, and are legal to use multiple times. Users can add nonces and chain IDs to their event arguments if they wish to change that for their use-case.

This verifier subsumes the `verify-spv` function in Pact, though with a drastically different interface.

### Hyperlane Message

We define the **Hyperlane Message** verifier as follows, though this is an abstract definition without all of the nuance of the Hyperlane protocol. Its use is to verify signatures of cross-network Hyperlane messages.

Given:
- a bridge module `B`
- a list of signing keypairs `(signerPublicKeys, signerPrivateKeys)`
- a Hyperlane message `msg`, which is a packed ABI-encoded Hyperlane v3 message as detailed in https://docs.hyperlane.xyz/docs/reference/libraries/message, which:
- can be represented as a Pact object `pactMsg`
- which is signed by `signerPrivateKeys`
- the message ID `messageId` that is the keccak256 hash of `msg`:

The **Hyperlane Message** verifier has as input `msg` and as output the singleton set containing the Pact capability `(B.PROCESS signerPublicKeys messageId pactMsg)`. The bridge module is responsible for:
- registering `messageId` as received in its storage, preventing message replays;
- checking that signature by `signerPublicKeys` is sufficient to authenticate the message;
- calling into `M` to ensure that the mint happens.

Conventionally, Hyperlane bridges are secured by "Interchain Security Modules" or ISMs, which both contain cryptographic code and manage the set of *validators*, that is, public keys that can authenticate Hyperlane messages. This verifier implements the *mechanism* of the ISM, that is the cryptography, while leaving the *policy* of the ISM up to the bridge code, for example managing the set of validators and ensuring that the signers match the validators in some way.

# Implementation

## `chainweb-node`

In `chainweb-node` we propose adding a mapping from verifier names to verifier functions, which is consulted when validating a transaction. A verifier may charge gas after gas is bought and before any transaction code is run. Because of this, currently, a verifier cannot grant the `GAS_PAYER` capability, which would allow paying for gas using a verifier’s attestation. This may change in the future.

Notably, writing a verifier plugin does not require changes to `pact`, only to `chainweb-node`, because verifiers run on the Pact container layer and verifier names are strings.

## Changes to the Pact transaction schema

For reference, this is the schema of the `signers` field:

`signers` is a required array of objects with:

- `pubKey`:
- the public key image. Pact default is base16 ED25519 encoding.
- a string.
- `clist`:
- a list of capabilities associated with/installed by this signer.
- an optional array of objects with:
- `name`: a fully-qualified capability name, as a string.
- `args`: an array of JSON-encoded Pact values.

And an example:

```json
"signers":
[
{
"pubKey": "368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca",
"clist":
[
{"name": "coin.GAS", "args": []},
{"name": "coin.TRANSFER", "args": ["sender00", "sender01", 1]}
]
}
]

```

The public key is assumed to be an Ed25519 key by default. The signatures are
located elsewhere in the transaction, but they are assumed to correspond with
signers. The first signature corresponds to the first signer, second signature
to the second signer, and so on.

We propose the following schema for including extra verification types in
transactions:

`verifiers`, an array of objects with:

- `name`:
- an identifier for the verification type.
- a string.
- `proof`:
- input to the verifier.
- an arbitrary JSON-encoded Pact value.
- `clist`:
- a list of capabilities granted by the verifier.
- an optional array of objects with:
- name: a fully-qualified capability name, a string.
- args: an array of JSON-encoded Pact values.

And an example:

```json
"verifiers":
[
{
"name": "zk",
"proof": "5jKgydHx1nSLPSBJ88z47Q==",
"clist":
[{"name": "zk.FUN", "args": ["in", "out"]}]
}
]
```

## Pact

### The enforce-verifier function

The `enforce-verifier` function is callable from within a capability. It takes a verifier's name as a parameter, and fails the transaction if said verifier is not scoped to the capability. Specifically, the verifier must be scoped to either the capability whose body is currently being run, or to a capability which the current capability is transitively composed inside.

### The env-verifiers function

The `env-verifiers` function is available in the Pact REPL, to simulate verifier presence in the evaluation environment. Using it is similar to using the `env-sigs` function - it takes as a parameter a list of verifiers to be included in the evaluation environment, excluding the proofs. The following example is from the Pact docs:

```pact
(env-verifiers [
{'name: "COOLZK", 'caps: [(accounts.USER_GUARD "my-account")]},
{'name: "HYPERCHAIN-BRIDGE", 'caps: [(bridge.MINT "mycoin" 20)]}
])
```

# Impact on Users

Impact of the interface depends on verifier plugins being written. Kadena has several ideas for plugins, for example a Hyperlane message verifier and zero-knowledge verifier, but it's our hope that interested developers will comment and potentially write their own, as pull requests to chainweb-node.

# Future Possibilities

As many proofs come in the form of messages which should be delivered at most once, like SPV proofs for cross-chain transfers, and signatures, perhaps some automated form of nonce management could be added. In addition, an automatic form of expiry for messages could be useful in combination, and would allow automatic pruning for used nonces have expired.

0 comments on commit 69f7f55

Please sign in to comment.