एक program derived address (PDA) एक ऐसा अकाउंट होता है जिसका एड्रेस उसे बनाने वाले प्रोग्राम के एड्रेस और init ट्रांजैक्शन में पास किए गए seeds से लिया (derive) जाता है। अब तक, हमने केवल PDAs का उपयोग किया है।
प्रोग्राम के बाहर अकाउंट बनाना और फिर उस अकाउंट को प्रोग्राम के अंदर init करना भी संभव है।
दिलचस्प बात यह है कि जो अकाउंट हम प्रोग्राम के बाहर बनाते हैं, उसकी एक private key होगी, लेकिन हम देखेंगे कि इसके वे सुरक्षा प्रभाव (security implications) नहीं होंगे जो प्रतीत होते हैं। हम इसे एक “keypair account” कहेंगे।
Account Creation पर एक नज़र
Keypair accounts में जाने से पहले, आइए देखें कि हम अब तक अपने Solana tutorials में अकाउंट कैसे बनाते आए हैं। यह वही boilerplate है जिसका हम उपयोग करते आ रहे हैं, और यह program-derived addresses (PDA) बनाता है:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("4wLnxvLwgXGT4eNg3D456K6Fxa1RieaUdERSPQ3WEpuV");
#[program]
pub mod keypair_vs_pda {
use super::*;
pub fn initialize_pda(ctx: Context<InitializePDA>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct InitializePDA<'info> {
// This is the program derived address
#[account(init,
payer = signer,
space=size_of::<MyPDA>() + 8,
seeds = [],
bump)]
pub my_pda: Account<'info, MyPDA>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyPDA {
x: u64,
}
initialize को कॉल करने के लिए संबंधित Typescript कोड निम्नलिखित है:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { KeypairVsPda } from "../target/types/keypair_vs_pda";
describe("keypair_vs_pda", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.KeypairVsPda as Program<KeypairVsPda>;
it("Is initialized -- PDA version", async () => {
const seeds = [];
const [myPda, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
console.log("the storage account address is", myPda.toBase58());
const tx = await program.methods.initializePda().accounts({myPda: myPda}).rpc();
});
});
अब तक यह सब परिचित होना चाहिए, सिवाय इसके कि हमने स्पष्ट रूप से अपने अकाउंट्स को “PDA” कहा है।
Program Derived Address
एक अकाउंट Program Derived Address (PDA) होता है यदि अकाउंट का एड्रेस प्रोग्राम के एड्रेस से लिया (derive) गया हो, यानी findProgramAddressSync(seeds, program.programId) में programId। यह seeds का भी एक फंक्शन है।
विशेष रूप से, हम जानते हैं कि यह एक PDA है क्योंकि init मैक्रो में seeds और bump मौजूद हैं।
Keypair Account
निम्नलिखित कोड ऊपर दिए गए कोड के काफी समान दिखेगा, लेकिन इस बात पर ध्यान दें कि init मैक्रो में seeds और bump की कमी है:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("4wLnxvLwgXGT4eNg3D456K6Fxa1RieaUdERSPQ3WEpuV");
#[program]
pub mod keypair_vs_pda {
use super::*;
pub fn initialize_keypair_account(ctx: Context<InitializeKeypairAccount>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct InitializeKeypairAccount<'info> {
// This is the keypair account
#[account(init,
payer = signer,
space = size_of::<MyKeypairAccount>() + 8,)]
pub my_keypair_account: Account<'info, MyKeypairAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyKeypairAccount {
x: u64,
}
जब seeds और bump अनुपस्थित होते हैं, Anchor प्रोग्राम अब यह अपेक्षा कर रहा है कि हम पहले एक अकाउंट बनाएं, और फिर उस अकाउंट को प्रोग्राम में पास करें। चूंकि हम अकाउंट खुद बनाते हैं, इसलिए इसका एड्रेस प्रोग्राम के एड्रेस से “derived from” (लिया गया) नहीं होगा। दूसरे शब्दों में, यह एक program derived account (PDA) नहीं होगा।
प्रोग्राम के लिए अकाउंट बनाना उतना ही सरल है जितना कि एक नया keypair जनरेट करना (उसी तरीके से जिसका उपयोग हमने test different signers in Anchor के लिए किया था)। हां, यह थोड़ा डरावना लग सकता है कि हम उस अकाउंट की secret key अपने पास रखते हैं जिसका उपयोग प्रोग्राम डेटा स्टोर करने के लिए कर रहा है — हम थोड़ी देर में इस पर दोबारा विचार करेंगे। अभी के लिए, यहां एक नया अकाउंट बनाने और उसे ऊपर दिए गए प्रोग्राम में पास करने के लिए Typescript कोड दिया गया है। हम आगे महत्वपूर्ण भागों की ओर ध्यान आकर्षित करेंगे:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { KeypairVsPda } from "../target/types/keypair_vs_pda";
// this airdrops sol to an address
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor.getProvider().connection.requestAirdrop(publicKey, amount * anchor.web3.LAMPORTS_PER_SOL);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx) {
const latestBlockHash = await anchor.getProvider().connection.getLatestBlockhash();
await anchor.getProvider().connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
describe("keypair_vs_pda", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.KeypairVsPda as Program<KeypairVsPda>;
it("Is initialized -- keypair version", async () => {
const newKeypair = anchor.web3.Keypair.generate();
await airdropSol(newKeypair.publicKey, 1e9); // 1 SOL
console.log("the keypair account address is", newKeypair.publicKey.toBase58());
await program.methods.initializeKeypairAccount()
.accounts({myKeypairAccount: newKeypair.publicKey})
.signers([newKeypair]) // the signer must be the keypair
.rpc();
});
});
कुछ बातें जिनकी ओर हम ध्यान आकर्षित करना चाहते हैं:
- हमने अपने द्वारा बनाए गए नए keypair
newKeypairपर SOL एयरड्रॉप करने के लिए एक यूटिलिटी फंक्शनairdropSolजोड़ा है। SOL के बिना, यह ट्रांजैक्शन का भुगतान करने में सक्षम नहीं होगा। इसके अतिरिक्त, चूंकि यह वही अकाउंट है जिसका उपयोग डेटा स्टोर करने के लिए किया जाएगा, इसलिए इसे rent exempt होने के लिए एक SOL बैलेंस की आवश्यकता होती है। SOL एयरड्रॉप करते समय, एक अतिरिक्तconfirmTransactionरूटीन की आवश्यकता होती है क्योंकि रनटाइम में इस बात को लेकर रेस कंडीशन हो सकती है कि SOL वास्तव में कब एयरड्रॉप होता है और ट्रांजैक्शन कब कन्फर्म होता है। - हमने
signersको डिफ़ॉल्ट सेnewKeypairमें बदल दिया है। Keypair account बनाते समय, आप ऐसा अकाउंट नहीं बना सकते जिसकी private key आपके पास न हो।
ऐसे keypair account को initialize करना संभव नहीं है जिसकी private key आपके पास न हो
यदि आप किसी भी मनचाहे (arbitrary) एड्रेस के साथ एक अकाउंट बना सकते हैं, तो यह एक बड़ा सुरक्षा जोखिम (security risk) होगा क्योंकि आप किसी भी मनचाहे अकाउंट में दुर्भावनापूर्ण (malicious) डेटा डाल सकते हैं।
Exercise: एक दूसरा keypair secondKeypair जनरेट करने के लिए टेस्ट को संशोधित करें। दूसरे keypair की public key का उपयोग करें और .accounts({myKeypairAccount: newKeypair.publicKey}) को .accounts({myKeypairAccount: secondKeypair.publicKey}) से बदलें। Signer को न बदलें। आपको टेस्ट फेल होता हुआ दिखाई देगा। आपको नए keypair में SOL एयरड्रॉप करने की आवश्यकता नहीं है क्योंकि यह ट्रांजैक्शन का signer नहीं है।
आपको निम्नलिखित जैसा एरर दिखाई देना चाहिए:

