Υπογραφή μηνυμάτων και έλεγχος ταυτότητας χρηστών

Η υπογραφή ενός μηνύματος αποδεικνύει ότι ένας χρήστης ελέγχει ένα πορτοφόλι, χωρίς συναλλαγή, χωρίς χρέωση και χωρίς κλήση δικτύου. Οι εφαρμογές χρησιμοποιούν υπογεγραμμένα μηνύματα για να συνδέσουν χρήστες, να καταγράψουν ότι ένας χρήστης αποδέχτηκε όρους, να συνδέσουν ένα πορτοφόλι με έναν υπάρχοντα λογαριασμό ή να εξουσιοδοτήσουν μια ενέργεια που γίνεται εκτός αλυσίδας, όπως η υποβολή μιας παραγγελίας.

Τα πορτοφόλια Solana υπογράφουν μηνύματα με δύο τρόπους:

  • Τα μηνύματα off-chain (solana:signOffchainMessage) περικλείουν το κείμενό σας σε μια τυπική μορφή, που ορίζεται από την προδιαγραφή υπογραφής μηνυμάτων off-chain (OCMS). Η μορφή αυτή δεν μπορεί ποτέ να εκληφθεί ως συναλλαγή, κατονομάζει τους λογαριασμούς που πρέπει να την υπογράψουν και είναι η μορφή που υπογράφουν τα πορτοφόλια υλικού.
  • Τα ακατέργαστα μηνύματα (solana:signMessage) υπογράφουν ακριβώς τα bytes που περνά η εφαρμογή σας, χωρίς πρόθεμα και χωρίς δηλωμένους υπογράφοντες. Τα περισσότερα πορτοφόλια λογισμικού τα υποστηρίζουν, αλλά τίποτα στα bytes δεν τα χαρακτηρίζει ως μήνυμα και όχι ως συναλλαγή.

Χρησιμοποιήστε μηνύματα off-chain όπου το πορτοφόλι τα υποστηρίζει και καταφύγετε σε ακατέργαστα μηνύματα όπου δεν τα υποστηρίζει. Ο υπόλοιπος οδηγός χρησιμοποιεί τη σύνδεση ως παράδειγμα, αλλά τα ίδια βήματα υπογραφής και επαλήθευσης ισχύουν για οποιοδήποτε μήνυμα.

Επιλέξτε μορφή μηνύματος

Μήνυμα off-chainΑκατέργαστο μήνυμαΣυναλλαγή
Δυνατότητα πορτοφολιούsignOffchainMessagesignMessagesignTransaction
Υποστήριξη πορτοφολιών υλικούΕξαρτάται από τη συσκευήΌχιΝαι
Αναγνώσιμο από τον χρήστηΝαιΜόνο λογισμικόΕξαρτάται από τη συσκευή
Δηλώνει τους απαιτούμενους υπογράφοντεςΝαιΌχιΝαι
Κλήσεις RPC και χρεώσειςΚαμίαΚαμίαBlockhash, αποστολή, χρέωση, επιβεβαίωση

Γιατί όχι συναλλαγή;

Παλαιότερα τα πορτοφόλια υλικού δεν είχαν τρόπο να υπογράψουν ένα αναγνώσιμο μήνυμα. Πολλές εφαρμογές ζητούσαν από αυτούς τους χρήστες να υπογράψουν αντί γι' αυτό μια συναλλαγή χωρίς ενέργεια. Αυτό λειτουργεί, αλλά προσθέτει καθυστέρηση, κόστος και κίνδυνο:

  • Χρεώσεις δικτύου. Μια συναλλαγή πληρώνει χρέωση, ακόμη κι όταν δεν κάνει τίποτα.
  • Πολλαπλές κλήσεις RPC. Η εφαρμογή σας ανακτά ένα blockhash, εκτελεί προσομοίωση, στέλνει και έπειτα ελέγχει τακτικά για επιβεβαίωση.
  • Πρόσθετη καθυστέρηση. Κάθε μία από αυτές τις κλήσεις είναι μια μετάβαση μετ' επιστροφής πριν ο χρήστης συνδεθεί.
  • Τίποτα αναγνώσιμο. Ο χρήστης εγκρίνει bytes συναλλαγής, όχι μια δήλωση για το τι αποδέχεται.
  • Κίνδυνος συμπεριφοράς. Το να ζητάτε από τους χρήστες να υπογράψουν μια συναλλαγή μόνο για να συνδεθούν τους κάνει πιο εύκολο στόχο εξαπάτησης ώστε να υπογράψουν μια επιβλαβή συναλλαγή.

Ένα μήνυμα off-chain τα παρακάμπτει όλα αυτά. Η μόνη μετάβαση μετ' επιστροφής γίνεται προς τον δικό σας διακομιστή.

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.

