Program Derived Address (PDA)

Οι Program Derived Addresses (PDAs) παρέχουν στους προγραμματιστές στο Solana δύο κύριες περιπτώσεις χρήσης:

  • Ντετερμινιστικές διευθύνσεις λογαριασμών: Οι PDAs παρέχουν έναν μηχανισμό για ντετερμινιστική δημιουργία μιας διεύθυνσης χρησιμοποιώντας έναν συνδυασμό προαιρετικών "seeds" (προκαθορισμένων εισόδων) και ενός συγκεκριμένου αναγνωριστικού προγράμματος.
  • Ενεργοποίηση υπογραφής προγράμματος: Το περιβάλλον εκτέλεσης του Solana επιτρέπει στα προγράμματα να "υπογράφουν" για PDAs που προέρχονται από τη διεύθυνση του προγράμματος.

Μπορείτε να σκεφτείτε τις PDAs ως έναν τρόπο δημιουργίας δομών τύπου hashmap στην αλυσίδα από ένα προκαθορισμένο σύνολο εισόδων (π.χ. συμβολοσειρές, αριθμούς και άλλες διευθύνσεις λογαριασμών).

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

Program Derived AddressProgram Derived Address

Είναι σημαντικό να κατανοήσετε ότι η απλή παραγωγή μιας Program Derived Address (PDA) δεν δημιουργεί αυτόματα έναν λογαριασμό στην αλυσίδα σε αυτή τη διεύθυνση. Οι λογαριασμοί με PDA ως διεύθυνση στην αλυσίδα πρέπει να δημιουργηθούν ρητά μέσω του προγράμματος που χρησιμοποιείται για την παραγωγή της διεύθυνσης. Μπορείτε να σκεφτείτε την παραγωγή μιας PDA ως εύρεση μιας διεύθυνσης σε έναν χάρτη. Το να έχετε απλώς μια διεύθυνση δεν σημαίνει ότι υπάρχει κάτι κατασκευασμένο σε αυτή την τοποθεσία.

Αυτή η ενότητα καλύπτει τις λεπτομέρειες της παραγωγής PDAs. Η ενότητα σχετικά με τις Cross Program Invocations (CPIs) εξηγεί πώς τα προγράμματα χρησιμοποιούν τις PDAs για υπογραφή.

Βασικά σημεία

  • Οι PDAs είναι διευθύνσεις που παράγονται ντετερμινιστικά χρησιμοποιώντας έναν συνδυασμό προκαθορισμένων seeds, ένα bump seed και το αναγνωριστικό ενός προγράμματος.
  • Οι PDAs είναι διευθύνσεις που βρίσκονται εκτός της καμπύλης Ed25519 και δεν έχουν αντίστοιχο ιδιωτικό κλειδί.
  • Τα προγράμματα Solana μπορούν να υπογράφουν εκ μέρους των PDAs που προέρχονται από το αναγνωριστικό του προγράμματός τους.
  • Η παραγωγή μιας PDA δεν δημιουργεί αυτόματα έναν λογαριασμό στην αλυσίδα.
  • Ένας λογαριασμός που χρησιμοποιεί μια PDA ως διεύθυνσή του πρέπει να δημιουργηθεί μέσω μιας εντολής εντός ενός προγράμματος Solana.

Τι είναι ένα PDA

Τα PDAs είναι διευθύνσεις που προκύπτουν με καθοριστικό τρόπο και μοιάζουν με δημόσια κλειδιά, αλλά δεν έχουν ιδιωτικά κλειδιά. Αυτό σημαίνει ότι δεν είναι δυνατή η δημιουργία έγκυρης υπογραφής για τη διεύθυνση. Ωστόσο, το περιβάλλον εκτέλεσης του Solana επιτρέπει στα προγράμματα να "υπογράφουν" για PDAs χωρίς να χρειάζονται ιδιωτικό κλειδί.

Για πλαίσιο, τα Solana Keypairs είναι σημεία στην καμπύλη Ed25519 (κρυπτογραφία ελλειπτικής καμπύλης) με ένα δημόσιο κλειδί και το αντίστοιχο ιδιωτικό κλειδί. Τα δημόσια κλειδιά χρησιμοποιούνται ως διευθύνσεις (μοναδικά αναγνωριστικά) για λογαριασμούς στην αλυσίδα.

Διεύθυνση Πάνω στην ΚαμπύληΔιεύθυνση Πάνω στην Καμπύλη