क्या होगा यदि हम PDA के एड्रेस को फर्जी (fake) बनाने की कोशिश करें?
Exercise: ऊपर दी गई एक्सरसाइज से secondKeypair पास करने के बजाय, इसके साथ एक PDA डिराइव करें:
const seeds = [];
const [pda, _bump] = anchor
.web3
.PublicKey
.findProgramAddressSync(
seeds,
program.programId);
फिर myKeypairAccount आर्गुमेंट को .accounts({myKeypairAccount: pda}) से बदल दें।
आपको फिर से एक unknown signer एरर दिखाई देना चाहिए।
Solana रनटाइम आपको ऐसा नहीं करने देगा। यदि किसी प्रोग्राम में बिना initialize किए अचानक से PDAs प्रकट होने लगे, तो इससे गंभीर सुरक्षा समस्याएं पैदा होंगी।
क्या यह कोई समस्या है कि किसी के पास private key वाला अकाउंट है?
ऐसा लग सकता है कि जिसके पास private key है, वह अकाउंट से SOL खर्च करने में सक्षम होगा, और संभवतः इसे rent-exempt सीमा से नीचे ला सकता है। हालांकि, जब अकाउंट को किसी प्रोग्राम द्वारा initialize किया जाता है, तो Solana रनटाइम ऐसा होने से रोकता है।
इसे देखने के लिए, निम्नलिखित यूनिट टेस्ट पर विचार करें:
- Typescript में एक keypair account बनाएं
- Keypair account में sol एयरड्रॉप करें
- Keypair account से दूसरे एड्रेस पर sol ट्रांसफर करें (सफल होता है)
- Keypair account को initialize करें
- Signer के रूप में keypair का उपयोग करके keypair account से sol ट्रांसफर करने का प्रयास करें (फेल होता है)
कोड नीचे दिखाया गया है:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { KeypairVsPda } from "../target/types/keypair_vs_pda";
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor.getProvider().connection.requestAirdrop(publicKey, amount * anchor.web3.LAMPORTS_PER_SOL);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx) {
const latestBlockHash = await anchor.getProvider().connection.getLatestBlockhash();
await anchor.getProvider().connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
describe("keypair_vs_pda", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.KeypairVsPda as Program<KeypairVsPda>;
it("Writing to keypair account fails", async () => {
const newKeypair = anchor.web3.Keypair.generate();
var receiverWallet = anchor.web3.Keypair.generate();
await airdropSol(newKeypair.publicKey, 10);
var transaction = new anchor.web3.Transaction().add(
anchor.web3.SystemProgram.transfer({
fromPubkey: newKeypair.publicKey,
toPubkey: receiverWallet.publicKey,
lamports: 1 * anchor.web3.LAMPORTS_PER_SOL,
}),
);
await anchor.web3.sendAndConfirmTransaction(anchor.getProvider().connection, transaction, [newKeypair]);
console.log('sent 1 SOL');
await program.methods.initializeKeypairAccount()
.accounts({myKeypairAccount: newKeypair.publicKey})
.signers([newKeypair]) // the signer must be the keypair
.rpc();
console.log("initialized");
// try to transfer again, this fails
var transaction = new anchor.web3.Transaction().add(
anchor.web3.SystemProgram.transfer({
fromPubkey: newKeypair.publicKey,
toPubkey: receiverWallet.publicKey,
lamports: 1 * anchor.web3.LAMPORTS_PER_SOL,
}),
);
await anchor.web3.sendAndConfirmTransaction(anchor.getProvider().connection, transaction, [newKeypair]);
});
});
यहाँ अपेक्षित एरर मैसेज दिया गया है:

