Warum ist OP_CHECKLOCKTIMEVERIFY durch die maximale Sequenznummer deaktiviert?

Im Code für OP_CHECKLOCKTIMEVERIFYhabe ich festgestellt, dass das Skript nicht validiert werden kann, wenn die Txin-Sequenznummer ausgeschöpft ist. Ich frage mich, was der Sinn davon ist? Warum sollte jemand jemals eine Transaktion an das Netzwerk senden, die nicht verifiziert werden kann?

Hier ist der relevante Codeabschnitt :

// Finally the nLockTime feature can be disabled and thus
// CHECKLOCKTIMEVERIFY bypassed if every txin has been
// finalized by setting nSequence to maxint. The
// transaction would be allowed into the blockchain, making
// the opcode ineffective.
//
// Testing if this vin is not final is sufficient to
// prevent this condition. Alternatively we could test all
// inputs, but testing just this input minimizes the data
// required to prove correct CHECKLOCKTIMEVERIFY execution.
if (txTo->vin[nIn].IsFinal())
    return false;

Ich bin auch verwirrt über die Kommentare im Code dafür. Sie sagen, dass jede Sequenznummer maximiert werden muss, damit das Skript nicht validiert wird, aber es sieht so aus, als ob dies nicht wahr ist - es sieht so aus, als müsste nur eine Sequenznummer maximiert werden und dann die gesamte Transaktion (all txins) wird fehlschlagen. Und ich nehme an, das würde bedeuten, dass die Transaktion deshalb nicht in die Blockchain aufgenommen wird? Aber das widerspricht den Code-Kommentaren. Es wäre sinnvoll, wenn es eine !on der if-Bedingung gäbe.

Antworten (3)

Der Zweck von OP_CHECKLOCKTIMEVERIFYist irgendwie das Gegenteil von dem Zweck von tx.nLockTime. tx.nLockTimeverhindert, dass Transaktionen mit zukünftigen Daten in die Blockchain gelangen, während OP_CHECKLOCKTIMEVERIFYes jemandem ermöglicht, Gelder einzufrieren, sodass sie erst nach einem bestimmten Zeitstempel oder einer bestimmten Blockhöhe ausgegeben werden können.

tx.nLockTime

tx.nLockTimewird validiert durch Funktion IsFinalTx() insrc/main.cpp :

bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime)
{
    if (tx.nLockTime == 0)
        return true;
    if ((int64_t)tx.nLockTime < ((int64_t)tx.nLockTime < LOCKTIME_THRESHOLD ? (int64_t)nBlockHeight : nBlockTime))
        return true;
    BOOST_FOREACH(const CTxIn& txin, tx.vin)
        if (!txin.IsFinal())
            return false;
    return true;
}

wo txin.IsFinal()ist drinsrc/primitives/transaction.h :

bool IsFinal() const
{
    return (nSequence == std::numeric_limits<uint32_t>::max());
}

Wenn die tx-Sperrzeit unter dem Schwellenwert liegt, wird sie als Blockhöhe behandelt, und wenn sie über dem Schwellenwert liegt, wird sie als Zeitstempel behandelt. In jedem Fall muss der Transaktionssperrzeitwert kleiner als die relevante Einschränkung sein. Wenn es größer ist, müssen Miner warten, bevor sie die Transaktion in einen Block aufnehmen.

Die einzige Möglichkeit, diese Einschränkung der Transaktionssperrzeit zu umgehen, besteht darin, die Transaktionssperrzeit vollständig zu deaktivieren, indem alle TXIN-Sequenznummern auf maxint gesetzt werden. Wenn dies erledigt ist, werden die Miner die Transaktion sofort einschließen, auch wenn die Sperrzeit noch nicht erreicht ist.

Die Idee hinter der Transaktionssperrzeit ist, dass jemand Änderungen an der Transaktion vornehmen kann, bevor die Transaktion gesperrt wird (dh bevor die Blockhöhe oder der Zeitstempel die tx-Sperrzeit einholen). Jedes Mal, wenn sie eine Änderung vornehmen, müssen sie die Sequenznummer erhöhen, um die Bergleute wissen zu lassen, welche Änderung nach der anderen kommt.