Ένα PDA είναι ένα σημείο που σκόπιμα προκύπτει ώστε να βρίσκεται εκτός της καμπύλης Ed25519 χρησιμοποιώντας ένα προκαθορισμένο σύνολο εισόδων. Ένα σημείο που δεν βρίσκεται στην καμπύλη Ed25519 δεν έχει έγκυρο αντίστοιχο ιδιωτικό κλειδί και δεν μπορεί να εκτελέσει κρυπτογραφικές λειτουργίες (υπογραφή).

Ένα PDA μπορεί να χρησιμεύσει ως διεύθυνση (μοναδικό αναγνωριστικό) για έναν λογαριασμό στην αλυσίδα, παρέχοντας μια μέθοδο για εύκολη αποθήκευση, αντιστοίχιση και ανάκτηση της κατάστασης του προγράμματος.

Διεύθυνση Εκτός ΚαμπύληςΔιεύθυνση Εκτός Καμπύλης

Πώς να παράγετε ένα PDA

Η παραγωγή ενός PDA απαιτεί τρεις εισόδους:

  • Προαιρετικά seeds: Προκαθορισμένες είσοδοι (π.χ. συμβολοσειρές, αριθμοί, άλλες διευθύνσεις λογαριασμών) για την παραγωγή PDA.
  • Bump seed: Ένα επιπλέον byte που προστίθεται στα προαιρετικά seeds για να διασφαλιστεί ότι παράγεται ένα έγκυρο PDA (εκτός καμπύλης). Το bump seed ξεκινά από το 255 και μειώνεται κατά 1 μέχρι να βρεθεί ένα έγκυρο PDA.
  • Program ID: Η διεύθυνση του προγράμματος από το οποίο παράγεται το PDA. Αυτό το πρόγραμμα μπορεί να υπογράψει εκ μέρους του PDA.

Παραγωγή PDAΠαραγωγή PDA

Χρησιμοποιήστε τις ακόλουθες συναρτήσεις από τα αντίστοιχα SDKs για να παράγετε ένα PDA.

SDKΛειτουργία
@solana/kit (Typescript)getProgramDerivedAddress
@solana/web3.js (Typescript)findProgramAddressSync
solana_sdk (Rust)find_program_address

Για να παράγετε ένα PDA, παρέχετε τις ακόλουθες εισόδους στη λειτουργία του SDK:

  • Τα προκαθορισμένα προαιρετικά seeds μετατρεμμένα σε bytes
  • Το αναγνωριστικό προγράμματος (διεύθυνση) που χρησιμοποιείται για την παραγωγή

Μόλις βρεθεί ένα έγκυρο PDA, η λειτουργία επιστρέφει τόσο τη διεύθυνση (PDA) όσο και το bump seed που χρησιμοποιήθηκε για την παραγωγή.

Παραδείγματα

Τα ακόλουθα παραδείγματα δείχνουν πώς να παράγετε ένα PDA χρησιμοποιώντας τα αντίστοιχα SDKs.

Κάντε κλικ στο κουμπί "Εκτέλεση" για να εκτελέσετε τον κώδικα.

Παραγωγή PDA με προαιρετικό 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}`);
Click to execute the code.

Παραγωγή 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}`);
Click to execute the code.