भले ही हमारे पास इस अकाउंट की private keys हों, लेकिन हम अकाउंट से “SOL खर्च” नहीं कर सकते क्योंकि अब इसका स्वामित्व (ownership) प्रोग्राम के पास है।
Ownership और initialization का परिचय
Solana रनटाइम को कैसे पता चलता है कि initialization के बाद SOL के ट्रांसफर को ब्लॉक करना है?
Exercise: टेस्ट को नीचे दिए गए कोड में संशोधित करें। जोड़े गए console log स्टेटमेंट्स पर ध्यान दें। वे अकाउंट्स में “owner” मेटाडेटा फ़ील्ड और प्रोग्राम के एड्रेस को लॉग आउट कर रहे हैं:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { KeypairVsPda } from "../target/types/keypair_vs_pda";
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor.getProvider().connection.requestAirdrop(publicKey, amount * anchor.web3.LAMPORTS_PER_SOL);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx) {
const latestBlockHash = await anchor.getProvider().connection.getLatestBlockhash();
await anchor.getProvider().connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
describe("keypair_vs_pda", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.KeypairVsPda as Program<KeypairVsPda>;
it("Console log account owner", async () => {
console.log(`The program address is ${program.programId}`);
const newKeypair = anchor.web3.Keypair.generate();
// get account owner before initialization
await airdropSol(newKeypair.publicKey, 10);
const accountInfoBefore = await anchor.getProvider().connection.getAccountInfo(newKeypair.publicKey);
console.log(`initial keypair account owner is ${accountInfoBefore.owner}`);
await program.methods.initializeKeypairAccount()
.accounts({myKeypairAccount: newKeypair.publicKey})
.signers([newKeypair]) // the signer must be the keypair
.rpc();
// get account owner after initialization
const accountInfoAfter = await anchor.getProvider().connection.getAccountInfo(newKeypair.publicKey);
console.log(`initial keypair account owner is ${accountInfoAfter.owner}`);
});
});
नीचे दिया गया स्क्रीनशॉट अपेक्षित परिणाम दिखाता है:

