Menandatangani pesan membuktikan bahwa pengguna mengendalikan sebuah wallet, 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 wallet ke akun yang sudah ada, atau mengotorisasi aksi yang terjadi di luar chain (off-chain), seperti membuat pesanan.
Wallet Solana menandatangani pesan dengan dua cara:
- Pesan off-chain (
solana:signOffchainMessage) membungkus teks Anda dalam format standar, yang ditetapkan oleh spesifikasi penandatanganan pesan off-chain (OCMS). Format ini tidak akan pernah bisa disalahartikan sebagai transaksi, mencantumkan akun yang harus menandatanganinya, dan merupakan format yang ditandatangani oleh hardware wallet. - Pesan mentah (
solana:signMessage) menandatangani persis byte yang diteruskan aplikasi Anda, tanpa prefiks dan tanpa penandatangan yang dideklarasikan. Sebagian besar software wallet mendukungnya, tetapi tidak ada apa pun di dalam byte tersebut yang menandainya sebagai pesan dan bukan transaksi.
Gunakan pesan off-chain jika wallet mendukungnya, dan beralihlah 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-chain | Pesan mentah | Transaksi | |
|---|---|---|---|
| Fitur wallet | signOffchainMessage | signMessage | signTransaction |
| Dukungan hardware wallet | Tergantung perangkat | Tidak | Ya |
| Dapat dibaca pengguna | Ya | Hanya software | Tergantung perangkat |
| Mendeklarasikan penandatangan yang diperlukan | Ya | Tidak | Ya |
| Panggilan RPC dan biaya | Tidak ada | Tidak ada | Blockhash, kirim, biaya, konfirmasi |
Mengapa bukan transaksi?
Dahulu hardware wallet tidak memiliki cara untuk menandatangani pesan yang dapat dibaca. Banyak aplikasi beralih dengan meminta pengguna ini menandatangani transaksi no-op. Cara itu berhasil, tetapi menambah latensi, biaya, dan risiko:
- Biaya jaringan. Transaksi membayar biaya, bahkan ketika tidak melakukan apa pun.
- Beberapa panggilan RPC. Aplikasi Anda mengambil blockhash, melakukan simulasi, mengirim, lalu 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 hanyalah ke server Anda sendiri.
Apa yang ditandatangani wallet
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 penandatangan yang diperlukan
dan membawa pesan sebagai teks UTF-8, tanpa batas panjang.
Wallet menyusun byte ini, menandatanganinya, dan mengembalikannya bersama tanda tangan. Spesifikasi mencakup format lengkap, termasuk versi 0 yang lama.
Pilih metode sign-in
Belum semua wallet mendukung pesan off-chain. Periksa fitur akun yang terhubung sebelum meminta tanda tangan, dan gunakan fallback saat fitur tersebut tidak tersedia:
- Jika akun mendukung
solana:signOffchainMessagedengan pesan versi 1, tanda tangani pesan off-chain. - Jika tidak, tanda tangani challenge yang sama dengan
solana:signMessage. Sebagian besar software wallet mendukungnya. - Berikan opsi opt-in eksplisit bagi pengguna hardware wallet yang tidak mendukung pesan, seperti tombol "Saya menggunakan hardware wallet", yang menandatangani transaksi sign-in sebagai gantinya. Jangan arahkan mereka ke jalur ini secara default.
Tanda tangani pesan di browser
Instal paket Wallet Standard bersama Kit:
$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:
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 seluruh byte yang ditandatangani
wallet, dan signature. Encode keduanya dengan Base64 lalu kirim ke server Anda bersama
alamat wallet.
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: 8f3a1c9eIssued at: 2026-09-29T17:04:00Z
Verifikasi tanda tangan di server Anda
Verifikasi adalah pemeriksaan tanda tangan secara lokal dan tidak memerlukan koneksi RPC. Dekode byte yang ditandatangani wallet, pastikan byte tersebut cocok dengan challenge yang Anda terbitkan, lalu periksa tanda tangannya:
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 akan melempar error jika konten atau penandatangan berbeda dari
yang Anda harapkan. verifyOffchainMessageEnvelope akan melempar error jika tanda tangan
hilang atau tidak valid. Cari expectedMessage berdasarkan nonce di server Anda, jangan
mempercayai salinan yang dikirim kembali oleh klien. Gunakan nonce sekali setelah
verifikasi berhasil, agar tanda tangan yang sama tidak dapat digunakan untuk sign in dua kali.
Verifikasi pesan mentah
Tanda tangan signMessage mencakup byte challenge secara langsung, sehingga klien
hanya perlu mengirim tanda tangan dan alamat wallet. Periksa terhadap
challenge yang Anda terbitkan:
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
sekali 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)
menstandarkan challenge itu sendiri. Aplikasi Anda meneruskan kolom terstruktur, seperti
domain, statement, nonce, dan issuedAt, ke fitur solana:signIn
milik wallet. Wallet memformatnya menjadi pesan standar, menghubungkan jika diperlukan,
dan menandatanganinya dalam satu langkah.
Versi 1.1.0 dari solana:signIn menambahkan opsi useOffchainMessage. Ketika Anda
meneruskan useOffchainMessage: { messageVersion: 1 }, wallet membungkus teks SIWS
dalam pesan off-chain versi 1 dengan akun penandatangan sebagai satu-satunya penandatangan
yang diperlukan. Hardware wallet dapat menampilkan dan menandatanganinya, dan output melaporkan
signedMessageFormat: { kind: "offchainMessage", messageVersion: 1 }. Wallet
yang mengiklankan versi 1.0.0 tidak mengenal opsi ini, jadi periksa versi fitur
terlebih dahulu dan hilangkan opsi tersebut jika tidak didukung.
solana:signIn adalah fitur wallet, bukan fitur akun, sehingga helper ini
menerima UiWallet:
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;}
Bangun input di server Anda, dengan nonce sekali pakai dan waktu issuedAt,
lalu simpan salinannya berdasarkan nonce. Gunakan nonce sekali setelah sign-in
terverifikasi.
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 isinya adalah teks SIWS.
Bangun ulang teks yang diharapkan dari input yang Anda simpan dan alamat penandatangan 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 wallet
Wallet menambahkan dukungan dengan mengimplementasikan fitur Wallet
Standard solana:signOffchainMessage dari @solana/wallet-standard-features:
- Laporkan
version: "1.0.0"dan daftarkan versi pesan yang dapat Anda tandatangani disupportedMessageVersions. Saat ini, nilainya adalah[1]. - Bangun preamble versi 1 dari
requiredSigners, yang Anda urutkan dan periksa apakah ada duplikat. Tolak permintaan jika akun tersebut bukan salah satu penandatangan yang diwajibkan. - Tampilkan teks pesan kepada pengguna secara lengkap sebelum penandatanganan, termasuk di perangkat keras.
- Kembalikan byte persis yang Anda tandatangani sebagai
signedOffchainMessage, besertasignatureEd25519. - Untuk mendukung Sign In With Solana secara off-chain, laporkan
version: "1.1.0"untuksolana:signIn. Jika input menyertakanuseOffchainMessage, tandatangani teks SIWS sebagai pesan off-chain versi 1, kembalikan byte-nya sebagaisignedMessage, dan tetapkansignedMessageFormat. - Tetap dukung
solana:signMessage. Fitur ini tidak berubah, dan aplikasi masih menggunakannya sebagai cadangan.
Is this page helpful?