Διεύθυνση Προερχόμενη από Πρόγραμμα

Μια διεύθυνση λογαριασμού της Solana δείχνει στην τοποθεσία του λογαριασμού στο blockchain. Πολλές διευθύνσεις λογαριασμών είναι το δημόσιο κλειδί ενός keypair, οπότε το αντίστοιχο ιδιωτικό κλειδί χρησιμοποιείται για την υπογραφή συναλλαγών που αφορούν τον λογαριασμό.

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

Το περιβάλλον εκτέλεσης της Solana επιτρέπει στα προγράμματα να υπογράφουν για PDAs χωρίς να χρειάζεται ιδιωτικό κλειδί. Η χρήση μιας PDA εξαλείφει την ανάγκη παρακολούθησης της διεύθυνσης του λογαριασμού. Αντίθετα, μπορείτε να ανακαλέσετε τις συγκεκριμένες εισόδους που χρησιμοποιήθηκαν για την παραγωγή της PDA. (Για να μάθετε πώς τα προγράμματα χρησιμοποιούν PDAs για υπογραφή, δείτε την ενότητα Διασταυρούμενες Κλήσεις Προγραμμάτων.)

Υπόβαθρο

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

Δύο λογαριασμοί με διευθύνσεις πάνω στην καμπύληΔύο λογαριασμοί με διευθύνσεις πάνω στην καμπύλη

Μια PDA σκόπιμα παράγεται ώστε να βρίσκεται εκτός της καμπύλης Ed25519. Αυτό σημαίνει ότι δεν έχει έγκυρο αντίστοιχο ιδιωτικό κλειδί και δεν μπορεί να εκτελέσει κρυπτογραφικές λειτουργίες. (Όπως η παροχή υπογραφής.) Ωστόσο, η Solana επιτρέπει στα προγράμματα να υπογράφουν για PDAs χωρίς να χρειάζεται ιδιωτικό κλειδί.

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

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

Program Derived AddressProgram 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Παραγωγή 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);
}
}
Console
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 προκαλεί σφάλμα. Το πρώτο 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}`);
Console
Click to execute the code.

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

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

Δημιουργία λογαριασμού PDA

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

Program
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 bumpd
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,
}

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

  • Η διεύθυνση του λογαριασμού χρήστη που παρέχεται στην εντολή
  • Η σταθερή συμβολοσειρά: "data"
  • Το κανονικό bump seed

Σε αυτό το παράδειγμα, ο περιορισμός bump δεν έχει αντιστοιχιστεί σε μια τιμή, οπότε το Anchor θα χρησιμοποιήσει το find_program_address για να παράγει το PDA και να βρει το 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>,

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

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

Test
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 program
const [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?

Πίνακας Περιεχομένων

Επεξεργασία Σελίδας

Διαχειρίζεται από

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