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). Het 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 precies de bytes die je app doorgeeft, zonder prefix en zonder vastgelegde ondertekenaars. De meeste softwarewallets ondersteunen dit, maar niets in de bytes geeft aan dat het om een bericht gaat in plaats van een transactie.
Gebruik off-chain berichten waar de wallet ze ondersteunt en val terug op ruwe berichten waar dat niet het geval is. In de rest van deze gids dient 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 voor hardware wallets | Afhankelijk van het apparaat | Nee | Ja |
| Leesbaar voor de gebruiker | Ja | Alleen software | Afhankelijk van het apparaat |
| Geeft vereiste ondertekenaars op | 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 een no-op-transactie te ondertekenen. Dat werkt, maar het voegt latentie, kosten en risico toe:
- Netwerkkosten. Een transactie kost geld, zelfs als er niets mee gebeurt.
- Meerdere RPC-aanroepen. Je app haalt een blockhash op, simuleert, verzendt en pollt daarna voor bevestiging.
- Extra latentie. Elk van die aanroepen is een round trip voordat de gebruiker is ingelogd.
- Niets leesbaars. De gebruiker keurt transactiebytes goed, niet een verklaring van waarmee hij akkoord gaat.
- Gedragsrisico. Gebruikers vragen een transactie te ondertekenen om alleen maar in te loggen maakt het makkelijker om hen te misleiden tot het ondertekenen van een schadelijke transactie.
Een off-chain bericht slaat dat alles over. De enige round trip gaat 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 deze bytes, dus een ondertekend bericht kan nooit
als transactie worden gebruikt. Versie 1 van het formaat bevat vervolgens de vereiste ondertekenaars
en het bericht als UTF-8-tekst, zonder lengtebeperking.
De wallet bouwt deze bytes, ondertekent ze en geeft ze samen met de handtekening terug. De specificatie behandelt het volledige formaat, inclusief de legacy-versie 0.
Kies een inlogmethode
Niet elke wallet ondersteunt al off-chain berichten. Controleer de functies van het verbonden account voordat je om een handtekening vraagt en val terug als 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", waarmee in plaats daarvan een inlogtransactie wordt ondertekend. Stuur ze niet standaard deze kant op.
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 verwachten een UiWalletAccount, zoals het account dat
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 hij goed leesbaar is op een klein scherm. Noem je app en domein, vermeld wat het ondertekenen doet en neem een nonce voor eenmalig gebruik en een uitgiftetijd van je server op:
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
De handtekening verifiëren 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 fout als de inhoud of de ondertekenaars afwijken van
wat je verwachtte. verifyOffchainMessageEnvelope geeft een fout 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 de verificatie
slaagt, zodat dezelfde handtekening niet twee keer kan inloggen.
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 die 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 nonce-regels gelden: zoek expectedMessage op via de nonce en verbruik die
zodra de verificatie slaagt.
@solana/kit re-exporteert deze helpers uit @solana/offchain-messages. Gebruik
het losse 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 in 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 } meegeeft, 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 het weergeven en ondertekenen, en de uitvoer meldt
signedMessageFormat: { kind: "offchainMessage", messageVersion: 1 }. Wallets
die versie 1.0.0 aanbieden, 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 verwachten 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 invoer 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 invoer en het ondertekenende adres met
createSignInMessageText uit @solana/wallet-standard-util en verifieer het daarna
zoals elk ander off-chain bericht, zoals in
De handtekening verifiëren 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 geef de berichtversies die je kunt ondertekenen op insupportedMessageVersions. Op dit moment is dat[1]. - Bouw de versie 1-preambule 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 volledig aan de gebruiker voordat je ondertekent, 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. Het is ongewijzigd en apps gebruiken het nog steeds als fallback.
Is this page helpful?