Skip to content

[WIP] 1.0.0 #1097

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open

[WIP] 1.0.0 #1097

wants to merge 11 commits into from

Conversation

jordaaash
Copy link
Collaborator

@jordaaash jordaaash commented May 30, 2025

We are preparing the release of Wallet Adapter v1.0.0

This will be the first major breaking change of Wallet Adapter in a long time, and may require small code changes in applications.

There are some things TBD (feel free to suggest others)

Release checklist (will add more steps/detail as we go)

  • Identify and merge any other non-breaking PRs to master
  • Merge the (hopefully last) changeset Release PR for 0.x.x
  • Identify and merge any other breaking PRs into this PR
  • Update the demo and docs
  • Add a changeset with major bump to all packages
  • Merge this PR to master
  • Merge the changeset Release PR for 1.0.0
  • Update @solana/wallet-standard-wallet-adapter-base to be 1.0.0-compatible (will require a breaking change of that package)
  • Update @solana-mobile/wallet-adapter-mobile to be 1.0.0-compatible (will require a breaking change of that package)

@ByteZhang1024
Copy link

I have a question—after making this change, it means that signMessage now has two different implementations (Phantom's implementation and Ledger's implementation). When a dApp verifies the result of signMessage, does it need to support both for compatibility?

@cemozerr
Copy link

I have a question—after making this change, it means that signMessage now has two different implementations (Phantom's implementation and Ledger's implementation). When a dApp verifies the result of signMessage, does it need to support both for compatibility?

Ideally every wallet should move to the Offchain Message Signing Spec @ByteZhang1024.

@jordaaash
Copy link
Collaborator Author

jordaaash commented Jun 3, 2025

Important Resolved discussion happening on #1094 (comment) about changing the return of Wallet Adapter's signMessage API.

@jordaaash jordaaash force-pushed the ledger-adapter-example branch from d5e55c5 to dcfa013 Compare June 10, 2025 21:51
@jordaaash jordaaash changed the title [WIP] Add fully specified signMessage support to Ledger adapter [WIP] 1.0.0 Jun 10, 2025
@ByteZhang1024
Copy link

ByteZhang1024 commented Jun 11, 2025

I originally envisioned two possible approaches:

All wallet implementations of signMessage should move to the Offchain Message Signing Spec going forward.

Introduce a new method to help wallets and dApps transition gradually, with the eventual deprecation of signMessage.

That's why I submitted another Issue, but it looks like we've decided to go with option one. So that Issue can probably be closed: anza-xyz/wallet-standard#81

@jordaaash
Copy link
Collaborator Author

Important to note that the Ledger implementation in this PR is using 0{32} for the application domain in the spec.

Application domain

A 32-byte array identifying the application requesting off-chain message signing. This may be any arbitrary bytes. For instance the on-chain address of a program, DAO instance, Candy Machine, etc.

This field SHOULD be displayed to users as a base58-encoded ASCII string rather than interpreted otherwise.

This is a poorly designed part of the spec, IMO, and I disagree with all of it. It's an off-chain message. Referencing an on-chain thing is unnecessary and antithetical. The spec also recommends that wallets display this value to users. Displaying a base58 pubkey of a Solana program that the user certainly will not recognize is useless and bad.

My recommendation to implementing wallets and apps:

  • DO use 0{32} for this value
  • DO NOT display this value in base58 or otherwise, especially not 1111...1111, to users
  • DO use Sign In With Solana for the message body, which includes a human-readable domain and the user's pubkey, which will be recognizable to them, since both of these fields serve an actual purpose

@jordaaash jordaaash closed this Jun 12, 2025
@jordaaash jordaaash reopened this Jun 12, 2025
@vsakos
Copy link
Contributor

vsakos commented Jun 13, 2025

I might be late to the discussion but IMO wallets will not and should not convert the dApp's message to an OCMSF message (at least for foreseeable future), because as soon as they do, any dApp that does not verify the signedMessage but only the original message and the received signature, will break instantly.

Until now, the only way to implement this was by implementing the Wallet Standard directly, and I don't think a lot of dApps do that. They might start doing it (and they should) with the release of the adapter v1.0.0 but it will take time until everybody upgrades since this is a breaking change and not just an npm install.

From the adapter's perspective, this is a breaking change that won't affect dApps until they upgrade the library, but as soon as wallets change how the window.<wallet>.signMessage() works, dApps will break. The same applies the Wallet Standard since the standard wallets replace the equivalent wallet adapter that's manually added by the dApp.

@cemozerr
Copy link

@vsakos are you saying that apps should special case when the user tries to sign with a Ledger-imported account? (Otherwise, Ledger will reject the signature request due to incorrect formatting—it only accepts OCMSF messages now.)

