Skip to content

XLS-66: specify the counterparty signing prefixes - #634

Open
hliosone wants to merge 1 commit into
XRPLF:masterfrom
hliosone:counterparty-signing-prefix
Open

hliosone wants to merge 1 commit into
XRPLF:masterfrom
hliosone:counterparty-signing-prefix

Conversation

@hliosone

Copy link
Copy Markdown

What this adds

XLS-66 section 3.8.1.1 describes the fields of CounterpartySignature, and section 3.8.3 describes the handoff between the two parties, but the specification never states what bytes a counterparty signs. A counterparty signature uses a different hash prefix from an ordinary transaction signature, and there are two of them, because 3.8.1.1 permits either a single-key signature in TxnSignature or a Signers list.

This adds a short table giving both, alongside the ordinary prefixes for contrast.

Verification

From HashPrefix.h on XRPLF/rippled@develop:

TxSign                  = makeHashPrefix('S', 'T', 'X'),
TxMultiSign             = makeHashPrefix('S', 'M', 'T'),
CounterpartyTxSign      = makeHashPrefix('C', 'P', 'T'),
CounterpartyTxMultiSign = makeHashPrefix('C', 'P', 'M'),

where makeHashPrefix(a, b, c) = a << 24 | b << 16 | c << 8, giving 0x53545800, 0x534D5400, 0x43505400 and 0x43504D00.

Which of the two counterparty prefixes applies is decided in Sign.cpp:

case SignatureRole::Counterparty:
    return multiSigning ? HashPrefix::CounterpartyTxMultiSign
                        : HashPrefix::CounterpartyTxSign;

Why it is worth documenting

We hit this building a two-party LoanSet at the XRPL Lending Protocol Hackathon in Paris on 12 and 13 September 2026. It cost roughly 114 minutes across 27 submissions on public Devnet. The rejection we saw was fails local checks: Counterparty: Invalid signature, which does not point at a prefix, and we eventually found the answer by reading the source of a wallet extension rather than the specification or the SDK.

Two nearby signals sent us the wrong way. Omitting CounterpartySignature returns temBAD_SIGNER with text about duplicate signers, which reads as a multisign problem. And combineLoanSetCounterpartySigners sits next to signLoanSetByCounterparty, mirroring the signMultiBatch and combineBatchSigners pair, so sign then combine then submit looks like the intended flow, when it is multisign only.

xrpl.js 5.2.0-beta.0 omitted the prefix in signLoanSetByCounterparty, and 5.2.0-beta.1 fixed it with a role argument to computeSignature. That library is correct now, but anyone implementing the signature outside that helper, in another language, in a wallet, or in a hardware signing path, still has nothing in the specification to work from.

Relationship to #570

#570 introduces a two-step flow and makes CounterpartySignature conditional, but it keeps the single-transaction flow and documents it in a new section 3.8.3.1. The prefixes are therefore still needed after that PR lands, and #570 does not document them. Happy to rebase on top of it if you would prefer to take that first.

A counterparty signs over a different hash prefix than an ordinary transaction
signature, and the specification does not say so. Document both prefixes, since
section 3.8.1.1 allows a single-key signature and a Signers list, and each uses
a different one.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Clarify that CPM applies to each nested signer’s TxnSignature and update the document metadata.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This pull request documents the hash prefixes used for single- and multi-signature counterparty signatures in XLS-66.

Changes:

  • Adds CPT and CPM prefix values.
  • Contrasts them with ordinary transaction prefixes.
  • References the corresponding rippled implementation.
File summaries
File Description
XLS-0066-lending-protocol/README.md Specifies counterparty signing prefixes.
Review details

Suppressed comments (1)

XLS-0066-lending-protocol/README.md:1017

  • CounterpartySignature.Signers is the array, not a signature-bearing field. The CPM prefix applies to each nested signer’s TxnSignature; labeling the array itself leaves implementers unsure whether to hash the array once or which bytes to sign, and can produce invalid signatures. Identify the per-entry field and state that the prefix applies to every signer entry.
| `CounterpartySignature.Signers`      | `CPM`  | `0x43504D00` |
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

1. The `SigningPubKey` and `TxnSignature` fields, or
2. The `Signers` field and, optionally, an empty `SigningPubKey`.

The counterparty signature is computed over the same signing fields as the primary signature, but with a different hash prefix:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @hliosone this is a nice PR! Could you address the Copilot comment please.

@mvadari
mvadari requested a review from Tapanito September 14, 2026 03:25

These differ from the prefixes used for an ordinary transaction signature, `STX` (`0x53545800`) and `SMT` (`0x534D5400`). A counterparty signature computed with an ordinary prefix does not verify.

The values are defined in `HashPrefix.h` as `CounterpartyTxSign` and `CounterpartyTxMultiSign`, and are selected in `Sign.cpp` by `SignatureRole::Counterparty`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please remove this sentence as well. It can cause the specs to go stale whenever we'll update the code.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants