Een bericht ondertekenen bewijst dat een gebruiker een wallet beheert, zonder transactie, zonder kosten en zonder netwerkaanroep. Apps gebruiken ondertekende berichten om gebruikers in te laten loggen, vast te leggen dat een gebruiker de voorwaarden heeft geaccepteerd, een wallet aan een bestaand account te koppelen of een actie te autoriseren die off-chain plaatsvindt, zoals het plaatsen van een bestelling.
Solana-wallets ondertekenen berichten op twee manieren:
- Off-chain berichten (
solana:signOffchainMessage) verpakken je tekst in een standaardformaat, gedefinieerd door de specificatie voor off-chain berichtondertekening (OCMS). Dit formaat kan nooit voor een transactie worden aangezien, vermeldt de accounts die moeten ondertekenen en is het formaat dat hardware wallets ondertekenen. - Ruwe berichten (
solana:signMessage) ondertekenen exact de bytes die je app aanlevert, zonder prefix en zonder vermelde ondertekenaars. De meeste softwarewallets ondersteunen ze, maar niets in de bytes laat zien dat het om een bericht gaat en niet om een transactie.
Gebruik off-chain berichten waar de wallet ze ondersteunt, en val terug op ruwe berichten waar dat niet het geval is. De rest van deze gids gebruikt inloggen als uitgewerkt voorbeeld, maar dezelfde stappen voor ondertekening en verificatie gelden voor elk bericht.
Kies een berichtformaat
| Off-chain bericht | Ruw bericht | Transactie | |
|---|---|---|---|
| Wallet-functie | signOffchainMessage | signMessage | signTransaction |
| Ondersteuning door hardware wallets | Afhankelijk van het apparaat | Nee | Ja |
| Leesbaar voor de gebruiker | Ja | Alleen software | Afhankelijk van het apparaat |
| Vermeldt vereiste ondertekenaars | Ja | Nee | Ja |
| RPC-aanroepen en kosten | Geen | Geen | Blockhash, verzenden, kosten, bevestigen |
Waarom geen transactie?
Hardware wallets konden vroeger geen leesbaar bericht ondertekenen. Veel apps vroegen deze gebruikers daarom in plaats daarvan een no-op-transactie te ondertekenen. Dat werkt, maar het brengt extra latentie, kosten en risico's met zich mee:
- Netwerkkosten. Een transactie kost een fee, ook als er niets mee gebeurt.
- Meerdere RPC-aanroepen. Je app haalt een blockhash op, simuleert, verzendt en controleert daarna periodiek of de transactie is bevestigd.
- Extra latentie. Elk van die aanroepen is een round trip voordat de gebruiker is ingelogd.
- Niets leesbaars. De gebruiker keurt transactiebytes goed, geen verklaring van waarmee hij of zij akkoord gaat.
- Gedragsrisico. Gebruikers vragen een transactie te ondertekenen om alleen maar in te loggen, maakt het makkelijker om ze te misleiden een schadelijke transactie te ondertekenen.
Een off-chain bericht slaat dit alles over. De enige round trip is die naar je eigen server.
Wat de wallet ondertekent
Een off-chain bericht begint met een vast ondertekeningsdomein,
\xffsolana offchain. Geen enkele geldige transactie begint met die bytes,
dus een ondertekend bericht kan nooit als transactie worden gebruikt. Versie 1
van het formaat vermeldt vervolgens de vereiste ondertekenaars en bevat het
bericht als UTF-8-tekst, zonder lengtelimiet.
De wallet bouwt deze bytes op, ondertekent ze en geeft ze samen met de handtekening terug. De specificatie behandelt het volledige formaat, inclusief de legacy-versie 0.
Kies een inlogmethode
Nog niet elke wallet ondersteunt off-chain berichten. Controleer de functies van het verbonden account voordat je om een handtekening vraagt, en val terug wanneer de functie ontbreekt:
- Als het account
solana:signOffchainMessagemet berichtversie 1 ondersteunt, onderteken dan een off-chain bericht. - Onderteken anders dezelfde challenge met
solana:signMessage. De meeste softwarewallets ondersteunen dit. - Geef gebruikers van hardware wallets zonder berichtondersteuning een expliciete opt-in, zoals een schakelaar "Ik gebruik een hardware wallet", die in plaats daarvan een inlogtransactie ondertekent. Stuur ze niet standaard via dit pad.
Een bericht ondertekenen in de browser
Installeer de Wallet Standard-pakketten naast Kit:
$npm install @solana/kit @solana/wallet-standard-features @wallet-standard/ui @wallet-standard/ui-features @wallet-standard/ui-registry
Deze helpers nemen een UiWalletAccount, zoals de account die
useConnectedWallet teruggeeft in de React-gids:
import {SolanaSignOffchainMessage,type SolanaSignOffchainMessageFeature} from "@solana/wallet-standard-features";import type { UiWalletAccount } from "@wallet-standard/ui";import { getWalletAccountFeature } from "@wallet-standard/ui-features";import { getWalletAccountForUiWalletAccount } from "@wallet-standard/ui-registry";type OffchainMessageFeature =SolanaSignOffchainMessageFeature[typeof SolanaSignOffchainMessage];export function supportsOffchainMessages(account: UiWalletAccount) {if (!account.features.includes(SolanaSignOffchainMessage)) return false;const feature = getWalletAccountFeature(account,SolanaSignOffchainMessage) as OffchainMessageFeature;return feature.supportedMessageVersions.includes(1);}export async function signOffchainMessage(account: UiWalletAccount,message: string) {const feature = getWalletAccountFeature(account,SolanaSignOffchainMessage) as OffchainMessageFeature;const [output] = await feature.signOffchainMessage({messageVersion: 1,account: getWalletAccountForUiWalletAccount(account),message,requiredSigners: [account.publicKey]});return output;}
signOffchainMessage geeft signedOffchainMessage terug, de volledige bytes
die de wallet heeft ondertekend, en signature. Codeer beide in Base64 en
stuur ze samen met het walletadres naar je server.
Schrijf de challenge zo dat deze goed leesbaar is op een klein scherm. Noem je app en domein, geef aan wat het ondertekenen inhoudt en voeg een nonce voor eenmalig gebruik en een uitgiftetijd van je server toe:
swap.app wants you to sign in with your Solana account.Signing in costs nothing and does not submit a transaction.Nonce: 8f3a1c9eIssued at: 2026-09-29T17:04:00Z
Verifieer de handtekening op je server
Verificatie is een lokale handtekeningcontrole en vereist geen RPC-verbinding. Decodeer de bytes die de wallet heeft ondertekend, bevestig dat ze overeenkomen met de challenge die je hebt uitgegeven en controleer dan de handtekening:
import {address,assertOffchainMessageV1Equal,getBase64Encoder,getOffchainMessageDecoder,verifyOffchainMessageEnvelope,type OffchainMessageBytes,type SignatureBytes} from "@solana/kit";export async function verifySignIn(input: {walletAddress: string;expectedMessage: string;signedOffchainMessage: string;signature: string;}) {const signer = address(input.walletAddress);const base64 = getBase64Encoder();const content = base64.encode(input.signedOffchainMessage) as OffchainMessageBytes;const signature = base64.encode(input.signature) as SignatureBytes;const received = getOffchainMessageDecoder().decode(content);if (received.version !== 1) {throw new Error(`Expected a v1 message, got v${received.version}`);}assertOffchainMessageV1Equal(received, {version: 1,requiredSignatories: [{ address: signer }],content: input.expectedMessage});await verifyOffchainMessageEnvelope({content,signatures: { [signer]: signature }});}
assertOffchainMessageV1Equal geeft een foutmelding als de inhoud of de
ondertekenaars afwijken van wat je verwachtte. verifyOffchainMessageEnvelope
geeft een foutmelding als de handtekening ontbreekt of ongeldig is. Zoek
expectedMessage op via de nonce op je server in plaats van de kopie te
vertrouwen die de client terugstuurt. Verbruik de nonce zodra deze is
geverifieerd, zodat dezelfde handtekening niet twee keer kan worden gebruikt om
in te loggen.
Een ruw bericht verifiëren
Een signMessage-handtekening dekt direct de bytes van de challenge, dus de
client hoeft alleen de handtekening en het walletadres te sturen. Controleer
de handtekening aan de hand van de challenge die je hebt uitgegeven:
import {address,getBase64Encoder,getPublicKeyFromAddress,getUtf8Encoder,signatureBytes,verifySignature} from "@solana/kit";export async function verifyRawSignIn(input: {walletAddress: string;expectedMessage: string;signature: string;}) {const publicKey = await getPublicKeyFromAddress(address(input.walletAddress));const signature = signatureBytes(getBase64Encoder().encode(input.signature));const message = getUtf8Encoder().encode(input.expectedMessage);if (!(await verifySignature(publicKey, signature, message))) {throw new Error("Invalid signature");}}
Dezelfde nonceregels gelden: zoek expectedMessage op via de nonce en
verbruik deze zodra de verificatie slaagt.
@solana/kit exporteert deze helpers opnieuw vanuit
@solana/offchain-messages. Gebruik het zelfstandige pakket als je server
niet van Kit afhankelijk is.
Sign In With Solana
Sign In With Solana (SIWS)
standaardiseert de challenge zelf. Je app geeft gestructureerde velden, zoals
domain, statement, nonce en issuedAt, door aan de functie
solana:signIn van de wallet. De wallet zet ze om naar een standaardbericht,
maakt indien nodig verbinding en ondertekent het in één stap.
Versie 1.1.0 van solana:signIn voegt een optie useOffchainMessage toe.
Als je useOffchainMessage: { messageVersion: 1 } doorgeeft, verpakt de wallet
de SIWS-tekst in een off-chain bericht van versie 1 met het ondertekenende
account als enige vereiste ondertekenaar. Hardware wallets kunnen dit tonen en
ondertekenen, en de uitvoer meldt
signedMessageFormat: { kind: "offchainMessage", messageVersion: 1 }. Wallets
die versie 1.0.0 aangeven, kennen de optie niet, dus controleer eerst de
functieversie en laat de optie anders weg.
solana:signIn is een wallet-functie, geen account-functie, dus deze
helpers nemen een UiWallet:
import {SolanaSignIn,type SolanaSignInFeature,type SolanaSignInInput} from "@solana/wallet-standard-features";import type { UiWallet } from "@wallet-standard/ui";import { getWalletFeature } from "@wallet-standard/ui-features";type SignInFeature = SolanaSignInFeature[typeof SolanaSignIn];export function supportsOffchainSignIn(wallet: UiWallet) {if (!wallet.features.includes(SolanaSignIn)) return false;const feature = getWalletFeature(wallet, SolanaSignIn) as SignInFeature;return feature.version === "1.1.0";}export async function signIn(wallet: UiWallet, input: SolanaSignInInput) {const feature = getWalletFeature(wallet, SolanaSignIn) as SignInFeature;const [output] = await feature.signIn({...input,...(supportsOffchainSignIn(wallet) && {useOffchainMessage: { messageVersion: 1 }})});return output;}
Bouw de input op je server, met een nonce voor eenmalig gebruik en een
issuedAt-tijd, en bewaar een kopie met de nonce als sleutel. Verbruik de
nonce zodra het inloggen is geverifieerd.
Zonder Wallet Adapter controleert verifySignIn uit
@solana/wallet-standard-util alleen gewone SIWS-uitvoer. Wanneer
signedMessageFormat een off-chain bericht meldt, is signedMessage een
off-chain bericht waarvan de inhoud de SIWS-tekst is. Bouw de verwachte tekst
opnieuw op uit je opgeslagen input en het ondertekenende adres met
createSignInMessageText uit @solana/wallet-standard-util, en verifieer deze
vervolgens zoals elk ander off-chain bericht, zoals in
Verifieer de handtekening op je server. De
README van @solana/offchain-messages
behandelt deze flow in Kit.
Voor wallets
Wallets voegen ondersteuning toe door de Wallet Standard-functie
solana:signOffchainMessage uit @solana/wallet-standard-features te
implementeren:
- Meld
version: "1.0.0"en vermeld de berichtversies die je kunt ondertekenen insupportedMessageVersions. Op dit moment is dat[1]. - Bouw de versie 1-preamble op basis van
requiredSigners, die je sorteert en controleert op duplicaten. Weiger het verzoek als het account niet een van de vereiste ondertekenaars is. - Toon de berichttekst vóór het ondertekenen volledig aan de gebruiker, ook op hardwareapparaten.
- Retourneer de exacte bytes die je hebt ondertekend als
signedOffchainMessage, samen met de Ed25519-signature. - Om off-chain Sign In With Solana te ondersteunen, geef je
version: "1.1.0"op voorsolana:signIn. Als de invoeruseOffchainMessagebevat, onderteken je de SIWS-tekst als een off-chain bericht van versie 1, retourneer je de bytes ervan alssignedMessageen stel jesignedMessageFormatin. - Blijf
solana:signMessageondersteunen. Dit is ongewijzigd en apps gebruiken het nog steeds als fallback.
Is this page helpful?