¿Por qué OP_CHECKLOCKTIMEVERIFY está deshabilitado por el número de secuencia máximo?

En el código para OP_CHECKLOCKTIMEVERIFYnoté que si el número de secuencia de txin está al máximo, el script no se validará. Me pregunto cuál es el punto de esto. ¿Por qué alguien enviaría una transacción que no se verificará en la red?

Aquí está la sección relevante del código :

// 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;

También estoy confundido acerca de los comentarios en el código para esto. Dicen que cada número de secuencia debe maximizarse para que el script no se valide, pero me parece que esto no es cierto: parece que solo se debe maximizar un número de secuencia y luego toda la transacción (todas txins) fallará. ¿Y supongo que esto significaría que la transacción no se incluirá en la cadena de bloques? Pero eso va en contra de los comentarios del código. Tendría sentido si hubiera una !condición sobre si.

Respuestas (3)

el propósito de OP_CHECKLOCKTIMEVERIFYes algo opuesto al propósito de tx.nLockTime. tx.nLockTimeevita que las transacciones con fechas futuras ingresen a la cadena de bloques, mientras que OP_CHECKLOCKTIMEVERIFYpermite que alguien congele los fondos para que solo se puedan gastar después de una marca de tiempo o altura de bloque determinada.

tx.nLockTime

tx.nLockTimees validado por la función IsFinalTx() ensrc/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;
}

donde txin.IsFinal()esta ensrc/primitives/transaction.h :

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

Si el tiempo de bloqueo de tx está por debajo del umbral, se trata como una altura de bloque y si está por encima del umbral, se trata como una marca de tiempo. De cualquier manera, el valor del tiempo de bloqueo de la transacción debe ser menor que la restricción relevante. Si es más grande, los mineros deben esperar antes de incluir la transacción en un bloque.

La única forma de eludir esta restricción de tiempo de bloqueo de transacciones es deshabilitar completamente el tiempo de bloqueo de transacciones configurando todos los números de secuencia de txin en maxint. Cuando se haga esto, los mineros incluirán la transacción de inmediato, incluso si aún no se ha alcanzado el tiempo de bloqueo.

La idea con el tiempo de bloqueo de la transacción es que antes de que se bloquee la transacción (es decir, antes de que la altura del bloque o la marca de tiempo alcancen el tiempo de bloqueo de tx), alguien puede modificar la transacción. Cada vez que hacen un cambio, deben incrementar el número de secuencia para que los mineros sepan qué enmienda viene después de otra.

Un caso de uso para esto podría ser un testamento digital . Si quisiera pasar su dinero a otra persona específicamente en caso de su muerte, entonces podría crear una transacción con un tiempo de bloqueo de un año a partir de ahora y luego dársela a algunos amigos. En caso de su muerte, pueden transmitir esta transacción en la red después de un año y los fondos se enviarán en consecuencia. Transmitir la transacción antes de este período de tiempo de un año no les permitiría recibir fondos, ya que los mineros ignorarán la transacción hasta que el período de tiempo sea válido (y obviamente los amigos no pueden alterar la funcionalidad de esta transacción ya que está firmada por su clave privada que nunca revelas).

Si no muere, puede gastar los fondos en una dirección diferente de su elección transmitiendo una transacción diferente en la red. Tus amigos no podrán usar la transacción original que les diste, ya que esto sería un doble gasto, que los mineros no permiten. Para la transacción que transmite para cancelar el testamento , modificaría el tiempo de bloqueo para hacerlo más rápido e incrementaría el número de secuencia. Alternativamente, puede establecer el tiempo de bloqueo en 0 o establecer el número de secuencia en maxint para gastar de inmediato.

OP_CHECKLOCKTIMEVERIFY

OP_CHECKLOCKTIMEVERIFYtiene un uso muy diferente. Se valida en función EvalScript() ensrc/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;
            }

que se basa en la función CheckLockTime() en el mismo archivo :

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;
}

Aquí el tiempo de bloqueo de la transacción se compara con un valor en la pila. Para validar con éxito, ambos deben estar en el mismo lado del umbral (es decir, ambos deben interpretarse como una altura de bloque, o ambos como una marca de tiempo), y el script solo validará si el valor de la pila es menor que el tiempo de bloqueo de tx. O para decirlo de otra manera, el script solo validará si el tiempo de bloqueo de la transacción ha pasado el valor de la pila.

Mientras que IsFinalTx()evita que las transacciones con tiempos de bloqueo en el futuro se incluyan en la cadena de bloques en el presente, OP_CHECKLOCKTIMEVERIFYcongela los fondos en la cadena de bloques para que solo puedan gastarse después de un tiempo específico en el futuro.

Tenga en cuenta que el valor de pila utilizado para la comparación es más útil cuando se coloca en scriptPubKey. El tiempo de bloqueo utilizado para la comparación con el valor de la pila es el de la transacción de firma. Esto obliga al gastador a esperar el bloque o la marca de tiempo para gastar los fondos.

