Menandatangani Pesan & Mengautentikasi Pengguna

Menandatangani pesan membuktikan bahwa pengguna mengendalikan sebuah dompet, tanpa transaksi, tanpa biaya, dan tanpa panggilan jaringan. Aplikasi menggunakan pesan yang ditandatangani untuk memasukkan pengguna (sign in), mencatat bahwa pengguna telah menerima ketentuan, menautkan dompet ke akun yang sudah ada, atau mengotorisasi tindakan yang terjadi di luar chain (off-chain), seperti membuat pesanan.

Dompet Solana menandatangani pesan dengan dua cara:

  • Pesan off-chain (solana:signOffchainMessage) membungkus teks Anda dalam format standar yang didefinisikan oleh spesifikasi penandatanganan pesan off-chain (OCMS). Format ini tidak akan pernah bisa disalahartikan sebagai transaksi, menyebutkan akun yang harus menandatanganinya, dan merupakan format yang ditandatangani oleh dompet perangkat keras.
  • Pesan mentah (raw) (solana:signMessage) menandatangani persis byte yang diteruskan aplikasi Anda, tanpa awalan dan tanpa penanda tangan yang dideklarasikan. Sebagian besar dompet perangkat lunak mendukungnya, tetapi tidak ada apa pun pada byte tersebut yang menandainya sebagai pesan, bukan transaksi.

Gunakan pesan off-chain jika dompet mendukungnya, dan beralih ke pesan mentah jika tidak. Sisa panduan ini menggunakan sign-in sebagai contoh kasus, tetapi langkah penandatanganan dan verifikasi yang sama berlaku untuk pesan apa pun.

Pilih format pesan

Pesan off-chainPesan mentahTransaksi
Fitur dompetsignOffchainMessagesignMessagesignTransaction
Dukungan dompet perangkat kerasTergantung perangkatTidakYa
Dapat dibaca penggunaYaHanya perangkat lunakTergantung perangkat
Mendeklarasikan penanda tangan yang diperlukanYaTidakYa
Panggilan RPC dan biayaTidak adaTidak adaBlockhash, kirim, biaya, konfirmasi

Mengapa bukan transaksi?

Dulu dompet perangkat keras tidak punya cara untuk menandatangani pesan yang dapat dibaca. Banyak aplikasi terpaksa meminta pengguna ini menandatangani transaksi no-op. Cara ini berhasil, tetapi menambah latensi, biaya, dan risiko:

  • Biaya jaringan. Transaksi membayar biaya, bahkan ketika tidak melakukan apa pun.
  • Banyak panggilan RPC. Aplikasi Anda mengambil blockhash, melakukan simulasi, mengirim, dan kemudian melakukan polling untuk konfirmasi.
  • Latensi tambahan. Setiap panggilan tersebut adalah satu putaran bolak-balik sebelum pengguna berhasil masuk.
  • Tidak ada yang dapat dibaca. Pengguna menyetujui byte transaksi, bukan pernyataan tentang apa yang mereka setujui.
  • Risiko perilaku. Meminta pengguna menandatangani transaksi hanya untuk login membuat mereka lebih mudah ditipu untuk menandatangani transaksi yang berbahaya.

Pesan off-chain melewati semua itu. Satu-satunya putaran bolak-balik hanya ke server Anda sendiri.

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.

Apa yang ditandatangani dompet

Pesan off-chain diawali dengan domain penandatanganan tetap, \xffsolana offchain. Tidak ada transaksi valid yang diawali dengan byte tersebut, sehingga pesan yang ditandatangani tidak akan pernah bisa digunakan sebagai transaksi. Format versi 1 kemudian mencantumkan penanda tangan yang diperlukan dan membawa pesan sebagai teks UTF-8, tanpa batas panjang.

Dompet membuat byte ini, menandatanganinya, dan mengembalikannya bersama tanda tangan. Spesifikasi membahas format lengkapnya, termasuk versi 0 yang lama.

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.

Pilih metode sign-in

Belum semua dompet mendukung pesan off-chain. Periksa fitur akun yang terhubung sebelum meminta tanda tangan, dan gunakan cadangan jika fitur tersebut tidak tersedia:

  1. Jika akun mendukung solana:signOffchainMessage dengan pesan versi 1, tandatangani pesan off-chain.
  2. Jika tidak, tandatangani challenge yang sama dengan solana:signMessage. Sebagian besar dompet perangkat lunak mendukungnya.
  3. Berikan pengguna dompet perangkat keras tanpa dukungan pesan opsi opt-in yang eksplisit, seperti tombol "Saya menggunakan dompet perangkat keras", yang menandatangani transaksi sign-in sebagai gantinya. Jangan arahkan mereka ke jalur ini secara default.
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.

Menandatangani pesan di browser

Instal paket Wallet Standard bersama Kit:

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

Helper ini menerima UiWalletAccount, seperti account yang dikembalikan oleh useConnectedWallet di panduan 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 mengembalikan signedOffchainMessage, yaitu byte lengkap yang ditandatangani dompet, dan signature. Encode keduanya dengan Base64 dan kirim ke server Anda bersama alamat dompet.