Τι υπογράφει το πορτοφόλι

Ένα μήνυμα off-chain ξεκινά με ένα σταθερό πεδίο υπογραφής, \xffsolana offchain. Καμία έγκυρη συναλλαγή δεν ξεκινά με αυτά τα bytes, επομένως ένα υπογεγραμμένο μήνυμα δεν μπορεί ποτέ να χρησιμοποιηθεί ως συναλλαγή. Η έκδοση 1 της μορφής παραθέτει στη συνέχεια τους απαιτούμενους υπογράφοντες και μεταφέρει το μήνυμα ως κείμενο UTF-8, χωρίς όριο μήκους.

Το πορτοφόλι δημιουργεί αυτά τα bytes, τα υπογράφει και τα επιστρέφει μαζί με την υπογραφή. Η προδιαγραφή καλύπτει ολόκληρη τη μορφή, συμπεριλαμβανομένης της παλαιότερης έκδοσης 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.

Επιλέξτε μέθοδο σύνδεσης

Δεν υποστηρίζουν όλα τα πορτοφόλια ακόμη μηνύματα off-chain. Ελέγξτε τις δυνατότητες του συνδεδεμένου λογαριασμού πριν ζητήσετε υπογραφή και καταφύγετε σε εναλλακτική λύση όταν η δυνατότητα λείπει:

  1. Αν ο λογαριασμός υποστηρίζει το solana:signOffchainMessage με έκδοση μηνύματος 1, υπογράψτε ένα μήνυμα off-chain.
  2. Διαφορετικά, υπογράψτε την ίδια πρόκληση με το solana:signMessage. Τα περισσότερα πορτοφόλια λογισμικού το υποστηρίζουν.
  3. Δώστε στους χρήστες πορτοφολιών υλικού χωρίς υποστήριξη μηνυμάτων μια ρητή επιλογή συμμετοχής, όπως έναν διακόπτη «Χρησιμοποιώ πορτοφόλι υλικού», που υπογράφει αντί γι' αυτό μια συναλλαγή σύνδεσης. Μην τους κατευθύνετε σε αυτή τη διαδρομή από προεπιλογή.
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.

Υπογραφή μηνύματος στο πρόγραμμα περιήγησης

Εγκαταστήστε τα πακέτα Wallet Standard μαζί με το Kit:

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

Αυτά τα βοηθητικά εργαλεία δέχονται ένα UiWalletAccount, όπως το account που επιστρέφει το useConnectedWallet στον οδηγό 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 επιστρέφει το signedOffchainMessage, τα πλήρη bytes που υπέγραψε το πορτοφόλι, και το signature. Κωδικοποιήστε και τα δύο σε Base64 και στείλτε τα στον διακομιστή σας μαζί με τη διεύθυνση του πορτοφολιού.

Γράψτε την πρόκληση έτσι ώστε να διαβάζεται εύκολα σε μικρή οθόνη. Αναφέρετε την εφαρμογή και τον τομέα σας, εξηγήστε τι κάνει η υπογραφή και συμπεριλάβετε ένα nonce μίας χρήσης και μια ώρα έκδοσης από τον διακομιστή σας:

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

Επαλήθευση της υπογραφής στον διακομιστή σας

Η επαλήθευση είναι ένας τοπικός έλεγχος υπογραφής και δεν χρειάζεται σύνδεση RPC. Αποκωδικοποιήστε τα bytes που υπέγραψε το πορτοφόλι, επιβεβαιώστε ότι ταιριάζουν με την πρόκληση που εκδώσατε και έπειτα ελέγξτε την υπογραφή:

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 πετά σφάλμα αν το περιεχόμενο ή οι υπογράφοντες διαφέρουν από αυτά που περιμένατε. Το verifyOffchainMessageEnvelope πετά σφάλμα αν η υπογραφή λείπει ή δεν είναι έγκυρη. Αναζητήστε το expectedMessage με βάση το nonce στον διακομιστή σας αντί να εμπιστεύεστε το αντίγραφο που στέλνει πίσω ο πελάτης. Καταναλώστε το nonce μόλις επαληθευτεί, ώστε η ίδια υπογραφή να μην μπορεί να χρησιμοποιηθεί για σύνδεση δύο φορές.

Επαλήθευση ακατέργαστου μηνύματος

Μια υπογραφή signMessage καλύπτει απευθείας τα bytes της πρόκλησης, οπότε ο πελάτης χρειάζεται να στείλει μόνο την υπογραφή και τη διεύθυνση του πορτοφολιού. Ελέγξτε την σε σχέση με την πρόκληση που εκδώσατε:

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

Ισχύουν οι ίδιοι κανόνες για το nonce: αναζητήστε το expectedMessage με βάση το nonce και καταναλώστε το μόλις επαληθευτεί.