Como se discutió anteriormente, IsFinalTx() permite extraer transacciones con tiempos de bloqueo por encima de la altura del bloque actual o la marca de tiempo, siempre que el número de secuencia esté al máximo, deshabilitando así el tiempo de bloqueo de tx. Enviar una transacción de este tipo con un número de secuencia al máximo sería una forma engañosa de que el destinatario gastara los fondos antes del tiempo especificado por el remitente en el script txout. Por lo tanto, para evitar que OP_CHECKLOCKTIMEVERIFYse eludan los criterios, la validación del script debe fallar cuando el número de secuencia deshabilita el tiempo de bloqueo de tx.

He estado buscando un ejemplo usando OP_CLTV, y creo que encontré uno aquí: api.blockcypher.com/v1/btc/main/txs/… Sin embargo, el nLockTime para esa transacción es 0 y la secuencia es máx. Esperaba que el código fuente comparara el valor empujado a la pila antes de OP_CHECKLOCKTIMEVERIFY para compararlo con el tiempo de bloque actual y no con tx.nLockTime

De Bitcoin.org :

Locktime permite a los firmantes crear transacciones con bloqueo de tiempo que solo serán válidas en el futuro, dando a los firmantes la oportunidad de cambiar de opinión.

...

Las versiones anteriores de Bitcoin Core proporcionaban una función que impedía que los firmantes de transacciones utilizaran el método descrito anteriormente para cancelar una transacción con bloqueo de tiempo, pero una parte necesaria de esta función se deshabilitó para evitar ataques de denegación de servicio. Un legado de este sistema son los números de secuencia de cuatro bytes en cada entrada. Los números de secuencia estaban destinados a permitir que varios firmantes accedieran a actualizar una transacción; cuando terminaron de actualizar la transacción , podían acordar establecer el número de secuencia de cada entrada en el máximo sin firmar de cuatro bytes (0xffffffff), lo que permitía agregar la transacción a un bloque incluso si su bloqueo de tiempo no había expirado.

Incluso hoy en día, establecer todos los números de secuencia en 0xffffffff (el valor predeterminado en Bitcoin Core) aún puede deshabilitar el bloqueo de tiempo, por lo que si desea usar el tiempo de bloqueo, al menos una entrada debe tener un número de secuencia por debajo del máximo. la red para cualquier otro propósito, establecer cualquier número de secuencia en cero es suficiente para habilitar el tiempo de bloqueo .

Entonces, esencialmente, nLockTime está deshabilitado si el valor de la secuencia es 0xffffffff, pero para 0 <= sequence_value < 0xffffffffnTimeLock está habilitado.

En mi opinión, la confusión surge porque hay una combinación de la funcionalidad de OP_CHECKLOCKTIMEVERIFY(BIP65) y nTimeLock . Estaba igualmente confundido , así que consulte estas definiciones de Bitcoin.org y nTimeLock puede decepcionarlo.

una buena discusión más sobre el tema - bitcointalk.org/index.php?topic=766893.0
Todavía estoy confundido, principalmente por el comentario donde dice "y, por lo tanto, se omite CHECKLOCKTIMEVERIFY". Si el número de secuencia está al máximo pero hay una secuencia de comandos con CHECKLOCKTIMEVERIFY en él, NO se omitirá, ¡la secuencia de comandos fallará!
@mulllhausen No estoy versado en C ++, ¿puede aclarar qué if (txTo->vin[nIn].isFinal());significa? txTo es la transacción de gasto, y vin[nIn] es solo una matriz de UTXO, ¿verdad? ¿Qué ->significa? 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, sí, veo tu punto. Me pregunto qué significa pasar por alto. ¿Puede significar que la secuencia de comandos elimina OP_CLTV si la secuencia es inferior a 0xffffffff?
isFinal() es un método en la transacción.h . es básicamente un cheque sequence num == 0xffffffffpara este txin. cuando se cumple esta condición, un script que contiene OP_CHECKLOCKTIMEVERIFYfalla conset_error(serror, SCRIPT_ERR_UNSATISFIED_LOCKTIME);

La respuesta de mulllhausen es excelente, pero aquí hay un ejemplo más breve que ilustra por qué el número de secuencia de vin no puede ser final o máximo.

Supongamos que quiero gastar una salida CLTV. Espero hasta que haya pasado el tiempo de CLTV y luego creo una transacción con nLockTime establecido en el tiempo de bloque actual y el número de secuencia establecido en algo menos de 2^32-1, digamos 0.

Ahora esa transacción se verificará porque

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

Básicamente estamos usando nLockTime como un proxy para el tiempo de bloque actual para decir que el tiempo de bloque actual ha pasado el tiempo de CLTV.

Sin embargo, si configuro todos mis números de secuencia al máximo, podría enviar una transacción con un nLockTime en el futuro, es decir, mayor que el tiempo de bloqueo actual, evitando así el tiempo CLTV. nLockTime debe ser <= al tiempo de bloque actual si no todos los números de secuencia están al máximo. Entonces, al verificar que al menos un número de Vin Seq no sea definitivo, nos aseguramos de que no estén al máximo, lo que permite este escenario de omisión.