Im Code für OP_CHECKLOCKTIMEVERIFY
habe 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.
Der Zweck von OP_CHECKLOCKTIMEVERIFY
ist irgendwie das Gegenteil von dem Zweck von tx.nLockTime
. tx.nLockTime
verhindert, dass Transaktionen mit zukünftigen Daten in die Blockchain gelangen, während OP_CHECKLOCKTIMEVERIFY
es jemandem ermöglicht, Gelder einzufrieren, sodass sie erst nach einem bestimmten Zeitstempel oder einer bestimmten Blockhöhe ausgegeben werden können.
tx.nLockTime
tx.nLockTime
wird 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_CHECKLOCKTIMEVERIFY
hat 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_CHECKLOCKTIMEVERIFY
friert 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_CHECKLOCKTIMEVERIFY
Kriterien umgangen werden, muss die Skriptvalidierung fehlschlagen, wenn die TX-Sperrzeit durch die Sequenznummer deaktiviert wird.
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 < 0xffffffff
ist, 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
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 == 0xffffffff
für dieses TXIN. Wenn diese Bedingung erfüllt ist, schlägt ein Skript mit OP_CHECKLOCKTIMEVERIFY
fehlset_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.
Müllhausen