proposal: hardware-wallet signing for x402 exact (sign_transaction_message)#210
Draft
ledgicr wants to merge 1 commit into
Draft
proposal: hardware-wallet signing for x402 exact (sign_transaction_message)#210ledgicr wants to merge 1 commit into
ledgicr wants to merge 1 commit into
Conversation
…rdware-wallet compatible) x402 'exact' on Solana signs a partially-signed VersionedTransaction (v0) — a standard ed25519 signature over the serialized message, per the x402 spec (specs/schemes/exact/scheme_exact_svm.md). The client did this with signer.sign_message(&tx.message.serialize()), which assumes a software key that raw-ed25519-signs arbitrary bytes. A hardware wallet (Ledger) cannot: it signs a transaction through the device's transaction-parsing APDU, never raw bytes. So x402 'exact' payments cannot be made with a hardware wallet today. Switch the one signing site to the new SolanaSigner::sign_transaction_message, which is byte-identical for software signers (default impl delegates to sign_message) and routes through the device APDU for hardware signers. No x402 protocol / facilitator / wire-format change: the produced partially-signed VersionedTransaction is exactly what any x402 'exact' facilitator expects. Depends on the companion keychain trait method (ledgicr/solana-keychain@ ledger-x402-sign-transaction-message); dep temporarily pointed there. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
x402
exacton Solana signs a partially-signedVersionedTransaction(v0) — a standard ed25519 signature over the serialized message, per the spec (specs/schemes/exact/scheme_exact_svm.md: "the transaction field contains the base64-encoded, serialized, partially-signed versioned Solana transaction"). The facilitator co-signs as fee-payer and submits.The client produces the payer signature with:
sign_messageassumes a software key that can raw-ed25519-sign arbitrary bytes. A hardware wallet cannot do that — it signs a transaction through the device's transaction-parsing APDU, or an off-chain message through the\xffsolana offchainenvelope, but never arbitrary raw bytes. So x402exactpayments can't be made with a hardware wallet (Ledger) today — andexactis most of the catalog.(The legacy MPP charge path has the same root cause and was fixed separately via
sign_transaction; but that method is typed to legacyTransactionand can't accept aVersionedTransaction, which is why x402 needs its own path.)What this changes
One signing site in
x402/client/exact/payment.rs:Everything else (message construction, the splice at the signer index, the base64 payload) is unchanged.
The proposed trait method (the actual decision)
Add to
SolanaSigner(insolana-keychain) a defaulted method:sign_message, so all existing backends (memory + the KMS/custody signers) produce the identical signature with no code change. Unit test asserts the equivalence for the software path.sign_transactionalready uses).Reference implementation, compiled + tested (incl. the Ledger override, hardware-validated on a Nano device):
ledgicr/solana-keychain@ledger-x402-sign-transaction-message. This PR'ssolana-keychaindep is temporarily pointed there; restore to a published release once the method ships.Why not the alternatives
sign_versioned_transaction(&mut VersionedTransaction)(mirroring the existing legacysign_transaction). Cleaner ergonomics, but pulls theVersionedTransactiontype into the trait across the SDK-version matrix, and the x402 client already owns the splice/encode, so the byte-level method is a smaller surface and reuses the client's existing structure. Happy to go typed if you prefer symmetry.sign_message, special-case hardware in the client. Pushes device-awareness into every consumer and leaks backend detail across the API boundary. The whole point of the trait is that consumers don't branch on signer type.TS parity
The TS
SolanaSignerinterface already distinguishessignTransactions()(handles versioned txs via@solana/kit) fromsignMessages(). This proposal brings the Rust trait to the same split — the Rust side is the asymmetric one today (sign_transactionis legacy-only, so consumers fall back tosign_messagefor versioned). So this is closing a Rust-side gap, not inventing a new concept.Scope
In scope (this PR): x402
exactpayment signing — the common catalog path.Out of scope (separate proposals):
pay send, subscriptions, x402 sign-in) and batch-settlement vouchers — these sign a canonical string / raw payload the verifier checks as raw ed25519, which a hardware wallet can only produce as an off-chain message (different bytes). That needs asignature_schemeaddition on both client and server — a wire-protocol design worth its own thread (it also intersects the Agent-Intents roadmap).Status
solana-keychainrelease carriessign_transaction_message(the dep points at the branch meanwhile).Would love written feedback on: (a) the method name/shape, (b) byte-level vs typed, (c) whether you'd rather own the keychain change or take ours.