Διεύθυνση Προερχόμενη από Πρόγραμμα
Μια διεύθυνση λογαριασμού της Solana δείχνει στην τοποθεσία του λογαριασμού στο blockchain. Πολλές διευθύνσεις λογαριασμών είναι το δημόσιο κλειδί ενός keypair, οπότε το αντίστοιχο ιδιωτικό κλειδί χρησιμοποιείται για την υπογραφή συναλλαγών που αφορούν τον λογαριασμό.
Μια χρήσιμη εναλλακτική λύση στη διεύθυνση δημόσιου κλειδιού είναι μια διεύθυνση προερχόμενη από πρόγραμμα (PDA). Οι PDAs παρέχουν μια εύκολη μέθοδο για αποθήκευση, αντιστοίχιση και ανάκτηση της κατάστασης του προγράμματος. Μια PDA είναι μια διεύθυνση που δημιουργείται καθοριστικά χρησιμοποιώντας ένα αναγνωριστικό προγράμματος και έναν συνδυασμό προαιρετικών προκαθορισμένων εισόδων. Οι PDAs μοιάζουν με διευθύνσεις δημόσιου κλειδιού, αλλά δεν έχουν αντίστοιχο ιδιωτικό κλειδί.
Το περιβάλλον εκτέλεσης της Solana επιτρέπει στα προγράμματα να υπογράφουν για PDAs χωρίς να χρειάζεται ιδιωτικό κλειδί. Η χρήση μιας PDA εξαλείφει την ανάγκη παρακολούθησης της διεύθυνσης του λογαριασμού. Αντίθετα, μπορείτε να ανακαλέσετε τις συγκεκριμένες εισόδους που χρησιμοποιήθηκαν για την παραγωγή της PDA. (Για να μάθετε πώς τα προγράμματα χρησιμοποιούν PDAs για υπογραφή, δείτε την ενότητα Διασταυρούμενες Κλήσεις Προγραμμάτων.)
Υπόβαθρο
Τα keypairs της Solana είναι σημεία στην καμπύλη Ed25519 (κρυπτογραφία ελλειπτικής καμπύλης). Αποτελούνται από ένα δημόσιο κλειδί και ένα ιδιωτικό κλειδί. Το δημόσιο κλειδί γίνεται η διεύθυνση του λογαριασμού, και το ιδιωτικό κλειδί χρησιμοποιείται για τη δημιουργία έγκυρης υπογραφής για τον λογαριασμό.
Δύο λογαριασμοί με διευθύνσεις πάνω στην καμπύλη
Μια PDA σκόπιμα παράγεται ώστε να βρίσκεται εκτός της καμπύλης Ed25519. Αυτό σημαίνει ότι δεν έχει έγκυρο αντίστοιχο ιδιωτικό κλειδί και δεν μπορεί να εκτελέσει κρυπτογραφικές λειτουργίες. (Όπως η παροχή υπογραφής.) Ωστόσο, η Solana επιτρέπει στα προγράμματα να υπογράφουν για PDAs χωρίς να χρειάζεται ιδιωτικό κλειδί.
Διεύθυνση Εκτός Καμπύλης
Μπορείτε να σκεφτείτε τα PDAs ως έναν τρόπο δημιουργίας δομών τύπου hashmap στην αλυσίδα χρησιμοποιώντας ένα προκαθορισμένο σύνολο εισόδων. (Για παράδειγμα, συμβολοσειρές, αριθμούς και άλλες διευθύνσεις λογαριασμών.)
Program Derived Address
Παραγωγή ενός PDA
Πριν δημιουργήσετε έναν λογαριασμό με ένα PDA, πρέπει πρώτα να παράγετε τη διεύθυνση. Η παραγωγή ενός PDA δεν δημιουργεί αυτόματα έναν λογαριασμό στην αλυσίδα σε αυτή τη διεύθυνση— ο λογαριασμός πρέπει να δημιουργηθεί ρητά μέσω του προγράμματος που χρησιμοποιείται για την παραγωγή του PDA. Μπορείτε να σκεφτείτε ένα PDA σαν μια διεύθυνση σε έναν χάρτη: το γεγονός ότι υπάρχει μια διεύθυνση δεν σημαίνει ότι έχει κατασκευαστεί κάτι εκεί.
Τα Solana SDKs υποστηρίζουν τη δημιουργία PDA με τις συναρτήσεις που φαίνονται στον παρακάτω πίνακα. Κάθε συνάρτηση λαμβάνει τις ακόλουθες εισόδους:
- Program ID: Η διεύθυνση του προγράμματος που χρησιμοποιείται για την παραγωγή του PDA. Αυτό το πρόγραμμα μπορεί να υπογράψει εκ μέρους του PDA.
- Optional seeds: Προκαθορισμένες είσοδοι, όπως συμβολοσειρές, αριθμοί ή άλλες διευθύνσεις λογαριασμών.
| SDK | Συνάρτηση |
|---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Η συνάρτηση χρησιμοποιεί το program ID και τα προαιρετικά seeds, και στη συνέχεια επαναλαμβάνει τις τιμές bump για να προσπαθήσει να δημιουργήσει μια έγκυρη διεύθυνση προγράμματος. Η επανάληψη των τιμών bump ξεκινά από το 255 και μειώνεται κατά 1 μέχρι να βρεθεί ένα έγκυρο PDA. Αφού βρεθεί ένα έγκυρο PDA, η συνάρτηση επιστρέφει το PDA και το bump seed.
Το bump seed είναι ένα επιπλέον byte που προστίθεται στα προαιρετικά seeds για να διασφαλιστεί ότι δημιουργείται μια έγκυρη διεύθυνση εκτός καμπύλης.
Παραγωγή PDA
Κανονικό bump
Το bump seed είναι ένα επιπλέον byte που προστίθεται στα προαιρετικά seeds. Η συνάρτηση παραγωγής επαναλαμβάνει τις τιμές bump, ξεκινώντας από το 255 και μειώνοντας κατά 1, μέχρι μια τιμή να παράγει μια έγκυρη διεύθυνση εκτός καμπύλης. Η πρώτη τιμή που παράγει μια έγκυρη διεύθυνση εκτός καμπύλης ονομάζεται "κανονικό bump".
Τα παρακάτω παραδείγματα δείχνουν την παραγωγή PDA χρησιμοποιώντας όλα τα πιθανά bump seeds (255 έως 0):
Το παράδειγμα Kit δεν συμπεριλαμβάνεται επειδή η συνάρτηση createProgramDerivedAddress δεν εξάγεται.
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 seed προκαλεί σφάλμα. Το πρώτο bump seed που παράγει ένα έγκυρο PDA είναι το 254. Τα bump seeds 253-251 επίσης παράγουν μοναδικά, έγκυρα PDAs.
Αυτό σημαίνει ότι με τα ίδια προαιρετικά seeds και programId, ένα bump seed με
διαφορετική τιμή μπορεί ακόμα να παράγει ένα έγκυρο PDA.
Πάντα να συμπεριλαμβάνετε ελέγχους ασφαλείας για να διασφαλίσετε ότι ένα PDA που περνάει στο πρόγραμμα παράγεται από το κανονικό bump. Η αποτυχία να το κάνετε αυτό μπορεί να εισαγάγει ευπάθειες που επιτρέπουν σε μη αναμενόμενους λογαριασμούς να χρησιμοποιηθούν στις εντολές του προγράμματος. Είναι καλή πρακτική να χρησιμοποιείτε μόνο το κανονικό bump κατά την παραγωγή PDAs.
Παραδείγματα
Τα παρακάτω παραδείγματα παράγουν ένα 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 με seed διεύθυνσης
Το παρακάτω παράδειγμα παράγει ένα PDA χρησιμοποιώντας ένα αναγνωριστικό προγράμματος και ένα προαιρετικό 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 χρησιμοποιώντας ένα αναγνωριστικό προγράμματος και πολλαπλά προαιρετικά 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}`);
Δημιουργία λογαριασμού PDA
Το παρακάτω παράδειγμα χρησιμοποιεί το
Anchor framework για να δημιουργήσει έναν
νέο λογαριασμό με μια διεύθυνση που παράγεται από το πρόγραμμα. Το πρόγραμμα
περιλαμβάνει μια μοναδική εντολή initialize για τη
δημιουργία του νέου λογαριασμού, ο οποίος θα αποθηκεύσει τη
διεύθυνση χρήστη και το bump seed που
χρησιμοποιήθηκε για την παραγωγή του PDA.
use anchor_lang::prelude::*;declare_id!("75GJVCJNhaukaa2vCCqhreY31gaphv7XTScBChmr1ueR");#[program]pub mod pda_account {use super::*;pub fn initialize(ctx: Context<Initialize>) -> Result<()> {let account_data = &mut ctx.accounts.pda_account;// store the address of the `user`account_data.user = *ctx.accounts.user.key;// store the canonical bumpdaccount_data.bump = ctx.bumps.pda_account;Ok(())}}#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub user: Signer<'info>,#[account(init,// define the seeds to derive the PDAseeds = [b"data", user.key().as_ref()],// use the canonical bumpbump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,pub system_program: Program<'info, System>,}#[account]#[derive(InitSpace)]pub struct DataAccount {pub user: Pubkey,pub bump: u8,}
Ο περιορισμός init λέει στο Anchor να
καλέσει το System Program για να
δημιουργήσει έναν νέο λογαριασμό χρησιμοποιώντας το PDA ως διεύθυνση. Τα
seeds που χρησιμοποιούνται για τη δημιουργία του PDA είναι:
- Η διεύθυνση του λογαριασμού χρήστη που παρέχεται στην εντολή
- Η σταθερή συμβολοσειρά: "data"
- Το κανονικό bump seed
Σε αυτό το παράδειγμα, ο περιορισμός bump δεν έχει αντιστοιχιστεί σε μια τιμή,
οπότε το Anchor θα χρησιμοποιήσει το find_program_address για να παράγει το
PDA και να βρει το bump.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
Το παρακάτω αρχείο δοκιμής περιέχει μια συναλλαγή που καλεί την εντολή
initialize για να δημιουργήσει έναν νέο λογαριασμό με
μια διεύθυνση παραγόμενη από πρόγραμμα. Το αρχείο περιέχει κώδικα για
την παραγωγή του PDA.
Το παράδειγμα δείχνει επίσης πώς να ανακτήσετε το νέο λογαριασμό που θα δημιουργηθεί.
import * as anchor from "@coral-xyz/anchor";import { Program } from "@coral-xyz/anchor";import { PdaAccount } from "../target/types/pda_account";import { PublicKey } from "@solana/web3.js";describe("pda-account", () => {const provider = anchor.AnchorProvider.env();anchor.setProvider(provider);const program = anchor.workspace.PdaAccount as Program<PdaAccount>;const user = provider.wallet as anchor.Wallet;// Derive the PDA address using the seeds specified on the programconst [PDA] = PublicKey.findProgramAddressSync([Buffer.from("data"), user.publicKey.toBuffer()],program.programId);it("Is initialized!", async () => {const transactionSignature = await program.methods.initialize().accounts({user: user.publicKey}).rpc();console.log("Transaction Signature:", transactionSignature);});it("Fetch Account", async () => {const pdaAccount = await program.account.dataAccount.fetch(PDA);console.log(JSON.stringify(pdaAccount, null, 2));});});
Εάν καλέσετε ξανά την εντολή initialize με το ίδιο seed διεύθυνσης user, η
συναλλαγή θα αποτύχει. Αυτό συμβαίνει επειδή υπάρχει ήδη ένας λογαριασμός στην
παραγόμενη διεύθυνση.
Is this page helpful?