Ein Anwendungsfall hierfür könnte ein digitales Testament sein . Wenn Sie Ihr Geld speziell für Ihren Todesfall an jemand anderen weitergeben möchten, können Sie eine Transaktion mit einer Sperrfrist von einem Jahr erstellen und es dann an ein paar Freunde verschenken. Im Falle Ihres Todes können sie diese Transaktion nach einem Jahr im Netzwerk übertragen und die Gelder werden entsprechend gesendet. Das Senden der Transaktion vor diesem Zeitraum von einem Jahr würde es ihnen nicht ermöglichen, Geld zu erhalten, da Miner die Transaktion ignorieren, bis der Zeitraum gültig wird (und die Freunde die Funktionalität dieser Transaktion natürlich nicht ändern können, da sie von Ihrem privaten Schlüssel signiert ist die Sie niemals preisgeben).

Wenn Sie nicht sterben, können Sie das Geld an eine andere Adresse Ihrer Wahl ausgeben, indem Sie eine andere Transaktion im Netzwerk übertragen. Ihre Freunde können dann die ursprüngliche Transaktion, die Sie ihnen gegeben haben, nicht verwenden, da dies eine doppelte Ausgabe wäre, die die Miner nicht zulassen. Für die Transaktion, die Sie senden, um das Testament zu stornieren , würden Sie die Sperrzeit ändern, um sie früher zu machen, und die Sequenznummer erhöhen. Alternativ können Sie die Sperrzeit auf 0 oder die Sequenznummer auf maxint setzen, um sie sofort auszugeben.

OP_CHECKLOCKTIMEVERIFY

OP_CHECKLOCKTIMEVERIFYhat eine ganz andere Verwendung. Es ist in Funktion validiert EvalScript() insrc/script/interpreter.cpp :

            case OP_CHECKLOCKTIMEVERIFY:
            {
                if (!(flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)) {
                    // not enabled; treat as a NOP2
                    if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) {
                        return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS);
                    }
                    break;
                }

                if (stack.size() < 1)
                    return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

                // Note that elsewhere numeric opcodes are limited to
                // operands in the range -2**31+1 to 2**31-1, however it is
                // legal for opcodes to produce results exceeding that
                // range. This limitation is implemented by CScriptNum's
                // default 4-byte limit.
                //
                // If we kept to that limit we'd have a year 2038 problem,
                // even though the nLockTime field in transactions
                // themselves is uint32 which only becomes meaningless
                // after the year 2106.
                //
                // Thus as a special case we tell CScriptNum to accept up
                // to 5-byte bignums, which are good until 2**39-1, well
                // beyond the 2**32-1 limit of the nLockTime field itself.
                const CScriptNum nLockTime(stacktop(-1), fRequireMinimal, 5);

                // In the rare event that the argument may be < 0 due to
                // some arithmetic being done first, you can always use
                // 0 MAX CHECKLOCKTIMEVERIFY.
                if (nLockTime < 0)
                    return set_error(serror, SCRIPT_ERR_NEGATIVE_LOCKTIME);

                // Actually compare the specified lock time with the transaction.
                if (!checker.CheckLockTime(nLockTime))
                    return set_error(serror, SCRIPT_ERR_UNSATISFIED_LOCKTIME);

                break;
            }

die sich auf die Funktion CheckLockTime() in derselben Datei stützt :

bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) const
{
    // There are two kinds of nLockTime: lock-by-blockheight
    // and lock-by-blocktime, distinguished by whether
    // nLockTime < LOCKTIME_THRESHOLD.
    //
    // We want to compare apples to apples, so fail the script
    // unless the type of nLockTime being tested is the same as
    // the nLockTime in the transaction.
    if (!(
        (txTo->nLockTime <  LOCKTIME_THRESHOLD && nLockTime <  LOCKTIME_THRESHOLD) ||
        (txTo->nLockTime >= LOCKTIME_THRESHOLD && nLockTime >= LOCKTIME_THRESHOLD)
    ))
        return false;

    // Now that we know we're comparing apples-to-apples, the
    // comparison is a simple numeric one.
    if (nLockTime > (int64_t)txTo->nLockTime)
        return false;

    // Finally the nLockTime feature can be disabled and thus
    // CHECKLOCKTIMEVERIFY bypassed if every txin has been
    // finalized by setting nSequence to maxint. The
    // transaction would be allowed into the blockchain, making
    // the opcode ineffective.
    //
    // Testing if this vin is not final is sufficient to
    // prevent this condition. Alternatively we could test all
    // inputs, but testing just this input minimizes the data
    // required to prove correct CHECKLOCKTIMEVERIFY execution.
    if (txTo->vin[nIn].IsFinal())
        return false;

    return true;
}

