Assine mensagens e autentique usuários

Assinar uma mensagem prova que um usuário controla uma carteira, sem transação, sem taxa e sem chamada de rede. Os aplicativos usam mensagens assinadas para fazer login de usuários, registrar que um usuário aceitou os termos, vincular uma carteira a uma conta existente ou autorizar uma ação que acontece off-chain, como fazer um pedido.

As carteiras Solana assinam mensagens de duas formas:

  • Mensagens off-chain (solana:signOffchainMessage) envolvem o seu texto em um formato padrão, definido pela especificação de assinatura de mensagens off-chain (OCMS). O formato nunca pode ser confundido com uma transação, indica as contas que devem assiná-lo e é o formato assinado por carteiras de hardware.
  • Mensagens brutas (solana:signMessage) assinam exatamente os bytes que o seu aplicativo envia, sem prefixo e sem signatários declarados. A maioria das carteiras de software as suporta, mas nada nos bytes as identifica como uma mensagem em vez de uma transação.

Use mensagens off-chain quando a carteira as suportar e recorra a mensagens brutas quando não suportar. O restante deste guia usa o login como exemplo prático, mas as mesmas etapas de assinatura e verificação se aplicam a qualquer mensagem.

Escolha um formato de mensagem

Mensagem off-chainMensagem brutaTransação
Recurso da carteirasignOffchainMessagesignMessagesignTransaction
Suporte a carteiras de hardwareDepende do dispositivoNãoSim
Legível para o usuárioSimSomente softwareDepende do dispositivo
Declara os signatários necessáriosSimNãoSim
Chamadas RPC e taxasNenhumaNenhumaBlockhash, envio, taxa, confirmação

Por que não uma transação?

As carteiras de hardware não tinham como assinar uma mensagem legível. Muitos aplicativos passaram a pedir a esses usuários que assinassem uma transação sem efeito. Isso funciona, mas acrescenta latência, custo e risco:

  • Taxas de rede. Uma transação paga uma taxa, mesmo quando não faz nada.
  • Várias chamadas RPC. O seu aplicativo busca um blockhash, simula, envia e depois consulta a confirmação.
  • Latência adicional. Cada uma dessas chamadas é uma ida e volta antes de o usuário fazer login.
  • Nada legível. O usuário aprova os bytes de uma transação, não uma declaração do que está aceitando.
  • Risco comportamental. Pedir aos usuários que assinem uma transação apenas para entrar os torna mais fáceis de enganar para assinarem uma transação maliciosa.

Uma mensagem off-chain evita tudo isso. A única ida e volta é ao seu próprio servidor.

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.

O que a carteira assina

Uma mensagem off-chain começa com um domínio de assinatura fixo, \xffsolana offchain. Nenhuma transação válida começa com esses bytes, portanto uma mensagem assinada nunca pode ser usada como transação. A versão 1 do formato lista então os signatários necessários e transporta a mensagem como texto UTF-8, sem limite de tamanho.

A carteira monta esses bytes, assina-os e os devolve junto com a assinatura. A especificação cobre o formato completo, incluindo a versão 0 legada.

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.

Escolha um método de login

Nem todas as carteiras suportam mensagens off-chain ainda. Verifique os recursos da conta conectada antes de solicitar uma assinatura e use a alternativa quando o recurso não estiver disponível:

  1. Se a conta suportar solana:signOffchainMessage com a versão de mensagem 1, assine uma mensagem off-chain.
  2. Caso contrário, assine o mesmo desafio com solana:signMessage. A maioria das carteiras de software o suporta.
  3. Ofereça aos usuários de carteiras de hardware sem suporte a mensagens uma opção explícita de adesão, como um interruptor "Estou usando uma carteira de hardware", que assine uma transação de login em vez disso. Não os encaminhe por esse caminho por padrão.
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.

Assine uma mensagem no navegador

Instale os pacotes do Wallet Standard junto com o Kit:

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

Esses utilitários recebem um UiWalletAccount, como o account retornado por useConnectedWallet no guia de 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 retorna signedOffchainMessage, os bytes completos que a carteira assinou, e signature. Codifique ambos em Base64 e envie-os ao seu servidor com o endereço da carteira.

Escreva o desafio de modo que seja bem legível em uma tela pequena. Indique o nome do seu aplicativo e o domínio, explique o que a assinatura faz e inclua um nonce de uso único e um horário de emissão vindo do seu servidor:

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

