Signer des messages et authentifier les utilisateurs

Signer un message prouve qu'un utilisateur contrôle un portefeuille, sans transaction, sans frais et sans appel réseau. Les applications utilisent les messages signés pour connecter des utilisateurs, enregistrer qu'un utilisateur a accepté des conditions, associer un portefeuille à un compte existant ou autoriser une action exécutée hors chaîne, comme passer une commande.

Les portefeuilles Solana signent les messages de deux manières :

  • Les messages off-chain (solana:signOffchainMessage) encapsulent votre texte dans un format standard, défini par la spécification de signature de messages off-chain (OCMS). Ce format ne peut jamais être confondu avec une transaction, indique les comptes qui doivent le signer, et c'est le format que signent les portefeuilles matériels.
  • Les messages bruts (solana:signMessage) signent exactement les octets que votre application transmet, sans préfixe et sans signataires déclarés. La plupart des portefeuilles logiciels les prennent en charge, mais rien dans ces octets ne les identifie comme un message plutôt que comme une transaction.

Utilisez les messages off-chain lorsque le portefeuille les prend en charge, et repliez-vous sur les messages bruts dans le cas contraire. Le reste de ce guide prend la connexion comme exemple concret, mais les mêmes étapes de signature et de vérification s'appliquent à n'importe quel message.

Choisir un format de message

Message off-chainMessage brutTransaction
Fonctionnalité du portefeuillesignOffchainMessagesignMessagesignTransaction
Prise en charge des portefeuilles matérielsSelon l'appareilNonOui
Lisible par l'utilisateurOuiPortefeuilles logiciels uniquementSelon l'appareil
Déclare les signataires requisOuiNonOui
Appels RPC et fraisAucunAucunBlockhash, envoi, frais, confirmation

Pourquoi pas une transaction ?

Les portefeuilles matériels ne pouvaient auparavant pas signer de message lisible. De nombreuses applications se rabattaient sur une transaction sans effet que ces utilisateurs devaient signer. Cela fonctionne, mais cela ajoute de la latence, des coûts et des risques :

  • Frais de réseau. Une transaction entraîne des frais, même lorsqu'elle ne fait rien.
  • Plusieurs appels RPC. Votre application récupère un blockhash, simule, envoie, puis interroge le réseau pour obtenir la confirmation.
  • Latence supplémentaire. Chacun de ces appels est un aller-retour avant que l'utilisateur ne soit connecté.
  • Rien de lisible. L'utilisateur approuve des octets de transaction, et non une déclaration de ce à quoi il consent.
  • Risque comportemental. Demander aux utilisateurs de signer une transaction uniquement pour se connecter les rend plus faciles à piéger pour qu'ils en signent une malveillante.

Un message off-chain évite tout cela. Le seul aller-retour se fait avec votre propre serveur.

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.

Ce que le portefeuille signe

Un message off-chain commence par un domaine de signature fixe, \xffsolana offchain. Aucune transaction valide ne commence par ces octets : un message signé ne peut donc jamais être utilisé comme transaction. La version 1 du format énumère ensuite les signataires requis et transporte le message sous forme de texte UTF-8, sans limite de longueur.

Le portefeuille construit ces octets, les signe et les renvoie avec la signature. La spécification détaille le format complet, y compris l'ancienne version 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.

Choisir une méthode de connexion

Tous les portefeuilles ne prennent pas encore en charge les messages off-chain. Vérifiez les fonctionnalités du compte connecté avant de demander une signature, et utilisez la solution de repli lorsque la fonctionnalité est absente :

  1. Si le compte prend en charge solana:signOffchainMessage avec la version 1 des messages, signez un message off-chain.
  2. Sinon, signez le même défi avec solana:signMessage. La plupart des portefeuilles logiciels le prennent en charge.
  3. Proposez aux utilisateurs de portefeuilles matériels sans prise en charge des messages une option explicite, comme un interrupteur « J'utilise un portefeuille matériel », qui signe à la place une transaction de connexion. Ne les orientez pas vers cette voie par défaut.
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.

Signer un message dans le navigateur

Installez les paquets Wallet Standard avec Kit :

Terminal
$
npm install @solana/kit @solana/wallet-standard-features @wallet-standard/ui @wallet-standard/ui-features @wallet-standard/ui-registry

Ces utilitaires prennent un UiWalletAccount, comme l'account renvoyé par useConnectedWallet dans le guide React :

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 renvoie signedOffchainMessage, les octets complets que le portefeuille a signés, ainsi que signature. Encodez les deux en Base64 et envoyez-les à votre serveur avec l'adresse du portefeuille.

