PDA導出

概要

PDA は、seed + プログラム ID + bump を SHA-256 でハッシュ化し、結果が Ed25519 曲線外になるまで導出されます。正規 bump は、曲線外アドレスを生成する最初の値です。最大 16 個の seed、seed あたり最大 32 バイト。

背景

Solana の Keypair 値は、Ed25519 曲線上の点です。keypair は、公開鍵(アカウントアドレスとして使用)と秘密鍵(署名の生成に使用)で構成されます。秘密鍵を持つ者は誰でも、そのアドレスのトランザクションに署名できます。

曲線上アドレスを持つ2つのアカウント曲線上アドレスを持つ2つのアカウント

PDA は意図的に Ed25519 曲線に導出されます。有効な曲線上の点ではないため、秘密鍵は存在せず、外部の当事者は署名を生成できません。導出プログラムのみが invoke_signed を通じて PDA の操作を承認できます。

曲線外アドレス曲線外アドレス

PDA と keypair アカウント

プロパティkeypair アカウントPDA アカウント
アドレスタイプEd25519 曲線上Ed25519 曲線外
秘密鍵の有無ありなし
トランザクションへの署名可能(秘密鍵使用)不可
CPI 中の署名不可(トランザクションに署名が含まれる場合を除く)可能(invoke_signed 経由)
導出Ed25519 keypair を生成seed + プログラム ID から決定論的
一般的な用途ユーザーウォレット、プログラム IDプログラム所有のデータアカウント

オプションのシード

オプションのシードは、PDA導出の入力として機能するユーザー定義のバイト文字列です。これらは、プログラムにスコープされた一意で決定論的なアドレスを作成します。たとえば、["user", user_pubkey]をシードとして使用すると、ユーザーごとに異なるPDAが導出されます。

シードは以下の制約に従う必要があります:

  • 1回の導出あたり最大16個のシード (MAX_SEEDS)
  • 1つのシードあたり最大32バイト (MAX_SEED_LEN)

バンプシード

バンプシードは、導出時にオプションのシードに追加される1バイト(0-255)です。 find_program_address は255から0まで検索し、各値で create_program_address を呼び出し、結果がEd25519曲線から外れるまで続けます。最初に成功した値が正規バンプです。

プログラムは、シードからアドレスへの一意で決定論的なマッピングを保証するために、常に正規バンプを使用する必要があります。

PDAを導出する際は、常に正規バンプを使用してください。非正規バンプを使用すると、同じシードに対して2つ目の有効なアドレスが作成され、攻撃者が予期しない別のアカウントに置き換える脆弱性につながる可能性があります。

PDA導出PDA導出

導出アルゴリズム

PDA導出は、SDKの create_program_address 関数で実装されています。アルゴリズムは以下のように動作します:

  1. シードの数が MAX_SEEDS (16)を超えず、個々のシードが MAX_SEED_LEN (32バイト)を超えないことを検証します。いずれかのチェックが失敗した場合、PubkeyError::MaxSeedLengthExceeded を返します。
  2. すべてのシード、プログラムID、および文字列 "ProgramDerivedAddress" を一緒にSHA-256ハッシュ化して、32バイトの結果を生成します。
  3. 結果がEd25519曲線上の有効な点であるかどうかを確認します。
  4. 結果が曲線上にある場合、PubkeyError::InvalidSeeds を返します(アドレスに対応する秘密鍵が存在することになり、PDAのセキュリティ特性に違反します)。
  5. 結果が曲線上にない場合、それをPDAとして返します。

コンピュートユニットコスト

*rscreate_program_address*のオンチェーンsyscallは、呼び出しごとに1,500 CUを消費します。

try_find_program_address syscallは、エントリ時(ループ前)に1,500 CUを消費し、ループ内でバンプ試行が失敗するたびに追加で1,500 CUを消費します。

一般的なseedパターン

Seedはアプリケーション固有です。一般的なパターンには以下が含まれます:

パターンSeedsユースケース
グローバルシングルトン["global"]プログラム全体で単一の設定アカウント
ユーザーごとのアカウント["user", user_pubkey]プログラムごとにユーザーごとに1つのアカウント
ユーザーごと・エンティティごと["vault", user_pubkey, mint_pubkey]トークンボールト、ユーザーごと・トークンごと
カウンター/連番["order", user_pubkey, &order_id.to_le_bytes()]ユーザーごとの連続レコード

Seedはハッシュ化前に連結されるため、["ab", "cd"]["abcd"]は同じPDAを生成します。衝突を避けるため、固定長のseedまたはセパレータを使用してください。例えば、["ab", "-", "cd"]は明確です。

例: PDAの導出

PDAの導出はアドレスのみを計算します。そのアドレスにオンチェーンアカウントを作成するわけではありません。アカウントは別の命令(通常はCPI経由のcreate_account)を通じて明示的に作成する必要があります。

Solana SDKはPDA導出用の関数を提供しています。各関数は以下を受け取ります:

  • プログラムID: PDAの導出に使用されるプログラムのアドレス。このプログラムはPDAの代わりに署名できます。
  • オプションのseeds: 文字列、数値、または他のアカウントアドレスなどの事前定義された入力。
SDK関数
@solana/kit (TypeScript)getProgramDerivedAddress
@solana/web3.js (TypeScript)findProgramAddressSync
solana_sdk (Rust)find_program_address

以下の例では、Solana SDKを使用してPDAを導出します。▷ 実行をクリックしてコードを実行してください。

文字列seedを使用したPDAの導出

以下の例では、プログラムIDとオプションの文字列seedを使用してPDAを導出します。

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.

アドレスseedを使用したPDAの導出

以下の例では、プログラムIDとオプションのアドレスseedを使用してPDAを導出します。

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.

複数のseedを使用したPDAの導出

以下の例では、プログラムIDと複数のオプションseedを使用してPDAを導出します。

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.

すべてのバンプの反復処理

以下の例では、すべての可能なバンプseed(255から0)を使用したPDA導出を示しており、find_program_address正規バンプを返す仕組みを説明しています。

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

この例では、バンプ255は曲線上のアドレスを生成するため失敗します。最初の有効なバンプは254であり、これが正規のバンプとなります。

Is this page helpful?

管理運営

© 2026 Solana Foundation.
無断転載を禁じます。
つながろう