Wie wird der Index für OP_CHECKMULTISIG berechnet?

Ich versuche, das Bitcoin-Protokoll zu lernen, indem ich es implementiere, und ich habe OP_CHECKSIGfunktioniert (Anweisungen in https://en.bitcoin.it/wiki/OP_CHECKSIG befolgen ), aber ich kann keine Transaktionseingabe mit OP_CHECKMULTISIGto pass erhalten Signaturprüfung.

Insbesondere stecke ich bei der zweiten tx_input der Transaktion fest eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb , und ich vermute, dass der Schuldige subscript ist (derjenige, der das Signaturskript in der aktuellen Transaktion ersetzt).

So wird der Index in meiner Implementierung berechnet.

Generieren Sie zuerst das Skript durch Verketten [sigScript][OP_CODESEPARATOR][pubkeyScript](ich verstehe, dass sich die Skriptausführung daraus entwickelt hat).

Ein (geparstes) Skript für die Eingabe würde also so aussehen:

Frame 00: 0(00) Frame 01: 0000 - 30 44 02 20 27 6d 6d ad 3d ef a3 7b 5f 81 ad d3 0010 - 99 2d 51 0d 2f 44 a3 17 fd 85 e0 4f 93 a1 e2 da 0020 - ea 64 66 02 02 20 0f 86 2a 0d a6 84 24 93 22 ce 0030 - b8 ed 84 2f b8 c8 59 c0 cb 94 c8 1e 1c 53 08 b4 0040 - 86 81 57 a4 28 ee 01 END Frame 02: OP_CODESEPARATOR(0xab) Frame 03: 1(0x51) Frame 04: 0000 - 02 32 ab dc 89 3e 7f 06 31 36 4d 7f d0 1c b3 3d 0010 - 24 da 45 32 9a 00 35 7b 3a 78 86 21 1a b4 14 d5 0020 - 5a END Frame 05: 1(0x51) Frame 06: OP_CHECKMULTISIG(0xae) Frame 07: OP_CODESEPARATOR(0xab) Frame 08: 0000 - 2a 9b c5 44 7d 66 4c 1d 01 41 39 2a 84 2d 23 db 0010 - a4 5c 4f 13 END Frame 09: OP_NOP2/OP_CHECKLOCKTIMEVERIFY(0xb1) Frame 10: OP_DROP(0x75)

Dann erstelle ich den Index vom nächsten OP_CODESEPARATORvor dem OP_CHECKMULTISIG, den ich bis zum Ende des Skripts ausführe. Also schließe ich Frame 03 bis Frame 10 ein.

Dann entferne ich andere OP_CODESEPARATORs aus dem Skript, also Frame 07.

Das bedeutet, dass diese Frames den Index bilden sollten:

Frame 03: 1(0x51) Frame 04: 0000 - 02 32 ab dc 89 3e 7f 06 31 36 4d 7f d0 1c b3 3d 0010 - 24 da 45 32 9a 00 35 7b 3a 78 86 21 1a b4 14 d5 0020 - 5a END Frame 05: 1(0x51) Frame 06: OP_CHECKMULTISIG(0xae) Frame 08: 0000 - 2a 9b c5 44 7d 66 4c 1d 01 41 39 2a 84 2d 23 db 0010 - a4 5c 4f 13 END Frame 09: OP_NOP2/OP_CHECKLOCKTIMEVERIFY(0xb1) Frame 10: OP_DROP(0x75)

Serialisiert sieht der letzte Index so aus:

0000 - 51 21 02 32 ab dc 89 3e 7f 06 31 36 4d 7f d0 1c 0010 - b3 3d 24 da 45 32 9a 00 35 7b 3a 78 86 21 1a b4 0020 - 14 d5 5a 51 ae 14 2a 9b c5 44 7d 66 4c 1d 01 41 0030 - 39 2a 84 2d 23 db a4 5c 4f 13 b1 75

Wenn dieses Subskript jedoch verwendet wird, um das Signaturskript zu ersetzen und schließlich Hash zu generieren, meldet die Funktion ecdsa_verify, dass die Signaturüberprüfung fehlschlägt (aber die Eingaben zulässig sind).

Da das gleiche Verfahren für funktioniert OP_CHECKSIG, habe ich wahrscheinlich etwas Offensichtliches übersehen, aber ich konnte dies beim Lesen des ursprünglichen Satoshi-Codes, der aktuellen Implementierung oder des Bitcoin-Wikis nicht herausfinden.

Wenn es hilft, mein Code für OP_CHECKMULTISIG ist hier: https://github.com/blaesus/tinybtc/blob/checkmultisig/src/script.c#L687

Eine einfachere Version (mit prevTx.pubkeyScript als Index) liefert auch keine gültigen Ergebnisse: https://github.com/blaesus/tinybtc/blob/validate/src/script.c#L344

Von welchen Rahmen redest du?

Antworten (2)

Die Hauptverwirrung hier kommt daher:

Generieren Sie zunächst das Skript durch Verketten[sigScript][OP_CODESEPARATOR][pubkeyScript]

Dann fügst du hinzu:

Ich verstehe, dass sich die Skriptausführung daraus entwickelt hat

Ich bin mir nicht sicher, ob Sie "Baut auf dieser Methode auf" oder "Hat diese Methode seitdem geändert" meinen, aber wenn es ersteres ist, könnte dies die unerwarteten Ergebnisse erklären, die Sie erhalten. Hoffentlich kann meine Antwort das klären.

Es ist wahr, dass die Skriptauswertung von Bitcoin für einen (relativ) kurzen Zeitraum so durchgeführt wurde, wie Sie es erwähnt haben, wo ein CODESEPARATORdazwischen gesetzt wurde scriptSigund scriptPubkeydas Ganze dann zu einem einzigen Skript verkettet wurde, das ausgeführt werden sollte, aber das wurde ziemlich früh geändert in Verpflichtung 6ff5f718b6a67797b2b3bab8905d607ad216ee21, die bis zum 31. Juli 2010 datiert.

Nach dieser Änderung, die eine Funktion mit dem Namen VerifyScriptin einführte script.cpp(heute ist die Logik in interpreter.cpp), scriptSigund scriptPubkeynicht mehr mit einem CODESEPARATORzwischen ihnen verkettet sind. Vielmehr werden sie als zwei getrennte Skripte nacheinander ausgeführt, wobei der Inhalt des Stacks vom ersten zum zweiten übertragen wird.

Der Unterschied ist subtil und ändert den Fluss der Standardeinlösungen nicht wirklich , wird aber bemerkbar, wenn eine CHECKSIGOperation innerhalb der durchgeführt wird scriptSig.

Beginnend mit der Finanzierungstransaktion

b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84d

Wir sehen, dass die zweite Ausgabe darin eine Zahlung an Folgendes scriptPubKeybei index ist 1:

14 2a9bc5447d664c1d0141392a842d23dba45c4f13
NOP2
DROP

OP_CLTVist noch nicht aktiv, also ist der Opcode 0xb1immer noch NOP2.

Die wird dann durch eingelöst

eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb

durch die Eingabe bei Index 1:

0
47
  30
  44
    02
    20 276d6dad3defa37b5f81add3992d510d2f44a317fd85e04f93a1e2daea646602
    02
    20 0f862a0da684249322ceb8ed842fb8c859c0cb94c81e1c5308b4868157a428ee
  01
CODESEPARATOR
1
21 0232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a
1
CHECKMULTISIG

In der kurzen Zeit vor dem Commit 6ff5f718würden die beiden Skripte mit einem CODESEPARATORzwischen ihnen stehenden verknüpft, um das vollständige Skript zu bilden, dann würde die Ausführung beginnen. Wir beginnen mit einem leeren Stapel und einem Zeiger auf den Anfang des Skripts, der den Anfang dessen markiert, was signiert wäre scriptCode:

  1. 0Auf den Stapel schieben
  2. Schieben Sie die Signatur auf den Stapel
  3. Ausführen CODESEPARATOR- markieren Sie diesen Punkt im Skript als Beginn von scriptCode(Überschreiben des vorherigen Werts, der am Anfang festgelegt wurde)
  4. 1Auf den Stapel schieben
  5. Schieben Sie den Pubkey auf den Stack
  6. 1Auf den Stapel schieben
  7. 1-von-1 ausführenCHECKMULTISIG

An dieser Stelle müssen wir die zu signierende scriptCodeerstellen. Die Regeln sind:

  1. Nehmen Sie den Index vom zuletzt ausgeführten CODESEPARATORbis zum Ende des Skripts
  2. Alles entfernenCODESEPARATORS
  3. Entfernen Sie alle Vorkommen der Signatur, die derzeit überprüft wird

Da die Signatur eigentlich vor dem letzten CODESEPARATORsteht, greift Regel #3 hier nicht, wohl aber Regel #1 und #2. Damit bleibt uns folgendes übrig scriptCode:

1
21 0232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a
1
CHECKMULTISIG
14 2a9bc5447d664c1d0141392a842d23dba45c4f13
NOP2
DROP

Das haben Sie sich ursprünglich ausgedacht, aber jetzt wollen wir sehen, wie sich die Änderung bei 6ff5f718auswirkt.

Denken Sie daran, dass wir es jetzt mit zwei separaten Skripten zu tun haben, aber keine der anderen Regeln geändert wird. Der einzige Unterschied besteht darin, dass diese beiden Skripte getrennt nacheinander ausgeführt werden und dass der Stack, der von der Ausführung von übrig bleibt, scriptSigan die Ausführung von weitergegeben wird scriptPubkey.

Wir beginnen mit der Ausführung scriptSigund durchlaufen die Schritte 1 bis 7 wie zuvor und erreichen den gleichen Punkt, an CHECKMULTISIGdem wir die scriptCode. Da das tatsächlich ausgeführte Skript jetzt nur noch aus dem besteht, was in war scriptSig, scriptCodewird es zu:

1
21 0232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a
1
CHECKMULTISIG

Wenn diese Signaturprüfung bestanden wird (und das tut sie), wird ein Wert 1auf den Stack geschoben und die scriptSigAusführung wird erfolgreich beendet. Dieser Stack wird nun an die Ausführung von weitergegeben scriptPubkey(was sehr einfach ist) und schließlich als letzter Wert auf dem Stack belassen, wodurch scriptPubkeyaufgelöst wird True.

Diese sehr interessante Transaktion zeigt wirklich, wie nicht trivial Commit 6ff5f718tatsächlich war. Es ist ein großartiges Beispiel.

Eine weitere Kleinigkeit, die zu beachten ist, ist, dass dies scriptPubKeyan sich ein Skript ist, das jeder ausgeben kann. Die Bedingungen dieses Skripts können erfüllt werden, indem einfach ein einziger Druck auf ein Element übergeben wird, das True ergibt (z. B. 1- 0x51), und dass die Unterschriftsüberprüfung scriptSigtatsächlich keine Bedeutung hat und die Gelder überhaupt nicht sichert.

Bearbeiten :

Wenn man sich das genauer ansieht, scheint es, dass diese Transaktion ausdrücklich in BIP-17 erwähnt wird (das inzwischen aufgegeben wurde). Die 20 Bytes in scriptPubkeysind eigentlich der Hash160 des 1-von-1-Multisig-Skripts selbst:

51210232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a51ae

Dies ist in der Tat eine der beispielhaften BIP-17-Transaktionen, die vom Autor des BIP in die Kette gestellt wurden. Wäre dieser BIP akzeptiert worden, wäre eine solche Transaktion nicht jedermanns Sache. (Sie können weitere Details im BIP selbst sehen)

Ich habe es selbst herausgefunden (indem ich alle Teilzeichenfolgen des Skripts ausprobiert habe).

Der korrekte Index ist dieser:

0000 - 51 21 02 32 ab dc 89 3e 7f 06 31 36 4d 7f d0 1c 0010 - b3 3d 24 da 45 32 9a 00 35 7b 3a 78 86 21 1a b4 0020 - 14 d5 5a 51 ae END

Mit anderen Worten, es ist Frame 03 bis Frame 06:

Frame 03: 1(0x51) Frame 04: 0000 - 02 32 ab dc 89 3e 7f 06 31 36 4d 7f d0 1c b3 3d 0010 - 24 da 45 32 9a 00 35 7b 3a 78 86 21 1a b4 14 d5 0020 - 5a END Frame 05: 1(0x51) Frame 06: OP_CHECKMULTISIG(0xae)

Im Gegensatz zur normalen Subscript-Generierung geht dieses Subscript nicht bis zum Ende des Skripts (es enthält überhaupt keinen Teil des öffentlichen Schlüssels).

Ich bin mir nicht sicher, warum dies der Fall ist.