Wallet
Schlüssel, Adressen und Senden — dazu der Verhaltensvertrag, den jede Wallet auf diesem Protokoll erfüllen muss. Jede Regel beschreibt eine Art, Geld zu verlieren, die das Protokoll zulässt und die eine Wallet daher verhindern muss.
Die Befehle#
zcd wallet new --out KEYFILE # create an encrypted key file
zcd wallet address --key KEYFILE # show an address
zcd wallet balance --key KEYFILE # ask a node for a balance
zcd wallet send --key KEYFILE --to ADDR --amount N
zcd wallet sweep --key KEYFILE --to ADDR --one-shot
zcd wallet retire --key KEYFILE [--addr ADDR]
zcd ui --key KEYFILE # the same wallet in a browser
Die grafische Wallet ist keine zweite Implementierung. zcd wallet, zcd
ui und die Desktop-Anwendung sind drei Oberflächen über einer einzigen
wallet/session: jede baut ihr Zertifikat dort, und die Regelprüfungen laufen
innerhalb dieses Aufrufs. Eine grafische Wallet ist daher strukturell außerstande, freizügiger zu sein
als die CLI — nicht weil derjenige, der sie geschrieben hat, sorgfältig war, sondern weil es keinen zweiten
Codepfad gibt, auf dem sie es sein könnte.
Zwei Arten von Adresse#
| Version | Art | Verwenden für | Wiederverwendbar? |
|---|---|---|---|
0x01 | One-Shot | Eine einzelne erwartete Zahlung. Leiten Sie je empfangener Zahlung eine frische ab. | Nein. Eine Belastung verbrennt sie dauerhaft. |
0x02 | Persistent | Händler, Spendenadressen, Hot Wallets, Mining-Auszahlungen. | Ja, für immer. Kann nie in die Spent-Registry gelangen. |
One-Shot gegen persistent ist eine Wallet-Richtlinie, die als Adressbit sichtbar wird, nicht zwei
Ledger. Wallets verwenden standardmäßig 0x01 je empfangener Zahlung, denn das ist es, was eine
Zahlung mit der nächsten unverknüpfbar macht; wer ein Konto will, verwendet 0x02.
Die acht Regeln#
Jede Regel unten beschreibt eine Art, Geld oder Gebühren zu verlieren, die das Protokoll zulässt — bewusst, weil die Alternative schlechter war — und die eine Wallet daher verhindern muss. Die Referenz-Wallet setzt sie um. Sie dokumentiert sie nicht bloß.
Regel 1 — ganze Zellen sweepen#
Wenn Sie von einer 0x01-Adresse ausgeben, bewegen Sie das gesamte Guthaben
— an den Zahlungsempfänger und an eine Wechseladresse, die Sie kontrollieren. Das Zertifikat trägt ein
MARK_SPENT dieser Adresse, und nachdem es angewendet wurde, schlagen alle Lese- und Schreibvorgänge unter der
Adresse dauerhaft fehl. Es gibt keine zweite Transaktion.
Die Chain mildert das inzwischen ab: was eine verbrannte Adresse noch hält, wandert beim Festschreiben in die
eigene RefundTo-Zelle des Zertifikats — sowohl für das native Guthaben der Adresse als auch für jede
Zelle, die das Zertifikat selbst benennt. Ein unvollständiger Sweep vernichtet Reste also nicht mehr; er liefert sie an
Ihre Wechseladresse.
Ein Zertifikat hat ein RefundTo und darf One-Shot-Adressen mehrerer Unterzeichner verbrennen,
ein Rest unter Ihrer Zelle kann also an deren Zelle geliefert werden.
Signieren Sie nie ein Zertifikat mit, das eine One-Shot-Adresse von Ihnen verbrennt und an eine
Adresse erstattet, die Sie nicht kontrollieren.
Wovor diese Regel nicht retten kann: ein Guthaben in einem Asset, das das Zertifikat nie erwähnt. Die Assets unter einer Adresse sind nicht aus einem Slot ableitbar, ein unbenanntes Asset zu erreichen hieße also, die gesamte Zelltabelle in der einen Stufe zu durchsuchen, die nicht parallelisiert. Benennen Sie jedes Asset in dem Zertifikat, das die Adresse verbrennt.
Regel 2 — RefundTo muss eine Adresse sein, die Sie noch nutzen können#
Es muss entweder eine persistente Adresse benennen, die Sie kontrollieren, oder eine frische
One-Shot-Adresse, die dieses Zertifikat nicht verbrennt. Eine Verrechnung in eine verbrannte Zelle strandet den
Rest: der Fold verbrennt ihn, statt ihn in eine Zelle zu schreiben, die niemand lesen kann, und meldet ihn als
refund_burned statt refunded, sodass eine Wallet, die ein Guthaben abgleicht, es
erkennen kann.
Regel 3 — eine Adresse, eine erwartete Zahlung#
Zwei Pflichten, je eine auf jeder Seite:
- Empfangen. Leiten Sie je erwarteter Zahlung eine frische
0x01-Adresse ab. Veröffentlichen Sie nie eine zweimal. Sweepen oder stilllegen Sie keine Adresse, die Sie innerhalb der letztenttl_maxBlöcke offengelegt haben, es sei denn, Sie nehmen in Kauf, dass eine unterwegs befindliche Zahlung an sie übersprungen wird und ihren Absender belastet. - Senden. Weigern Sie sich, an eine One-Shot-Adresse zu zahlen, der Sie ansehen können, dass sie bereits gutgeschrieben oder ausgegeben wurde. Wenn ein Zahlungsempfänger Ihnen eine Adresse ein zweites Mal reicht, behandeln Sie das als Fehler, nicht als Bequemlichkeit.
Für alles, was mehr als einmal bezahlt wird — ein Händler, eine Spendenadresse, eine
Mining-Auszahlung —, verwenden Sie eine persistente (0x02) Adresse. Das ist die einzige
Regel, die die Angriffsfläche beseitigt, statt sie zu verengen.
Regel 4 — abhängige Ketten: bestätigen oder das Risiko tragen#
Erhöhen Sie Seq für jedes Zertifikat, das von einem vorherigen abhängt. Innerhalb eines
Blocks schreibt der Fold die Zertifikate eines Unterzeichners in Seq-Reihenfolge fest, eine abhängige Kette im
selben Block ist also gefahrlos. Über Blöcke hinweg ist sie es nicht: senden Sie Seq = n+1
erst, nachdem Seq = n bestätigt wurde, oder nehmen Sie in Kauf, dass das abhängige allein festgeschrieben
und übersprungen werden kann. Das ist kein Protokollmangel — der Unterzeichner hat das Veraltungsrisiko mit dem Signieren akzeptiert.
Regel 5 — das Maximum großzügig und die Priorität ehrlich setzen#
Jeder Markt nimmt zwei Preise. Das Maximum ist eine Solvenzschranke: sobald die Grundgebühr es übersteigt, ist das Zertifikat nicht mehr aufnehmbar und muss neu signiert werden. Die Priorität ist das, was ein Miner tatsächlich bezahlt bekommt.
- Das Maximum anzuheben kostet keine Gebühren — der Sicherheitspuffer ist umsonst.
- Aber die Einlage reserviert
gas × max, das Maximum begrenzt also, wie viel Guthaben für einen Fold-Schritt gesperrt wird. Bemessen Sie es am tatsächlich verfügbaren Guthaben. - Ein Zertifikat mit langer TTL braucht mehr Luft nach oben als eines mit kurzer TTL, denn die Grundgebühr hat mehr Blöcke Zeit, sich zu bewegen.
Der Ausweg für kleine Guthaben besteht darin, das Fenster zu verkleinern, nicht die Sicherheit: signieren Sie mit kurzer TTL und signieren Sie bei Ablauf neu, tauschen Sie also Bindung gegen Latenz. Die falsche Antwort ist, die lange TTL zu behalten und das Maximum zu verkleinern, was das Zertifikat genau dann strandungsfähig macht, wenn sich der Markt bewegt — der Fall, für den der Puffer da war.
Regel 6 — Bewegungen kanonisch sortieren#
Das Protokoll erzwingt keine Reihenfolge für die Bewegungen einer Überweisung, ohne kanonische Reihenfolge erzeugt eine Wiederholung
derselben logischen Zahlung also eine andere ID, und die Wallet kann “bereits gesendet” nicht von
“zweimal gesendet” unterscheiden. wallet.Transfer sortiert nach Asset, Quelle, Ziel und dann Betrag, sodass eine
Wiederholung die ID reproduziert.
Eine Wiederholung muss die Signatur nicht reproduzieren: das Urbild der ID schließt die Signaturliste aus, eine mit frischer Nonce neu signierte Wiederholung ist also dieselbe ID, und das Netzwerk weist sie als das Duplikat ab, das sie ist. Idempotenz ist eine Eigenschaft, die das Protokoll bereitstellt, für jede Wallet und nicht nur für die sorgfältigen.
Regel 7 — nie einen Seed herausgeben#
Der Seed ist der Schlüssel. Wer ihn liest, besitzt alles, was beide seiner Adressen halten — die One-Shot- und die persistente, die auf der Chain unverbunden, aber aus demselben Schlüssel abgeleitet sind.
Schlüsseldateien sind immer verschlüsselt: Argon2id über die Passphrase, AES-256-GCM über den Seed, beide mit ihren Parametern in der Datei gespeichert, sodass ein Nutzer, den diese Binärdatei aussperrt, mit der Standardbibliothek jeder Sprache wiederherstellen kann. Es gibt kein Flag, um eine unverschlüsselt zu schreiben. Passphrasen werden ohne Echo vom Terminal gelesen, nie aus einem Flag — eine Passphrase auf einer Kommandozeile steht in der Shell-Historie und in der Prozesstabelle.
Das Schreiben hält zwei Eigenschaften zugleich, und beide betreffen denselben Satz — eine davon zu verlieren, heißt Geld zu verlieren:
- Nie stillschweigend überschreiben. Eine Schlüsseldatei, die schon am Ziel liegt, bleibt unangetastet, und der Schreibvorgang schlägt fehl.
- Nie eine zerrissene hinterlassen. Der Seed geht in eine temporäre Datei im selben Verzeichnis, wird dort fsynct und erst dann in einer einzigen Dateisystemoperation unter seinem endgültigen Namen veröffentlicht.
Beides gilt auf jedem Dateisystem, gegen das die CLI gemessen wurde, FAT32 und exFAT eingeschlossen — den Formaten, die ein USB-Backup zur Kaltlagerung am ehesten verwendet.
FAT32 und exFAT haben keine Unix-Berechtigungsbits, die 0600, mit denen eine Schlüsseldatei angelegt
wird, überleben darauf also nicht — die Mount-Optionen entscheiden. Unter Linux lässt die voreingestellte
fmask sie typischerweise für alle lesbar; macOS mountet solche Volumes
noowners, was 0700 meldet und nichts durchsetzt. Wählen Sie die Passphrase
entsprechend. Sie haben auch kein Journal, wenn also ein Veröffentlichungsschritt auf FAT unterbrochen wird und Fehlschlag meldet,
prüfen Sie das Ziel, bevor Sie erneut ausführen.
Regel 8 — ablehnen, was das Netzwerk ablehnen wird#
Eine Wallet, die Erfolg für etwas meldet, das das Netzwerk nur verwerfen kann, hat den Fehlschlag dorthin verschoben, wo der Nutzer nie nachsehen wird. Zwei Ausprägungen davon, beide im Feld gefunden:
- Bytes, die kein Peer dekodieren kann. Der Codec ist eine Instanz für sich, mit Regeln, die der Validator nicht wiederholt. Der Builder behauptet die Eigenschaft nun direkt: was immer er ausgibt, der Dekoder nimmt es an.
- Eine Überweisung, die der Fold nur überspringen kann. Eine Überweisung oberhalb des Guthabens der Quelle verletzt keine Regel, jeder Node lässt sie also zu — und nichts weist einen Producer ab, der sie trotzdem aufnimmt, woraufhin der Fold sie zum
skip_feeabschließt, aus der Einlage verbrannt und an niemanden gezahlt. Der schlimme Fall ist nicht “die Gebühr wurde verbrannt und der Wert hat sich nie bewegt”
zcd wallet send --force umgeht diese eine Verweigerung und nichts sonst,
denn eine Einlage, deren Eintreffen innerhalb des TTL-Fensters erwartet wird, lässt dasselbe Zertifikat anwendbar werden, und die
Wallet kann nicht in die Zukunft sehen. Es kann ein ungültiges Zertifikat nicht gültig machen; es kann nur ein
gültiges einreichen, das übersprungen werden kann — und ein Skip ist nicht umsonst.
Einem Node vertrauen, den Sie nicht betreiben#
zcd und die Wallet sind keine Full Nodes. Sie glauben, was ein Node ihnen sagt, und eine
CLI, die per RPC mit einem Node spricht, den sie nicht selbst validiert, muss irgendetwas an den Antworten
dieses Nodes glauben — das ist kein zu behebender Fehler, das ist es, was eine Wallet ausmacht. Drei Abhilfen,
jede gegen eine bestimmte Lüge:
| Abhilfe | Was sie abfängt |
|---|---|
--devnet / --testnet / --params | Der Betreiber behauptet, für welches Netzwerk er signieren will, und jeder befragte Node wird gegen diese Behauptung geprüft. Die Netzwerkidentität wird nie einem Node allein entnommen. Das gilt auch für balance, nicht nur für die Befehle, die signieren. |
--confirm-rpc NODE | Benennt einen zweiten, unabhängigen Node. Guthaben und Spent-Flag jeder Adresse müssen von beiden identisch gemeldet werden, bevor irgendetwas weitergeht. Ein Node, dessen chain_id abweicht, wird abgewiesen, und ein --confirm-rpc, das den Endpunkt benennt, den --rpc bereits benennt, wird rundweg abgewiesen — ein Node, der sich selbst gegenprüft, stimmt mit sich selbst überein. |
| Die getippte Bestätigung | Vor dem Einreichen gibt zcd wallet sweep die genauen Zahlen aus und verlangt, dass Sie sweep tippen. --yes überspringt nur diese Abfrage, nie die Prüfungen darüber. |
Checkliste für eine Wallet-Implementierung#
Wenn Sie eine Wallet gegen dieses Protokoll schreiben, ist das hier der Vertrag:
- Das Ausgeben von
0x01bewegt das gesamte Guthaben, wobei die Einlagenreservierung dazuzählt — auch bei Programmen ganz ohne Bewegungen. RETIREweist ein Ziel ab, das noch ein Guthaben hält.- Die Guthabenmeldung des Nodes wird nicht als über jeden Zweifel erhaben behandelt: die Netzwerkidentität wird vom Betreiber behauptet, eine zweite Quelle ist gegenprüfbar, und die genauen Zahlen werden vor einem unumkehrbaren Sweep bestätigt.
RefundTowird vor dem Signieren als persistent-oder-frisch validiert.- Beim Empfangen wird je erwarteter Zahlung eine frische Adresse abgeleitet; beim Senden wird eine bereits gutgeschriebene oder ausgegebene One-Shot-Adresse abgewiesen.
- Händler- und Auszahlungsadressen sind standardmäßig
0x02. Seqwird je abhängigem Zertifikat erhöht, und die Wallet wartet auf die Bestätigung, bevor sie das nächste sendet — oder sagt laut, dass sie es nicht tut.- Gebührenmaxima werden aus verfügbarem Guthaben und TTL bemessen, nicht fest verdrahtet.
- Bewegungen werden kanonisch sortiert, sodass eine Wiederholung die Zertifikats-ID reproduziert.
- Schlüsseldateien sind verschlüsselt, werden dauerhaft und ohne stilles Überschreiben geschrieben, und Passphrasen erreichen nie ein Flag.
- Nichts wird signiert, aus dessen eigener Kodierung nicht zurückdekodiert werden kann, und nichts wird eingereicht, was das Quellguthaben nicht deckt — wobei jede Übersteuerung benannt, eng gefasst und in der Vorschau gemeldet wird.