# Delegationen

> Wie eine teure Funktion einen eigenen Schaltkreis erhält, ohne dass die Befehlsschaltkreise wachsen. Wie eine Delegation aufgerufen wird, der Anker, der jede Anfrage mit genau einem Aufruf paart, die sechs Schaltkreise und ihre Wirtschaftlichkeit.

Hashing und Arithmetik mit großen Ganzzahlen dominieren reale Arbeitslasten: In einem Ethereum-Mainnet-Block machten allein die Körpermultiplikation und -quadrierung von secp256k1 44 % der Zyklen aus, bevor sie delegiert wurden. Sie Befehl für Befehl zu beweisen ist möglich und langsam. Eine **Delegation** gibt einer solchen Funktion eine eigene Schaltkreisfamilie, die aus dem Gastprogramm (Guest) aufgerufen wird; die Befehlsschaltkreise bleiben so klein, und ein Programm bezahlt nur für die Delegationen, die es aufruft.

## Der Aufruf

Eine Delegation wird aufgerufen, nie dekodiert. Das Gastprogramm schreibt einen Frame aus 32-Bit-Wörtern in den RAM und setzt einen `ecall` ab, mit der Nummer der Delegation in `a7` und der Basisadresse des Frames in `a0`. Der `ecall` ist eine Zeile der Familie `ADD_SUB_LUI_AUIPC`, die **Anfrage**. Die Arbeit ist eine Zeile der eigenen Familie der Delegation, der **Aufruf**, der im anfragenden Zyklus jedes Frame-Wort liest und jedes Frame-Wort zurückschreibt, darunter die Ergebnisse. Ein Aufruf besitzt keinen Zyklus; er läuft in dem Zyklus mit, der ihn angefordert hat.

Frames sind wortausgerichtet und liegen vollständig im gewöhnlichen RAM; kein Frame überlappt also ein öffentliches Fenster oder die Hilfsdaten (Advice), und die Lese- und Schreibzugriffe des Frames sind gewöhnliche Speicherabfragen. Was eine Delegation berechnet hat, ist daher genau so gebunden wie jeder Store: über die eine Speicher-Multimenge.

## Der Anker

Anfragen und Aufrufe müssen sich eins zu eins paaren: Sonst könnten viele Anfragen gegen einen einzigen Aufruf aufgehen und Anfragen unausgeführt zurücklassen, oder ein nicht angefragter Aufruf könnte einen Frame überschreiben. Sie paaren sich über dieselbe Speicher-Multimenge, in einem **Ankerraum**, der allein dem Delegationstyp gehört und den kein Befehl erreichen kann:

| | Liest | Schreibt |
| --- | --- | --- |
| Anfrage, Zyklus `c` | `T(s, base, 0, 0)` | `T(s, base, 4c + 3, v)` |
| Aufruf | `T(s, base, 4c + 3, v′)` | `T(s, base, 0, 0)` |

Drei Gates auf der Anfrageseite legen ihren Lesezugriff auf Zeitstempel 0 und Wert 0 fest und lassen sie 0 in `a0` schreiben. Dann sind die mit 0 gestempelten Tupel genau die Lesezugriffe der Anfragen und die Antworten der Aufrufe; es gibt also ebenso viele Aufrufe wie Anfragen über denselben Basisadressen; und da keine zwei Anfragen einen Zyklus teilen, ist der Lesezugriff jedes Aufrufs genau der Schreibzugriff einer Anfrage. Jeder Aufruf sitzt an der Basisadresse und im Zyklus seiner Anfrage. Kein Gate in einem Delegationsschaltkreis musste überhaupt etwas von Anfragen wissen.

## Viele Aufrufe, eine Operation

Eine Operation, die für eine Zeile zu breit ist, besteht aus mehreren Aufrufen auf einem Frame, wobei ein Frame-Wort den Schritt benennt: Eine Permutation von keccak-f[1600] sind 24 Rundenaufrufe, eine SHA-256-Kompression 16 Aufrufe zu je vier Runden, eine vollständige Punktaddition drei Aufrufe. Kein Gate verbindet zwei Zeilen. Jeder Aufruf beweist seinen Schritt auf dem Frame so, wie er ihn vorfindet; seine Lesezugriffe liegen auf der einzigen Speicherhistorie jedes Worts, also liest er die Schreibzugriffe des vorherigen Schritts. Dass jeder Schritt läuft, und zwar in der richtigen Reihenfolge, muss der aufrufende Code sicherstellen, und der aufrufende Code ist Code des Gastprogramms, der als Befehle bewiesen wird. Das SDK setzt jede Operation aus mehreren Aufrufen aus einer einzigen Funktion ab; ein Gastprogramm ordnet die Schritte also nie von Hand.

## Statisch deklariert

