Defining custom predicates

(Note: At the moment, we consider a "custom operation" to be exactly the same thing as the "definition of a custom predicate.")

A custom operation 1 is a rule that allows one to deduce a custom statement from one or more existing statements according to a logical rule, described below.

Note: Unlike built-in operations, it is not possible to perform arbitrary calculations inside a custom operation.

The syntax of a custom operation is best explained with an example.

Original example with anchored keys, origins, and keys.

ArgsConditionOutput
pod: Origin,
good_boy_issuers: AnchoredKey::MerkleRoot,
receiver: AnchoredKey
ValueOf(AK(pod, "_type"), SIGNATURE),
Contains(good_boy_issuers, AK(pod,"_signer")),
Equals(AK(pod, "friend"), receiver)
GoodBoy(receiver, good_boy_issuers)

Compiled example with only origins and keys.

ArgsConditionOutput
pod: Origin,
good_boy_issuers_origin: Origin,
good_boy_issuers_key: Key::MerkleRoot,
receiver_origin: Origin,
receiver_key: Key
ValueOf(AK(pod, "_type"), SIGNATURE),
Contains(AK(good_boy_issuers_origin, good_boy_issuers_key), AK(pod,"_signer")),
Equals(AK(pod, "friend"), AK(receiver_origin, receiver_key))
GoodBoy(AK(receiver_origin, receiver_key), AK(good_boy_issuers_origin, good_boy_issuers_key))

A custom operation accepts as input a number of statements (the Condition); each statement has a number of arguments, which may be constants or anchored keys; and an anchored key in turn can optionally be decomposed as a pair of an Origin and a Key.

In the "original example" above, the anchored keys good_boy_issuers and receiver are not broken down, but AK(pod, "_type"), AK(pod, "_signer"), AK(pod, "friend") are. The purpose of breaking them down, in this case, is to force the three anchored keys to come from the same pod.

In the "compiled example", all the anchored keys have been broken down into origins and keys.

In general, in the front-end language, the "arguments" to an operation define a list of identifiers with types. Every statement in the "condition" must have valid arguments of the correct types: either constants, or identifiers defined in the "arguments".

In order to apply the operation, the user who wants to create a POD must give acceptable values for all the arguments. The POD prover will substitute those values for all the statements in the "Condition" and check that all substituted statements previously appear in the POD. If this check passes, the output statement is then a valid statement.

What applying the operation looks like on the back end

On the back end the "compiled example" deduction rule is converted to a sort of "template":

ArgsConditionOutput
*1 (pod),
*2 (good_boy_issuers_origin),
*3 (good_boy_issuers_key),
*4 (receiver_origin),
*5 (receiver_key)
ValueOf(AK(*1, "_type"), SIGNATURE),
Contains(AK(*2, *3), AK(*1,"_signer")),
Equals(AK(*1, "friend"), AK(*4, *5))
GoodBoy(AK(*4, *5), AK(*2, *3))

If you want to apply this deduction rule to prove a GoodBoy statement, you have to provide the following witnesses in-circuit.

  • Copy of the deduction rule
  • Values for *1, *2, *3, *4, *5.
  • Copy of the three statements in the deduction rule with *1, *2, *3, *4, *5 filled in
  • Indices of the three statements ValueOf, Contains, Equals in the list of previous statements.

And the circuit will verify:

  • *1, *2, *3, *4, *5 were correctly substituted into the statements
  • The three statements ValueOf, Contains, Equals do indeed appear at the claimed indices.
1

In previous versions of these docs, "operations" were called "deduction rules".