You are a model-assisted governance replay worker. You are reviewing a pre-disclosure XRPL amendment packet. Do not assume any future bug report. Use only the packet facts and code excerpts. Your job is not to prove an exploit; it is to decide whether the packet earns a public challenge before validators default to YES. Return only strict JSON matching this schema: {"cited_facts": [{"fact_id": "F1", "why_it_matters": "short"}], "decision_summary": "short answer", "forbidden_stronger_claim": "claim this output does not support", "identified_surfaces": ["function/file/surface names"], "predicted_failure_class": "specific vulnerability class or generic risk", "review_questions": ["questions validators should answer before voting yes"], "route": "PROCEED | DELAY_FOR_FIX | HOLD_FOR_CHALLENGE | REJECT", "specific_challenge": "the most concrete validator challenge this packet justifies", "vote_default": "YES | NO | HOLD"} Routing policy: - PROCEED means default YES is justified by this packet. - HOLD_FOR_CHALLENGE means public validator challenge is required. - DELAY_FOR_FIX means the packet already contains bug/fix sequencing evidence. - REJECT means the packet already contains disabling/obsolete evidence. A high-value answer names the concrete code surface and the failure class validators should inspect. Avoid generic 'security review needed' unless the packet gives no more specific signal. PACKET: { "amendment_name": "Batch / XLS-56", "code_excerpts": { "rippled_2_5_0_STTx_batch_signature_window": " 269 STTx::checkBatchSign(\n 270 RequireFullyCanonicalSig requireCanonicalSig,\n 271 Rules const& rules) const\n 272 {\n 273 try\n 274 {\n 275 XRPL_ASSERT(\n 276 getTxnType() == ttBATCH,\n 277 \"STTx::checkBatchSign : not a batch transaction\");\n 278 if (getTxnType() != ttBATCH)\n 279 {\n 280 JLOG(debugLog().fatal()) << \"not a batch transaction\";\n 281 return Unexpected(\"Not a batch transaction.\");\n 282 }\n 283 STArray const& signers{getFieldArray(sfBatchSigners)};\n 284 for (auto const& signer : signers)\n 285 {\n 286 Blob const& signingPubKey = signer.getFieldVL(sfSigningPubKey);\n 287 auto const result = signingPubKey.empty()\n 288 ? checkBatchMultiSign(signer, requireCanonicalSig, rules)\n 289 : checkBatchSingleSign(signer, requireCanonicalSig);\n 290 \n 291 if (!result)\n 292 return result;\n 293 }\n 294 return {};\n 295 }\n 296 catch (std::exception const& e)\n 297 {\n 298 JLOG(debugLog().error())\n 299 << \"Batch signature check failed: \" << e.what();\n 300 }\n 301 return Unexpected(\"Internal batch signature check failure.\");\n 302 }\n 303 \n 304 Json::Value\n 305 STTx::getJson(JsonOptions options) const\n 306 {\n 307 Json::Value ret = STObject::getJson(JsonOptions::none);\n 308 if (!(options & JsonOptions::disable_API_prior_V2))\n 309 ret[jss::hash] = to_string(getTransactionID());\n 310 return ret;\n 311 }\n 312 \n 313 Json::Value\n 314 STTx::getJson(JsonOptions options, bool binary) const\n 315 {\n 316 bool const V1 = !(options & JsonOptions::disable_API_prior_V2);\n 317 \n 318 if (binary)\n 319 {\n 320 Serializer s = STObject::getSerializer();\n 321 std::string const dataBin = strHex(s.peekData());\n 322 \n 323 if (V1)\n 324 {\n 325 Json::Value ret(Json::objectValue);\n 326 ret[jss::tx] = dataBin;\n 327 ret[jss::hash] = to_string(getTransactionID());\n 328 return ret;\n 329 }\n 330 else\n 331 return Json::Value{dataBin};\n 332 }\n 333 \n 334 Json::Value ret = STObject::getJson(JsonOptions::none);\n 335 if (V1)\n 336 ret[jss::hash] = to_string(getTransactionID());\n 337 \n 338 return ret;\n 339 }\n 340 \n 341 std::string const&\n 342 STTx::getMetaSQLInsertReplaceHeader()\n 343 {\n 344 static std::string const sql =\n 345 \"INSERT OR REPLACE INTO Transactions \"\n 346 \"(TransID, TransType, FromAcct, FromSeq, LedgerSeq, Status, RawTxn, \"\n 347 \"TxnMeta)\"\n 348 \" VALUES \";\n 349 \n 350 return sql;\n 351 }\n 352 \n 353 std::string\n 354 STTx::getMetaSQL(std::uint32_t inLedger, std::string const& escapedMetaData)\n 355 const\n 356 {\n 357 Serializer s;\n 358 add(s);\n 359 return getMetaSQL(s, inLedger, txnSqlValidated, escapedMetaData);\n 360 }\n 361 \n 362 // VFALCO This could be a free function elsewhere\n 363 std::string\n 364 STTx::getMetaSQL(\n 365 Serializer rawTxn,\n 366 std::uint32_t inLedger,\n 367 char status,\n 368 std::string const& escapedMetaData) const\n 369 {\n 370 static boost::format bfTrans(\n 371 \"('%s', '%s', '%s', '%d', '%d', '%c', %s, %s)\");\n 372 std::string rTxn = sqlBlobLiteral(rawTxn.peekData());\n 373 \n 374 auto format = TxFormats::getInstance().findByType(tx_type_);\n 375 XRPL_ASSERT(format, \"ripple::STTx::getMetaSQL : non-null type format\");\n 376 \n 377 return str(\n 378 boost::format(bfTrans) % to_string(getTransactionID()) %\n 379 format->getName() % toBase58(getAccountID(sfAccount)) %\n 380 getFieldU32(sfSequence) % inLedger % status % rTxn % escapedMetaData);\n 381 }\n 382 \n 383 static Expected\n 384 singleSignHelper(\n 385 STObject const& signer,\n 386 Slice const& data,\n 387 bool const fullyCanonical)\n 388 {\n 389 // We don't allow both a non-empty sfSigningPubKey and an sfSigners.\n 390 // That would allow the transaction to be signed two ways. So if both\n 391 // fields are present the signature is invalid.\n 392 if (signer.isFieldPresent(sfSigners))\n 393 return Unexpected(\"Cannot both single- and multi-sign.\");\n 394 \n 395 bool validSig = false;\n 396 try\n 397 {\n 398 auto const spk = signer.getFieldVL(sfSigningPubKey);\n 399 if (publicKeyType(makeSlice(spk)))\n 400 {\n 401 Blob const signature = signer.getFieldVL(sfTxnSignature);\n 402 validSig = verify(\n 403 PublicKey(makeSlice(spk)),\n 404 data,\n 405 makeSlice(signature),\n 406 fullyCanonical);\n 407 }\n 408 }\n 409 catch (std::exception const&)\n 410 {\n 411 validSig = false;\n 412 }\n 413 \n 414 if (!validSig)\n 415 return Unexpected(\"Invalid signature.\");\n 416 \n 417 return {};\n 418 }\n 419 \n 420 Expected\n 421 STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const\n 422 {\n 423 auto const data = getSigningData(*this);\n 424 bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||\n 425 (requireCanonicalSig == STTx::RequireFullyCanonicalSig::yes);\n 426 return singleSignHelper(*this, makeSlice(data), fullyCanonical);\n 427 }\n 428 \n 429 Expected\n 430 STTx::checkBatchSingleSign(\n 431 STObject const& batchSigner,\n 432 RequireFullyCanonicalSig requireCanonicalSig) const\n 433 {\n 434 Serializer msg;\n 435 serializeBatch(msg, getFlags(), getBatchTransactionIDs());\n 436 bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||\n 437 (requireCanonicalSig == STTx::RequireFullyCanonicalSig::yes);\n 438 return singleSignHelper(batchSigner, msg.slice(), fullyCanonical);\n 439 }\n 440 \n 441 Expected\n 442 multiSignHelper(\n 443 STObject const& signerObj,\n 444 bool const fullyCanonical,\n 445 std::function makeMsg,\n 446 Rules const& rules)\n 447 {\n 448 // Make sure the MultiSigners are present. Otherwise they are not\n 449 // attempting multi-signing and we just have a bad SigningPubKey.\n 450 if (!signerObj.isFieldPresent(sfSigners))\n 451 return Unexpected(\"Empty SigningPubKey.\");\n 452 \n 453 // We don't allow both an sfSigners and an sfTxnSignature. Both fields\n 454 // being present would indicate that the transaction is signed both ways.\n 455 if (signerObj.isFieldPresent(sfTxnSignature))\n 456 return Unexpected(\"Cannot both single- and multi-sign.\");\n 457 \n 458 STArray const& signers{signerObj.getFieldArray(sfSigners)};\n 459 \n 460 // There are well known bounds that the number of signers must be within.\n 461 if (signers.size() < STTx::minMultiSigners ||\n 462 signers.size() > STTx::maxMultiSigners(&rules))\n 463 return Unexpected(\"Invalid Signers array size.\");\n 464 \n 465 // We also use the sfAccount field inside the loop. Get it once.\n 466 auto const txnAccountID = signerObj.getAccountID(sfAccount);\n 467 \n 468 // Signers must be in sorted order by AccountID.\n 469 AccountID lastAccountID(beast::zero);\n 470 \n 471 for (auto const& signer : signers)\n 472 {\n 473 auto const accountID = signer.getAccountID(sfAccount);\n 474 \n 475 // The account owner may not multisign for themselves.\n 476 if (accountID == txnAccountID)\n 477 return Unexpected(\"Invalid multisigner.\");\n 478 \n 479 // No duplicate signers allowed.\n 480 if (lastAccountID == accountID)\n 481 return Unexpected(\"Duplicate Signers not allowed.\");\n 482 \n 483 // Accounts must be in order by account ID. No duplicates allowed.\n 484 if (lastAccountID > accountID)\n 485 return Unexpected(\"Unsorted Signers array.\");\n 486 \n 487 // The next signature must be greater than this one.\n 488 lastAccountID = accountID;\n 489 \n 490 // Verify the signature.\n 491 bool validSig = false;\n 492 try\n 493 {\n 494 auto spk = signer.getFieldVL(sfSigningPubKey);\n 495 if (publicKeyType(makeSlice(spk)))\n 496 {\n 497 Blob const signature = signer.getFieldVL(sfTxnSignature);\n 498 validSig = verify(\n 499 PublicKey(makeSlice(spk)),\n 500 makeMsg(accountID).slice(),\n 501 makeSlice(signature),\n 502 fullyCanonical);\n 503 }\n 504 }\n 505 catch (std::exception const&)\n 506 {\n 507 // We assume any problem lies with the signature.\n 508 validSig = false;\n 509 }\n 510 if (!validSig)\n 511 return Unexpected(\n 512 std::string(\"Invalid signature on account \") +\n 513 toBase58(accountID) + \".\");\n 514 }\n 515 // All signatures verified.\n 516 return {};\n 517 }\n 518 \n 519 Expected\n 520 STTx::checkBatchMultiSign(\n 521 STObject const& batchSigner,\n 522 RequireFullyCanonicalSig requireCanonicalSig,\n 523 Rules const& rules) const\n 524 {\n 525 bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||\n 526 (requireCanonicalSig == RequireFullyCanonicalSig::yes);\n 527 \n 528 // We can ease the computational load inside the loop a bit by\n 529 // pre-constructing part of the data that we hash. Fill a Serializer\n 530 // with the stuff that stays constant from signature to signature.\n 531 Serializer dataStart;\n 532 serializeBatch(dataStart, getFlags(), getBatchTransactionIDs());\n 533 return multiSignHelper(\n 534 batchSigner,\n 535 fullyCanonical,\n 536 [&dataStart](AccountID const& accountID) mutable -> Serializer {\n 537 Serializer s = dataStart;\n 538 finishMultiSigningData(accountID, s);\n 539 return s;\n 540 },\n 541 rules);\n", "rippled_3_1_3_STTx_batch_signature_window": " 300 STTx::checkBatchSign(\n 301 RequireFullyCanonicalSig requireCanonicalSig,\n 302 Rules const& rules) const\n 303 {\n 304 try\n 305 {\n 306 XRPL_ASSERT(\n 307 getTxnType() == ttBATCH,\n 308 \"STTx::checkBatchSign : not a batch transaction\");\n 309 if (getTxnType() != ttBATCH)\n 310 {\n 311 JLOG(debugLog().fatal()) << \"not a batch transaction\";\n 312 return Unexpected(\"Not a batch transaction.\");\n 313 }\n 314 STArray const& signers{getFieldArray(sfBatchSigners)};\n 315 for (auto const& signer : signers)\n 316 {\n 317 Blob const& signingPubKey = signer.getFieldVL(sfSigningPubKey);\n 318 auto const result = signingPubKey.empty()\n 319 ? checkBatchMultiSign(signer, requireCanonicalSig, rules)\n 320 : checkBatchSingleSign(signer, requireCanonicalSig);\n 321 \n 322 if (!result)\n 323 return result;\n 324 }\n 325 return {};\n 326 }\n 327 catch (std::exception const& e)\n 328 {\n 329 JLOG(debugLog().error())\n 330 << \"Batch signature check failed: \" << e.what();\n 331 }\n 332 return Unexpected(\"Internal batch signature check failure.\");\n 333 }\n 334 \n 335 Json::Value\n 336 STTx::getJson(JsonOptions options) const\n 337 {\n 338 Json::Value ret = STObject::getJson(JsonOptions::none);\n 339 if (!(options & JsonOptions::disable_API_prior_V2))\n 340 ret[jss::hash] = to_string(getTransactionID());\n 341 return ret;\n 342 }\n 343 \n 344 Json::Value\n 345 STTx::getJson(JsonOptions options, bool binary) const\n 346 {\n 347 bool const V1 = !(options & JsonOptions::disable_API_prior_V2);\n 348 \n 349 if (binary)\n 350 {\n 351 Serializer s = STObject::getSerializer();\n 352 std::string const dataBin = strHex(s.peekData());\n 353 \n 354 if (V1)\n 355 {\n 356 Json::Value ret(Json::objectValue);\n 357 ret[jss::tx] = dataBin;\n 358 ret[jss::hash] = to_string(getTransactionID());\n 359 return ret;\n 360 }\n 361 else\n 362 return Json::Value{dataBin};\n 363 }\n 364 \n 365 Json::Value ret = STObject::getJson(JsonOptions::none);\n 366 if (V1)\n 367 ret[jss::hash] = to_string(getTransactionID());\n 368 \n 369 return ret;\n 370 }\n 371 \n 372 std::string const&\n 373 STTx::getMetaSQLInsertReplaceHeader()\n 374 {\n 375 static std::string const sql =\n 376 \"INSERT OR REPLACE INTO Transactions \"\n 377 \"(TransID, TransType, FromAcct, FromSeq, LedgerSeq, Status, RawTxn, \"\n 378 \"TxnMeta)\"\n 379 \" VALUES \";\n 380 \n 381 return sql;\n 382 }\n 383 \n 384 std::string\n 385 STTx::getMetaSQL(std::uint32_t inLedger, std::string const& escapedMetaData)\n 386 const\n 387 {\n 388 Serializer s;\n 389 add(s);\n 390 return getMetaSQL(s, inLedger, txnSqlValidated, escapedMetaData);\n 391 }\n 392 \n 393 // VFALCO This could be a free function elsewhere\n 394 std::string\n 395 STTx::getMetaSQL(\n 396 Serializer rawTxn,\n 397 std::uint32_t inLedger,\n 398 char status,\n 399 std::string const& escapedMetaData) const\n 400 {\n 401 static boost::format bfTrans(\n 402 \"('%s', '%s', '%s', '%d', '%d', '%c', %s, %s)\");\n 403 std::string rTxn = sqlBlobLiteral(rawTxn.peekData());\n 404 \n 405 auto format = TxFormats::getInstance().findByType(tx_type_);\n 406 XRPL_ASSERT(format, \"ripple::STTx::getMetaSQL : non-null type format\");\n 407 \n 408 return str(\n 409 boost::format(bfTrans) % to_string(getTransactionID()) %\n 410 format->getName() % toBase58(getAccountID(sfAccount)) %\n 411 getFieldU32(sfSequence) % inLedger % status % rTxn % escapedMetaData);\n 412 }\n 413 \n 414 static Expected\n 415 singleSignHelper(\n 416 STObject const& sigObject,\n 417 Slice const& data,\n 418 bool const fullyCanonical)\n 419 {\n 420 // We don't allow both a non-empty sfSigningPubKey and an sfSigners.\n 421 // That would allow the transaction to be signed two ways. So if both\n 422 // fields are present the signature is invalid.\n 423 if (sigObject.isFieldPresent(sfSigners))\n 424 return Unexpected(\"Cannot both single- and multi-sign.\");\n 425 \n 426 bool validSig = false;\n 427 try\n 428 {\n 429 auto const spk = sigObject.getFieldVL(sfSigningPubKey);\n 430 if (publicKeyType(makeSlice(spk)))\n 431 {\n 432 Blob const signature = sigObject.getFieldVL(sfTxnSignature);\n 433 validSig = verify(\n 434 PublicKey(makeSlice(spk)),\n 435 data,\n 436 makeSlice(signature),\n 437 fullyCanonical);\n 438 }\n 439 }\n 440 catch (std::exception const&)\n 441 {\n 442 validSig = false;\n 443 }\n 444 \n 445 if (!validSig)\n 446 return Unexpected(\"Invalid signature.\");\n 447 \n 448 return {};\n 449 }\n 450 \n 451 Expected\n 452 STTx::checkSingleSign(\n 453 RequireFullyCanonicalSig requireCanonicalSig,\n 454 STObject const& sigObject) const\n 455 {\n 456 auto const data = getSigningData(*this);\n 457 bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||\n 458 (requireCanonicalSig == STTx::RequireFullyCanonicalSig::yes);\n 459 return singleSignHelper(sigObject, makeSlice(data), fullyCanonical);\n 460 }\n 461 \n 462 Expected\n 463 STTx::checkBatchSingleSign(\n 464 STObject const& batchSigner,\n 465 RequireFullyCanonicalSig requireCanonicalSig) const\n 466 {\n 467 Serializer msg;\n 468 serializeBatch(msg, getFlags(), getBatchTransactionIDs());\n 469 bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||\n 470 (requireCanonicalSig == STTx::RequireFullyCanonicalSig::yes);\n 471 return singleSignHelper(batchSigner, msg.slice(), fullyCanonical);\n 472 }\n 473 \n 474 Expected\n 475 multiSignHelper(\n 476 STObject const& sigObject,\n 477 std::optional txnAccountID,\n 478 bool const fullyCanonical,\n 479 std::function makeMsg,\n 480 Rules const& rules)\n 481 {\n 482 // Make sure the MultiSigners are present. Otherwise they are not\n 483 // attempting multi-signing and we just have a bad SigningPubKey.\n 484 if (!sigObject.isFieldPresent(sfSigners))\n 485 return Unexpected(\"Empty SigningPubKey.\");\n 486 \n 487 // We don't allow both an sfSigners and an sfTxnSignature. Both fields\n 488 // being present would indicate that the transaction is signed both ways.\n 489 if (sigObject.isFieldPresent(sfTxnSignature))\n 490 return Unexpected(\"Cannot both single- and multi-sign.\");\n 491 \n 492 STArray const& signers{sigObject.getFieldArray(sfSigners)};\n 493 \n 494 // There are well known bounds that the number of signers must be within.\n 495 if (signers.size() < STTx::minMultiSigners ||\n 496 signers.size() > STTx::maxMultiSigners(&rules))\n 497 return Unexpected(\"Invalid Signers array size.\");\n 498 \n 499 // Signers must be in sorted order by AccountID.\n 500 AccountID lastAccountID(beast::zero);\n 501 \n 502 for (auto const& signer : signers)\n 503 {\n 504 auto const accountID = signer.getAccountID(sfAccount);\n 505 \n 506 // The account owner may not usually multisign for themselves.\n 507 // If they can, txnAccountID will be unseated, which is not equal to any\n 508 // value.\n 509 if (txnAccountID == accountID)\n 510 return Unexpected(\"Invalid multisigner.\");\n 511 \n 512 // No duplicate signers allowed.\n 513 if (lastAccountID == accountID)\n 514 return Unexpected(\"Duplicate Signers not allowed.\");\n 515 \n 516 // Accounts must be in order by account ID. No duplicates allowed.\n 517 if (lastAccountID > accountID)\n 518 return Unexpected(\"Unsorted Signers array.\");\n 519 \n 520 // The next signature must be greater than this one.\n 521 lastAccountID = accountID;\n 522 \n 523 // Verify the signature.\n 524 bool validSig = false;\n 525 std::optional errorWhat;\n 526 try\n 527 {\n 528 auto spk = signer.getFieldVL(sfSigningPubKey);\n 529 if (publicKeyType(makeSlice(spk)))\n 530 {\n 531 Blob const signature = signer.getFieldVL(sfTxnSignature);\n 532 validSig = verify(\n 533 PublicKey(makeSlice(spk)),\n 534 makeMsg(accountID).slice(),\n 535 makeSlice(signature),\n 536 fullyCanonical);\n 537 }\n 538 }\n 539 catch (std::exception const& e)\n 540 {\n 541 // We assume any problem lies with the signature.\n 542 validSig = false;\n 543 errorWhat = e.what();\n 544 }\n 545 if (!validSig)\n 546 return Unexpected(\n 547 std::string(\"Invalid signature on account \") +\n 548 toBase58(accountID) + errorWhat.value_or(\"\") + \".\");\n 549 }\n 550 // All signatures verified.\n 551 return {};\n 552 }\n 553 \n 554 Expected\n 555 STTx::checkBatchMultiSign(\n 556 STObject const& batchSigner,\n 557 RequireFullyCanonicalSig requireCanonicalSig,\n 558 Rules const& rules) const\n 559 {\n 560 bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||\n 561 (requireCanonicalSig == RequireFullyCanonicalSig::yes);\n 562 \n 563 // We can ease the computational load inside the loop a bit by\n 564 // pre-constructing part of the data that we hash. Fill a Serializer\n 565 // with the stuff that stays constant from signature to signature.\n 566 Serializer dataStart;\n 567 serializeBatch(dataStart, getFlags(), getBatchTransactionIDs());\n 568 return multiSignHelper(\n 569 batchSigner,\n 570 std::nullopt,\n 571 fullyCanonical,\n 572 [&dataStart](AccountID const& accountID) -> Serializer {\n 573 Serializer s = dataStart;\n 574 finishMultiSigningData(accountID, s);\n 575 return s;\n 576 },\n 577 rules);\n 578 }\n" }, "code_receipts": [ { "excerpt_file": "source/rippled-2.5.0-STTx-batch-signature-window.txt", "excerpt_sha256": "06bb570aa3e25eb9da54a2792282f7edde0507d5e500b6c100f3e641e2542c22", "git_head": "1e01cd34f7a216092ed779f291b43324c167167a", "line_window": "269-541", "path": "src/libxrpl/protocol/STTx.cpp", "repo": "/home/postfiat/repos/rippled-2.5.0" }, { "excerpt_file": "source/rippled-3.1.3-STTx-batch-signature-window.txt", "excerpt_sha256": "e7d52292f28a85b092b4721d13b72df4fdc19a2584b3f0a980616ae67f22eccc", "git_head": "46b241ace8b30d9c9775d60ffba7d24b21903896", "line_window": "300-578", "path": "src/libxrpl/protocol/STTx.cpp", "repo": "/home/postfiat/repos/rippled-3.1.3-old" } ], "constructed_at": "2026-06-01T23:54:09+00:00", "disclosure_cutoff": "2026-02-18T23:59:59Z", "facts": [ { "claim": "Batch packages multiple inner transactions into one atomic outer transaction.", "fact_id": "F1", "source": "XLS-56 / XRPL Batch documentation; user-supplied research packet" }, { "claim": "Inner Batch transactions are intentionally unsigned; authorization is delegated through outer Batch signer data.", "fact_id": "F2", "source": "Batch design summary and local rippled signer-validation code surface" }, { "claim": "Batch signer validation is implemented through STTx::checkBatchSign, checkBatchSingleSign, checkBatchMultiSign, and multiSignHelper.", "fact_id": "F3", "source": "local rippled source excerpts" }, { "claim": "Batch was in the validator-voting/amendment process, so a HOLD/NO route could still prevent activation without mainnet fund loss.", "fact_id": "F4", "source": "XRPL amendment process" } ], "historical_truth_withheld_from_model": { "later_bug_class": "critical signer-validation loop/early-success flaw in Batch authorization that could skip remaining signer checks", "later_disclosure_date": "2026-02-19", "later_outcome": "validators advised to vote No; rippled 3.1.1 disabled Batch and fixBatchInnerSigs" }, "packet_id": "xrpl-batch-predisclosure-2026", "purpose": "Pre-disclosure simulation packet: contains amendment/process/code-surface facts but excludes the February 19, 2026 vulnerability disclosure, exploit description, and emergency response." }