Verifique a assinatura no seu servidor

A verificação é uma checagem local da assinatura e não precisa de conexão RPC. Decodifique os bytes que a carteira assinou, confirme que correspondem ao desafio que você emitiu e, em seguida, verifique a assinatura:

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 lança um erro se o conteúdo ou os signatários diferirem do esperado. verifyOffchainMessageEnvelope lança um erro se a assinatura estiver ausente ou for inválida. Busque expectedMessage pelo nonce no seu servidor em vez de confiar na cópia que o cliente devolve. Consuma o nonce assim que ele for verificado, para que a mesma assinatura não possa ser usada para entrar duas vezes.

Verifique uma mensagem bruta

Uma assinatura de signMessage cobre diretamente os bytes do desafio, portanto o cliente precisa enviar apenas a assinatura e o endereço da carteira. Compare-a com o desafio que você emitiu:

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

Aplicam-se as mesmas regras de nonce: busque expectedMessage pelo nonce e consuma-o assim que ele for verificado.

O @solana/kit reexporta esses utilitários de @solana/offchain-messages. Use o pacote independente se o seu servidor não depender do Kit.

Sign In With Solana

O Sign In With Solana (SIWS) padroniza o próprio desafio. O seu aplicativo passa campos estruturados, como domain, statement, nonce e issuedAt, para o recurso solana:signIn da carteira. A carteira os formata em uma mensagem padrão, conecta-se, se necessário, e a assina em uma única etapa.

A versão 1.1.0 de solana:signIn adiciona uma opção useOffchainMessage. Quando você passa useOffchainMessage: { messageVersion: 1 }, a carteira envolve o texto SIWS em uma mensagem off-chain da versão 1, tendo a conta que assina como único signatário necessário. As carteiras de hardware podem exibi-la e assiná-la, e a saída informa signedMessageFormat: { kind: "offchainMessage", messageVersion: 1 }. As carteiras que anunciam a versão 1.0.0 não conhecem a opção, portanto verifique primeiro a versão do recurso e omita a opção caso contrário.

solana:signIn é um recurso da carteira, não da conta, portanto esses utilitários recebem um 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;
}

Monte a entrada no seu servidor, com um nonce de uso único e um horário issuedAt, e guarde uma cópia indexada pelo nonce. Consuma o nonce assim que o login for verificado.

Sem o Wallet Adapter, verifySignIn de @solana/wallet-standard-util verifica apenas a saída SIWS simples. Quando signedMessageFormat indica uma mensagem off-chain, signedMessage é uma mensagem off-chain cujo conteúdo é o texto SIWS. Reconstrua o texto esperado a partir da entrada armazenada e do endereço que assinou com createSignInMessageText de @solana/wallet-standard-util e, em seguida, verifique-o como qualquer outra mensagem off-chain, conforme descrito em Verifique a assinatura no seu servidor. O README de @solana/offchain-messages cobre esse fluxo no Kit.

Para carteiras

As carteiras adicionam suporte implementando o recurso solana:signOffchainMessage do Wallet Standard, de @solana/wallet-standard-features:

  • Informe version: "1.0.0" e liste as versões de mensagem que você pode assinar em supportedMessageVersions. Atualmente, isso é [1].
  • Crie o preâmbulo da versão 1 a partir de requiredSigners, que deve ser ordenado e verificado quanto a duplicados. Rejeite a solicitação se a conta não for um dos signatários obrigatórios.
  • Mostre o texto da mensagem ao usuário na íntegra antes da assinatura, inclusive em dispositivos de hardware.
  • Retorne os bytes exatos que você assinou como signedOffchainMessage, juntamente com a signature Ed25519.
  • Para oferecer suporte ao Sign In With Solana off-chain, informe version: "1.1.0" para solana:signIn. Quando a entrada incluir useOffchainMessage, assine o texto SIWS como uma mensagem off-chain da versão 1, retorne os respectivos bytes como signedMessage e defina signedMessageFormat.
  • Continue oferecendo suporte a solana:signMessage. Ele permanece inalterado e os apps ainda o utilizam como alternativa.

Is this page helpful?

Índice

Editar Página
© 2026 Fundação Solana. Todos os direitos reservados.