Initialization के बाद, keypair account का owner 111...111 से बदलकर डिप्लॉय किए गए प्रोग्राम में बदल गया। हमने अभी तक अपने Solana tutorials में account ownership या system program (सभी वन्स का एड्रेस) के महत्व को गहराई से कवर नहीं किया है। हालांकि, इससे आपको यह विचार मिल जाना चाहिए कि “initialization” क्या कर रहा है और private key का owner अब अकाउंट से SOL ट्रांसफर क्यों नहीं कर सकता है।
क्या मुझे PDAs या Keypair accounts का उपयोग करना चाहिए?
एक बार अकाउंट initialize हो जाने के बाद, वे उसी तरह व्यवहार करते हैं, इसलिए व्यावहारिक रूप से कोई खास अंतर नहीं है।
एकमात्र महत्वपूर्ण अंतर (जो अधिकांश एप्लिकेशन्स को प्रभावित नहीं करेगा) यह है कि PDAs को केवल 10,240 बाइट्स के आकार के साथ initialize किया जा सकता है, लेकिन एक keypair account को पूरे 10 MB के आकार तक initialize किया जा सकता है। हालांकि, एक PDA का आकार (resize) 10 MB की सीमा तक बढ़ाया जा सकता है।
अधिकांश एप्लिकेशन PDAs का उपयोग करते हैं क्योंकि उन्हें seeds पैरामीटर के माध्यम से प्रोग्रामेटिक रूप से संबोधित (addressed) किया जा सकता है, लेकिन एक keypair account तक पहुंचने के लिए आपको पहले से ही एड्रेस पता होना चाहिए। हमने keypair accounts की चर्चा को शामिल किया है क्योंकि ऑनलाइन कई ट्यूटोरियल उन्हें उदाहरण के रूप में उपयोग करते हैं, इसलिए हम चाहते हैं कि आपके पास कुछ संदर्भ (context) हो। हालांकि, व्यवहार में डेटा स्टोर करने के लिए PDAs पसंदीदा तरीका है।
RareSkills के साथ और जानें
सीखना जारी रखने के लिए हमारे Solana course के साथ आगे बढ़ें!
मूल रूप से 6 मार्च, 2024 को प्रकाशित