Παραγωγή 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}`);
Click to execute the code.

Κανονικό Bump

Η παραγωγή PDA απαιτεί ένα "bump seed", ένα επιπλέον byte που προστίθεται στα προαιρετικά seeds. Η συνάρτηση παραγωγής επαναλαμβάνει τις τιμές bump, ξεκινώντας από το 255 και μειώνοντας κατά 1, μέχρι μια τιμή να παράγει μια έγκυρη διεύθυνση εκτός καμπύλης. Η πρώτη τιμή bump που παράγει μια έγκυρη διεύθυνση εκτός καμπύλης είναι το "canonical 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);
}
}
Click to execute the code.
bump 255: Error: Invalid seeds, address must fall off the curve
bump 254: 46GZzzetjCURsdFPb7rcnspbEMnCBXe9kpjrsZAkKb6X
bump 253: GBNWBGxKmdcd7JrMnBdZke9Fumj9sir4rpbruwEGmR4y
bump 252: THfBMgduMonjaNsCisKa7Qz2cBoG1VCUYHyso7UXYHH
bump 251: EuRrNqJAofo7y3Jy6MGvF7eZAYegqYTwH2dnLCwDDGdP
bump 250: Error: Invalid seeds, address must fall off the curve
...
// remaining bump outputs

Το bump seed 255 προκαλεί σφάλμα και το πρώτο bump seed που παράγει ένα έγκυρο PDA είναι το 254.

Σημειώστε ότι τα bump seeds 253-251 παράγουν όλα έγκυρα PDAs με διαφορετικές διευθύνσεις. Αυτό σημαίνει ότι με τα ίδια προαιρετικά seeds και program_id, ένα bump seed με διαφορετική τιμή μπορεί ακόμα να παράγει ένα έγκυρο PDA.

Κατά την ανάπτυξη προγραμμάτων Solana, να συμπεριλαμβάνετε πάντα ελέγχους ασφαλείας για να διασφαλίσετε ότι ένα PDA που περνάει στο πρόγραμμα προέρχεται από το κανονικό bump. Η αποτυχία συμπερίληψης αυτών των ελέγχων μπορεί να εισαγάγει ευπάθειες που επιτρέπουν σε μη αναμενόμενους λογαριασμούς να χρησιμοποιηθούν στις εντολές του προγράμματος. Είναι καλή πρακτική να χρησιμοποιείτε μόνο το κανονικό bump κατά την παραγωγή PDAs.

Δημιουργία Λογαριασμών PDA

Το παρακάτω παράδειγμα προγράμματος δείχνει πώς να δημιουργήσετε έναν λογαριασμό χρησιμοποιώντας ένα PDA ως διεύθυνση του νέου λογαριασμού. Το παράδειγμα προγράμματος χρησιμοποιεί το Anchor framework.

Το πρόγραμμα περιλαμβάνει μια μοναδική initialize εντολή για τη δημιουργία ενός νέου λογαριασμού χρησιμοποιώντας ένα PDA ως διεύθυνση του λογαριασμού. Ο νέος λογαριασμός αποθηκεύει τη διεύθυνση του payer και το 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 bump
account_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 PDA
seeds = [b"data", user.key().as_ref()],
// use the canonical bump
bump,
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,
}

Σε αυτό το παράδειγμα, τα seeds για την παραγωγή PDA περιλαμβάνουν το σταθερό string data και τη διεύθυνση του λογαριασμού user που παρέχεται στην εντολή. Το πλαίσιο Anchor βρίσκει αυτόματα το κανονικό seed bump.

pda_account
#[account(
init,
seeds = [b"data", user.key().as_ref()],
bump,
payer = user,
space = 8 + DataAccount::INIT_SPACE
)]
pub pda_account: Account<'info, DataAccount>,

Ο περιορισμός init δίνει οδηγίες στο Anchor να καλέσει το System Program για να δημιουργήσει έναν νέο λογαριασμό χρησιμοποιώντας το PDA ως διεύθυνση. Το Anchor το κάνει αυτό μέσω ενός CPI.

pda_account
#[account(
init,
seeds = [b"data", user.key().as_ref()],
bump,
payer = user,
space = 8 + DataAccount::INIT_SPACE
)]
pub pda_account: Account<'info, DataAccount>,

Το αρχείο δοκιμής περιέχει τον κώδικα Typescript για την παραγωγή του PDA.

Derive PDA
const [PDA] = PublicKey.findProgramAddressSync(
[Buffer.from("data"), user.publicKey.toBuffer()],
program.programId
);

Η συναλλαγή στο αρχείο δοκιμής καλεί την εντολή initialize για να δημιουργήσει ένα νέο λογαριασμό on-chain χρησιμοποιώντας το PDA ως διεύθυνση. Σε αυτό το παράδειγμα, το Anchor μπορεί να συμπεράνει τη διεύθυνση PDA στους λογαριασμούς εντολών, οπότε δεν χρειάζεται να παρέχεται ρητά.

Invoke Initialize Instruction
it("Is initialized!", async () => {
const transactionSignature = await program.methods
.initialize()
.accounts({
user: user.publicKey
})
.rpc();
console.log("Transaction Signature:", transactionSignature);
});

Το αρχείο δοκιμής δείχνει επίσης πώς να ανακτήσετε τον λογαριασμό on-chain που δημιουργήθηκε σε αυτή τη διεύθυνση μόλις αποσταλεί η συναλλαγή.

Fetch Account
it("Fetch Account", async () => {
const pdaAccount = await program.account.dataAccount.fetch(PDA);
console.log(JSON.stringify(pdaAccount, null, 2));
});

Σημειώστε ότι σε αυτό το παράδειγμα, αν καλέσετε την εντολή initialize περισσότερες από μία φορές χρησιμοποιώντας την ίδια διεύθυνση user ως seed, τότε η συναλλαγή αποτυγχάνει. Αυτό συμβαίνει επειδή υπάρχει ήδη ένας λογαριασμός στην παραγόμενη διεύθυνση.

Is this page helpful?