Skip to content

Commit bd65a76

Browse files
committed
Merge #21330: Deal with missing data in signature hashes more consistently
725d7ae Use PrecomputedTransactionData in signet check (Pieter Wuille) 497718b Treat amount<0 also as missing data for P2WPKH/P2WSH (Pieter Wuille) 3820090 Make all SignatureChecker explicit about missing data (Pieter Wuille) b77b0cc Add MissingDataBehavior and make TransactionSignatureChecker handle it (Pieter Wuille) Pull request description: Currently we have 2 levels of potentially-missing data in the transaction signature hashes: * P2WPKH/P2WSH hashes need the spent amount * P2TR hashes need all spent outputs (amount + scriptPubKey) Missing amounts are treated as -1 (thus leading to unexpected signature failures), while missing outputs in P2TR validation cause assertion failure. This is hard to extend for signing support, and also quite ugly in general. In this PR, an explicit configuration option to {Mutable,}TransactionSignatureChecker is added (MissingDataBehavior enum class) to either select ASSERT_FAIL or FAIL. Validation code passes ASSERT_FAIL (as at validation time all data should always be passed, and anything else is a serious bug in the code), while signing code uses FAIL. The existence of the ASSERT_FAIL option is really just an abundance of caution. Always using FAIL should be just fine, but if there were for some reason a code path in consensus code was introduced that misses certain data, I think we prefer as assertion failure over silently introducing a consensus change. Potentially useful follow-ups (not for this PR, in my preference): * Having an explicit script validation error code for missing data. * Having a MissingDataBehavior::SUCCEED option as well, for use in script/sign.cpp DataFromTransaction (if a signature is present in a witness, and we don't have enough data to fully validate it, we should probably treat it as valid and not touch it). ACKs for top commit: sanket1729: reACK 725d7ae Sjors: ACK 725d7ae achow101: re-ACK 725d7ae benthecarman: ACK 725d7ae fjahr: Code review ACK 725d7ae Tree-SHA512: d67dc51bae9ca7ef6eb9acccefd682529f397830f77d74cd305500a081ef55aede0e9fa380648c3a8dd4857aa7eeb1ab54fe808979d79db0784ac94ceb31b657
2 parents 89b72ce + 725d7ae commit bd65a76

14 files changed

+74
-42
lines changed

src/bench/verify_script.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static void VerifyScriptBench(benchmark::Bench& bench)
5656
txCredit.vout[0].scriptPubKey,
5757
&txSpend.vin[0].scriptWitness,
5858
flags,
59-
MutableTransactionSignatureChecker(&txSpend, 0, txCredit.vout[0].nValue),
59+
MutableTransactionSignatureChecker(&txSpend, 0, txCredit.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL),
6060
&err);
6161
assert(err == SCRIPT_ERR_OK);
6262
assert(success);

src/script/bitcoinconsensus.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP
9292
set_error(err, bitcoinconsensus_ERR_OK);
9393

9494
PrecomputedTransactionData txdata(tx);
95-
return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata), nullptr);
95+
return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata, MissingDataBehavior::FAIL), nullptr);
9696
} catch (const std::exception&) {
9797
return set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing
9898
}

src/script/interpreter.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,8 +1488,20 @@ static const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
14881488
static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
14891489
static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
14901490

1491+
static bool HandleMissingData(MissingDataBehavior mdb)
1492+
{
1493+
switch (mdb) {
1494+
case MissingDataBehavior::ASSERT_FAIL:
1495+
assert(!"Missing data");
1496+
break;
1497+
case MissingDataBehavior::FAIL:
1498+
return false;
1499+
}
1500+
assert(!"Unknown MissingDataBehavior value");
1501+
}
1502+
14911503
template<typename T>
1492-
bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache)
1504+
bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache, MissingDataBehavior mdb)
14931505
{
14941506
uint8_t ext_flag, key_version;
14951507
switch (sigversion) {
@@ -1509,7 +1521,9 @@ bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata
15091521
assert(false);
15101522
}
15111523
assert(in_pos < tx_to.vin.size());
1512-
assert(cache.m_bip341_taproot_ready && cache.m_spent_outputs_ready);
1524+
if (!(cache.m_bip341_taproot_ready && cache.m_spent_outputs_ready)) {
1525+
return HandleMissingData(mdb);
1526+
}
15131527

15141528
CHashWriter ss = HASHER_TAPSIGHASH;
15151529

@@ -1667,6 +1681,9 @@ bool GenericTransactionSignatureChecker<T>::CheckECDSASignature(const std::vecto
16671681
int nHashType = vchSig.back();
16681682
vchSig.pop_back();
16691683

1684+
// Witness sighashes need the amount.
1685+
if (sigversion == SigVersion::WITNESS_V0 && amount < 0) return HandleMissingData(m_mdb);
1686+
16701687
uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, this->txdata);
16711688

16721689
if (!VerifyECDSASignature(vchSig, pubkey, sighash))
@@ -1696,7 +1713,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const uns
16961713
}
16971714
uint256 sighash;
16981715
assert(this->txdata);
1699-
if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, *this->txdata)) {
1716+
if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, *this->txdata, m_mdb)) {
17001717
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE);
17011718
}
17021719
if (!VerifySchnorrSignature(sig, pubkey, sighash)) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG);

