Hat die Einführung von VerifyScript zu einer rückwärtsinkompatiblen Änderung des Konsenses geführt?

Meine Frage bezieht sich auf die Änderungen an Bitcoin, die durch den Commit-Bereich vorgenommen wurden [a75560d8, 6ff5f718], und ihre Auswirkungen auf den Konsens. Aus dieser Reihe von vier Commits (alle Satoshi zugeschrieben) während der Daten vom 30. bis 31. Juli 2010 sind nur die ersten und letzten Commits für die Frage relevant (die beiden in der Mitte scheinen eine Änderung des Build-Systems zu sein, kein Code).

Die beiden Commits (erster und zweiter von nun an) a75560d8werden 6ff5f718der Behebung einiger Schwachstellen in der Semantik und Ausführung von Bitcoin-Skripten zugeschrieben. Zum Beispiel im ersten Commit:

  1. Einschränkungen wurden auf Push- und Stack-Größen angewendet
  2. Der sogenannte Script-Versioning-Mechanismus wurde entfernt und NOP#stattdessen die verschiedenen Opcodes hinzugefügt
  3. Auf Bignum-Operationen wurden Einschränkungen angewendet
  4. OP_RETURNwurde vom bloßen Beenden des Skripts zum Zurückkehren falsefür die gesamte Ausführung geändert

Im zweiten Commit:

  1. Einschränkungen wurden auf die maximale Anzahl von Opcodes im Skript angewendet
  2. Die Ausführung unbekannter Opcodes wurde so eingestellt, dass sie falsefür die Ausführung zurückkehrt
  3. Die maximale Größe des Skripts wurde um die Hälfte auf 10kb reduziert
  4. Eine Änderung der *SHIFTOpcodes wurde vorgenommen (ich verstehe es aber nicht wirklich)
  5. In Bitcoin wurde eine aufgerufene Funktion VerifyScripteingeführt, die das Verhalten der Skriptausführung änderte

Diese Frage bezieht sich auf diese Liste von Änderungen und ob eine davon möglicherweise nicht abwärtskompatibel ist. Insbesondere werde ich mich auf die Änderung (5) aus dem zweiten Commit konzentrieren: 6ff5f718:script.cpp

Vor dieser Änderung wurden zum Zeitpunkt der Txout-Einlösung die Eingaben einer einlösenden Transaktion scriptSigund die Prevouts scriptPubKeyzu einem einzigen Skript zusammengefügt OP_CODESEPARATOR, indem ein zwischen ihnen platziert und das Ergebnis an das übergeben wurde, EvalScriptdas das Skript ausführen würde.

Innerhalb EvalScriptwurde ein leerer Stack erstellt und die Ergebnisse der verschiedenen im Skript ausgeführten Operationen werden darauf geschoben. Wenn die Skriptausführung ohne Fehler abgeschlossen wird, wird ein boolescher Wert zurückgegeben; Wenn der Stapel nicht leer ist, wird das oberste Element an eine CastToBoolFunktion übergeben und zurückgegeben ( trueoder false), und wenn der Stapel leer ist, falsewird zurückgegeben.

Nach der Änderung VerifyScriptumschließt die Funktion zwei separate Aufrufe von EvalScript, running scriptSigund scriptPubKeyseparat nacheinander. Der leere Stack, der zuvor in erstellt wurde, EvalScriptwird stattdessen in erstellt VerifyScript, bevor eine Skriptausführung beginnt, und die abschließende Überprüfung des Inhalts des Stacks (oder dessen Fehlen) und CastToBoolwurde auch nach verschoben VerifyScript, bis zu dem Punkt, nachdem beide scriptSigihre scriptPubKeyAusführung abgeschlossen haben.

In wird VerifyScriptzunächst die scriptSigvon der Einlösetransaktion EvalScriptzusammen mit dem leeren Stapel an übergeben. Die Maschine führt das Skript aus und die Ergebnisse der Operationen werden auf den Stack geschoben, der damit übergeben wurde. Wenn während der Ausführung keine Fehler aufgetreten sind, truewird zu zurückgegeben VerifyScript. Zweitens, wenn tatsächlich truezurückgegeben wurde, scriptPubKeywird die aus der Finanzierungstransaktion EvalScriptzusammen mit dem Stack an übergeben, der an diesem Punkt den Inhalt enthält, der von scriptSigder Ausführung übrig geblieben ist. Der scriptPubKeywird ausgeführt, der den Inhalt des Stapels weiter manipuliert und, wenn keine Fehler aufgetreten sind, zu truezurückkehrt VerifyScript. Zuletzt, wenn truezurückgegeben wurde, dann die Prüfung auf leeren Stapel undCastToBoolabgeschlossen sind, was das Endergebnis der Skriptüberprüfung bestimmt.