Το @solana/kit επανεξάγει αυτά τα βοηθητικά εργαλεία από το @solana/offchain-messages. Χρησιμοποιήστε το αυτόνομο πακέτο αν ο διακομιστής σας δεν εξαρτάται από το Kit.

Sign In With Solana

Το Sign In With Solana (SIWS) τυποποιεί την ίδια την πρόκληση. Η εφαρμογή σας περνά δομημένα πεδία, όπως domain, statement, nonce και issuedAt, στη δυνατότητα solana:signIn του πορτοφολιού. Το πορτοφόλι τα μορφοποιεί σε ένα τυπικό μήνυμα, συνδέεται αν χρειαστεί και το υπογράφει σε ένα βήμα.

Η έκδοση 1.1.0 του solana:signIn προσθέτει την επιλογή useOffchainMessage. Όταν περάσετε useOffchainMessage: { messageVersion: 1 }, το πορτοφόλι περικλείει το κείμενο SIWS σε ένα μήνυμα off-chain έκδοσης 1 με τον λογαριασμό υπογραφής ως τον μοναδικό απαιτούμενο υπογράφοντα. Τα πορτοφόλια υλικού μπορούν να το εμφανίσουν και να το υπογράψουν, και η έξοδος αναφέρει signedMessageFormat: { kind: "offchainMessage", messageVersion: 1 }. Τα πορτοφόλια που δηλώνουν την έκδοση 1.0.0 δεν γνωρίζουν την επιλογή, γι' αυτό ελέγξτε πρώτα την έκδοση της δυνατότητας και παραλείψτε την σε αντίθετη περίπτωση.

Το solana:signIn είναι δυνατότητα πορτοφολιού και όχι λογαριασμού, επομένως αυτά τα βοηθητικά εργαλεία δέχονται ένα 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;
}

Δημιουργήστε την είσοδο στον διακομιστή σας, με ένα nonce μίας χρήσης και μια ώρα issuedAt, και κρατήστε ένα αντίγραφο με κλειδί το nonce. Καταναλώστε το nonce μόλις η σύνδεση επαληθευτεί.

Χωρίς το Wallet Adapter, το verifySignIn από το @solana/wallet-standard-util ελέγχει μόνο απλή έξοδο SIWS. Όταν το signedMessageFormat αναφέρει μήνυμα off-chain, το signedMessage είναι ένα μήνυμα off-chain με περιεχόμενο το κείμενο SIWS. Ξαναδημιουργήστε το αναμενόμενο κείμενο από την αποθηκευμένη είσοδο και τη διεύθυνση υπογραφής με το createSignInMessageText από το @solana/wallet-standard-util και έπειτα επαληθεύστε το όπως οποιοδήποτε άλλο μήνυμα off-chain, όπως στην ενότητα Επαλήθευση της υπογραφής στον διακομιστή σας. Το README του @solana/offchain-messages καλύπτει αυτή τη ροή στο Kit.

Για πορτοφόλια

Τα πορτοφόλια προσθέτουν υποστήριξη υλοποιώντας τη δυνατότητα solana:signOffchainMessage του Wallet Standard από το @solana/wallet-standard-features:

  • Αναφέρετε version: "1.0.0" και παραθέστε τις εκδόσεις μηνυμάτων που μπορείτε να υπογράψετε στο supportedMessageVersions. Σήμερα, αυτό είναι [1].
  • Δημιουργήστε το preamble έκδοσης 1 από το requiredSigners, το οποίο ταξινομείτε και ελέγχετε για διπλότυπα. Απορρίψτε το αίτημα αν ο λογαριασμός δεν είναι ένας από τους απαιτούμενους υπογράφοντες.
  • Εμφανίστε το κείμενο του μηνύματος στον χρήστη ολόκληρο πριν από την υπογραφή, μεταξύ άλλων και σε συσκευές hardware.
  • Επιστρέψτε τα ακριβή bytes που υπογράψατε ως signedOffchainMessage, μαζί με την signature Ed25519.
  • Για να υποστηρίξετε το off-chain Sign In With Solana, δηλώστε version: "1.1.0" για το solana:signIn. Όταν η είσοδος περιλαμβάνει το useOffchainMessage, υπογράψτε το κείμενο SIWS ως off-chain μήνυμα έκδοσης 1, επιστρέψτε τα bytes του ως signedMessage και ορίστε το signedMessageFormat.
  • Συνεχίστε να υποστηρίζετε το solana:signMessage. Παραμένει αμετάβλητο και οι εφαρμογές εξακολουθούν να το χρησιμοποιούν ως εναλλακτική λύση.

Is this page helpful?

© 2026 Ίδρυμα Solana. Με επιφύλαξη παντός δικαιώματος.