Tulis challenge agar mudah dibaca di layar kecil. Sebutkan nama aplikasi dan domain Anda, jelaskan apa yang dilakukan penandatanganan, dan sertakan nonce sekali pakai serta waktu penerbitan dari server Anda:

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

Verifikasi tanda tangan di server Anda

Verifikasi merupakan pemeriksaan tanda tangan secara lokal dan tidak memerlukan koneksi RPC. Decode byte yang ditandatangani dompet, pastikan byte tersebut cocok dengan challenge yang Anda terbitkan, lalu periksa tanda tangannya:

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 melempar error jika konten atau penanda tangan berbeda dari yang Anda harapkan. verifyOffchainMessageEnvelope melempar error jika tanda tangan tidak ada atau tidak valid. Cari expectedMessage berdasarkan nonce di server Anda, bukan mempercayai salinan yang dikirim kembali oleh klien. Gunakan (consume) nonce setelah verifikasi berhasil, sehingga tanda tangan yang sama tidak dapat dipakai untuk sign-in dua kali.

Memverifikasi pesan mentah

Tanda tangan signMessage mencakup byte challenge secara langsung, sehingga klien hanya perlu mengirim tanda tangan dan alamat dompet. Periksa terhadap challenge yang Anda terbitkan:

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

Aturan nonce yang sama berlaku: cari expectedMessage berdasarkan nonce dan gunakan (consume) nonce setelah verifikasi berhasil.

@solana/kit mengekspor ulang helper ini dari @solana/offchain-messages. Gunakan paket mandiri jika server Anda tidak bergantung pada Kit.

Sign In With Solana

Sign In With Solana (SIWS) menstandardisasi challenge itu sendiri. Aplikasi Anda meneruskan kolom terstruktur, seperti domain, statement, nonce, dan issuedAt, ke fitur solana:signIn milik dompet. Dompet memformatnya menjadi pesan standar, terhubung jika diperlukan, dan menandatanganinya dalam satu langkah.

Versi 1.1.0 dari solana:signIn menambahkan opsi useOffchainMessage. Ketika Anda meneruskan useOffchainMessage: { messageVersion: 1 }, dompet membungkus teks SIWS dalam pesan off-chain versi 1 dengan akun penanda tangan sebagai satu-satunya penanda tangan yang diperlukan. Dompet perangkat keras dapat menampilkan dan menandatanganinya, dan output melaporkan signedMessageFormat: { kind: "offchainMessage", messageVersion: 1 }. Dompet yang mengiklankan versi 1.0.0 tidak mengenal opsi ini, jadi periksa versi fitur terlebih dahulu dan abaikan opsi tersebut jika tidak didukung.

solana:signIn adalah fitur dompet, bukan fitur akun, sehingga helper ini menerima 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;
}

Susun input di server Anda, dengan nonce sekali pakai dan waktu issuedAt, lalu simpan salinannya dengan nonce sebagai kunci. Gunakan (consume) nonce setelah sign-in berhasil diverifikasi.

Tanpa Wallet Adapter, verifySignIn dari @solana/wallet-standard-util hanya memeriksa output SIWS biasa. Ketika signedMessageFormat melaporkan pesan off-chain, signedMessage adalah pesan off-chain yang kontennya adalah teks SIWS. Bangun ulang teks yang diharapkan dari input yang Anda simpan dan alamat penanda tangan dengan createSignInMessageText dari @solana/wallet-standard-util, lalu verifikasi seperti pesan off-chain lainnya, seperti pada Verifikasi tanda tangan di server Anda. README @solana/offchain-messages membahas alur ini di Kit.

Untuk dompet

Dompet menambahkan dukungan dengan mengimplementasikan fitur Wallet Standard solana:signOffchainMessage dari @solana/wallet-standard-features:

  • Laporkan version: "1.0.0" dan cantumkan versi pesan yang dapat Anda tandatangani di supportedMessageVersions. Saat ini, nilainya adalah [1].
  • Buat preamble versi 1 dari requiredSigners, yang Anda urutkan dan periksa apakah ada duplikat. Tolak permintaan jika akun bukan salah satu penandatangan yang diwajibkan.
  • Tampilkan teks pesan kepada pengguna secara utuh sebelum penandatanganan, termasuk di perangkat keras.
  • Kembalikan byte persis yang Anda tandatangani sebagai signedOffchainMessage, beserta signature Ed25519.
  • Untuk mendukung Sign In With Solana secara off-chain, laporkan version: "1.1.0" untuk solana:signIn. Jika input menyertakan useOffchainMessage, tandatangani teks SIWS sebagai pesan off-chain versi 1, kembalikan byte-nya sebagai signedMessage, dan atur signedMessageFormat.
  • Teruslah mendukung solana:signMessage. Fungsi ini tidak berubah, dan aplikasi tetap menggunakannya sebagai cadangan.

Is this page helpful?

Daftar Isi

Edit Halaman
© 2026 Yayasan Solana. Semua hak dilindungi.