Als Grund für diese Änderung wurde die Behebung einer möglichen Schwachstelle in Script : SE answer , BCTalk - Thread genannt . Obwohl es orthogonal zu dieser Frage ist, habe ich deshalb die Liste der Änderungen von commit eingefügt a75560d8.

Es ist nicht allzu schwer zu erkennen, dass diese Änderung in Anbetracht einfacher Skripte (ohne Prüfzeichen) tatsächlich abwärtskompatibel ist. Im schlimmsten Fall könnten einige Skripte unverbrauchbar werden, aber es scheint keinen Fall zu geben, in dem ein zuvor unverbrauchtes Skript infolge dieser Änderung unverbrauchbar werden könnte.

Auch bei Skripten mit einem Checksig wie und Multisig findet der Opcode immer in statt , was vor dieser Änderung p2pkdurch ein von den Elementen von getrennt war . Anscheinend blieb die Semantik gleich – bis etwa zwei Jahre später.p2pkhCHECKSIGscriptPubKeyscriptSigOP_CODESEPARATOR

OP_CHECKSIG Wenn Sie sich die Wiki-Seite für wiki-checksig ansehen , erklären die Schritte (2) bis (4), wie Sie von zu wechseln scriptCodeund subScriptwas insbesondere passiert, wenn ein OP_CODESEPARATORin existiert scriptCode:

  1. Ein neues Unterskript wird aus dem erstellt scriptCode(das scriptCodeist das tatsächlich ausgeführte Skript – entweder das scriptPubKeyfür Nicht-Segwit-, Nicht-P2SH-Skripts oder das redeemscriptin Nicht-Segwit-P2SH-Skripts). Das Skript unmittelbar nach dem zuletzt analysierten OP_CODESEPARATORbis zum Ende des Skripts ist die subScript. Wenn es keine gibt, OP_CODESEPARATORwird das gesamte Skript zumsubScript
  2. Alle Vorkommen von sigwerden aus subScript gelöscht, falls vorhanden (es ist kein Standard, eine Signatur in einem Eingabeskript einer Transaktion zu haben).
  3. Alle verbleibenden OP_CODESEPARATORSwerden entferntsubScript

Nun, vor dem Commit 6ff5f718, wenn scriptSigund scriptPubKeyzu einem einzigen Skript zusammengefügt wurden, scriptCodewürde das so aussehen:

<scriptSig> CODESEPARATOR <scriptPubKey>

Bei Checksig-Operationen, die in stattfinden scriptPubKey, subScriptist alles rechts von CODESEPARATOR- im Grunde <scriptPubKey>sich selbst (es sei denn, es gibt mehr Codeseparatoren oder wenn Signaturen, die von einem Checksig verbraucht werden, auch in existieren scriptPubKey).

Nach dem Commit gäbe es tatsächlich zwei separate Ausführungen mit scriptSigfirst und second, wobei jede Ausführung ihre eigene und anschließend ihre eigene scriptPubKeyhätte . Nun, da Checksig-Operationen immer noch nur in ausgeführt werden , scheint es so zu bleiben, aber was würde passieren, wenn ein Opcode in ausgeführt wird ?scriptCodesubScriptscriptPubKeysubScriptCHECK(MULTI)SIGscriptSig

Am 24. Januar 2012 wurde Block 163685 abgebaut, der die Transaktion enthielt eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb. Diese Transaktion, die beiden unmittelbar danach und ihre Finanzierung, bei b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84dder ebenfalls im selben Block abgebaut wurde, werden alle in BIP-17 erwähnt , was eine alternative Implementierung der p2shSemantik ist:

  • OP_CHECKHASHVERIFYdefiniert den vorhandenen OP_NOP2Opcode neu und funktioniert bei der Ausführung wie folgt:

  • Hashen Sie zuerst das Ende des vorherigen Skripts (im allgemeinen Fall ; scriptSigwenn kein vorheriges Skript vorhanden ist, wird eine Nullzeichenfolge gehasht), beginnend mit dem zuletzt ausgewerteten OP_CODESEPARATOR(oder ab dem Anfang des Skripts, wenn keines OP_CODESEPARATORvorhanden war).

  • Vergleichen Sie dies dann mit dem Element oben auf dem Stapel (wenn es keines gibt, schlägt das Skript sofort fehl)
  • Wenn die Hashes übereinstimmen, tun Sie nichts, fahren Sie fort, als ob ein OP_NOP; Wenn sie nicht übereinstimmen, schlägt das Skript sofort fehl. Beachten Sie, dass im Fall eines übereinstimmenden Hashs das oberste Stack-Element (der Hash, mit dem verglichen wird) nicht vom Stack entfernt wird. Dies dient der Abwärtskompatibilität.