If that's not the case, the ecosystem won't have Ledger support for off-chain signatures for quite some time.

Another question I'm wondering about—what major apps are currently using the message signing flow? My understanding is that not many teams have adopted it yet, since doing so currently excludes Ledger users, who represent a significant group.

@jordaaash
Copy link
Collaborator Author

@vsakos Not too late, and it's good to highlight this problem.

Apps AND wallets, whether using Wallet Adapter or the Wallet Standard, are going to have to make changes to handle Ledger-signed messages, no matter what. To illustrate this, here are the set of possible outcomes I see:

  1. Status quo: a given app and wallet both make no changes to support OCMSF. Setting aside the transaction signing tricks they are doing today, the app asks the wallet to sign a non-OCMSF message.
    a. It's a non-Ledger account. Signing in the wallet and verification in the app work.
    b. It's a Ledger account. Signing fails, with an opaque error in the wallet, app, or both, regardless of the version of the Solana Ledger installed.
  2. A given app makes changes to supports OCMSF for all messages, and a given wallet makes no changes. The app asks to sign an OCMSF message. It uses the restricted ASCII format to get non-blind-signing display on Ledger, and it doesn't know if it's a Ledger account or not.
    a. If it's a non-Ledger account, the wallet decodes the message as utf-8, likely displays a lot of garbage from the OCMSF envelope. Signing and verification maybe work, but it depends on the implementation of the wallet's parsing of the message.
    b. If it's a Ledger account, signing fails, potentially with an opaque error in the wallet, app or both, because the wallet made no changes to support OCMSF.
  3. A given wallet makes changes to support OCMSF for all messages, and a given app makes no changes. The app asks to sign a non-OCMSF message. The wallet converts the message to OCMSF, signs it, and returns signature, and possibly the signedMessage. Regardless, the app is unaware of the conversion or signedMessage, so verification of signature fails.
  4. A given wallet makes changes to support OCMSF for Ledger only, and a given app makes no changes. The app asks to sign a non-OCMSF message.
    a. It's a non-Ledger account, so the wallet does not convert the message to OCMSF. Signing and verification work.
    b. It's a Ledger account. The wallet converts the message to OCMSF, signs it, and returns signature, and possibly the signedMessage. Signing succeeds, but verification fails.
  5. A given app and wallet both make changes to support OCMSF. The app asks to sign either an OCMSF message or non-OCMSF message.
    a. If it's a non-Ledger account, the wallet either converts the message to OCMSF or not, and signs it. The app either receives the signedMessage or not. It can still verify against the message it provided, and if that fails, it can convert its own message to OCMSF and verify against that.
    b. If it's a Ledger account, the wallet must either receive an OCMSF from the app or convert the message the OCMSF to sign it. The app either receives the signedMessage or not. It can still verify against the message it provided, and if that fails, it can convert its own message to OCMSF and verify against that.

My takeaways from this:

In the near term, wallets SHOULD NOT convert messages to OCMSF unless they are signing with a Ledger account. This does leak information about the nature of the account if it's a Ledger account, but it means that most calls to window.<wallet>.signMessage() will continue to work as-is (because they won't be signing an OCMSF message). Wallets SHOULD convert to OCMSF if they are signing with a Ledger account, because signing will fail otherwise.

This implies that apps also SHOULD NOT convert messages to OCMSF, because they don't know if they are signing with a Ledger account or not, and they don't know if a given wallet supports OCMSF. Apps SHOULD expect that wallets may have converted the message to OCMSF, so if they don't get a signedMessage back, and apps should verify both ways. If they do get a signedMessage back, they should use that to verify, but they will still also need to parse the format of the signedMessage to make sure it's valid and that it contains the message they asked to sign. In practice this is almost the same (but not quite) as just converting their own message to OCMSF (the main difference is that the application domain may not be 0{32}, though it should be).

@jordaaash
Copy link
Collaborator Author

jordaaash commented Jun 13, 2025

I was asked by someone what the recommendations for wallets are:

  1. Use the Wallet Standard, since the solana:signMessage API there is what we are mirroring here, and you probably/hopefully already implement it. Return the signedMessage with this API and we'll plumb it through Wallet Adapter.
  2. Support Ledger message signing using OCMSF especially with the restricted ASCII charset. Detect which version of the Ledger app the user has, and encourage them to update to the latest so it’ll work. Accept both OCMSF and non-OCMSF messages from apps and convert to OCMSF either always (preserves privacy of Ledger accounts) or only when necessary for Ledger (sacrifices privacy of Ledger accounts to preserve compatibility). IMO they should probably only convert when necessary for Ledger, since privacy of Ledger accounts for message signing is already being lost today by asking the user if they are signing with Ledger.

