Η υπογραφή ενός μηνύματος αποδεικνύει ότι ένας χρήστης ελέγχει ένα πορτοφόλι, χωρίς συναλλαγή, χωρίς κόστος και χωρίς κλήση δικτύου. Οι εφαρμογές χρησιμοποιούν υπογεγραμμένα μηνύματα για να συνδέσουν χρήστες, να καταγράψουν ότι ένας χρήστης αποδέχτηκε όρους, να συνδέσουν ένα πορτοφόλι με έναν υπάρχοντα λογαριασμό ή να εξουσιοδοτήσουν μια ενέργεια που γίνεται εκτός αλυσίδας, όπως η υποβολή μιας παραγγελίας.
Τα πορτοφόλια Solana υπογράφουν μηνύματα με δύο τρόπους:
- Μηνύματα εκτός αλυσίδας (
solana:signOffchainMessage): περιτυλίγουν το κείμενό σας σε μια τυπική μορφή, η οποία ορίζεται από την προδιαγραφή υπογραφής μηνυμάτων εκτός αλυσίδας (OCMS). Η μορφή αυτή δεν μπορεί ποτέ να εκληφθεί ως συναλλαγή, δηλώνει τους λογαριασμούς που πρέπει να την υπογράψουν και είναι η μορφή που υπογράφουν τα πορτοφόλια υλικού. - Ακατέργαστα μηνύματα (
solana:signMessage): υπογράφουν ακριβώς τα bytes που περνά η εφαρμογή σας, χωρίς πρόθεμα και χωρίς δηλωμένους υπογράφοντες. Τα περισσότερα πορτοφόλια λογισμικού τα υποστηρίζουν, αλλά τίποτα στα bytes δεν τα χαρακτηρίζει ως μήνυμα αντί για συναλλαγή.
Χρησιμοποιήστε μηνύματα εκτός αλυσίδας όπου το πορτοφόλι τα υποστηρίζει και καταφύγετε σε ακατέργαστα μηνύματα όπου δεν τα υποστηρίζει. Ο υπόλοιπος οδηγός χρησιμοποιεί τη σύνδεση ως πρακτικό παράδειγμα, αλλά τα ίδια βήματα υπογραφής και επαλήθευσης ισχύουν για οποιοδήποτε μήνυμα.
Επιλέξτε μορφή μηνύματος
| Μήνυμα εκτός αλυσίδας | Ακατέργαστο μήνυμα | Συναλλαγή | |
|---|---|---|---|
| Δυνατότητα πορτοφολιού | signOffchainMessage | signMessage | signTransaction |
| Υποστήριξη πορτοφολιών υλικού | Εξαρτάται από τη συσκευή | Όχι | Ναι |
| Αναγνώσιμο για τον χρήστη | Ναι | Μόνο λογισμικό | Εξαρτάται από τη συσκευή |
| Δηλώνει τους απαιτούμενους υπογράφοντες | Ναι | Όχι | Ναι |
| Κλήσεις RPC και χρεώσεις | Καμία | Καμία | Blockhash, αποστολή, χρέωση, επιβεβαίωση |
Γιατί όχι συναλλαγή;
Παλαιότερα, τα πορτοφόλια υλικού δεν είχαν τρόπο να υπογράψουν ένα αναγνώσιμο μήνυμα. Πολλές εφαρμογές κατέφευγαν στο να ζητούν από αυτούς τους χρήστες να υπογράψουν μια συναλλαγή χωρίς ουσιαστική ενέργεια. Αυτό λειτουργεί, αλλά προσθέτει καθυστέρηση, κόστος και κίνδυνο:
- Χρεώσεις δικτύου. Μια συναλλαγή επιβαρύνεται με χρέωση, ακόμη και όταν δεν κάνει τίποτα.
- Πολλαπλές κλήσεις RPC. Η εφαρμογή σας ανακτά ένα blockhash, εκτελεί προσομοίωση, στέλνει και έπειτα ελέγχει διαρκώς για επιβεβαίωση.
- Πρόσθετη καθυστέρηση. Κάθε μία από αυτές τις κλήσεις είναι μια μετάβαση μετ' επιστροφής πριν ο χρήστης συνδεθεί.
- Τίποτα αναγνώσιμο. Ο χρήστης εγκρίνει bytes συναλλαγής και όχι μια δήλωση για το τι αποδέχεται.
- Συμπεριφορικός κίνδυνος. Το να ζητάτε από τους χρήστες να υπογράψουν μια συναλλαγή μόνο για να συνδεθούν τους κάνει πιο εύκολους στο να εξαπατηθούν και να υπογράψουν μια επιζήμια συναλλαγή.
Ένα μήνυμα εκτός αλυσίδας τα παρακάμπτει όλα αυτά. Η μόνη μετάβαση μετ' επιστροφής γίνεται προς τον δικό σας διακομιστή.
Τι υπογράφει το πορτοφόλι
Ένα μήνυμα εκτός αλυσίδας ξεκινά με έναν σταθερό τομέα υπογραφής, \xffsolana offchain.
Καμία έγκυρη συναλλαγή δεν ξεκινά με αυτά τα bytes, επομένως ένα υπογεγραμμένο μήνυμα δεν μπορεί ποτέ να
χρησιμοποιηθεί ως συναλλαγή. Η έκδοση 1 της μορφής στη συνέχεια απαριθμεί τους απαιτούμενους υπογράφοντες
και μεταφέρει το μήνυμα ως κείμενο UTF-8, χωρίς όριο μήκους.
Το πορτοφόλι δημιουργεί αυτά τα bytes, τα υπογράφει και τα επιστρέφει μαζί με την υπογραφή. Η προδιαγραφή καλύπτει την πλήρη μορφή, συμπεριλαμβανομένης της παλαιότερης έκδοσης 0.
Επιλέξτε μέθοδο σύνδεσης
Δεν υποστηρίζουν όλα τα πορτοφόλια ακόμη μηνύματα εκτός αλυσίδας. Ελέγξτε τις δυνατότητες του συνδεδεμένου λογαριασμού πριν ζητήσετε υπογραφή και καταφύγετε σε εναλλακτική λύση όταν η δυνατότητα λείπει:
- Εάν ο λογαριασμός υποστηρίζει το
solana:signOffchainMessageμε έκδοση μηνύματος 1, υπογράψτε ένα μήνυμα εκτός αλυσίδας. - Διαφορετικά, υπογράψτε το ίδιο challenge με το
solana:signMessage. Τα περισσότερα πορτοφόλια λογισμικού το υποστηρίζουν. - Δώστε στους χρήστες πορτοφολιών υλικού χωρίς υποστήριξη μηνυμάτων μια ρητή επιλογή συμμετοχής, όπως έναν διακόπτη «Χρησιμοποιώ πορτοφόλι υλικού», που υπογράφει αντ' αυτού μια συναλλαγή σύνδεσης. Μην τους κατευθύνετε σε αυτή τη διαδρομή από προεπιλογή.
Υπογραφή μηνύματος στο πρόγραμμα περιήγησης
Εγκαταστήστε τα πακέτα Wallet Standard μαζί με το Kit:
$npm install @solana/kit @solana/wallet-standard-features @wallet-standard/ui @wallet-standard/ui-features @wallet-standard/ui-registry
Αυτές οι βοηθητικές συναρτήσεις δέχονται ένα UiWalletAccount, όπως το account που επιστρέφει το
useConnectedWallet στον οδηγό 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 επιστρέφει το signedOffchainMessage, τα πλήρη bytes που υπέγραψε
το πορτοφόλι, και το signature. Κωδικοποιήστε και τα δύο σε Base64 και στείλτε τα στον διακομιστή σας μαζί με
τη διεύθυνση του πορτοφολιού.
Γράψτε το challenge έτσι ώστε να διαβάζεται εύκολα σε μικρή οθόνη. Αναφέρετε την εφαρμογή και τον τομέα σας, εξηγήστε τι κάνει η υπογραφή και συμπεριλάβετε ένα nonce μίας χρήσης και μια ώρα έκδοσης από τον διακομιστή σας:
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
Επαληθεύστε την υπογραφή στον διακομιστή σας
Η επαλήθευση είναι ένας τοπικός έλεγχος υπογραφής και δεν απαιτεί σύνδεση RPC. Αποκωδικοποιήστε τα bytes που υπέγραψε το πορτοφόλι, επιβεβαιώστε ότι ταιριάζουν με το challenge που εκδώσατε και έπειτα ελέγξτε την υπογραφή:
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 του challenge, επομένως ο πελάτης
χρειάζεται να στείλει μόνο την υπογραφή και τη διεύθυνση του πορτοφολιού. Ελέγξτε την σε σχέση με το
challenge που εκδώσατε:
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)
τυποποιεί το ίδιο το challenge. Η εφαρμογή σας περνά δομημένα πεδία, όπως
domain, statement, nonce και issuedAt, στη δυνατότητα solana:signIn
του πορτοφολιού. Το πορτοφόλι τα μορφοποιεί σε ένα τυπικό μήνυμα, συνδέεται εάν χρειάζεται
και το υπογράφει σε ένα βήμα.
Η έκδοση 1.1.0 του solana:signIn προσθέτει την επιλογή useOffchainMessage. Όταν
περάσετε useOffchainMessage: { messageVersion: 1 }, το πορτοφόλι περιτυλίγει το κείμενο SIWS
σε ένα μήνυμα εκτός αλυσίδας έκδοσης 1 με τον λογαριασμό υπογραφής ως τον μοναδικό απαιτούμενο
υπογράφοντα. Τα πορτοφόλια υλικού μπορούν να το εμφανίσουν και να το υπογράψουν, και η έξοδος αναφέρει
signedMessageFormat: { kind: "offchainMessage", messageVersion: 1 }. Τα πορτοφόλια
που δηλώνουν την έκδοση 1.0.0 δεν γνωρίζουν την επιλογή, γι' αυτό ελέγξτε πρώτα την έκδοση της δυνατότητας
και παραλείψτε την σε αυτή την περίπτωση.
Το solana:signIn είναι δυνατότητα πορτοφολιού και όχι λογαριασμού, επομένως αυτές οι
βοηθητικές συναρτήσεις δέχονται ένα 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;}
Δημιουργήστε την είσοδο στον διακομιστή σας, με ένα nonce μίας χρήσης και μια ώρα issuedAt,
και κρατήστε ένα αντίγραφο με κλειδί το nonce. Καταναλώστε το nonce μόλις η σύνδεση
επαληθευτεί.
Χωρίς το Wallet Adapter, το verifySignIn από το @solana/wallet-standard-util
ελέγχει μόνο απλή έξοδο SIWS. Όταν το signedMessageFormat αναφέρει μήνυμα εκτός αλυσίδας,
το signedMessage είναι ένα μήνυμα εκτός αλυσίδας με περιεχόμενο το κείμενο SIWS.
Ανακατασκευάστε το αναμενόμενο κείμενο από την αποθηκευμένη είσοδο και τη διεύθυνση υπογραφής με
το createSignInMessageText από το @solana/wallet-standard-util και έπειτα επαληθεύστε το
όπως οποιοδήποτε άλλο μήνυμα εκτός αλυσίδας, όπως στην ενότητα
Επαληθεύστε την υπογραφή στον διακομιστή σας. Το
README του @solana/offchain-messages
καλύπτει αυτή τη ροή στο Kit.
Για πορτοφόλια
Τα πορτοφόλια προσθέτουν υποστήριξη υλοποιώντας τη δυνατότητα solana:signOffchainMessage του Wallet
Standard από το @solana/wallet-standard-features:
- Αναφέρετε
version: "1.0.0"και απαριθμήστε τις εκδόσεις μηνυμάτων που μπορείτε να υπογράψετε στοsupportedMessageVersions. Σήμερα, αυτή είναι η[1]. - Δημιουργήστε το preamble έκδοσης 1 από το
requiredSigners, το οποίο ταξινομείτε και ελέγχετε για διπλότυπα. Απορρίψτε το αίτημα, αν ο λογαριασμός δεν είναι ένας από τους απαιτούμενους υπογράφοντες. - Εμφανίστε στον χρήστη ολόκληρο το κείμενο του μηνύματος πριν από την υπογραφή, ακόμη και σε συσκευές hardware.
- Επιστρέψτε τα ακριβή byte που υπογράψατε ως
signedOffchainMessage, μαζί με την υπογραφήsignatureEd25519. - Για να υποστηρίξετε το off-chain Sign In With Solana, δηλώστε
version: "1.1.0"για τοsolana:signIn. Όταν η είσοδος περιλαμβάνει τοuseOffchainMessage, υπογράψτε το κείμενο SIWS ως off-chain μήνυμα έκδοσης 1, επιστρέψτε τα byte του ωςsignedMessageκαι ορίστε τοsignedMessageFormat. - Συνεχίστε να υποστηρίζετε το
solana:signMessage. Παραμένει αμετάβλητο και οι εφαρμογές εξακολουθούν να το χρησιμοποιούν ως εφεδρική λύση.
Is this page helpful?