Beachten Sie, dass in bip17 das redeemScriptals tatsächlich ausführbares Skript in angegeben wird scriptSigund nicht als einzelner Daten-Push eines Blobs, wie es bei bip16 der Fall ist. Auch wenn ältere Knoten nicht erzwingen, dass hash160of scriptSiggleich dem 20-Byte-Wert von prevout ist scriptPubKey, müssten sie es dennoch ausführen und alle CHECK(MULTI)SIGdarin enthaltenen Operationen validieren.

Ich gehe hier davon aus, dass diese bip17-Beispieltransaktionen von Knoten abgebaut und validiert wurden, auf denen Software ausgeführt wurde, die im Januar 2012 neu war, aber nehmen wir an, dass es noch Knoten im Netzwerk gibt, auf denen ältere Software ausgeführt wird, und insbesondere solche, auf denen Versionen und niedriger ausgeführt werden v0.3.6. Wären diese älteren Knoten in der Lage, sich mit den neueren Knoten über die Gültigkeit von Block 163685 zu einigen?

Wenn wir die Finanzierungstransaktion durchgehen b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84dund die Ausgabe bei Index 1 betrachten, sehen wir die scriptPubKey( NOP2ist eigentlich eine nicht aktive OP_CHECKHASHVERIFY)

0x14 0x2a9bc5447d664c1d0141392a842d23dba45c4f13 NOP2 DROP

Und in der Ausgabentransaktion eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccbhaben wir von der Eingabe bei Index 1 diescriptSig

0 0x47 0x30440220276d6dad3defa37b5f81add3992d510d2f44a317fd85e04f93a1e2daea64660202200f862a0da684249322ceb8ed842fb8c859c0cb94c81e1c5308b4868157a428ee01 CODESEPARATOR 1 0x21 0x0232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a 1 CHECKMULTISIG

Überlegen Sie zuerst, wie Software von und höher die für die in v0.3.7konstruieren würde . Wir beginnen mit a , das genau ist , und da a ausgeführt wird, wird es zu:subScriptCHECKMULTISIGscriptSigscriptCodescriptSigCODESEPARATORsubScript

1 0x21 0x0232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a 1 CHECKMULTISIG

Überlegen Sie nun, wie Software von v0.3.6und darunter die subScriptfür dasselbe erstellen würde CHECKMULTISIG. Unsere scriptCodebesteht nicht mehr nur aus scriptSig, sondern aus einer Verbindung von ihr und scriptPubKeydurch eine CODESEPARATOR. Das scriptCodewürde so aussehen:

0 0x47 0x30440220276d6dad3defa37b5f81add3992d510d2f44a317fd85e04f93a1e2daea64660202200f862a0da684249322ceb8ed842fb8c859c0cb94c81e1c5308b4868157a428ee01 CODESEPARATOR 1 0x21 0x0232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a 1 CHECKMULTISIG CODESEPARATOR 0x14 0x2a9bc5447d664c1d0141392a842d23dba45c4f13 NOP2 DROP

Erinnern Sie sich an die Regeln aus dem Wiki. An dem Punkt, an dem CHECKMULTISIGausgeführt wird, subScriptist alles von dem Punkt nach der letzten Ausführung CODESEPARATORbis zum Ende des Skripts, wobei alle CODESEPARATORs auf der rechten Seite des Prüfzeichenoperators entfernt wurden:

1 0x21 0x0232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a 1 CHECKMULTISIG 0x14 0x2a9bc5447d664c1d0141392a842d23dba45c4f13 NOP2 DROP

Wenn dies korrekt ist, scheinen sich alte Knoten mit Versionen v0.3.6und niedriger nicht darauf einigen zu können, welche sighashfür diese Ausgabetransaktion die richtige ist, aber da ich realistisch gesehen keine solche alte Softwareversion ausführen kann, kann ich mir nicht absolut sicher sein. Ich habe eine aktuelle Bitcoin-Core-Version mit einigen Änderungen gepatcht, die es mir ermöglichten, die Validierung von tx zu simulieren eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb, und wie erwartet hat sie die Validierung nicht bestanden. Ich werde die beiden Seufzer unten zusammen mit dem öffentlichen Schlüssel und der Signatur zum Vergleich hinzufügen.