Rédigez le défi pour qu'il se lise bien sur un petit écran. Indiquez le nom de votre application et son domaine, expliquez ce que fait la signature, et incluez un nonce à usage unique ainsi qu'une heure d'émission provenant de votre serveur :

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

Vérifier la signature sur votre serveur

La vérification est un contrôle local de la signature et ne nécessite aucune connexion RPC. Décodez les octets signés par le portefeuille, confirmez qu'ils correspondent au défi que vous avez émis, puis vérifiez la signature :

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 lève une erreur si le contenu ou les signataires diffèrent de ce que vous attendiez. verifyOffchainMessageEnvelope lève une erreur si la signature est absente ou invalide. Retrouvez expectedMessage à partir du nonce sur votre serveur plutôt que de faire confiance à la copie renvoyée par le client. Consommez le nonce dès que la vérification réussit, afin que la même signature ne puisse pas servir à se connecter deux fois.

Vérifier un message brut

Une signature signMessage porte directement sur les octets du défi : le client n'a donc qu'à envoyer la signature et l'adresse du portefeuille. Vérifiez-la par rapport au défi que vous avez émis :

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");
}
}

Les mêmes règles de nonce s'appliquent : retrouvez expectedMessage à partir du nonce et consommez-le dès que la vérification réussit.

@solana/kit réexporte ces utilitaires depuis @solana/offchain-messages. Utilisez le paquet autonome si votre serveur ne dépend pas de Kit.

Sign In With Solana

Sign In With Solana (SIWS) standardise le défi lui-même. Votre application transmet des champs structurés, tels que domain, statement, nonce et issuedAt, à la fonctionnalité solana:signIn du portefeuille. Le portefeuille les met en forme dans un message standard, se connecte si nécessaire et le signe en une seule étape.

La version 1.1.0 de solana:signIn ajoute une option useOffchainMessage. Lorsque vous passez useOffchainMessage: { messageVersion: 1 }, le portefeuille encapsule le texte SIWS dans un message off-chain de version 1, avec le compte signataire comme unique signataire requis. Les portefeuilles matériels peuvent l'afficher et le signer, et la sortie indique signedMessageFormat: { kind: "offchainMessage", messageVersion: 1 }. Les portefeuilles qui annoncent la version 1.0.0 ne connaissent pas cette option : vérifiez donc d'abord la version de la fonctionnalité et omettez l'option dans ce cas.

solana:signIn est une fonctionnalité du portefeuille, et non du compte ; ces utilitaires prennent donc un 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;
}

Construisez l'entrée sur votre serveur, avec un nonce à usage unique et une heure issuedAt, et conservez une copie indexée par le nonce. Consommez le nonce dès que la connexion est vérifiée.

Sans Wallet Adapter, verifySignIn de @solana/wallet-standard-util ne vérifie que la sortie SIWS brute. Lorsque signedMessageFormat indique un message off-chain, signedMessage est un message off-chain dont le contenu est le texte SIWS. Reconstruisez le texte attendu à partir de l'entrée stockée et de l'adresse du signataire avec createSignInMessageText de @solana/wallet-standard-util, puis vérifiez-le comme n'importe quel autre message off-chain, comme dans Vérifier la signature sur votre serveur. Le README de @solana/offchain-messages décrit ce flux dans Kit.

Pour les portefeuilles

Les portefeuilles ajoutent la prise en charge en implémentant la fonctionnalité Wallet Standard solana:signOffchainMessage de @solana/wallet-standard-features :

  • Indiquez version: "1.0.0" et listez les versions de message que vous pouvez signer dans supportedMessageVersions. Aujourd'hui, il s'agit de [1].
  • Construisez le préambule de la version 1 à partir de requiredSigners, que vous triez et dont vous vérifiez l'absence de doublons. Rejetez la requête si le compte ne figure pas parmi les signataires requis.
  • Affichez le texte du message à l'utilisateur dans son intégralité avant la signature, y compris sur les appareils matériels.
  • Renvoyez les octets exacts que vous avez signés sous la forme de signedOffchainMessage, ainsi que la signature Ed25519.
  • Pour prendre en charge Sign In With Solana hors chaîne, indiquez version: "1.1.0" pour solana:signIn. Lorsque l'entrée inclut useOffchainMessage, signez le texte SIWS comme un message hors chaîne de version 1, renvoyez ses octets sous la forme de signedMessage, et définissez signedMessageFormat.
  • Continuez à prendre en charge solana:signMessage. Il reste inchangé, et les applications l'utilisent toujours comme solution de repli.

Is this page helpful?

Table des matières

Modifier la page
© 2026 Fondation Solana. Tous droits réservés.