Hier wird die Transaktionssperrzeit mit einem Wert auf dem Stack verglichen. Um erfolgreich zu validieren, müssen sich beide auf derselben Seite des Schwellenwerts befinden (dh beide müssen als Blockhöhe oder beide als Zeitstempel interpretiert werden), und das Skript validiert nur, wenn der Stapelwert niedriger als die tx-Sperrzeit ist. Oder anders ausgedrückt, das Skript validiert nur, wenn die Transaktionssperrzeit den Stapelwert überschritten hat.

Verhindert IsFinalTx(), dass Transaktionen mit Sperrzeiten in der Zukunft in der Gegenwart in die Blockchain aufgenommen werden, und OP_CHECKLOCKTIMEVERIFYfriert Gelder in der Blockchain ein, sodass sie erst nach einer bestimmten Zeit in der Zukunft ausgegeben werden können.

Beachten Sie, dass der für den Vergleich verwendete Stapelwert am nützlichsten ist, wenn er im scriptPubKey platziert wird. Die zum Vergleich mit dem Stapelwert verwendete Sperrzeit ist die der signierenden Transaktion. Dies zwingt den Spender, auf den Block oder Zeitstempel zu warten, um das Geld auszugeben.

Wie zuvor besprochen, können Transaktionen mit Sperrzeiten oberhalb der aktuellen IsFinalTx() Blockhöhe oder des aktuellen Zeitstempels abgebaut werden – vorausgesetzt, die Sequenznummer ist ausgeschöpft, wodurch die tx-Sperrzeit deaktiviert wird. Das Senden einer solchen Transaktion mit einer maxxed-out-Sequenznummer wäre eine hinterhältige Möglichkeit für den Empfänger, das Geld vor der vom Absender im txout-Skript angegebenen Zeit auszugeben. Um zu verhindern, dass die OP_CHECKLOCKTIMEVERIFYKriterien umgangen werden, muss die Skriptvalidierung fehlschlagen, wenn die TX-Sperrzeit durch die Sequenznummer deaktiviert wird.

Ich habe mit OP_CLTV nach einem Beispiel gesucht, und ich glaube, ich habe hier eines gefunden: api.blockcypher.com/v1/btc/main/txs/… Die nLockTime für diese Transaktion ist jedoch 0 und seq ist max. Ich hatte erwartet, dass der Quellcode den Wert vergleicht, der vor OP_CHECKLOCKTIMEVERIFY auf den Stapel geschoben wurde, um ihn mit der aktuellen Blockzeit und nicht mit tx.nLockTime zu vergleichen

Von Bitcoin.org :

Locktime ermöglicht es den Unterzeichnern, zeitlich gesperrte Transaktionen zu erstellen, die erst in der Zukunft gültig werden, und gibt den Unterzeichnern die Möglichkeit, ihre Meinung zu ändern.

...

Frühere Versionen von Bitcoin Core boten eine Funktion, die Transaktionsunterzeichner daran hinderte, die oben beschriebene Methode zum Abbrechen einer zeitgesperrten Transaktion zu verwenden, aber ein notwendiger Teil dieser Funktion wurde deaktiviert, um Denial-of-Service-Angriffe zu verhindern. Ein Vermächtnis dieses Systems sind Vier-Byte-Folgenummern in jeder Eingabe. Sequenznummern sollten es mehreren Unterzeichnern ermöglichen, sich auf die Aktualisierung einer Transaktion zu einigen; Wenn sie mit der Aktualisierung der Transaktion fertig waren, konnten sie vereinbaren, die Sequenznummer jeder Eingabe auf das vorzeichenlose Vier-Byte-Maximum (0xffffffff) zu setzen, wodurch die Transaktion zu einem Block hinzugefügt werden konnte, selbst wenn ihre Zeitsperre noch nicht abgelaufen war.