Meine Frage ist also , gibt es etwas, das ich falsch verstehe oder vielleicht übersehen habe, das es Prä- v0.3.7und Postknoten ermöglichen würde v0.3.7, sich über die Gültigkeit von Block 163685 zu einigen?

(auch relevant: diese SE-Frage )


Pubkey : 0232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a
Signature (DER) : 30440220276d6dad3defa37b5f81add3992d510d2f44a317fd85e04f93a1e2daea64660202200f862a0da684249322ceb8ed842fb8c859c0cb94c81e1c5308b4868157a428ee
Signature (r,s) : (276D6DAD3DEFA37B5F81ADD3992D510D2F44A317FD85E04F93A1E2DAEA646602, F862A0DA684249322CEB8ED842FB8C859C0CB94C81E1C5308B4868157A428EE)

Vorseufzen v0.3.7(bestätigt nicht):

01000000
02
4de8b0c4c2582db95fa6b3567a989b664484c7ad6672c85a3da413773e63fdb8 00000000
00
FFFFFFFF
4de8b0c4c2582db95fa6b3567a989b664484c7ad6672c85a3da413773e63fdb8 01000000
3C 51210232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a51ae142a9bc5447d664c1d0141392a842d23dba45c4f13b175
FFFFFFFF
02
E0FD1C0000000000 19 76a914380cb3c594de4e7e9b8e18db182987bebb5a4f7088ac
C0C62D0000000000 17 142a9bc5447d664c1d0141392a842d23dba45c4f13b175
00000000
01000000

sha256  : 1EB276326D72CB358F6C275D6542F76EED4E36364727CB82D40A116244EBDDB5
sha256d : 11491E74778E1FA8C40CC8E07E1F835677CF1AC81F54255EC1C7125C1894939A

Post- v0.3.7Seufzer (bestätigt) :

01000000
02
4de8b0c4c2582db95fa6b3567a989b664484c7ad6672c85a3da413773e63fdb8 00000000
00
FFFFFFFF
4de8b0c4c2582db95fa6b3567a989b664484c7ad6672c85a3da413773e63fdb8 01000000
25 51210232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a51ae
FFFFFFFF
02
E0FD1C0000000000 19 76a914380cb3c594de4e7e9b8e18db182987bebb5a4f7088ac
C0C62D0000000000 17 142a9bc5447d664c1d0141392a842d23dba45c4f13b175
00000000
01000000

sha256  : 3858A592C15A47F3058010689883DECCD4AF41F5367B9776429613DFB3339883
sha256d : 8D7AD159644D312664472F90E7B823071B1361725CAC78531569FD836EA90350
Ich glaube, Sie haben recht. Dies ist ein gutes Beispiel dafür, warum Änderungen an allem, was mit Konsens zu tun hat, sehr sorgfältig geprüft werden müssen, um keine mit Konsens inkompatiblen Änderungen einzuführen.
Danke, dass du es durchgelesen hast. Es wird sehr geschätzt.
Sehr gute Erkundung! Gut gemacht!

Antworten (1)

Fast alle Commits an den Client vor den Commits, die Sie hier genannt haben, waren Hard Forks aufgrund der Einbeziehung von OP_VERund OP_VERIF, selbst wenn die Änderung am Client ansonsten keine inkompatible Änderung war.

        case OP_VER:
        {
            CBigNum bn(VERSION);
            stack.push_back(bn.getvch());
        }
        break;

Ganz einfach, der Opcode wird VERSIONauf den Stack geschoben, was die Versionsnummer des Codes ist, der zur Validierung verwendet wird. Diese Nummer wurde mit jeder Veröffentlichung aktualisiert, sodass keine zwei Versionen vor ihrer Entfernung tatsächlich miteinander kompatibel sind.

Es gibt mehrere andere, die Sie nicht erwähnt haben, wie z. B. die nLockTimeÄnderung in eine vorzeichenlose Variable mit einem höhenbasierten Hard Fork, einige geringfügige Änderungen an den Implementierungen der Opcodes und das Entfernen von Double-Byte-Opcodes. Es ist sehr schwierig aufzuzählen, welche dieser Versionen tatsächlich miteinander kompatibel sind, aufgrund der großen Anzahl von Änderungen und der schlechten Werkzeuge, die wir haben, die mit diesen alten Versionen kompatibel sind.