Branch data Line data Source code
# 1 : : // Copyright (c) 2009-2021 The Bitcoin Core developers
# 2 : : // Distributed under the MIT software license, see the accompanying
# 3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
# 4 : :
# 5 : : #include <psbt.h>
# 6 : :
# 7 : : #include <util/check.h>
# 8 : : #include <util/strencodings.h>
# 9 : :
# 10 : :
# 11 : : PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx)
# 12 : 276 : {
# 13 : 276 : inputs.resize(tx.vin.size());
# 14 : 276 : outputs.resize(tx.vout.size());
# 15 : 276 : }
# 16 : :
# 17 : : bool PartiallySignedTransaction::IsNull() const
# 18 : 0 : {
# 19 [ # # ][ # # ]: 0 : return !tx && inputs.empty() && outputs.empty() && unknown.empty();
# [ # # ][ # # ]
# 20 : 0 : }
# 21 : :
# 22 : : bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)
# 23 : 7 : {
# 24 : : // Prohibited to merge two PSBTs over different transactions
# 25 [ - + ]: 7 : if (tx->GetHash() != psbt.tx->GetHash()) {
# 26 : 0 : return false;
# 27 : 0 : }
# 28 : :
# 29 [ + + ]: 18 : for (unsigned int i = 0; i < inputs.size(); ++i) {
# 30 : 11 : inputs[i].Merge(psbt.inputs[i]);
# 31 : 11 : }
# 32 [ + + ]: 17 : for (unsigned int i = 0; i < outputs.size(); ++i) {
# 33 : 10 : outputs[i].Merge(psbt.outputs[i]);
# 34 : 10 : }
# 35 [ - + ]: 7 : for (auto& xpub_pair : psbt.m_xpubs) {
# 36 [ # # ]: 0 : if (m_xpubs.count(xpub_pair.first) == 0) {
# 37 : 0 : m_xpubs[xpub_pair.first] = xpub_pair.second;
# 38 : 0 : } else {
# 39 : 0 : m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end());
# 40 : 0 : }
# 41 : 0 : }
# 42 : 7 : unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
# 43 : :
# 44 : 7 : return true;
# 45 : 7 : }
# 46 : :
# 47 : : bool PartiallySignedTransaction::AddInput(const CTxIn& txin, PSBTInput& psbtin)
# 48 : 36 : {
# 49 [ + + ]: 36 : if (std::find(tx->vin.begin(), tx->vin.end(), txin) != tx->vin.end()) {
# 50 : 2 : return false;
# 51 : 2 : }
# 52 : 34 : tx->vin.push_back(txin);
# 53 : 34 : psbtin.partial_sigs.clear();
# 54 : 34 : psbtin.final_script_sig.clear();
# 55 : 34 : psbtin.final_script_witness.SetNull();
# 56 : 34 : inputs.push_back(psbtin);
# 57 : 34 : return true;
# 58 : 36 : }
# 59 : :
# 60 : : bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput& psbtout)
# 61 : 18 : {
# 62 : 18 : tx->vout.push_back(txout);
# 63 : 18 : outputs.push_back(psbtout);
# 64 : 18 : return true;
# 65 : 18 : }
# 66 : :
# 67 : : bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const
# 68 : 1492 : {
# 69 : 1492 : const PSBTInput& input = inputs[input_index];
# 70 : 1492 : uint32_t prevout_index = tx->vin[input_index].prevout.n;
# 71 [ + + ]: 1492 : if (input.non_witness_utxo) {
# 72 [ + + ]: 771 : if (prevout_index >= input.non_witness_utxo->vout.size()) {
# 73 : 7 : return false;
# 74 : 7 : }
# 75 [ - + ]: 764 : if (input.non_witness_utxo->GetHash() != tx->vin[input_index].prevout.hash) {
# 76 : 0 : return false;
# 77 : 0 : }
# 78 : 764 : utxo = input.non_witness_utxo->vout[prevout_index];
# 79 [ + + ]: 764 : } else if (!input.witness_utxo.IsNull()) {
# 80 : 42 : utxo = input.witness_utxo;
# 81 : 679 : } else {
# 82 : 679 : return false;
# 83 : 679 : }
# 84 : 806 : return true;
# 85 : 1492 : }
# 86 : :
# 87 : : bool PSBTInput::IsNull() const
# 88 : 0 : {
# 89 [ # # ][ # # ]: 0 : return !non_witness_utxo && witness_utxo.IsNull() && partial_sigs.empty() && unknown.empty() && hd_keypaths.empty() && redeem_script.empty() && witness_script.empty();
# [ # # ][ # # ]
# [ # # ][ # # ]
# [ # # ]
# 90 : 0 : }
# 91 : :
# 92 : : void PSBTInput::FillSignatureData(SignatureData& sigdata) const
# 93 : 12574 : {
# 94 [ - + ]: 12574 : if (!final_script_sig.empty()) {
# 95 : 0 : sigdata.scriptSig = final_script_sig;
# 96 : 0 : sigdata.complete = true;
# 97 : 0 : }
# 98 [ - + ]: 12574 : if (!final_script_witness.IsNull()) {
# 99 : 0 : sigdata.scriptWitness = final_script_witness;
# 100 : 0 : sigdata.complete = true;
# 101 : 0 : }
# 102 [ - + ]: 12574 : if (sigdata.complete) {
# 103 : 0 : return;
# 104 : 0 : }
# 105 : :
# 106 : 12574 : sigdata.signatures.insert(partial_sigs.begin(), partial_sigs.end());
# 107 [ + + ]: 12574 : if (!redeem_script.empty()) {
# 108 : 794 : sigdata.redeem_script = redeem_script;
# 109 : 794 : }
# 110 [ + + ]: 12574 : if (!witness_script.empty()) {
# 111 : 1597 : sigdata.witness_script = witness_script;
# 112 : 1597 : }
# 113 [ + + ]: 12574 : for (const auto& key_pair : hd_keypaths) {
# 114 : 6796 : sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair);
# 115 : 6796 : }
# 116 : 12574 : }
# 117 : :
# 118 : : void PSBTInput::FromSignatureData(const SignatureData& sigdata)
# 119 : 6265 : {
# 120 [ + + ]: 6265 : if (sigdata.complete) {
# 121 : 353 : partial_sigs.clear();
# 122 : 353 : hd_keypaths.clear();
# 123 : 353 : redeem_script.clear();
# 124 : 353 : witness_script.clear();
# 125 : :
# 126 [ + + ]: 353 : if (!sigdata.scriptSig.empty()) {
# 127 : 101 : final_script_sig = sigdata.scriptSig;
# 128 : 101 : }
# 129 [ + + ]: 353 : if (!sigdata.scriptWitness.IsNull()) {
# 130 : 266 : final_script_witness = sigdata.scriptWitness;
# 131 : 266 : }
# 132 : 353 : return;
# 133 : 353 : }
# 134 : :
# 135 : 5912 : partial_sigs.insert(sigdata.signatures.begin(), sigdata.signatures.end());
# 136 [ + + ][ + + ]: 5912 : if (redeem_script.empty() && !sigdata.redeem_script.empty()) {
# 137 : 109 : redeem_script = sigdata.redeem_script;
# 138 : 109 : }
# 139 [ + + ][ + + ]: 5912 : if (witness_script.empty() && !sigdata.witness_script.empty()) {
# 140 : 82 : witness_script = sigdata.witness_script;
# 141 : 82 : }
# 142 [ + + ]: 5912 : for (const auto& entry : sigdata.misc_pubkeys) {
# 143 : 3650 : hd_keypaths.emplace(entry.second);
# 144 : 3650 : }
# 145 : 5912 : }
# 146 : :
# 147 : : void PSBTInput::Merge(const PSBTInput& input)
# 148 : 11 : {
# 149 [ + + ][ + + ]: 11 : if (!non_witness_utxo && input.non_witness_utxo) non_witness_utxo = input.non_witness_utxo;
# 150 [ + + ][ + + ]: 11 : if (witness_utxo.IsNull() && !input.witness_utxo.IsNull()) {
# 151 : : // TODO: For segwit v1, we will want to clear out the non-witness utxo when setting a witness one. For v0 and non-segwit, this is not safe
# 152 : 2 : witness_utxo = input.witness_utxo;
# 153 : 2 : }
# 154 : :
# 155 : 11 : partial_sigs.insert(input.partial_sigs.begin(), input.partial_sigs.end());
# 156 : 11 : ripemd160_preimages.insert(input.ripemd160_preimages.begin(), input.ripemd160_preimages.end());
# 157 : 11 : sha256_preimages.insert(input.sha256_preimages.begin(), input.sha256_preimages.end());
# 158 : 11 : hash160_preimages.insert(input.hash160_preimages.begin(), input.hash160_preimages.end());
# 159 : 11 : hash256_preimages.insert(input.hash256_preimages.begin(), input.hash256_preimages.end());
# 160 : 11 : hd_keypaths.insert(input.hd_keypaths.begin(), input.hd_keypaths.end());
# 161 : 11 : unknown.insert(input.unknown.begin(), input.unknown.end());
# 162 : :
# 163 [ + + ][ - + ]: 11 : if (redeem_script.empty() && !input.redeem_script.empty()) redeem_script = input.redeem_script;
# 164 [ + + ][ - + ]: 11 : if (witness_script.empty() && !input.witness_script.empty()) witness_script = input.witness_script;
# 165 [ + - ][ - + ]: 11 : if (final_script_sig.empty() && !input.final_script_sig.empty()) final_script_sig = input.final_script_sig;
# 166 [ + + ][ + + ]: 11 : if (final_script_witness.IsNull() && !input.final_script_witness.IsNull()) final_script_witness = input.final_script_witness;
# 167 : 11 : }
# 168 : :
# 169 : : void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
# 170 : 775 : {
# 171 [ + + ]: 775 : if (!redeem_script.empty()) {
# 172 : 16 : sigdata.redeem_script = redeem_script;
# 173 : 16 : }
# 174 [ + + ]: 775 : if (!witness_script.empty()) {
# 175 : 51 : sigdata.witness_script = witness_script;
# 176 : 51 : }
# 177 [ + + ]: 775 : for (const auto& key_pair : hd_keypaths) {
# 178 : 247 : sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair);
# 179 : 247 : }
# 180 : 775 : }
# 181 : :
# 182 : : void PSBTOutput::FromSignatureData(const SignatureData& sigdata)
# 183 : 775 : {
# 184 [ + + ][ + + ]: 775 : if (redeem_script.empty() && !sigdata.redeem_script.empty()) {
# 185 : 66 : redeem_script = sigdata.redeem_script;
# 186 : 66 : }
# 187 [ + + ][ + + ]: 775 : if (witness_script.empty() && !sigdata.witness_script.empty()) {
# 188 : 76 : witness_script = sigdata.witness_script;
# 189 : 76 : }
# 190 [ + + ]: 775 : for (const auto& entry : sigdata.misc_pubkeys) {
# 191 : 704 : hd_keypaths.emplace(entry.second);
# 192 : 704 : }
# 193 : 775 : }
# 194 : :
# 195 : : bool PSBTOutput::IsNull() const
# 196 : 0 : {
# 197 [ # # ][ # # ]: 0 : return redeem_script.empty() && witness_script.empty() && hd_keypaths.empty() && unknown.empty();
# [ # # ][ # # ]
# 198 : 0 : }
# 199 : :
# 200 : : void PSBTOutput::Merge(const PSBTOutput& output)
# 201 : 10 : {
# 202 : 10 : hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.end());
# 203 : 10 : unknown.insert(output.unknown.begin(), output.unknown.end());
# 204 : :
# 205 [ + - ][ - + ]: 10 : if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
# 206 [ + + ][ - + ]: 10 : if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
# 207 : 10 : }
# 208 : : bool PSBTInputSigned(const PSBTInput& input)
# 209 : 23643 : {
# 210 [ + + ][ + + ]: 23643 : return !input.final_script_sig.empty() || !input.final_script_witness.IsNull();
# 211 : 23643 : }
# 212 : :
# 213 : 0 : size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt) {
# 214 : 0 : size_t count = 0;
# 215 [ # # ]: 0 : for (const auto& input : psbt.inputs) {
# 216 [ # # ]: 0 : if (!PSBTInputSigned(input)) {
# 217 : 0 : count++;
# 218 : 0 : }
# 219 : 0 : }
# 220 : :
# 221 : 0 : return count;
# 222 : 0 : }
# 223 : :
# 224 : : void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index)
# 225 : 775 : {
# 226 : 775 : CMutableTransaction& tx = *Assert(psbt.tx);
# 227 : 775 : const CTxOut& out = tx.vout.at(index);
# 228 : 775 : PSBTOutput& psbt_out = psbt.outputs.at(index);
# 229 : :
# 230 : : // Fill a SignatureData with output info
# 231 : 775 : SignatureData sigdata;
# 232 : 775 : psbt_out.FillSignatureData(sigdata);
# 233 : :
# 234 : : // Construct a would-be spend of this output, to update sigdata with.
# 235 : : // Note that ProduceSignature is used to fill in metadata (not actual signatures),
# 236 : : // so provider does not need to provide any private keys (it can be a HidingSigningProvider).
# 237 : 775 : MutableTransactionSignatureCreator creator(&tx, /*input_idx=*/0, out.nValue, SIGHASH_ALL);
# 238 : 775 : ProduceSignature(provider, creator, out.scriptPubKey, sigdata);
# 239 : :
# 240 : : // Put redeem_script, witness_script, key paths, into PSBTOutput.
# 241 : 775 : psbt_out.FromSignatureData(sigdata);
# 242 : 775 : }
# 243 : :
# 244 : : PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt)
# 245 : 915 : {
# 246 : 915 : const CMutableTransaction& tx = *psbt.tx;
# 247 : 915 : bool have_all_spent_outputs = true;
# 248 : 915 : std::vector<CTxOut> utxos(tx.vin.size());
# 249 [ + + ]: 2385 : for (size_t idx = 0; idx < tx.vin.size(); ++idx) {
# 250 [ + + ]: 1470 : if (!psbt.GetInputUTXO(utxos[idx], idx)) have_all_spent_outputs = false;
# 251 : 1470 : }
# 252 : 915 : PrecomputedTransactionData txdata;
# 253 [ + + ]: 915 : if (have_all_spent_outputs) {
# 254 : 465 : txdata.Init(tx, std::move(utxos), true);
# 255 : 465 : } else {
# 256 : 450 : txdata.Init(tx, {}, true);
# 257 : 450 : }
# 258 : 915 : return txdata;
# 259 : 915 : }
# 260 : :
# 261 : : bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata, bool finalize)
# 262 : 6648 : {
# 263 : 6648 : PSBTInput& input = psbt.inputs.at(index);
# 264 : 6648 : const CMutableTransaction& tx = *psbt.tx;
# 265 : :
# 266 [ + + ]: 6648 : if (PSBTInputSigned(input)) {
# 267 : 339 : return true;
# 268 : 339 : }
# 269 : :
# 270 : : // Fill SignatureData with input info
# 271 : 6309 : SignatureData sigdata;
# 272 : 6309 : input.FillSignatureData(sigdata);
# 273 : :
# 274 : : // Get UTXO
# 275 : 6309 : bool require_witness_sig = false;
# 276 : 6309 : CTxOut utxo;
# 277 : :
# 278 [ + + ]: 6309 : if (input.non_witness_utxo) {
# 279 : : // If we're taking our information from a non-witness UTXO, verify that it matches the prevout.
# 280 : 6210 : COutPoint prevout = tx.vin[index].prevout;
# 281 [ - + ]: 6210 : if (prevout.n >= input.non_witness_utxo->vout.size()) {
# 282 : 0 : return false;
# 283 : 0 : }
# 284 [ - + ]: 6210 : if (input.non_witness_utxo->GetHash() != prevout.hash) {
# 285 : 0 : return false;
# 286 : 0 : }
# 287 : 6210 : utxo = input.non_witness_utxo->vout[prevout.n];
# 288 [ + + ]: 6210 : } else if (!input.witness_utxo.IsNull()) {
# 289 : 87 : utxo = input.witness_utxo;
# 290 : : // When we're taking our information from a witness UTXO, we can't verify it is actually data from
# 291 : : // the output being spent. This is safe in case a witness signature is produced (which includes this
# 292 : : // information directly in the hash), but not for non-witness signatures. Remember that we require
# 293 : : // a witness signature in this situation.
# 294 : 87 : require_witness_sig = true;
# 295 : 87 : } else {
# 296 : 12 : return false;
# 297 : 12 : }
# 298 : :
# 299 : 6297 : sigdata.witness = false;
# 300 : 6297 : bool sig_complete;
# 301 [ + + ]: 6297 : if (txdata == nullptr) {
# 302 : 2 : sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata);
# 303 : 6295 : } else {
# 304 : 6295 : MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, txdata, sighash);
# 305 : 6295 : sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata);
# 306 : 6295 : }
# 307 : : // Verify that a witness signature was produced in case one was required.
# 308 [ + + ][ + + ]: 6297 : if (require_witness_sig && !sigdata.witness) return false;
# 309 : :
# 310 : : // If we are not finalizing, set sigdata.complete to false to not set the scriptWitness
# 311 [ + + ][ + + ]: 6265 : if (!finalize && sigdata.complete) sigdata.complete = false;
# 312 : :
# 313 : 6265 : input.FromSignatureData(sigdata);
# 314 : :
# 315 : : // If we have a witness signature, put a witness UTXO.
# 316 : : // TODO: For segwit v1, we should remove the non_witness_utxo
# 317 [ + + ]: 6265 : if (sigdata.witness) {
# 318 : 4584 : input.witness_utxo = utxo;
# 319 : : // input.non_witness_utxo = nullptr;
# 320 : 4584 : }
# 321 : :
# 322 : : // Fill in the missing info
# 323 [ + + ]: 6265 : if (out_sigdata) {
# 324 : 6 : out_sigdata->missing_pubkeys = sigdata.missing_pubkeys;
# 325 : 6 : out_sigdata->missing_sigs = sigdata.missing_sigs;
# 326 : 6 : out_sigdata->missing_redeem_script = sigdata.missing_redeem_script;
# 327 : 6 : out_sigdata->missing_witness_script = sigdata.missing_witness_script;
# 328 : 6 : }
# 329 : :
# 330 : 6265 : return sig_complete;
# 331 : 6297 : }
# 332 : :
# 333 : : bool FinalizePSBT(PartiallySignedTransaction& psbtx)
# 334 : 216 : {
# 335 : : // Finalize input signatures -- in case we have partial signatures that add up to a complete
# 336 : : // signature, but have not combined them yet (e.g. because the combiner that created this
# 337 : : // PartiallySignedTransaction did not understand them), this will combine them into a final
# 338 : : // script.
# 339 : 216 : bool complete = true;
# 340 : 216 : const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
# 341 [ + + ]: 575 : for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
# 342 : 359 : complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL, nullptr, true);
# 343 : 359 : }
# 344 : :
# 345 : 216 : return complete;
# 346 : 216 : }
# 347 : :
# 348 : : bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransaction& result)
# 349 : 214 : {
# 350 : : // It's not safe to extract a PSBT that isn't finalized, and there's no easy way to check
# 351 : : // whether a PSBT is finalized without finalizing it, so we just do this.
# 352 [ + + ]: 214 : if (!FinalizePSBT(psbtx)) {
# 353 : 13 : return false;
# 354 : 13 : }
# 355 : :
# 356 : 201 : result = *psbtx.tx;
# 357 [ + + ]: 537 : for (unsigned int i = 0; i < result.vin.size(); ++i) {
# 358 : 336 : result.vin[i].scriptSig = psbtx.inputs[i].final_script_sig;
# 359 : 336 : result.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness;
# 360 : 336 : }
# 361 : 201 : return true;
# 362 : 214 : }
# 363 : :
# 364 : : TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs)
# 365 : 7 : {
# 366 : 7 : out = psbtxs[0]; // Copy the first one
# 367 : :
# 368 : : // Merge
# 369 [ + + ]: 14 : for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) {
# 370 [ - + ]: 7 : if (!out.Merge(*it)) {
# 371 : 0 : return TransactionError::PSBT_MISMATCH;
# 372 : 0 : }
# 373 : 7 : }
# 374 : 7 : return TransactionError::OK;
# 375 : 7 : }
# 376 : :
# 377 : 24 : std::string PSBTRoleName(PSBTRole role) {
# 378 [ - + ]: 24 : switch (role) {
# 379 [ + + ]: 8 : case PSBTRole::CREATOR: return "creator";
# 380 [ + + ]: 4 : case PSBTRole::UPDATER: return "updater";
# 381 [ + + ]: 4 : case PSBTRole::SIGNER: return "signer";
# 382 [ + + ]: 4 : case PSBTRole::FINALIZER: return "finalizer";
# 383 [ + + ]: 4 : case PSBTRole::EXTRACTOR: return "extractor";
# 384 : : // no default case, so the compiler can warn about missing cases
# 385 : 24 : }
# 386 : 0 : assert(false);
# 387 : 0 : }
# 388 : :
# 389 : : bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
# 390 : 717 : {
# 391 : 717 : bool invalid;
# 392 : 717 : std::string tx_data = DecodeBase64(base64_tx, &invalid);
# 393 [ + + ]: 717 : if (invalid) {
# 394 : 4 : error = "invalid base64";
# 395 : 4 : return false;
# 396 : 4 : }
# 397 : 713 : return DecodeRawPSBT(psbt, tx_data, error);
# 398 : 717 : }
# 399 : :
# 400 : : bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, std::string& error)
# 401 : 713 : {
# 402 : 713 : CDataStream ss_data(MakeByteSpan(tx_data), SER_NETWORK, PROTOCOL_VERSION);
# 403 : 713 : try {
# 404 : 713 : ss_data >> psbt;
# 405 [ - + ]: 713 : if (!ss_data.empty()) {
# 406 : 0 : error = "extra data after PSBT";
# 407 : 0 : return false;
# 408 : 0 : }
# 409 : 713 : } catch (const std::exception& e) {
# 410 : 56 : error = e.what();
# 411 : 56 : return false;
# 412 : 56 : }
# 413 : 657 : return true;
# 414 : 713 : }
# 415 : :
# 416 : : uint32_t PartiallySignedTransaction::GetVersion() const
# 417 : 868 : {
# 418 [ + + ]: 868 : if (m_version != std::nullopt) {
# 419 : 2 : return *m_version;
# 420 : 2 : }
# 421 : 866 : return 0;
# 422 : 868 : }
|