Auch heute noch kann das Setzen aller Sequenznummern auf 0xffffffff (der Standard in Bitcoin Core) die Zeitsperre deaktivieren, wenn Sie also Locktime verwenden möchten, muss mindestens eine Eingabe eine Sequenznummer unter dem Maximum haben, da Sequenznummern nicht verwendet werden von das Netzwerk für andere Zwecke zu verwenden, reicht es aus, eine beliebige Sequenznummer auf Null zu setzen, um die Sperrzeit zu aktivieren .

Es ist also im Wesentlichen so, dass nLockTime deaktiviert ist, wenn der Sequenzwert 0xffffffff, aber für 0 <= sequence_value < 0xffffffffist, nTimeLock aktiviert ist.

IMO entsteht die Verwirrung, weil die Funktionalität von OP_CHECKLOCKTIMEVERIFY(BIP65) und nTimeLock vermengt wird . Ich war ähnlich verwirrt , also sehen Sie sich diese Bitcoin.org-Definitionen an und nTimeLock könnte Sie enttäuschen

noch eine gute Diskussion zum Thema - bitcointalk.org/index.php?topic=766893.0
Ich bin immer noch verwirrt - hauptsächlich durch den Kommentar, wo es heißt "und damit CHECKLOCKTIMEVERIFY umgangen". Wenn die Sequenznummer ausgeschöpft ist, aber ein Skript mit CHECKLOCKTIMEVERIFY darin enthalten ist, wird dies NICHT umgangen - das Skript schlägt fehl!
@mulllhausen Ich kenne mich mit C++ nicht aus, also kannst du klären, was das if (txTo->vin[nIn].isFinal());bedeutet? txTo ist die Ausgabetransaktion und vin[nIn] ist nur eine Reihe von UTXOs, richtig? Was ->bedeutet? Re if the sequence number is maxxed out but there is a script with CHECKLOCKTIMEVERIFY in it, then this will NOT be bypassed - the script will fail!... Hmm, ja, ich verstehe Ihren Punkt. Ich frage mich, was Umgehen bedeutet? Es kann nur bedeuten, dass das Skript OP_CLTV löscht, wenn die Sequenz kleiner als 0xffffffff ist?
isFinal() ist eine Methode in transaction.h . Es ist im Grunde eine Überprüfung sequence num == 0xfffffffffür dieses TXIN. Wenn diese Bedingung erfüllt ist, schlägt ein Skript mit OP_CHECKLOCKTIMEVERIFYfehlset_error(serror, SCRIPT_ERR_UNSATISFIED_LOCKTIME);

Die Antwort von mulllhausen ist großartig, aber hier ist ein kürzeres Beispiel, das veranschaulicht, warum die vin seq-Nummer nicht endgültig oder maximal sein kann.

Angenommen, ich möchte einen CLTV-Ausgang ausgeben. Ich warte, bis die CLTV-Zeit abgelaufen ist, und erstelle dann eine Transaktion, bei der nLockTime auf die aktuelle Blockzeit und die Sequenznummer auf etwas weniger als 2^32-1, sagen wir 0, gesetzt ist.

Jetzt wird diese Transaktion verifiziert, weil

CLTV time <= tx.nLockTime <= current block time

Wir verwenden nLockTime im Grunde als Proxy für die aktuelle Blockzeit, um zu sagen, dass die aktuelle Blockzeit nach der CLTV-Zeit liegt.

Wenn ich jedoch alle meine Seq-Nummern auf max setze, wäre ich in der Lage, eine Transaktion mit einer nLockTime in der Zukunft zu übermitteln, dh größer als die aktuelle Blockzeit, wodurch die CLTV-Zeit umgangen wird. nLockTime muss <= zur aktuellen Blockzeit sein, wenn nicht alle Sequenznummern maximal sind. Indem wir also überprüfen, ob mindestens eine Vin-Seq-Nummer nicht endgültig ist, stellen wir sicher, dass nicht alle ausgeschöpft sind, was dieses Bypass-Szenario ermöglicht.