This does leave an open question: what should wallets do with their window.<wallet>.signMessage() API? The choice is similar to what was discussed in #1094 (comment) but I think more nuanced -- the extension can't really version itself, so here option 3 is interesting:

  1. "Non-breaking" change with insane JavaScript kludge. We return Uint8Array & { signedMessage?: Uint8Array }. App signature verification will fail since they aren't verifying OCMSF messages correctly.

While I think this is unnecessarily hacky for Wallet Adapter to do, because we have to make other changes to format and verify messages anyway and we can publish multiple versions, it's actually a decent option for wallets with an existing signMessage method, because it doesn't break apps directly, in combination with recommendation 2 above.

Apps will still need to make changes and should use Wallet Adapter 1.x to do so.

@vsakos
Copy link
Contributor

vsakos commented Jun 13, 2025

For me it still makes sense to keep the wallet side as-is (sign the whole payload and return the signature), basically this:

A given app makes changes to supports OCMSF for all messages, and a given wallet makes no changes. The app asks to sign an OCMSF message. It uses the restricted ASCII format to get non-blind-signing display on Ledger, and it doesn't know if it's a Ledger account or not.
a. If it's a non-Ledger account, the wallet decodes the message as utf-8, likely displays a lot of garbage from the OCMSF envelope. Signing and verification maybe work, but it depends on the implementation of the wallet's parsing of the message.
b. If it's a Ledger account, signing fails, potentially with an opaque error in the wallet, app or both, because the wallet made no changes to support OCMSF.

I do not agree that it will fail since the signing is (and should always be) handled in Uint8Array and UTF-8 is strictly for the UI. If the app sends a correct OCMSF payload (just the message, not the whole count + sig[] + msg envelope), the wallet will just sign it as a whole. Some changes are definitely needed because as you said it will show garbage if we don't handle the data correctly on the UI but the core signing functionality should still work even without that.

Apps would have to change their logic anyway if they want to support Ledger users (either by formatting to OCMSF before signing or sending the base message but then verifying if the returned OCMSF is allright), but if for some reason they do not implement any of this, the base signing would still work without Ledger support.

Now, my recommendation would be to keep signMessage() as a "sign anything without touching the data" but with the ability to show both UTF-8 and OCMSF correctly on the UI, and add a new method that accepts only the UTF-8 (maybe even as a string) and returns { signature, signedMessage } but I know this idea was discussed before and was rejected.

@jordaaash
Copy link
Collaborator Author

There is no guarantee or mechanism to enforce that a wallet will not modify the message before signing, just as with signTransaction. The proposed API change makes this explicit by achieving compatibility with what the Wallet Standard already specifies and wallets who implement it support, but it is a breaking change. Apps are going to have to make changes eventually anticipating changes in wallet behavior, regardless of whether they use this release or 0.x releases.