src/script/interpreter.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,21 @@ class BaseSignatureChecker
247247
virtual ~BaseSignatureChecker() {}
248248
};
249249

250+
/** Enum to specify what *TransactionSignatureChecker's behavior should be
251+
* when dealing with missing transaction data.
252+
*/
253+
enum class MissingDataBehavior
254+
{
255+
ASSERT_FAIL, //!< Abort execution through assertion failure (for consensus code)
256+
FAIL, //!< Just act as if the signature was invalid
257+
};
258+
250259
template <class T>
251260
class GenericTransactionSignatureChecker : public BaseSignatureChecker
252261
{
253262
private:
254263
const T* txTo;
264+
const MissingDataBehavior m_mdb;
255265
unsigned int nIn;
256266
const CAmount amount;
257267
const PrecomputedTransactionData* txdata;
@@ -261,8 +271,8 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker
261271
virtual bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const;
262272

263273
public:
264-
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
265-
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
274+
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
275+
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
266276
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
267277
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override;
268278
bool CheckLockTime(const CScriptNum& nLockTime) const override;

src/script/sigcache.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class CachingTransactionSignatureChecker : public TransactionSignatureChecker
2727
bool store;
2828

2929
public:
30-
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn), store(storeIn) {}
30+
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn, MissingDataBehavior::ASSERT_FAIL), store(storeIn) {}
3131

3232
bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override;
3333
bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const override;

src/script/sign.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
typedef std::vector<unsigned char> valtype;
1616

17-
MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {}
17+
MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL) {}
1818

1919
bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const
2020
{
@@ -26,6 +26,9 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid
2626
if (sigversion == SigVersion::WITNESS_V0 && !key.IsCompressed())
2727
return false;
2828

29+
// Signing for witness scripts needs the amount.
30+
if (sigversion == SigVersion::WITNESS_V0 && amount < 0) return false;
31+
2932
uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion);
3033
if (!key.Sign(hash, vchSig))
3134
return false;
@@ -292,7 +295,7 @@ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nI
292295
Stacks stack(data);
293296

294297
// Get signatures
295-
MutableTransactionSignatureChecker tx_checker(&tx, nIn, txout.nValue);
298+
MutableTransactionSignatureChecker tx_checker(&tx, nIn, txout.nValue, MissingDataBehavior::FAIL);
296299
SignatureExtractorChecker extractor_checker(data, tx_checker);
297300
if (VerifyScript(data.scriptSig, txout.scriptPubKey, &data.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, extractor_checker)) {
298301
data.complete = true;
@@ -499,7 +502,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
499502
}
500503

