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

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 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.

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

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:

  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", waarmee in plaats daarvan een inlogtransactie wordt ondertekend. Stuur ze niet standaard deze kant op.
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 verwachten een UiWalletAccount, zoals het account dat 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 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: 8f3a1c9e
Issued 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:

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

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

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 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 in supportedMessageVersions. 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 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. Het is ongewijzigd en apps gebruiken het nog steeds als fallback.

Is this page helpful?

Inhoudsopgave

Pagina Bewerken
© 2026 Solana Foundation. Alle rechten voorbehouden.