We may want to take this approach now with some of the other methods as well for 1.0.0 if it makes sense to: a longer-term goal is to get everyone using the Standard and phase Wallet Adapter out (e.g. @solana/kit, fka web3js 2.x, supports the Wallet Standard and will not support Wallet Adapter which is coupled to web3.js 1.x). For example, it's been requested to add signAndSendAllTransactions, and when we do so, we will use the Wallet Standard API for this. We may want to make this part of 1.x even though we don't absolutely need to (it's non-breaking) in order to encourage updating.

For VersionedTransaction support, we added a supportedTransactionVersions field to the adapter. It could make sense for wallets on the window (or elsewhere) to do something like this to indicate OCMSF support, and for us to plumb it through the adapter, though I don't think that's sufficient reason to have two different methods for message signing since this is breaking. Individual wallets could implement two versions on their window objects, but we generally wouldn't call them from Wallet Adapter because the Wallet Standard API already covers this.

@jordaaash
Copy link
Collaborator Author

A related data point -- the Mobile Wallet Adapter protocol returns a payload of signedMessage || signature, which is plumbed through their Wallet Standard implementation in expectation that the wallet may change it.

@ByteZhang1024
Copy link

ByteZhang1024 commented Jun 14, 2025

We at the OneKey team offer both hardware and software wallets, so I’d like to share our perspective as well.

About signMessage

Initially, we only implemented signMessage in our software wallet. It wasn’t implemented in the hardware wallet because allowing arbitrary message signing is inherently dangerous. We believe Ledger shares the same concern, which is likely why they didn’t support it either.

However, websites like gmgn became extremely popular, and we received a large volume of user feedback complaining that our hardware wallet couldn’t connect to gmgn and similar dApps. As a result, we reluctantly allowed signMessage for Solana paths m/44'/501'/x'/0', with strict checks to ensure the message is not a transaction and added clear warnings that this is unsafe.

About OCMSF

From a wallet developer’s perspective, our priority is stability and reliability.
Therefore, we would like to see signMessage gradually deprecated — or at least augmented with a flag indicating that the dApp intends to use OCMSF.

For example:

// Gradually deprecate signMessage
window.signMessage(Buffer.from("010203", 'utf8'))

// Add a second parameter to explicitly indicate OCMSF usage
window.signMessage(Buffer.from("010203", 'utf8'), "OCMSF")

// (Alternatively) Introduce a new API — though we’ve already discussed and abandoned this idea
window.signOffChainMessage(Buffer.from("010203", 'utf8'))  // wallet handles OCMSF and returns a signed result

The benefit of this approach is that both the dApp and the wallet clearly know what the other expects.

The current approach in our PR.

If a wallet starts returning two different signature formats, it still heavily depends on dApp-side changes. We’re concerned that the intent of this change isn’t obvious enough — dApps may not realize that we’ve started supporting a new signing format. If a dApp doesn’t actively support OCMSF, then returning an OCMSF signature alone won’t help.

With the current direction, wallet developers have little incentive to be early adopters of OCMSF — because once we ship support, it could immediately break compatibility with many existing dApps.

dApps would need to make changes like this:

// Before
const request = "010203"
const result = await signMessage(Buffer.from(request, 'utf8'))

const isValidSignature = verify(
  Buffer.from(request, 'utf8'),
  result,
  publicKey.toBytes(),
);

// After
const { signature, signedMessage } = await signMessage(Buffer.from(request, 'utf8'))

const isValidSignature = verify(
  signedMessage || request,
  signature,
  publicKey.toBytes(),
);

Lastly, we’d be happy to help with external promotion and outreach related to OCMSF adoption.
Both our hardware products and Ledger’s share the same goal: to encourage the Solana ecosystem to migrate to OCMSF.

@jordaaash
Copy link
Collaborator Author

From a wallet developer’s perspective, our priority is stability and reliability. Therefore, we would like to see signMessage gradually deprecated — or at least augmented with a flag indicating that the dApp intends to use OCMSF.

For example:

// Gradually deprecate signMessage
window.signMessage(Buffer.from("010203", 'utf8'))

// Add a second parameter to explicitly indicate OCMSF usage
window.signMessage(Buffer.from("010203", 'utf8'), "OCMSF")

// (Alternatively) Introduce a new API — though we’ve already discussed and abandoned this idea
window.signOffChainMessage(Buffer.from("010203", 'utf8'))  // wallet handles OCMSF and returns a signed result

@ByteZhang1024 I want to address this first before coming back to the rest of the ideas here.

I'm not sure I understand why it would help to have an optional "OCMSF" flag. If the message is OCMSF formatted, a supporting wallet will parse "\xsolana offchain" at the start of the message, indicating it's OCMSF. A non-supporting wallet won't know about the `"OCMSF" flag and will just parse the message as UTF-8.

Wallets can add a separate signOffChainMessage method if they wish to, nothing in the proposed approach would prevent that. Their Wallet Standard wrappers can parse the message and call it if needed. But I don't think Wallet Adapter needs to have two methods, because it's supposed to use the Wallet Standard wherever possible. And if not possible, it needs to know about the underlying wallet implementation, and can call that wallet's signOffChainMessage method if it exists since the adapter will know about this.

As a result, I'm still not sure why this would be necessary, because the reality is that apps who want to do Ledger signing are going to have to be liberal in what they sign AND liberal in what they verify, because they simply cannot know for sure what a given wallet implementation will do, or if it even supports Ledger devices at all. The most flexible thing for them to do is to NOT convert messages to OCMSF, see what the wallet does with them, and verify accordingly.

As an example for why I think this is valuable --

Right now, we just have Ledger devices supporting OCMSF, displaying via restricted ASCII, and (maybe) blind signing for UTF-8. But nothing precludes Ledger or a different wallet from displaying longer messages in UTF-8 in the future for various reasons (e.g., perhaps for an EIP-712 type of format). The app has no way of knowing what type of device the account is on before encoding the message, but the wallet can and usually would know this. This leaves the wallet in generally the best position to do the encoding, unless the app knows specifically what the wallet supports AND wants to be restrictive about the encoding it accepts. I think restrictiveness is an anti-goal for apps right now -- they just want signing of very basic messages, or SIWS messages, to work minimally in the broadest set of places.

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.

4 participants