Skip to content

Commit d3bc892

Browse files
tideofwordsax0ed255
authored
Aard custom (#49)
* Merge changes to docs * Fix typo * Correct SUMMARY so it compiles; update .gitignore * Clean up statements.md Make syntax and notation consistent with Rust source code. * Fix statements for Merkle trees and compound types * First draft of custom statements and small updates to signedpod.md * Update book/src/merkletree.md Co-authored-by: Ahmad Afuni <root@ahmadafuni.com> * merklestatements correct typo Co-authored-by: Ahmad Afuni <root@ahmadafuni.com> * add todo for gadget ids Co-authored-by: Ahmad Afuni <root@ahmadafuni.com> * Separate out custom statements version 1 * More details on custom statements version 1 * new file custom2 * Partial draft of version 2 * First draft of version 2 spec, it's kind of a mess * Another version of the custom predicates spec * Update book/src/custom2.md Co-authored-by: Eduard S. <eduardsanou@posteo.net> * Simple example of deduction rule applied in circuit * Implement Edu's comments on custom predicates * Backend predicates must be defined in groups * Add more examples * Two diff statements using same constant * Remove deprecated example --------- Co-authored-by: Ahmad Afuni <root@ahmadafuni.com> Co-authored-by: Eduard S. <eduardsanou@posteo.net>
1 parent c101d94 commit d3bc892

File tree

10 files changed

+543
-0
lines changed

10 files changed

+543
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
Cargo.lock
33
.DS_Store
44
aardnotes.md
5+
notes

book/src/SUMMARY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- [Introduction](./introduction.md)
44

55
# Specification
6+
- [Front and back end](./front_and_back.md)
67
- [The frontend structure of a POD]()
78
- [Frontend POD value types](./values.md)
89
- [Anchored keys](./anchoredkeys.md)
@@ -13,6 +14,11 @@
1314
- [Statements](./statements.md)
1415
- [Statements involving compound types and Merkle trees](./merklestatements.md)
1516
- [Operations](./operations.md)
17+
- [Simple example](./simpleexample.md)
18+
- [Custom statements and custom operations](./custom.md)
19+
- [Version 1](./custom1.md)
20+
- [Version 1, written again](./custom1b.md)
21+
- [Custom statement example](./customexample.md)
1622
- [POD types](./podtypes.md)
1723
- [SignedPOD](./signedpod.md)
1824
- [MainPOD](./mainpod.md)

book/src/custom.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,19 @@
11
# Custom statements and custom operations
2+
3+
Users of the POD system can introduce _custom predicates_ (previously called _custom statements_) to express complex logical relations not available in the built-in predicates. Every custom predicate is defined as the conjunction (AND) or disjunction (OR) of a small number of other statements.
4+
5+
When a custom predicate is introduced in a MainPod, it becomes available for use in that POD and all PODs that inherit[^inherit] from it.
6+
7+
On the frontend, a custom predicate is defined as a collection of conjunctions and disjunctions of statements. The definition can be recursive: the definition of a predicate can involve the predicate itself, or the definitions of several predicates can depend on each other.
8+
9+
At the backend level, every definition of a predicate is either a conjunction or a disjunction of statements. To convert a frontend custom predicate to the backend, the middleware may need to introduce _sub-predicates_.
10+
11+
On the backend, custom predicates are defined in _groups_. A group can contain one or more custom predicates and their associated sub-predicates. Recursive definition is only possible within a group: the definition of a predicate in a group can only depend on previously existing predicates, itself, and other predicates in the same group.
12+
13+
## Custom predicates and their IDs
14+
15+
A custom predicate, like a built-in predicate, is identified by a _name_ on the front end and an _identifier_ on the back end. The identifier is a cryptographic hash of the definition of the group.
16+
17+
18+
[^inherit]: What to call this? One POD "inherits" from another?
19+

book/src/custom1.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Custom operations (or: how to define a custom predicate): VERSION 1
2+
3+
(Note: At the moment, we consider a "custom operation" to be exactly the same thing as the "definition of a custom predicate.")
4+
5+
A custom operation [^operation] is a rule that allows one to deduce a custom statement from one or more existing statements according to a logical rule, described below.
6+
7+
> Note: Unlike built-in operations, it is not possible to perform arbitrary calculations inside a custom operation.
8+
9+
The syntax of a custom operation is best explained with an example.
10+
11+
Original example with anchored keys, origins, and keys.
12+
| Args | Condition | Output |
13+
|------------|-----------------------------------------|----|
14+
| pod: Origin, <br> good_boy_issuers: AnchoredKey::MerkleRoot, <br> receiver: AnchoredKey | ValueOf(AK(pod, "_type"), SIGNATURE), <br> Contains(good_boy_issuers, AK(pod,"_signer")), <br> Equals(AK(pod, "friend"), receiver) | GoodBoy(receiver, good_boy_issuers) |
15+
16+
Compiled example with only origins and keys.
17+
| Args | Condition | Output |
18+
|------------|-----------------------------------------|----|
19+
| pod: Origin, <br> good_boy_issuers_origin: Origin, <br> good_boy_issuers_key: Key::MerkleRoot, <br> receiver_origin: Origin, <br> receiver_key: Key | ValueOf(AK(pod, "_type"), SIGNATURE), <br> Contains(AK(good_boy_issuers_origin, good_boy_issuers_key), AK(pod,"_signer")), <br> Equals(AK(pod, "friend"), AK(receiver_origin, receiver_key)) | GoodBoy(AK(receiver_origin, receiver_key), AK(good_boy_issuers_origin, good_boy_issuers_key)) |
20+
21+
A custom operation accepts as input a number of statements (the `Condition`);
22+
each statement has a number of arguments, which may be constants or anchored keys; and an [anchored key](./anchoredkeys.md) in turn can optionally be decomposed as a pair of an Origin and a Key.
23+
24+
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.
25+
26+
In the "compiled example", all the anchored keys have been broken down into origins and keys.
27+
28+
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".
29+
30+
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.
31+
32+
## What applying the operation looks like on the back end
33+
34+
On the back end the "compiled example" deduction rule is converted to a sort of "template":
35+
36+
| Args | Condition | Output |
37+
|------------|-----------------------------------------|----|
38+
| *1 (pod), <br> *2 (good_boy_issuers_origin), <br> *3 (good_boy_issuers_key), <br> *4 (receiver_origin), <br> *5 (receiver_key) | ValueOf(AK(*1, "_type"), SIGNATURE), <br> Contains(AK(*2, *3), AK(*1,"_signer")), <br> Equals(AK(*1, "friend"), AK(*4, *5)) | GoodBoy(AK(*4, *5), AK(*2, *3)) |
39+
40+
If you want to apply this deduction rule to prove a `GoodBoy` statement,
41+
you have to provide the following witnesses in-circuit.
42+
43+
- Copy of the deduction rule
44+
- Values for *1, *2, *3, *4, *5.
45+
- Copy of the three statements in the deduction rule with *1, *2, *3, *4, *5 filled in
46+
- Indices of the three statements `ValueOf`, `Contains`, `Equals` in the list of previous statements.
47+
48+
And the circuit will verify:
49+
- *1, *2, *3, *4, *5 were correctly substituted into the statements
50+
- The three statements `ValueOf`, `Contains`, `Equals` do indeed appear at the claimed indices.
51+
52+
[^operation]: In previous versions of these docs, "operations" were called "deduction rules".

book/src/custom1b.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Custom operations (or: how to define a custom predicate), version 1, written again
2+
3+
[All constant integers in this spec are determined by circuit size and subject to change.]
4+
5+
On the frontend, a _custom predicate_ is defined as a combination of conjunctions and disjunctions of other custom predicates and possibly the predicate itself.
6+
7+
On the backend, _custom predicates_ are defined in groups of up to 10. Each custom predicate in the group is either the AND or OR of five predicates which are either pre-existing, or defined in the same group.
8+
9+
[Note: We could potentially allow the AND or OR of, say, two predicates instead of five. To make this work, we might need to have access to pod ID as a virtual key, see github issue #60]
10+
11+
## Arguments of custom predicates
12+
13+
The definition of a custom predicate might also be called an _operation_ or _deduction rule_. It includes two (or, potentially, say, five) statement templates as conditions. The arguments to the statement templates are decomposed as (origin, key) pairs: if statements are allowed to have arity at most 4, then the statement templates in a deduction rule will have at most 8 arguments (4 origins and 4 keys). The same holds for the output statement.
14+
15+
Each argument (origin or key) to an statement template is either a wildcard or a literal. In the backend, the wildcard arguments will be identified as *1, *2, *3, ....
16+
17+
## Examples
18+
19+
See [examples](./customexample.md)
20+
21+
## Hashing and predicate IDs
22+
23+
Each custom predicate is defined as part of a _group_ of predicates. The definitions of all statements in the group are laid out consecutively (see [examples](./customexample.md)) and hashed.
24+
25+
## How to prove an application of an operation
26+
27+
A POD contains a "tabular proof", in which each row includes a "statement" and a "reason". The "reason" is everything the circuit needs as a witness to verify the statement.
28+
29+
For a custom statement, the "reason" includes the following witnesses and verifications:
30+
- the definition of the statement, serialized (see [examples](./customexample.md))
31+
- if the statement is part of a group, the definition of the full group, serialized
32+
- verify that the hash of the definition is the statement ID
33+
- the definition will have some number of "wildcards" (*1, *2, ...) as arguments to statement templates; a value for each wildcard must be provided as a witness (each will be either an origin ID or key)
34+
- the circuit must substitute the claimed values for the wildcards, and the resulting statements (true statements with origins and keys) will appear as witnesses
35+
- the circuit must verify that all the input statement templates (with origins and keys) appear in the previous statements (in higher rows of the table)
36+
- the circuit also substitutes the claimed values for the wildcards in the output statement, and verifies that it matches the claimed output statement

book/src/custom2.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Custom operations (or: how to define a custom predicate): VERSION 2
2+
3+
# DO NOT USE THIS DOC
4+
# SAVING IN THE GITHUB JUST SO WE HAVE A COPY
5+
# WE ARE NOT USING THIS SPEC
6+
# DO NOT USE
7+
8+
## The local variable requirement
9+
10+
This spec differs from the main spec in that there are no anchored keys. However, there are still types `Origin` and `Key`.
11+
12+
An `Origin` refers to an input POD; in-circuit, the `Origin` is the pod ID of the pod.
13+
14+
A `Key` refers to a value within a POD.
15+
16+
With the exception of the special statement `ValueFromPodKey`, a key always refers to a value within the POD _self. In other words, a statement (except for `ValueFromPodKey`) cannot refer to a value from a previous POD, only to a value in the "namespace" of the current POD.
17+
18+
Roughly speaking, the statement
19+
```
20+
ValueFromPodKey(local_key, origin_id, key)
21+
```
22+
means that the value of `local_key` on the current POD (_self) is the same as the value of `key` on the POD `origin_id` -- in other words, it is basically the same as
23+
```
24+
Equals(AnchoredKey(_SELF, local_key), AnchoredKey(origin_id, key)).
25+
```
26+
27+
I say "basically the same" because, in this spec, it is possible to refer to both keys and origin IDs by reference.
28+
29+
## Referencing
30+
31+
Recall that in the front-end, a `Key` is a string that functions as an identifier (like a variable name in other languages), and a `Value` is the value of that variable -- an `Integer`, `String`, or compound value.
32+
33+
In the back-end, a `Key` is four field elements (computed as a hash of the front-end key); and a `Value` is again four field elements. Again, each `Key` has a unique `Value`.
34+
35+
A `Reference` statement allows a key to be reinterpreted as a value; it is analogous to a pointer in C.
36+
37+
The statement
38+
```
39+
Reference(reference_key, key)
40+
```
41+
means that `reference_key` is a key, whose associated value is the same as the key `key`.
42+
43+
## ValueFromPodKey, precisely this time
44+
45+
```
46+
ValueFromPodKey(local_key: KeyOrLiteral::String, origin_id: KeyOrLiteral::OriginID, key: KeyOrLiteral::String).
47+
```
48+
49+
means that the _values_ of `local_key` and `key` are _keys_, the _value_ of `origin_id` is an _origin ID_, and the value assigned to the key `local_key` on the present POD is the same as the value assigned to the key `key` on the pod `origin_ID`.
50+
51+
An example with literals:
52+
```
53+
ValueFromPodKey("local_ssn", 0x4030, "ssn")
54+
```
55+
means that the pod `0x4030` has a key called `ssn`, the local pod has a key `local_ssn`, and they have the same value.
56+
57+
An example with keys, that expresses the same semantic meaning:
58+
```
59+
ValueOf(local_varname, "local_ssn")
60+
ValueOf(remote_varname, "ssn")
61+
ValueOf(gov_id_pod_id, 0x4030)
62+
ValueFromPodKey(local_varname, gov_id_pod_id, remote_varname)
63+
```
64+
65+
## Summary of additional statements in this spec
66+
67+
```
68+
ValueFromPodKey(local_key: KeyOrLiteral::String, origin_id: KeyOrLiteral::OriginID, key: KeyOrLiteral::String).
69+
```
70+
71+
72+
In addition to the built-in statements in the [main spec](./statements.md):
73+
74+
There is one additional front-end type: `OriginID`. As the name suggests, it contains the "origin ID" of a POD.
75+
76+
There are two additional built-in statements:
77+
```
78+
Reference(reference_key: Key::String, key: Key)
79+
80+
ValueFromPodKey(local_key: KeyOrLiteral::String, origin_id: KeyOrLiteral::OriginID, key: KeyOrLiteral::String).
81+
```
82+
83+
```
84+
Reference(reference_key, key)
85+
```
86+
means that the *value* of `reference key` is the *key name* of `key`.
87+
88+
```
89+
ValueFromPodKey(local_key, origin_id, key)
90+
```
91+
means that the key `local_key` in the local scope has the same value as the key `key` in the scope of the pod `origin_id`.
92+
93+
## How to work with the local variable requirement
94+
95+
To make a statement about an inherited value (a value introduced in an ancestor POD), the value must be copied to a local value:
96+
97+
The statements below assert that "name" on pod1 and "friend" on pod2 are assigned the same value.
98+
```
99+
ValueFromPodKey(name_from_pod1, pod1, "name")
100+
ValueFromPodKey(friend_from_pod2, pod2, "friend")
101+
Equal(name_from_pod1, friend_from_pod2)
102+
```
103+
104+
## How to inherit local variables from a previous POD
105+
106+
In this design, an additional complication arises when you
107+
carry a value from one POD to another,
108+
and you want to keep track of the origin POD on which it originated.
109+
110+
To allow this operation, we introduce an additional deduction rule
111+
```
112+
InheritValueFromPodKey,
113+
```
114+
which works as follows.
115+
116+
Suppose "self" is the current POD and "parent_id" is the POD id of one of the input PODs to "self".
117+
118+
Suppose "parent" has, among its public statements, the statement
119+
```
120+
ValueFromPodKey(parent_name, origin, original_name)
121+
```
122+
and "self" has the statement (public or private)
123+
```
124+
ValueFromPodKey(self_name, parent_id, parent_name).
125+
```
126+
127+
Then ```InheritValueFromPodKey``` allows you to generate the following statement on "self":
128+
```
129+
ValueFromPodKey(self_name, origin, original_name).
130+
```

book/src/customexample.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
# Ethdos custom predicate, using binary AND and OR: example of a recursive group
3+
4+
```
5+
eth_dos_distance(src_or, src_key, dst_or, dst_key, distance_or, distance_key) = or<
6+
eth_dos_distance_ind_0(src_or, src_key, dst_or, dst_key, distance_or, distance_key),
7+
eth_dos_distance_base(src_or, src_key, dst_or, dst_key, distance_or, distance_key)
8+
>
9+
10+
eth_dos_distance_base(src_or, src_key, dst_or, dst_key, distance_or, distance_key) = and<
11+
eq(src_or, src_key, dst_or, dst_key),
12+
ValueOf(distance_or, distance_key, 0)
13+
>
14+
15+
16+
eth_dos_distance_ind_0(src_or, src_key, dst_or, dst_key, distance_or, distance_key) = and<
17+
eth_dos_distance(src_or, src_key, intermed_or, intermed_key, shorter_distance_or, shorter_distance_key)
18+
19+
// distance == shorter_distance + 1
20+
ValueOf(one_or, one_key, 1)
21+
SumOf(distance_or, distance_key, shorter_distance_or, shorter_distance_key, one_or, one_key)
22+
23+
// intermed is a friend of dst
24+
eth_friend(intermed_or, intermed_key, dst_or, dst_key)
25+
>
26+
```
27+
28+
This group includes three statements.
29+
30+
When the definition is serialized for hashing, the statements are renamed to SELF.1, SELF.2, SELF.3.
31+
32+
With this renaming and the wildcards, the first of the three definitions becomes:
33+
```
34+
SELF.1( *1, *2, *3, *4, *5, *6 ) = or<
35+
SELF.2( *1, *2, *3, *4, *5, *6 ) ,
36+
SELF.3( *1, *2, *3, *4, *5, *6 )
37+
>
38+
```
39+
and similarly for the other two definitions.
40+
41+
The above definition is serialized in-circuit and hashed with a zk-friendly hash to generate the "group hash", a unique cryptographic identifier for the group.
42+
43+
Then the individual statements in the group are identified as:
44+
```
45+
eth_dos_distance = groupHASH.1
46+
eth_dos_distance_base = groupHASH.2
47+
eth_dos_distance_ind = groupHASH.3
48+
```

0 commit comments

Comments
 (0)