3

Going through the anchor-docs provided by the official Anchor github I've learned a fair bit about how PDAs work and manipulating them. Though I get a sneaking feeling like its extremely insecure and prone to exploits galore.

const [userStatsPDA, _] = await PublicKey.findProgramAddress(
      [
        anchor.utils.bytes.utf8.encode('user-stats'),
        provider.wallet.publicKey.toBuffer(),
      ],
      program.programId
    )

This line in particular "anchor.utils.bytes.utf8.encode('user-stats')" worries me in the fact its ran client side.

I'm running with Anchor 0.25.0, how secure is this client-side generation and is there a way to do it entirely contract side?

I want to pass as little data as possible from the client into the contract and especially not generate anything client side.

1
  • you allude to "security" and "exploits" but don't cite any specific concerns. please elaborate
    – trent.sol
    Commented Jul 25, 2022 at 18:41

2 Answers 2

4

It's safe because Anchor is going to be generating and checking the address using the seeds. It isn't trusting the input.

From the same doc (https://www.anchor-lang.com/docs/pdas), you have this Anchor code:

// validation struct
#[derive(Accounts)]
pub struct CreateUserStats<'info> {
    #[account(mut)]
    pub user: Signer<'info>,
    // space: 8 discriminator + 2 level + 4 name length + 200 name + 1 bump
    #[account(
        init,
        payer = user,
        space = 8 + 2 + 4 + 200 + 1,
        seeds = [b"user-stats", user.key().as_ref()],
        bump
    )]
    pub user_stats: Account<'info, UserStats>,
    pub system_program: Program<'info, System>,
}

Anchor will perform its own PDA calculation with the given seeds to find the user_stats address. If it doesn't match the one you've passed in then the transaction will be rejected. You can see this yourself - just try passing some other address in, or mess with the seeds so Anchor generates a different address.

Solana transactions must know about every account that's affected from the moment the transaction is sent. That's how the runtime is able to optimize things so it's non-negotiable. That's why we generate the PDA on the client side and pass it in. It's why we have to pass the system account in. But you don't write your program to trust the accounts that are passed in, you verify all of them. That's a lot of what Anchor is doing for us.

4

Maybe you're misinterpreting what findProgramAddress does. It simply finds the public key of the program derived address (PDA) with the given set of seeds.

The line you have issue with anchor.utils.bytes.utf8.encode('user-stats'), just converts the string 'user-stats' to an array of u8s. Another valid way of doing this would be Buffer.from('user-stats').

Only the program used in the PDA derivation, program.programId in the above example, can sign for the PDA. The program needs to sign to create the PDA account but after the account is created and ownership is assigned to particular program (not necessarily the same one used in derivation) that program now has control over what gets written to the account.

For example, associated token accounts are PDAs derived from the associated token program but the ownership of the PDA accounts is transferred to the SPL token program and from then onwards the SPL token program uses the owner public key on token account to determine who is allowed to update data on the account; transfer tokens, burn tokens, etc.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.