Skip to content

Instantly share code, notes, and snippets.

@jamesob
Created March 21, 2023 00:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesob/17b57bda67051c18687a78d6e51bd882 to your computer and use it in GitHub Desktop.
Save jamesob/17b57bda67051c18687a78d6e51bd882 to your computer and use it in GitHub Desktop.
OP_VAULT -> FLU
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index 9963be758d..e4395dc329 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -11,8 +11,10 @@
#include <pubkey.h>
#include <span.h>
#include <script/script.h>
+#include <script/sign.h>
#include <uint256.h>
#include <util/rbf.h>
+#include <util/vector.h>
#include <algorithm>
@@ -1331,51 +1333,64 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
}
// Stack:
- // - <spend-delay>
- // - <target-ctv-hash>
+ // - <flu-script>
+ // - <n-pushes>
+ // [ n items ... ]
// - <trigger-vout-idx>
// - <revault-vout-idx>
if (stack.size() < 4) {
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
}
- // `spend-delay` is a CScriptNum, up to 4 bytes, that is interpreted
- // in the same way as the first 23 bits of nSequence are per
- // BIP 68 (relative time-locks). This enables users of vaults to
- // express spend delays in either wall time or block count, and reuse
- // the same machinery as OP_CHECKSEQUENCEVERIFY.
- const CScriptNum spend_delay(stacktop(-1), fRequireMinimal);
- if (spend_delay < 0) {
- return set_error(serror, SCRIPT_ERR_VAULT_INVALID_DELAY);
+ const valtype& flu_script_data{stacktop(-1)};
+ CScript flu_script_body{flu_script_data.begin(), flu_script_data.end()};
+ popstack(stack);
+
+ const CScriptNum n_pushes_data{stacktop(-1), fRequireMinimal};
+ const size_t n_pushes = static_cast<size_t>(n_pushes_data.GetInt64());
+ popstack(stack);
+
+ if (stack.size() < (2 + n_pushes)) {
+ return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
}
- const valtype& target_hash_bytes = stacktop(-2);
- if (target_hash_bytes.size() != 32) {
+
+ // Extract the `n` data pushes and construct the entire expected
+ // FORWARD_LEAF_UPDATE script.
+
+ std::vector<valtype> flu_data{
+ std::make_move_iterator(stack.end() - n_pushes),
+ std::make_move_iterator(stack.end())};
+ stack.erase(stack.end() - n_pushes, stack.end());
+
+ CScript flu_script = PushAll(flu_data);
+ flu_script.reserve(flu_script.size() + flu_script_body.size());
+ move_to_end(flu_script, flu_script_body);
+
+ // Still need to pop last two <*-idx> items.
+ if (stack.size() < 2) {
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
}
- const uint256 target_ctv_hash{target_hash_bytes};
// Indicates which of the vouts carries forward
// the value from the vault into the unvault trigger output.
std::optional<size_t> trigger_vout_idx;
- if (!GetVoutIdxFromStack(trigger_vout_idx, -3) || !trigger_vout_idx) {
+ if (!GetVoutIdxFromStack(trigger_vout_idx, -1) || !trigger_vout_idx) {
return set_error(serror, SCRIPT_ERR_VAULT_BAD_VOUT_IDX);
}
// Indicates which (if any) of the vouts carries forward
// the value from the vault into the unvault trigger output.
std::optional<size_t> revault_vout_idx;
- if (!GetVoutIdxFromStack(revault_vout_idx, -4)) {
+ if (!GetVoutIdxFromStack(revault_vout_idx, -2)) {
return set_error(serror, SCRIPT_ERR_VAULT_BAD_VOUT_IDX);
}
if (const auto& err = checker.CheckVaultTrigger(
execdata, *trigger_vout_idx, revault_vout_idx,
- spend_delay, target_ctv_hash, flags, serror)) {
+ flu_script, flags, serror)) {
return set_error(serror, *err);
}
- popstack(stack);
- popstack(stack);
popstack(stack);
popstack(stack);
stack.push_back(vchTrue);
@@ -2149,8 +2164,7 @@ std::optional<ScriptError> GenericTransactionSignatureChecker<T>::CheckVaultTrig
ScriptExecutionData& execdata,
const size_t trigger_out_idx,
const std::optional<size_t> maybe_revault_out_idx,
- const CScriptNum& spend_delay,
- const uint256& target_ctv_hash,
+ CScript flu_script,
unsigned int flags,
ScriptError* serror) const
{
@@ -2181,13 +2195,6 @@ std::optional<ScriptError> GenericTransactionSignatureChecker<T>::CheckVaultTrig
return SCRIPT_ERR_UNVAULT_INCOMPAT_OUTPUT_TYPE;
}
else if (trigger_witversion == 1) {
- CScript expected_trigger_script;
-
- // The expected tapleaf substitution is a timelocked OP_CTV.
- expected_trigger_script <<
- spend_delay.getint() << OP_CHECKSEQUENCEVERIFY << OP_DROP <<
- ToByteVector(target_ctv_hash) << OP_CHECKTEMPLATEVERIFY;
-
assert(execdata.m_internal_key);
assert(execdata.m_taproot_control.size() > 0);
@@ -2201,7 +2208,7 @@ std::optional<ScriptError> GenericTransactionSignatureChecker<T>::CheckVaultTrig
const XOnlyPubKey q{Span{val_sPK}.subspan(2)};
auto tapleaf_hash = ComputeTapleafHash(
- control[0] & TAPROOT_LEAF_MASK, expected_trigger_script);
+ control[0] & TAPROOT_LEAF_MASK, flu_script);
const uint256 merkle_root = ComputeTaprootMerkleRoot(control, tapleaf_hash);
// Ensure the currently executing OP_VAULT tapleaf is substituted for the
diff --git a/src/script/interpreter.h b/src/script/interpreter.h
index f8b7d1c6ed..3d35714313 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -421,8 +421,7 @@ public:
ScriptExecutionData& execdata,
const size_t trigger_out_idx,
const std::optional<size_t> revault_out_idx,
- const CScriptNum& spend_delay,
- const uint256& target_ctv_hash,
+ CScript flu_script_with_data,
unsigned int flags,
ScriptError* serror) const
{
@@ -478,8 +477,7 @@ public:
ScriptExecutionData& execdata,
const size_t trigger_out_idx,
const std::optional<size_t> revault_out_idx,
- const CScriptNum& spend_delay,
- const uint256& target_ctv_hash,
+ CScript flu_script_with_data,
unsigned int flags,
ScriptError* serror) const override;
};
@@ -527,13 +525,12 @@ public:
ScriptExecutionData& execdata,
const size_t trigger_out_idx,
const std::optional<size_t> revault_out_idx,
- const CScriptNum& spend_delay,
- const uint256& target_ctv_hash,
+ CScript flu_script_with_data,
unsigned int flags,
ScriptError* serror) const override
{
return m_checker.CheckVaultTrigger(
- execdata, trigger_out_idx, revault_out_idx, spend_delay, target_ctv_hash, flags, serror);
+ execdata, trigger_out_idx, revault_out_idx, flu_script_with_data, flags, serror);
}
};
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index 1443b8adae..6d299ae289 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -365,7 +365,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
assert(false);
}
-static CScript PushAll(const std::vector<valtype>& values)
+CScript PushAll(const std::vector<valtype>& values)
{
CScript result;
for (const valtype& v : values) {
diff --git a/src/script/sign.h b/src/script/sign.h
index 813dfe04e3..f4d174c70c 100644
--- a/src/script/sign.h
+++ b/src/script/sign.h
@@ -105,4 +105,6 @@ bool IsSegWitOutput(const SigningProvider& provider, const CScript& script);
/** Sign the CMutableTransaction */
bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* provider, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors);
+CScript PushAll(const std::vector<valtype>& values);
+
#endif // BITCOIN_SCRIPT_SIGN_H
diff --git a/test/functional/feature_vaults.py b/test/functional/feature_vaults.py
index 4dfa05129e..34bb7d14ec 100755
--- a/test/functional/feature_vaults.py
+++ b/test/functional/feature_vaults.py
@@ -678,12 +678,20 @@ class VaultsTest(BitcoinTestFramework):
init_tx = vault.get_initialize_vault_tx(wallet)
self.assert_broadcast_tx(init_tx, mine_all=True)
- unvault_spec = UnvaultSpec([vault], [])
+ final_target_vout = [CTxOut(nValue=COIN, scriptPubKey=b'\x00' * 34)]
+ unvault_spec = UnvaultSpec([vault], final_target_vout)
start_unvault_tx = get_trigger_tx([unvault_spec])
- self.assert_broadcast_tx(start_unvault_tx, err_msg='invalid spend delay')
+ txid = self.assert_broadcast_tx(start_unvault_tx, mine_all=True)
+
+ unvault_outpoint = COutPoint(
+ hash=int.from_bytes(bytes.fromhex(txid), byteorder="big"), n=0)
+
+ # Withdrawal fails due to negative timelock
+ fails = get_final_withdrawal_tx(unvault_outpoint, unvault_spec)
+ self.assert_broadcast_tx(fails, err_msg='Negative locktime')
# On the other hand, recovering works fine because it is an independent tapleaf.
- recovery_tx = get_recovery_tx({vault: vault.vault_outpoint})
+ recovery_tx = get_recovery_tx({vault: unvault_outpoint}, start_unvault_tx)
self.assert_broadcast_tx(recovery_tx, mine_all=True)
def test_bad_vout_idx(self, node, wallet):
@@ -826,9 +834,13 @@ class VaultSpec:
])
self.unvault_xonly_pubkey = key.compute_xonly_pubkey(self.unvault_key.get_bytes())[0]
+ vault_script = CScript([
+ script.OP_CHECKSEQUENCEVERIFY, script.OP_DROP, script.OP_CHECKTEMPLATEVERIFY,
+
+ ])
self.trigger_script = CScript([
self.unvault_xonly_pubkey, script.OP_CHECKSIGVERIFY,
- self.spend_delay, OP_VAULT,
+ self.spend_delay, 2, vault_script, OP_VAULT,
])
# The initializing taproot output is either spendable via OP_VAULT
@@ -1130,8 +1142,8 @@ class UnvaultSpec:
self.target_hash = self.withdrawal_template.get_standard_template_hash(0)
self.withdraw_script: t.Union[CScript, bytes] = CScript([
- self.spend_delay, script.OP_CHECKSEQUENCEVERIFY, script.OP_DROP,
- self.target_hash, script.OP_CHECKTEMPLATEVERIFY,
+ self.target_hash, self.spend_delay, script.OP_CHECKSEQUENCEVERIFY,
+ script.OP_DROP, script.OP_CHECKTEMPLATEVERIFY,
])
example_vault = self.compat_vaults[0]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment