Meine Rohtransaktion hat 0,0284377 BTC zerstört. Was habe ich falsch gemacht?

Vor Jahren habe ich ein .NET-Modul entworfen, das die Übermittlung von BTC an meine Kunden erleichtert. Es erstellt eine binäre Darstellung der gewünschten Transaktion basierend auf dem hier und hier präsentierten Material :

Die binäre Darstellung wird dann in Hex konvertiert und über verschiedene kostenlose Dienste wie BlockExplorer, BlockCypher usw. ausgegeben.

Das System läuft seit Jahren einwandfrei. Bis gestern. Ein Kunde forderte, dass 0,0284377 BTC an 38MRMGjMBMp4k7vZhKLHhcM9Pm8AMLy18v gesendet werden . Er hat es nie erhalten. Sicher genug, er hatte Recht. Es wurde nie verschickt.

Ich habe alle meine Protokolle durchgesehen und festgestellt, dass meine Software tatsächlich eine Transaktion mit den folgenden Eingaben und Ausgaben übermittelt hat:

  • Eingabe: 13P38hMYJXFdxDJJn8TtPUJZFXmcpf2J99
  • Ausgabe Nr. 1: 38MRMGjMBMp4k7vZhKLHhcM9Pm8AMLy18v (mein Kunde)
  • Ausgabe Nr. 2: 1Ny3CV3rAsNMWpLfpxhXW3Fh71YmMEXXU7 (meine Änderungsadresse)

Auf meiner Seite sah alles gut aus, aber als ich den Block Explorer ansah, schockierte mich das, was ich sah. Betrachten Sie Transaktion 9a138b14dcc8ae740073c06933ae04e3b08fe6be6ada0dc175e6484250dfe269 und sehen Sie sich die Eingaben und Ausgaben an:

Wie Sie sehen können, ist meine Eingabe korrekt. Meine Änderungsadresse XU7 ist auch korrekt. Aber schauen Sie sich jetzt die Adresse meines Kunden an. Es sagt 17fQRjEudTVgexE8aDfhGyzDFEqSnnJJCA (was ich als JCA bezeichnen werde) anstelle der erwarteten 38MRMGjMBMp4k7vZhKLHhcM9Pm8AMLy18v (was ich als 18v bezeichnen werde). Was zum Teufel? Die JCA-Adresse ist weder mir noch meinem Kunden bekannt. Es ist eine völlig unbekannte Adresse.

Das Problem liegt eindeutig irgendwo in meinem Code, also habe ich tiefer gegraben, um herauszufinden, was diese spezielle Transaktion einzigartig gemacht hat. Ich habe festgestellt, dass seine gewünschte Adresse (18v) mit einer „3“ beginnt, während jede andere ausgehende Transaktion, die ich im Verlauf meiner App abgeschlossen habe, Zieladressen hat, die mit „1“ beginnen.

Meine Notlösung bestand darin, Benutzer zu zwingen, nur Adressen anzugeben, die mit einer 1 beginnen. Aber das ist eine vorübergehende Lösung. Ich muss herausfinden, was ich falsch mache.

Ich wandte mich an Google. Untersuchungen haben ergeben, dass Adressen, die mit „3“ beginnen, typischerweise SegWit-Adressen sind, während diejenigen, die mit „1“ beginnen, traditionelle Adressen der alten Schule sind. Ich erinnere mich an all das Gerede über SegWit vor ein paar Monaten, aber ich dachte, es hätte keine Auswirkungen auf mich und meine alten Transaktionen würden immer noch richtig gelöscht werden. Offensichtlich war das ein Fehlurteil meinerseits, also muss ich jetzt herausfinden, was ich falsch gemacht habe und woher die falsche JCA-Adresse stammt.

Da stecke ich fest. Ich denke, mein Problem könnte mit unkomprimierten vs. komprimierten öffentlichen Schlüsseln zu tun haben, basierend auf Diskussionen hier und hier :

Folgendes weiß ich: Wenn es an der Zeit ist, die Ausgaben für meine Transaktion zu erstellen, macht mein Code Folgendes (Erklärung folgt):

Func<String,byte[]> makeOutScript=(btcAddrHex)=>{
    byte[] addrBytes=BTCUtils.Base58Decode(btcAddrHex);
    byte[] pubKeyBytes=addrBytes.Take(addrBytes.Length-4).Skip(1).ToArray();

    using(MemoryStream ms=new MemoryStream()){
        using(BinaryWriter bw=new BinaryWriter(ms)){
            bw.Write((byte)0x76);   //op_dup
            bw.Write((byte)0xa9);   //op_hash160
            bw.Write((byte)20);     //size of public key
            bw.Write(pubKeyBytes);  //public key
            bw.Write((byte)0x88);   //op_equalverify
            bw.Write((byte)0xac);   //op_checksig

            bw.Flush();
            return ms.ToArray();
        }
    }
};

Was ich hier mache, ist: Konvertieren der Ausgabeadresse in Bytes durch Base58-Decodierung. Ich entferne die letzten vier Bytes, die die Prüfsumme sind, und das erste Byte, das immer 0x00 ist (anscheinend eine Art Netzwerkanzeige). Damit bleibt mir der rohe öffentliche Schlüssel. Wenn ich mir meine internen Notizen ansehe, ist es eigentlich nicht der öffentliche Schlüssel, sondern 0x04, das dem öffentlichen Schlüssel vorangestellt und dann durch SHA256 und dann RIPEMD160 geleitet wird. Aber ich werde diesen Blob als den öffentlichen Schlüssel bezeichnen. Von dort aus ist die Bytefolge 0x76, 0xa9, 20 (die Größe des Public-Key-Blobs), das Public-Key-Blob selbst, 0x88 und 0xac.

