Created
March 21, 2023 00:23
-
-
Save jamesob/17b57bda67051c18687a78d6e51bd882 to your computer and use it in GitHub Desktop.
OP_VAULT -> FLU
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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