Περίληψη
Τα PDA παράγονται με hashing των seeds + program ID + bump μέσω SHA-256 μέχρι το αποτέλεσμα να βρίσκεται εκτός της καμπύλης Ed25519. Το κανονικό bump είναι η πρώτη τιμή που παράγει μια διεύθυνση εκτός καμπύλης. Μέγιστο 16 seeds, μέγιστο 32 bytes ανά seed.
Ιστορικό
Οι τιμές
Keypair
του Solana είναι σημεία στην καμπύλη Ed25519. Ένα
keypair αποτελείται από ένα δημόσιο κλειδί (που χρησιμοποιείται ως διεύθυνση
λογαριασμού) και ένα μυστικό κλειδί (που χρησιμοποιείται για την παραγωγή
υπογραφών). Οποιοσδήποτε έχει το μυστικό κλειδί μπορεί να υπογράψει συναλλαγές
για αυτή τη διεύθυνση.
Δύο λογαριασμοί με διευθύνσεις επί της καμπύλης
Ένα PDA παράγεται σκόπιμα ώστε να βρίσκεται εκτός της καμπύλης Ed25519. Επειδή
δεν είναι έγκυρο σημείο της καμπύλης, δεν υπάρχει μυστικό κλειδί και κανένα
εξωτερικό μέρος δεν μπορεί να παράγει υπογραφή. Μόνο το πρόγραμμα παραγωγής
μπορεί να εξουσιοδοτήσει λειτουργίες στο PDA μέσω invoke_signed.
Διεύθυνση εκτός καμπύλης
Λογαριασμοί PDA έναντι keypair
| Ιδιότητα | Λογαριασμός keypair | Λογαριασμός PDA |
|---|---|---|
| Τύπος διεύθυνσης | Επί της καμπύλης Ed25519 | Εκτός της καμπύλης Ed25519 |
| Έχει ιδιωτικό κλειδί | Ναι | Όχι |
| Μπορεί να υπογράψει συναλλαγές | Ναι (με ιδιωτικό κλειδί) | Όχι |
| Μπορεί να υπογράψει κατά τη διάρκεια CPI | Όχι (εκτός αν η υπογραφή περιλαμβάνεται στη συναλλαγή) | Ναι (μέσω invoke_signed) |
| Παραγωγή | Δημιουργία keypair Ed25519 | Ντετερμινιστική από seeds + program ID |
| Τυπική χρήση | Πορτοφόλια χρηστών, program ID | Λογαριασμοί δεδομένων που ανήκουν σε πρόγραμμα |
Προαιρετικά seeds
Τα προαιρετικά seeds είναι byte strings που ορίζονται από τον χρήστη και
χρησιμεύουν ως εισροές για την παραγωγή PDA. Δημιουργούν μοναδικές,
ντετερμινιστικές διευθύνσεις που ανήκουν σε ένα πρόγραμμα. Για παράδειγμα, η
χρήση ["user", user_pubkey] ως seeds παράγει διαφορετική PDA για κάθε χρήστη.
Τα seeds πρέπει να ακολουθούν τους εξής περιορισμούς:
- Μέγιστο 16 seeds ανά παραγωγή (
MAX_SEEDS) - Μέγιστο 32 bytes ανά seed (
MAX_SEED_LEN)
Bump seed
Το bump seed είναι ένα μονό byte (0-255) που προσαρτάται στα προαιρετικά seeds
κατά την παραγωγή. Η
find_program_address
αναζητά από το 255 προς το 0, καλώντας την create_program_address με κάθε
τιμή μέχρι το αποτέλεσμα να βγει εκτός της καμπύλης Ed25519. Η πρώτη τιμή που
πετυχαίνει είναι το κανονικό bump.
Τα προγράμματα θα πρέπει πάντα να χρησιμοποιούν το κανονικό bump για να διασφαλίζουν μια μοναδική, ντετερμινιστική αντιστοίχιση από τα seeds στη διεύθυνση.
Χρησιμοποιείτε πάντα το κανονικό bump κατά την παραγωγή PDAs. Η χρήση μη κανονικού bump δημιουργεί μια δεύτερη έγκυρη διεύθυνση για τα ίδια seeds, κάτι που μπορεί να οδηγήσει σε ευπάθειες όπου ένας εισβολέας υποκαθιστά διαφορετικό λογαριασμό από τον αναμενόμενο.
PDA Derivation
Αλγόριθμος παραγωγής
Η παραγωγή PDA υλοποιείται στη συνάρτηση
create_program_address
του SDK. Ο αλγόριθμος λειτουργεί ως εξής:
- Επικυρώνει ότι ο αριθμός των seeds δεν υπερβαίνει το
MAX_SEEDS(16) και κανένα μεμονωμένο seed δεν υπερβαίνει τοMAX_SEED_LEN(32 bytes). Εάν αποτύχει κάποιος από τους ελέγχους, επιστρέφειPubkeyError::MaxSeedLengthExceeded. - Κάνει SHA-256 hash όλα τα seeds, το program ID και το string
"ProgramDerivedAddress"μαζί για να παράγει ένα αποτέλεσμα 32 bytes. - Ελέγχει αν το αποτέλεσμα είναι έγκυρο σημείο στην καμπύλη Ed25519.
- Εάν το αποτέλεσμα ΕΙΝΑΙ στην καμπύλη, επιστρέφει
PubkeyError::InvalidSeeds(η διεύθυνση θα είχε αντίστοιχο ιδιωτικό κλειδί, κάτι που παραβιάζει την ιδιότητα ασφαλείας του PDA). - Εάν το αποτέλεσμα ΔΕΝ ΕΙΝΑΙ στην καμπύλη, το επιστρέφει ως PDA.
Κόστος μονάδων υπολογισμού
Η
on-chain syscall
για create_program_address χρεώνει
1.500 CUs
ανά κλήση.
Η
try_find_program_address syscall
χρεώνει 1.500 CUs κατά την είσοδο (πριν από τον βρόχο), και στη συνέχεια
επιπλέον 1.500 CUs για κάθε αποτυχημένη προσπάθεια bump εντός του βρόχου.
Συνήθη μοτίβα seed
Τα seeds είναι εξειδικευμένα ανά εφαρμογή. Συνήθη μοτίβα περιλαμβάνουν:
| Μοτίβο | Seeds | Περίπτωση χρήσης |
|---|---|---|
| Global singleton | ["global"] | Μοναδικός λογαριασμός ρυθμίσεων σε επίπεδο προγράμματος |
| Per-user account | ["user", user_pubkey] | Ένας λογαριασμός ανά χρήστη ανά πρόγραμμα |
| Per-user-per-entity | ["vault", user_pubkey, mint_pubkey] | Token vaults, ανά χρήστη ανά token |
| Counter / sequential | ["order", user_pubkey, &order_id.to_le_bytes()] | Διαδοχικές εγγραφές ανά χρήστη |
Τα seeds συνενώνονται πριν από το hashing, επομένως τα ["ab", "cd"] και
["abcd"] παράγουν το ίδιο PDA. Χρησιμοποιήστε seeds σταθερού μήκους ή
διαχωριστικό για να αποφύγετε συγκρούσεις. Για παράδειγμα, το ["ab", "-", "cd"] είναι μονοσήμαντο.
Παραδείγματα: Παραγωγή ενός PDA
Η παραγωγή ενός PDA υπολογίζει μόνο μια διεύθυνση. Δεν δημιουργεί λογαριασμό
on-chain σε αυτή τη διεύθυνση. Ο λογαριασμός πρέπει να δημιουργηθεί ρητά μέσω
ξεχωριστής εντολής (συνήθως create_account μέσω CPI).
Τα Solana SDKs παρέχουν συναρτήσεις για την παραγωγή PDA. Κάθε συνάρτηση δέχεται:
- Program ID: Η διεύθυνση του προγράμματος που χρησιμοποιείται για την παραγωγή του PDA. Αυτό το πρόγραμμα μπορεί να υπογράψει εκ μέρους του PDA.
- Προαιρετικά seeds: Προκαθορισμένες εισόδους όπως strings, αριθμούς ή άλλες διευθύνσεις λογαριασμών.
| SDK | Συνάρτηση |
|---|---|
@solana/kit (TypeScript) | getProgramDerivedAddress |
@solana/web3.js (TypeScript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Τα παρακάτω παραδείγματα παράγουν ένα PDA χρησιμοποιώντας τα Solana SDKs. Κάντε κλικ στο ▷ Run για να εκτελέσετε τον κώδικα.
Παραγωγή ενός PDA με ένα string seed
Το παρακάτω παράδειγμα παράγει ένα PDA χρησιμοποιώντας ένα program ID και ένα προαιρετικό string seed.
import { Address, getProgramDerivedAddress } from "@solana/kit";const programAddress = "11111111111111111111111111111111" as Address;const seeds = ["helloWorld"];const [pda, bump] = await getProgramDerivedAddress({programAddress,seeds});console.log(`PDA: ${pda}`);console.log(`Bump: ${bump}`);
Παραγωγή ενός PDA με ένα address seed
Το παρακάτω παράδειγμα παράγει ένα PDA χρησιμοποιώντας ένα program ID και ένα προαιρετικό address seed.
import {Address,getAddressEncoder,getProgramDerivedAddress} from "@solana/kit";const programAddress = "11111111111111111111111111111111" as Address;const addressEncoder = getAddressEncoder();const optionalSeedAddress = addressEncoder.encode("B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka" as Address);const seeds = [optionalSeedAddress];const [pda, bump] = await getProgramDerivedAddress({programAddress,seeds});console.log(`PDA: ${pda}`);console.log(`Bump: ${bump}`);
Παραγωγή ενός PDA με πολλαπλά seeds
Το παρακάτω παράδειγμα παράγει ένα PDA χρησιμοποιώντας ένα program ID και πολλαπλά προαιρετικά seeds.
import {Address,getAddressEncoder,getProgramDerivedAddress} from "@solana/kit";const programAddress = "11111111111111111111111111111111" as Address;const optionalSeedString = "helloWorld";const addressEncoder = getAddressEncoder();const optionalSeedAddress = addressEncoder.encode("B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka" as Address);const seeds = [optionalSeedString, optionalSeedAddress];const [pda, bump] = await getProgramDerivedAddress({programAddress,seeds});console.log(`PDA: ${pda}`);console.log(`Bump: ${bump}`);
Επανάληψη όλων των bumps
Τα παρακάτω παραδείγματα δείχνουν την παραγωγή PDA χρησιμοποιώντας όλα τα πιθανά
bump seeds (255 έως 0), επεξηγώντας πώς το find_program_address επιστρέφει
το κανονικό bump:
Το παράδειγμα Kit δεν περιλαμβάνεται επειδή η συνάρτηση
createProgramDerivedAddress
δεν είναι exported.
import { PublicKey } from "@solana/web3.js";const programId = new PublicKey("11111111111111111111111111111111");const optionalSeed = "helloWorld";// Loop through all bump seeds (255 down to 0)for (let bump = 255; bump >= 0; bump--) {try {const PDA = PublicKey.createProgramAddressSync([Buffer.from(optionalSeed), Buffer.from([bump])],programId);console.log("bump " + bump + ": " + PDA);} catch (error) {console.log("bump " + bump + ": " + error);}}
bump 255: Error: Invalid seeds, address must fall off the curvebump 254: 46GZzzetjCURsdFPb7rcnspbEMnCBXe9kpjrsZAkKb6Xbump 253: GBNWBGxKmdcd7JrMnBdZke9Fumj9sir4rpbruwEGmR4ybump 252: THfBMgduMonjaNsCisKa7Qz2cBoG1VCUYHyso7UXYHHbump 251: EuRrNqJAofo7y3Jy6MGvF7eZAYegqYTwH2dnLCwDDGdPbump 250: Error: Invalid seeds, address must fall off the curve...// remaining bump outputs
Σε αυτό το παράδειγμα, το bump 255 παράγει μια διεύθυνση επί της καμπύλης και αποτυγχάνει. Το πρώτο έγκυρο bump είναι το 254, καθιστώντας το κανονικό bump.
Is this page helpful?