Tanda Tangani Pesan & Autentikasi Pengguna

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-chainPesan mentahTransaksi
Fitur walletsignOffchainMessagesignMessagesignTransaction
Dukungan hardware walletTergantung perangkatTidakYa
Dapat dibaca penggunaYaHanya softwareTergantung perangkat
Mendeklarasikan penandatangan yang diperlukanYaTidakYa
Panggilan RPC dan biayaTidak adaTidak adaBlockhash, 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.

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

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 wallet mendukung pesan off-chain. Periksa fitur akun yang terhubung sebelum meminta tanda tangan, dan gunakan fallback saat fitur tersebut tidak tersedia:

  1. Jika akun mendukung solana:signOffchainMessage dengan pesan versi 1, tanda tangani pesan off-chain.
  2. Jika tidak, tanda tangani challenge yang sama dengan solana:signMessage. Sebagian besar software wallet mendukungnya.
  3. 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.
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.

Tanda tangani 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 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: 8f3a1c9e
Issued 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:

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

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

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

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 di supportedMessageVersions. 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, 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 tetapkan signedMessageFormat.
  • Tetap dukung solana:signMessage. Fitur ini tidak berubah, dan aplikasi masih menggunakannya sebagai cadangan.

Is this page helpful?

Daftar Isi

Edit Halaman
© 2026 Yayasan Solana. Semua hak dilindungi.