Berichten ondertekenen & gebruikers authenticeren

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 berichtRuw berichtTransactie
Wallet-functiesignOffchainMessagesignMessagesignTransaction
Ondersteuning door hardware walletsAfhankelijk van het apparaatNeeJa
Leesbaar voor de gebruikerJaAlleen softwareAfhankelijk van het apparaat
Vermeldt vereiste ondertekenaarsJaNeeJa
RPC-aanroepen en kostenGeenGeenBlockhash, 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.

A sign-in transaction fetches a blockhash, signs, simulates, sends, and polls for confirmation before the server can verify it. An off-chain message goes from a server challenge to a wallet signature to a server-side check, with no RPC calls and no fee.

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.

A version 1 off-chain message is a 16-byte signing domain, a version byte, a signer count, each signer's 32-byte public key in sorted order, and the UTF-8 content. The envelope prefixes it with a signature count and one 64-byte signature per signer.

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:

  1. Als het account solana:signOffchainMessage met berichtversie 1 ondersteunt, onderteken dan een off-chain bericht.
  2. Onderteken anders dezelfde challenge met solana:signMessage. De meeste softwarewallets ondersteunen dit.
  3. 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.
When a wallet connects, check whether its account supports solana:signOffchainMessage version 1. If it does, sign an off-chain message. If not, sign a raw message with solana:signMessage, and let hardware wallet users opt into signing a sign-in transaction.

Een bericht ondertekenen in de browser

Installeer de Wallet Standard-pakketten naast Kit:

Terminal
$
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:

offchain-message.ts
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: 8f3a1c9e
Issued 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:

verify-sign-in.ts
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:

verify-raw-sign-in.ts
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:

sign-in.ts
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 in supportedMessageVersions. 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 voor solana:signIn. Als de invoer useOffchainMessage bevat, onderteken je de SIWS-tekst als een off-chain bericht van versie 1, retourneer je de bytes ervan als signedMessage en stel je signedMessageFormat in.
  • Blijf solana:signMessage ondersteunen. Dit is ongewijzigd en apps gebruiken het nog steeds als fallback.

Is this page helpful?

Inhoudsopgave

Pagina Bewerken
© 2026 Solana Foundation. Alle rechten voorbehouden.