Ich gebe nicht vor, zu verstehen, was all diese Single-Byte-Einträge bedeuten, aber die obigen Blog-Posts sagten, dass sie sie enthalten, also habe ich das getan, und bis jetzt hat es gut funktioniert.

Die Frage ist: Wie um alles in der Welt wurde die 18v-Adresse in die JCA-Adresse umgewandelt, als sie an das Netzwerk übermittelt wurde? Ist mein Wert "Größe des öffentlichen Schlüssels" (20) vielleicht falsch? Auf Bauchebene erscheint es seltsam, dass ein komprimierter (18v) und ein unkomprimierter (JCA) öffentlicher Schlüssel beide eine konstante Größe von 20 haben würden. Aber vielleicht bin ich völlig auf dem falschen Weg und die Komprimierung hat nichts damit zu tun.

Ich muss Ihnen nicht sagen, wie schrecklich es wäre, wenn diese Transaktion 100 BTC statt 0,0284377 wäre. Ich hatte Glück, dass mein Fehler bei einer Transaktion mit geringem Wert ausgelöst wurde. Aber ich möchte das wirklich lösen. Können Sie mir den richtigen Weg weisen?

Antworten (1)

Adressen, die mit beginnen , 3...sind P2SH-Adressen und es gibt sie schon seit fast 6 Jahren. In der Vergangenheit wurden sie nur begrenzt genutzt – von Multisig-Wallets wie Copay und GreenAddress. Der Kompatibilitätsmodus von SegWit verwendet jedoch auch P2SH, wodurch ihre Verwendung in letzter Zeit häufiger wird.

Das Hauptproblem besteht darin, dass Ihr Code zum Konvertieren einer Adresse in einen scriptPubKey das Versionsbyte nicht betrachtet. Nach der Base58-Decodierung der Adresse sollten Sie die Struktur <1-Byte-Version> <20-Byte-Hash> <4-Byte-Prüfsumme> erhalten. Wenn diese Version 0 ist, was für 1...Adressen gilt, ist dieser Hash ein öffentlicher Schlüssel-Hash, und Sie konstruieren den entsprechenden scriptPubKey korrekt.

Wenn diese Versionsnummer jedoch 5 ist, handelt es sich um eine P2SH-Adresse, die in BIP13 angegeben wurde , die einen entsprechenden scriptPubKey verwendet, der in BIP16 angegeben ist .

Während SegWit ursprünglich nur das alte P2SH-Format verwendete, wird auch für SegWit ein neues Adressformat eingeführt (das etwas bessere Leistung, Flexibilität und Fehlererkennungsfunktionen bietet), beschrieben in BIP173 . (Disclaimer: Ich bin Autor von BIP173). Diese Adressen beginnen mit bc1....

Ich rate Ihnen, Ihren Dienst einzustellen und zumindest eine Überprüfung zu implementieren, dass die Versionsnummer erwartet wird, zusammen mit einem Testsatz, den Sie in vielen Implementierungen finden können. Wenn Sie Lust haben, können Sie auch BIP13- und BIP173-Adressen implementieren, die beide wahrscheinlich bald verbreiteter sein werden.

Wow, das ist wahnsinnig interessant. Ich weiß nicht, wie ich all die Jahre damit verbracht habe, mich nie mit einer Bitcoin-Adresse zu befassen, die mit etwas anderem als „1“ beginnt. Auch außerhalb meiner App beginnen alle meine Electrum-Adressen mit '1'. Ok, Sie haben also Recht, wenn ich die 18-V-Adresse entpacke, ist tatsächlich ein führendes Byte auf 5 gesetzt. Sie sagen also: (1) Überprüfen Sie dieses Versionsbyte! und (2) wenn es eine 5 ist, muss ich die Struktur dieses Skripts ändern? Was genau ist „BIP“? Ist das das Referenzdokument? Warum behaupten so viele Leute, dass die führende „3“ eine „Segwit“-Adresse ist? So viele schlechte Infos da draußen :(
Nein, als erstes müssen Sie die Prüfsumme und die Versionsnummer prüfen. Der Gedanke, dass die Version immer null ist, ist die Ursache dieses Problems. Selbst wenn Sie glauben, dass es immer Null sein wird, sollte Ihre Software versagen, wenn dies nicht der Fall ist.
BIPs oder Bitcoin Improvement Proposals ist ein Prozess zur Veröffentlichung von Ideen zu Änderungen an den Bitcoin-Regeln oder deren Verwendung: github.com/bitcoin/bips
SegWit brauchte einen Weg, um mit alter Software kompatibel zu sein (damit alte Wallets an SegWit-Wallets senden konnten), sodass es entweder im Kompatibilitätsmodus (der P2SH 3...-Adressen verwendet) oder im nativen Modus (der BIP173 bc1 verwendet) verwendet werden kann. .. Adressen). Vor SegWit waren Multisig-Wallets wie GreenAddress oder Copay die einzige Software, die P2SH-Adressen ausgeben würde.
Erwischt. Wenn der Benutzer eine Adresse an die App übermittelt, führe ich die Prüfsummenvalidierung durch. Ich habe diesen Teil abgedeckt. Mein Fehler war zu denken, dass das, was ich jetzt weiß, das "Versionsbyte" nur eine konstante Null war. Jetzt kann ich es überprüfen und Maßnahmen ergreifen. Danke! Ich bin immer noch etwas verwirrt darüber, wie sich das Format meines scriptPubKey ändern wird, wenn die Versionsnummer 5 ist. Vermutlich kann ich das irgendwo finden.
Das finden Sie in BIP16.
Hallo Leute @PieterWuille, danke für diese hervorragende QA, großartig. es hat mich veranlasst, eine Frage zu stellen. bitcoin.stackexchange.com/questions/64166
Wow, so eine tolle Lektüre Jungs.