Der Befehlsdurchlauf kann einen Aufruf nicht sehen, weil die Nummer ein Laufzeitwert von `a7` ist. Deshalb hinterlässt jeder Shim im SDK einen 12-Byte-Deklarationsdatensatz in einer eigenen Linker-Sektion, der nur erhalten bleibt, wenn der Shim erreichbar ist. Die Ableitung des Programms durchsucht das Image nach solchen Datensätzen, und eine deklarierte Familie wird Teil der Konfiguration, gebunden durch die Identität über die Bytes des Images. Eine gelinkte, aber nie aufgerufene Familie beweist null Shards; eine aufgerufene Nummer, deren Familie das Programm nie deklariert hat, hat keinen Beweis.

## Die sechs Schaltkreise

| Familie | Ein Aufruf | Aufgebaut aus |
| --- | --- | --- |
| `KECCAK_F` | eine Runde von keccak-f[1600] über einem Frame aus 51 Wörtern | Bytes: 1.020 `XOR8`-Lookups pro Runde; Rotationen als Linearformen über Bytes und maskierten Kopien |
| `SHA256_COMP` | vier Runden und vier Schedule-Wörter | Bytes und `XOR8`: 52 Verpflichtungen pro Runde, 32 pro Schedule-Wort; `Ch` und `Maj` als Linearformen in XORs |
| `POSEIDON2` | eine Permutation der Breite 3 | die Runden in den eigenen Schichten des Schaltkreises berechnet, drei Gate-Listen pro Runde, ohne Lookup; die einzige Delegation, die oberhalb ihrer ersten Schicht rechnet |
| `FR_ARITH` | eine `Fr`-Addition, -Multiplikation oder -Inversion in Montgomery-Form | Bitzerlegungen und Kanonizitätsketten gegen `p` |
| `MOD_MUL` | ein 256-Bit-`a·b mod m`, vier Ethereum-Moduln | 32-Bit-Limbs, ein Quotient, Überträge und eine Kanonizitätskette, die `out < m` beweist |
| `EC_ADD` | ein Drittel einer vollständigen Punktaddition auf secp256k1 oder BN254 G1 | die vollständige Formel von Renes–Costello–Batina als drei Reduktionen pro Zeile |

Einige Konstruktionen kehren in ihnen wieder. Eine **Ein-Code-Regel** dekodiert ein Frame-Wort, das einen von `k` Fällen benennt, in boolesche Selektoren, von denen genau einer gesetzt ist, weil sich Codes addieren: Ohne sie beantworten die Selektoren 1 und 3 eine Anfrage nach 4. Eine **Kanonizitätskette** beweist mittels Borrows über 32-Bit-Limbs, dass ein 256-Bit-Wert unter einem Modul liegt. Und jedes geschriebene Wort ist unter `2^32` beschränkt, damit der RAM aus Wörtern besteht, worauf sich jede Befehlsfamilie verlässt.

## Die Wirtschaftlichkeit

Die Höhe einer Delegationsfamilie bestimmt, wie viele Aufrufe ein Shard aufnimmt, und ein Shard kostet seine Höhe, unabhängig von seiner Belegung:

| Familie | Höhe | Einheiten pro Shard | Shard-Beweis |
| --- | --- | --- | --- |
| `KECCAK_F` | `2^18` | 10.922 Permutationen | 381.100 B |
| `SHA256_COMP` | `2^18` | 16.384 Kompressionen | 189.988 B |
| `EC_ADD` | `2^16` | 21.845 Additionen | 434.916 B |
| `MOD_MUL` | `2^16` | 65.536 Multiplikationen | 135.220 B |
| `POSEIDON2` | `2^8` | 256 Permutationen | 664.780 B |
| `FR_ARITH` | `2^8` | 256 Operationen | 266.292 B |

Für eine Familie mit vielen Aufrufen ist der größere Shard der günstigere: Ein `KECCAK_F`-Beweis wächst von `2^16` auf `2^18` kaum. Der Preis ist Speicher. Der Vorwärtsdurchlauf eines `KECCAK_F`-Shards der Höhe `2^18` umfasst 42 GiB, und zwei davon, gleichzeitig in Bearbeitung, bestimmten die Spitze des gemessenen Blocks.

## Was delegiert wird und was nicht

Bibliothekscode erreicht die Delegationen über gepatchte Kopien von `k256`, `ark-ff` und `revm-precompile`: Die secp256k1-Recovery wird zu `k256`-Code über `MOD_MUL` und `EC_ADD`, und ein BN254-Pairing wird zu `ark-bn254`-Code über `MOD_MUL`. `MULMOD` der EVM mit beliebigem Modul, `MODEXP`, BLS12-381 und jedes Signaturverfahren als Ganzes laufen als Befehle. Eigene Signaturunterstützung für Gastprogramme ist Teil der [Flugbahn zu v2.0.0](https://apogee.gweb3networks.com/docs/quantum-leap/signatures).

Die Spezifikation: [Delegations-ABI](https://apogee.gweb3networks.com/docs/auditors/spec/delegation), [Delegationsschaltkreise](https://apogee.gweb3networks.com/docs/auditors/spec/delegation-circuits).