501504
ScriptError serror = SCRIPT_ERR_OK;
502-
if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
505+
if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, MissingDataBehavior::FAIL), &serror)) {
503506
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
504507
// Unable to sign input and verification failed (possible attempt to partially sign).
505508
input_errors[i] = "Unable to sign input, invalid stack size (possibly missing key)";

src/signet.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ bool CheckSignetBlockSolution(const CBlock& block, const Consensus::Params& cons
139139
const CScript& scriptSig = signet_txs->m_to_sign.vin[0].scriptSig;
140140
const CScriptWitness& witness = signet_txs->m_to_sign.vin[0].scriptWitness;
141141

142-
TransactionSignatureChecker sigcheck(&signet_txs->m_to_sign, /*nIn=*/ 0, /*amount=*/ signet_txs->m_to_spend.vout[0].nValue);
142+
PrecomputedTransactionData txdata;
143+
txdata.Init(signet_txs->m_to_sign, {signet_txs->m_to_spend.vout[0]});
144+
TransactionSignatureChecker sigcheck(&signet_txs->m_to_sign, /*nIn=*/ 0, /*amount=*/ signet_txs->m_to_spend.vout[0].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
143145

144146
if (!VerifyScript(scriptSig, signet_txs->m_to_spend.vout[0].scriptPubKey, &witness, BLOCK_SCRIPT_VERIFY_FLAGS, sigcheck)) {
145147
LogPrint(BCLog::VALIDATION, "CheckSignetBlockSolution: Errors in block (block solution invalid)\n");

src/test/fuzz/script_assets_test_minimizer.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ void Test(const std::string& str)
161161
tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
162162
PrecomputedTransactionData txdata;
163163
txdata.Init(tx, std::vector<CTxOut>(prevouts));
164-
MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata);
164+
MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
165165
for (const auto flags : ALL_FLAGS) {
166166
// "final": true tests are valid for all flags. Others are only valid with flags that are
167167
// a subset of test_flags.
@@ -176,7 +176,7 @@ void Test(const std::string& str)
176176
tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
177177
PrecomputedTransactionData txdata;
178178
txdata.Init(tx, std::vector<CTxOut>(prevouts));
179-
MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata);
179+
MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
180180
for (const auto flags : ALL_FLAGS) {
181181
// If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
182182
if ((flags & test_flags) == test_flags) {

src/test/fuzz/script_flags.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ FUZZ_TARGET_INIT(script_flags, initialize_script_flags)
4848

4949
for (unsigned i = 0; i < tx.vin.size(); ++i) {
5050
const CTxOut& prevout = txdata.m_spent_outputs.at(i);
51-
const TransactionSignatureChecker checker{&tx, i, prevout.nValue, txdata};
51+
const TransactionSignatureChecker checker{&tx, i, prevout.nValue, txdata, MissingDataBehavior::ASSERT_FAIL};
5252

5353
ScriptError serror;
5454
const bool ret = VerifyScript(tx.vin.at(i).scriptSig, prevout.scriptPubKey, &tx.vin.at(i).scriptWitness, verify_flags, checker, &serror);

src/test/multisig_tests.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,20 +77,20 @@ BOOST_AUTO_TEST_CASE(multisig_verify)
7777
keys.assign(1,key[0]);
7878
keys.push_back(key[1]);
7979
s = sign_multisig(a_and_b, keys, CTransaction(txTo[0]), 0);
80-
BOOST_CHECK(VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err));
80+
BOOST_CHECK(VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err));
8181
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err));
8282

8383
for (int i = 0; i < 4; i++)
8484
{
8585
keys.assign(1,key[i]);
8686
s = sign_multisig(a_and_b, keys, CTransaction(txTo[0]), 0);
87-
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err), strprintf("a&b 1: %d", i));
87+
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("a&b 1: %d", i));
8888
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_INVALID_STACK_OPERATION, ScriptErrorString(err));
8989

9090
keys.assign(1,key[1]);
9191
keys.push_back(key[i]);
9292
s = sign_multisig(a_and_b, keys, CTransaction(txTo[0]), 0);
93-
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err), strprintf("a&b 2: %d", i));
93+
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("a&b 2: %d", i));
9494
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err));
9595
}
9696

@@ -101,18 +101,18 @@ BOOST_AUTO_TEST_CASE(multisig_verify)
101101
s = sign_multisig(a_or_b, keys, CTransaction(txTo[1]), 0);
102102
if (i == 0 || i == 1)
103103
{
104-
BOOST_CHECK_MESSAGE(VerifyScript(s, a_or_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount), &err), strprintf("a|b: %d", i));
104+
BOOST_CHECK_MESSAGE(VerifyScript(s, a_or_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("a|b: %d", i));
105105
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err));
106106
}
107107
else
108108
{
109-
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount), &err), strprintf("a|b: %d", i));
109+
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("a|b: %d", i));
110110
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err));
111111
}
112112
}
113113
s.clear();
114114
s << OP_0 << OP_1;
115-
BOOST_CHECK(!VerifyScript(s, a_or_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount), &err));
115+
BOOST_CHECK(!VerifyScript(s, a_or_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err));
116116
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_SIG_DER, ScriptErrorString(err));
117117

118118

@@ -124,12 +124,12 @@ BOOST_AUTO_TEST_CASE(multisig_verify)
124124
s = sign_multisig(escrow, keys, CTransaction(txTo[2]), 0);
125125
if (i < j && i < 3 && j < 3)
126126
{
127-
BOOST_CHECK_MESSAGE(VerifyScript(s, escrow, nullptr, flags, MutableTransactionSignatureChecker(&txTo[2], 0, amount), &err), strprintf("escrow 1: %d %d", i, j));
127+
BOOST_CHECK_MESSAGE(VerifyScript(s, escrow, nullptr, flags, MutableTransactionSignatureChecker(&txTo[2], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("escrow 1: %d %d", i, j));
128128
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err));
129129
}
130130
else
131131
{
132-
BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, nullptr, flags, MutableTransactionSignatureChecker(&txTo[2], 0, amount), &err), strprintf("escrow 2: %d %d", i, j));
132+
BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, nullptr, flags, MutableTransactionSignatureChecker(&txTo[2], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("escrow 2: %d %d", i, j));
133133
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err));
134134
}
135135
}

0 commit comments

Comments
 (0)